webpack中的publicPath

webpack的常用基本配置我们可能已经耳熟能详,比如 input,output,module,plugins,devServer的配置等等。而在这些基本配置中,其实还有一些细节参数,它可以帮助我们更好的定制化打包的目录结构,但它可能并不是那么好理解,比如publicPath。

webpack官网这么解释:
publicPath配置项在很多场景下都非常有用,它允许你给你的应用中的所有静态资源指定一个基本路径。

The publicPath configuration option can be quite useful in a variety of scenarios. It allows you to specify the base path for all the assets within your application.

有些抽象,我总结了下,它大概做的事情就是:可以帮我们处理资源引用的url路径问题:为生成的资源自动添加特定路径前缀。

什么时候需要使用publicPath?

1. 打包出来的文件有特定目录结构划分时

webpack打包出来的文件,默认都统一放在output配置的path路径下,项目稍大一点,这个目录中的文件就比较杂乱了,我们可能会希望给这些文件进行归类。当然我们可以粗暴一点通过filename来指定一个子目录。但是,如果在这这个子目录中,文件还有层级,就需要配置相应 plugin 或者 loader 的 publicPath 了。

例如file-loader,我们可以配置它的outputPath 自定义生成文件存放在output.path的哪个子目录,并且配置它的 publicPath 指定资源路径前缀:
file-loaderpublicPath的值可以是stringfunction

1
2
3
4
5
6
7
8
9
10
11
12
module: {
rules: [
{
test: /\.(png|jpe?g|gif)$/i,
loader: 'file-loader',
options: {
outputPath: 'media', // string
publicPath: 'media', // string
}
}
]
}

这样,编译打包后的图片资源就会放在dist/media目录下(假设你设置的output path为dist),并且所有引用到图片的资源路径都会自动加上前缀 media/

如果想对不同的图片添加不同的路径前缀,可以使用函数来定义publicPath:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
module: {
rules: [
{
test: /\.(png|jpe?g|gif)$/i,
loader: 'file-loader',
options: {
publicPath: (url, resourcePath, context) => {
// `resourcePath` 是这个资源的本地绝对路径
// `context` 是存放这个资源的目录,或者是`context`配置项的值
// 想获取相对路径可以这样:
// const relativePath = path.relative(context, resourcePath);

// 将符合下面条件的png图片url添加前缀 `other_public_path`
if (/my-custom-image\.png/.test(resourcePath)) {
return `other_public_path/${url}`;
}
// 将符合下面条件的图片url添加前缀 `image_output_path`
if (/images/.test(context)) {
return `image_output_path/${url}`;
}
// 其他图片url添加前缀 `public_path`
return `public_path/${url}`;
}
}
}
]
}

这样编译打包出来的图片url,就会根据你的设置,分别加上 other_public_pathimage_output_pathpublic_path前缀了(当然这几个目录的名称你自己来定),是不是很不错?

2. 生产模式要求index首页不在根目录下

例如在某些生产模式下,要求产出的文件目录类似这样:

1
2
3
4
5
|--assets/
| |--index.js
| |--vendor.js
|--page/
|--index.html

那么可以这么配置webpack:

1
2
3
4
5
6
7
8
9
10
11
12
// webpack.prod.js file
output: {
filename: 'assets/[name].js',
path: resolve(__dirname, '../', 'dist'),
publicPath: '../' // 相对HTML页面的路径
},
plugins: [
new HtmlWebpackPlugin({
template: '../public/index.html',
filename: 'pages/index.html'
})
],

编译后,在index.htmlindex.js的引用就会变成这样:

1
<script src=../assets/index.js></script>

outputpublicPath的值可以是以下几种:

1
2
3
4
5
6
7
8
9
10
11
12
module.exports = {
//...
output: {
// 以下几种之一
publicPath: 'https://cdn.example.com/assets/', // CDN (一定是HTTPS)
publicPath: '//cdn.example.com/assets/', // CDN (HTTPS协议)
publicPath: '/assets/', // 相对服务端跟目录
publicPath: 'assets/', // 相对 HTML 页面文件
publicPath: '../assets/', // 相对 HTML 页面文件
publicPath: '', // 相对 HTML 页面文件 (与HTML同一目录)
}
};

3. 生产模式下的静态资源在CDN上托管时

例如在某些生产模式下,静态文件都由www.xx.com/assets来托管
那么可以在 webpack 中这么配置 publicPath:

1
2
3
4
5
6
// webpack.prod.js file
output: {
filename: 'assets/[name].js',
path: resolve(__dirname, '../', 'dist'),
publicPath: 'https://www.xx.com/assets' // CDN URL
},

编译后,在 index.html 中 index.js 的引用就会变成这样:

1
<script src=https://www.xx.com/assets/index.js></script>

其他

devServer中也有publicPath配置,默认它是获取outputpublicPath的值。
要提一下,webpack-dev-server生成的文件是不会放在硬盘的,而是在内存中,所以看不到。只有在请求资源的时候,可以证明文件的存在。。。

1
2
3
devServer {
publicPath: '/assets/'
}

一定要在string的前后都放上/

需要注意的是,devServer中还有一个叫做contentBase的参数,这个参数如果配置的不好,跟publicPath一搭配,很可能会导致请求不到页面(我也是被这个坑了很久)。这个地方如果出问题,基本上原因在于contentBase设置的路径范围太小了,去掉contentBase配置,或者给它配置多个路径,把输出目录包含进来,就可以解决问题。

1
2
3
4
devServer {
contentBase: [path.resolve(__dirname, '../assets'), path.resolve(__dirname, '../dist')], // contentBase可以放多个路径
publicPath: '/assets/'
}


GOOD LUCK!

参考:
https://webpack.js.org/guides/public-path/
https://www.npmjs.com/package/file-loader