浅谈webpack(二)

上一篇文章浅谈webpack(一)里主要是通过文字方式对构建工具做了一个大概的阐述,可能很多人还是有点晕,其实我也不是很清楚,我也是收集很多人的文章,然后总结汇总起来的。。。但从这篇文章开始,我会借助代码表述,这样或许能更加直观一些,希望能帮助到你。

一:原始的js使用方式

首先新建一个文件夹,然后新建index.html,index.js两个文件,然后在index.html文件里引入index.js,如下:

然后打开用浏览器打开index.html文件,即可看到两行英文:

hello webpack

this word from index.js

上面的方式是自己定义js文件引入,如果是第三方js库文件呢?则需要下载引入才可以,比如处理时间的库moment.js,如下:

这里需要注意,因为在index.js里调用moment.js里面的api了,因此在index.html文件里,需要先引入moment.js才可以,如上图。

此时刷新页面,应该会多出一行,也就是我今天编辑的日期,如下:

March 26th 2018, 5:43:41 pm

上面的这种引入js文件的方式很简单明了,同样对于css文件也可以这样引入,但一旦第三方库文件更新了,则我们必须重新下载再引入。。。

既然维护和更新第三方包,采用上述的方式比较繁琐,且下载引入的工作大多是重复且无意义的,因此包(模块)管理器就诞生了。。。

二:包(模块)管理器时代

大概从2010年开始,数个JavaScript包管理工具诞生了,它们旨在通过一个中央仓库,使得下载和更新JavaScript库更加自动化。2013年时,Bower可能是最流行的;到了2015年,npm(Node Package Manager)逐渐占据统治地位;而2016年,yarn开始逐渐引起关注,但是它的底层是基于npm的。还有一点,npm最初是为node.js开发的,并不是为了前端,但像Bower和Browserify这些包管理器的主要应用场合主要还是前端,为了能用到后端强大的功能,因此配合其他工具,npm完全可以将应用在后端的包拿到前端来用。。。

npm包管理器在安装node.js时会自动安装,一般不用单独安装。安装完在终端输入:npm -v 可以查看npm的版本号。

然后咱看看如何通过npm来管理第三方包,首先在项目的根目录打开终端,输入:npm init 即可出现一系列的交互问题,一般可以一直敲击回车即可,最后会在根目录生成一个package.json的文件,如下:

那这个文件有什么用呢,各个字段又代表什么意思呢?

package.json这个文件定义了项目所需要的各种模块,以及项目的配置信息(比如项目名,版本,许可证,作者,描述等元数据)。它是一个json对象,也就意味着它不能含有任何的注释,而且所有字段都必须双引号包裹。

version:项目版本字段(遵守 大版本.次要版本.小版本 的格式)

description:项目的描述信息,可以根据项目情况自己定义。

main:指定了加载的入口文件,当代码里出现require("moduleName")就会加载这个文件,这个字段的默认值是项目根目录下面的index.js。

scripts:指定了运行脚本命令的npm命令行缩写,每个属性对应一段 Shell 脚本。其底层实现原理是通过调用 Shell 去运行脚本命令,例如执行命令等同于执行命令。(这里的dev字段是我自己为了说明添加的,npm init时没有)。

license:是声明项目代码遵循的许可证。

当然package.json文件也可以自己新建,而不用通过npm init命令。既然是个json对象,因此里面的字段还可以根据项目情况修改或添加其他字段。。。

说完了package.json文件的作用,那如何安装第三方包呢?

只需:npm install moment --save

还可以支持缩写:npm i moment -S

如果有淘宝cnpm镜像,还可以:cnpm i moment -S

然后再看package.json文件,就会多一个dependencies字段,如下:

另外再看项目的根目录,会多出来一个node_modules的文件夹,里面就是有关moment模块的代码。

其实上面的命令就是做两件事:首先,它会下载moment.js,将其保存到node_modules目录中。然后,它会更新package.json文件,保存moment的安装信息。

这样,当我们需要与其他人分享这个项目时,就不需要将node_modules发送给对方了,而只需要给它package.json文件,因为它可以使用npm install安装所有依赖库。

现在,我们不需要手动下载moment.js了,而可以通过npm自动下载以及更新,这样方便很多;但是,我们需要在node_modules中找到对应的JS文件,然后将它引入html,这样很不方便,如下:

到这里,我们知道现在需要一种工具,可以自动的查找依赖包的位置,并且通过构建打包后自动引入到文件中。。。这便是打包工具

三:构建打包工具时代

2009年,一个叫做CommenJS的项目出现了,它为JavaScript模块化定义了一个规范,从而允许JavaScript能够和其他编程语言一样在不同文件中引入模块。Node.js是支持CommenJS规范的,它可以使用require直接引用模块:

这样写非常方便,然而重新刷新浏览器页面,打开控制台,你会发现报错了:require is not defined

这时就需要打包工具webpack上场了,他可以将源代码构建成为兼容浏览器的静态代码,依次来避免上面的问题。而具体的原理是什么呢?

因为ECMAScript6 标准增加了 JavaScript 语言层面的模块体系定义。ES6 模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,却只能在运行时确定这些东西。而Webpack 这个模块打包器,却可以根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源,既然都是静态资源了,也就不用再区分前后端兼容问题了。。。

当然webpack也是一个第三方库文件,也需要安装才能使用,但我们还要明白,webpack这个打包工具只是在我们开发的时候用,等到真正将代码部署到生产环境时,部署的是静态资源,也就是说构建完成的代码。因此只需要将webpack安装到开发依赖即可。。。

如:npm install webpack --save-dev

缩写:npm i webpack -D

如有淘宝cnpm镜像,则:cnpm i webpack -D

然后再看package.json文件,则多了下面的代码:

而字段devDependencies就表示开发依赖,和上面dependencies生产依赖不同,后者表示即使是静态资源,也需要这些依赖包。。。

然后安装了webpack后,如何使用呢?其实任何一个工具包,都有一个可执行文件,这个可执行文件就是运行这个工具包的入口,因此找到这个可执行文件的路径,然后在终端运行即可。。。而一般的应用程序,都会把可执行文件放在bin(binary)目录,表示可执行脚本文件。你如果打开这个文件,可以看到其实就是一个js文件,如下:

而第一行的:#!/usr/bin/env node,表示这个文件的执行环境是node.js,在try代码块里,还要引入webpack-cli这个依赖,因此我们还需要安装webpack-cli

我电脑已有淘宝镜像,因此:cnpm i webpack-cli -D

既然是可执行文件了,那咱在终端执行这个可执行文件,看有什么效果,因为我安装的是webpack4.2.0版本,这个版本要求在不配置任何选项的情况下,在终端运行这个webpack可执行文件,需要指定模式,也就是webpack有自己默认的打包模式,一个是开发环境模式,一个是生产环境模式,两种模式的差别是打包生成的文件在生产环境下做了代码压缩及丑陋处理,如下:

任意在终端运行上面一条命令,则可在看到在项目的根目录多出一个dist文件夹,然后里面有一个main.js文件,然后再index.html文件里引入,再打开index.html正常显示了,代码如下:

以上就说明了,通过打包工具得到的打包文件,再引入到浏览器就没有兼容问题了。。。

但是这样还有一个问题,每次当文件发生了变化,都需要重新在终端来运行打包命令不是一个好的选择,因此如果有一个配置文件的话,就不用每次在终端输入命令了。。。

这里将打包后输出的文件重命名为bundle.js文件了,然后在终端输入(注意此时不用再指明需要打包的入口文件index.js了):

会发现dist目录下的增加bundle.js了,这时将main.js删除,在index.html重新引入bundle.js,会发现index.html文件在浏览器打开依然正常。。。

这里Webpack 会分析entry入口文件,解析包含依赖关系的各个文件。这些文件(模块)都会打包到 bundle.js 。Webpack 会给每个模块分配一个唯一的 id 并通过这个 id 索引和访问模块。在页面启动时,会先执行 entry中的代码,其它模块会在运行 require 的时候再执行。

然后终端命令后面还有 --mode development,

此时再在终端运行下面语句,index.html依然正常:

这里还是不方便,每次当文件发生修改了,还是需要重新在终端输入重新构建的命令,因此需要一种机制,可以自动监控文件变动,一旦文件发生变动了自动构建,并自动刷新页面。。。因此DevServer出场 了

DevServer会启动一个http服务器用于网页请求,同时帮助启动webpack,并接收webpack发出的文件变更信号,通过webSocket协议自动刷新网页做到实时预览。

安装 cnpm i -D webpack-dev-server ,然后配置package.json的scripts字段,

就可以直接在终端输入npm run server 就可以自动开启DevServer服务了,同时会自动打开浏览器页面,页面地址是:http://localhost:8080/,DevServer 启动后会一直驻留在后台保持运行,访问这个网址你就能获取项目根目录下的。 用浏览器打开这个地址会发现页面空白,错误原因是加载404了。 同时你会发现并没有文件输出到目录,原因是 DevServer 会把 Webpack 构建出的文件保存在内存中,想要访问输出的文件时,必须通过 HTTP 服务访问。 由于 DevServer 不会理会里配置的属性,所以要获取的正确 URL 是,对应的应该修改为:

以上都是引入的js文件,但一个应用肯定也需要css等样式文件,那如何引入呢?

在根目录新建main.css文件,如下:

然后在index.js里面通过CommonJS的require("")函数引入,如下:

然后发现构建出错了,错误信息为:Module parse failed,模块转换失败,这是为什么呢?

这是因为 Webpack 不原生支持解析 CSS 文件。要支持非 JavaScript 类型的文件,需要使用 Webpack 的 Loader 机制。Webpack的配置修改使用如下:

Loader 可以看作具有文件转换功能的翻译员,配置里的 数组配置了一组规则,告诉 Webpack 在遇到哪些文件时使用哪些 Loader 去加载和转换。 如上配置告诉 Webpack 在遇到以 结尾的文件时先使用 读取 CSS 文件,再交给 把 CSS 内容注入到 JavaScript 里。 在配置 Loader 时需要注意的是:

属性的值需要是一个由 Loader 名称组成的数组,Loader 的执行顺序是由后到前的;

每一个 Loader 都可以通过 URL querystring 的方式传入参数,例如 中的 告诉 要开启 CSS 压缩。

在重新执行 Webpack 构建前要先安装新引入的 Loader:

安装成功后重新执行构建时,你会发现文件被更新了,里面注入了在中写的 CSS,而不是会额外生成一个 CSS 文件。 但是重新刷新网页时将会发现this word from index.js 竟然有颜色了,样式生效了! 也许你会对此感到奇怪,CSS文件竟然被写在了 JavaScript 里!这其实都是的功劳,它的工作原理是把 CSS 内容用 JavaScript 里的字符串存储起来, 在网页执行 JavaScript 时通过 DOM 操作动态地往标签里插入标签。 也许你认为这样做会导致js文件会越来越大,并导致加载网页时间变长,想让 Webpack 单独输出 CSS 文件。这便到Plugin插件出场的时候。。。

Plugin 是用来扩展 Webpack 功能的,通过在构建流程里注入钩子实现,它给 Webpack 带来了很大的灵活性。

在上面我们通过 Loader 加载了 CSS 文件,并把css文件注入到了bundle.js里面了,但我们不想让bundle.js变的越来越大,因此这里通过 Plugin 把注入到文件里的 CSS 提取到单独的文件中,配置修改如下:

要让以上代码运行起来,需要先安装新引入的插件:

安装成功后重新执行构建,你会发现 dist 目录下多出一个 文件, 里也没有 CSS 代码了,再把该 CSS 文件引入到 里,刷新页面依然没有问题。

从以上代码可以看出, Webpack 是通过 属性来配置需要使用的插件列表的。 属性是一个数组,里面的每一项都是插件的一个实例,在实例化一个组件时可以通过构造函数传入这个组件支持的配置属性。

例如 插件的作用是提取出 JavaScript 代码里的 CSS 到一个单独的文件。 对此你可以通过插件的 属性,告诉插件输出的 CSS 文件名称是通过 字符串模版生成的,里面的 代表css文件名称, 代表根据文件内容算出的8位 hash 值。

到这里差不多就对webpack基本用法有一个初步的了解了,Webpack 启动后会从 Entry 里配置的 Module 开始递归解析 Entry 依赖的所有 Module。 每找到一个 Module, 就会根据配置的 Loader 去找出对应的转换规则,对 Module 进行转换后,再解析出当前 Module 依赖的 Module。 这些模块会以 Entry 为单位进行分组,一个 Entry 和其所有依赖的 Module 被分到一个组也就是一个 Chunk。最后 Webpack 会把所有 Chunk 转换成文件输出。 在整个流程中 Webpack 会在恰当的时机执行 Plugin 里定义的逻辑。

这一篇就告一段落,下一篇开始说一些高级的特性。。。

谢谢你的时间,希望能帮到你。

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180326G1X31700?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券