这一章我们来学习 webpack。关于 webpack 就算大家没有使用过,或多或少的应该也都听说过,它是一个静态模块打包器(static module bundler)

我们先来看一下官网中对 webpack 的定义:

webpack 是一个现代 JavaScript 应用程序的静态模块打包器(static module bundler)。在 webpack 处理应用程序时,它会在内部创建一个依赖图(dependency graph),用于映射到项目需要的每个模块,然后将所有这些依赖生成到一个或多个 bundle。

光看理论可能帮助不大,大家记住上面的定义,然后我们通过一个实例来看一下 webpack 是如何来工作,以及它到底能带来了怎样的帮助。

在正式开始事例之前,需要先安装 webpack 到本地:

npm install webpack -g

通过上面的指令来安装 webpack,通过执行 webpack -v 来查看安装的版本。目前最新的 webpack 版本为 4.17.2。在执行 webpack -v 时应该能够发现,webpack 给了这段提示:

One CLI for webpack must be installed. These are recommended choices, delivered as separate packages:
webpack-cli (https://github.com/webpack/webpack-cli) The original webpack full-featured CLI.
webpack-command (https://github.com/webpack-contrib/webpack-command) A lightweight, opinionated webpack CLI.
We will use “npm” to install the CLI via “npm install -D”.
Which one do you like to install (webpack-cli/webpack-command):

它提示我们必须安装一个用于 webpack 的 cli,这里它给了两个选项:webpack-cli 和 webpack-command。区别在于:

  • webpack-cli:最初的 webpack 全功能 CLI。
  • webpack-command:在 webpack 4.0 的时候推出,说是用于取代webpack-cli,等 webpack 5 正式发布,webpack-cli 将被废弃。但在最近 webpack-command 作者称 webpack 项目带头人不支持 webpack-cli 迁移到 webpack-command 的计划。
  • 所以目前我们依然安装 webpack-cli,等待 webpack 最终的确定即可。

我们可以通过

npm install webpack-cli -g

来安装 webpack-cli。安装完成之后,就可以执行 webpack -v 指令,同时也可以执行 webpack-cli -v,webpack-cli 目前的最新版本为 3.1.0。

先通过一个简单的代码来实验 webpack 的作用。

首先新建两个 js 文件,分别为 a.js 和 b.js:

// a.js
function a () {
    console.log('a');
}
a();

// b.js
function b () {
    console.log('b');
}
b();

大家想一下,如果要同时执行 a 方法和 b 方法的话,那么是不是需要在 html 中同时引入 a.js 和 b.js,就像这样:

<script src="./a.js"></script>
<script src="./b.js"></script>

同样当我们需要引入的 js 文件特别多时,比如在第 09 课:如何实现一个响应式框架这里实现的 mvue,大家还记得吗?我们引入的 script 是这样的:

<script src="./mvue/patch.js"></script>
<script src="./mvue/dep.js"></script>
<script src="./mvue/directive.js"></script>
<script src="./mvue/watcher.js"></script>
<script src="./mvue/observer.js"></script>
<script src="./mvue/compile.js"></script> 
<script src="./mvue/mvue.js"></script>

这就是没有模块化的结果,而我们期望的效果应该是这个样子:

<script src="./dist/mvue.js"></script>

如何才能达到我们的期望呢,这就需要使用模块打包器来实现。

先来看一下如何使用*模块打包器,把它打包成一个 js 文件,也就是一个 bundle。

首先需要让这两个 js 文件满足模块化的规范,这里使用 es6 规范。

// a.js
import b from './b.js';
function a () {
    console.log('a');
}
a();
b();

// b.js
export default function b () {
    console.log('b');
}

我们需要一个 webpack.config.js 文件,作为 webpack 的默认配置文件。

// webpack.config.js

module.exports = {
    entry: './a.js'
}

然后在控制台直接 cd 到项目文件夹下,执行 webpack 命令。看一下打印出来的内容:

Hash: b44cac8c0a6bda14b6d6
Version: webpack 4.17.2
Time: 603ms
Built at: 2018-09-04 17:15:19
  Asset       Size  Chunks             Chunk Names
main.js  968 bytes       0  [emitted]  main
Entrypoint main = main.js
[0] ./a.js + 1 modules 124 bytes {0} [built]
    | ./a.js 70 bytes [built]
    | ./b.js 54 bytes [built]

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/

一开始是一些这次打包的 hash 当前 webpack 的版本信息,打包耗时等等,主要看这里

Asset       Size  Chunks             Chunk Names
main.js  968 bytes       0  [emitted]  main

这里描述的是生成文件的信息,比如说:文件名为 main.js,大小为 968 bytes,它的 Chunks 为 0。

这里的 Chunk 表示为打包后的代码块,它的 Chunk Name 为 main。然后在最下面有一个警告,它告诉我们需要添加一个 mode 选项。这个后面再说,先看一下打包后的文件,因为它默认是一个压缩版本,我们贴出最后的代码部分:

"use strict";r.r(t),console.log("a"),console.log("b")

可以看到 a、b 中的实际代码已经被执行了。而需要引入的只是这个打包之后的 main.js。

这就是 webpack 的最基础使用,借助这个事例,是不是对 webpack 的定义会更加清楚?再来回顾一下 webpack 的定义:

  • webpack 是一个现代 JavaScript 应用程序的静态模块打包器,帮助我们把 a.js、b.js 打包成了 main.js。
  • 在 webpack 处理应用程序时,它会在内部创建一个依赖图。什么叫做依赖图?任何时候,一个文件依赖于另一个文件,webpack 就把此视为文件之间有依赖关系。webpack 从命令行或配置文件(webpack.config.js)中定义的一个模块列表开始(entry),处理你的应用程序。然后递归地构建一个依赖图,这个依赖图包含着应用程序所需的每个模块。

最后将所有这些依赖生成到一个或多个 bundle。

至此大家是不是对 webpack 的定义比较清楚了。接下来我们就开始学习 webpack 中的各种概念(配置),配置都应该写入 webpack 的配置文件中,默认的配置文件为 webpack.config.js。

webpack 的核心概念

webpack 的核心概念主要包含了 4 个:

  1. 入口(entry)
  2. 输出(output)
  3. loader
  4. 插件(plugins)
  5. 模式(mode)4.0 新增

入口(entry)

入口(entry point)指 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。

webpack 的入口属性主要包含两个部分:

  1. 单 entry 应用程序:就好像上面的例子中 entry: './a.js',只构建一个依赖图
  2. 多 entry 应用程序:表示应用有多个入口,多个入口代表有多个独立分离的依赖图。当项目中有多个单独的依赖关系意图构建一个多页面的应用程序时,我们就需要使用多 entry 的模式。

输出(output)

我们可以通过 output 属性来指定 bundle 的输出位置。output 属性告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist

在上面的事例中,我们并没有为其指定 output 属性,所以它默认的输出位置为 ./dist/main。而当指定 output 属性的时候,至少应该包括以下两点:

  1. filename 用于输出文件的文件名。
  2. 目标输出目录 path 的绝对路径。

比如下面的这段配置:

  entry: {
      main: './a.js'
    },
    output: {
        filename: '[name][hash:5].js',
        path: __dirname + '/dist'
    }

意思为:输出的文件的名称为入口文件的定义名称(main)加上打包哈希的前五位,输出文件的路径为当前目录下的 /dist 目录。

loader

loader是 webpack 中的一个重要概念,使用 module 表示。因为 webpack 本身只能用于处理 JavaScript 文件,也就是说项目中的 css、html 文件 webpack 默认是无法处理的!如果我们想要处理项目中的 css、html 文件那么就需要使用对应的 loader。

loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。本质上,webpack loader 将所有类型的文件,转换为应用程序的依赖图(和最终的 bundle)可以直接引用的模块。

loader 属性主要包含两块主要的内容:

  1. test 属性,用于标识出应该被对应的 loader 进行转换的某个或某些文件。
  2. use 属性,表示进行转换时,应该使用哪个 loader。

指定 lodaer 的方式有三种:

  1. 配置(推荐):在 webpack.config.js 文件中指定 loader ,通过 module 关键字。
  2. 内联:在每个 import 语句中显式指定 loader。
  3. CLI:在 shell 命令中指定它们

在这里只看第一种,也就是 webpack 所推荐的通过配置的方式指定 loader,其他的两种大家了解一下就可以。我们看下面这段 loader:

module: {
    rules: [
      { test: /\.txt$/, use: 'raw-loader' }
    ]
  }

以上配置中,对一个单独的 module 对象定义了 rules 属性,里面包含两个必须属性:test 和 use。这告诉 webpack 编译器(compiler) 如下信息:

“嘿,webpack 编译器,当你碰到「在 require()/import 语句中被解析为 ‘.txt’ 的路径」时,在你对它打包之前,先使用 raw-loader 转换一下。”

然后通过 css-loader 和 style-loader 来进行一个事例演示,css-loader 和 style-loader 主要用于对项目中 css 解析。

// index.html
<div>
    <p>这是一个P标签</p>
</div>


// style.css
p {
    font-size: 18px;
    color: red;
}

// a.js
import b from './b.js';
import './style.css';

function a () {
    console.log('a');
}

a();
b();

我们声明了一个 style.css 文件,然后在 a.js 中通过 es6 的方式引入,先不使用 loader,执行 webpack 命令,看一下现在的执行结果:

ERROR in ./style.css 1:2 Module parse failed: Unexpected token (1:2) You may need an appropriate loader to handle this file type.

它提示我们需要一个合适的 loader 来处理这个类型的文件。打开 webpack.config.js,添加一下代码:

module: {
    rules: [
        {
            test: /\.css$/,
            use: [
              { loader: 'style-loader' },
              {
                loader: 'css-loader',
                options: {
                  modules: true
                }
              }
            ]
          }
    ]
}

通过 module 关键字声明了 loader 属性,再通过 rules 关键字可以指定多个 loader。在这里指定了用以处理 /\.css$/ 的 loader 为 'style-loader' 和 'css-loader'

  • 'css-loader' 的作用为:解析 css 样式
  • 'style-loader' 的作用为:将解析之后的样式加入页面

所以需要关注 'style-loader' 和 'css-loader' 的引入顺序,因为 webpack 对 loader 的处理是自下而上的,也就是说我们需要 'css-loader' 先执行 'style-loader' 后执行(先解析后使用),就需要先定义 { loader: 'style-loader' } 后定义 { loader: 'css-loader' }

插件(plugins)

loader 被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务。

想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new 操作符来创建它的一个实例。

插件(plugins)被 webpack 称为支柱功能,插件目的在于解决 loader 无法实现的其他事。

在上面的例子中,如果把 output 设置为 filename: '[name][hash:5].js',那么会发现打包出的 bundle 的命名包含有一个 hash,这样在 html 文件中引入这个文件时会有些不太方便。解决这个问题可以通过 webpack 提供的一个插件 html-webpack-plugin 来解决。

如果要使用 html-webpack-plugin,那么需要先安装它,执行:npm install html-webpack-plugin --save-dev,然后在 webpack.config.js 中进行如下配置:

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

module.exports = {
    ...
    plugins: [
        new HtmlWebpackPlugin({template: './index.html'})
    ]
}

这里通过 require 方法引入 html-webpack-plugin,然后在 webpack 的 plugins 属性中通过 new HtmlWebpackPlugin({template: './index.html'}) 来声明 html-webpack-plugin,其中的 template 为 html-webpack-plugin 中的属性,意思为基于 index.html 模板,生成新的 html 文件。

PS:如果在执行 webpack 执行时出现 Error: Cannot find module 'webpack/lib/node/NodeTemplatePlugin' 错误,那么需要先执行 npm install webpack 指令,把 webpack 安装到本地。

模式(mode)4.0 新增

在通过 webpack 指令进行打包时,都会出现如下的警告:

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/

这是提示需要为当前的打包操作提供一个模式 mode,我们可以通过 mode 属性来为 webpack 指定一个模式,目前支持的模式有 development(开发模式)和 production(生产模式):

module.exports = {
  mode: 'production'
};

总结

关于 webpack 的知识我们就介绍到这里,在本章学习到的只是 webpack 的最基础知识。整个 webpack 的内容是非常多的,如果要全部介绍可能 10 章都不够,本章的目的是让大家对 webpack 有一定的认识,算是个让大家入门 webpack 的内容,只要大家入了门之后,就可以从 webpack 的官网中进行逐步的学习了。

在下一章我们会学习 Vue 中的官方脚手架 @vue/cli,@vue/cli 是基于 webpack 进行的一个基础配置,学习 @vue/cli 也是对 webpack 进行一个更深入的学习。

发表评论

电子邮件地址不会被公开。 必填项已用*标注