本系列博客汇总在这里:Vue.js 汇总

Webpack 详解


源码工程文件为:

一、认识 webpack

1、什么是 webpack?

我们先看看官方的解释:

At its core, webpack is a static module bundler for modern JavaScript applications.

从本质上来讲,webpack 是一个现代的 JavaScript 应用的静态模块打包工具。但是它是什么呢?用概念解释概念,还是不清晰。我们从两个点来解释上面这句话:模块 和 打包
Vue.js_34_Webpack 详解_文件处理

2、前端模块化

在前面学习中,我已经用了大量的篇幅解释了为什么前端需要模块化。而且我也提到了目前使用前端模块化的一些方案:AMD、CMD、CommonJS、ES6。在ES6之前,我们要想进行模块化开发,就必须借助于其他的工具,让我们可以进行模块化开发。并且在通过模块化开发完成了项目后,还需要处理模块间的各种依赖,并且将其进行整合打包。而 webpack 其中一个核心就是让我们可能进行模块化开发,并且会帮助我们处理模块间的依赖关系。而且不仅仅是 JavaScript 文件,我们的 CSS、图片、 json 文件等等在 webpack 中都可以被当做模块来使用(在后续我们会看到)。这就是 webpack 中模块化的概念。

3、打包如何理解呢?

理解了 webpack 可以帮助我们进行模块化,并且处理模块间的各种复杂关系后,打包的概念就非常好理解了。就是将 webpack 中的各种资源模块进行打包合并成一个或多个包(Bundle)。并且在打包的过程中,还可以对资源进行处理,比如压缩图片,将 scss 转成 css,将 ES6 语法转成 ES5 语法,将 TypeScript 转成 JavaScript 等等操作。但是打包的操作似乎 grunt/gulp 也可以帮助我们完成,它们有什么不同呢?

4、和 grunt/gulp 的对比

grunt/gulp 的核心是 Task。我们可以配置一系列的 task,并且定义 task 要处理的事务(例如 ES6、ts转化,图片压缩,scss 转成 css),之后让 grunt/gulp 来依次执行这些 task,而且让整个流程自动化。所以 grunt/gulp 也被称为前端自动化任务管理工具。

我们来看一个 gulp 的 task,下面的 task 就是将 src 下面的所有 js 文件转成 ES5 的语法,并且最终输出到 dist 文件夹中。
Vue.js_34_Webpack 详解_vue_02
什么时候用 grunt/gulp 呢?

如果你的工程模块依赖非常简单,甚至是没有用到模块化的概念。只需要进行简单的合并、压缩,就使用 grunt/gulp 即可。但是如果整个项目使用了模块化管理,而且相互依赖非常强,我们就可以使用更加强大的 webpack 了。

所以,grunt/gulp 和 webpack 有什么不同呢?

  • grunt/gulp 更加强调的是前端流程的自动化,模块化不是它的核心。
  • webpack 更加强调模块化开发管理,而文件压缩合并、预处理等功能,是它附带的功能。
二、webpack 的安装

安装 webpack 首先需要安装 Node.js,Node.js 自带了软件包管理工具 npm。

1、查看自己的 node 版本

Vue.js_34_Webpack 详解_Vue.js_03

2、全局安装 webpack

这里我先指定版本号3.6.0,因为 vue cli2 依赖该版本。
Vue.js_34_Webpack 详解_css_04
安装完成以后查看版本
Vue.js_34_Webpack 详解_文件处理_05

3、局部安装 webpack(后续才需要)

--save-dev 是开发时依赖,项目打包后不需要继续使用的。
Vue.js_34_Webpack 详解_vue_06

为什么全局安装后,还需要局部安装呢?

在终端直接执行 webpack 命令,使用的全局安装的 webpack,当在 package.json 中定义了 scripts 时,其中包含了 webpack 命令,那么使用的是局部 webpack。

三、webpack 的起步

1、准备工作

我们创建如下文件和文件夹
Vue.js_34_Webpack 详解_Vue.js_07
文件和文件夹解析

  • dist 文件夹:用于存放之后打包的文件。
  • src 文件夹:用于存放我们写的源文件。
  • main.js:项目的入口文件。
    Vue.js_34_Webpack 详解_Vue.js_08
  • mathUtils.js:定义了一些数学工具函数,可以在其他地方引用,并且使用。
    Vue.js_34_Webpack 详解_css_09
  • index.html:浏览器打开展示的首页 html。
    Vue.js_34_Webpack 详解_vue_10
  • package.json:通过 npm init 生成的,npm 包管理的文件(暂时没有用上,后面才会用上)。

2、js 文件的打包

现在的 js 文件中使用了模块化的方式进行开发,他们可以直接使用吗?不可以。

因为如果直接在 index.html 引入这两个 js 文件,浏览器并不识别其中的模块化代码。另外,在真实项目中当有许多这样的 js 文件时,我们一个个引用非常麻烦,并且后期非常不方便对它们进行管理。

我们应该怎么做呢?

使用 webpack 工具,对多个 js 文件进行打包。

我们知道,webpack 就是一个模块化的打包工具,所以它支持我们代码中写模块化,可以对模块化的代码进行处理。另外,如果在处理完所有模块之间的关系后,将多个 js 打包到一个 js 文件中,引入时就变得非常方便了。
Vue.js_34_Webpack 详解_Vue.js_11
Vue.js_34_Webpack 详解_Vue.js_12
注意:这里打包的时候不能有 package.json 文件,否则无法打包。
Vue.js_34_Webpack 详解_文件处理_13
使用打包后的文件

打包后会在 dist 文件下,生成一个 bundle.js 文件,文件内容有些复杂,这里暂时先不看,后续再进行分析。bundle.js 文件是 webpack 处理了项目直接文件依赖后生成的一个 js 文件,我们只需要将这个 js 文件在 index.html 中引入即可
Vue.js_34_Webpack 详解_css_14
测试
Vue.js_34_Webpack 详解_Vue.js_15

3、测试 ES6 的写法

我们再新建一个 JS 文件 info.js,并使用ES6的模块化的规范导出数据。
Vue.js_34_Webpack 详解_html_16
使用 ES6 的模块化的规范导入数据。
Vue.js_34_Webpack 详解_css_17
注意:我们需要重新打包! 再进行测试。
Vue.js_34_Webpack 详解_html_18

4、入口和出口

我们考虑一下,如果每次使用 webpack 的命令都需要写上入口和出口作为参数,就非常麻烦,有没有一种方法可以将这两个参数写到配置中,在运行时,直接读取呢?当然可以,就是创建一个 webpack.config.js 文件
Vue.js_34_Webpack 详解_Vue.js_19
然后。
Vue.js_34_Webpack 详解_html_20
可以看到。
Vue.js_34_Webpack 详解_Vue.js_21
Vue.js_34_Webpack 详解_html_22
Vue.js_34_Webpack 详解_vue_23

5、局部安装 webpack

目前,我们使用的 webpack 是全局的 webpack,如果我们想使用局部来打包呢?因为一个项目往往依赖特定的 webpack 版本,全局的版本可能很这个项目的 webpack 版本不一致,导出打包出现问题。所以通常一个项目,都有自己局部的 webpack。

  • 第一步,项目中需要安装自己局部的 webpack。
    Vue.js_34_Webpack 详解_Vue.js_24
    这里我们让局部安装 webpack3.6.0,Vue CLI3 中已经升级到 webpack4,但是它将配置文件隐藏了起来,所以查看起来不是很方便。

  • 第二步,通过 node_modules/.bin/webpack 启动 webpack 打包。
    Vue.js_34_Webpack 详解_html_25

6、package.json 中定义启动

如果每次执行都敲很长串有没有觉得不方便呢?其实,我们可以在 package.jsonscripts 中定义自己的执行脚本。

package.json 中的 scripts 的脚本在执行时,会按照一定的顺序寻找命令对应的位置。
首先,会寻找本地的 node_modules/.bin 路径中对应的命令,如果没有找到,会去全局的环境变量中寻找。

如何执行我们的 build 指令呢?
Vue.js_34_Webpack 详解_vue_26

四、loader 的使用

loader 是 webpack 中一个非常核心的概念。

webpack 用来做什么呢?

在我们之前的实例中,我们主要是用 webpack 来处理我们写的 js 代码,并且 webpack 会自动处理 js 之间相关的依赖。但是,在开发中我们不仅仅有基本的 js 代码处理,我们也需要加载 css、图片,也包括一些高级的将 ES6 转成 ES5 代码,将 TypeScript 转成 ES5 代码,将 scss、less 转成 css,将 .jsx、.vue 文件转成 js 文件等等。

对于 webpack 本身的能力来说,对于这些转化是不支持的。

那怎么办呢?

给 webpack 扩展对应的 loader 就可以啦。

loader 使用过程

  • 步骤一:通过 npm 安装需要使用的 loader。
  • 步骤二:在 webpack.config.js 中的 modules 关键字下进行配置。

大部分 loader 我们都可以在 webpack 的官网中找到,并且学习对应的用法。

1、css 文件处理 - 准备工作

项目开发过程中,我们必然需要添加很多的样式,而样式我们往往写到一个单独的文件中。在 src 目录中,创建一个 css 文件,其中创建一个 normal.css 文件。我们也可以重新组织文件的目录结构,将零散的 js 文件放在一个 js 文件夹中。normal.css 中的代码非常简单,就是将 body 设置为 red。但是,这个时候 normal.css 中的样式会生效吗?

当然不会,因为我们压根就没有引用它。

webpack 也不可能找到它,因为我们只有一个入口,webpack 会从入口开始查找其他依赖的文件。

在入口文件中引用:
Vue.js_34_Webpack 详解_html_27

2、css 文件处理 – 打包报错信息

重新打包,会出现如下错误:
Vue.js_34_Webpack 详解_html_28
这个错误告诉我们:加载 normal.css 文件必须有对应的 loader。

3、css 文件处理 – css-loader

在 webpack 的 官方网站 中,或者 中文站点

我们可以找到如下关于样式的 loader 使用方法:
Vue.js_34_Webpack 详解_Vue.js_29
Vue.js_34_Webpack 详解_vue_30
按照官方文档,我们需要安装两个对应的 loader。
Vue.js_34_Webpack 详解_html_31
Vue.js_34_Webpack 详解_文件处理_32
按照官方配置 webpack.config.js 文件。
Vue.js_34_Webpack 详解_html_33

重新打包项目
Vue.js_34_Webpack 详解_vue_34
成功!
Vue.js_34_Webpack 详解_文件处理_35

4、less 文件处理 – 准备工作

如果我们希望在项目中使用 less、scss、stylus 来写样式,webpack 是否可以帮助我们处理呢?我们这里以 less 为例,其他也是一样的。我们还是先创建一个 less 文件,依然放在 css 文件夹中。
Vue.js_34_Webpack 详解_文件处理_36
在 main.js 中引入
Vue.js_34_Webpack 详解_文件处理_37

5、less 文件处理 – 打包报错信息

Vue.js_34_Webpack 详解_html_38

6、less 文件处理 – less-loader

继续在官方中查找,我们会找到 less-loader 相关的使用说明,首先,还是需要安装对应的 loader。
Vue.js_34_Webpack 详解_html_39
注意:我们这里还安装了 less,因为 webpack 会使用 less 对 less 文件进行编译。其次,修改对应的配置文件。
添加一个 rules 选项,用于处理 .less 文件。
Vue.js_34_Webpack 详解_html_40
成功!
Vue.js_34_Webpack 详解_html_41

7、图片文件处理 – 资源准备阶段

首先,我们在项目中加入两张图片,一张较小的图片 test01.jpg(小于30kb),一张较大的图片 test02.jpg(大于30kb)。待会儿我们会针对这两张图片进行不同的处理。
Vue.js_34_Webpack 详解_css_42
我们先考虑在 css 样式中引用图片的情况,所以我更改了 normal.css 中的样式:
Vue.js_34_Webpack 详解_文件处理_43

8、图片文件处理 – 打包报错信息

如果我们现在直接打包,会出现如下问题。
Vue.js_34_Webpack 详解_vue_44

9、图片文件处理 – url-loader

图片处理,我们使用 url-loader 来处理,依然先安装 url-loader
Vue.js_34_Webpack 详解_css_45
修改 webpack.config.js 配置文件:

再次打包,运行 index.html,就会发现我们的背景图片选出了出来。而仔细观察,你会发现背景图是通过 base64 显示出来的,这就是 limit 属性的作用,当图片小于 30kb(1024*30)时,对图片进行 base64 编码。
Vue.js_34_Webpack 详解_vue_46

10、图片文件处理 – file-loader

由于 limit 属性的作用,当图片大于 30kb(1024*30)时,就需要使用 file-loader 了。这次因为大于 30kb 的图片,会通过 file-loader 进行处理,但是我们的项目中并没有 file-loader。Vue.js_34_Webpack 详解_css_47
那我们来安装一下 file-loader,如下。
Vue.js_34_Webpack 详解_文件处理_48
安装好以后,再次打包,就会发现 dist 文件夹下多了一个图片文件,但是图片并没有显示出来。
Vue.js_34_Webpack 详解_html_49
我们需要再配置一些东西。
Vue.js_34_Webpack 详解_css_50

11、图片文件处理 – 修改文件名称

Vue.js_34_Webpack 详解_html_51
我们发现 webpack 自动帮助我们生成一个非常长的名字。这是一个 32 位 hash 值,目的是防止名字重复,但是,真实开发中,我们可能对打包的图片名字有一定的要求,比如,将所有的图片放在一个文件夹中,跟上图片原来的名称,同时也要防止重复 ,所以,我们可以在 options 中添加上如下选项:

  • img:文件要打包到的文件夹。
  • name:获取图片原来的名字,放在该位置。
  • hash:8:为了防止图片名称冲突,依然使用 hash,但是我们只保留 8 位。
  • ext:使用图片原来的扩展名。
    Vue.js_34_Webpack 详解_vue_52
    但是,我们发现图片并没有显示出来,这是因为图片使用的路径不正确,默认情况下,webpack 会将生成的路径直接返回给使用者,但是,我们整个程序是打包在 dist 文件夹下的,所以这里我们需要在路径下再添加一个 dist/
五、ES6 语法处理

如果你仔细阅读 webpack 打包的 js 文件,发现写的 ES6 语法并没有转成 ES5,那么就意味着可能一些对 ES6 还不支持的浏览器没有办法很好的运行我们的代码。在前面我们说过,如果希望将 ES6 的语法转成 ES5,那么就需要使用 babel

而在 webpack 中,我们直接使用 babel 对应的 loader 就可以了。

npm install --save-dev babel-loader@7 babel-core babel-preset-es2015
Vue.js_34_Webpack 详解_html_53

配置 webpack.config.js 文件
Vue.js_34_Webpack 详解_css_54
重新打包,查看 bundle.js 文件,发现其中的内容变成了 ES5 的语法。
Vue.js_34_Webpack 详解_css_55

六、引入 vue.js

后续项目中,我们会使用 Vuejs 进行开发,而且会以特殊的文件来组织 vue 的组件。所以,下面我们来学习一下如何在我们的 webpack 环境中集成 Vuejs,现在,我们希望在项目中使用 Vuejs,那么必然需要对其有依赖,所以需要先进行安装。

npm install vue --save

Vue.js_34_Webpack 详解_html_56
注:因为我们后续是在实际项目中也会使用 vue 的,所以并不是开发时依赖,也就是并不是 npm install vue --save-dev

那么,接下来就可以按照我们之前学习的方式来使用 Vue 了
Vue.js_34_Webpack 详解_html_57
Vue.js_34_Webpack 详解_html_58
打包项目 – 错误信息

修改完成后,重新打包,运行程序。打包过程没有任何错误(因为只是多打包了一个vue的js文件而已),但是运行程序,没有出现想要的效果,而且浏览器中有报错。
Vue.js_34_Webpack 详解_vue_59
这个错误说的是我们使用的是 runtime-only 版本的 Vue,什么意思呢?

  • runtime-only ——> 代码中,不可以有任何的 template。
  • runtime-compiler ——> 代码中,可以有 template,因为有 compiler 可以用于编译 template。

这里我只说解决方案:Vue 不同版本构建,后续我具体讲解 runtime-only 和 runtime-compiler 的区别。

所以我们修改 webpack 的配置,添加如下内容即可。
Vue.js_34_Webpack 详解_html_60
具体的过程如下:
Vue.js_34_Webpack 详解_css_61

七、Vue 的终极使用方案

正常运行之后,我们来考虑另外一个问题,如果我们希望将 data 中的数据显示在界面中,就必须是修改 index.html,如果我们后面自定义了组件,也必须修改 index.html 来使用组件,但是 html 模板在之后的开发中,我并不希望手动的来频繁修改,是否可以做到呢?

在前面的 Vue 实例中,我们定义了 el 属性,用于和 index.html 中的 #app 进行绑定,让 Vue 实例之后可以管理它其中的内容,这里,我们可以将 div 元素中的 {{message}} 内容删掉,只保留一个基本的 id 为 div 的元素。但是如果我依然希望在其中显示 {{message}} 的内容,应该怎么处理呢?我们可以再定义一个 template 属性,代码如下:
Vue.js_34_Webpack 详解_html_62
重新打包,运行程序,显示一样的结果和 HTML 代码结构,那么,el 和 template 模板的关系是什么呢?在我们之前的学习中,我们知道 el 用于指定 Vue 要管理的 DOM,可以帮助解析其中的指令、事件监听等等。而如果 Vue实例中同时指定了 template,那么 template 模板的内容会替换掉挂载的对应 el 的模板。

这样做有什么好处呢?

这样做之后我们就不需要在以后的开发中再次操作 index.html,只需要在 template 中写入对应的标签即可。

但是,书写 template 模块非常麻烦怎么办呢?

没有关系,我们会将 template 模板中的内容进行抽离。

会分成三部分书写:template、script、style,结构变得非常清晰。

我们一步一步引入。

在学习组件化开发的时候,我说过以后的 Vue 开发过程中,我们都会采用组件化开发的思想。那么,在当前项目中,如果我也想采用组件化的形式进行开发,应该怎么做呢?

查看下面的代码:
Vue.js_34_Webpack 详解_css_63
当然,我们也可以将下面的代码抽取到一个js文件中,并且导出(进一步抽取)。
Vue.js_34_Webpack 详解_css_64
但是,还有一个问题,就是模板和 JS 文件还是没有分离,一个组件以一个 js 对象的形式进行组织和使用的时候是非常不方便的,一方面编写 template 模块非常的麻烦,另外一方面如果有样式的话,我们写在哪里比较合适呢?我们再进一步抽取。
Vue.js_34_Webpack 详解_Vue.js_65
现在,我们以一种全新的方式来组织一个 vue 的组件,但是,这个时候这个文件可以被正确的加载吗?必然不可以,这种特殊的文件以及特殊的格式,必须有人帮助我们处理。谁来处理呢?vue-loader 以及 vue-template-compiler
Vue.js_34_Webpack 详解_html_66
安装 vue-loader 和 vue-template-compiler

npm install vue-loader vue-template-compiler --save-dev
Vue.js_34_Webpack 详解_html_67

修改 webpack.config.js 的配置文件
Vue.js_34_Webpack 详解_html_68
最后,我们再来打包运行,但发现还是报错了。
Vue.js_34_Webpack 详解_html_69
处理办法。
Vue.js_34_Webpack 详解_Vue.js_70
Vue.js_34_Webpack 详解_css_71
之后,我们再来打包测试。
Vue.js_34_Webpack 详解_vue_72
后续使用
Vue.js_34_Webpack 详解_vue_73
Vue.js_34_Webpack 详解_文件处理_74

八、plugin

1、plugin 是什么?

plugin 是插件的意思,通常是用于对某个现有的架构进行扩展。webpack中的插件,就是对 webpack 现有功能的各种扩展,比如打包优化,文件压缩等等。

2、loader 和 plugin 区别

loader 主要用于转换某些类型的模块,它是一个转换器。plugin 是插件,它是对 webpack 本身的扩展,是一个扩展器。

3、plugin 的使用过程

  • 步骤一:通过 npm 安装需要使用的 plugins (某些 webpack 已经内置的插件不需要安装)
  • 步骤二:在 webpack.config.js 中的 plugins 中配置插件。

下面,我们就来看看可以通过哪些插件对现有的 webpack 打包过程进行扩容,让我们的 webpack 变得更加好用。

4、添加版权的 Plugin

我们先来使用一个最简单的插件,为打包的文件添加版权声明,该插件名字叫 BannerPlugin,属于 webpack 自带的插件。

按照下面的方式来修改 webpack.config.js 的文件:
Vue.js_34_Webpack 详解_Vue.js_75
重新打包程序,查看 bundle.js 文件的头部,看到如下信息:
Vue.js_34_Webpack 详解_文件处理_76

5、打包 html 的 plugin

目前,我们的 index.html 文件是存放在项目的根目录下的。我们知道,在真实发布项目时,发布的是 dist 文件夹中的内容,但是 dist 文件夹中如果没有 index.html 文件,那么打包的 js 等文件也就没有意义了。所以,我们需要将 index.html 文件打包到 dist 文件夹中,这个时候就可以使用 HtmlWebpackPlugin 插件,HtmlWebpackPlugin 插件可以为我们做这些事情:

  • 自动生成一个 index.html 文件(可以指定模板来生成)。
  • 将打包的 js 文件,自动通过 script 标签插入到 body 中。

安装 HtmlWebpackPlugin 插件

npm install html-webpack-plugin --save-dev
Vue.js_34_Webpack 详解_Vue.js_77

使用插件,修改 webpack.config.js 文件中 plugins 部分的内容如下:
Vue.js_34_Webpack 详解_Vue.js_78
Vue.js_34_Webpack 详解_Vue.js_79
这里的 template 表示根据什么模板来生成 index.html,另外,我们需要删除之前在 output 中添加的 publicPath 属性,否则插入的 script 标签中的 src 可能会有问题。
Vue.js_34_Webpack 详解_vue_80
修改以后,我们再来打包运行。
Vue.js_34_Webpack 详解_html_81
注意:如果其余都正确,打包出现问题,请留意html-webpack-plugin版本。
Vue.js_34_Webpack 详解_css_82

6、js 压缩的 Plugin

在项目发布之前,我们必然需要对 js 等文件进行压缩处理,这里,我们就对打包的 js 文件进行压缩
我们使用一个第三方的插件 uglifyjs-webpack-plugin,并且版本号指定 1.1.1,和 CLI2 保持一致。

npm install uglifyjs-webpack-plugin@1.1.1 --save-dev
Vue.js_34_Webpack 详解_html_83

修改 webpack.config.js 文件,使用插件。
Vue.js_34_Webpack 详解_css_84
查看打包后的 bunlde.js 文件,是已经被压缩过了。
Vue.js_34_Webpack 详解_vue_85

九、搭建本地服务器

webpack 提供了一个可选的本地开发服务器,这个本地服务器基于 node.js 搭建,内部使用 express 框架,可以实现我们想要的让浏览器自动刷新显示我们修改后的结果,不过它是一个单独的模块,在 webpack 中使用之前需要先安装它

npm install --save-dev webpack-dev-server@2.9.1
Vue.js_34_Webpack 详解_css_86
devserver 也是作为 webpack 中的一个选项,选项本身可以设置如下属性

  • contentBase:为哪一个文件夹提供本地服务,默认是根文件夹,我们这里要填写./dist
  • port:端口号。
  • inline:页面实时刷新。
  • historyApiFallback:在 SPA 页面中,依赖 HTML5 的 history 模式。

webpack.config.js 文件配置修改如下:
Vue.js_34_Webpack 详解_css_87
--open参数表示直接打开浏览器
Vue.js_34_Webpack 详解_Vue.js_88
Vue.js_34_Webpack 详解_Vue.js_89
这样修改代码以后,就不用每次都重新构建项目了。

十、配置文件的分离

经过上面的学习,我们可以发现,在配置文件 package.json 当中的一些配置使用场景有所不用,比如:
Vue.js_34_Webpack 详解_Vue.js_90
Vue.js_34_Webpack 详解_vue_91
Vue.js_34_Webpack 详解_Vue.js_92
如有错误,欢迎指正!