Webpack--理解“publicPath”的奥秘(翻译)
你曾多少次被webapck's publicPath
的配置绊倒?老实说,在每次开始一个新项目的时候,我总是遇到这个问题,并且花费很长时间去探索它到底是怎么工作的。我阅读了官方文档,但并没有什么用。
所以,这里我决定根据我的经验和理解去揭开publicPath
的神秘面纱。如果对你有帮助,欢迎反馈意见。
当你的publicPath配置错误时,通常会遇到这2个问题:
- webpack-dev-server:
live-reload
(浏览器自动刷新)机制没有正常执行,比如说,浏览器不会自动重新加载,或者加载的代码不是最新的。 - 生成的css文件,图片、字体路径发生报错,打断编译打包的过程。
让我们来从一个基础的配置去分析这件事。我们有一个文件结构,如下显示:
appMain [Project Folder]
|-- src
|-- index.js
|-- index.html
|-- webpack.config.js
|-- package.json
|-- dist
// 极简配置的webpack
module.exports = {
entry: './src/index.js',
output: {
filename: './dist/bundle.js'
}
};
// index.html中引入生成的bundle.js
<script src="./dist/bundle.js"></script>
如果我们只运行 webpack
命令,会默认执行webpack.config.js
, 生产打包文件到 dist 文件夹下。
此外,如果你使用了 webpack-dev-server
启动,live-reload
就开始生效了,即是,任何在js源文件 [index.js] 中的修改都会立即在浏览器中生效,不需要手动刷新。
我们可以通过console.log()
打印一些消息来测试这个功能。
注意:webpack-dev-server 启动的服务,使用的并不是真实生成的包文件,它只是监听你的源代码,当它们发生变化时去重新编译。这个修改的包是直接从内存中读取的。😎
现在让我们加入配置path
到webpack output 对象中,来代替在output.filename
中使用完整路径的配置。
const resolve = require('path').resolve;
module.exports = {
entry: './src/index.js',
output: {
// seperated path and filename of generated output
path: resolve('dist'),
filename: 'bundle.js'
}
};
请注意output.path
需要使用绝对路径而不是相对路径,否则webpack会报错。所以,我们使用了path
模块的resolve
方法。
报错信息: Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema. \- configuration.output.path: The provided value "./dist" is not an absolute path!
在这种情况下,webpack会生成错误的输出,比如有bundle.js文件,但webpack-dev-server
的live-reload
会突然停止执行。并且,关于源文件[index.js]的任何修改,无论怎么刷新浏览器都无法显示,尽管终端显示源文件已经编译成功。
现在,如果你停止你的webpack-dev-server 服务,再次执行webpack命令,然后再重新启动 webpack-dev-server,修改的自动更新又正常了,但是我们不能每次都这样做吧,真的神烦。😖
所以,是哪个环节出错了?
主要是因为当path
的未配置时,webpack-dev-config 会把它的值默认为项目的根目录,即 ./
const resolve = require('path').resolve;
module.exports = {
entry: './src/index.js',
output: {
// 如果path未配置,默认值是 './'
path: resolve('./'),
filename: './dist/bundle.js'
}
};
所以,可以正常工作。
但当我们配置为真实的路径,比如reslove('dist')
, webpack仍然在./
位置编译生成输出文件。
你可以直接在浏览器中访问服务的URL地址验证这一点
http://localhost:8080/dist/bundle.js
// 总是指向我们运行webpack命令时生成的旧代码
http://localhost:8080/bundle.js
// 总是指向最新编译后的代码,在src变化后会立即重新加载
注意:这两个打包的文件并不是完全相同的,后者因为使用dev-server会注入一些额外的代码
所以,这里live-reload
和旧代码
导致的问题是,我们使用了前者打包的 bundle.js,却在index.html
使用的是 webpack 命令生成的路径。
任何代码的改变,webpack-dev-server 会生成一个包,但地址是不同的。
解决这个问题,只需要改变一下index.html
中的script:src
属性,一切又能正常运行了。
神速!我们理解并修复了这个问题。
进一步来说,我们使用 webpack 的publicPath
去配置 webpack-dev-server 生成的打包文件的位置,是个虚拟位置
, 而不是真实的文件系统。
这个虚拟位置可以用来在 index.html 中定位引入生成的文件。
const resolve = require('path').resolve;
module.exports = {
entry: './src/index.js',
output: {
path: resolve('dist'),
filename: 'bundle.js',
// Add vitual path for generating the bundle files
publicPath: 'some-virtual-location'
}
};
// 更新script:src到新的虚拟位置(Virtual-Path)
<script src="./some-virtual-location/bundle.js"></script>
这样,我们就可以在本地开发中模拟真实服务器部署环境或者CDN。
一些开发者更喜欢命名为 assets
, public
, dist
, /
等等,这完全看你喜欢如何维护生成的文件结构和命名方式。
底线就是,index.html 中引入的script:src
的值必须与 publicPath 的配置保持一致。
publicPath对CSS的其他影响 -- 图片、字体的路径等等
在生成最终的styles时,webpack默认会把 publicPath
的配置添加到 图片的URL,字体的路径上。
示例,我添加2个文件到当前的项目中:
appMain
|-- src
|-- index.js
|-- main.css [Source CSS/SCSS file]
|-- background-image.jpg
|-- index.html
|-- webpack.config.js
|-- package.json
|-- dist
main.css (使用相对路径引入背景图片)
.main {
background-image: url("background.jpg");
}
我们使用了 css/scss 文件和图片,所以也要更新webpack.config
配置,添加相应的 loaders 或 plugin。
webpack.config.js
const resolve = require('path').resolve;
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: resolve('dist'),
filename: 'bundle.js',
publicPath: 'some-virtual-location/'
},
// 添加 CSS 和 Style Loader
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: 'css-loader'
})
},
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]'
}
}
]
}
]
},
// 生成单独的样式文件
plugins: [
new ExtractTextPlugin('styles.css')
]
};
当你运行webpack
命令时,会触发下面行为:
-
background-image.jpg
文件会被file-loader
拷贝到dist
文件夹中。 -
styles.css
会被插件 ExtractTextPlugin 根据main.css
生成到dist
文件夹中。 - 生成的css文件中
background-image: url
会被自动添加publicPath
的配置。
.main {
background-image: url(some-virtual-location/background.jpg);
}
这个规则同样适用于其他的静态资源,比如fonts.
所以正确的配置publicPath
非常重要,Webpack 的 Loaders 和 Plugins 会使用这个配置。
希望这篇文章能让你深入的理解神秘的publicPath
是怎么工作的,怎样正确的使用它。
欢迎在评论区写下你的想法和意见,我们可以进一步的讨论。✌🏻✌🏻