Vue 项目之 Webpack 的 Plugins(2)

660 阅读6分钟

「这是我参与11月更文挑战的第16天,活动详情查看:2021最后一次更文挑战

1. HtmlWebpackPlugin

HtmlWebpackPlugin 插件可以用来帮助我们生成 HTML 文件。前面我们提到了项目中主要存在的第二个不太好的地方:

  • 我们的 HTML 文件是编写在根目录下的,而最终打包输出的文件夹中是没有 index.html 文件的;
  • 但在进行项目部署时,必然是需要有对应的入口文件 index.html 的;
  • 所以我们也需要index.html 进行打包

那么对 HTML 进行打包处理,我们就可以使用 HtmlWebpackPlugin 这个插件了。

我们先来安装它:

npm install html-webpack-plugin -D

事实上,有了这个插件之后,我们的项目目录下就可以删掉 index.html 文件了,因为在这个插件里面有一个 ejsHTML 模板,到时候会根据这个模板自动生成 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 命令进行打包,效果如下:

image-20211114133109943

你会发现,输出目录下自动生成了一个 index.html 文件,它是如何自动生成的呢?

  • 默认情况下是根据 ejs 的一个模板来生成的;

  • html-webpack-plugin 的源码中,有一个 default_index.ejs 模块:

    image-20211114154330234

如果你打开这个 index.html 文件,还会发现里面的代码删除了无用的空格(这意味着文件的大小会小一点点,也算是一个小小的优化),但为了便于阅读,我们可以格式化一下代码,然后你会看到它已经通过 <script> 元素帮我们引入了 bundle.js 文件。这就意味着,这个 index.html 文件就已经是我们项目的入口文件了,浏览器中打开它,也能正常显示对应的效果:

image-20211114134540422.png

现在,入口文件的自动生成没问题了,但还有一个地方希望可以改进:输出文件夹下的 bundle.js 文件能不能放到 js 文件夹下呢?当然可以,我们可以对 output.filename 进行修改:

...

module.exports = {
  ...
  output: {
    ...
    filename: 'js/bundle.js' // 打包到 js 目录下
  }
}

再来打包看下效果:

image-20211114135239614

bundle.js 文件已经放在了 js 文件夹下。

但在真实开发中,我们一般会使用自己的 HTML 模板来生成入口文件,而不是使用 HtmlWebpackPlugin 插件的默认模板。

因为我们可能想在自己的模板中加入一些特别的内容:

  • 比如添加一个 <noscript> 元素,在用户的 JavaScript 被关闭时,给予相应的提示;
  • 比如在开发 VueReact 项目时,需要有一个可以挂载后续组件的根元素:<div id="app"></div>

那么我们就需要一个属于自己的 index.html 模板。我们可以先来看下 Vue CLI 创建的项目打包后的结果:

image-20211114140849034

你会看到,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' // 指定要使用的模板所在的路径
    })
  ]
}

修改完后我们打包试试:

image-20211114143141190

可以看到,编译过程报错了,提示我们 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 会把这里字符串的值当成另外一个变量去上下文中寻找其值
    })
  ]
}

配置完后再来进行打包,效果如下:

image-20211114151503831

这次打包就没有再报错了,也就是说成功对 template 进行了编译,能读取到 BASE_URL 的值了,并且可以看到,原来模板中的 <%= BASE_URL %> 在用来生成我们最终的 index.html 时就被替换成了 ./

此外,入口文件中的 title 其实也是可以配置的,而对于 title 的配置则不需要像前面使用 BASE_URL 时还需要使用 DefinePlugin 插件了,因为你会发现在 ./public/index.html 模板中,存在这样一行代码:

<title><%= htmlWebpackPlugin.options.title %></title>

也就是说,title 的内容是通过上下文中的 htmlWebpackPlugin 变量的 optionstitle 获取的,即我们配置 pluginsnew 出来的 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 打包,效果如下:

image-20211114153519681

可以看到,<title> 的内容就是我们设置的值了。

当然,HtmlWebpackPlugin 插件还有很多其它的参数可以配置,我们可以查阅它 GitHub 上的文档:github.com/jantimon/ht…