「这是我参与11月更文挑战的第16天,活动详情查看:2021最后一次更文挑战」
1. HtmlWebpackPlugin
HtmlWebpackPlugin
插件可以用来帮助我们生成 HTML
文件。前面我们提到了项目中主要存在的第二个不太好的地方:
- 我们的
HTML
文件是编写在根目录下的,而最终打包输出的文件夹中是没有index.html
文件的; - 但在进行项目部署时,必然是需要有对应的入口文件
index.html
的; - 所以我们也需要对
index.html
进行打包;
那么对 HTML
进行打包处理,我们就可以使用 HtmlWebpackPlugin
这个插件了。
我们先来安装它:
npm install html-webpack-plugin -D
事实上,有了这个插件之后,我们的项目目录下就可以删掉 index.html
文件了,因为在这个插件里面有一个 ejs
的 HTML
模板,到时候会根据这个模板自动生成 HTML
文件,然后打包到输出的文件夹下。
下面,我们来修改 webpack
的配置文件:
...
// 与 loader 不一样,插件必须手动导入
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
// 导入 html-webpack-plugin 插件时不用做解构,因为它导出的就是一个类
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
...
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin()
]
}
然后我们运行 npm run build
命令进行打包,效果如下:
你会发现,输出目录下自动生成了一个 index.html
文件,它是如何自动生成的呢?
-
默认情况下是根据
ejs
的一个模板来生成的; -
在
html-webpack-plugin
的源码中,有一个default_index.ejs
模块:
如果你打开这个 index.html
文件,还会发现里面的代码删除了无用的空格(这意味着文件的大小会小一点点,也算是一个小小的优化),但为了便于阅读,我们可以格式化一下代码,然后你会看到它已经通过 <script>
元素帮我们引入了 bundle.js
文件。这就意味着,这个 index.html
文件就已经是我们项目的入口文件了,浏览器中打开它,也能正常显示对应的效果:
现在,入口文件的自动生成没问题了,但还有一个地方希望可以改进:输出文件夹下的 bundle.js
文件能不能放到 js
文件夹下呢?当然可以,我们可以对 output.filename
进行修改:
...
module.exports = {
...
output: {
...
filename: 'js/bundle.js' // 打包到 js 目录下
}
}
再来打包看下效果:
bundle.js
文件已经放在了 js
文件夹下。
但在真实开发中,我们一般会使用自己的 HTML
模板来生成入口文件,而不是使用 HtmlWebpackPlugin
插件的默认模板。
因为我们可能想在自己的模板中加入一些特别的内容:
- 比如添加一个
<noscript>
元素,在用户的JavaScript
被关闭时,给予相应的提示; - 比如在开发
Vue
或React
项目时,需要有一个可以挂载后续组件的根元素:<div id="app"></div>
;
那么我们就需要一个属于自己的 index.html
模板。我们可以先来看下 Vue CLI
创建的项目打包后的结果:
你会看到,Vue CLI
项目打包输出的 dist
文件夹下也会有一个 index.html
文件,如果你打开它,格式化代码后就会发现里面除了打包出来的东西有点多(因为做了分包处理,我们后面会说),还存在一些我们当前项目打包生成的 index.html
文件中没有的内容,这是为什么呢?原因就是 Vue CLI
创建的项目在使用 HtmlWebpackPlugin
插件进行打包时,指定了自己的模板(./public/index.html
),而不是使用默认的模板生成 HTML
文件。那我们可以复制一下 Vue CLI
项目中的模板内容(./public/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>
然后来到我们的项目目录下,也新建一个 public
文件夹,在 public
文件夹下再新建 index.html
文件,在里面粘贴刚才复制的内容。现在,我们的项目中也有了自己的 HTML
模板,下面,该如何利用这个模板来进行打包呢?我们来修改 webpack
的配置:
...
module.exports = {
...
plugins: [
...
new HtmlWebpackPlugin({
template: './public/index.html' // 指定要使用的模板所在的路径
})
]
}
修改完后我们打包试试:
可以看到,编译过程报错了,提示我们 BASE_URL
没有定义,报错位置是在 ./public/index.html
文件中,那我们打开这个模板文件(其实是一个 ejs
模板),可以看到在第 7
行有用 BASE_URL
进行内容填充:
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
这里使用了一种 EJS
模块填充数据的语法(<%= 变量 %>
),在使用 BASE_URL
前需要先进行定义,但当前并没有在任何地方定义过它,所以才导致报错了。如果把这行代码删除掉再来打包,就不会报错了。但删除掉它肯定不是万全之策,如果我们就是想要填充一个 BASE_URL
的值,就需要另外一个插件来帮忙了,这个插件就是 webpack
内置的(意味着不需要再单独安装)一个插件:DefinePlugin
。这个插件可以帮助我们在编译时定义变量的值,到时候就会使用这个值替换掉代码中的变量。
我们来配置 DefinePlugin
插件:
...
// 通过解构的方式拿到 webpack 内置的 DefinePlugin 插件,你也可以通过 require('webpack').DefinePlugin 直接获取到该插件
const { DefinePlugin } = require('webpack');
module.exports = {
...
plugins: [
...
new DefinePlugin({
BASE_URL: "'./'" // 注意,这里的值如果是字符串,需要再加一层引号包裹起来(双引号里面给单引号/单引号里面给双引号),因为 DefinePlugin 会把这里字符串的值当成另外一个变量去上下文中寻找其值
})
]
}
配置完后再来进行打包,效果如下:
这次打包就没有再报错了,也就是说成功对 template
进行了编译,能读取到 BASE_URL
的值了,并且可以看到,原来模板中的 <%= BASE_URL %>
在用来生成我们最终的 index.html
时就被替换成了 ./
。
此外,入口文件中的 title
其实也是可以配置的,而对于 title
的配置则不需要像前面使用 BASE_URL
时还需要使用 DefinePlugin
插件了,因为你会发现在 ./public/index.html
模板中,存在这样一行代码:
<title><%= htmlWebpackPlugin.options.title %></title>
也就是说,title
的内容是通过上下文中的 htmlWebpackPlugin
变量的 options
的 title
获取的,即我们配置 plugins
时 new
出来的 HtmlWebpackPlugin
的对象中的 options
对象的 title
属性的值了:
...
const HtmlWebpackPlugin = require('html-webpack-plugin');
...
module.exports = {
...
plugins: [
...
new HtmlWebpackPlugin({
template: './public/index.html',
title: '我是标题呀~' // 在进行 htmlWebpackPlugin.options.title 读取时,就会读到此项数据
}),
...
]
}
再来 npm run build
打包,效果如下:
可以看到,<title>
的内容就是我们设置的值了。
当然,HtmlWebpackPlugin
插件还有很多其它的参数可以配置,我们可以查阅它 GitHub
上的文档:github.com/jantimon/ht… 。