HomeCSS Tailwind
Chapter 12
12 — Build Tools & Workflow
Package Managers
npm
# Initialize
npm init -y
# Install
npm install lodash
npm install -D tailwindcss # Dev dependency
# Scripts
npm run build
npm run dev
# Update
npm update lodash
npm outdated # Check outdated packagesyarn (v1 Classic)
# Initialize
yarn init -y
# Install
yarn add lodash
yarn add -D tailwindcss
# Scripts
yarn build
yarn dev
# Why yarn? Faster installs, better offline cachepnpm (Recommended)
# Install
npm install -g pnpm
# Initialize
pnpm init
# Install
pnpm add lodash
pnpm add -D tailwindcss
# Scripts
pnpm build
pnpm dev
# Benefits: Disk space efficient, fast, strict node_modulesBundlers
Vite (Modern, Fast)
# Create project
npm create vite@latest my-app -- --template react
# Install dependencies
cd my-app
npm install
# Dev server
npm run dev
# Build
npm run build// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 3000,
open: true,
},
build: {
outDir: 'dist',
sourcemap: true,
}
});Webpack (Traditional)
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.[contenthash].js',
clean: true,
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: 'babel-loader',
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
}),
],
devServer: {
static: './dist',
hot: true,
},
};Parcel (Zero Config)
# Install
npm install -D parcel
# Scripts
"scripts": {
"dev": "parcel src/index.html",
"build": "parcel build src/index.html"
}Task Runners
npm Scripts
{
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint src/**/*.{js,jsx}",
"format": "prettier --write src/**/*.{js,jsx,css}",
"test": "jest",
"test:watch": "jest --watch"
}
}Cross-Platform Scripts
{
"scripts": {
"clean": "rimraf dist",
"build": "npm run clean && vite build",
"start": "npm run dev"
}
}Use rimraf instead of rm -rf for Windows compatibility
Transpilation
Babel
// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', {
targets: {
browsers: ['> 1%', 'last 2 versions']
}
}],
'@babel/preset-react'
],
plugins: [
'@babel/plugin-proposal-class-properties'
]
};TypeScript
# Install
npm install -D typescript
# Initialize
npx tsc --init// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020", "DOM"],
"jsx": "react-jsx",
"moduleResolution": "node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}CSS Processing
PostCSS
// postcss.config.js
module.exports = {
plugins: [
require('tailwindcss'),
require('autoprefixer'),
require('cssnano')({ preset: 'default' })
]
};Autoprefixer: Adds vendor prefixes automatically
cssnano: Minifies CSS
Sass
npm install -D sass// _variables.scss
$primary: #3b82f6;
$spacing: 1rem;
// main.scss
@import 'variables';
.button {
background: $primary;
padding: $spacing;
}Linting
ESLint
npm install -D eslint eslint-plugin-react// .eslintrc.js
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
],
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 12,
sourceType: 'module',
},
plugins: ['react'],
rules: {
'react/prop-types': 'off',
'no-unused-vars': 'warn',
},
};Prettier
// .prettierrc
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 80,
"tabWidth": 2
}// package.json
{
"scripts": {
"lint": "eslint src/**/*.{js,jsx}",
"format": "prettier --write src/**/*.{js,jsx,css}"
}
}Environment Variables
Vite
# .env
VITE_API_URL=https://api.example.com
VITE_APP_VERSION=1.0.0
# .env.local (gitignored)
VITE_API_KEY=secret_key// Access
const apiUrl = import.meta.env.VITE_API_URL;Webpack
// webpack.config.js
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env.API_URL': JSON.stringify(process.env.API_URL),
}),
],
};Hot Module Replacement (HMR)
// Vite - Built-in
// Just import and edit, HMR works automatically
// Webpack
if (module.hot) {
module.hot.accept('./App', () => {
const NextApp = require('./App').default;
render(<NextApp />);
});
}Optimization
Code Splitting
// Dynamic import
const LazyComponent = React.lazy(() => import('./LazyComponent'));
function App() {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</React.Suspense>
);
}Tree Shaking
// Named imports (tree-shakeable)
import { debounce } from 'lodash-es';
// Avoid default imports from large libraries
import _ from 'lodash'; // ❌ Pulls entire libraryDocker Build
# Multi-stage build
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80CI/CD Integration
# .github/workflows/build.yml
name: Build
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- run: npm ci
- run: npm run build
- run: npm run lint
- run: npm run testMonorepo Tools
Turborepo
# Install
npx create-turbo@latest// turbo.json
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"dev": {
"cache": false
}
}
}# Run build across all packages
turbo run buildKey Takeaways
- Vite: Fast, modern bundler with HMR out of the box
- pnpm: Disk-efficient package manager
- npm scripts: Keep them simple, use
npm runfor cross-platform - ESLint + Prettier: Enforce code quality and formatting
- Environment variables: Prefix with
VITE_(Vite) or use.envfiles - Code splitting: Dynamic imports for lazy loading
- Docker: Multi-stage builds for smaller images
- Monorepo: Turborepo for managing multiple packages