这几天一直在研究 Webpack
这些打包工具链,在研究项目启动的过程中,初步认识到平时开发时是通过 Webpack-dev-server
调用 Webpack
的打包能力结合静态资源服务器能力开发的
大概是下面这个图片
但是由于 webpack-dev-server
为了更快,它的打包文件都是放在缓存里的,对于我来说其实可以说是黑盒操作
首先来到我的第一个疑问
public 目录是什么?
通常基于 Webpack
的项目都会有一个 public
文件夹
比如 Vue CLI@4.5.15
创建的项目
├── node_modules
├── public
| ├── favicon.ico
| └── index.html
├── src
├── babel.config.js
├── package.json
├── package-lock.json
└── README.md
比如 creat react app@5.0.1
├── node_modules
├── public
| ├── favicon.ico
| ├── index.html
| ├── logo192.png
| ├── logo512.png
| ├── manifest.json
| └── robots.txt
├── src
├── .gitignore
├── babel.config.js
├── package.json
├── package-lock.json
└── README.md
共同点是都有 public/index.html
和 public/favicon.ico
,如果你尝试修改 index.html
还可以在浏览器中看到相应的修改
首先解密 public
目录是干什么的?
静态资源目录
public
其实是作为 Webpack
的静态资源目录,通过 http
请求的方式访问 public
目录下的文件内容,默认 http://localhost:port/
会返回 public
的 index.html
那么可不可以返回其它的文件呢?
当然可以,但是需要改动的地方会比较长,webpack-dev-server
提供的静态资源是依靠一下库的赋能
webpack-dev-server <- express <- serve-static
回到正题,Webpack
作为前端工具链重要的一环,有一个很重要的作用就是热更新,并将热更新修改内容反映在我们的浏览器中,这也就是我们需要 public/index.html
,为什么需要静态资源服务器
那么来到我的第二个疑问
既然 public/index.html
是热更新的入口,那么为什么没有看到脚手架创建的 publc/index.html
中存在和打包文件相关的部分呢?
我之前写过一篇文章基于 Webpack 从 0 到 1 启动一个 Vue 项目 - 掘金
里面纯手工运行了一个 Vue
项目(以单文件组件(SFC/.vue
)的方式)
但里面的 index.html
需要依赖打包文件
<!-- index.html -->
<body>
<div id="app"></div>
</body>
<script type="text/javascript" src="bundle.js" charset="utf-8"></script>
// webpack.config.js
// ...
module.exports = {
mode: "development",
devServer: {
static: {
directory: path.join(__dirname, "public"),
},
port: 8080,
},
// ...
entry: path.join(__dirname, "./main.js"),
output: {
publicPath: "",
filename: "bundle.js",
},
// ...
};
但脚手架的默认 index.html
是没有任何的 js
引入的,以 Vue CLI
为例
<!-- index.html -->
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
但你先别急,脚手架项目运行之后的 index.html
可不是上面这样的,实际如下
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="/favicon.ico">
<title>vue-cli-create-demo</title>
<link href="/js/app.js" rel="preload" as="script"><link href="/js/chunk-vendors.js" rel="preload" as="script"></head>
<body>
<noscript>
<strong>We're sorry but vue-cli-create-demo doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
<script type="text/javascript" src="/js/chunk-vendors.js"></script><script type="text/javascript" src="/js/app.js"></script></body>
</html>
对比上下两个 index.html
的内容,多出了 .js
的引入以及 <%= htmlWebpackPlugin.options.title %>
被覆盖
所以脚手架它们到底是怎么做到呢?
html-webpack-plugin
回到上面的两个 index.html
里面有个 htmlWebpackPlugin
很关键,其实答案就在它身上,文档上关于 html-webpack-plugin
的介绍是这样的
HtmlWebpackPlugin
简化了 HTML 文件的创建,以便为你的 webpack 包提供服务。这对于那些文件名中包含哈希值,并且哈希值会随着每次编译而改变的 webpack 包特别有用。你可以让该插件为你生成一个 HTML 文件,使用
lodash 模板提供模板,或者使用你自己的
loader。
那么它和 public/index.html
有什么联系呢?我从它的配置上得到了答案
其实这个 html-webpack-plugin
干的活就是会在 webpack
打包时生成 index.html
并且放置在 webpack-dev-server
设置的静态资源服务文件夹,因此会替换掉默认的 index.html
通常用户设置的 index.html
会作为 webpack-dev-server
调用 Webpack
打包能力时通过 html-webpack-plugin
的模版,基于此生成一个 index.html
,因此像 Vue CLI
此类脚手架会在配置中设置插入打包文件的脚本引入,因此无需用户手动配置
大致流程如下图
要实现基本脚手架的这个能力可以参照下面的实现
// webpack.config.js
// ...
module.exports = {
// ...
plugins: [
// ...
new HtmlWebpackPlugin({
// 设置模板
template: "public/index.html",
// 设置 html-webpack-plugin 以 defer 模式自动引入打包文件
scriptLoading: "defer",
}),
],
};
总结
为什么需要 html-webpack-plugin
?比如生产环境的打包 js
文件其实是动态文件名,像这种情况不能够写死,因此就需要 html-webpack-plugin
去帮忙配置