Elpis系列之前端工程化

83 阅读4分钟

什么是前端工程化

前端工程化是指通过代码规范(eslint/prettier)、编译构建(webpack/vite)、打包优化自动化流程(CI/CD)来提高开发效率,保证代码质量、优化页面性能、维护团队合作的一系列操作。

该图简洁明了的展示了业务文件通过前端工程化到页面输出的整个流程。学习了抖音“哲玄前端”《大前端全栈实践》课程后,本文对编译构建和打包优化两个流程中部分知识点做一个总结。

分包

将 js 文件打包成3种类型。

  • vendor: 第三方lib库 基本不会改动 除非依赖改动或升级
  • common:业务组件代码的公共部分抽取出来,改动较少
  • entry.{page}:不同页面entry里的业务组件代码的差异部分,会经常改动

目的:

  1. 把改动和引用频率不一样的 js 区分出来,以达到更好利用浏览器缓存的效果,降低流量
  2. 打包成一个文件,太大,首屏和SEO不友好
optimization: {
    splitChunks: {
      chunks: "all", // 对同步和异步模块都进行分割
      maxAsyncRequests: 10, // 每次异步加载(import()动态导入的模块)的最大并行请求数
      maxInitialRequests: 10, // (页面初始加载时)入口文件的最大并行请求数
      cacheGroups: {
        // 第三方依赖库。当第三方依赖库同时满足common公共模块的配置时,跟根据优先级决定打包到哪个js下
        vendor: {
          name: "vendor", //模块名称
          test: /[\\/]node_modules[\\/]/, // 打包 node_modules 中的文件([\\/]是为了适配不同系统,具体可查看文档)
          priority: 20, // 优先级 数字越大优先级越高
          enforce: true, // 强制执行
          reuseExistingChunk: true, // 复用已有的公共chunk
        },
        // 公共模块
        common: {
          name: "common", //模块名称
          minChunks: 2, //被两处引用即被归为公共模块
          minSize: 1, // 生成 chunk 的最小体积(1 byte) 教学需求,根据实际情况配置
          priority: 10, // 优先级 数字越大优先级越高
          reuseExistingChunk: true, // 复用已有的公共chunk
        },
      },
    },
    // 将 webpack 运行时生成的代码打包到 runtime.js
    runtimeChunk: true,
  },

此外在optimization中还可以执行缓存,TreeShaking,压缩优化等策略

热更新

热更新HMR(Hot Module Replacement,热模块替换)是我们在开发环境中必备的功能。下图清晰展示了开发环境中启动的服务器是如何实现这一功能的

其核心功能有二:1、监控能力;2、通知能力

  1. 监控能力
    • 监控业务文件的变动。
    • 当文件被修改时,devServer 触发 Webpack 重新解析依赖关系,生成新的模块依赖图,如图中【解析引擎】
    • 解析引擎的编译结果直接存入内存而不是磁盘中,提升响应速度。
  2. 通知能力
    • devServer 通过 WebSocket与浏览器保持长连接,推送以下信息:
      • 文件变动事件(如 hash-changed
      • 新模块的加载指令(如 js/css 文件路径)
    • 浏览器接收到通知后,动态请求新的产物文件(从内存中),执行模块替换,保持应用状态

其他

多页面

  • 单页面应用只有一个页面,通过前端路由控制页面跳转,适合交互复杂的页面。多页面应用有多个页面,通过浏览器跳转,每次跳转会刷新整个页面,适合页面多单交互,不复杂的应用,各页面相互独立
  • 由于之后的工程是多页面的,每个页面都需要有一个js文件(entry)和一个模板文件(HtmlWebpackPlugin),所以初始做了遍历处理
// 动态构造 entry入口pageEntries 和HtmlWebpackPlugin htmlWebpackPluginList
const pageEntries = {};
const htmlWebpackPluginList = [];
//  path.resolve 构建绝对路径,entryStr:当前工作目录/app/pages/**/entry.*.js。 *用于给glob做匹配
const entryStr = path.resolve(process.cwd(), "./app/pages/**/entry.*.js");
// 获取app/pages 目录下所有入口文件(entry.**.js)
glob.sync(entryStr).forEach((file) => {
  const entryName = path.basename(file, ".js"); // entry.page1; entry.page2
  // 构造entry
  pageEntries[entryName] = file;
  // 构建最终渲染的页面模板(tpl) ==> 将js chunks注入到模板template中 然后输出到filename位置
  htmlWebpackPluginList.push(
    new HtmlWebpackPlugin({
      // 产物(最终模板tpl)输出路径
      filename: path.resolve(
        process.cwd(),
        "./app/public/dist/",
        `${entryName}.tpl`
      ),
      // 指定要使用的模板文件
      template: path.resolve(process.cwd(), "./app/view/entry.tpl"),
      // 要注入的代码块 此处对应入口entry key
      chunks: [entryName],
    })
  );
});

多线程打包

加快编译打包速度

// thread-loader 多线程build 通用设置
const threadLoaderCommonOptions = {
  // 产生的 worker 的数量 几核cpu就用几个线程
  workers: os.cpus().length,
  // 一个 worker 进程中并行执行工作的数量
  workerParallelJobs: 50,
  // 闲置时定时删除 worker 进程
  poolTimeout: 2000,
};

文件压缩

 optimization: {
    // 使用 TerserWebpackPlugin 的并发和缓存,提升压缩阶段的性能
    minimize: true,
    minimizer: [
      new TerserWebpackPlugin({
        cache: true, // 开启缓存濑加速构建过程
        parallel: true, // 利用多核CPU的优势濑加快压缩速度
        terserOptions: {
          compress: {
            drop_console: true, // 移除所有的console语句
          },
        },
      }),
      // 优化并压缩 css 资源
      new CssMinimizerPlugin(),
    ],
  },

参考文献

  1. juejin.cn/post/753830…
  2. juejin.cn/post/749595…