跳转至

前端工程化

前端工程化

📚 章节目标

本章节将全面介绍前端工程化的核心技术和工具,包括Webpack、Vite、Rollup、模块化等,帮助学习者掌握前端工程化的核心方法。

学习目标

  1. 理解前端工程化的核心概念
  2. 掌握Webpack的深度配置
  3. 掌握Vite的构建优化
  4. 掌握Rollup的打包技术
  5. 理解模块化规范
  6. 掌握工程化最佳实践

🏗️ 前端工程化概述

1. 什么是前端工程化

JavaScript
// 前端工程化的核心要素
// 1. 模块化:将代码拆分成独立的模块
// 2. 组件化:将UI拆分成可复用的组件
// 3. 规范化:制定代码规范和项目规范
// 4. 自动化:自动化构建、测试、部署
// 5. 优化化:性能优化、代码优化

// 示例:模块化
// utils.js
export function formatDate(date) {
  return new Date(date).toLocaleDateString();
}

// main.js
import { formatDate } from './utils';
console.log(formatDate(new Date()));

// 示例:组件化
// Button.jsx
export function Button({ children, onClick }) {
  return <button onClick={onClick}>{children}</button>;
}

// App.jsx
import { Button } from './Button';

function App() {
  return <Button onClick={() => console.log('clicked')}>Click me</Button>;
}

2. 工程化工具链

JavaScript
// 前端工程化工具链
// 1. 包管理器:npm, yarn, pnpm
// 2. 构建工具:Webpack, Vite, Rollup
// 3. 代码规范:ESLint, Prettier
// 4. 测试工具:Jest, Cypress, Playwright
// 5. CI/CD:GitHub Actions, GitLab CI, Jenkins

// 示例:使用pnpm
pnpm install
pnpm add react
pnpm add -D typescript

// 示例:使用ESLint
npx eslint src/
npx eslint src/ --fix

// 示例:使用Prettier
npx prettier --write src/

📦 Webpack深度配置

1. Webpack基础

1.1 基本配置

JavaScript
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  // 入口文件
  entry: './src/index.js',

  // 输出配置
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
    clean: true,
  },

  // 模式
  mode: 'production',

  // Loader配置
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
        },
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /\.(png|jpg|jpeg|gif|svg)$/,
        type: 'asset/resource',
      },
    ],
  },

  // 插件配置
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],
};

1.2 多入口配置

JavaScript
// webpack.config.js
module.exports = {
  entry: {
    main: './src/index.js',
    vendor: './src/vendor.js',
  },

  output: {
    filename: '[name].[contenthash].js',
    path: path.resolve(__dirname, 'dist'),
    clean: true,
  },
};

2. Loader配置

2.1 JavaScript Loader

JavaScript
// babel-loader配置
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              '@babel/preset-env',
              '@babel/preset-react',
              '@babel/preset-typescript',
            ],
            plugins: [
              '@babel/plugin-transform-runtime',
              '@babel/plugin-transform-class-properties',
            ],
          },
        },
      },
    ],
  },
};

// .babelrc
{
  "presets": [
    ["@babel/preset-env", {
      "targets": {
        "browsers": ["> 1%", "last 2 versions", "not dead"]
      }
    }],
    "@babel/preset-react",
    "@babel/preset-typescript"
  ],
  "plugins": [
    "@babel/plugin-transform-runtime",
    "@babel/plugin-transform-class-properties"
  ]
}

2.2 CSS Loader

JavaScript
// CSS Loader配置
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader',
          'postcss-loader',
        ],
      },
      {
        test: /\.scss$/,
        use: [
          'style-loader',
          'css-loader',
          'postcss-loader',
          'sass-loader',
        ],
      },
      {
        test: /\.less$/,
        use: [
          'style-loader',
          'css-loader',
          'less-loader',
        ],
      },
    ],
  },
};

// postcss.config.js
module.exports = {
  plugins: [
    require('autoprefixer'),
    require('cssnano'),
  ],
};

2.3 资源Loader

JavaScript
// 资源Loader配置
module.exports = {
  module: {
    rules: [
      // 图片处理
      {
        test: /\.(png|jpg|jpeg|gif|svg|webp)$/,
        type: 'asset',
        parser: {
          dataUrlCondition: {
            maxSize: 8 * 1024, // 8kb以下转base64
          },
        },
        generator: {
          filename: 'images/[name].[hash:6][ext]',
        },
      },
      // 字体处理
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        type: 'asset/resource',
        generator: {
          filename: 'fonts/[name].[hash:6][ext]',
        },
      },
      // 媒体文件
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)$/,
        type: 'asset/resource',
        generator: {
          filename: 'media/[name].[hash:6][ext]',
        },
      },
    ],
  },
};

3. Plugin配置

3.1 HTML Plugin

JavaScript
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html',
      filename: 'index.html',
      title: 'My App',
      minify: {
        collapseWhitespace: true,
        removeComments: true,
        removeRedundantAttributes: true,
        removeScriptTypeAttributes: true,
        useShortDoctype: true,
      },
    }),
  ],
};

3.2 清理插件

JavaScript
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  plugins: [
    new CleanWebpackPlugin({
      cleanOnceBeforeBuildPatterns: ['**/*', '!static-files/**'],
    }),
  ],
};

3.3 环境变量插件

JavaScript
const webpack = require('webpack');

module.exports = {
  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
      'process.env.API_URL': JSON.stringify(process.env.API_URL),
    }),
  ],
};

4. 优化配置

4.1 代码分割

JavaScript
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          priority: 10,
        },
        common: {
          minChunks: 2,
          priority: 5,
          reuseExistingChunk: true,
        },
      },
    },
    runtimeChunk: {
      name: 'runtime',
    },
  },
};

4.2 Tree Shaking

JavaScript
module.exports = {
  optimization: {
    usedExports: true,
    sideEffects: false,
    minimize: true,
  },
};

// package.json
{
  "sideEffects": false
}

4.3 压缩优化

JavaScript
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true,
            drop_debugger: true,
          },
        },
      }),
      new CssMinimizerPlugin(),
    ],
  },
};

⚡ Vite构建优化

1. Vite基础

1.1 基本配置

JavaScript
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';

export default defineConfig({
  plugins: [react()],

  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },

  server: {
    port: 3000,
    open: true,
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
      },
    },
  },

  build: {
    outDir: 'dist',
    assetsDir: 'assets',
    sourcemap: false,
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true,
      },
    },
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],
          router: ['react-router-dom'],
        },
      },
    },
  },
});

1.2 环境变量

JavaScript
// .env
VITE_API_URL=https://api.example.com

// .env.development
VITE_API_URL=http://localhost:8080

// .env.production
VITE_API_URL=https://api.example.com

// 在代码中使用
const apiUrl = import.meta.env.VITE_API_URL;

2. Vite插件

2.1 常用插件

JavaScript
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import vitePluginImp from 'vite-plugin-imp';
import legacy from '@vitejs/plugin-legacy';
import viteCompression from 'vite-plugin-compression';

export default defineConfig({
  plugins: [
    react(),

    // 按需引入
    vitePluginImp({
      libList: [
        {
          libName: 'antd',
          style: (name) => `antd/es/${name}/style`,
        },
      ],
    }),

    // 兼容旧浏览器
    legacy({
      targets: ['defaults', 'not IE 11'],
    }),

    // Gzip压缩
    viteCompression({
      verbose: true,
      disable: false,
      threshold: 10240,
      algorithm: 'gzip',
      ext: '.gz',
    }),
  ],
});

2.2 自定义插件

JavaScript
// vite.config.js
import { defineConfig } from 'vite';

function myPlugin() {
  return {
    name: 'my-plugin',
    transform(code, id) {
      if (id.endsWith('.js')) {
        // 转换代码
        return code.replace(/console\.log\(/g, 'logger.log(');
      }
    },
  };
}

export default defineConfig({
  plugins: [myPlugin()],
});

3. Vite优化

3.1 依赖预构建

JavaScript
// vite.config.js
export default defineConfig({
  optimizeDeps: {
    include: ['react', 'react-dom'],
    exclude: ['your-custom-dep'],
  },
});

3.2 构建优化

JavaScript
// vite.config.js
export default defineConfig({
  build: {
    // 代码分割
    rollupOptions: {
      output: {
        manualChunks: (id) => {
          if (id.includes('node_modules')) {
            return id.toString().split('node_modules/')[1].split('/')[0].toString();
          }
        },
      },
    },

    // 压缩配置
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true,
      },
    },

    // Chunk大小警告
    chunkSizeWarningLimit: 1000,
  },
});

🔄 Rollup打包技术

1. Rollup基础

1.1 基本配置

JavaScript
// rollup.config.js
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
import terser from '@rollup/plugin-terser';

export default {
  input: 'src/index.ts',
  output: [
    {
      file: 'dist/bundle.js',
      format: 'cjs',
      exports: 'auto',
    },
    {
      file: 'dist/bundle.esm.js',
      format: 'esm',
    },
    {
      file: 'dist/bundle.umd.js',
      format: 'umd',
      name: 'MyLibrary',
    },
  ],
  plugins: [
    resolve(),
    commonjs(),
    typescript(),
    terser(),
  ],
};

1.2 多入口配置

JavaScript
// rollup.config.js
export default {
  input: {
    main: 'src/index.ts',
    utils: 'src/utils/index.ts',
  },
  output: {
    dir: 'dist',
    format: 'esm',
    entryFileNames: '[name].js',
    chunkFileNames: '[name]-[hash].js',
  },
};

2. Rollup插件

2.1 常用插件

JavaScript
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
import json from '@rollup/plugin-json';
import babel from '@rollup/plugin-babel';
import replace from '@rollup/plugin-replace';
import alias from '@rollup/plugin-alias';
import postcss from 'rollup-plugin-postcss';

export default {
  plugins: [
    // 路径别名
    alias({
      entries: [
        { find: '@', replacement: './src' },
      ],
    }),

    // 解析node_modules
    resolve({
      browser: true,
    }),

    // 转换CommonJS
    commonjs(),

    // TypeScript支持
    typescript(),

    // JSON支持
    json(),

    // Babel转译
    babel({
      babelHelpers: 'bundled',
      exclude: 'node_modules/**',
    }),

    // 环境变量替换
    replace({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
      preventAssignment: true,
    }),

    // PostCSS
    postcss(),
  ],
};

2.2 自定义插件

JavaScript
// rollup.config.js
function myPlugin(options = {}) {
  return {
    name: 'my-plugin',

    transform(code, id) {
      if (id.endsWith('.js')) {
        // 转换代码
        return {
          code: code.replace(/console\.log\(/g, 'logger.log('),
          map: null,
        };
      }
    },

    generateBundle(options, bundle) {
      // 生成bundle时执行
      console.log('Bundle generated');
    },
  };
}

export default {
  plugins: [myPlugin()],
};

3. Rollup优化

3.1 Tree Shaking

JavaScript
// rollup.config.js
export default {
  treeshake: {
    moduleSideEffects: false,
    propertyReadSideEffects: false,
    unknownGlobalSideEffects: false,
  },
};

3.2 代码分割

JavaScript
// rollup.config.js
export default {
  output: {
    manualChunks: (id) => {  // 箭头函数:简洁的函数语法
      if (id.includes('node_modules')) {
        return 'vendor';
      }
    },
  },
};

📦 模块化规范

1. CommonJS

JavaScript
// 导出
// utils.js
function formatDate(date) {
  return new Date(date).toLocaleDateString();
}

function formatNumber(num) {
  return num.toLocaleString();
}

module.exports = {
  formatDate,
  formatNumber,
};

// 或者
exports.formatDate = formatDate;
exports.formatNumber = formatNumber;

// 导入
const { formatDate, formatNumber } = require('./utils');  // 解构赋值:从对象/数组提取值

// 或者
const utils = require('./utils');  // const不可重新赋值;let块级作用域变量
utils.formatDate(new Date());

2. ES Modules

JavaScript
// 导出
// utils.js
export function formatDate(date) {
  return new Date(date).toLocaleDateString();
}

export function formatNumber(num) {
  return num.toLocaleString();
}

// 默认导出
export default function logger(message) {
  console.log(message);
}

// 导入
import { formatDate, formatNumber } from './utils';
import logger from './logger';

// 或者
import * as utils from './utils';
utils.formatDate(new Date());

// 动态导入
button.addEventListener('click', async () => {  // async定义异步函数;await等待Promise完成
  const module = await import('./heavy-module.js');  // await等待异步操作完成
  module.doSomething();
});

3. UMD

JavaScript
// UMD模块
(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD
    define(['exports'], factory);
  } else if (typeof exports === 'object') {
    // CommonJS
    factory(exports);
  } else {
    // Browser globals
    factory(root.myLibrary = {});
  }
}(this, function (exports) {
  exports.formatDate = function(date) {
    return new Date(date).toLocaleDateString();
  };

  exports.formatNumber = function(num) {
    return num.toLocaleString();
  };
}));

🛠️ 工程化最佳实践

1. 项目结构

Text Only
project/
├── public/              # 静态资源
│   ├── index.html
│   └── favicon.ico
├── src/                 # 源代码
│   ├── assets/          # 资源文件
│   ├── components/      # 组件
│   ├── pages/           # 页面
│   ├── services/        # 服务
│   ├── utils/           # 工具函数
│   ├── hooks/           # 自定义Hooks
│   ├── store/           # 状态管理
│   ├── router/          # 路由配置
│   ├── styles/          # 样式文件
│   └── index.js         # 入口文件
├── tests/               # 测试文件
├── .eslintrc.js        # ESLint配置
├── .prettierrc.js      # Prettier配置
├── .gitignore           # Git忽略文件
├── package.json         # 项目配置
└── README.md            # 项目说明

2. 代码规范

2.1 ESLint配置

JavaScript
// .eslintrc.js
module.exports = {
  env: {
    browser: true,
    es2021: true,
    node: true,
  },
  extends: [
    'eslint:recommended',
    'plugin:react/recommended',
    'plugin:react-hooks/recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:prettier/recommended',
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 'latest',
    sourceType: 'module',
  },
  plugins: ['react', '@typescript-eslint', 'prettier'],
  rules: {
    'react/react-in-jsx-scope': 'off',
    '@typescript-eslint/no-explicit-any': 'warn',
    'prettier/prettier': 'error',
  },
  settings: {
    react: {
      version: 'detect',
    },
  },
};

2.2 Prettier配置

JavaScript
// .prettierrc.js
module.exports = {
  semi: true,
  singleQuote: true,
  tabWidth: 2,
  trailingComma: 'es5',
  printWidth: 80,
  arrowParens: 'always',
  endOfLine: 'lf',
};

3. Git工作流

Bash
# 分支策略
# main - 主分支
# develop - 开发分支
# feature/* - 功能分支
# hotfix/* - 紧急修复分支

# 工作流程
# 1. 从develop创建功能分支
git checkout -b feature/new-feature

# 2. 开发并提交
git add .
git commit -m "feat: add new feature"

# 3. 推送到远程
git push origin feature/new-feature

# 4. 创建Pull Request

# 5. 代码审查

# 6. 合并到develop

# 7. 定期合并到main

4. CI/CD配置

YAML
# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'

      - name: Install dependencies
        run: npm ci

      - name: Lint
        run: npm run lint

      - name: Test
        run: npm test

      - name: Build
        run: npm run build

  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'

    steps:
      - uses: actions/checkout@v4

      - name: Deploy to Vercel
        uses: amondnet/vercel-action@v20
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.ORG_ID }}
          vercel-project-id: ${{ secrets.PROJECT_ID }}
          vercel-args: '--prod'

📝 练习题

1. 基础题

题目1:配置Webpack处理TypeScript

JavaScript
// webpack.config.js
module.exports = {
  // 配置Webpack处理TypeScript
  module: {
    rules: [
      // 添加TypeScript loader
    ],
  },
};

2. 进阶题

题目2:配置Vite实现按需引入

JavaScript
// vite.config.js
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [
    // 配置按需引入插件
  ],
});

3. 面试题

题目3:比较Webpack和Vite的区别

JavaScript
// 答案要点:
// 1. Webpack使用bundle模式,Vite使用ESM模式
// 2. Webpack冷启动慢,Vite冷启动快
// 3. Webpack热更新中等,Vite热更新快
// 4. Webpack配置复杂,Vite配置简单
// 5. Webpack生态成熟,Vite生态快速发展
// 6. Webpack适合所有项目,Vite适合现代项目

🎯 本章总结

本章节全面介绍了前端工程化的核心技术和工具,包括Webpack、Vite、Rollup、模块化等。关键要点:

  1. 工程化概念:理解前端工程化的核心要素
  2. Webpack:掌握Webpack的深度配置和优化
  3. Vite:掌握Vite的构建优化和插件
  4. Rollup:掌握Rollup的打包技术
  5. 模块化:理解CommonJS、ES Modules、UMD规范
  6. 最佳实践:掌握项目结构、代码规范、Git工作流、CI/CD配置

下一步将深入学习前端测试技术。