前端工程化终极指南(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、微前端等企业架构
  • 完整代码示例:所有配置和工具都可以直接使用
  • 性能优化专项:从构建到运行的全链路优化
  • 监控体系建设:错误、性能、行为的全方位监控
相关推荐
比老马还六3 小时前
Bipes项目二次开发/海龟编程(六)
前端·javascript
梨子同志3 小时前
Node.js 文件系统 fs
前端
码农胖大海3 小时前
微前端架构(二):封装与实现
前端
瘦的可以下饭了3 小时前
2 数组 递归 复杂度 字符串
前端·javascript
Kellen3 小时前
ReactDOM.preload
前端·react.js
weixin_462446233 小时前
nodejs 下使用 Prettier 美化单个 JS 文件(完整教程)
开发语言·javascript·ecmascript
岭子笑笑3 小时前
vant 4 之loading组件源码阅读
前端
hxmmm4 小时前
自定义封装 vue多页项目新增项目脚手架
前端·javascript·node.js
ETA84 小时前
JS执行机制揭秘:你以为的“顺序执行”,其实是V8引擎在背后搞事情!
前端·javascript