4、核心配置之Output:path与filename配置、publicPath详解、clean清理输出、多入口输出配置、library与libraryTarget

好,咱们接着聊 Webpack 的核心配置。上一章我们把 entry 入口讲透了,那打包出来的东西放哪儿?怎么命名?这就是 output 配置要干的事。

说实话,output 这块儿我刚开始学的时候觉得挺简单的,不就是指定个输出目录嘛。但后来踩了不少坑才发现,这里面的门道真不少。尤其是 publicPath,当年让我在部署环境上折腾了一整天。

path 与 filename:最基础的输出配置

先看最常用的两个配置项:pathfilename

path 指定输出目录,必须是绝对路径。我习惯用 Node.js 的 path.resolve() 来处理。

filename 指定输出文件的名称。你可以写死,比如 bundle.js,但更常见的是用占位符动态生成。

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

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

嗯,这是最基础的写法。但实际项目中,我们很少用固定的文件名。为什么?因为浏览器缓存。

你想想看,如果每次打包都叫 bundle.js,浏览器会认为文件没变,直接读缓存。你修了个 bug 重新部署,用户看到的还是旧代码。这就尴尬了。

所以我们会用 [contenthash] 这个占位符。它根据文件内容生成哈希值,内容变了哈希就变,文件名也就变了。浏览器一看文件名不同,乖乖去下载新文件。

output: {
  path: path.resolve(__dirname, 'dist'),
  filename: 'js/[name].[contenthash:8].js'
}

这里 [name] 对应入口的名称,[contenthash:8] 取哈希的前8位。我还习惯把 JS 文件放到 js/ 子目录下,保持输出目录整洁。

我的小习惯:生产环境用 [contenthash],开发环境用 [name] 就够了。开发时频繁改代码,每次哈希都变,编译速度会受影响。

publicPath:部署路径的坑

这个配置我当年栽过大跟头。简单说,publicPath 告诉 Webpack:打包后的资源在服务器上通过什么 URL 路径来访问。

举个例子。你的项目部署在 https://example.com/my-app/ 这个路径下,那 publicPath 就要设为 /my-app/。否则加载 JS、CSS、图片时路径会出错。

output: {
  path: path.resolve(__dirname, 'dist'),
  filename: 'js/[name].[contenthash:8].js',
  publicPath: '/my-app/'
}

我曾经把一个项目部署到 CDN 上,CDN 的域名是 https://cdn.example.com,资源放在 /assets/ 目录下。结果我忘了配 publicPath,所有资源都去根路径找,页面白屏了一下午。

后来怎么解决的?把 publicPath 设为 https://cdn.example.com/assets/,问题就解决了。

注意:publicPath 的值要以 / 结尾,否则路径拼接会出问题。比如 /my-app/my-app/ 差别很大。

常见的 publicPath 场景:

部署场景 publicPath 示例
根路径部署 '/'
子路径部署 '/my-app/'
CDN 部署 'https://cdn.example.com/assets/'
相对路径 './'''

clean:告别手动删除 dist

每次打包前,你是不是还要手动删一下 dist 目录?我以前就是这么干的,直到发现了 clean: true

在 Webpack 5 中,output.clean 可以自动清理输出目录。每次打包前,它会删除 path 指定的目录下的所有文件,然后重新生成。

output: {
  path: path.resolve(__dirname, 'dist'),
  filename: 'js/[name].[contenthash:8].js',
  clean: true
}

这个配置太省心了。你再也不用担心旧文件残留,也不用写什么 rm -rf dist 的脚本了。

注意:clean: true 只清理 path 目录下的文件,不会动其他目录。放心用。

多入口输出配置

实际项目很少只有一个入口。比如你有个后台管理系统,登录页和首页是两套独立的页面,那就需要多入口。

多入口的配置其实很简单,entry 用对象形式,output.filename[name] 占位符就行。

module.exports = {
  entry: {
    login: './src/login.js',
    admin: './src/admin.js',
    common: './src/common.js'
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'js/[name].[contenthash:8].js',
    clean: true
  }
};

这样打包后会生成三个文件:login.abc123.jsadmin.def456.jscommon.ghi789.js

我做过一个项目,有十几个入口页面。当时我用了 glob 库来动态扫描入口文件,省去了手动一个个配置的麻烦。代码大概是这样的:

const glob = require('glob');
const path = require('path');

const entries = {};
glob.sync('./src/pages/**/index.js').forEach(file => {
  const name = path.basename(path.dirname(file));
  entries[name] = file;
});

module.exports = {
  entry: entries,
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'js/[name].[contenthash:8].js',
    clean: true
  }
};

嗯,这个技巧在处理大型多页面应用时特别有用。

library 与 libraryTarget:打包成库

如果你不是打包一个应用,而是打包一个供别人使用的库(比如组件库、工具库),那就要用到 librarylibraryTarget 了。

library 指定库的名称,libraryTarget 指定库的导出格式。

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'my-library.js',
    library: 'MyLibrary',
    libraryTarget: 'umd'
  }
};

常见的 libraryTarget 有这些:

libraryTarget 说明 使用场景
var 将库赋值给一个全局变量 直接通过 <script> 标签引入
commonjs 通过 module.exports 导出 Node.js 环境
commonjs2 通过 module.exports 导出(更标准) Node.js 环境
amd AMD 规范 RequireJS 等
umd 兼容 CommonJS、AMD 和全局变量 通用库,推荐使用
module ES Module 规范 支持 import 的环境

我个人强烈推荐用 umd。为什么?因为它兼容性最好。不管用户是用 importrequire 还是直接 <script> 标签引入,都能正常工作。

避坑指南:我曾经写过一个工具库,用了 libraryTarget: 'commonjs',结果用户用 ES Module 方式引入时报错了。后来改成 umd,问题解决。所以,除非你有特殊需求,否则一律用 umd

另外,如果你用的是 libraryTarget: 'module',记得把 experiments.outputModule 设为 true。这是 Webpack 5 的新特性,支持输出真正的 ES Module。

output: {
  path: path.resolve(__dirname, 'dist'),
  filename: 'my-library.mjs',
  library: {
    type: 'module'
  }
},
experiments: {
  outputModule: true
}

嗯,这块内容比较多,但核心就一句话:打包应用用 filenamepath,打包库用 librarylibraryTarget

好了,output 配置的核心内容就这些。下一章我们聊聊 modulerules,也就是 Webpack 怎么处理不同类型的文件。到时候见。