前端工程化终极指南(Webpack + Gulp + Vite + 实战项目)

📚 目录

  1. 前端工程化概述
  2. Webpack深度解析
  3. Gulp构建实践
  4. Vite现代化构建
  5. 工程化最佳实践
  6. CI/CD与部署自动化
  7. 实战项目搭建
  8. 性能优化与监控

前端工程化概述

什么是前端工程化?

前端工程化是指使用工程化方法和工具来规范前端开发流程、提高开发效率、保证代码质量的综合性解决方案。

核心目标:

  • 标准化:统一的开发规范和流程
  • 自动化:减少重复性手工操作
  • 效率化:提升开发和构建性能
  • 质量化:保证代码质量和项目稳定性

工程化体系:

graph TD A[前端工程化] --> B[开发工具链] A --> C[构建工具] A --> D[代码规范] A --> E[自动化测试] A --> F[部署流程] B --> B1[代码编辑器] B --> B2[调试工具] B --> B3[版本控制] C --> C1[Webpack] C --> C2[gulp] C --> C3[Vite] D --> D1[ESLint] D --> D2[Prettier] D --> D3[Git Hooks] E --> E1[单元测试] E --> E2[E2E测试] E --> E3[性能测试] F --> F1[CI/CD] F --> F2[容器化] F --> F3[监控]

工程化发展历程

javascript 复制代码
// 传统开发方式(手工时代)
function manualDevelopment() {
    // 手动下载依赖库
    const jquery = downloadFromCDN('jquery.min.js');
    const bootstrap = downloadFromCDN('bootstrap.min.js');
    
    // 手动合并文件
    const combinedJS = concatenateFiles([jquery, bootstrap, 'app.js']);
    
    // 手动压缩
    const minifiedJS = minify(combinedJS);
    
    // 手动上传到服务器
    uploadToServer(minifiedJS);
}

// 工程化开发方式(自动化)
function modernDevelopment() {
    // 包管理器
    npmInstall();
    
    // 模块化开发
    import { moduleA } from './modules';
    
    // 自动化构建
    webpackBuild();
    
    // 自动化测试
    runTests();
    
    // 自动化部署
    deployToProduction();
}

Webpack深度解析

Webpack核心概念

入口点(Entry):

javascript 复制代码
// webpack.config.js
module.exports = {
    // ========== 单入口配置 ==========
    // 指定Webpack打包的入口文件,这是整个依赖图的起点
    // Webpack会从这个文件开始递归查找所有依赖的模块
    entry: './src/index.js',
    
    // ========== 多入口配置 ==========
    // 应用于多页面应用或需要分离业务代码和第三方库的场景
    // main: 主要的业务逻辑入口
    // vendor: 第三方库/公共模块的入口,便于长期缓存
    entry: {
        main: './src/index.js',           // 主应用入口
        vendor: './src/vendor.js'          // 第三方库入口
    },
    
    // ========== 动态入口配置 ==========
    // 异步返回入口文件路径,适用于需要根据条件动态选择入口的场景
    // 例如:根据环境变量、用户权限等决定加载哪个入口文件
    entry: () => new Promise((resolve) => {
        // 这里可以根据某些条件动态决定入口文件
        // 比如根据环境变量选择不同的入口
        resolve('./src/index.js');
    })
};

出口点(Output):

javascript 复制代码
module.exports = {
    output: {
        // ===== 打包输出目录 =====
        // path.resolve确保得到绝对路径,__dirname是当前文件所在目录
        // 所有打包后的文件都会输出到dist目录下
        path: path.resolve(__dirname, 'dist'),
        
        // ===== 主文件命名规则 =====
        // [name]: 对应entry中的key(如main、vendor)
        // [contenthash]: 根据文件内容生成的hash值,内容变化时hash才会变
        // 作用:利于浏览器缓存,只有文件内容变化时才需要重新下载
        filename: '[name].[contenthash].js',
        
        // ===== 代码分割文件命名规则 =====
        // 用于动态import()或splitChunks分割出来的chunk文件
        // 确保这些异步加载的文件也有正确的缓存策略
        chunkFilename: '[name].[contenthash].chunk.js',
        
        // ===== 公共路径前缀 =====
        // 所有静态资源引用时会添加这个前缀
        // 开发环境:'/static/' 表示资源在静态服务器下
        // 生产环境:'https://cdn.example.com/' 表示使用CDN
        publicPath: '/static/',
        
        // ===== 自动清理输出目录 =====
        // true: 每次构建前自动清理dist目录
        // 避免旧文件残留,确保输出目录的干净
        clean: true,
        
        // ===== 资源文件命名规则 =====
        // 针对图片、字体等静态资源文件的命名规则
        // [hash]: 文件内容的hash值,[ext]: 文件扩展名,[query]: URL查询参数
        assetModuleFilename: 'assets/[hash][ext][query]'
    }
};

加载器(Loaders):

javascript 复制代码
module.exports = {
    module: {
        rules: [
            // ========== JavaScript/TypeScript/JSX/TSX处理 ==========
            {
                // 匹配所有.js, .jsx, .ts, .tsx结尾的文件
                test: /\.(js|jsx|ts|tsx)$/,
                
                // 排除node_modules目录,避免对第三方库进行转译,提高构建速度
                exclude: /node_modules/,
                
                // 使用多个loader,执行顺序从右到左
                use: [
                    // ===== ESLint代码检查 =====
                    // 先进行代码规范检查,在Babel转译之前
                    'eslint-loader',
                    
                    // ===== Babel转译 =====
                    {
                        loader: 'babel-loader',
                        options: {
                            // Babel预设配置
                            presets: [
                                // @babel/preset-env: 根据目标浏览器自动转换ES6+语法
                                ['@babel/preset-env', {
                                    targets: {
                                        // 浏览器兼容性目标
                                        // '> 1%': 全球使用率大于1%的浏览器
                                        // 'last 2 versions': 每个浏览器的最后2个版本
                                        browsers: ['> 1%', 'last 2 versions']
                                    }
                                }]
                            ],
                            // Babel插件配置
                            plugins: [
                                // 转换运行时,避免在每个文件中重复引入helper函数
                                '@babel/plugin-transform-runtime',
                                // 转换类属性语法(class MyClass { prop = value; })
                                '@babel/plugin-proposal-class-properties'
                            ]
                        }
                    }
                ]
            },
            
            // ========== CSS文件处理 ==========
            {
                test: /\.css$/,
                use: [
                    // ===== 将CSS注入到DOM =====
                    // 创建style标签,将CSS插入到页面head中
                    // 开发环境使用,便于热更新
                    'style-loader',
                    
                    // ===== CSS模块化处理 =====
                    // 解析CSS文件中的@import和url()
                    // 支持CSS Modules(配置css-loader.options.modules)
                    'css-loader',
                    
                    // ===== PostCSS处理 =====
                    // 自动添加浏览器前缀(-webkit-, -moz-等)
                    // 使用autoprefixer插件,根据caniuse数据添加前缀
                    'postcss-loader'
                ]
            },
            
            // ========== SCSS/Sass文件处理 ==========
            {
                test: /\.scss$/,
                use: [
                    // 将编译后的CSS注入到DOM
                    'style-loader',
                    // 解析SCSS中的@import和url()
                    'css-loader',
                    // 将SCSS编译为CSS
                    'sass-loader',
                    
                    // ===== 全局变量/混合器注入 =====
                    // 在所有SCSS文件中自动引入全局变量和混合器
                    // 避免每个文件都要手动@import
                    {
                        loader: 'sass-resources-loader',
                        options: {
                            // 全局引入的SCSS文件路径
                            resources: './src/styles/variables.scss'
                        }
                    }
                ]
            },
            
            // ========== 图片资源处理 ==========
            {
                test: /\.(png|jpe?g|gif|webp|svg)$/,
                
                // ===== 资源类型 =====
                // 'asset': Webpack4+的统一资源处理方式
                // 根据文件大小自动选择内联base64或独立文件
                type: 'asset',
                
                // ===== 内联条件设置 =====
                // 当文件小于8KB时,转为base64内联到JS中
                // 优点:减少HTTP请求,缺点:增加JS体积
                // 适用于小图标、小图片等
                parser: {
                    dataUrlCondition: {
                        maxSize: 8 * 1024 // 8KB以下转为base64
                    }
                },
                
                // ===== 文件输出路径 =====
                // 大文件独立输出时的命名规则
                // [name]: 原文件名,[hash]: 内容hash,[ext]: 扩展名
                generator: {
                    filename: 'images/[name].[hash][ext]'
                }
            },
            
            // ========== 字体文件处理 ==========
            {
                test: /\.(woff2?|eot|ttf|otf)$/,
                
                // ===== 字体文件类型 =====
                // 'asset/resource': 始终输出为独立文件,不内联
                // 字体文件通常较大,不适合内联
                type: 'asset/resource',
                
                // ===== 字体文件输出路径 =====
                generator: {
                    filename: 'fonts/[name].[hash][ext]'
                }
            }
        ]
    }
};

插件(Plugins):

javascript 复制代码
// ===== 导入所需的Webpack插件 =====
const HtmlWebpackPlugin = require('html-webpack-plugin');           // HTML模板插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin');  // CSS提取插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin');    // 清理目录插件
const CopyWebpackPlugin = require('copy-webpack-plugin');          // 复制文件插件
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; // Bundle分析插件

module.exports = {
    plugins: [
        // ========== HTML模板处理插件 ==========
        // 自动生成HTML文件,并注入打包后的JS和CSS文件
        // 省去手动管理script标签和link标签的麻烦
        new HtmlWebpackPlugin({
            // HTML模板文件路径
            template: './public/index.html',
            
            // 生成的HTML文件名
            filename: 'index.html',
            
            // 资源注入位置:'head'或'body'
            // 'body': 将script标签放在body底部,推荐做法
            inject: 'body',
            
            // ===== HTML压缩配置 =====
            // 生产环境启用HTML压缩,减少文件体积
            minify: {
                removeComments: true,              // 移除HTML注释
                collapseWhitespace: true,           // 折叠空白字符
                removeRedundantAttributes: true,   // 移除冗余属性(如type="text/javascript")
                useShortDoctype: true,             // 使用短doctype(<!DOCTYPE html>)
                removeEmptyAttributes: true,        // 移除空属性
                removeStyleLinkTypeAttributes: true,// 移除style/link的type属性
                keepClosingSlash: true,             // 自闭合标签保持/
                minifyJS: true,                    // 压缩内联JS
                minifyCSS: true,                   // 压缩内联CSS
                minifyURLs: true                   // 压缩内联URL
            }
        }),
        
        // ========== CSS提取插件 ==========
        // 将CSS从JS中提取出来,生成独立的CSS文件
        // 生产环境使用,便于浏览器并行加载CSS,避免FOUC(无样式内容闪烁)
        new MiniCssExtractPlugin({
            // 主CSS文件命名规则
            filename: 'css/[name].[contenthash].css',
            
            // 异步加载的CSS文件命名规则
            chunkFilename: 'css/[name].[contenthash].chunk.css'
        }),
        
        // ========== 目录清理插件 ==========
        // 在每次构建前清理输出目录
        // 确保每次构建都是全新的,避免旧文件残留
        new CleanWebpackPlugin(),
        
        // ========== 文件复制插件 ==========
        // 将静态文件直接复制到输出目录,不经过Webpack处理
        // 适用于不需要处理的静态资源,如favicon、robots.txt等
        new CopyWebpackPlugin({
            patterns: [
                {
                    // 源目录
                    from: 'public/static',
                    
                    // 目标目录(相对于output.path)
                    to: 'static',
                    
                    // 忽略的文件模式
                    globOptions: {
                        ignore: ['**/index.html'] // 忽略index.html,由HtmlWebpackPlugin处理
                    }
                }
            ]
        }),
        
        // ========== 环境变量定义插件 ==========
        // 在编译时将环境变量注入到代码中
        // 可以在代码中直接使用process.env.NODE_ENV等变量
        new webpack.DefinePlugin({
            'process.env': {
                // 将环境变量转换为字符串字面量
                NODE_ENV: JSON.stringify(process.env.NODE_ENV),
                API_URL: JSON.stringify(process.env.API_URL)
            }
        }),
        
        // ========== Bundle分析插件 ==========
        // 生成Bundle分析报告,帮助优化打包体积
        // analyzerMode: 'static' 生成静态HTML报告文件
        // openAnalyzer: false 构建完成后不自动打开报告
        new BundleAnalyzerPlugin({
            analyzerMode: 'static',
            openAnalyzer: false
        })
    ]
};

Webpack高级配置

环境配置分离:

javascript 复制代码
// webpack.common.js
const path = require('path');
const { VueLoaderPlugin } = require('vue-loader');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].[contenthash].js'
    },
    module: {
        rules: [
            {
                test: /\.vue$/,
                loader: 'vue-loader'
            },
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: 'babel-loader'
            },
            {
                test: /\.css$/,
                use: ['vue-style-loader', 'css-loader']
            }
        ]
    },
    plugins: [
        new VueLoaderPlugin(),
        new HtmlWebpackPlugin({
            template: './public/index.html'
        })
    ],
    optimization: {
        splitChunks: {
            chunks: 'all',
            cacheGroups: {
                vendor: {
                    test: /[\\/]node_modules[\\/]/,
                    name: 'vendors',
                    chunks: 'all'
                }
            }
        }
    }
};

// webpack.prod.js
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

module.exports = merge(common, {
    mode: 'production',
    devtool: 'source-map',
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [MiniCssExtractPlugin.loader, 'css-loader']
            }
        ]
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: '[name].[contenthash].css'
        })
    ],
    optimization: {
        minimizer: [
            new TerserPlugin({
                parallel: true,
                terserOptions: {
                    compress: {
                        drop_console: true // 移除console
                    }
                }
            }),
            new CssMinimizerPlugin()
        ]
    }
});

// webpack.dev.js
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
const webpack = require('webpack');

module.exports = merge(common, {
    mode: 'development',
    devtool: 'eval-source-map',
    devServer: {
        contentBase: path.resolve(__dirname, 'dist'),
        port: 3000,
        hot: true,
        overlay: true,
        historyApiFallback: true,
        proxy: {
            '/api': {
                target: 'http://localhost:8080',
                changeOrigin: true,
                pathRewrite: {
                    '^/api': ''
                }
            }
        }
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin()
    ]
});

性能优化配置:

javascript 复制代码
module.exports = {
    optimization: {
        // 代码分割
        splitChunks: {
            chunks: 'all',
            minSize: 30000,
            maxSize: 0,
            minChunks: 1,
            maxAsyncRequests: 5,
            maxInitialRequests: 3,
            automaticNameDelimiter: '~',
            cacheGroups: {
                vendors: {
                    test: /[\\/]node_modules[\\/]/,
                    priority: -10,
                    name(module) {
                        const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
                        return `npm.${packageName.replace('@', '')}`;
                    }
                },
                common: {
                    name: 'common',
                    minChunks: 2,
                    chunks: 'initial',
                    priority: -20,
                    reuseExistingChunk: true
                }
            }
        },
        
        // 运行时代码分离
        runtimeChunk: {
            name: 'runtime'
        },
        
        // Tree shaking
        usedExports: true,
        sideEffects: false,
        
        // 压缩优化
        minimize: true,
        minimizer: [
            new TerserPlugin({
                parallel: true,
                terserOptions: {
                    compress: {
                        drop_console: true,
                        drop_debugger: true,
                        pure_funcs: ['console.log', 'console.info']
                    },
                    output: {
                        comments: false,
                        ascii_only: true
                    }
                },
                extractComments: false
            })
        ]
    },
    
    // 解析优化
    resolve: {
        extensions: ['.js', '.jsx', '.ts', '.tsx', '.vue'],
        alias: {
            '@': path.resolve(__dirname, 'src'),
            '@components': path.resolve(__dirname, 'src/components'),
            '@utils': path.resolve(__dirname, 'src/utils'),
            '@assets': path.resolve(__dirname, 'src/assets')
        },
        modules: ['node_modules', 'src/vendor']
    },
    
    // 缓存配置
    cache: {
        type: 'filesystem',
        buildDependencies: {
            config: [__filename]
        }
    }
};

自定义Loader和Plugin

自定义Loader示例:

javascript 复制代码
// my-custom-loader.js
module.exports = function(source) {
    // 去除console.log
    const noConsoleSource = source.replace(/console\.log\(.*?\);?/g, '');
    
    // 添加文件信息注释
    const filePath = this.resourcePath;
    const fileName = path.basename(filePath);
    const comment = `/* File: ${fileName} */\n`;
    
    return comment + noConsoleSource;
};

// webpack.config.js中使用
module.exports = {
    module: {
        rules: [
            {
                test: /\.js$/,
                use: [
                    'babel-loader',
                    {
                        loader: path.resolve(__dirname, 'my-custom-loader.js'),
                        options: {
                            // loader选项
                        }
                    }
                ]
            }
        ]
    }
};

自定义Plugin示例:

javascript 复制代码
// FileListPlugin.js
class FileListPlugin {
    constructor(options = {}) {
        this.filename = options.filename || 'filelist.md';
    }
    
    apply(compiler) {
        compiler.hooks.emit.tapAsync('FileListPlugin', (compilation, callback) => {
            const filelist = [];
            
            // 遍历所有编译后的资源
            for (const filename in compilation.assets) {
                const source = compilation.assets[filename].source();
                const size = Buffer.byteLength(source, 'utf8');
                filelist.push(`- ${filename} (${size} bytes)`);
            }
            
            // 创建新的资源
            compilation.assets[this.filename] = {
                source: () => filelist.join('\n'),
                size: () => filelist.join('\n').length
            };
            
            callback();
        });
    }
}

// 使用自定义Plugin
module.exports = {
    plugins: [
        new FileListPlugin({
            filename: 'build-files.md'
        })
    ]
};

Gulp构建实践

Gulp基础配置

javascript 复制代码
// gulpfile.js - Gulp构建配置文件

// ===== 导入Gulp核心方法 =====
const { src, dest, watch, series, parallel } = require('gulp');
const gulp = require('gulp');

// ===== 导入各种Gulp插件 =====
const sass = require('gulp-sass')(require('sass'));           // SCSS编译
const postcss = require('gulp-postcss');                    // PostCSS处理
const autoprefixer = require('autoprefixer');                // 浏览器前缀自动添加
const cssnano = require('cssnano');                         // CSS压缩
const babel = require('gulp-babel');                        // ES6+转ES5
const uglify = require('gulp-uglify');                      // JS压缩
const concat = require('gulp-concat');                      // 文件合并
const imagemin = require('gulp-imagemin');                  // 图片优化
const browserSync = require('browser-sync').create();       // 开发服务器
const del = require('del');                                // 文件删除
const plumber = require('gulp-plumber');                   // 错误处理
const sourcemaps = require('gulp-sourcemaps');              // 源码映射

// ===== 文件路径配置 =====
// 统一管理所有源文件和输出目录路径,便于维护
const paths = {
    html: 'src/**/*.html',              // HTML源文件路径
    styles: 'src/scss/**/*.scss',        // SCSS源文件路径
    scripts: 'src/js/**/*.js',           // JS源文件路径
    images: 'src/images/**/*',          // 图片源文件路径
    fonts: 'src/fonts/**/*',            // 字体源文件路径
    dist: 'dist'                        // 输出目录
};

// ===== 清理输出目录 =====
// 在每次构建前删除dist目录,确保输出环境的干净
function clean() {
    return del(paths.dist);
}

// ===== HTML文件处理 =====
// 将HTML文件从src复制到dist目录,不做其他处理
function html() {
    return src(paths.html)              // 读取HTML文件
        .pipe(plumber())                // 添加错误处理,防止错误中断构建流程
        .pipe(dest(paths.dist));         // 输出到dist目录
}

// ===== SCSS编译处理 =====
// 将SCSS编译为CSS,添加浏览器前缀,压缩,并生成源码映射
function styles() {
    return src(paths.styles)            // 读取SCSS文件
        .pipe(plumber())                // 错误处理
        .pipe(sourcemaps.init())        // 初始化源码映射
        .pipe(sass({ 
            outputStyle: 'expanded'     // 输出格式:expanded(展开式,便于调试)
        }).on('error', sass.logError))  // SCSS编译错误处理
        .pipe(postcss([                 // 使用PostCSS处理
            autoprefixer(),              // 自动添加浏览器前缀
            cssnano()                   // CSS压缩
        ]))
        .pipe(sourcemaps.write('.'))     // 写入源码映射文件
        .pipe(dest(`${paths.dist}/css`)) // 输出到dist/css目录
        .pipe(browserSync.stream());     // 触发浏览器热更新
}

// ===== JavaScript处理 =====
// 转译ES6+为ES5,合并文件,压缩,并生成源码映射
function scripts() {
    return src(paths.scripts)           // 读取JS文件
        .pipe(plumber())                // 错误处理
        .pipe(sourcemaps.init())        // 初始化源码映射
        .pipe(babel({                  // Babel转译
            presets: ['@babel/preset-env'] // 使用env预设,根据目标浏览器自动转译
        }))
        .pipe(concat('main.min.js'))     // 合并所有JS文件为单个文件
        .pipe(uglify())                 // 压缩JS代码
        .pipe(sourcemaps.write('.'))     // 写入源码映射
        .pipe(dest(`${paths.dist}/js`)) // 输出到dist/js目录
        .pipe(browserSync.stream());     // 触发浏览器热更新
}

// ===== 图片优化处理 =====
// 压缩和优化各种格式的图片文件
function images() {
    return src(paths.images)            // 读取图片文件
        .pipe(imagemin([               // 图片压缩处理
            // ===== GIF优化 =====
            imagemin.gifsicle({ 
                interlaced: true     // 交错式GIF,渐进加载
            }),
            
            // ===== JPEG优化 =====
            imagemin.mozjpeg({ 
                quality: 80,        // 压缩质量:80%(平衡质量和文件大小)
                progressive: true   // 渐进式JPEG
            }),
            
            // ===== PNG优化 =====
            imagemin.optipng({ 
                optimizationLevel: 5  // 优化级别:1-7,5是较好的平衡点
            }),
            
            // ===== SVG优化 =====
            imagemin.svgo({
                plugins: [
                    { removeViewBox: true },    // 移除viewBox(如果可以)
                    { cleanupIDs: false }       // 保留ID,避免CSS选择器失效
                ]
            })
        ]))
        .pipe(dest(`${paths.dist}/images`)); // 输出到dist/images目录
}

// ===== 字体文件复制 =====
// 将字体文件直接复制到输出目录,不做处理
function fonts() {
    return src(paths.fonts)            // 读取字体文件
        .pipe(dest(`${paths.dist}/fonts`)); // 输出到dist/fonts目录
}

// ===== 开发服务器配置 =====
// 启动本地开发服务器,支持热更新和实时刷新
function serve() {
    browserSync.init({
        server: {
            baseDir: paths.dist     // 服务器根目录
        },
        port: 3000,                // 端口号
        open: true,                 // 自动打开浏览器
        notify: false               // 不显示浏览器通知
    });
    
    // ===== 文件监听配置 =====
    // 监听不同类型的文件变化,执行相应的构建任务
    watch(paths.styles, styles);                      // SCSS文件变化时,重新编译样式
    
    watch(paths.scripts, scripts);                    // JS文件变化时,重新编译脚本
    
    // HTML文件变化时,重新复制并刷新浏览器
    watch(paths.html, html).on('change', browserSync.reload);
    
    // 图片文件变化时,重新优化并刷新浏览器
    watch(paths.images, images).on('change', browserSync.reload);
}

// ===== 构建任务组合 =====
// 使用series(串行)和parallel(并行)组合多个任务
const build = series(
    clean,                           // 首先清理目录
    parallel(                        // 然后并行执行以下任务
        html,                      // HTML处理
        styles,                    // 样式处理
        scripts,                   // 脚本处理
        images,                    // 图片优化
        fonts                      // 字体复制
    )
);

// ===== 开发任务 =====
// 先执行完整构建,然后启动开发服务器
const dev = series(build, serve);

// ===== 任务导出 =====
// 将任务导出为Gulp命令,可在命令行中使用
exports.default = dev;      // 默认任务:npm run gulp 或 npm start
exports.build = build;      // 生产构建:npm run gulp build
exports.serve = serve;      // 仅启动服务:npm run gulp serve

高级Gulp配置

javascript 复制代码
// 复杂的Gulp配置
const gulp = require('gulp');
const $ = require('gulp-load-plugins')();
const webpackStream = require('webpack-stream');
const webpack = require('webpack');
const named = require('vinyl-named');
const rev = require('gulp-rev');
const revRewrite = require('gulp-rev-rewrite');
const gulpsizereport = require('gulp-sizereport');

// 环境配置
const isProduction = process.env.NODE_ENV === 'production';

// Webpack配置
const webpackConfig = {
    mode: isProduction ? 'production' : 'development',
    devtool: isProduction ? 'source-map' : 'eval-source-map',
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env'],
                        plugins: ['@babel/plugin-transform-runtime']
                    }
                }
            }
        ]
    },
    plugins: [
        new webpack.ProvidePlugin({
            $: 'jquery',
            jQuery: 'jquery'
        })
    ],
    optimization: {
        splitChunks: {
            chunks: 'all',
            cacheGroups: {
                vendor: {
                    test: /[\\/]node_modules[\\/]/,
                    name: 'vendors',
                    chunks: 'all'
                }
            }
        }
    }
};

// 增量构建
function changed(done) {
    return $.changed('dist', { hasChanged: $.changed.compareSha1Digest });
}

// 错误处理
function handleError(error) {
    $.util.log($.util.colors.red('Error: ' + error.message));
    if (isProduction) {
        process.exit(1);
    }
    this.emit('end');
}

// 处理JavaScript
function scripts() {
    return gulp.src(['src/js/**/*.js', '!src/js/**/*.min.js'])
        .pipe(plumber({ errorHandler: handleError }))
        .pipe($.if(!isProduction, $.sourcemaps.init()))
        .pipe(named())
        .pipe(webpackStream(webpackConfig, webpack))
        .pipe($.if(isProduction, $.uglify({
            compress: { drop_console: true }
        })))
        .pipe($.if(!isProduction, $.sourcemaps.write('.')))
        .pipe($.if(isProduction, rev()))
        .pipe(dest('dist/js'))
        .pipe($.if(isProduction, rev.manifest()))
        .pipe(dest('dist/js'));
}

// 处理CSS
function styles() {
    const postcssPlugins = [
        require('autoprefixer')(),
        require('cssnano')()
    ];
    
    return gulp.src('src/scss/**/*.scss')
        .pipe(plumber({ errorHandler: handleError }))
        .pipe($.if(!isProduction, $.sourcemaps.init()))
        .pipe($.sass.sync({ outputStyle: 'expanded' }).on('error', $.sass.logError))
        .pipe($.postcss(postcssPlugins))
        .pipe($.if(isProduction, $.cleanCss()))
        .pipe($.if(isProduction, rev()))
        .pipe(dest('dist/css'))
        .pipe($.if(isProduction, rev.manifest()))
        .pipe(dest('dist/css'));
}

// 版本控制
function revision() {
    const manifest = gulp.src('dist/**/*.json');
    
    return gulp.src(['dist/**/*.html'])
        .pipe($.revRewrite({ manifest }))
        .pipe(gulp.dest('dist'));
}

// 文件大小报告
function sizeReport() {
    return gulp.src('dist/**/*')
        .pipe($.sizereport({
            gzip: true,
            total: true,
            title: 'Build Size Report'
        }));
}

// 压缩检查
function gzip() {
    return gulp.src('dist/**/*.{js,css,html}')
        .pipe($.gzip({ append: true }))
        .pipe(gulp.dest('dist'));
}

// 完整的生产构建
const production = series(
    clean,
    parallel(html, styles, scripts, images),
    revision,
    sizeReport,
    gzip
);

// 导出任务
exports.default = dev;
exports.build = build;
exports.production = production;

Vite现代化构建

Vite基础配置

javascript 复制代码
// vite.config.js - Vite构建配置文件

// ===== 导入Vite相关模块 =====
import { defineConfig } from 'vite';        // Vite配置工具函数
import vue from '@vitejs/plugin-vue';         // Vue单文件组件支持
import { resolve } from 'path';               // Node.js路径解析工具

export default defineConfig({
    // ===== 插件配置 =====
    // Vite使用插件系统来扩展功能
    plugins: [
        vue(),  // 启用Vue单文件组件(.vue文件)支持
    ],
    
    // ===== 路径别名配置 =====
    // 设置模块导入的别名,简化相对路径引用
    resolve: {
        alias: {
            // '@' 指向src目录,便于文件引用
            '@': resolve(__dirname, 'src'),
            
            // 各个子模块的别名,提高代码可读性
            '@components': resolve(__dirname, 'src/components'),
            '@utils': resolve(__dirname, 'src/utils'),
            '@assets': resolve(__dirname, 'src/assets')
        }
    },
    
    // ===== 开发服务器配置 =====
    // 配置Vite的开发服务器行为
    server: {
        host: '0.0.0.0',           // 监听所有网络接口,便于局域网访问
        port: 3000,                // 开发服务器端口号
        open: true,                 // 启动时自动打开浏览器
        cors: true,                 // 启用CORS,允许跨域请求
        
        // ===== 代理配置 =====
        // 将特定路径的请求代理到后端服务器,解决开发环境跨域问题
        proxy: {
            // API请求代理
            '/api': {
                target: 'http://localhost:8080',  // 后端API服务器地址
                changeOrigin: true,                 // 改变请求头的Origin字段
                rewrite: (path) => path.replace(/^\/api/, '') // 重写路径,移除/api前缀
            },
            
            // 文件上传代理
            '/upload': {
                target: 'http://localhost:8080',  // 文件上传服务器地址
                changeOrigin: true                  // 改变请求头Origin
            }
        }
    },
    
    // ===== 构建配置 =====
    // 生产环境打包的相关配置
    build: {
        target: 'es2015',           // 构建目标:支持ES2015语法的浏览器
        outDir: 'dist',             // 输出目录
        assetsDir: 'assets',         // 静态资源输出目录(相对于outDir)
        sourcemap: false,           // 是否生成源码映射文件,生产环境通常关闭
        
        // ===== 压缩配置 =====
        minify: 'terser',           // 使用Terser进行代码压缩(也可以用'esbuild')
        
        // ===== Terser压缩选项 =====
        terserOptions: {
            compress: {
                drop_console: true,     // 移除所有console语句
                drop_debugger: true     // 移除所有debugger语句
            }
        },
        
        // ===== 代码分割配置 =====
        // 使用Rollup的manualChunks进行手动代码分割
        rollupOptions: {
            output: {
                // 手动分割第三方库,优化缓存策略
                manualChunks: {
                    // Vue核心库单独分包
                    'vendor': ['vue', 'vue-router', 'vuex'],
                    
                    // UI组件库单独分包
                    'ui': ['element-plus'],
                    
                    // 工具库单独分包
                    'utils': ['lodash-es', 'axios']
                }
            }
        },
        
        // ===== 构建优化配置 =====
        chunkSizeWarningLimit: 1000,     // Chunk大小警告阈值(KB)
        assetsInlineLimit: 4096          // 小于4KB的资源转为base64内联
    },
    
    // ===== CSS配置 =====
    // CSS处理和预处理器配置
    css: {
        // ===== 预处理器选项 =====
        preprocessorOptions: {
            // SCSS全局变量注入
            scss: {
                // 在每个SCSS文件开头自动导入全局变量文件
                // 避免每个文件都要手动@import
                additionalData: `@import "@/styles/variables.scss";`
            }
        },
        
        // ===== CSS模块配置 =====
        modules: {
            // CSS类名转换规则:使用驼峰命名
            localsConvention: 'camelCaseOnly'
        }
    },
    
    // ===== 环境变量定义 =====
    // 在构建时定义全局常量,可在代码中直接使用
    define: {
        // 应用版本号,从package.json中读取
        __APP_VERSION__: JSON.stringify(process.env.npm_package_version)
    },
    
    // ===== 依赖优化配置 =====
    // Vite在开发时会预构建依赖,优化启动性能
    optimizeDeps: {
        // ===== 强制预构建的依赖 =====
        // 这些依赖会在开发时被预构建,避免按需转换
        include: [
            'vue',                   // Vue核心
            'vue-router',            // 路由库
            'vuex',                  // 状态管理库
            'axios',                 // HTTP客户端
            'lodash-es'              // 工具库
        ],
        
        // ===== 排除预构建的依赖 =====
        // 这些依赖不会被预构建,按需加载
        exclude: ['@babel/polyfill']   // Babel polyfill通常需要按需加载
    }
});

Vite环境配置

javascript 复制代码
// ===== 开发环境变量文件 =====
// .env.development - 开发环境专用配置
VITE_APP_TITLE=My App Development      // 应用标题(开发环境)
VITE_API_BASE_URL=http://localhost:8080/api  // API基础URL(开发环境)
VITE_APP_ENV=development              // 环境标识

// ===== 生产环境变量文件 =====
// .env.production - 生产环境专用配置
VITE_APP_TITLE=My App               // 应用标题(生产环境)
VITE_API_BASE_URL=https://api.example.com  // API基础URL(生产环境)
VITE_APP_ENV=production             // 环境标识

// ===== 预发布环境变量文件 =====
// .env.staging - 预发布环境专用配置
VITE_APP_TITLE=My App Staging        // 应用标题(预发布环境)
VITE_API_BASE_URL=https://staging-api.example.com  // API基础URL(预发布环境)
VITE_APP_ENV=staging                // 环境标识

// ===== 在Vite配置中使用环境变量 =====
// vite.config.js - 根据不同环境动态配置
export default defineConfig(({ mode }) => {
    // 根据命令行参数判断当前环境
    const isProduction = mode === 'production';
    const isStaging = mode === 'staging';
    
    return {
        // ===== 环境变量定义 =====
        // 将环境变量注入到代码中,可在构建时使用
        define: {
            // 是否为生产环境标识
            __IS_PROD__: isProduction,
            
            // 环境标识字符串
            __APP_ENV__: JSON.stringify(process.env.VITE_APP_ENV)
        },
        
        // ===== 根据环境配置构建选项 =====
        build: {
            // 生产环境启用压缩,开发环境不压缩便于调试
            minify: isProduction ? 'terser' : false,
            
            // 生产环境关闭源码映射,开发环境启用内联源码映射
            sourcemap: isProduction ? false : 'inline'
        },
        
        // ===== 根据环境配置服务器 =====
        server: {
            // 不同环境使用不同的代理配置
            proxy: {
                '/api': {
                    // 根据环境选择不同的API地址
                    target: isProduction 
                        ? 'https://api.example.com' 
                        : isStaging 
                            ? 'https://staging-api.example.com' 
                            : 'http://localhost:8080',
                    changeOrigin: true,
                    rewrite: (path) => path.replace(/^\/api/, '')
                }
            }
        }
    };
});

Vite插件开发

javascript 复制代码
// vite-plugin-custom.js - 自定义Vite插件示例

// ===== 插件主函数 =====
export function customPlugin(options = {}) {
    return {
        // ===== 插件名称 =====
        // 用于在日志和调试中识别插件
        name: 'vite-plugin-custom',
        
        // ===== 配置钩子 =====
        // 在Vite配置解析之前被调用,可以修改配置
        config(config, { command }) {
            // command: 'serve' | 'build',表示当前是开发还是构建模式
            
            // 如果是开发模式,添加开发服务器配置
            if (command === 'serve') {
                config.server = {
                    ...config.server,
                    ...options.devServer     // 合并插件传入的服务器配置
                };
            }
            
            return config; // 返回修改后的配置
        },
        
        // ===== 配置解析完成钩子 =====
        // 在Vite配置解析完成后被调用
        configResolved(config) {
            console.log('Vite config resolved:', config);
            // 这里可以验证配置或进行额外的初始化工作
        },
        
        // ===== 开发服务器配置钩子 =====
        // 在开发服务器创建后被调用,可以添加自定义中间件
        configureServer(server) {
            // 添加自定义API路由
            server.middlewares.use('/api/custom', (req, res, next) => {
                // 自定义API响应
                res.setHeader('Content-Type', 'application/json');
                res.end(JSON.stringify({
                    message: 'Custom middleware response',
                    timestamp: Date.now()
                }));
            });
        },
        
        // ===== 代码转换钩子 =====
        // 对模块的源码进行转换
        transform(code, id) {
            // code: 文件内容
            // id: 文件路径(含查询参数)
            
            // 只处理.custom.js文件
            if (id.endsWith('.custom.js')) {
                // 替换代码中的占位符
                const transformedCode = code.replace(
                    /__VERSION__/g, 
                    options.version || '1.0.0'
                );
                
                return {
                    code: transformedCode,     // 转换后的代码
                    map: null                 // 源码映射(这里不需要)
                };
            }
            
            // 返回null表示不处理此文件
            return null;
        },
        
        // ===== 构建开始钩子 =====
        // 在构建过程开始时被调用
        buildStart() {
            console.log('🚀 Build started...');
            
            // 可以在这里进行构建前的准备工作
            // 比如清理临时文件、检查依赖等
        },
        
        // ===== 构建结束钩子 =====
        // 在构建过程结束时被调用
        buildEnd() {
            console.log('✅ Build completed!');
            
            // 可以在这里进行构建后的清理工作
            // 比如发送构建通知、生成构建报告等
        },
        
        // ===== 生成钩子 =====
        // 在构建完成后,生成最终文件时被调用
        generateBundle(options, bundle) {
            // options: 生成选项
            // bundle: 包含所有生成文件的信息
            
            console.log(`Generated ${Object.keys(bundle).length} files`);
            
            // 可以在这里分析打包结果或生成额外文件
        }
    };
}

// ===== 使用自定义插件 =====
// vite.config.js
import { defineConfig } from 'vite';
import { customPlugin } from './vite-plugin-custom';

export default defineConfig({
    plugins: [
        // 使用自定义插件,传入配置选项
        customPlugin({
            version: '1.0.0',          // 插件版本号
            devServer: {                // 开发服务器配置
                port: 3001,            // 自定义端口
                open: false             // 不自动打开浏览器
            }
        })
    ]
});

工程化最佳实践

代码规范和质量控制

ESLint配置:

javascript 复制代码
// .eslintrc.js - ESLint代码检查配置文件

module.exports = {
    // ===== 运行环境配置 =====
    // 指定代码运行的环境,ESLint会根据环境自动确定全局变量
    env: {
        browser: true,    // 浏览器环境:支持window、document等全局变量
        es2021: true,     // ES2021语法环境:支持Promise、Set等
        node: true        // Node.js环境:支持require、process等全局变量
    },
    
    // ===== 继承的规则集 =====
    // 从已有的规则集继承,避免重复配置
    extends: [
        'eslint:recommended',           // ESLint推荐规则
        '@vue/standard',               // Vue.js标准规则
        '@vue/typescript/recommended',  // Vue + TypeScript推荐规则
        'prettier'                    // Prettier规则集成,避免格式冲突
    ],
    
    // ===== 解析器选项 =====
    parserOptions: {
        ecmaVersion: 'latest',     // 支持最新的ECMAScript语法
        sourceType: 'module'        // 使用ES模块语法(import/export)
    },
    
    // ===== 自定义规则 =====
    rules: {
        // ===== 调试语句规则 =====
        // 根据环境严格程度控制调试语句
        'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'warn',
        'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'warn',
        
        // ===== 代码质量规则 =====
        'no-unused-vars': 'warn',     // 未使用的变量警告(不报错,允许临时调试)
        'prefer-const': 'error',       // 优先使用const而不是let
        'no-var': 'error',             // 禁止使用var,强制使用let/const
        
        // ===== 代码风格规则 =====
        'object-shorthand': 'error',    // 对象方法使用简写语法
        'prefer-arrow-callback': 'error' // 回调函数优先使用箭头函数
    },
    
    // ===== 文件覆盖规则 =====
    // 针对特定文件类型使用不同的规则配置
    overrides: [
        {
            // Vue单文件组件的特殊规则
            files: ['*.vue'],
            rules: {
                // Vue属性每行数量限制
                'vue/max-attributes-per-line': ['error', {
                    singleline: 3,    // 单行最多3个属性
                    multiline: 1      // 多行每行1个属性
                }]
            }
        }
    ]
};

Prettier配置:

json 复制代码
// .prettierrc - Prettier代码格式化配置文件

{
    // ===== 分号配置 =====
    // false: 不使用分号
    // true: 使用分号
    "semi": false,
    
    // ===== 引号配置 =====
    // true: 优先使用单引号
    // false: 优先使用双引号
    "singleQuote": true,
    
    // ===== 缩进配置 =====
    "tabWidth": 2,        // 缩进空格数
    "useTabs": false,      // 使用空格而不是Tab进行缩进
    
    // ===== 行宽配置 =====
    "printWidth": 100,     // 每行最大字符数
    
    // ===== 括号配置 =====
    "bracketSpacing": true, // 对象字面量括号内添加空格
    "bracketSameLine": false, // JSX标签的>不与属性在同一行
    
    // ===== 箭头函数配置 =====
    // "avoid": 单参数时不加括号 (x) => {} 改为 x => {}
    // "always": 总是加括号
    "arrowParens": "avoid",
    
    // ===== 行尾配置 =====
    "endOfLine": "lf",   // 使用LF换行符(Unix风格)
    
    // ===== 尾随逗号配置 =====
    // "es5": ES5支持的对象/数组最后元素加逗号
    // "none": 不加尾随逗号
    // "all": 所有地方都加尾随逗号
    "trailingComma": "es5"
}

Husky和lint-staged:

json 复制代码
// package.json - Git钩子和代码质量检查配置

{
    "scripts": {
        // ===== 代码检查脚本 =====
        "lint": "eslint src --ext .js,.vue,.ts",
        
        // ===== 代码修复脚本 =====
        "lint:fix": "eslint src --ext .js,.vue,.ts --fix",
        
        // ===== 代码格式化脚本 =====
        "format": "prettier --write src/**/*.{js,vue,ts,scss}",
        
        // ===== Pre-commit钩子脚本 =====
        "pre-commit": "lint-staged"
    },
    
    // ===== Git钩子配置 =====
    "husky": {
        "hooks": {
            // 提交前执行代码检查
            "pre-commit": "lint-staged",
            
            // 提交信息格式检查
            "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
        }
    },
    
    // ===== 暂存文件处理配置 =====
    // 只对Git暂存区的文件执行检查,提高性能
    "lint-staged": {
        // JavaScript/Vue/TypeScript文件处理
        "*.{js,vue,ts}": [
            "eslint --fix",        // 自动修复ESLint错误
            "prettier --write",    // 格式化代码
            "git add"            // 将修复后的文件重新添加到暂存区
        ],
        
        // 样式文件处理
        "*.{scss,css}": [
            "prettier --write",    // 格式化样式
            "git add"            // 重新添加到暂存区
        ]
    }
}

Git工作流规范

yaml 复制代码
# .github/workflows/ci.yml - GitHub Actions CI/CD配置文件

# ===== 流水线名称 =====
name: CI/CD Pipeline

# ===== 触发条件 =====
# 定义什么时候触发此工作流
on:
  push:
    # 推送到以下分支时触发
    branches: [main, develop]
  pull_request:
    # 创建针对以下分支的PR时触发
    branches: [main]

# ===== 工作定义 =====
jobs:
  # ===== 测试任务 =====
  test:
    runs-on: ubuntu-latest  # 使用Ubuntu最新版作为运行环境
    
    # ===== 矩阵策略 =====
    # 在多个Node.js版本上并行测试
    strategy:
      matrix:
        node-version: [14.x, 16.x, 18.x]  # 测试多个Node.js版本
    
    # ===== 执行步骤 =====
    steps:
      # 步骤1:检出代码
      - uses: actions/checkout@v3
      
      # 步骤2:设置Node.js环境
      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'  # 启用npm缓存加速
      
      # 步骤3:安装依赖
      - name: Install dependencies
        run: npm ci  # 使用npm ci进行快速、可靠的安装
      
      # 步骤4:运行代码检查
      - name: Run lint
        run: npm run lint
      
      # 步骤5:运行测试并生成覆盖率报告
      - name: Run tests
        run: npm run test:coverage
      
      # 步骤6:上传覆盖率报告
      - name: Upload coverage
        uses: codecov/codecov-action@v3
      
      # 步骤7:构建项目
      - name: Build
        run: npm run build
      
      # 步骤8:上传构建产物
      - name: Upload build artifacts
        uses: actions/upload-artifact@v3
        with:
          name: build-${{ matrix.node-version }}
          path: dist/

  # ===== 部署任务 =====
  deploy:
    # 依赖测试任务完成
    needs: test
    runs-on: ubuntu-latest
    
    # ===== 条件部署 =====
    # 只有推送到main分支才执行部署
    if: github.ref == 'refs/heads/main'
    
    steps:
      # 步骤1:检出代码
      - uses: actions/checkout@v3
      
      # 步骤2:部署到生产环境
      - name: Deploy to production
        run: |
          echo "Deploying to production..."
          # 这里可以是实际的部署脚本
          # 例如:上传到CDN、部署到服务器等
          # docker build && docker push
          # kubectl apply -f deployment.yaml

CI/CD与部署自动化

Docker化部署

dockerfile 复制代码
# Dockerfile
# 构建阶段
FROM node:18-alpine as build-stage

WORKDIR /app

# 复制package文件
COPY package*.json ./
RUN npm ci --only=production

# 复制源代码
COPY . .

# 构建应用
RUN npm run build

# 生产阶段
FROM nginx:alpine as production-stage

# 复制构建产物
COPY --from=build-stage /app/dist /usr/share/nginx/html

# 复制nginx配置
COPY nginx.conf /etc/nginx/nginx.conf

# 暴露端口
EXPOSE 80

# 启动nginx
CMD ["nginx", "-g", "daemon off;"]
nginx 复制代码
# 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;
    
    server {
        listen 80;
        server_name localhost;
        root /usr/share/nginx/html;
        index index.html;
        
        # 启用缓存
        location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
            expires 1y;
            add_header Cache-Control "public, immutable";
        }
        
        # 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;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}

Kubernetes部署

yaml 复制代码
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend-app
  labels:
    app: frontend
spec:
  replicas: 3
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: frontend
        image: my-frontend-app:latest
        ports:
        - containerPort: 80
        resources:
          requests:
            memory: "64Mi"
            cpu: "250m"
          limits:
            memory: "128Mi"
            cpu: "500m"
        env:
        - name: API_URL
          value: "https://api.example.com"
---
apiVersion: v1
kind: Service
metadata:
  name: frontend-service
spec:
  selector:
    app: frontend
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  type: LoadBalancer
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: frontend-ingress
spec:
  rules:
  - host: example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: frontend-service
            port:
              number: 80

实战项目搭建

脚手架工具开发

javascript 复制代码
// create-vue-app.js
#!/usr/bin/env node

const { Command } = require('commander');
const inquirer = require('inquirer');
const chalk = require('chalk');
const fs = require('fs-extra');
const path = require('path');
const spawn = require('cross-spawn');

const program = new Command();

program
    .name('create-vue-app')
    .description('Create a new Vue.js project')
    .version('1.0.0')
    .argument('[project-name]', 'Project name')
    .action(async (projectName) => {
        if (!projectName) {
            const { name } = await inquirer.prompt([
                {
                    type: 'input',
                    name: 'name',
                    message: 'Project name:',
                    validate: input => input.trim() ? true : 'Project name is required'
                }
            ]);
            projectName = name;
        }
        
        const targetDir = path.resolve(process.cwd(), projectName);
        
        if (fs.existsSync(targetDir)) {
            const { overwrite } = await inquirer.prompt([
                {
                    type: 'confirm',
                    name: 'overwrite',
                    message: 'Directory already exists. Overwrite?',
                    default: false
                }
            ]);
            
            if (!overwrite) {
                console.log(chalk.yellow('Project creation cancelled.'));
                process.exit(0);
            }
            
            await fs.remove(targetDir);
        }
        
        const answers = await inquirer.prompt([
            {
                type: 'list',
                name: 'preset',
                message: 'Please pick a preset:',
                choices: [
                    {
                        name: 'Default (Vue 3 + Vite)',
                        value: 'default'
                    },
                    {
                        name: 'Default Plus (Vue 3 + Vite + TypeScript + Router + Pinia)',
                        value: 'default-plus'
                    },
                    {
                        name: 'Manually select features',
                        value: 'manual'
                    }
                ]
            }
        ]);
        
        if (answers.preset === 'manual') {
            const manualAnswers = await inquirer.prompt([
                {
                    type: 'checkbox',
                    name: 'features',
                    message: 'Check the features needed for your project:',
                    choices: [
                        { name: 'TypeScript', value: 'typescript' },
                        { name: 'Router', value: 'router' },
                        { name: 'Pinia (状态管理)', value: 'pinia' },
                        { name: 'ESLint', value: 'eslint' },
                        { name: 'Prettier', value: 'prettier' },
                        { name: 'Unit Testing', value: 'testing' },
                        { name: 'E2E Testing', value: 'e2e' }
                    ]
                }
            ]);
            
            answers.features = manualAnswers.features;
        }
        
        console.log(chalk.blue('\nCreating project...'));
        
        await createProject(projectName, targetDir, answers);
        
        console.log(chalk.green('\n✨ Project created successfully!'));
        console.log(chalk.cyan('\nNext steps:'));
        console.log(chalk.white(`  cd ${projectName}`));
        console.log(chalk.white('  npm install'));
        console.log(chalk.white('  npm run dev'));
    });

async function createProject(name, targetDir, answers) {
    // 创建项目目录
    await fs.ensureDir(targetDir);
    
    // 生成package.json
    const packageJson = generatePackageJson(name, answers);
    await fs.writeJSON(path.join(targetDir, 'package.json'), packageJson, { spaces: 2 });
    
    // 生成配置文件
    await generateConfigFiles(targetDir, answers);
    
    // 生成源代码
    await generateSourceFiles(targetDir, answers);
    
    // 安装依赖
    console.log(chalk.blue('Installing dependencies...'));
    await installDependencies(targetDir, answers);
}

function generatePackageJson(name, answers) {
    const base = {
        name,
        version: '0.0.1',
        private: true,
        scripts: {
            dev: 'vite',
            build: 'vite build',
            preview: 'vite preview'
        }
    };
    
    const dependencies = [];
    const devDependencies = ['vite'];
    
    if (answers.preset === 'default' || answers.preset === 'default-plus' || answers.features?.includes('typescript')) {
        devDependencies.push('@vitejs/plugin-vue');
        dependencies.push('vue');
    }
    
    if (answers.preset === 'default-plus' || answers.features?.includes('router')) {
        dependencies.push('vue-router');
    }
    
    if (answers.preset === 'default-plus' || answers.features?.includes('pinia')) {
        dependencies.push('pinia');
    }
    
    if (answers.features?.includes('typescript')) {
        devDependencies.push('typescript', 'vue-tsc');
        base.scripts.typecheck = 'vue-tsc --noEmit';
    }
    
    return {
        ...base,
        dependencies: dependencies.length ? dependencies : undefined,
        devDependencies
    };
}

async function installDependencies(targetDir, answers) {
    return new Promise((resolve, reject) => {
        const child = spawn('npm', ['install'], {
            cwd: targetDir,
            stdio: 'inherit'
        });
        
        child.on('close', (code) => {
            if (code !== 0) {
                reject(new Error('npm install failed'));
            } else {
                resolve();
            }
        });
    });
}

program.parse();

项目结构模板

bash 复制代码
my-project/
├── public/                 # 静态资源
│   ├── favicon.ico
│   └── index.html
├── src/                   # 源代码
│   ├── api/              # API接口
│   ├── assets/           # 静态资源
│   ├── components/       # 组件
│   │   ├── common/       # 公共组件
│   │   └── business/     # 业务组件
│   ├── composables/      # 组合式函数
│   ├── layouts/          # 布局组件
│   ├── pages/            # 页面
│   ├── router/           # 路由配置
│   ├── stores/           # 状态管理
│   ├── styles/           # 样式文件
│   ├── utils/            # 工具函数
│   ├── types/            # TypeScript类型
│   ├── App.vue           # 根组件
│   └── main.ts           # 入口文件
├── tests/                # 测试文件
│   ├── unit/             # 单元测试
│   └── e2e/              # E2E测试
├── .env                  # 环境变量
├── .env.development
├── .env.production
├── .eslintrc.js          # ESLint配置
├── .prettierrc           # Prettier配置
├── package.json
├── tsconfig.json         # TypeScript配置
├── vite.config.ts        # Vite配置
└── README.md

性能优化与监控

性能监控配置

javascript 复制代码
// 性能监控工具
class PerformanceMonitor {
    constructor() {
        this.metrics = {
            navigation: {},
            resources: [],
            paint: {},
            memory: {}
        };
        
        this.init();
    }
    
    init() {
        this.observeNavigation();
        this.observeResources();
        this.observePaint();
        this.observeMemory();
        this.observeLayoutShift();
    }
    
    observeNavigation() {
        if (performance.timing) {
            const timing = performance.timing;
            this.metrics.navigation = {
                dns: timing.domainLookupEnd - timing.domainLookupStart,
                tcp: timing.connectEnd - timing.connectStart,
                request: timing.responseStart - timing.requestStart,
                response: timing.responseEnd - timing.responseStart,
                dom: timing.domContentLoadedEventEnd - timing.domContentLoadedEventStart,
                load: timing.loadEventEnd - timing.loadEventStart,
                total: timing.loadEventEnd - timing.navigationStart
            };
        }
    }
    
    observeResources() {
        const resources = performance.getEntriesByType('resource');
        this.metrics.resources = resources.map(resource => ({
            name: resource.name,
            type: this.getResourceType(resource.name),
            duration: resource.duration,
            size: resource.transferSize || 0
        }));
    }
    
    observePaint() {
        const paintEntries = performance.getEntriesByType('paint');
        paintEntries.forEach(entry => {
            this.metrics.paint[entry.name] = entry.startTime;
        });
    }
    
    observeMemory() {
        if (performance.memory) {
            this.metrics.memory = {
                used: Math.round(performance.memory.usedJSHeapSize / 1048576),
                total: Math.round(performance.memory.totalJSHeapSize / 1048576),
                limit: Math.round(performance.memory.jsHeapSizeLimit / 1048576)
            };
        }
    }
    
    observeLayoutShift() {
        let cumulativeLayoutShift = 0;
        
        const observer = new PerformanceObserver((list) => {
            for (const entry of list.getEntries()) {
                if (!entry.hadRecentInput) {
                    cumulativeLayoutShift += entry.value;
                }
            }
            
            this.metrics.cls = cumulativeLayoutShift;
        });
        
        observer.observe({ entryTypes: ['layout-shift'] });
    }
    
    getResourceType(url) {
        if (url.includes('.css')) return 'css';
        if (url.includes('.js')) return 'javascript';
        if (url.match(/\.(png|jpg|jpeg|gif|svg|webp)$/)) return 'image';
        if (url.match(/\.(woff|woff2|ttf|eot)$/)) return 'font';
        return 'other';
    }
    
    getReport() {
        return {
            ...this.metrics,
            timestamp: Date.now(),
            userAgent: navigator.userAgent,
            url: window.location.href
        };
    }
    
    sendReport() {
        const report = this.getReport();
        
        // 发送到监控系统
        fetch('/api/performance', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(report)
        }).catch(error => {
            console.error('Failed to send performance report:', error);
        });
    }
}

// 初始化监控
const monitor = new PerformanceMonitor();

// 页面加载完成后发送报告
window.addEventListener('load', () => {
    setTimeout(() => {
        monitor.sendReport();
    }, 1000);
});

Bundle分析工具

javascript 复制代码
// webpack-bundle-analyzer集成
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
    plugins: [
        new BundleAnalyzerPlugin({
            analyzerMode: 'static',
            openAnalyzer: false,
            reportFilename: 'bundle-report.html',
            defaultSizes: 'parsed',
            generateStatsFile: true,
            statsFilename: 'bundle-stats.json',
            statsOptions: {
                source: false,
                modules: true,
                chunks: true,
                chunkModules: true
            }
        })
    ]
};

🚀 落地实战项目案例

微前端架构实践

javascript 复制代码
// 主应用配置 - qiankun
import { registerMicroApps, start } from 'qiankun';

registerMicroApps([
  {
    name: 'vue-app',
    entry: '//localhost:8081',
    container: '#vue-app',
    activeRule: '/vue',
    props: { data: 'main-app-data' }
  },
  {
    name: 'react-app',
    entry: '//localhost:8082',
    container: '#react-app',
    activeRule: '/react'
  }
]);

start({
  sandbox: {
    experimentalStyleIsolation: true,
    strictStyleIsolation: false
  },
  prefetch: true
});

// 微应用配置
// vue-app/src/main.js
import Vue from 'vue';
import App from './App.vue';
import { registerMicroApps, start } from 'qiankun';

let instance = null;

function render(props = {}) {
  const { container } = props;
  instance = new Vue({
    render: h => h(App)
  }).$mount(container ? container.querySelector('#app') : '#app');
}

if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

export async function bootstrap() {
  console.log('[vue-app] bootstrap');
}

export async function mount(props) {
  console.log('[vue-app] mount', props);
  render(props);
}

export async function unmount() {
  console.log('[vue-app] unmount');
  instance.$destroy();
  instance.$el.innerHTML = '';
  instance = null;
}

Monorepo工程架构

json 复制代码
// package.json (根目录)
{
  "name": "frontend-monorepo",
  "private": true,
  "workspaces": [
    "packages/*",
    "apps/*"
  ],
  "scripts": {
    "dev": "turbo run dev",
    "build": "turbo run build",
    "lint": "turbo run lint",
    "test": "turbo run test",
    "clean": "turbo run clean && rm -rf node_modules"
  },
  "devDependencies": {
    "turbo": "^1.6.3",
    "@changesets/cli": "^2.24.4"
  }
}

// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", "build/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    },
    "lint": {
      "outputs": []
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": ["coverage/**"]
    }
  }
}
bash 复制代码
frontend-monorepo/
├── packages/
│   ├── ui/              # UI组件库
│   ├── utils/           # 工具库
│   ├── hooks/           # 自定义hooks
│   └── types/           # 类型定义
├── apps/
│   ├── admin/           # 管理后台
│   ├── mobile/          # 移动端应用
│   └── docs/            # 文档站点
├── tools/
│   ├── build/           # 构建工具
│   └── scripts/         # 脚本工具
└── shared/              # 共享配置
    ├── eslint-config/
    ├── tsconfig/
    └── webpack-config/

组件库工程化

javascript 复制代码
// packages/ui/build.js
const { build } = require('esbuild');
const { glob } = require('glob');
const { resolve } = require('path');

async function buildLibrary() {
  // 入口文件
  const entryPoints = await glob('./src/**/*.{js,ts,jsx,tsx}');
  
  // 构建ESM
  await build({
    entryPoints,
    bundle: false,
    outdir: 'dist/es',
    format: 'esm',
    target: ['es2018'],
    sourcemap: true,
    tsconfig: './tsconfig.json'
  });
  
  // 构建CJS
  await build({
    entryPoints,
    bundle: false,
    outdir: 'dist/lib',
    format: 'cjs',
    target: ['es2018'],
    sourcemap: true,
    tsconfig: './tsconfig.json'
  });
  
  // 生成类型声明
  await build({
    entryPoints: './src/index.ts',
    bundle: false,
    outdir: 'dist/types',
    format: 'esm',
    target: ['es2018'],
    tsconfig: './tsconfig.json',
    jsxFactory: 'React.createElement',
    jsxFragment: 'React.Fragment'
  });
}

buildLibrary();

// packages/ui/.storybook/main.js
module.exports = {
  stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
  addons: [
    '@storybook/addon-essentials',
    '@storybook/addon-interactions',
    '@storybook/addon-docs'
  ],
  framework: {
    name: '@storybook/react-webpack5',
    options: {}
  },
  webpackFinal: async (config) => {
    // 自定义webpack配置
    config.resolve.alias = {
      ...config.resolve.alias,
      '@': resolve(__dirname, '../src')
    };
    
    return config;
  }
};

低代码平台工程化

javascript 复制代码
// 低代码引擎核心
class LowCodeEngine {
  constructor() {
    this.components = new Map();
    this.schemas = new Map();
    this.plugins = [];
  }
  
  // 注册组件
  registerComponent(name, component) {
    this.components.set(name, component);
  }
  
  // 注册插件
  registerPlugin(plugin) {
    this.plugins.push(plugin);
    plugin.init(this);
  }
  
  // 解析schema并渲染
  render(schema) {
    const Component = this.components.get(schema.type);
    if (!Component) {
      throw new Error(`Component ${schema.type} not found`);
    }
    
    const props = {
      ...schema.props,
      children: schema.children?.map(child => this.render(child))
    };
    
    return React.createElement(Component, props, props.children);
  }
  
  // 页面构建器
  buildPage(schema) {
    return this.render(schema);
  }
}

// 拖拽系统
class DragDropSystem {
  constructor(container) {
    this.container = container;
    this.draggedElement = null;
    this.dropZones = [];
    this.init();
  }
  
  init() {
    this.container.addEventListener('dragstart', this.handleDragStart.bind(this));
    this.container.addEventListener('dragover', this.handleDragOver.bind(this));
    this.container.addEventListener('drop', this.handleDrop.bind(this));
  }
  
  handleDragStart(e) {
    this.draggedElement = e.target;
    e.dataTransfer.effectAllowed = 'move';
  }
  
  handleDragOver(e) {
    e.preventDefault();
    e.dataTransfer.dropEffect = 'move';
  }
  
  handleDrop(e) {
    e.preventDefault();
    if (this.draggedElement && e.target.classList.contains('drop-zone')) {
      e.target.appendChild(this.draggedElement);
      this.onDrop(this.draggedElement, e.target);
    }
  }
}

📊 企业级监控体系

错误监控系统

javascript 复制代码
// 错误监控SDK - 前端错误采集和上报系统

class ErrorMonitor {
  constructor(config) {
    // ===== 配置初始化 =====
    // 合并用户配置和默认配置
    this.config = {
      apiUrl: '/api/errors',           // 错误上报API地址
      maxErrors: 50,                 // 本地最大错误缓存数
      sampleRate: 1,                  // 采样率(1=100%,0.1=10%)
      ...config                       // 合并用户自定义配置
    };
    
    this.errors = [];                 // 本地错误缓存数组
    this.init();                      // 初始化监控
  }
  
  // ===== 初始化方法 =====
  // 启动所有错误监听机制
  init() {
    this.captureGlobalErrors();        // 监听全局JavaScript错误
    this.captureUnhandledRejections(); // 监听未处理的Promise rejection
    this.captureResourceErrors();     // 监听资源加载错误
    this.startReporting();            // 启动定时上报机制
  }
  
  // ===== 全局JavaScript错误监听 =====
  // 捕获所有未处理的JavaScript运行时错误
  captureGlobalErrors() {
    window.addEventListener('error', (event) => {
      // 构建错误对象,包含详细的错误信息
      this.captureError({
        type: 'javascript',           // 错误类型:JavaScript运行时错误
        message: event.message,        // 错误消息
        filename: event.filename,      // 出错的文件名
        lineno: event.lineno,          // 错误行号
        colno: event.colno,           // 错误列号
        stack: event.error?.stack     // 错误堆栈信息
      });
    });
  }
  
  // ===== Promise rejection监听 =====
  // 捕获所有未处理的Promise rejection
  captureUnhandledRejections() {
    window.addEventListener('unhandledrejection', (event) => {
      this.captureError({
        type: 'promise',                           // 错误类型:Promise错误
        message: event.reason?.message || 'Unhandled Promise Rejection', // 错误消息
        stack: event.reason?.stack                  // 错误堆栈
      });
    });
  }
  
  // ===== 资源加载错误监听 =====
  // 捕获图片、脚本、样式等资源加载失败
  captureResourceErrors() {
    // 使用捕获阶段监听,能获取到资源加载错误
    window.addEventListener('error', (event) => {
      // event.target !== window 确保是资源错误,不是JS错误
      if (event.target !== window) {
        this.captureError({
          type: 'resource',                                      // 错误类型:资源加载错误
          message: `Failed to load ${event.target.tagName}`,         // 错误描述
          resource: event.target.src || event.target.href,          // 失败的资源URL
          type: event.target.tagName.toLowerCase()                   // 资源类型(img, script等)
        });
      }
    }, true);  // true表示使用捕获阶段
  }
  
  // ===== 错误捕获核心方法 =====
  // 统一处理所有类型的错误
  captureError(error) {
    // ===== 采样控制 =====
    // 根据采样率决定是否处理此错误,减少上报量
    if (Math.random() > this.config.sampleRate) {
      return;
    }
    
    // ===== 错误信息增强 =====
    // 添加环境信息和上下文信息
    const enrichedError = {
      ...error,                           // 原始错误信息
      timestamp: Date.now(),               // 错误发生时间戳
      url: window.location.href,           // 当前页面URL
      userAgent: navigator.userAgent,       // 浏览器用户代理
      sessionId: this.getSessionId(),      // 会话ID(用于关联同一用户的错误)
      userId: this.getUserId()            // 用户ID(需要用户自己实现)
    };
    
    // ===== 错误缓存 =====
    // 将错误添加到本地缓存
    this.errors.push(enrichedError);
    
    // ===== 缓存大小控制 =====
    // 如果缓存超过最大值,移除最旧的错误
    if (this.errors.length > this.config.maxErrors) {
      this.errors.shift();
    }
  }
  
  // ===== 错误上报方法 =====
  // 将缓存中的错误批量发送到服务器
  async reportErrors() {
    // 如果没有错误,直接返回
    if (this.errors.length === 0) return;
    
    // 复制错误数组并清空缓存
    const errorsToSend = [...this.errors];
    this.errors = [];
    
    try {
      // 发送错误到监控服务器
      await fetch(this.config.apiUrl, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          errors: errorsToSend,          // 错误数组
          timestamp: Date.now()          // 上报时间戳
        })
      });
    } catch (error) {
      // ===== 上报失败处理 =====
      // 将错误重新添加到缓存头部,下次重试
      console.error('Failed to report errors:', error);
      this.errors.unshift(...errorsToSend);
    }
  }
  
  // ===== 启动定时上报 =====
  startReporting() {
    // 定时上报:每30秒上报一次
    setInterval(() => this.reportErrors(), 30000);
    
    // 页面卸载时上报:确保用户离开页面前上报所有错误
    window.addEventListener('beforeunload', () => {
      this.reportErrors();
    });
  }
  
  // ===== 会话ID生成 =====
  // 生成唯一的会话标识符
  getSessionId() {
    // 从sessionStorage获取或生成新的
    if (!sessionStorage.getItem('sessionId')) {
      const sessionId = 'session_' + 
        Math.random().toString(36).substr(2, 9) + 
        Date.now();
      sessionStorage.setItem('sessionId', sessionId);
      return sessionId;
    }
    return sessionStorage.getItem('sessionId');
  }
  
  // ===== 用户ID获取 =====
  // 获取当前用户ID(需要根据具体业务实现)
  getUserId() {
    // 可以从cookie、localStorage、全局变量等获取
    return localStorage.getItem('userId') || 'anonymous';
  }
}

// ===== 初始化错误监控 =====
// 在应用入口处初始化监控系统
new ErrorMonitor({
  apiUrl: 'https://monitor.example.com/api/errors',  // 监控API地址
  sampleRate: 0.1                                    // 10%采样率,减少上报量
});

性能监控系统

javascript 复制代码
// Web Vitals监控 - 核心Web性能指标监控

// 导入web-vitals库的核心指标函数
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';

class PerformanceMonitor {
  constructor() {
    this.metrics = {};           // 存储性能指标
    this.init();                // 初始化监控
  }
  
  // ===== 初始化方法 =====
  init() {
    this.observeWebVitals();           // 监控核心Web性能指标
    this.observeUserInteractions();    // 监控用户交互性能
    this.observeAPIPerformance();     // 监控API请求性能
  }
  
  // ===== Web Vitals指标监控 =====
  // 监控Google推荐的核心Web性能指标
  observeWebVitals() {
    // CLS - Cumulative Layout Shift(累积布局偏移)
    // 衡量页面视觉稳定性,值越小越好(<0.1为良好)
    getCLS((metric) => this.recordMetric('CLS', metric));
    
    // FID - First Input Delay(首次输入延迟)
    // 衡量页面交互性,值越小越好(<100ms为良好)
    getFID((metric) => this.recordMetric('FID', metric));
    
    // FCP - First Contentful Paint(首次内容绘制)
    // 衡量页面加载速度,值越小越好(<1.8s为良好)
    getFCP((metric) => this.recordMetric('FCP', metric));
    
    // LCP - Largest Contentful Paint(最大内容绘制)
    // 衡量页面主要内容加载速度,值越小越好(<2.5s为良好)
    getLCP((metric) => this.recordMetric('LCP', metric));
    
    // TTFB - Time to First Byte(首字节时间)
    // 衡量服务器响应速度,值越小越好(<800ms为良好)
    getTTFB((metric) => this.recordMetric('TTFB', metric));
  }
  
  // ===== 用户交互性能监控 =====
  // 监控用户的点击、输入等交互的响应延迟
  observeUserInteractions() {
    // 监控点击响应延迟
    document.addEventListener('click', (event) => {
      const startTime = performance.now();  // 记录点击开始时间
      
      // 使用requestAnimationFrame确保在下一帧测量
      requestAnimationFrame(() => {
        const clickDelay = performance.now() - startTime;
        this.recordMetric('ClickDelay', { value: clickDelay });
      });
    });
    
    // 监控键盘输入响应延迟
    document.addEventListener('keydown', (event) => {
      const startTime = performance.now();
      
      requestAnimationFrame(() => {
        const keyDelay = performance.now() - startTime;
        this.recordMetric('KeyDelay', { value: keyDelay });
      });
    });
  }
  
  // ===== API性能监控 =====
  // 拦截fetch API,监控所有网络请求的性能
  observeAPIPerformance() {
    // 保存原始的fetch函数
    const originalFetch = window.fetch;
    
    // 重写fetch函数,添加性能监控
    window.fetch = async (...args) => {
      const startTime = performance.now();  // 记录请求开始时间
      
      try {
        // 执行原始fetch请求
        const response = await originalFetch(...args);
        const endTime = performance.now();   // 记录请求结束时间
        
        // 记录成功的API请求指标
        this.recordMetric('APIRequest', {
          url: args[0],                           // 请求URL
          method: args[1]?.method || 'GET',        // 请求方法
          status: response.status,                   // 响应状态码
          duration: endTime - startTime,            // 请求耗时
          size: response.headers.get('content-length') // 响应大小
        });
        
        return response;
      } catch (error) {
        const endTime = performance.now();
        
        // 记录失败的API请求指标
        this.recordMetric('APIError', {
          url: args[0],                           // 请求URL
          error: error.message,                    // 错误信息
          duration: endTime - startTime             // 请求耗时
        });
        
        // 继续抛出原始错误
        throw error;
      }
    };
  }
  
  // ===== 指标记录方法 =====
  // 统一记录各种性能指标
  recordMetric(name, metric) {
    // 存储指标数据
    this.metrics[name] = {
      ...metric,                   // 原始指标数据
      timestamp: Date.now(),       // 记录时间
      url: window.location.href    // 页面URL
    };
    
    // 立即发送指标到服务器
    this.sendMetric(name, metric);
  }
  
  // ===== 指标上报方法 =====
  // 将性能指标发送到监控服务器
  async sendMetric(name, metric) {
    try {
      await fetch('/api/metrics', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          name: name,                        // 指标名称
          metric: metric,                    // 指标数据
          url: window.location.href,          // 页面URL
          userAgent: navigator.userAgent,       // 浏览器信息
          timestamp: Date.now()               // 上报时间
        })
      });
    } catch (error) {
      console.error('Failed to send metric:', error);
      // 可以考虑添加重试逻辑或本地缓存
    }
  }
  
  // ===== 性能报告生成 =====
  // 生成性能报告摘要
  getPerformanceReport() {
    return {
      // 核心Web Vitals指标
      vitals: {
        cls: this.metrics.CLS?.value || 0,
        fid: this.metrics.FID?.value || 0,
        fcp: this.metrics.FCP?.value || 0,
        lcp: this.metrics.LCP?.value || 0,
        ttfb: this.metrics.TTFB?.value || 0
      },
      
      // 交互性能指标
      interactions: {
        clickDelay: this.metrics.ClickDelay?.value || 0,
        keyDelay: this.metrics.KeyDelay?.value || 0
      },
      
      // 网络请求性能
      network: {
        avgResponseTime: this.calculateAverageResponseTime(),
        errorRate: this.calculateErrorRate()
      },
      
      // 页面信息
      page: {
        url: window.location.href,
        timestamp: Date.now()
      }
    };
  }
  
  // ===== 计算平均响应时间 =====
  calculateAverageResponseTime() {
    const requests = Object.values(this.metrics)
      .filter(metric => metric.name === 'APIRequest');
    
    if (requests.length === 0) return 0;
    
    const totalTime = requests.reduce((sum, req) => sum + req.duration, 0);
    return totalTime / requests.length;
  }
  
  // ===== 计算错误率 =====
  calculateErrorRate() {
    const allRequests = Object.values(this.metrics)
      .filter(metric => metric.name.startsWith('API'));
    
    if (allRequests.length === 0) return 0;
    
    const errors = allRequests.filter(metric => metric.name === 'APIError');
    return (errors.length / allRequests.length) * 100;
  }
}

// ===== 初始化性能监控 =====
// 在应用入口处初始化性能监控系统
new PerformanceMonitor();

用户行为分析

javascript 复制代码
// 用户行为追踪 - 用户交互行为数据采集系统

class UserBehaviorTracker {
  constructor() {
    this.events = [];                          // 本地事件缓存
    this.sessionId = this.generateSessionId();   // 会话唯一标识
    this.init();                               // 初始化追踪
  }
  
  // ===== 初始化方法 =====
  init() {
    this.trackPageViews();          // 页面访问追踪
    this.trackClicks();            // 点击行为追踪
    this.trackScrolls();           // 滚动行为追踪
    this.trackFormInteractions();   // 表单交互追踪
    this.startBatching();          // 启动批量上报
  }
  
  // ===== 页面访问追踪 =====
  trackPageViews() {
    // 页面首次加载时的访问记录
    this.track('page_view', {
      path: window.location.pathname,        // 页面路径
      referrer: document.referrer,          // 来源页面
      title: document.title,               // 页面标题
      timestamp: Date.now(),               // 访问时间
      userAgent: navigator.userAgent         // 浏览器信息
    });
    
    // SPA路由变化监听(监听浏览器历史记录变化)
    window.addEventListener('popstate', () => {
      this.track('page_view', {
        path: window.location.pathname,    // 新页面路径
        type: 'spa_navigation'            // 标记为SPA路由跳转
      });
    });
    
    // 监听pushState和replaceState(大多数SPA框架的路由方法)
    const originalPushState = history.pushState;
    const originalReplaceState = history.replaceState;
    
    history.pushState = function(...args) {
      originalPushState.apply(this, args);
      window.dispatchEvent(new Event('popstate'));
    };
    
    history.replaceState = function(...args) {
      originalReplaceState.apply(this, args);
      window.dispatchEvent(new Event('popstate'));
    };
  }
  
  // ===== 点击行为追踪 =====
  trackClicks() {
    document.addEventListener('click', (event) => {
      const target = event.target;
      
      // 构建点击事件数据
      this.track('click', {
        elementType: target.tagName.toLowerCase(),    // 元素类型
        elementClass: target.className,            // 元素类名
        elementId: target.id,                     // 元素ID
        text: target.textContent?.slice(0, 100),  // 元素文本(截取前100字符)
        attributes: this.getElementAttributes(target), // 元素属性
        coordinates: {                             // 点击坐标
          x: event.clientX,
          y: event.clientY
        },
        viewportSize: {                            // 视窗大小
          width: window.innerWidth,
          height: window.innerHeight
        }
      });
    });
  }
  
  // ===== 滚动行为追踪 =====
  trackScrolls() {
    let lastScrollDepth = 0;     // 记录上次滚动深度
    let scrollTimeout = null;      // 防抖定时器
    
    window.addEventListener('scroll', () => {
      // 防抖处理,避免频繁触发
      clearTimeout(scrollTimeout);
      
      scrollTimeout = setTimeout(() => {
        // 计算当前滚动深度百分比
        const scrollDepth = Math.round(
          (window.scrollY / (document.body.scrollHeight - window.innerHeight)) * 100
        );
        
        // 只有在滚动深度增加时才记录(避免重复记录同一深度)
        if (scrollDepth > lastScrollDepth) {
          this.track('scroll', {
            depth: scrollDepth,                          // 滚动深度百分比
            scrollPosition: window.scrollY,               // 滚动位置
            pageHeight: document.body.scrollHeight,        // 页面总高度
            viewportHeight: window.innerHeight,            // 视窗高度
            timestamp: Date.now()                       // 滚动时间
          });
          lastScrollDepth = scrollDepth;
        }
      }, 100); // 100ms防抖
    });
  }
  
  // ===== 表单交互追踪 =====
  trackFormInteractions() {
    // 表单元素变化追踪(input、select、textarea)
    document.addEventListener('change', (event) => {
      const target = event.target;
      
      if (target.tagName === 'INPUT' || target.tagName === 'SELECT' || target.tagName === 'TEXTAREA') {
        this.track('form_interaction', {
          fieldType: target.type,                       // 字段类型
          fieldName: target.name,                       // 字段名称
          fieldValue: this.sanitizeValue(target.value),    // 字段值(脱敏处理)
          elementId: target.id,                         // 元素ID
          formId: target.form?.id,                     // 表单ID
          timestamp: Date.now()                          // 交互时间
        });
      }
    });
    
    // 表单提交追踪
    document.addEventListener('submit', (event) => {
      const form = event.target;
      
      this.track('form_submit', {
        formId: form.id,                              // 表单ID
        formName: form.name,                            // 表单名称
        formAction: form.action,                        // 表单提交地址
        formMethod: form.method,                        // 提交方法
        fieldCount: form.elements.length,                // 字段数量
        timestamp: Date.now()                           // 提交时间
      });
    });
    
    // 表单聚焦/失焦追踪(可选)
    document.addEventListener('focus', (event) => {
      if (['INPUT', 'SELECT', 'TEXTAREA'].includes(event.target.tagName)) {
        this.track('form_focus', {
          fieldName: event.target.name,
          fieldType: event.target.type,
          timestamp: Date.now()
        });
      }
    }, true);
  }
  
  // ===== 事件追踪核心方法 =====
  // 统一处理所有用户行为事件
  track(eventName, data) {
    // 构建完整的事件对象
    const event = {
      eventName,                              // 事件名称
      data,                                   // 事件数据
      timestamp: Date.now(),                   // 事件时间戳
      sessionId: this.sessionId,               // 会话ID
      userId: this.getUserId(),                // 用户ID
      url: window.location.href,              // 当前页面URL
      userAgent: navigator.userAgent,          // 浏览器信息
      screenResolution: {                     // 屏幕分辨率
        width: screen.width,
        height: screen.height
      }
    };
    
    // 添加到本地缓存
    this.events.push(event);
  }
  
  // ===== 批量上报机制 =====
  startBatching() {
    // 定时批量上报:每30秒上报一次
    const batchInterval = setInterval(() => {
      this.sendBatch();
    }, 30000);
    
    // 页面可见性变化时上报(用户切换标签页)
    document.addEventListener('visibilitychange', () => {
      if (document.hidden) {
        this.sendBatch();
      }
    });
    
    // 页面卸载时上报:确保数据不丢失
    window.addEventListener('beforeunload', () => {
      this.sendBatch();
      clearInterval(batchInterval);
    });
  }
  
  // ===== 事件上报方法 =====
  async sendBatch() {
    // 如果没有事件,直接返回
    if (this.events.length === 0) return;
    
    // 复制事件数组并清空缓存
    const eventsToSend = [...this.events];
    this.events = [];
    
    try {
      // 发送批量事件到服务器
      await fetch('/api/events', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          events: eventsToSend,                 // 事件数组
          batchId: this.generateBatchId(),      // 批次ID
          timestamp: Date.now()                 // 上报时间
        })
      });
    } catch (error) {
      // 上报失败处理:将事件重新添加到缓存头部
      console.error('Failed to send events:', error);
      this.events.unshift(...eventsToSend);
    }
  }
  
  // ===== 工具方法 =====
  
  // 生成会话ID
  generateSessionId() {
    return 'session_' + 
      Math.random().toString(36).substr(2, 9) +  // 随机字符串
      Date.now();                                 // 时间戳
  }
  
  // 生成批次ID
  generateBatchId() {
    return 'batch_' + Date.now() + '_' + Math.random().toString(36).substr(2, 5);
  }
  
  // 获取用户ID
  getUserId() {
    return localStorage.getItem('userId') || 'anonymous';
  }
  
  // 获取元素属性
  getElementAttributes(element) {
    const attributes = {};
    for (let attr of element.attributes) {
      if (['id', 'class', 'href', 'src', 'alt', 'title'].includes(attr.name)) {
        attributes[attr.name] = attr.value;
      }
    }
    return attributes;
  }
  
  // 数据脱敏处理
  sanitizeValue(value) {
    if (typeof value !== 'string') return value;
    
    // 密码字段完全隐藏
    if (value.length > 0 && ['password', 'pwd', 'pass'].some(keyword => 
        this.currentFieldName?.toLowerCase().includes(keyword))) {
      return '******';
    }
    
    // 邮箱部分脱敏
    if (value.includes('@')) {
      const [username, domain] = value.split('@');
      return username.substring(0, 2) + '***@' + domain;
    }
    
    // 手机号脱敏
    if (/^\d{11}$/.test(value)) {
      return value.substring(0, 3) + '****' + value.substring(7);
    }
    
    // 其他长文本截断
    return value.length > 50 ? value.substring(0, 50) + '...' : value;
  }
}

// ===== 初始化用户行为追踪 =====
// 在应用入口处初始化行为追踪系统
const behaviorTracker = new UserBehaviorTracker();

// 暴露全局接口,供业务代码调用自定义事件
window.trackEvent = (eventName, data) => {
  behaviorTracker.track(eventName, data);
};

window.trackCustomGoal = (goalName, value) => {
  behaviorTracker.track('goal_conversion', {
    goalName,
    value,
    timestamp: Date.now()
  });
};

🎯 总结

这个前端工程化终极指南提供了从基础到高级的完整解决方案:

📋 核心覆盖内容

  1. 构建工具深入

    • Webpack:核心概念、高级配置、性能优化
    • Gulp:任务流管理、自动化构建
    • Vite:现代构建、开发体验优化
  2. 工程化最佳实践

    • 代码规范:ESLint、Prettier、Git Hooks
    • 工作流规范:分支管理、提交规范
    • Monorepo架构:多项目管理
  3. CI/CD与部署

    • Docker容器化部署
    • Kubernetes集群管理
    • 自动化流水线
  4. 实战项目落地

    • 微前端架构实现
    • 组件库工程化
    • 低代码平台搭建
  5. 监控与优化

    • 错误监控系统
    • 性能监控体系
    • 用户行为分析

🚀 技术亮点

  • 深度技术解析:每个工具都有原理解释和实战配置
  • 企业级方案:包含Monorepo、微前端等企业架构
  • 完整代码示例:所有配置和工具都可以直接使用
  • 性能优化专项:从构建到运行的全链路优化
  • 监控体系建设:错误、性能、行为的全方位监控
相关推荐
GIS之路8 分钟前
GDAL 实现矢量裁剪
前端·python·信息可视化
是一个Bug12 分钟前
后端开发者视角的前端开发面试题清单(50道)
前端
Amumu1213814 分钟前
React面向组件编程
开发语言·前端·javascript
持续升级打怪中35 分钟前
Vue3 中虚拟滚动与分页加载的实现原理与实践
前端·性能优化
GIS之路39 分钟前
GDAL 实现矢量合并
前端
hxjhnct41 分钟前
React useContext的缺陷
前端·react.js·前端框架
冰暮流星1 小时前
javascript逻辑运算符
开发语言·javascript·ecmascript
前端 贾公子1 小时前
从入门到实践:前端 Monorepo 工程化实战(4)
前端
菩提小狗1 小时前
Sqlmap双击运行脚本,双击直接打开。
前端·笔记·安全·web安全
前端工作日常1 小时前
我学习到的AG-UI的概念
前端