跟着 Webpack 配置学习前端工程化

这段时间我跟着课程搭建了一套 Webpack 配置。刚开始,我主要想弄明白 entry、module、plugins 和 output 分别怎么写。后来沿着"源代码如何变成可访问的页面"这条线重新阅读,发现这些配置背后有更值得记录的工程化思路。

这篇文章结合几段实际配置,整理我目前的理解。

通过入口扫描,减少重复注册

项目会扫描 app/pages 下符合 entry.*.js 规则的文件,并把它们加入 Webpack 入口:

ini 复制代码
const pageEntries = {};

const entryList = path.resolve(
  process.cwd(),
  "./app/pages/**/entry.*.js",
);

glob.sync(entryList).forEach((file) => {
  const entryName = path.basename(file, ".js");
  pageEntries[entryName] = file;
});

module.exports = {
  entry: pageEntries,
};

例如,创建:

bash 复制代码
app/pages/page1/entry.page1.js

构建程序就能发现这个页面。

如果每新增一个页面,都要到 Webpack 配置里手动添加 entry,页面多了以后,配置文件会持续增长,也容易漏改。这里采用了目录和文件名约定,让页面自己"声明"它是一个入口。

我从这段配置学到的第一个思想是:把重复的人工操作沉淀为统一约定。当然,约定也要足够清楚。文件名如果不符合规则,页面就不会进入构建流程,因此命名规则本身也是项目文档的一部分。

从页面模板反推构建产物

发现入口后,配置会为每个入口创建一个页面模板:

css 复制代码
new HtmlWebpackPlugin({
  filename: path.resolve(
    process.cwd(),
    "./app/public/dist/",
    `${entryName}.tpl`,
  ),
  template: path.resolve(
    process.cwd(),
    "./app/view/entry.tpl",
  ),
  chunks: [entryName],
});

这段代码建立了页面入口和模板之间的对应关系:

复制代码
entry.page1.js → entry.page1.tpl

所有页面可以共用 entry.tpl 中的基础 HTML 结构,但每个生成的模板会注入对应入口所需的构建资源。

以前我看到 output 或 filename,会把它们当成"文件放在哪里"的配置。现在我会多问一步:生成的文件接下来由谁读取?

在这个项目里,.tpl 会交给页面服务渲染。因此模板的名称和路径需要与页面服务的查找方式对应。构建产物不是只要生成成功就行,还必须能被运行中的程序正确使用。

通过 Loader 规则控制处理范围

Webpack 需要处理 JavaScript、Vue 组件、Less、图片等不同文件。Loader 规则告诉它遇到某类文件时该怎样转换。

项目中的 JavaScript 规则限定了处理范围:

css 复制代码
{
  test: /.js$/,
  include: [
    path.resolve(process.cwd(), "./app/pages"),
  ],
  use: {
    loader: "babel-loader",
  },
}

test 指定文件类型,include 指定目录范围。这里将 Babel 转换集中在页面业务代码中。

我从这段配置认识到,工程化不能只考虑"规则是否生效",还要考虑"规则作用于哪些文件"。处理范围越明确,越容易判断构建过程中发生了什么,也能减少不必要的转换工作。

Less 的规则则让我理解了文件转换的顺序:

bash 复制代码
{
  test: /.less$/,
  use: ["style-loader", "css-loader", "less-loader"],
}

Loader 从右向左执行:

复制代码
Less
→ CSS
→ 解析 CSS 中的依赖
→ 将样式加入页面

这几项配置需要连起来看。前一步的输出是后一步的输入,所以 Loader 的排列顺序会直接影响构建结果。

拆分配置,是因为环境目标不同

项目把 Webpack 配置分成三个文件:

csharp 复制代码
webpack.base.js
webpack.dev.js
webpack.prod.js

开发和生产配置分别在基础配置上增加自己的内容:

php 复制代码
const baseConfig = require("./webpack.base.js");

const webpackConfig = merge.smart(baseConfig, {
  mode: "development",
  devtool: "eval-cheap-module-source-map",
  // 其他开发环境配置
});

基础配置保存入口扫描、模块处理等公共规则。开发配置关注源码定位和热更新;生产配置负责资源提取、压缩和输出。

这让我理解了配置拆分的原因:开发和生产使用同一份源代码,但需要不同的构建结果和反馈方式。开发时,我希望修改代码后尽快看到效果,也希望报错能对应到源文件。部署后,浏览器需要加载体积合理、地址正确、能够利用缓存的资源。

把公共规则放在基础配置里,可以避免两份配置逐渐出现不一致;把环境差异分别写出,也方便解释每项配置服务于什么目标。

从模板写盘,看构建工具如何与其他程序协作

开发环境使用 webpack-dev-middleware 提供构建资源,其中有一项配置:

css 复制代码
devMiddleware(compiler, {
  writeToDisk: (filePath) => filePath.endsWith(".tpl"),
  publicPath: webpackConfig.output.publicPath,
});

开发时,构建服务可以直接提供资源,不必把每个文件都写入磁盘。但项目中的页面模板需要由另一个服务从文件系统读取,因此配置选择只把 .tpl 写到磁盘。

这段配置让我印象很深。单看 Webpack,可能会觉得"为什么只写出模板";把页面访问过程连起来就容易理解了:

复制代码
Webpack 生成模板
→ 模板写入磁盘
→ 页面服务读取并渲染模板
→ 浏览器加载前端资源

我从这里学到:构建流程要考虑产物的使用方。当前端构建工具和页面服务协作时,双方怎样找到文件、怎样读取文件,都会影响构建配置。

从热更新,理解开发反馈流程

开发配置会把热更新客户端加入页面入口,并通过中间件建立更新通道:

ini 复制代码
baseConfig.entry[v] = [
  baseConfig.entry[v],
  "webpack-hot-middleware/client?...",
];

开发者修改源文件后,Webpack 重新编译,浏览器接收更新消息。

我以前把"提升开发效率"理解成缩短打包耗时。现在觉得还要看修改代码到观察结果之间,有多少步骤需要自己完成。文件监听、重新编译、通知浏览器连成一条流程后,开发者可以更快确认修改效果。

不过,配置中出现热更新插件,不代表所有改动都一定能保留页面状态并局部替换。我还需要在浏览器中实际修改组件,观察它是热替换还是重新加载页面。

从代码拆分,理解浏览器缓存

基础配置使用 splitChunks 提取第三方依赖和多个页面共用的代码:

yaml 复制代码
splitChunks: {
  chunks: "all",
  cacheGroups: {
    vendor: {
      test: /[\/]node_modules[\/]/,
      name: "vendor",
      priority: 20,
      enforce: true,
    },
    common: {
      name: "common",
      minChunks: 2,
      priority: 10,
    },
  },
}

输出文件名中还加入了哈希:

ini 复制代码
filename: "js/[name]_[chunkhash:8].bundle.js"

我对这部分的理解是:第三方依赖和业务页面的变化频率不同。把它们拆成不同资源后,未变化的文件有机会继续使用浏览器缓存;文件内容变化时,新的文件名又能让浏览器获取更新后的资源。

这也是为什么我认为代码拆分不能只停留在"配置了 splitChunks"。还应该检查最终产物:生成了几个文件、各自包含什么、页面实际请求了哪些资源。拆分是否合理,需要结合这些结果判断。

我目前对前端工程化的理解

沿着这份配置看下来,我把构建流程整理成了一条线:

复制代码
按约定发现入口
→ 将不同源文件转换为模块
→ 生成页面需要的模板和资源
→ 在开发环境提供快速反馈
→ 在生产环境组织和压缩产物
→ 由浏览器加载并运行
相关推荐
用户6353300254791 小时前
Day 1|HTML5:一个商城首页的骨架,从 DOCTYPE 到语义化
前端
平凡的阿泽1 小时前
我用 Trae 实现了AI辩论赛
前端
对空六课1 小时前
指标口径生效时间的版本查询实现
大数据·服务器·前端·算法·数据分析
对空六课2 小时前
RFM分层边界变化的回溯计算
大数据·服务器·前端·数据库·人工智能·算法·数据分析
IT_陈寒2 小时前
Vite打包的诡异行为,让我差点提交了个错误版本
前端·人工智能·后端
鹏多多2 小时前
前端是如何死去的?| 完整时间线
前端·javascript·程序员
coding漫漫长路2 小时前
海报挪进Worker画,canvas字体又变回默认了
前端
看浪的路人2 小时前
第2讲:ReAct 模式从零实现——不用框架,手写一个 Agent 内核
前端·react.js·前端框架
独孤九剑打醒他2 小时前
【原创开源·航空终极篇】氢能航空动力升级:双燃料掺氢、富氧补气与露天储氢安全模型
前端·架构·开源·硬件工程·能源