Skip to Content
🚀 欢迎来到前端学习指南!这是一份从零基础到专家的完整学习路径
🚀 第三部分:进阶篇15. 前端工程化深入

15. 前端工程化深入

📋 目录

构建工具深度解析

现代前端工程化离不开构建工具,理解其原理和配置对于提升开发效率至关重要。

Webpack深度配置

// webpack.config.js - 生产级配置 const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); const TerserPlugin = require('terser-webpack-plugin'); const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); const CompressionPlugin = require('compression-webpack-plugin'); module.exports = (env, argv) => { const isProduction = argv.mode === 'production'; return { entry: { main: './src/index.js', vendor: ['react', 'react-dom', 'lodash'] }, output: { path: path.resolve(__dirname, 'dist'), filename: isProduction ? '[name].[contenthash:8].js' : '[name].js', chunkFilename: isProduction ? '[name].[contenthash:8].chunk.js' : '[name].chunk.js', publicPath: '/', clean: true }, optimization: { minimize: isProduction, minimizer: [ new TerserPlugin({ terserOptions: { compress: { drop_console: isProduction, drop_debugger: isProduction } } }), new CssMinimizerPlugin() ], splitChunks: { chunks: 'all', cacheGroups: { vendor: { test: /[\\/]node_modules[\\/]/, name: 'vendors', chunks: 'all', priority: 10 }, common: { name: 'common', minChunks: 2, chunks: 'all', priority: 5, reuseExistingChunk: true } } }, runtimeChunk: { name: 'runtime' } }, module: { rules: [ { test: /\.(js|jsx|ts|tsx)$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: [ ['@babel/preset-env', { targets: { browsers: ['> 1%', 'last 2 versions'] }, useBuiltIns: 'usage', corejs: 3 }], '@babel/preset-react', '@babel/preset-typescript' ], plugins: [ '@babel/plugin-proposal-class-properties', '@babel/plugin-syntax-dynamic-import', isProduction && 'babel-plugin-transform-remove-console' ].filter(Boolean) } } }, { test: /\.css$/, use: [ isProduction ? MiniCssExtractPlugin.loader : 'style-loader', { loader: 'css-loader', options: { modules: { auto: true, localIdentName: isProduction ? '[hash:base64:8]' : '[name]__[local]--[hash:base64:5]' } } }, 'postcss-loader' ] }, { test: /\.scss$/, use: [ isProduction ? MiniCssExtractPlugin.loader : 'style-loader', 'css-loader', 'postcss-loader', 'sass-loader' ] }, { test: /\.(png|jpg|jpeg|gif|svg)$/, type: 'asset', parser: { dataUrlCondition: { maxSize: 8 * 1024 // 8KB } }, generator: { filename: 'images/[name].[hash:8][ext]' } }, { test: /\.(woff|woff2|eot|ttf|otf)$/, type: 'asset/resource', generator: { filename: 'fonts/[name].[hash:8][ext]' } } ] }, plugins: [ new HtmlWebpackPlugin({ template: './public/index.html', minify: isProduction ? { removeComments: true, collapseWhitespace: true, removeRedundantAttributes: true, useShortDoctype: true, removeEmptyAttributes: true, removeStyleLinkTypeAttributes: true, keepClosingSlash: true, minifyJS: true, minifyCSS: true, minifyURLs: true } : false }), isProduction && new MiniCssExtractPlugin({ filename: '[name].[contenthash:8].css', chunkFilename: '[name].[contenthash:8].chunk.css' }), isProduction && new CompressionPlugin({ algorithm: 'gzip', test: /\.(js|css|html|svg)$/, threshold: 8192, minRatio: 0.8 }), process.env.ANALYZE && new BundleAnalyzerPlugin() ].filter(Boolean), resolve: { extensions: ['.js', '.jsx', '.ts', '.tsx'], alias: { '@': path.resolve(__dirname, 'src'), '@components': path.resolve(__dirname, 'src/components'), '@utils': path.resolve(__dirname, 'src/utils'), '@assets': path.resolve(__dirname, 'src/assets') } }, devServer: { port: 3000, hot: true, historyApiFallback: true, compress: true, open: true }, devtool: isProduction ? 'source-map' : 'eval-source-map' }; };

Vite配置优化

// vite.config.js import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import { resolve } from 'path'; import { visualizer } from 'rollup-plugin-visualizer'; import { createHtmlPlugin } from 'vite-plugin-html'; export default defineConfig(({ command, mode }) => { const isProduction = mode === 'production'; return { plugins: [ react(), createHtmlPlugin({ minify: isProduction, inject: { data: { title: 'My App', injectScript: isProduction ? '<script src="/analytics.js"></script>' : '' } } }), isProduction && visualizer({ filename: 'dist/stats.html', open: true, gzipSize: true }) ].filter(Boolean), resolve: { alias: { '@': resolve(__dirname, 'src'), '@components': resolve(__dirname, 'src/components'), '@utils': resolve(__dirname, 'src/utils'), '@assets': resolve(__dirname, 'src/assets') } }, css: { modules: { localsConvention: 'camelCase' }, preprocessorOptions: { scss: { additionalData: '@import "@/styles/variables.scss";' } } }, build: { target: 'es2015', outDir: 'dist', assetsDir: 'assets', sourcemap: isProduction ? 'hidden' : true, rollupOptions: { output: { manualChunks: { vendor: ['react', 'react-dom'], utils: ['lodash', 'axios', 'dayjs'] } } }, terserOptions: { compress: { drop_console: isProduction, drop_debugger: isProduction } } }, server: { port: 3000, open: true, cors: true, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, '') } } }, optimizeDeps: { include: ['react', 'react-dom', 'lodash'], exclude: ['@vite/client', '@vite/env'] } }; });

微前端架构

Single-SPA实现

⚠️

微前端架构能够让大型应用拆分为多个独立的小应用,提高开发效率和可维护性。

// 主应用配置 - main-app/src/index.js import { registerApplication, start } from 'single-spa'; // 注册微应用 registerApplication({ name: 'react-app', app: () => import('./microfrontends/react-app/main.js'), activeWhen: ['/react'], customProps: { authToken: 'token123', apiUrl: 'https://api.example.com' } }); registerApplication({ name: 'vue-app', app: () => import('./microfrontends/vue-app/main.js'), activeWhen: ['/vue'], customProps: { theme: 'dark' } }); // 启动single-spa start({ urlRerouteOnly: true }); // React微应用 - react-app/src/main.js import React from 'react'; import ReactDOM from 'react-dom'; import singleSpaReact from 'single-spa-react'; import App from './App'; const lifecycles = singleSpaReact({ React, ReactDOM, rootComponent: App, errorBoundary(err, info, props) { return <div>Error in React app: {err.message}</div>; } }); export const { bootstrap, mount, unmount } = lifecycles; // 独立运行模式 if (!window.singleSpaNavigate) { ReactDOM.render(<App />, document.getElementById('root')); } // Vue微应用 - vue-app/src/main.js import { createApp } from 'vue'; import singleSpaVue from 'single-spa-vue'; import App from './App.vue'; const vueLifecycles = singleSpaVue({ createApp, appOptions: { render() { return h(App, { // 传递props ...this.props }); } }, handleInstance: (app, info) => { app.use(router); app.use(store); } }); export const { bootstrap, mount, unmount } = vueLifecycles; // 独立运行模式 if (!window.singleSpaNavigate) { createApp(App).mount('#app'); }

Module Federation实现

// 主应用 webpack.config.js const ModuleFederationPlugin = require('@module-federation/webpack'); module.exports = { mode: 'development', devServer: { port: 3000 }, plugins: [ new ModuleFederationPlugin({ name: 'shell', remotes: { mf_react: 'mf_react@http://localhost:3001/remoteEntry.js', mf_vue: 'mf_vue@http://localhost:3002/remoteEntry.js' } }) ] }; // React微应用 webpack.config.js module.exports = { mode: 'development', devServer: { port: 3001 }, plugins: [ new ModuleFederationPlugin({ name: 'mf_react', filename: 'remoteEntry.js', exposes: { './App': './src/App', './Button': './src/components/Button' }, shared: { react: { singleton: true }, 'react-dom': { singleton: true } } }) ] }; // 主应用中使用微应用 import React, { Suspense } from 'react'; const ReactApp = React.lazy(() => import('mf_react/App')); const ReactButton = React.lazy(() => import('mf_react/Button')); function App() { return ( <div> <h1>主应用</h1> <Suspense fallback={<div>Loading React App...</div>}> <ReactApp /> </Suspense> <Suspense fallback={<div>Loading Button...</div>}> <ReactButton onClick={() => alert('Clicked!')}> Remote Button </ReactButton> </Suspense> </div> ); } export default App;

CI/CD流水线

GitHub Actions配置

# .github/workflows/ci-cd.yml name: CI/CD Pipeline on: push: branches: [ main, develop ] pull_request: branches: [ main ] env: NODE_VERSION: '18' REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} jobs: test: runs-on: ubuntu-latest strategy: matrix: node-version: [16, 18, 20] steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: 'npm' - name: Install dependencies run: npm ci - name: Run linting run: npm run lint - name: Run type checking run: npm run type-check - name: Run tests run: npm run test:coverage - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: file: ./coverage/lcov.info build: needs: test runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies run: npm ci - name: Build application run: npm run build - name: Upload build artifacts uses: actions/upload-artifact@v3 with: name: build-files path: dist/ security-scan: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Run security audit run: npm audit --audit-level high - name: Run Snyk security scan uses: snyk/actions/node@master env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} deploy-staging: needs: [test, build, security-scan] runs-on: ubuntu-latest if: github.ref == 'refs/heads/develop' environment: name: staging url: https://staging.example.com steps: - name: Download build artifacts uses: actions/download-artifact@v3 with: name: build-files path: dist/ - name: Deploy to staging run: | # 部署到staging环境的脚本 echo "Deploying to staging..." deploy-production: needs: [test, build, security-scan] runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' environment: name: production url: https://example.com steps: - name: Download build artifacts uses: actions/download-artifact@v3 with: name: build-files path: dist/ - name: Deploy to production run: | # 部署到生产环境的脚本 echo "Deploying to production..." - name: Notify deployment uses: 8398a7/action-slack@v3 with: status: ${{ job.status }} channel: '#deployments' webhook_url: ${{ secrets.SLACK_WEBHOOK }}

Docker化部署

# Dockerfile # 多阶段构建 FROM node:18-alpine AS builder WORKDIR /app # 复制package文件 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 # 复制nginx配置 COPY nginx.conf /etc/nginx/nginx.conf # 暴露端口 EXPOSE 80 # 启动nginx CMD ["nginx", "-g", "daemon off;"]
# nginx.conf events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; # Gzip压缩 gzip on; gzip_vary on; gzip_min_length 1024; gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json; # 缓存配置 location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { expires 1y; add_header Cache-Control "public, immutable"; } server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; # SPA路由支持 location / { try_files $uri $uri/ /index.html; } # API代理 location /api/ { proxy_pass http://backend:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 健康检查 location /health { access_log off; return 200 "healthy\n"; add_header Content-Type text/plain; } } }

代码质量保障

ESLint和Prettier配置

// .eslintrc.js module.exports = { env: { browser: true, es2021: true, node: true, jest: true }, extends: [ 'eslint:recommended', '@typescript-eslint/recommended', 'plugin:react/recommended', 'plugin:react-hooks/recommended', 'plugin:jsx-a11y/recommended', 'plugin:import/recommended', 'plugin:import/typescript', 'prettier' ], parser: '@typescript-eslint/parser', parserOptions: { ecmaFeatures: { jsx: true }, ecmaVersion: 'latest', sourceType: 'module', project: './tsconfig.json' }, plugins: [ 'react', '@typescript-eslint', 'react-hooks', 'jsx-a11y', 'import' ], rules: { // TypeScript规则 '@typescript-eslint/no-unused-vars': 'error', '@typescript-eslint/no-explicit-any': 'warn', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', // React规则 'react/react-in-jsx-scope': 'off', 'react/prop-types': 'off', 'react/display-name': 'off', 'react-hooks/rules-of-hooks': 'error', 'react-hooks/exhaustive-deps': 'warn', // 导入规则 'import/order': [ 'error', { groups: [ 'builtin', 'external', 'internal', 'parent', 'sibling', 'index' ], 'newlines-between': 'always', alphabetize: { order: 'asc', caseInsensitive: true } } ], // 通用规则 'no-console': 'warn', 'no-debugger': 'error', 'prefer-const': 'error', 'no-var': 'error' }, settings: { react: { version: 'detect' }, 'import/resolver': { typescript: { alwaysTryTypes: true, project: './tsconfig.json' } } } };
// .prettierrc { "semi": true, "trailingComma": "es5", "singleQuote": true, "printWidth": 80, "tabWidth": 2, "useTabs": false, "bracketSpacing": true, "bracketSameLine": false, "arrowParens": "avoid", "endOfLine": "lf" }

Husky和lint-staged配置

// package.json { "scripts": { "prepare": "husky install", "lint": "eslint src --ext .js,.jsx,.ts,.tsx", "lint:fix": "eslint src --ext .js,.jsx,.ts,.tsx --fix", "format": "prettier --write src/**/*.{js,jsx,ts,tsx,json,css,md}", "type-check": "tsc --noEmit" }, "lint-staged": { "*.{js,jsx,ts,tsx}": [ "eslint --fix", "prettier --write" ], "*.{json,css,md}": [ "prettier --write" ] } }
#!/bin/sh # .husky/pre-commit . "$(dirname "$0")/_/husky.sh" npx lint-staged npm run type-check
#!/bin/sh # .husky/commit-msg . "$(dirname "$0")/_/husky.sh" npx commitlint --edit $1

前端工程化是现代Web开发的基石,通过合理的工具链配置和流程设计,能够大大提升开发效率和代码质量。


📚 参考学习资料

📖 官方文档

🎓 优质教程

🛠️ 实践项目

🔧 开发工具

📝 深入阅读

💡 学习建议:建议从基础的构建工具开始,学习Webpack或Vite配置,然后掌握代码质量工具,最后建立完整的CI/CD流水线。

Last updated on