3.3 配置资源出口
接着我们来看资源出口相关的配置,所有与出口相关的配置都集中在output对象里。请看下面的例子:
const path = require('path'); module.exports = { entry: './src/app.js', output: { filename: 'bundle.js', path: path.join(__dirname, 'assets'), publicPath: '/dist/', }, };
output对象里可以包含数十个配置项,不过大多数在日常开发中的使用频率并不高,我们这里只介绍几个常用的配置项,基本可以覆盖大多数场景。
3.3.1 filename
顾名思义,filename的作用是控制输出资源的文件名,其形式为字符串,如:
module.exports = { entry: './src/app.js', output: { filename: 'bundle.js', }, };
使用上面的配置打包的结果如图3-5所示。
图3-5 生成bundle.js
filename可以不仅仅是bundle的名字,还可以是一个相对路径,即便路径中的目录不存在也没关系,因为Webpack会在输出资源时创建该目录。请看下面的例子:
module.exports = { entry: './src/app.js', output: { filename: './js/main.js', }, };
打包结果如图3-6所示。
图3-6 生成./js目录
在多入口的场景中,我们需要为对应产生的每个bundle指定不同的名字。Webpack支持使用一种类似模板语言的形式动态地生成文件名,如:
module.exports = { entry: { app: './src/app.js', vendor: './src/vendor.js', }, output: { filename: '[name].js', }, };
在资源输出时,上面配置的filename中的[name]会被替换为chunk name,因此最后项目中实际生成的资源是vendor.js与app.js,如图3-7所示。
图3-7 通过chunk name动态指定资源名
除了[name]可以指代chunk name以外,还有其他几种模板变量可以用于filename的配置中,如表3-1所示。
表3-1 filename配置项模板变量
上述变量一般有如下两种作用。
·当有多个chunk存在时对不同的chunk进行区分。如[name]、[chunkhash]和[id],它们对于每个chunk来说都是不同的。
·实现客户端缓存。表中的[contenthash]和[chunkhash]都与chunk内容直接相关,在filename中使用了这些变量后,当chunk的内容改变时,资源文件名也会随之更改,从而使用户在下一次请求资源文件时会立即下载新的版本,而不会使用本地缓存。
在实际工程中,我们使用比较多的是[name],它与chunk是一一对应的关系,并且可读性较高。如果要控制客户端缓存,最好还要加上[chunkhash],因为每个chunk所产生的[chunkhash]只与自身内容有关,单个chunk内容的改变不会影响其他资源,可以最精确地让客户端缓存得到更新。
让我们看以下的例子:
module.exports = { entry: { app: './src/app.js', vendor: './src/vendor.js', }, output: { filename: '[name]@[chunkhash].js', }, };
打包结果如图3-8所示。
图3-8 使用了[name]和[chunkhash]的filename配置
注意
更新缓存一般只用在生产环境的配置下,在开发环境中可以不必配置[chunkhash],详见第7章介绍分离配置文件的部分。
3.3.2 path
path可以指定资源输出的位置,要求值必须为绝对路径。如:
const path = require('path'); module.exports = { entry: './src/app.js', output: { filename: 'bundle.js', path: path.join(__dirname, 'dist') , }, };
上述配置将资源输出位置设置为工程的dist目录。在Webpack 4以前的版本中,打包资源默认会存储在工程根目录,因此我们需要上述配置;而在Webpack 4之后,output.path已经默认为dist目录,除非我们需要更改它,否则不必单独配置。
3.3.3 publicPath
publicPath是一个非常重要的配置项,并且容易与path混淆。从功能上来说,path用来指定资源的输出位置,publicPath则用来指定资源的请求位置。下面详细解释它们之间的区别。
·输出位置:打包完成后资源产生的目录,一般将其指定为工程中的dist目录。
·请求位置:由JS或CSS所请求的间接资源路径。页面中的资源分为两种,一种是由HTML页面直接请求的,比如通过script标签加载的JS;另一种是由JS或CSS来发起请求的间接资源,如图片、字体等(也包括异步加载的JS)。publicPath的作用就是指定这部分间接资源的请求位置。
publicPath有3种形式,下面我们逐一进行介绍。
1. HTML相关
HTML相关,也就是说我们可以将publicPath指定为HTML的相对路径,在请求这些资源时,以当前页面HTML所在路径加上相对路径,构成实际请求的URL。如:
// 假设当前HTML地址为 https://example.com/app/index.html // 异步加载的资源名为 0.chunk.js publicPath: "" // 实际路径https://example.com/app/0.chunk.js publicPath: "./js" // 实际路径https://example.com/app/js/0.chunk.js publicPath: "../assets/" // 实际路径https://example.com/aseets/0.chunk.js
注意
这里不需要过多关注如何异步加载JS,后面章节会详细介绍。
2. Host相关
若publicPath的值以“/”开始,则代表此时publicPath是以当前页面的host name为基础路径的。如:
// 假设当前HTML地址为https://example.com/app/index.html // 异步加载的资源名为0.chunk.js publicPath: "/" // 实际路径https://example.com/0.chunk.js publicPath: "/js/" // 实际路径https://example.com/js/0.chunk.js publicPath: "/dist/" // 实际路径https://example.com/dist/0.chunk.js
3. CDN相关
上面两种配置都是相对路径,我们也可以使用绝对路径的形式配置publicPath。这种情况一般在静态资源放在CDN上面,由于其域名与当前页面域名不一致,需要以绝对路径的形式进行指定时发生。当publicPath以协议头或相对协议的形式开始时,代表当前路径是CDN相关。如:
// 假设当前页面路径为 https://example.com/app/index.html // 异步加载的资源名为 0.chunk.js publicPath: "http://cdn.com/" // 实际路径http://cdn.com/0.chunk.js publicPath: "https://cdn.com/" // 实际路径https://cdn.com/0.chunk.js publicPath: "//cdn.com/assets/" 实际路径 //cdn.com/assets/0.chunk.js
webpack-dev-server的配置中也有一个publicPath,但是这个publicPath与Webpack中的配置项含义不同,它的作用是指定webpack-dev-server的静态资源服务路径。请看下面的例子:
const path = require('path'); module.exports = { entry: './src/app.js', output: { filename: 'bundle.js', path: path.join(__dirname, 'dist'), }, devServer: { publicPath: '/assets/', port: 3000, }, };
从上面可以看到,Webpack配置中的output.path为dist目录,因此bundle.js应该存储在dist目录。但是当我们启动webpack-dev-server的服务后,访问localhost:3000/dist/bundle.js时却会得到404错误。这是因为devServer.publicPath配置项将资源位置指向了localhost:3000/assets/,因此只有访问localhost:3000/assets/bundle.js才能得到我们想要的结果。
为了避免开发环境和生产环境产生不一致而造成开发者的疑惑,我们可以将webpack-dev-server的publicPath与Webpack中的output.path保持一致,这样在任何环境下资源输出的目录都是相同的。请看下面的例子:
const path = require('path'); module.exports = { entry: './src/app.js', output: { filename: 'bundle.js', path: path.join(__dirname, 'dist') , }, devServer: { publicPath: '/dist/', port: 3000, }, };
上面的配置可以保证我们访问localhost:3000/dist/bundle.js时得到预期的结果。
3.3.4 实例
下面我们看两个在不同场景下配置资源出口的例子。
1. 单入口
对于单入口的场景来说,通常不必设置动态的output.filename,直接指定输出的文件名即可。下面是一个简单的例子:
const path = require('path'); module.exports = { entry: './src/app.js', output: { filename: 'bundle.js', }, devServer: { publicPath: '/dist/', }, };
上面是通常情况下的配置。工程的源码目录为src,资源的输出目录为dist(Webpack 4以后已经默认)。此时我们还不需要配置output.publicPath,但是需要为webpack-dev-server指定资源的服务路径,因此我们设置了devServer.publicPath为/dist/。
2. 多入口
对于多入口的场景,我们必然会需要模板变量来配置filename。请看下面的例子:
const path = require('path'); module.exports = { entry: { pageA: './src/pageA.js', pageB: './src/pageB.js', }, output: { filename: '[name].js', }, devServer: { publicPath: '/dist/', }, };
我们通过output.filename中的[name]变量指代chunk name,使最终生成的资源为pageA.js和pageB.js。如果是生产环境下的配置,我们还可以把[name].js改为[name]@[chunkhash].js。