4. 核心概念-Output:output.path与output.filename配置、占位符的使用

好,咱们接着聊 Webpack 的核心概念。前面讲完了入口(Entry),打包的原材料有了,那成品往哪放?怎么命名?这就是 Output 要解决的问题。

我个人觉得,Output 配置是新手最容易忽略、但坑最多的地方。你想想看,项目构建完,一堆文件散落在 dist 目录里,名字要么是 main.js,要么是 bundle.js,浏览器缓存策略根本没法做。嗯,今天咱们就把这块彻底捋清楚。

4.1 output.path 与 output.filename

先说两个最基础的属性:pathfilename

  • output.path:指定打包后文件的输出目录。必须是绝对路径。
  • output.filename:指定每个输出文件的名称。

看个最简单的例子:

// webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  }
};

这样配置后,所有打包产物都会扔到项目根目录下的 dist 文件夹里,主文件叫 bundle.js

注意:path 必须用绝对路径。我见过不少新手直接用 './dist',结果打包完文件不知道跑哪去了。用 path.resolvepath.join 处理一下,准没错。

4.2 占位符的使用

如果所有输出文件都叫 bundle.js,那多入口打包怎么办?缓存更新怎么处理?

这时候就需要占位符了。Webpack 提供了一组内置的占位符,可以动态生成文件名。常用的有四个:

占位符 含义 典型场景
[name] 模块名称,对应 entry 的 key 多入口打包时区分文件
[hash] 本次构建的全局唯一哈希 所有文件共享同一个 hash
[chunkhash] 每个 chunk 独立的哈希 按需加载、代码分割时使用
[contenthash] 文件内容本身的哈希 CSS 文件、图片等静态资源

4.3 [name] 占位符

[name] 是最直观的占位符。它会把 entry 配置中的 key 直接替换进来。

// 多入口配置
module.exports = {
  entry: {
    index: './src/index.js',
    about: './src/about.js'
  },
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  }
};

打包后你会得到 index.jsabout.js 两个文件。简单明了。

小技巧:我习惯在开发环境用 [name].js,这样调试时能一眼看出哪个文件对应哪个入口。生产环境再换成带 hash 的版本。

4.4 [hash]、[chunkhash]、[contenthash] 的区别

这三个 hash 相关的占位符,是面试常考题,也是实际项目中最容易踩坑的地方。我一个个说。

[hash]

[hash] 是构建级别的。一次构建生成一个唯一的 hash 值,所有输出文件都共享它。

output: {
  filename: '[name].[hash].js'
}

比如你改了 index.js 里的一个变量,重新构建后,index.jsabout.js 的 hash 都会变。为什么?因为整个构建过程的 hash 变了。

避坑指南:我曾经在一个老项目里看到有人用 [hash] 做缓存策略。结果每次改一行代码,所有文件 hash 都变了,用户得重新下载全部资源。这其实违背了缓存更新的初衷。后来我全改成了 [chunkhash][contenthash]

[chunkhash]

[chunkhash] 是 chunk 级别的。每个 chunk 有自己的 hash,只有这个 chunk 的内容变了,它的 hash 才会变。

output: {
  filename: '[name].[chunkhash].js'
}

还是上面那个例子,你只改了 index.js,那只有 index.[chunkhash].js 的 hash 会变,about.[chunkhash].js 保持不变。浏览器可以继续使用缓存中的 about.js,只重新下载 index.js

为什么会这样?因为 Webpack 在构建时,会根据每个 chunk 的内容计算独立的 hash。内容不变,hash 就不变。

[contenthash]

[contenthash] 是更细粒度的。它基于文件内容本身计算 hash。哪怕你只是改了一个空格,hash 都会变。

// 配合 MiniCssExtractPlugin 使用
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  output: {
    filename: '[name].[contenthash].js'
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash].css'
    })
  ]
};

我个人建议,生产环境中 JS 文件用 [chunkhash],CSS 文件用 [contenthash]。因为 CSS 文件可能被多个 chunk 引用,用 [contenthash] 能更精确地控制缓存失效。

核心总结:

  • [hash]:一次构建一个值,所有文件共享。不推荐生产环境使用。
  • [chunkhash]:每个 chunk 独立。适合 JS 文件。
  • [contenthash]:每个文件独立。适合 CSS、图片等静态资源。

4.5 clean: true 清理旧文件

每次构建都会生成新文件,但旧文件不会自动删除。时间长了,dist 目录里会堆积大量无用的历史文件。

Webpack 5 提供了一个非常方便的配置:clean: true

module.exports = {
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[chunkhash].js',
    clean: true  // 构建前清理 dist 目录
  }
};

设置 clean: true 后,每次构建都会先清空 output.path 目录,再写入新文件。相当于每次都是「干净」的构建。

小提示:在 Webpack 4 时代,我们需要借助 clean-webpack-plugin 这个第三方插件来实现同样的功能。Webpack 5 直接内置了,省了一个依赖。如果你还在用 Webpack 4,可以考虑升级了。

嗯,到这里 Output 的核心配置就讲完了。总结一下:path 定目录,filename 定名字,占位符让名字动态化,clean: true 让目录保持整洁。下一节咱们聊聊 Loader,那才是 Webpack 真正强大的地方。