前端工程化

17 阅读3分钟

前端工程化的总结

实现完里程碑2,以下是我的一些理解和分享。
这次最大的收获,不只是“会用 webpack”,而是开始真正理解前端工程化背后的运行机制。

在以往项目中,我大多数是通过 CLI 脚手架快速启动。
包括 loaderplugin、开发环境热更新(HMR)这些能力,很多时候都是脚手架默认提供。
虽然我之前也手动配置过部分插件,写过一些插件 demo,但对“热更新是怎么跑起来的”“多线程打包为什么能提速”这类底层问题,理解并不系统。

这次里程碑2,我把重点放在了打包链路本身:解析编译 -> 模块分包 -> 压缩优化还有开发环境热更新上。
即使现在浏览器对 ES Module 支持越来越好、很多配置被封装,打包工具也具备devServer功能不需要配置热更新。理解这些底层原理依然很有价值,尤其在排查性能问题和面试表达时非常有帮助。


1. Loader 和 Plugin 的理解

先分享一下 webpack 的 loaderplugin 配置(开发环境基础能力)。

module: {
  rules: [{
    test: /\.vue$/,
    use: {
      loader: 'vue-loader'
    }
  }, {
    test: /\.js$/,
    include: [path.resolve(process.cwd(), './app/pages')], // 只对业务代码 babel
    use: {
      loader: 'babel-loader'
    }
  }, {
    test: /\.(png|jpe?g|gif)(\?.+)?$/,
    use: {
      loader: 'url-loader',
      options: {
        limit: 300,
        esModule: false
      }
    }
  }, {
    test: /\.css$/,
    use: ['style-loader', 'css-loader']
  }, {
    test: /\.less$/,
    use: ['style-loader', 'css-loader', 'less-loader']
  }, {
    test: /\.(eot|svg|ttf|woff|woff2)(\?\S*)?$/,
    use: 'file-loader'
  }]
}

这套规则的核心目的,是把 .vue/.js/.css/.less/图片/字体 等不同资源都纳入 webpack 的模块系统,统一处理依赖关系和产物引用。


2. 生产环境增强:CSS 抽离 + 多线程

生产环境我增加了 CSS 抽离和多线程打包。

module: {
  rules: [{
    test: /\.css$/,
    use: [
      MiniCssExtractPlugin.loader,
      'happypack/loader?id=css'
    ]
  }, {
    test: /\.js$/,
    include: [path.resolve(process.cwd(), './app/pages')], // 只对业务代码 babel
    use: {
      loader: 'happypack/loader?id=js'
    }
  }]
}

在我的理解里:

  • MiniCssExtractPlugin:把样式从 JS 中抽离成独立 CSS 文件,利于缓存与首屏加载策略。
  • happypack:并行处理 JS/CSS,减少构建耗时(当前也可用 thread-loader 替代,思路一致)。

另外还用到了常见插件:

  • HtmlWebpackPlugin
  • CleanWebpackPlugin
  • TerserPlugin

这些插件在实际工程和面试场景里都很常见,属于必须掌握的 webpack 基础能力。


3. 模块分包策略(splitChunks)

分包上我采用三类:

  • vendornode_modules 里的第三方依赖
  • common:被复用两次以上的公共模块
  • entry:页面业务代码

配置如下:

splitChunks: {
  chunks: 'all', // 对同步和异步模块都进行分割
  maxAsyncRequests: 10, // 每次异步加载最大并行请求数
  maxInitialRequests: 10, // 入口点最大并行请求数
  cacheGroups: {
    vendor: {
      test: /[\\/]node_modules[\\/]/,
      name: 'vendor', // 模块名称
      priority: 20, // 优先级,数字越大越高,默认 0
      enforce: true, // 强制执行
      reuseExistingChunk: true // 复用已有的公共 chunk
    },
    common: {
      name: 'common',
      minChunks: 2, // 被两处引用即为公共模块
      minSize: 1, // 最小分割文件大小(1 byte)
      priority: 10,
      reuseExistingChunk: true // 复用已有的公共 chunk
    }
  }
}

这类拆分的主要收益是:把“变动频率不同”的代码分开,提升浏览器缓存命中率,避免每次上线都让用户重复下载所有资源。


4. HMR 热更新实现理解

我这次自己实践了 HMR,基于:

  • webpack-dev-middleware
  • webpack-hot-middleware

理解如下:

  • webpack-dev-middleware:将构建产物写入内存,而不是磁盘,浏览器直接读取内存产物。
  • webpack-hot-middleware:建立客户端与构建结果的通信,变更时通知浏览器做局部更新。
  • webpack.HotModuleReplacementPlugin:开启模块热替换能力。

所以整体流程是:
监听代码变更 -> 增量编译到内存 -> 通知客户端 -> 局部替换模块
这也是当前大多数 devServer 热更新机制的核心思路。