Skip to content
On this page

彩蛋篇-Webpack基础介绍与两大利器


Webpack 概念

本小节主要介绍 Webpack 相关知识,聊聊 Webpack 的由来以为我们为什么要使用 Webpack,通过两大利器:Loader 与 Plugins 进行讲解,整篇内容相对较长,请耐住性子阅读。

如果对 Webpack 熟悉的同学,本章可以快速阅读或跳过,如果对 Webpack 不熟悉的同学,希望本章节可以帮你快速熟悉 Webpack 开发

要想快速知道 Webpack 是什么,最好的方式就是通过官网去了解它。通过官方介绍,我们可以知道:webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。

image.png

在最初,Webpack 并不被人熟知,它刚出现时,主打的优势是 Code Splitting,我们现在从官网也能看到对它的定义:

Code Splitting : 代码分离指将代码分成不同的包/块,然后可以按需加载,而不是加载包含所有内容的单个包。

什么时候 Webpack 才受人关注?2014 年,Instagram 的前端团队在一次大会上分享其内部前端页面加载性能优化,提到最重要的一点就是用到了 Webpack 的 Code Splitting

这简直就是为 Webpack 好友助力了一波,之后形成了一个热潮。Webpack 的风口来了,很多公司纷纷使用 Webpack,并贡献了无数的 Plugin、Loader,你一刀,我一刀,明天 Webpack 就出道,果不其然,短短时间内,Webpack 被推上了高潮。

大家都用,我需要用吗?如果说你的应用程序非常小,没有什么静态资源,只需要一个 JS 文件就可以满足需求,这时使用 Webpack 并不是一个好的选择。至于你用与不用,得靠你自身评估~

两大利器

得益于 Webpack 扩展性强,插件机制完善,官方提供了许多的 Loader、Plugin,接下来通过问题,配合简单明了的 demo,给大家讲解这两大利器,在此之前,我们先全局安装一下 Webpack。

bash
npm install webpack@4.44.1 --save --dev
npm install webpack-cli@3.3.12 --save --dev

Loader 模块打包方案

官方对 Loader 的介绍是:Webpack 可以使用 Loader 来预处理文件。这允许你打包除 JS 之外的任何静态资源。

在我看来,Loader 就是一种模块打包方案,怎么理解?给大家科普一个知识点:Webpack 默认是知道如何打包 js 类型文件,但对于其他类型文件,它是不知道如何处理,我们得告诉它,对这种类型文件,打包的方案是什么。接下来,我们通过例子,帮助小伙伴们理解为什么我说它是一种方案。

我们新建一个 demo 文件夹,创建一个 index.js 文件,文件结构是这样的

├── demo
│ └── index.js
└──...

此时我们在 index.js 中写下这行代码

javascript
// index.js
const myName = '我叫彭道宽';
console.log(myName);

执行一下 npx webpack index.js,意思就是对我们的 index.js 文件打包。

我们在终端可以看到,在不配置任何东西情况下,Webpack 也能够打包 JS 类型文件,这说明 Webpack 默认对 JS 文件是有一套打包方案的

接下来,我们将代码改成这样,引入我们的图片

javascript
// index.js
import myPdkAvatar from './avatar.jpg';

const myName = '我叫彭道宽';
console.log(myName);

同上,执行 npx webpack index.js,此时会报错。对 jpg 类型的文件打包失败了

Webpack 很友好,它会告诉你,你需要一个 loader 去处理此文件类型。

官方提供了一种专门处理此类型的方案:file-loader,我们安装一下这个 loader

bash
npm install --save-dev file-loader

接着新增一个文件 webpack.config.js,此时的文件结构是这样的

├── demo
│ ├── index.js
│ └── webpack.config.js
└──...

我们在配置文件中,添加一下对于 jpg 这种类型文件的处理方案

javascript
// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.jpg$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: '[name]_[hash].[ext]',
              outputPath: 'images/',
            },
          },
        ],
      },
    ],
  },
};

解读一下这段代码,意思就是:当遇到模块(module)时,进行规则(rules)匹配,如果匹配到 /\.jpg$/ 类型的文件,就采用 file-loader 方案进行打包,并且配置了参数:nameoutputPath,意味着打包后的文件名是按照 [文件名]_[哈希值].[源类型] 规则命名,并且输出在 images/ 目录下

理解了这段代码含义之后,我们再来打包,看看结果如何,执行 npx webpack index.js

image.png

打包正常!我们再看看打包之后的 dist 文件下,是不是真的有个 images/ 目录存放着打包后的图片?

如我们所想,现在回过头细品,Loader 就是一种模块打包方案是不是也有点道理?下面写几行代码,大家细品细品

javascript
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader', 'postcss-loader'],
      },
      {
        test: /\.less$/,
        use: {
          loader: 'less-loader',
        },
      },
      {
        test: /\.vue$/,
        use: {
          loader: 'vue-loader',
        },
      },
    ],
  },
};

Plugins 打包更加便捷

继续以上边的 Loader demo 为例子,回顾一下我们现在 demo 的文件目录结构

├── demo
│ ├── index.js
│ └── webpack.config.js
└──

我们先来执行一下 npx webpack index.js,来看看 dist 目录下有哪些文件

image.png

通过官网可知,在我们未配置 output 属性时,它的默认值是 ./dist/main.js,其他生成文件默认放置在 ./dist 文件夹中。

因为我们都用的默认配置,所以打包生成的文件夹名就叫 dist,bundle 默认名称就是 main.js

接下来我们手动创建一个 HTML,加载打包后的 js 文件,如何加载呢?通过 script 加载打包后的 main.js

html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>webpack plugins demo</title>
  </head>
  <body>
    <div id="root"></div>

    <!-- 在这里加载打包好之后的 main.js 文件 -->
    <script src="./dist/main.js"></script>
  </body>
</html>

然后运行此页面,通过控制台,可以看到会打印出:我叫彭道宽

假设现在有一种场景,需要通过 hash 进行命名输出的 bundle。我们来修改一下 webpack.config.js

javascript
module.exports = {
  // 1. webpack 执行构建的第一步将从 entry 开始,这里我们的入口文件为 index.js
  entry: './index.js',

  // 2. 经过一系列处理得到最终的代码,然后输出结果
  output: {
    // 这里将输出的结果代码文件自定义配置文件名
    filename: '[彭道宽]_[hash].bundle.js',
  },
  // ...
};

执行 npx webpack index.js,来看看打包之后的文件命名格式是否如我们预期

image.png

没毛病,这时候我们 HTML 加载该怎么办?手动修改成正确的文件地址

html
<script src="./dist/[彭道宽]_657b45ee79dee39108f7.bundle.js"></script>

如果我们将 index.js 文件中的内容修改(👇 下面添加一行代码)

javascript
// index.js
import myPdkAvatar from './avatar.jpg';

const myName = '我叫彭道宽';
console.log(myName);

// 👇 添加一行新代码
console.log('new add code ......');

然后把 dist 目录删除,再打包一次,看看文件 hash 是否一致?

image.png

通过对比,我们发现,每次修改,重新打包生成的 bundle 文件名哈希值都不一样。等价于每次打包都需要手动修改 HTML 中的文件引用

太原始太麻烦了,低效率!为此,Webpack 提供了 Plugins 插件能力,让 Webpack 变得更加灵活。

官方提供了很多 Plugins,让我们的打包更加便捷,上面的问题,我们可以通过 HtmlWebpackPlugin 插件进行简化 HTML 文件的创建,这对于在文件名中包含每次会随着编译而发生变化哈希的 webpack bundle 尤其有用!

多说无益,上手试试,先根据文档,安装一下插件,看看它能实现怎样的效果

bash
npm install --save-dev html-webpack-plugin

安装好之后,我们来修改 webpack.config.js 内容,将这个插件引入

javascript
// 👇 引入此插件
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './index.js',
  output: {
    filename: '[彭道宽]_[hash].bundle.js',
  },
  // 👇 使用此插件
  plugins: [new HtmlWebpackPlugin()],
};

执行一下 npx webpack index.js,打包的出来的文件有哪些?

image.png

image.png

HtmlWebpackPlugin 会在打包结束后,自动帮我们生成一个 HTML 文件,同时把打包后的 bundle 自动引入。当我们内容修改,重新打包,生成的 HTML 也会随着每次编译导致哈希变化的 bundle 自动引入。

是不是很完美呢?不,我们采用火眼金睛瞧一瞧由 HtmlWebpackPlugin 生成的 HTML 文件,你会发现好像有些问题?是不是 body 下少了一些 DOM 节点(比如 Vue、React 都会有一个 id 为 app 的 DOM 元素),怎么办?这是该插件默认生成的,有没有办法生成我想要的 DOM 结构呢?

HtmlWebpackPlugin 提供了一个配置参数 template,它允许你自定义 HTML 文件,以此文件为模版,生成一份一样的 HTML 并为你自动引入打包后的 bundle。

我们来动手实现一下,首先定义一份“别具一格”的 HTML 模版。

html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>我是 HtmlWebpackPlugin 的模版</title>
    <style>
      * {
        margin: 0;
      }
    </style>
  </head>
  <body>
    <div id="root">
      <div id="pdk">PDK Demo</div>
    </div>
  </body>
</html>

然后通过修改 webpack.config.js 配置,采用此模版为基础

javascript
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './index.js',
  output: {
    filename: '[彭道宽]_[hash].bundle.js',
  },
  plugins: [
    // 👇 以我们写的 html 为模版生成
    new HtmlWebpackPlugin({
      template: './index.html',
    }),
  ],
};

最后我们来瞧瞧,是否打包后自动生成的 HTML 文件结构跟我们的模版一致?

image.png

事实证明,确实一模一样。

官方还有很多精巧有用的 Plugins 插件,几乎每个插件目的都是出于让你的打包构建更加便捷。小伙伴们要善于使用搜索引擎去寻找所需的插件工具(官方插件或第三方 Plugins 插件)及解决问题的方法。

总结

  • Loader 就是一种模块打包方案,换言之,它是一名具备文件类型转换的翻译员
  • Plugins 用于扩展 Webpack 的功能,使得 webpack 变得极其灵活。
  • Plugins 可以在 Webpack 运行到某个时刻,帮你做一些事情。学过 Vue、React 的小伙伴应该对生命周期不陌生,其实 Plugins 很像生命周期函数,在 Webpack 运行到某个生命周期去做些事情。

如上述例子中,HtmlWebpackPlugin 就是在 Webpack 打包过程结束的生命周期时刻,去做了一些事情——自动生成 HTML 文件,引入打包后的 bundle。

在比如 clean-webpack-plugin 第三方的插件,它其实就是在 Webpack 打包之前的生命周期时刻,去做了一些事情——删除我们打包的目录

这两个 Plugins 相信你们的项目中都会用到,回去翻一翻项目的配置,结合文档,在细品细品。

建议

官方提供了很多 Loader、Plugins ,小伙伴们如果在遇到对于某种类型文件打包有问题时,直接百度找资料,看文档,95%的问题都能被解决。

再三思考下,还是没有讲解 Loader 的工作原理,以基础介绍为重点,生怕一上来就讲原理吓倒一批同学,如果有小伙伴对其原理感兴趣,阿宽可以再出一小彩蛋章节介绍。