前端工程化与性能优化指南
一、Webpack 核心配置与原理
1.1 Webpack 基础配置手写
1.1.1 最小化 Webpack 配置
javascript
复制代码
// webpack.config.js
// 1. 引入 Node.js 路径处理模块,用于处理文件路径
const path = require('path');
module.exports = {
// 2. mode 指定构建模式
// - 'development':开发模式,代码不压缩,生成 sourcemap
// - 'production':生产模式,代码压缩,启用 tree-shaking
mode: 'development',
// 3. entry 指定入口文件
// Webpack 从这里开始递归解析所有模块,构建依赖图
entry: './src/index.js',
// 4. output 指定输出配置
output: {
// 5. filename 输出文件名
// [name] 是入口名称,[hash] 是本次构建的 hash 值
// hash 用于解决缓存问题:当文件内容变化时,hash 变化,浏览器重新请求
filename: '[name].[hash].js',
// 6. path 输出目录(必须是绝对路径)
// __dirname 是 Node.js 全局变量,表示当前文件所在目录的绝对路径
path: path.resolve(__dirname, 'dist'),
// 7. clean:构建前清理输出目录
// 避免残留旧文件导致混乱
clean: true
}
};
1.1.2 完整开发环境配置
javascript
复制代码
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { VueLoaderPlugin } = require('vue-loader');
const webpack = require('webpack');
module.exports = {
// 1. 入口配置
entry: {
// 入口名称:入口路径
// 可以有多个入口,实现多页面应用
main: './src/main.js',
// 另一个入口,用于第三方库单独打包
vendors: './src/vendors.js'
},
// 2. 输出配置
output: {
// [name] = 入口名称,保留多个入口时文件名不冲突
// [hash:8] 取 hash 的前8位,平衡缓存和文件名长度
filename: '[name].[hash:8].js',
// 3. 输出目录(绝对路径)
path: path.resolve(__dirname, 'dist'),
// 4. 公开路径:生成的文件引用的基础路径
// 适用于 CDN 部署或子目录部署
publicPath: '/',
// 5. 是否使用哈希命名(可用于缓存)
// 实际项目中可用 contenthash 替代 hash
hashDigestLength: 8,
// 6. 资源模块的公开 URL 前缀
// 用于 css、图片等资源在 HTML 中的路径
assetModuleFilename: 'assets/[hash][ext][query]',
// 7. 清理输出目录
clean: true
},
// 8. 加载器配置(处理非 JS 文件)
// 加载器从右到左执行(最后的先执行)
module: {
rules: [
// 8.1 处理 JavaScript 文件
{
test: /\.js$/,
// exclude 排除不需要处理的文件
// 提升构建速度
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
// Babel 配置文件路径
configFile: path.resolve(__dirname, '.babelrc'),
// 开启缓存:编译过的文件缓存起来,加快下次构建
cacheDirectory: true
}
}
},
// 8.2 处理 CSS 文件
{
test: /\.css$/,
// use 数组从右到左执行:
// 1. css-loader:将 CSS 转为 JS 模块(处理 @import、url() 等)
// 2. style-loader:将 CSS 注入到 DOM 的 <style> 标签中
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader',
options: {
// 开启 CSS 模块化(针对 .module.css 文件)
modules: {
// 自定义生成的类名格式
localIdentName: '[name]__[local]--[hash:base64:5]'
}
}
}
]
},
// 8.3 处理 SCSS 文件
{
test: /\.scss$/,
// 从右到左:sass-loader -> css-loader -> style-loader
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' },
{
loader: 'sass-loader',
options: {
// 指定 sass 实现(dart-sass/node-sass)
// 推荐 dart-sass,性能更好
implementation: require('sass')
}
}
]
},
// 8.4 处理图片文件
{
test: /\.(png|jpg|jpeg|gif|svg)$/i,
// asset/resource 将图片输出到输出目录
// 适合大图,打包进 bundle 会导致体积过大
type: 'asset/resource',
// 自定义输出路径和文件名
generator: {
filename: 'images/[hash][ext][query]'
}
},
// 8.5 处理字体文件
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
generator: {
filename: 'fonts/[hash][ext][query]'
}
},
// 8.6 处理 Vue 单文件组件
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
},
// 9. 插件配置
plugins: [
// 9.1 生成 HTML 文件
// 自动注入 entry 生成的 JS 文件
new HtmlWebpackPlugin({
// 模板文件路径
template: './public/index.html',
// 输出文件名
filename: 'index.html',
// 是否注入 chunk(JS 文件)
inject: true,
// 是否压缩 HTML
minify: {
removeComments: true, // 删除注释
collapseWhitespace: true, // 折叠空白
removeAttributeQuotes: true // 删除属性引号
}
}),
// 9.2 Vue Loader 插件(必须)
new VueLoaderPlugin(),
// 9.3 DefinePlugin:定义全局常量
// 替换源代码中的 process.env.NODE_ENV
new webpack.DefinePlugin({
// 注意:值必须是字符串,会被直接替换
// 'development' 会替换为字符串 'development'
// 而 NODE_ENV 会替换为实际的字符串
'process.env.NODE_ENV': JSON.stringify('development'),
'process.env.VUE_APP_BASE_API': JSON.stringify('/api')
}),
// 9.4 ProvidePlugin:自动加载模块
// 当模块未找到时,自动注入指定模块
// 不需要手动 import $ 或 import Vue
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery',
Vue: ['vue/dist/vue.esm.js', 'default']
})
],
// 10. 解析配置
resolve: {
// 10.1 文件扩展名:自动补全后缀
// 导入时不用写 .js / .vue 等后缀
extensions: ['.js', '.vue', '.json', '.css'],
// 10.2 路径别名:简化导入路径
alias: {
// '@' 通常指向 src 目录
'@': path.resolve(__dirname, 'src'),
// 统一第三方库的路径,方便替换
'vue$': 'vue/dist/vue.esm.js',
// 组件路径别名
'components': path.resolve(__dirname, 'src/components'),
'utils': path.resolve(__dirname, 'src/utils')
},
// 10.3 解析模块的目录
// 减少解析路径,提升构建速度
modules: [
path.resolve(__dirname, 'src'),
'node_modules'
],
// 10.4 主文件字段
// 用于 package.json 的 exports 字段
mainFields: ['browser', 'module', 'main']
},
// 11. 开发服务器配置
devServer: {
// 11.1 静态文件目录
static: {
directory: path.join(__dirname, 'public')
},
// 11.2 端口号
port: 8080,
// 11.3 自动打开浏览器
open: true,
// 11.4 开启热更新(HMR)
// 修改代码后浏览器自动刷新,只更新变化的部分
hot: true,
// 11.5 代理配置(解决跨域)
proxy: {
'/api': {
// 代理目标服务器
target: 'http://localhost:3000',
// 是否改变 Origin(开发环境设为 true)
changeOrigin: true,
// 重写路径(去掉 /api 前缀)
pathRewrite: { '^/api': '' }
}
},
// 11.6 .historyApiFallback
// SPA 使用 HTML5 History API 时,404 请求回退到 index.html
historyApiFallback: true,
// 11.7 压缩
compress: true
},
// 12. 优化配置
optimization: {
// 12.1 代码分割
splitChunks: {
chunks: 'all',
// 分割的最小块大小(字节)
minSize: 20000,
// 最大请求数(限制并行下载数量)
maxAsyncRequests: 30,
maxInitialRequests: 30,
cacheGroups: {
// 第三方库单独打包
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
priority: -10 // 优先级,数字越大越先处理
},
// 公共模块打包
common: {
minChunks: 2,
name: 'common',
chunks: 'all',
priority: -20
}
}
},
// 12.2 runtime 代码单独打包
// 使 chunk 缓存更有效
runtimeChunk: 'single',
// 12.3 模块标识符优化
// 改变时只影响该模块,不影响其他模块的 hash
moduleIds: 'deterministic',
// 12.4 生产环境 tree-shaking
usedExports: true,
// 合并合并模块
concatenateModules: true
},
// 13. 构建目标
target: 'web',
// 14. Source Map 配置
// 开发环境用 cheap-module-source-map(快)
// 生产环境用 hidden-source-map 或 false(安全)
devtool: 'cheap-module-source-map',
// 15. 统计信息输出
stats: 'errors-warnings',
// 16. 外部扩展
// 运行时从 CDN 加载,不打包进 bundle
externals: {
vue: 'Vue',
react: 'React'
}
};
1.2 Webpack 高级配置
1.2.1 生产环境优化配置
javascript
复制代码
// webpack.prod.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const PrerenderSPAPlugin = require('prerender-webpack-plugin');
module.exports = (env, argv) => {
// 1. 根据 mode 确定是否为生产环境
const isProduction = argv.mode === 'production';
return {
// 2. 生产模式自动优化,但我们可以进一步控制
mode: isProduction ? 'production' : 'development',
// 3. 生产环境 source map 设为 false(安全)
// 或者用 nosources-source-map(只有错误位置,没有源码)
devtool: isProduction ? false : 'eval-source-map',
// 4. 入口
entry: {
app: './src/main.js'
},
output: {
// 5. 生产环境用 contenthash,实现长期缓存
// 只有内容变化时 hash 才变化
filename: isProduction
? 'js/[name].[contenthash:8].js'
: 'js/[name].js',
path: path.resolve(__dirname, 'dist'),
// 6. 所有资源的基础路径(CDN 部署时需要)
publicPath: 'https://cdn.example.com/',
// 7. 不生成 Source Map 文件(安全)
sourceMapFilename: '[file].map',
clean: true
},
module: {
rules: [
// 8. CSS 处理:开发环境用 style-loader,生产环境提取为独立文件
{
test: /\.css$/,
use: [
// 开发环境:CSS 注入到 <style> 标签
// 生产环境:MiniCssExtractPlugin 提取到 .css 文件
isProduction
? MiniCssExtractPlugin.loader
: { loader: 'style-loader' },
{ loader: 'css-loader' }
]
},
// 9. 图片资源优化
{
test: /\.(png|jpg|jpeg|gif|svg)$/i,
type: 'asset',
parser: {
// 10. 数据URI策略:小图转 base64(减少请求)
dataUrlCondition: {
// 小于 8KB 的图片转为 base64,减少 HTTP 请求
maxSize: 8 * 1024
}
},
generator: {
filename: 'images/[name].[hash:8][ext]'
}
}
]
},
plugins: [
// 11. HTML 模板
new HtmlWebpackPlugin({
template: './public/index.html',
// 12. 压缩 HTML
minify: isProduction ? {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true
} : false
}),
// 13. 提取 CSS 为独立文件(生产环境)
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
chunkFilename: 'css/[name].[contenthash:8].chunk.css'
}),
// 14. 清理输出目录
new CleanWebpackPlugin(),
// 15. 预渲染 SPA(可选,优化首屏渲染)
new PrerenderSPAPlugin({
staticDir: path.join(__dirname, 'dist'),
routes: ['/', '/about', '/contact'],
minify: {
collapseWhitespace: true,
removeComments: true
}
})
],
optimization: {
// 16. 最小化器配置
minimizer: [
// 16.1 JS 压缩(Terser)
new TerserPlugin({
terserOptions: {
parse: {
// 开启 ESTree 解析(更精确)
ecma: 2020
},
compress: {
// 删除 console.log(上线后不需要)
drop_console: true,
// 删除 debugger
drop_debugger: true,
// 移除未使用的代码
dead_code: true,
// 优化布尔运算
booleans: true,
// 优化 if 语句
conditionals: true,
// 移除 unused 代码
unused: true
},
mangle: {
// 生产环境擦除变量名(压缩)
safari10: true
},
output: {
// 保持注释(许可证声明)
comments: /@license/i
}
},
extractComments: false, // 不生成 LICENSE 文件
parallel: true // 开启多进程压缩
}),
// 16.2 CSS 压缩(CssMinimizerPlugin)
new CssMinimizerPlugin({
minimizerOptions: {
preset: [
'default',
{
discardComments: { removeAll: true }
}
]
}
})
],
// 17. 代码分割
splitChunks: {
chunks: 'all',
maxInitialRequests: 5,
cacheGroups: {
// 第三方库
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
priority: 10
},
// 公共组件
common: {
minChunks: 2,
name: 'common',
chunks: 'all',
priority: 5
},
// 高频库单独打包(可能长期缓存)
vue: {
test: /[\\/]node_modules[\\/](vue|vue-router|vuex)[\\/]/,
name: 'vue-vendor',
chunks: 'all',
priority: 20
}
}
},
// 18. runtime 单独打包
runtimeChunk: 'single',
// 19. 生成模块映射(便于调试)
moduleIds: 'deterministic',
// 20. 启用 tree-shaking
usedExports: true,
// 21. 尽力压缩
concatenateModules: true
},
// 22. 性能提示
performance: {
hints: isProduction ? 'warning' : false,
// 超过 500KB 给出警告
maxEntrypointSize: 512000,
maxAssetSize: 512000
}
};
};
1.2.2 Webpack 插件手写
javascript
复制代码
// 1. 自定义 Webpack 插件基本结构
class CustomPlugin {
// 1.1 插件构造函数
// compiler 是 webpack 实例,包含所有配置和状态
constructor(options = {}) {
// 保存插件配置
this.options = options;
// 插件名(用于日志和调试)
this.pluginName = 'CustomPlugin';
}
// 1.2 apply 是插件的主入口
// Webpack 会在构建过程中调用此方法
apply(compiler) {
// 2. 监听 Webpack 生命周期钩子
// 2.1 compilation:每次编译开始时调用
// compilation 是本次编译的实例,包含模块和 chunk 信息
compiler.hooks.compilation.tap(this.pluginName, (compilation) => {
// 3. 监听 compilation 的特定钩子
// 这里监听优化资源的过程
compilation.hooks.processAssets.tapAsync(
{
name: this.pluginName,
// 资源处理阶段:在优化和生成资源之后
stage: compilation.PROCESS_ASSETS_STAGE_ADDITIONAL
},
(assets, callback) => {
// assets 是本次编译生成的所有资源
// 可以在这里修改、添加或删除资源
for (const [filename, asset] of Object.entries(assets)) {
console.log(`Generated asset: ${filename}`);
console.log(`Size: ${asset.size()} bytes`);
}
// 处理完成后调用 callback
callback();
}
);
});
// 2.2 emit:资源生成到输出目录之前调用
// 可以在这里修改输出文件的内容
compiler.hooks.emit.tapAsync(this.pluginName, (compilation, callback) => {
// compilation.assets 包含所有要输出的资源
const stats = compilation.getStats();
const assets = Object.keys(compilation.assets);
console.log(`Total assets: ${assets.length}`);
// 2.3 添加新的资源文件
// 可以生成额外的文件,如分析报告
const manifest = {
buildTime: new Date().toISOString(),
assets: assets.map(name => ({
name,
size: compilation.assets[name].size()
}))
};
// 添加一个 JSON 文件到输出
compilation.assets['manifest.json'] = {
source: () => JSON.stringify(manifest, null, 2),
size: () => JSON.stringify(manifest).length
};
callback();
});
// 2.4 afterEmit:资源输出完成后调用
// 适合做清理工作或发送通知
compiler.hooks.afterEmit.tapAsync(this.pluginName, (compilation, callback) => {
console.log('Build completed and files emitted');
callback();
});
// 2.5 done:编译完成后调用
// 不管成功还是失败都会调用
compiler.hooks.done.tapAsync(this.pluginName, (stats, callback) => {
const buildInfo = stats.toJson();
console.log('Build done:', {
time: buildInfo.time,
assets: buildInfo.assets.length,
warnings: buildInfo.warnings.length,
errors: buildInfo.errors.length
});
callback();
});
}
}
// 使用自定义插件
module.exports = {
plugins: [
new CustomPlugin({
// 插件配置
verbose: true
})
]
};
1.2.3 Webpack Loader 手写
javascript
复制代码
// 2. 自定义 Webpack Loader 基本结构
// 2.1 同步 Loader
// 简单场景,返回处理后的 source
function simpleLoader(source) {
// source 是原始文件内容
// 转换 source
const transformed = source.replace(/旧文本/g, '新文本');
// 返回处理后的内容
// 可以返回 this.callback(err, result) 传递更多信息
return transformed;
}
// 2.2 完整 Loader(带上下文信息)
function fullLoader(source, inputSourceMap) {
// 1. 获取 loader 配置
const options = this.getOptions();
const callback = this.async();
// 2. this.callback 用于异步处理
// 第一个参数是错误,第二个是处理后的源码,第三个是 source map
callback(null, transformedSource, sourceMap);
// 或者报告错误
// callback(new Error('处理失败'), null);
}
// 2.3 使用 loader-utils 解析选项
const { getOptions } = require('loader-utils');
const { validate } = require('schema-utils');
const schema = {
type: 'object',
properties: {
name: { type: 'string' },
age: { type: 'number' }
},
additionalProperties: false
};
function loader(source) {
const options = getOptions(this);
// 验证选项
validate(schema, options, {
name: 'MyLoader',
baseDataPath: 'options'
});
// 转换 source
let transformed = source;
// 根据配置处理
if (options.name) {
transformed = transformed.replace(/PROJECT_NAME/g, options.name);
}
// 返回(同步)
return transformed;
}
// 2.4 完整 Loader 示例:markdown 转 HTML
const marked = require('marked');
function markdownLoader(source) {
// 1. 获取配置
const options = this.getOptions();
// 2. 解析 markdown
const html = marked(source, {
// marked 配置
gfm: true,
breaks: options.breaks || false
});
// 3. 包装成 HTML 模块
// 导出字符串,供其他模块引用
const result = `
const html = ${JSON.stringify(html)};
export default html;
`;
// 4. 如果需要 source map
// 返回数组 [result, sourceMap]
return result;
}
// 2.5 Pitch Loader
// 在 Loader 主函数之前执行,可以决定是否跳过主函数
function pitchLoader(remainingRequest, precedingRequest, data) {
// 1. remainingRequest:剩余的 loader 路径
// 2. precedingRequest:已经执行过的 loader 路径
// 3. data:可以在 loader 间传递数据
// 如果返回非 undefined,会跳过所有后续 loader
// 常用于条件加载
if (someCondition) {
return '';
}
}
// 2.6 导出 Loader
module.exports = markdownLoader;
module.exports.pitch = pitchLoader;
1.3 Webpack 核心原理
1.3.1 Tapable 插件系统原理
javascript
复制代码
// Tapable 是 Webpack 的核心插件系统
const { SyncHook, SyncBailHook, SyncWaterfallHook, SyncLoopHook } = require('tapable');
const { AsyncParallelHook, AsyncSeriesHook } = require('tapable');
// 1. SyncHook:同步钩子,回调依次执行,不关心返回值
class MyPlugin {
constructor() {
// 1.1 创建钩子,定义参数
this.hooks = {
// 1.2 定义一个同步钩子,接受 (name, age) 两个参数
greet: new SyncHook(['name', 'age'])
};
}
// 1.3 注册事件(tap)
register() {
this.hooks.greet.tap('Plugin1', (name, age) => {
console.log('Plugin1:', name, age);
});
this.hooks.greet.tap('Plugin2', (name, age) => {
console.log('Plugin2:', name, age);
});
}
// 1.4 触发事件(call)
call(name, age) {
this.hooks.greet.call(name, age);
// 输出:
// Plugin1: Alice 25
// Plugin2: Alice 25
}
}
// 2. SyncBailHook:同步钩子,回调依次执行
// 如果某个回调返回非 undefined,后续回调不再执行
class MyBailPlugin {
constructor() {
this.hooks = {
// 2.1 bail 钩子
find: new SyncBailHook(['data'])
};
}
register() {
this.hooks.find.tap('CachePlugin', (data) => {
if (data.cache) {
return data.cache; // 返回值,后续不执行
}
});
this.hooks.find.tap('FetchPlugin', (data) => {
return fetchFromNetwork(data); // 一旦执行,后续不执行
});
}
call(data) {
const result = this.hooks.find.call(data);
console.log('Result:', result);
}
}
// 3. SyncWaterfallHook:同步钩子,依次传递结果
// 前一个回调的返回值传给下一个回调
class MyWaterfallPlugin {
constructor() {
this.hooks = {
process: new SyncWaterfallHook(['initial'])
};
}
register() {
this.hooks.process.tap('Step1', (initial) => {
return initial + ' -> Step1';
});
this.hooks.process.tap('Step2', (preResult) => {
return preResult + ' -> Step2';
});
this.hooks.process.tap('Step3', (preResult) => {
return preResult + ' -> Step3';
});
}
call() {
const result = this.hooks.process.call('Start');
// 结果: 'Start -> Step1 -> Step2 -> Step3'
}
}
// 4. AsyncSeriesHook:异步串行钩子
// 依次执行异步回调,每个完成后执行下一个
class MyAsyncPlugin {
constructor() {
this.hooks = {
load: new AsyncSeriesHook(['resource'])
};
}
register() {
this.hooks.load.tapAsync('FileLoader', (resource, callback) => {
fs.readFile(resource, (err, data) => {
if (err) return callback(err);
console.log('File loaded');
callback(); // 必须调用 callback
});
});
this.hooks.load.tapAsync('CacheLoader', (resource, callback) => {
checkCache(resource, (err, cached) => {
if (err) return callback(err);
console.log('Cache checked');
callback();
});
});
}
call(resource) {
this.hooks.load.callAsync(resource, (err) => {
if (err) console.error('Error:', err);
else console.log('All done');
});
}
}
// 5. AsyncParallelHook:异步并行钩子
// 所有异步回调同时执行,全部完成后执行最终回调
class MyParallelPlugin {
constructor() {
this.hooks = {
parallel: new AsyncParallelHook(['data'])
};
}
register() {
this.hooks.parallel.tapAsync('Task1', (data, callback) => {
setTimeout(() => {
console.log('Task1 done');
callback(null, 'result1');
}, 100);
});
this.hooks.parallel.tapAsync('Task2', (data, callback) => {
setTimeout(() => {
console.log('Task2 done');
callback(null, 'result2');
}, 50);
});
}
call(data) {
this.hooks.parallel.callAsync(data, (err, results) => {
console.log('All tasks completed');
// results 是所有回调结果的数组
});
}
}
1.3.2 Webpack 构建流程
javascript
复制代码
// Webpack 构建流程简化实现
class SimpleWebpack {
constructor(options) {
this.options = options; // webpack 配置
this.modules = {}; // 模块缓存
this.chunks = {}; // chunk 缓存
this.assets = {}; // 资源缓存
}
// 1. run:启动构建
run() {
const { entry, output, module, plugins } = this.options;
console.log('=== Webpack Build Start ===');
// 2. 执行插件的 apply 方法(插件初始化)
plugins.forEach(plugin => {
if (typeof plugin.apply === 'function') {
plugin.apply(this);
}
});
// 3. 从入口文件开始编译
const entryModule = this.buildModule(entry, {});
// 4. 生成 chunk(代码块)
const chunks = this.createChunks(entryModule);
// 5. 输出资源
this.emitAssets(chunks, output);
console.log('=== Webpack Build Complete ===');
}
// 6. buildModule:编译单个模块
buildModule(filename, parent) {
// 6.1 读取文件内容
const source = fs.readFileSync(filename, 'utf-8');
console.log(`Building: ${filename}`);
// 6.2 获取 loader 规则
const rules = this.options.module?.rules || [];
// 6.3 应用 loader 链
let processedSource = source;
for (const rule of rules) {
if (rule.test.test(filename)) {
// 执行 loader
const loader = rule.use;
if (typeof loader === 'string') {
// 同步 loader
const loaderFn = require(loader);
processedSource = loaderFn(processedSource);
}
}
}
// 6.4 调用 Webpack 的 module API(处理依赖)
const module = {
id: filename,
dependencies: [], // 存储依赖
source: processedSource
};
// 6.5 解析依赖
// 使用正则解析 import/require 语句
const dependencyPattern = /require\(['"](.+?)['"]\)/g;
let match;
while ((match = dependencyPattern.exec(processedSource)) !== null) {
const depPath = path.resolve(dirname(filename), match[1]);
module.dependencies.push(depPath);
}
// 6.6 递归编译依赖模块
module.dependencies.forEach(dep => {
this.buildModule(dep, module);
});
return module;
}
// 7. createChunks:生成代码块
createChunks(entryModule) {
// 将模块组合成 chunk
return [{
id: 'main',
modules: [entryModule],
name: 'main'
}];
}
// 8. emitAssets:输出资源
emitAssets(chunks, output) {
for (const chunk of chunks) {
// 生成文件内容
const filename = output.filename.replace('[name]', chunk.name);
this.assets[filename] = this.generateCode(chunk);
// 写入文件系统
const outputPath = path.join(output.path, filename);
fs.writeFileSync(outputPath, this.assets[filename]);
console.log(`Emitted: ${outputPath}`);
}
}
// 9. generateCode:生成最终代码
generateCode(chunk) {
// 拼接模块代码,生成 bundle
return `
(function(modules) {
// 9.1 定义 require 函数
function __webpack_require__(moduleId) {
var module = {
exports: {}
};
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
return module.exports;
}
// 9.2 启动模块加载
return __webpack_require__("${chunk.modules[0].id}");
})({
"${chunk.modules[0].id}": function(module, exports, __webpack_require__) {
${chunk.modules[0].source}
}
})
`;
}
}
二、Vite 核心原理
2.1 Vite 核心配置
2.1.1 Vite 基础配置
javascript
复制代码
// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import react from '@vitejs/plugin-react';
import cssPlugin from 'vite-plugin-css';
import { viteStaticCopy } from 'vite-plugin-static-copy';
// 1. defineConfig:类型安全的配置函数
// 提供 TypeScript 类型提示和自动补全
export default defineConfig({
// 2. 项目根目录(默认为 process.cwd())
root: process.cwd(),
// 3. 公共基础路径
// - '/':绝对路径
// - './':相对路径(适合部署到任意路径)
base: '/',
// 4. 服务器配置(开发服务器)
server: {
// 4.1 端口
port: 3000,
// 4.2 是否自动打开浏览器
open: true,
// 4.3 代理配置
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
},
// 4.4 CORS 配置
cors: true,
// 4.5 监听所有网卡(局域网可访问)
host: true,
// 4.6 热更新配置
hmr: {
// 热更新端口
port: 3001,
// 是否在同模块激活
overlay: true
}
},
// 5. 构建配置
build: {
// 5.1 目标浏览器
target: 'es2015',
// 5.2 输出目录
outDir: 'dist',
// 5.3 生成 sourcemap
sourcemap: false,
// 5.4 压缩配置
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
},
// 5.5 chunk 大小警告限制
chunkSizeWarningLimit: 500,
// 5.6 rollup 配置
rollupOptions: {
output: {
// 手动分包
manualChunks: {
'vue-vendor': ['vue', 'vue-router', 'vuex'],
'element-ui': ['element-plus']
},
// 文件名哈希
entryFileNames: 'js/[name]-[hash].js',
chunkFileNames: 'js/[name]-[hash].js',
assetFileNames: '[ext]/[name]-[hash][extname]'
}
}
},
// 6. 插件配置
plugins: [
// 6.1 Vue 插件
vue({
// 是否使用 Vue 3 runtime
include: [/\.vue$/],
// 自定义块处理
customBlocks: {
'i18n': resolve(__dirname, 'src/i18n')
}
}),
// 6.2 React 插件(Fast Refresh)
react(),
// 6.3 静态文件拷贝
viteStaticCopy({
targets: [
{ src: 'public/*', dest: 'public' }
]
})
],
// 7. 路径解析
resolve: {
// 7.1 文件扩展名
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json'],
// 7.2 路径别名
alias: {
'@': path.resolve(__dirname, 'src'),
'components': path.resolve(__dirname, 'src/components'),
'utils': path.resolve(__dirname, 'src/utils')
}
},
// 8. 依赖优化(开发时)
optimizeDeps: {
// 8.1 预构建的依赖
include: ['vue', 'vue-router', 'element-plus'],
// 8.2 排除的依赖(不使用预构建)
exclude: [],
// 8.3 预构建入口
entries: ['src/main.js']
},
// 9. CSS 配置
css: {
// 9.1 CSS 模块化配置
modules: {
// 类名生成格式
localsConvention: 'camelCase',
// 忽略的文件
exclude: []
},
// 9.2 CSS 预处理器
preprocessorOptions: {
scss: {
// 注入全局变量
additionalData: `@import "@/styles/variables.scss";`
}
}
},
// 10. 日志配置
logLevel: 'info',
// 11. 环境变量
envDir: path.resolve(__dirname, 'env'),
envPrefix: 'VITE_'
});
2.2 Vite 核心原理详解
2.2.1 Vite 工作流程
javascript
复制代码
// Vite 工作流程简化实现
// 1. 开发环境:基于 ES Module 的开发服务器
// 不打包,按需编译,只编译浏览器请求的文件
class ViteDevServer {
constructor() {
// 1.1 已编译的模块缓存
this.moduleCache = new Map();
// 1.2 已转换的源码缓存(处理过 import/export)
this.transformCache = new Map();
// 1.3 依赖图谱
this.depGraph = new Map();
}
// 2. 启动开发服务器
async start() {
const server = http.createServer(async (req, res) => {
// 2.1 解析请求
const url = req.url.split('?')[0];
const filePath = path.join(process.cwd(), url);
try {
// 2.2 读取文件
let source = fs.readFileSync(filePath, 'utf-8');
// 2.3 如果是 JS/TS 文件,进行转换
if (filePath.endsWith('.js') || filePath.endsWith('.ts')) {
source = await this.transform(source, filePath);
}
// 2.4 发送响应
res.setHeader('Content-Type', this.getContentType(filePath));
res.end(source);
} catch (err) {
// 2.5 404 处理
if (err.code === 'ENOENT') {
res.statusCode = 404;
res.end('Not Found');
}
}
});
server.listen(3000);
}
// 3. transform:转换源码
// 核心:处理 import/export,改写为浏览器认识的格式
async transform(source, filePath) {
// 3.1 检查缓存
if (this.transformCache.has(filePath)) {
return this.transformCache.get(filePath);
}
// 3.2 使用 esbuild 转换代码
// esbuild 比 Babel 快 10-100 倍
const result = await esbuild.transform(source, {
// 目标环境
target: 'esnext',
// 启用 sourcemap
sourcemap: true,
// 是否支持 JSX
jsx: 'automatic',
// 源码格式
format: 'esm'
});
// 3.3 处理依赖(import 语句)
const deps = this.extractDeps(source);
// 3.4 更新依赖图谱
this.depGraph.set(filePath, {
imports: deps,
transformed: result.code
});
// 3.5 缓存转换结果
this.transformCache.set(filePath, result.code);
return result.code;
}
// 4. extractDeps:提取依赖
// 正则匹配 import 语句
extractDeps(source) {
const deps = [];
// 匹配: import xxx from 'yyy'
// 匹配: import('xxx') 动态导入
const importRegex = /import\s+(?:(?:[\w*{}\s]+ from )|(?:.* from )|(?:[\w*{}\s]+,\s*\{[\s\w*}]+ from ))?['"]([@\w/-]+)['"]/g;
let match;
while ((match = importRegex.exec(source)) !== null) {
deps.push(match[1]);
}
return deps;
}
// 5. getContentType:获取 Content-Type
getContentType(filePath) {
const ext = path.extname(filePath);
const types = {
'.js': 'application/javascript',
'.ts': 'application/typescript',
'.css': 'text/css',
'.html': 'text/html',
'.json': 'application/json'
};
return types[ext] || 'text/plain';
}
}
// Vite 的核心优势:
// - 开发环境:no-bundle,按需编译,速度快
// - 生产环境:Rollup 打包,代码分割,优化
// - HMR:模块热更新,毫秒级响应
2.2.2 Vite 热更新原理
javascript
复制代码
// Vite HMR(热模块替换)实现原理
// 1. HMR 架构
// - WebSocket 实时推送更新
// - 客户端根据更新类型决定如何处理
// 2. 更新类型
const HMR_UPDATE_TYPES = {
// 2.1 js/vue 文件更新:重新执行模块
'JS_UPDATE': 'js-update',
// 2.2 CSS 更新:只需更新 <style> 标签内容
'CSS_UPDATE': 'css-update',
// 2.3 组件重新渲染
'RELOAD': 'reload'
};
// 3. HMR 客户端实现
function setupHMRClient() {
// 3.1 创建 WebSocket 连接
const socket = new WebSocket('ws://localhost:3001');
socket.addEventListener('message', async (event) => {
const { type, path, timestamp } = JSON.parse(event.data);
switch (type) {
case 'js-update':
// 4. JS 更新处理
await handleJSUpdate(path);
break;
case 'css-update':
// 5. CSS 更新处理
await handleCSSUpdate(path);
break;
case 'reload':
// 6. 完全重载(某些情况无法热更新)
window.location.reload();
break;
}
});
// 4. handleJSUpdate:JS 更新处理
async function handleJSUpdate(path) {
// 4.1 从服务器获取更新后的模块
const newModule = await fetchModule(path);
// 4.2 更新模块缓存
updateModuleCache(path, newModule);
// 4.3 查找使用该模块的所有模块
const affectedModules = findAffectedModules(path);
// 4.4 依次执行受影响的模块
for (const modulePath of affectedModules) {
const module = getModule(modulePath);
if (module && module.hot) {
// 4.5 调用 Vue 的 update 钩子
if (typeof module.hot.accept === 'function') {
module.hot.accept();
}
}
}
// 4.6 打印更新信息
console.log(`[HMR] Updated: ${path}`);
}
// 5. handleCSSUpdate:CSS 更新处理
async function handleCSSUpdate(path) {
// 5.1 获取新的 CSS 内容
const newCSS = await fetchModule(path);
// 5.2 找到对应的 <style> 标签
const style = document.querySelector(`link[href*="${path}"]`);
if (style) {
// 5.3 直接替换 CSS 内容
// 不需要刷新页面,用户体验好
style.textContent = newCSS;
}
console.log(`[HMR] CSS Updated: ${path}`);
}
}
// 6. Vite 插件的 HMR 集成点
// 插件通过 handleHotUpdate 实现 HMR
const vuePlugin = {
handleHotUpdate({ server, file, modules }) {
// 6.1 判断文件类型
if (file.endsWith('.vue')) {
// 6.2 解析 SFC(Vue 单文件组件)
const descriptor = parse(file);
// 6.3 如果是模板变化,只需更新组件实例
if (descriptor.template) {
// 发送 css-update 类型
server.ws.send({
type: 'css-update',
path: file
});
// 6.4 更新组件的 template
modules.forEach(m => {
// 重新执行模块
server.reload(m);
});
}
// 6.5 如果是脚本变化,需要完整更新
if (descriptor.script) {
server.ws.send({
type: 'reload',
path: file
});
}
}
}
};
// Vite HMR 的优势:
// - 精确更新:只更新变化的模块,不重载整个页面
// - 速度快:基于 WebSocket,毫秒级响应
// - 用户体验好:CSS 更新无需刷新,状态保持
2.3 Vite 与 Webpack 对比
javascript
复制代码
// Vite vs Webpack 核心对比
// 1. 开发环境启动速度
// Webpack:需要打包所有模块,构建时间长
// Vite:基于 ESM,只编译请求的模块,秒级启动
// 2. 热更新速度
// Webpack:重新构建依赖图,可能打包大量模块
// Vite:精准更新,只更新变化的模块
// 3. 构建速度(生产环境)
// Webpack:HMR 模式下可能较慢
// Vite:使用 Rollup,.tree-shaking 更彻底
// 4. 配置对比
const compareConfig = {
// Webpack 需要:
webpack: {
// 需要配置 mode
mode: 'development',
// 需要配置 entry
entry: './src/main.js',
// 需要配置 output
output: { filename: '[name].js' },
// 需要配置 module.rules
module: {
rules: [
{ test: /\.js$/, use: 'babel-loader' },
{ test: /\.vue$/, use: 'vue-loader' }
]
},
// 需要配置 plugins
plugins: [
new HtmlWebpackPlugin(),
new VueLoaderPlugin()
],
// 需要配置 resolve.alias
resolve: {
alias: { '@': path.resolve(__dirname, 'src') }
}
},
// Vite 配置更简洁:
vite: {
// 不需要 mode(自动根据环境)
// 不需要 entry(默认从 index.html 开始)
// 不需要 output(自动处理)
plugins: [vue()],
// resolve 就是顶层配置
resolve: {
alias: { '@': path.resolve(__dirname, 'src') }
}
// server 和 build 是顶层配置
}
};
// 5. 依赖预构建(预绑定)
// Vite 使用 esbuild 预构建依赖,比 Webpack 快 10-100 倍
// Webpack 使用 thread-loader 或 cache-loader 实现类似功能
// 6. 适用场景
const适用场景 = {
vite: [
'小型/中型项目(< 500 个模块)',
'需要快速启动的开发环境',
'Vue 3 / React 17+ 项目',
'TypeScript 项目(esbuild 原生支持)'
],
webpack: [
'大型复杂项目(> 1000 个模块)',
'需要高度自定义的构建流程',
'旧项目迁移',
'需要兼容性优先(IE11)'
]
};
三、前端性能优化
3.1 运行时性能优化
3.1.1 React 性能优化
javascript
复制代码
// 1. React.memo:包装组件,避免不必要的重渲染
// 只有 props 变化时才重渲染
import { memo } from 'react';
// 1.1 普通组件包装
const Button = memo(function Button({ onClick, children }) {
return <button onClick={onClick}>{children}</button>;
});
// 1.2 带比较函数的 memo(精细控制)
const ProductList = memo(
function ProductList({ products, onSelect }) {
return (
<ul>
{products.map(p => (
<ProductItem
key={p.id}
product={p}
onSelect={onSelect}
/>
))}
</ul>
);
},
// 1.3 自定义比较函数
// prevProps 和 nextProps 是前后两次的 props
// 返回 true 表示相同时,不重渲染
(prevProps, nextProps) => {
// 只比较 products 数组内容(引用变化但内容相同时不重渲染)
return prevProps.products === nextProps.products;
}
);
// 2. useMemo:缓存计算结果
// 避免每次渲染都重新计算
import { useMemo } from 'react';
function ExpensiveList({ items, filter }) {
// 2.1 依赖数组 [items, filter]
// 只有 items 或 filter 变化时才重新计算
// items 和 filter 没变化时,直接返回缓存的结果
const filteredItems = useMemo(() => {
console.log('Filtering items...'); // 验证是否重新计算
return items.filter(item => {
// 过滤逻辑
return item.name.includes(filter);
});
}, [items, filter]); // 依赖项
return <List data={filteredItems} />;
}
// 2.2 useMemo 用于引用稳定
// 避免子组件因为引用变化而重渲染
const contextValue = useMemo(() => ({
// 创建复杂的上下文值
theme: 'dark',
toggleTheme: () => {}
}), []); // 空依赖,引用永远不变
<ThemeContext.Provider value={contextValue}>
{children}
</ThemeContext.Provider>;
// 3. useCallback:缓存回调函数
// 避免子组件因为回调函数引用变化而重渲染
import { useCallback } from 'react';
function ParentComponent() {
const [count, setCount] = useState(0);
// 3.1 使用 useCallback 包装回调
// count 作为依赖时,count 变化才创建新函数
// 空依赖 [] 时,函数引用永远不变
const handleClick = useCallback((event) => {
console.log('Clicked:', event.target);
setCount(c => c + 1);
}, []); // 空依赖,函数引用稳定
return <MemoizedButton onClick={handleClick} />;
}
// 3.2 useCallback 的典型场景
// 传给使用 React.memo 包装的子组件的回调
const MemoizedButton = memo(function Button({ onClick, children }) {
console.log('Button rendered');
return <button onClick={onClick}>{children}</button>;
});
// 4. useTransition:标记非紧急更新
// 让紧急更新(如输入)优先渲染
import { useState, useTransition } from 'react';
function SearchComponent() {
const [query, setQuery] = useState('');
// 4.1 isPending 表示过渡是否进行中
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
const value = e.target.value;
setQuery(value); // 紧急更新,立即响应
// 4.2 startTransition 包裹的更新是不紧急的
// 可以被输入等紧急更新打断
startTransition(() => {
// 这里是搜索相关的更新
// 即使慢,也不会阻塞输入
fetchResults(value);
});
};
return (
<div>
<input value={query} onChange={handleChange} />
{isPending && <LoadingSpinner />}
</div>
);
}
// 5. useDeferredValue:延迟更新
// 与 useTransition 类似,但用于值
import { useDeferredValue } from 'react';
function SearchResults({ query }) {
// 5.1 deferredQuery 会比 query 延迟更新
// 浏览器可以在延迟期间响应输入
const deferredQuery = useDeferredValue(query);
// 5.2 使用 isStale 判断是否在延迟中
const isStale = query !== deferredQuery;
return (
<div style={{ opacity: isStale ? 0.5 : 1 }}>
{/* 根据 deferredQuery 渲染结果 */}
<Results query={deferredQuery} />
</div>
);
}
// 6. useRef:保持引用稳定
// 用于存储不需要触发渲染的数据
function Timer() {
// 6.1 ref 不会引起组件重渲染
const intervalRef = useRef(null);
const countRef = useRef(0);
useEffect(() => {
// 6.2 ref.current 可以直接修改
intervalRef.current = setInterval(() => {
countRef.current += 1;
console.log('Count:', countRef.current);
}, 1000);
return () => clearInterval(intervalRef.current);
}, []);
return <div>{countRef.current}</div>;
}
// 7. 虚拟列表:大量数据渲染优化
import { useVirtualizer } from 'react-virtual';
function VirtualList({ items }) {
const parentRef = useRef(null);
// 7.1 虚拟化配置
const rowVirtualizer = useVirtualizer({
count: items.length, // 总数量
getScrollElement: () => parentRef.current,
estimateSize: () => 50, // 每行预估高度
overscan: 5 // 视口外渲染数量
});
return (
<div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
<div style={{ height: rowVirtualizer.getTotalSize() }}>
{rowVirtualizer.getVirtualItems().map((virtualRow) => (
<div
key={virtualRow.key}
style={{
position: 'absolute',
top: virtualRow.start,
height: virtualRow.size
}}
>
<ListItem item={items[virtualRow.index]} />
</div>
))}
</div>
</div>
);
}
3.1.2 渲染性能优化
javascript
复制代码
// 1. 避免使用 index 作为 key
// 错误的做法:删除中间项会导致 index 变化,重新渲染
const BadList = ({ items }) => (
<ul>
{items.map((item, index) => (
// index 作为 key,删除 item[1] 后,item[2] 的 key 从 2 变成 1
// React 会认为这是同一个元素,只是位置变了
<li key={index}>{item.name}</li>
))}
</ul>
);
// 正确的做法:使用唯一 id
const GoodList = ({ items }) => (
<ul>
{items.map(item => (
// 唯一 id 作为 key,删除 item[1] 时,item[2] 不受影响
<li key={item.id}>{item.name}</li>
))}
</ul>
);
// 2. 避免在渲染中创建新函数/对象
// 错误的做法:每次渲染都创建新回调
function BadComponent({ items }) {
return (
<ul>
{items.map(item => (
// onClick 每次都是新函数,导致子组件重渲染
<Item key={item.id} onClick={() => console.log(item.id)} />
))}
</ul>
);
}
// 3. 列表项使用 React.memo + 稳定 key
const Item = memo(function Item({ id, onClick }) {
return <li onClick={onClick}>{id}</li>;
});
// 4. 避免嵌套层级过深
// 深层嵌套的组件树会导致 diff 算法变慢
// 使用扁平化结构或 Portal
// 5. 使用 Fragment 减少节点数量
function TableRow() {
return (
// Fragment 不产生真实 DOM 节点,减少 DOM 节点数量
<>
<td>Name</td>
<td>Age</td>
</>
);
}
// 6. 条件渲染优化
function ConditionalRender({ shouldShow, heavyComponent }) {
// 6.1 早 return 避免不必要渲染
if (!shouldShow) {
return null; // 不渲染任何东西
}
// 6.2 懒加载非首屏组件
const LazyHeavy = lazy(() => import('./HeavyComponent'));
return (
<>
<Header />
{/* 懒加载,只有在需要时才加载 */}
{shouldShow && <LazyHeavy />}
</>
);
}
3.2 网络性能优化
3.2.1 资源加载优化
javascript
复制代码
// 1. 预加载关键资源
// <link rel="preload"> 提前加载即将使用的资源
// 预加载字体
<link rel="preload" href="/fonts/custom.woff2" as="font" crossorigin="anonymous">
// 预加载 JS
<link rel="preload" href="/main.js" as="script">
// 预加载 CSS
<link rel="preload" href="/styles.css" as="style">
// 2. 预连接
// 提前建立 TCP/TLS 连接
<link rel="preconnect" href="https://cdn.example.com">
<link rel="dns-prefetch" href="https://cdn.example.com">
// 3. 懒加载图片
// <img loading="lazy"> 原生懒加载
const LazyImage = ({ src, alt }) => (
<img loading="lazy" src={src} alt={alt} />
);
// 4. 第三方资源优化
// 使用 Intersection Observer 实现懒加载
class LazyLoad {
constructor(selector) {
// 4.1 创建 IntersectionObserver
// 当元素进入视口时触发回调
this.observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// 4.2 加载资源
this.loadResource(entry.target);
// 4.3 停止观察
this.observer.unobserve(entry.target);
}
});
},
{
rootMargin: '50px 0px', // 提前 50px 开始加载
threshold: 0.01
}
);
// 4.4 开始观察所有目标元素
document.querySelectorAll(selector).forEach(el => {
this.observer.observe(el);
});
}
loadResource(element) {
// 处理图片懒加载
if (element.tagName === 'IMG') {
element.src = element.dataset.src;
}
// 处理脚本懒加载
if (element.tagName === 'SCRIPT') {
const script = document.createElement('script');
script.src = element.dataset.src;
document.body.appendChild(script);
}
}
}
// 使用
new LazyLoad('img[data-src], script[data-src]');
// 5. 请求合并
// 将多个小请求合并为一个大请求
class RequestBatcher {
constructor(batchSize = 10, delay = 100) {
this.queue = []; // 待处理请求队列
this.batchSize = batchSize; // 每批数量
this.delay = delay; // 延迟时间(ms)
// 定时器
this.timer = null;
}
// 5.1 添加请求
add(request) {
return new Promise((resolve, reject) => {
this.queue.push({ request, resolve, reject });
this.schedule();
});
}
// 5.2 调度执行
schedule() {
if (this.timer) return;
this.timer = setTimeout(() => {
this.flush();
this.timer = null;
}, this.delay);
}
// 5.3 执行批量请求
async flush() {
// 取出最多 batchSize 个请求
const batch = this.queue.splice(0, this.batchSize);
// 批量执行
try {
const results = await batchRequest(batch.map(b => b.request));
batch.forEach((item, index) => {
item.resolve(results[index]);
});
} catch (err) {
batch.forEach(item => item.reject(err));
}
}
}
// 6. DNS 预解析
// 提前解析域名,减少连接时间
<meta http-equiv="x-dns-prefetch-control" content="on">
<link rel="dns-prefetch" href="//api.example.com">
<link rel="dns-prefetch" href="//cdn.example.com">
3.2.2 缓存策略实现
javascript
复制代码
// 1. Service Worker 缓存策略
// 注册 Service Worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('SW registered:', registration.scope);
});
}
// sw.js 内容
const CACHE_NAME = 'v1';
const STATIC_ASSETS = [
'/',
'/index.html',
'/main.js',
'/styles.css'
];
// 1.1 安装事件:缓存静态资源
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => {
return cache.addAll(STATIC_ASSETS);
})
);
// 跳过等待,立即激活
self.skipWaiting();
});
// 1.2 激活事件:清理旧缓存
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames
.filter(name => name !== CACHE_NAME)
.map(name => caches.delete(name))
);
})
);
// 立即接管所有页面
self.clients.claim();
});
// 1.3 请求拦截:实现缓存策略
self.addEventListener('fetch', (event) => {
const { request } = event;
const url = new URL(request.url);
// 根据 URL 选择策略
if (url.pathname.startsWith('/api/')) {
// API 请求:网络优先,失败时用缓存
event.respondWith(networkFirst(request));
} else if (isStaticAsset(url.pathname)) {
// 静态资源:缓存优先,失败时用网络
event.respondWith(cacheFirst(request));
}
});
// 1.4 缓存优先策略
async function cacheFirst(request) {
// 先从缓存中查找
const cached = await caches.match(request);
if (cached) {
return cached;
}
// 缓存中没有,发起网络请求
const response = await fetch(request);
// 将响应缓存
const cache = await caches.open(CACHE_NAME);
cache.put(request, response.clone());
return response;
}
// 1.5 网络优先策略
async function networkFirst(request) {
try {
const response = await fetch(request);
// 成功时更新缓存
const cache = await caches.open(CACHE_NAME);
cache.put(request, response.clone());
return response;
} catch (error) {
// 失败时返回缓存
const cached = await caches.match(request);
if (cached) {
return cached;
}
// 没有缓存,返回离线响应
return new Response('Offline', { status: 503 });
}
}
// 2. HTTP 缓存配置(服务器端)
// Node.js Express 示例
const express = require('express');
const app = express();
// 2.1 强缓存:Cache-Control
app.use('/static/', express.static('public', {
// 资源可以被任何缓存存储
maxAge: 31536000, // 1 年
// 缓存时使用 Vary: Accept-Encoding
etag: true
}));
// 2.2 协商缓存:Last-Modified / ETag
app.get('/api/data', (req, res) => {
// 检查 If-Modified-Since
const lastModified = req.headers['if-modified-since'];
// 检查 If-None-Match
const etag = req.headers['if-none-match'];
const data = getData();
// 如果没有变化,返回 304
if (lastModified === data.lastModified) {
return res.status(304).end();
}
if (etag === data.etag) {
return res.status(304).end();
}
// 返回完整数据和缓存头
res.set({
'Last-Modified': data.lastModified,
'ETag': data.etag,
'Cache-Control': 'private, max-age=60'
});
res.json(data);
});
// 3. 浏览器内存缓存
// 使用 sessionStorage/localStorage 手动控制
class CacheManager {
constructor(storage = localStorage) {
this.storage = storage;
this.prefix = 'cache:';
}
// 3.1 设置缓存(带过期时间)
set(key, value, ttl = 3600000) {
const data = {
value,
expiry: Date.now() + ttl
};
this.storage.setItem(this.prefix + key, JSON.stringify(data));
}
// 3.2 获取缓存
get(key) {
const raw = this.storage.getItem(this.prefix + key);
if (!raw) return null;
const data = JSON.parse(raw);
// 检查是否过期
if (Date.now() > data.expiry) {
this.delete(key);
return null;
}
return data.value;
}
// 3.3 删除缓存
delete(key) {
this.storage.removeItem(this.prefix + key);
}
// 3.4 清空所有缓存
clear() {
Object.keys(this.storage)
.filter(key => key.startsWith(this.prefix))
.forEach(key => this.storage.removeItem(key));
}
}
3.3 构建性能优化
3.3.1 Webpack 构建优化
javascript
复制代码
// 1. 减少解析范围
module.exports = {
module: {
rules: [
{
test: /\.js$/,
// 1.1 排除 node_modules,大幅提升速度
exclude: /node_modules/,
// 或者使用 include 精确指定
include: path.resolve(__dirname, 'src'),
use: 'babel-loader'
}
]
},
// 2. 开启缓存
cache: {
// 2.1 使用文件系统缓存
type: 'filesystem',
// 2.2 缓存目录
buildPath: '.cache',
// 2.3 缓存配置
cacheDirectory: path.resolve(__dirname, '.cache/babel'),
// 2.4 压缩等级
compression: 'gzip',
// 2.5 缓存版本
version: 'v1'
},
// 3. 并行构建
optimization: {
// 3.1 使用 thread-loader 启用多进程
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: 'thread-loader',
options: {
workers: 4, // 启动 4 个 worker
workerParallelJobs: 50
}
},
'babel-loader'
]
}
]
},
// 3.2 使用 TerserPlugin 多进程压缩
minimizer: [
new TerserPlugin({
parallel: true, // 启用多进程
terserOptions: {
compress: {
drop_console: true
}
}
})
]
},
// 4. 减少查找路径
resolve: {
// 4.1 明确指定模块目录,减少搜索
modules: [
path.resolve(__dirname, 'src'),
'node_modules'
],
// 4.2 减少扩展名列表
extensions: ['.js', '.vue', '.json'],
// 4.3 使用 alias 快速定位
alias: {
'@': path.resolve(__dirname, 'src'),
'vue$': 'vue/dist/vue.esm.js'
}
},
// 5. 分包策略
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
// 第三方库单独打包
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10,
reuseExistingChunk: true // 复用已存在的 chunk
},
// 公共模块打包
common: {
minChunks: 2,
priority: 5,
reuseExistingChunk: true
}
}
},
// 运行时单独打包
runtimeChunk: 'single'
},
// 6. 生成环境优化
mode: 'production',
// 6.1 不生成 sourcemap
devtool: false,
// 6.2 启用 tree-shaking
optimization: {
usedExports: true,
sideEffects: true
},
// 6.3 标记副作用
module: {
rules: [
{
test: /\.css$/,
sideEffects: true
}
]
}
};
3.3.2 Vite 构建优化
javascript
复制代码
// vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
// 1. 依赖预构建
optimizeDeps: {
// 1.1 需要预构建的依赖
include: [
'vue',
'vue-router',
'vuex',
'element-plus',
'lodash-es'
],
// 1.2 排除不需要预构建的
exclude: [],
// 1.3 构建算法
buildId: 'vite',
// 1.4 清理缓存
clean: false
},
build: {
// 2. 目标环境
target: 'es2015',
// 2.1 不生成 sourcemap
sourcemap: false,
// 2.2 压缩
minify: 'terser',
// 2.3 chunk 大小警告
chunkSizeWarningLimit: 500,
// 2.4 手动分包
rollupOptions: {
output: {
manualChunks: {
// Vue 相关
'vue-vendor': ['vue', 'vue-router', 'vuex'],
// UI 库
'element-vendor': ['element-plus'],
// 工具库
'utils-vendor': ['lodash-es', 'axios']
},
// 分割 vendor
vendorChunkId: 'vendors'
}
},
// 3. CSS 代码分割
cssCodeSplit: true,
// 4. 资源内联阈值
// 4.1 小于此大小的资源内联为 base64
assetsInlineLimit: 4096 // 4KB
// 5. 静态资源内联
// 使用 ?inline 查询参数内联资源
// <script src="./utils.js?inline">
},
// 6. 压缩配置
compress: {
// 6.1 gzip 压缩
gzip: true,
// 6.2 gzip 压缩级别
gzipSize: 0.5,
// 6.3 brotli 压缩
brotli: true
},
// 7. 构建分析
// 使用 vite-plugin-visualizer
plugins: [
// 可视化构建分析
// { stats: 'summary' }
],
// 8. 环境变量注入
define: {
// 注入全局变量,避免运行时求值
__APP_VERSION__: JSON.stringify(process.env.npm_package_version)
}
});
四、浏览器原理与前端架构
4.1 浏览器渲染原理
4.1.1 关键渲染路径
javascript
复制代码
// 1. 关键渲染路径(Critical Rendering Path)
// 从 HTML 到像素在屏幕上显示的过程
// 1.1 构建 DOM 树
// HTML 解析器逐行解析 HTML,构建 DOM 树
// 遇到 <script> 会阻塞渲染(默认行为)
// 1.2 构建 CSSOM 树
// CSS 解析器解析所有 CSS(内联、外链、inline)
// CSS 是 "渲染阻塞":必须等到所有 CSS 下载解析完才能渲染
// 1.3 合成渲染树(Render Tree)
// DOM + CSSOM = Render Tree
// 只包含可见节点(display: none 的不包含)
// 1.4 布局(Layout)
// 计算每个节点的位置和大小
// 首次布局后,后续更新叫 "重排"(reflow)
// 1.5 绘制(Paint)
// 将节点转换为像素
// 首次绘制后,后续更新叫 "重绘"(repaint)
// 1.6 合成(Composite)
// 将各层合成为最终图像
// 使用 GPU 加速
// 2. 优化关键渲染路径
// 2.1 减少关键资源数量
// - 异步加载 JS:<script async> 或 <script defer>
// - 合并 CSS
// - 预加载关键资源
// 2.2 减少关键路径长度
// - 精简 CSS(移除未使用的样式)
// - 压缩资源
// 2.3 减少关键字节数
// - gzip 压缩
// - HTTP/2 多路复用
// 3. 示例:异步加载脚本
// 不阻塞渲染
<script defer src="/main.js"></script>
// defer 在 HTML 解析完成后执行,按顺序执行
// 阻塞渲染,但可以在下载期间解析 HTML
<script async src="/analytics.js"></script>
// async 下载完立即执行,不按顺序,可能阻塞渲染
// 4. CSS 加载优化
// <link rel="preload"> 提前加载关键 CSS
<link rel="preload" href="/critical.css" as="style" onload="this.rel='stylesheet'">
// 5. 内联关键 CSS
// 将首屏需要的 CSS 内联到 <head> 中
<style>
/* 关键 CSS */
body { margin: 0; padding: 0; }
.header { background: #333; }
</style>
// 非关键 CSS 异步加载
<link rel="preload" href="/non-critical.css" as="style" onload="this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/non-critical.css"></noscript>
4.1.2 事件循环与渲染调度
javascript
复制代码
// 1. 事件循环(Event Loop)
// JavaScript 单线程的执行模型
// 1.1 执行栈(Call Stack)
// 存放正在执行的函数
function foo() {
bar(); // 调用 bar
}
function bar() {
console.log('bar');
}
foo(); // foo 入栈 -> bar 入栈 -> bar 出栈 -> foo 出栈
// 1.2 任务队列(Task Queue)
// 宏任务:setTimeout, setInterval, I/O, UI rendering
// 微任务:Promise.then, MutationObserver, queueMicrotask
console.log('1'); // 同步,立即执行
setTimeout(() => {
console.log('2'); // 宏任务,在下一个事件循环执行
}, 0);
Promise.resolve().then(() => {
console.log('3'); // 微任务,在当前事件循环末尾执行
});
console.log('4'); // 同步
// 输出顺序:1, 4, 3, 2
// 微任务在下一个宏任务之前执行
// 2. requestAnimationFrame(RAF)
// 在下一次浏览器重绘之前调用
// 用于动画和视觉更新
let startTime;
function animate(timestamp) {
if (!startTime) startTime = timestamp;
const elapsed = timestamp - startTime;
// 执行动画
element.style.transform = `translateX(${elapsed}px)`;
if (elapsed < 1000) {
// 继续动画
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
// 每秒 60 帧(大约 16.67ms)
// 3. requestIdleCallback
// 在浏览器空闲时执行低优先级任务
// 用于后台工作,不影响主线程
requestIdleCallback((deadline) => {
// deadline.timeRemaining() 返回剩余时间
// deadline.didTimeout 是否超时
while (deadline.timeRemaining() > 0 && tasks.length > 0) {
const task = tasks.shift();
task();
}
// 如果还有任务,继续调度
if (tasks.length > 0) {
requestIdleCallback(arguments.callee);
}
}, { timeout: 2000 }); // 2秒超时
// 4. 宏任务与微任务调度
console.log('script start'); // 1
setTimeout(() => console.log('setTimeout'), 0); // 宏任务
Promise.resolve()
.then(() => console.log('Promise 1'))
.then(() => console.log('Promise 2'));
queueMicrotask(() => console.log('queueMicrotask'));
console.log('script end'); // 2
// 输出:
// script start
// script end
// Promise 1
// queueMicrotask
// Promise 2
// setTimeout
// 5. 渲染时机
// 浏览器在每个宏任务之后检查是否需要渲染
// 同一事件循环内多次 DOM 修改,合并为一次渲染
// 错误的做法:多次修改触发多次重排
element.style.transform = 'translateX(100px)';
element.style.transform = 'translateX(200px)'; // 覆盖前一个
// 正确的做法:合并修改
element.style.transform = 'translateX(200px)'; // 只触发一次
// 6. 使用 requestAnimationFrame 批量更新
class BatchUpdater {
constructor() {
this.pendingUpdates = [];
this.frameId = null;
}
scheduleUpdate(update) {
this.pendingUpdates.push(update);
if (!this.frameId) {
this.frameId = requestAnimationFrame(() => {
this.flush();
});
}
}
flush() {
// 在下一帧批量执行所有更新
this.pendingUpdates.forEach(fn => fn());
this.pendingUpdates = [];
this.frameId = null;
}
}
4.2 前端架构设计
4.2.1 微前端架构
javascript
复制代码
// 微前端(Micro Frontends)架构实现
// 1. 微前端基座(主应用)
class MicroFrontend {
constructor() {
// 存储注册的子应用
this.apps = {};
// 当前激活的子应用
this.currentApp = null;
}
// 1.1 注册子应用
register(name, entry, props = {}) {
this.apps[name] = {
entry, // 子应用入口(HTML/JS)
props, // 传递给子应用的 props
instance: null, // 子应用实例
activeRule: null // 激活规则
};
}
// 1.2 设置激活规则
setActiveRule(name, rule) {
if (this.apps[name]) {
this.apps[name].activeRule = rule;
}
}
// 1.3 启动子应用
async mount(name) {
const app = this.apps[name];
if (!app) throw new Error(`App ${name} not found`);
// 获取子应用资源
const { scripts, styles } = await this.loadAssets(app.entry);
// 加载样式
await this.loadStyles(styles);
// 加载脚本并执行
const mountFunction = await this.loadScripts(scripts);
// 调用子应用的 mount 方法
app.instance = mountFunction(app.props);
this.currentApp = name;
}
// 1.4 卸载子应用
async unmount(name) {
const app = this.apps[name];
if (app && app.instance) {
// 调用子应用的 unmount 方法
if (typeof app.instance.unmount === 'function') {
await app.instance.unmount();
}
app.instance = null;
}
if (this.currentApp === name) {
this.currentApp = null;
}
}
// 1.5 加载资源
async loadAssets(entry) {
// 获取 HTML
const html = await fetch(entry).then(r => r.text());
// 解析 HTML 中的 script 和 link 标签
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const scripts = Array.from(doc.querySelectorAll('script[src]'))
.map(s => s.src);
const styles = Array.from(doc.querySelectorAll('link[href]'))
.filter(l => l.rel === 'stylesheet')
.map(l => l.href);
return { scripts, styles };
}
// 1.6 加载样式
async loadStyles(urls) {
await Promise.all(urls.map(url => {
return new Promise((resolve, reject) => {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = url;
link.onload = resolve;
link.onerror = reject;
document.head.appendChild(link);
});
}));
}
// 1.7 加载脚本
async loadScripts(urls) {
// 依次加载脚本(保持顺序)
for (const url of urls) {
await this.loadScript(url);
}
}
async loadScript(url) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = url;
script.onload = resolve;
script.onerror = reject;
document.body.appendChild(script);
});
}
}
// 2. 子应用导出(子应用需导出以下生命周期)
// 子应用 main.js 示例:
export function mount(props) {
// 创建应用实例
const app = createApp(App);
// 挂载到容器
app.mount(props.container);
// 返回实例
return {
unmount() {
app.unmount();
}
};
}
// 3. 路由驱动
function setupRouter() {
// 子应用路由
const routes = [
{ path: '/app1/*', app: 'app1' },
{ path: '/app2/*', app: 'app2' }
];
window.addEventListener('popstate', () => {
renderRoute();
});
function renderRoute() {
const path = window.location.pathname;
for (const route of routes) {
if (path.match(route.path)) {
// 挂载对应的子应用
microFrontend.mount(route.app);
return;
}
}
// 404
microFrontend.unmount(currentApp);
}
// 拦截链接点击
document.addEventListener('click', (e) => {
const link = e.target.closest('a[href]');
if (link && link.href.startsWith(window.location.origin)) {
e.preventDefault();
window.history.pushState(null, '', link.href);
renderRoute();
}
});
renderRoute();
}
// 4. 子应用通信(自定义事件)
// 4.1 主应用发送事件
function emit(event, data) {
window.dispatchEvent(new CustomEvent(`mf:${event}`, { detail: data }));
}
// 4.2 子应用监听事件
window.addEventListener('mf:data', (e) => {
console.log('Received:', e.detail);
});
// 4.3 使用 shared 模块共享状态
const sharedState = {
user: null,
setUser(user) {
this.user = user;
// 通知所有应用
window.dispatchEvent(new CustomEvent('user:change', { detail: user }));
}
};
// 4.4 子应用获取状态
function getSharedState() {
return sharedState.user;
}
4.2.2 模块联邦(Module Federation)
javascript
复制代码
// Webpack 5 模块联邦(Module Federation)
// 允许独立构建的应用共享模块
// 1. 主机应用(Host)
// webpack.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
plugins: [
new ModuleFederationPlugin({
// 1.1 应用名称
name: 'host_app',
// 1.2 暴露的模块(其他应用可引用)
// 其他应用可以通过 import('host_app/navbar') 使用
exposes: {
'./Navbar': './src/components/Navbar'
},
// 1.3 共享的依赖(多个应用共用同一个库)
shared: {
// 共享 vue
vue: {
singleton: true, // 单例模式,整个页面只有一个 vue 实例
requiredVersion: '^3.0.0'
},
// 共享 vuex
vuex: {
singleton: true
}
}
})
]
};
// 2. 远程应用(Remote)
// webpack.config.js
module.exports = {
plugins: [
new ModuleFederationPlugin({
// 2.1 应用名称
name: 'remote_app',
// 2.2 暴露的模块
exposes: {
'./Button': './src/components/Button',
'./Card': './src/components/Card'
},
// 2.3 远程地址(主机引用时从此地址加载)
// 在开发环境可以是 localhost
// 在生产环境需要指向实际部署地址
filename: 'remoteEntry.js', // 生成的入口文件名
shared: ['vue']
})
]
};
// 3. 主机应用使用远程模块
// App.js
import React, { lazy, Suspense } from 'react';
// 3.1 方式一:直接 import(同步)
// import Button from 'remote_app/Button';
// 3.2 方式二:懒加载(异步)
const Button = lazy(() => import('remote_app/Button'));
function App() {
return (
<div>
<h1>Host App</h1>
<Suspense fallback={<div>Loading Button...</div>}>
<Button />
</Suspense>
</div>
);
}
// 4. 动态引用(运行时加载)
// 4.1 创建容器
const container = document.createElement('div');
// 4.2 动态加载远程模块
async function loadRemoteModule(remoteName, moduleName) {
// 动态导入远程入口
await __webpack_require__.e(`${remoteName}-${moduleName}`);
// 获取模块
return __webpack_require__(`.${remoteName}/${moduleName}`);
}
// 使用
async function loadButton() {
const { default: Button } = await loadRemoteModule('remote_app', './Button');
return Button;
}
// 5. 模块联邦类型共享
// 5.1 暴露类型
new ModuleFederationPlugin({
name: 'shared_lib',
// 暴露类型定义
exposes: {
'./types': './src/types/index.ts'
}
});
// 5.2 使用共享类型
// 在 tsconfig.json 中配置
{
"compilerOptions": {
"paths": {
"shared_lib/*": ["./node_modules/shared_lib/*"]
}
}
}
// 6. 运行时共享
// 如果 remote_app 需要共享状态,可以通过 shared 模块
// remote_app 暴露一个 store
new ModuleFederationPlugin({
name: 'remote_app',
exposes: {
'./store': './src/store'
},
shared: ['vue']
});
// host_app 在使用时可以传递自己的 store
import("./remote_app/store").then(module => {
// 使用 host 提供的 store
module.initStore(hostStore);
});
4.3 前端监控与错误处理
4.3.1 错误监控实现
javascript
复制代码
// 1. 全局错误监控
class ErrorMonitor {
constructor(options = {}) {
this.options = options;
this.errors = []; // 错误队列
this.maxQueue = options.maxQueue || 100; // 最大队列长度
}
// 1.1 初始化监控
init() {
// 监控未捕获的异步错误
window.addEventListener('error', (event) => {
this.handleError({
type: 'error',
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
stack: event.error?.stack
});
});
// 监控未捕获的 Promise 错误
window.addEventListener('unhandledrejection', (event) => {
this.handleError({
type: 'unhandledrejection',
message: event.reason?.message || event.reason,
stack: event.reason?.stack
});
});
// 监控资源加载错误
window.addEventListener('error', (event) => {
if (event.target !== window) {
this.handleError({
type: 'resource',
message: `Failed to load: ${event.target.src || event.target.href}`
});
}
}, true);
}
// 1.2 处理错误
handleError(error) {
const errorInfo = {
...error,
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent
};
// 添加到队列
this.errors.push(errorInfo);
// 超过最大长度,移除最早的
if (this.errors.length > this.maxQueue) {
this.errors.shift();
}
// 上报错误
this.report(errorInfo);
}
// 1.3 上报错误
report(errorInfo) {
// 延迟发送,避免影响性能
setTimeout(() => {
this.sendToServer(errorInfo);
}, 1000);
}
async sendToServer(errorInfo) {
try {
await fetch('/api/errors', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(errorInfo)
});
} catch (e) {
console.error('Failed to report error:', e);
}
}
// 1.4 获取错误列表
getErrors() {
return [...this.errors];
}
// 1.5 清空错误列表
clear() {
this.errors = [];
}
}
// 使用
const monitor = new ErrorMonitor({ maxQueue: 50 });
monitor.init();
// 2. Vue 错误监控
Vue.config.errorHandler = (err, vm, info) => {
console.error('Vue Error:', err);
console.error('Component:', vm);
console.error('Info:', info);
};
// 3. React 错误边界
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 更新 state,使下次渲染显示 fallback UI
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
// 上报错误
console.error('React Error:', error, errorInfo);
// 可以在这里发送错误到服务器
reportError(error, errorInfo);
}
render() {
if (this.state.hasError) {
return this.props.fallback || <div>出错了</div>;
}
return this.props.children;
}
}
// 使用
<ErrorBoundary fallback={<div>加载失败</div>}>
<MyComponent />
</ErrorBoundary>;
// 4. 性能监控
class PerformanceMonitor {
constructor() {
this.metrics = {};
}
// 4.1 监控页面加载
observeLoad() {
// LCP (Largest Contentful Paint)
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
const lastEntry = entries[entries.length - 1];
console.log('LCP:', lastEntry.startTime);
}).observe({ entryTypes: ['largest-contentful-paint'] });
// FID (First Input Delay)
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
entries.forEach(entry => {
console.log('FID:', entry.processingStart - entry.startTime);
});
}).observe({ entryTypes: ['first-input'] });
// CLS (Cumulative Layout Shift)
new PerformanceObserver((entryList) => {
let cls = 0;
entries.forEach(entry => {
if (!entry.hadRecentInput) {
cls += entry.value;
}
});
console.log('CLS:', cls);
}).observe({ entryTypes: ['layout-shift'] });
}
// 4.2 监控资源加载
observeResource() {
new PerformanceObserver((entryList) => {
entries.getEntries().forEach(entry => {
console.log('Resource:', {
name: entry.name,
duration: entry.duration,
size: entry.transferSize
});
});
}).observe({ entryTypes: ['resource'] });
}
// 4.3 获取完整性能指标
getMetrics() {
const timing = performance.timing;
return {
// DNS 解析时间
dns: timing.domainLookupEnd - timing.domainLookupStart,
// TCP 连接时间
tcp: timing.connectEnd - timing.connectStart,
// TTFB (Time To First Byte)
ttfb: timing.responseStart - timing.requestStart,
// DOM 解析时间
domParse: timing.domInteractive - timing.responseEnd,
// DOM Content Loaded
domReady: timing.domContentLoadedEventEnd - timing.navigationStart,
// 首屏渲染时间
fcp: performance.getEntriesByType('paint')[0]?.startTime,
// 最大内容绘制
lcp: this.getLCP()
};
}
getLCP() {
const entries = performance.getEntriesByType('largest-contentful-paint');
return entries[entries.length - 1]?.startTime;
}
}
五、综合架构设计
5.1 前端工程化体系
javascript
复制代码
// 完整前端工程化体系架构
// 1. 目录结构
const projectStructure = {
'src': {
// 页面/视图
'pages': {
'Home': {
'index.js', // 页面入口
'index.scss', // 页面样式
'components': {} // 页面级组件
}
},
// 业务组件(可复用)
'components': {
'Button': {
'index.js',
'index.scss',
'index.test.js'
}
},
// 通用工具
'utils': {
'request.js', // axios 封装
'storage.js', // 存储封装
'format.js' // 格式化工具
},
// 公共样式
'styles': {
'variables.scss', // 变量
'mixins.scss', // mixin
'reset.scss' // 重置样式
},
// 服务层(API)
'services': {
'user.js',
'product.js'
},
// 状态管理
'store': {
'index.js',
'user.js',
'cart.js'
},
// 路由配置
'router': {
'index.js',
'routes.js'
},
// 全局配置
'config': {
'index.js',
'dev.js',
'prod.js'
}
},
// 配置目录
'config': {
'jest.config.js',
'eslintrc.js',
'prettierrc.js',
'commitlint.config.js'
},
// 脚本目录
'scripts': {
'build.js', // 自定义构建脚本
'analyze.js' // 构建分析
},
// 文档
'docs': {}
};
// 2. package.json scripts 配置
const packageScripts = {
// 开发
"dev": "vite --mode development",
"dev:prod": "vite --mode production",
// 构建
"build": "vite build",
"build:analyze": "vite build --mode analyze",
// 测试
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
// 代码检查
"lint": "eslint src --ext .js,.vue,.jsx,.tsx",
"lint:fix": "eslint src --fix",
"lint:style": "stylelint src/**/*.{css,scss,vue}",
// 格式化
"format": "prettier --write \"src/**/*.{js,vue,jsx,tsx,css,scss}\"",
// 提交
"commit": "cz",
"push": "git push",
// 完整检查
"pre-commit": "npm run lint && npm run test",
// CI/CD
"ci": "npm install && npm run lint && npm run test && npm run build"
};
// 3. 环境变量配置
const viteEnvConfig = {
// .env.development
'VITE_APP_TITLE': 'My App (Dev)',
'VITE_API_BASE_URL': 'http://localhost:3000',
'VITE_ENABLE_MOCK': 'true',
// .env.production
'VITE_APP_TITLE': 'My App',
'VITE_API_BASE_URL': 'https://api.example.com',
'VITE_ENABLE_MOCK': 'false'
};
// 4. CI/CD 配置
// .github/workflows/main.yml
const ciConfig = `
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '16'
- run: npm ci
- run: npm run lint
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '16'
- run: npm ci
- run: npm run test:coverage
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '16'
- run: npm ci
- run: npm run build
env:
VITE_API_BASE_URL: \${{ secrets.API_URL }}
`;
// 5. Monorepo 配置
// pnpm-workspace.yaml
const pnpmWorkspace = `
packages:
- 'packages/*'
`;
// 6. 模块联邦在 Monorepo 中的应用
// packages/host/webpack.config.js
new ModuleFederationPlugin({
name: 'host',
remotes: {
// 直接引用本地包
remote_app: 'remote_app@http://localhost:3001/remoteEntry.js'
},
shared: ['vue']
});
六、学习清单
| 类别 |
题目 |
难度 |
重点 |
| Webpack |
loader 和 plugin 区别 |
⭐ |
生命周期、执行顺序 |
| Webpack |
Webpack 构建流程 |
⭐⭐ |
Tapable、编译流程 |
| Webpack |
splitChunks 原理 |
⭐⭐ |
分包策略、缓存 |
| Webpack |
Tree-shaking 原理 |
⭐⭐ |
sideEffects、usedExports |
| Webpack |
手写 loader |
⭐⭐ |
同步/异步、pitch |
| Vite |
Vite 工作原理 |
⭐⭐ |
ESM、按需编译 |
| Vite |
Vite HMR 原理 |
⭐⭐ |
WebSocket、模块更新 |
| Vite |
Vite 与 Webpack 对比 |
⭐ |
启动速度、构建速度 |
| React |
useMemo/useCallback 区别 |
⭐ |
依赖、缓存策略 |
| React |
React.memo 原理 |
⭐ |
浅比较、自定义比较 |
| React |
useTransition 作用 |
⭐⭐ |
并发模式、优先级 |
| React |
虚拟列表原理 |
⭐⭐ |
懒渲染、滚动计算 |
| 性能 |
关键渲染路径优化 |
⭐⭐ |
CRP、阻塞分析 |
| 性能 |
防抖/节流原理 |
⭐ |
场景、区别 |
| 性能 |
浏览器缓存策略 |
⭐⭐ |
强缓存、协商缓存 |
| 架构 |
微前端实现 |
⭐⭐⭐ |
qiankun、single-spa |
| 架构 |
模块联邦原理 |
⭐⭐ |
共享依赖、远程加载 |
| 监控 |
错误边界原理 |
⭐⭐ |
生命周期、错误恢复 |