Elpis项目工程化实践分析
目录
多页面构建机制
入口文件识别
Elpis项目采用了自动化的多页面构建机制,通过约定式的入口文件命名规则实现:
javascript
// webpack.base.js 中的实现
const glob = require("glob");
const path = require("path");
// 动态构造 pageEntries 和 HtmlWebpackPluginList
const pageEntries = {};
const HtmlWebpackPluginList = [];
// 获取 app/pages 目录下所有入口文件(entry.xx.js)
const entryList = path.resolve(process.cwd(), './app/pages/**/entry.*.js');
glob.sync(entryList).forEach((file) => {
const entryName = path.basename(file, '.js');
// 构造entry
pageEntries[entryName] = file;
// 构造最终渲染的页面文件
HtmlWebpackPluginList.push(
// html-webpack-plugin 辅助注入打包后的 bundle 文件到 tpl 文件中
new HtmlWebpackPlugin({
// 产物 (最终模板) 输出路径
filename: path.resolve(process.cwd(), './app/public/dist', `${entryName}.tpl`),
// 指定要使用的模板文件
template: path.resolve(process.cwd(), './app/view/entry.tpl'),
// 要注入的代码块
chunks: [entryName],
})
)
});
核心工作原理
- 约定式命名 :所有页面入口必须以
entry.页面名.js
形式命名 - 自动扫描 :使用
glob
库扫描app/pages
目录下所有符合规则的入口文件 - 动态配置 :根据扫描结果自动构建webpack的
entry
配置和HtmlWebpackPlugin
插件配置 - 模板生成 :为每个页面生成独立的
.tpl
模板文件,注入对应的js资源
页面入口结构
每个页面入口文件通过标准化的引导方式创建Vue应用:
javascript
// app/pages/page1/entry.page1.js
import Page1 from './page1.vue';
import boot from '../boot';
boot(Page1);
统一的引导文件boot.js
处理常见的初始化逻辑:
javascript
// boot.js主要功能
export default (pageComponent, { routes, libs } = {}) => {
const app = createApp(pageComponent);
// 使用element-plus
app.use(ElementPlus);
// 使用pinia
app.use(pinia);
// 使用第三方库和路由
// ...
app.mount("#root");
}
分包策略
代码分割方案
Elpis项目使用了精细的代码分割策略,将代码分为三类:
javascript
// webpack.base.js中的分包配置
optimization: {
splitChunks: {
chunks: 'all', // 对同步和异步模块都进行分割
maxAsyncRequests: 10, // 每次异步加载的最大并行请求数
maxInitialRequests: 10, // 入口点的最大并行请求数
cacheGroups: {
vendor: { // 第三方依赖库
test: /[\\/]node_modules[\\/]/, // 打包 node_modules 下的模块
name: 'vendor', // 模块名称
priority: 20, // 优先级,数字越大,优先级越高
enforce: true, // 强制执行
reuseExistingChunk: true, // 复用已有的公共 chunk
},
common: { // 业务代码公共部分
name: 'common', // 模块名称
minChunks: 2, // 被两处引用即被归为公共模块
minSize: 1, // 最小分割文件大小(1 byte)
priority: 10, // 优先级,数字越大,优先级越高
reuseExistingChunk: true, // 复用已有的公共 chunk
}
}
},
// 将 webpack 运行时生成的代码打包到 runtime.js
runtimeChunk: true,
}
分包类型详解
-
vendor包:
- 包含所有第三方库代码(node_modules)
- 变动频率最低,适合长期缓存
- 优先级最高,确保第三方库代码不会被其他规则分割
-
common包:
- 包含业务代码中的公共部分
- 条件是被至少两个不同页面引用
- 变动频率适中,适合中期缓存
-
页面特定代码:
- 每个页面特有的业务逻辑
- 变动频率最高,适合短期缓存
-
runtime包:
- 包含webpack的运行时代码
- 独立抽取,避免影响其他包的缓存效果
缓存优化机制
开发环境和生产环境使用不同的文件名策略:
javascript
// 开发环境(webpack.dev.js)
output: {
filename: 'js/[name]_[chunkhash:8].bundle.js',
//...
}
// 生产环境(webpack.prod.js)
output: {
filename: 'js/[name]_[chunkhash:8].bundle.js',
//...
}
使用chunkhash
确保文件内容变化时哈希值才会变化,有效利用浏览器缓存。
热更新原理
开发环境配置
Elpis项目中的热更新(HMR)主要通过以下方式实现:
javascript
// webpack.dev.js 中的热更新配置
// 开发阶段的 entry 配置需要加入 hmr
Object.keys(baseConfig.entry).forEach(v => {
// 第三方包不作为 hmr 的入口
if (v !== 'vendor') {
baseConfig.entry[v] = [
// 主入口文件
baseConfig.entry[v],
`webpack-hot-middleware/client?path=http://${DEV_SERVER_CONFIG.HOST}:${DEV_SERVER_CONFIG.PORT}/${DEV_SERVER_CONFIG.HMR_PATH}&timeout=${DEV_SERVER_CONFIG.TIMEOUT}&reload=true`,
];
}
});
// 开发阶段插件
plugins: [
// 热更新插件 用于实现热模块替换
// 模块热替换允许在应用程序运行时替换模块
// 极大的提升开发效率,因为能让应用程序一直保持运行状态
new webpack.HotModuleReplacementPlugin({
multiStep: true,
}),
],
HMR工作流程
-
客户端注入:
- 为每个页面入口添加
webpack-hot-middleware/client
客户端脚本 - 客户端与服务器建立WebSocket连接,监听变更
- 为每个页面入口添加
-
服务器监听:
- 使用
webpack-dev-middleware
和webpack-hot-middleware
在服务器端监控文件变化 - 当文件变化时,重新编译并通知客户端
- 使用
-
模块替换:
- 客户端接收到变更通知后,请求更新的模块代码
- 通过HMR运行时API安全地替换旧模块,而不需刷新整个页面
- Vue单文件组件天然支持HMR,组件自动更新
-
多步骤更新:
- 使用
multiStep: true
选项实现更细粒度的热更新 - 减少单次更新的体积,提高更新效率
- 使用
构建优化
开发环境优化
javascript
// 开发环境特定配置
mode: 'development',
// source-map 开发工具,呈现代码的映射关系,便于在开发过程中调试代码
devtool: 'eval-cheap-module-source-map',
生产环境优化
javascript
// 生产环境配置
mode: 'production',
// 生产环境不需要 source map 或使用适合生产的轻量级 source map
devtool: false,
// 压缩和优化
optimization: {
minimize: true,
minimizer: [
new TerserWebpackPlugin({
cache: true, // 启用缓存来加速构建过程
parallel: true, // 利用多核 CPU 加速构建过程
terserOptions: {
compress: {
drop_console: true, // 删除所有的 console.*
},
},
}),
],
}
加载器优化
javascript
// 只对业务代码进行babel转换,加快构建速度
{
test: /\.js$/,
include: [
// 只对业务代码进行 babel ,加快 webpack 打包速度
path.resolve(process.cwd(), 'app/pages'),
],
use: {
loader: 'babel-loader',
}
}
工程化价值
多页面应用的优势
- 独立部署:各页面可以独立开发、测试和部署
- 按需加载:用户只需加载当前访问页面的资源
- 资源隔离:页面间资源互不影响,降低耦合度
- 性能优化:首屏加载更快,用户体验更好
分包策略的价值
- 缓存效率:不同变更频率的代码分开打包,优化缓存策略
- 并行加载:多个小包可以并行下载,提高资源加载速度
- 资源复用:公共模块只需加载一次,减少重复下载
- 按需加载:结合动态导入实现真正的按需加载
热更新的价值
- 开发效率:无需手动刷新,保持应用状态
- 即时反馈:修改代码后立即看到效果
- 状态保持:保留应用运行状态,便于调试
- 提升体验:减少等待时间,提高开发体验
工程化实践总结
- 规范化:通过约定式开发降低协作成本
- 自动化:通过工具链自动完成重复工作
- 模块化:清晰的模块划分提高代码可维护性
- 优化化:针对不同环境的优化策略提高性能
By:抖音"哲玄前端"《大前端全栈实践》