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 packages

yarn (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 cache

pnpm (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_modules

Bundlers

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 library

Docker 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 80

CI/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 test

Monorepo 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 build

Key Takeaways

  • Vite: Fast, modern bundler with HMR out of the box
  • pnpm: Disk-efficient package manager
  • npm scripts: Keep them simple, use npm run for cross-platform
  • ESLint + Prettier: Enforce code quality and formatting
  • Environment variables: Prefix with VITE_ (Vite) or use .env files
  • Code splitting: Dynamic imports for lazy loading
  • Docker: Multi-stage builds for smaller images
  • Monorepo: Turborepo for managing multiple packages