购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

3.2 Webpack

Webpack是由德国开发者Tobias Koppers开发的模块加载器,如图3-7所示。Webpack最初主要想解决代码拆分的问题,而这也是Webpack今天受欢迎的主要原因。随着Web应用规模越写越大,移动设备越来越普及,拆分代码的需求与日俱增。如果不拆分代码,就很难实现期望的性能。

图3-7 Webpack框架Logo

2014年,Facebook的Instagram的前端团队分享了他们在对前端页面加载进行性能优化时用到了Webpack的Code Splitting(代码拆分)功能。随即Webpack被广泛传播使用,同时开发者也给Webpack社区贡献了大量的Plugin(插件)和Loader(转换器)。

3.2.1 Webpack介绍

Webpack是一个现代JavaScript应用程序的静态模块打包器(Module Bundler)。当Webpack处理应用程序时,它会递归地构建一个依赖关系图(Dependency Graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个包。

1.Webpack的优点

Webpack相比较其他构建工具,具有以下几个优点。

(1)智能解析:对CommonJS、AMD、CMD等支持得很好。

(2)代码拆分:创建项目依赖树,每个依赖都可拆分成一个模块,从而可以按需加载。

(3)Loader:Webpack核心模块之一,主要处理各类型文件编译转换及Babel语法转换。

(4)Plugin(插件系统):强大的插件系统,可实现对代码压缩、分包chunk、模块热替换等,也可实现自定义模块、对图片进行base64编码等,文档非常全面,自动化工作都有直接的解决方案。

(5)快速高效:开发配置可以选择不同环境的配置模式,可选择打包文件使用异步I/O和多级缓存提高运行效率。

(6)微前端支持:Module Federation也对项目中如何使用微型前端应用提供了一种解决方案。

(7)功能全面:最主流的前端模块打包工具,支持流行的框架打包,如React、Vue、Angular等,社区全面。

2.Webpack的工作方式

Webpack的工作方式是把项目当作一个整体,通过一个给定的主文件(如index.js),Webpack将从这个主文件开始找到项目的所有依赖文件,使用Loader处理它们,最后打包为一个(或多个)浏览器可识别的JavaScript文件,如图3-8所示。

图3-8 Webpack运行方式

3.2.2 Webpack安装与配置

接下来,详细介绍如何安装和配置Webpack工具。

1.安装Node.js

推荐到Node.js官网下载stable版本。NPM作为Node.js包管理工具在安装Node.js时已经顺带安装好了。

注意: 保持Node.js和Webpack的版本尽量新,可以提升Webpack打包速度。

2.更新Node.js

如果想更新Node.js版本,则可以使用n模块,命令如下:

3.更新NPM

如果需要更新NPM,则可以执行的命令如下:

4.项目创建及初始化

初始化一个Webpack编译的项目,命令如下:

目录结构如下:

5.Webpack安装与卸载

注意: Webpack安装需要同时安装Webpack和Webpack-cli这两个模块。

6.通过配置文件使用Webpack

配置文件规定了Webpack该如何打包,而执行npx webpack./main.js进行打包使用的则是Webpack提供的默认配置文件。

配置webpack.config.js文件,配置如下:

默认模式为mode:production,如果mode被配置为production,则打包出的文件会被压缩,如果mode被配置为development,则不会被压缩。

entry的意思是这个项目要打包,以及从哪一个文件开始打包。打包输出中Chunk Names配置的main就是entry中的main。简写模式如下:

output的意思是打包后的文件放在哪里:

(1)output.filename指打包后的文件名。

(2)output.path指打包后的文件放到哪一个文件夹下,是一个绝对路径。需要引入Node中的path模块,然后调用这个模块的resolve方法。

Webpack配置文件的作用是设置配置的参数,提供给Webpack-cli,Webpack从main.js文件开始打包,打包生成的文件放到bundle文件夹下,生成的文件名叫作bundle.js。如果运行npx webpack命令,则会按照配置文件进行打包。

Webpack默认的配置文件名为webpack.config.js,如果要使用自定义名字(例如my.webpack.js作为配置文件名),则可以用指令npx webpack--config my.webpack.js实现。

7.配置package.json

NPM scripts原理:当执行npm run xx命令时,实际上运行的是package.json文件中的xx命令。在scripts标签中使用Webpack,会优先到当前项目的node_modules中查找是否安装了Webpack(和直接使用Webpack命令时到全局查找是否安装Webpack不同),命令如下:

如果运行dev命令,则会自动执行webpack命令。最后可以直接运行npm run dev命令进行Webpack打包。

执行npm run dev命令后,在命令行中会输出编译完成后的提示信息,效果如图3-9所示。

图3-9 Webpack编译提示

3.2.3 Webpack基础

Webpack的模块打包工具通过分析模块之间的依赖,最终将所有模块打包成一份或者多份代码包,供HTML直接引用。

1.核心概念

Webpack最核心的概念如下。

(1)Entry:入口文件,Webpack会从该文件开始进行分析与编译。

(2)Output:出口路径,打包后创建包的文件路径及文件名。

(3)Module:模块,在Webpack中任何文件都可以作为一个模块,会根据配置使用不同的Loader进行加载和打包。

(4)Chunk:代码块,可以根据配置将所有模块代码合并成一个或多个代码块,以便按需加载,提高性能。

(5)Loader:模块加载器,进行各种文件类型的加载与转换。通过不同的Loader,Webpack有能力调用外部的脚本或工具,实现对不同格式文件的处理,例如分析及将scss转换为css,或者把ES6+文件(ES6和ES7)转换为现代浏览器兼容的JS文件,对React的开发而言,合适的Loader可以把React中用到的JSX文件转换为JS文件。

(6)Plugin:拓展插件,可以通过Webpack相应的事件钩子,介入打包过程中的任意环节,从而对代码按需修改。

2.常见加载器(Loader)

Webpack仅仅提供了打包功能和一套文件处理机制,然后通过生态中的各种Loader和Plugin对代码进行预编译和打包。

Loader的作用:

(1)Loader让Webpack能够去处理那些非JavaScript文件。

(2)Loader专注实现资源模块加载从而实现模块的打包。

将常用的加载器分成以下三类。

(1)编译转换类:将资源模块转换为JS代码。以JS形式工作的模块,如css-loader。

(2)文件操作类:将资源模块复制到输出目录,同时将文件的访问路径向外导出,如file-loader。

(3)代码检查类:对加载的资源文件(一般是代码)进行校验,以便统一代码风格,提高代码质量,一般不会修改生产环境的代码。

下面介绍最常用的几种加载器:解析ES6+、处理JSX、CSS/Less/SASS样式、图片与字体。

3.解析ES6+

在Webpack中解析ES6需要使用Babel,Babel是一个JavaScript编译器,可以实现将ES6+转换成浏览器能够识别的代码。

Babel在执行编译时,可以依赖.babelrc文件,当设置依赖文件时,会从项目的根目录下读取.babelrc的配置项,.babelrc配置文件主要是对预设(presets)和插件(plugins)进行配置。

下面介绍如何在Webpack中使用Babel。

安装依赖,命令如下:

注意: Babel 7推荐使用@babel/preset-env套件来处理转译需求。顾名思义,preset即“预制套件”,包含了各种可能用到的转译工具。之前的以年份为准的preset已经废弃了,现在统一用这个总包。同时,Babel已经放弃开发stage-∗包,以后的转译组件都只会放进preset-env包里。

配置webpack.config.js文件的Loader,配置如下:

在根目录创建.babelrc,并配置preset-env对ES6+语法特性进行转换,配置如下:

注意: Babel默认只转换新的JavaScript句法(syntax),而不转换新的API,例如Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局对象,以及一些定义在全局对象上的方法(例如Object.assign)都不会转码。

转译新的API,需要借助polyfill方案去解决,可使用@babel/polyfill或@babel/plugin-transform-runtime,二选一即可。

4.@babel/polyfill

本质上@babel/polyfill是core-js库的别名,随着core-js@3的更新,@babel/polyfill无法从2过渡到3,所以@babel/polyfill已经被放弃,可查看corejs 3的更新。

安装依赖包,命令如下:

.babelrc文件需写入配置,而@babel/polyfill不用写入配置,会根据useBuiltIns参数去决定如何被调用,配置如下:

配置参数说明如下。

(1)Modules:"amd"|"umd"|"systemjs"|"commonjs"|"cjs"|"auto"|false。

默认值为auto。用来转换ES6的模块语法。如果使用false,则不会对文件的模块语法进行转换。

如果要使用Webpack中的一些新特性,例如,Tree Shaking和sideEffects,就需要设置为false,对ES6的模块文件不进行转换,因为这些特性只对ES6模块有效。

(2)useBuiltIns:"usage"|"entry"|false,默认值为false。

false:需要在JS代码的第一行主动通过import'@babel/polyfill'语句将@babel/polyfill整个包导入(不推荐,能覆盖到所有API的转译,但体积最大)。

entry:需要在JS代码的第一行主动通过import'@babel/polyfill'语句将browserslist环境不支持的所有垫片导入(能够覆盖到'hello'.includes('h')这种句法,足够安全且代码体积不是特别大)。

usage:项目里不用主动通过import导入,会自动将代码里已用到的且browserslist环境不支持的垫片导入(但是检测不到'hello'.includes('h')这种句法,对这类原型链上的句法问题不会进行转译,书写代码需注意)。

(3)targets用来配置需要支持的环境,不仅支持浏览器,还支持Node。如果没有配置targets选项,就会读取项目中的browserslist配置项。

(4)loose的默认值为false,如果preset-env中包含的Plugin支持loose的设置,则可以通过这个字段来统一设置。

5.解析React JSX

JSX是React框架中引入的一种JavaScript XML扩展语法,它能够支持在JS中编写类似HTML的标签,本质上来讲JSX就是JS,所以需要Babel和JSX的插件preset-react支持解析。

安装React及@babel/preset-react,命令如下:

配置解析React的presets,配置如下:

6.解析CSS

传统上会在HTML文件中引入CSS代码,借助webpack style-loader和css-loader可以在.js文件中引入CSS文件并让样式生效,如果需要使用预处理脚本,如LESS,则需要安装less-loader。

(1)css-loader:用于加载.css文件并转换成commonJS对象。

(2)style-loader:将样式通过style标签插入head中。

安装依赖css-loader和style-loader,命令如下:

Webpack配置项添加Loader配置,其中由于Loader的执行顺序是从右向左执行的,所以会先进行CSS的样式解析后执行style标签的插入,配置如下:

less-loader将.less转换成.css。安装less-loader依赖并添加Webpack配置,配置如下:

7.解析图片和字体

Webpack提供了两个Loader来处理二进制格式的文件,如图片和字体等,url-loader允许有条件地将文件转换为内联的base-64 URL(当文件小于给定的阈值时),这会减少小文件的HTTP请求数。如果文件大于该阈值,则会自动交给file-loader处理。

1)file-loader

file-loader用于处理文件及字体。安装file-loader依赖并配置,配置如下:

2)url-loader

url-loader也可以处理文件及字体,对比file-loader的优势是可以通过配置,将小资源自动转换为base64。

安装url-loader依赖并配置Webpack,配置如下:

8.常见插件(Plugin)

插件的目的是为了增强Webpack的自动化能力,Plugin可解决其他自动化工作,如清除dist目录、将静态文件复制至输出代码、压缩输出代码,常见的场景如下:

(1)实现自动在打包之前清除dist目录(上次的打包结果)。

(2)自动生成应用所需要的HTML文件。

(3)根据不同环境为代码注入类似API地址这种可能变化的部分。

(4)将不需要参与打包的资源文件复制到输出目录。

(5)压缩Webpack打包完成后输出的文件。

(6)自动将打包结果发布到服务器以实现自动部署。

9.文件指纹

文件指纹的作用:

(1)在前端发布体系中,为了实现增量发布,一般会对静态资源加上md5文件后缀,保证每次发布的文件都没有缓存,同时对于未修改的文件不会受发布的影响,最大限度地利用缓存。

(2)简单地来讲“文件指纹”的应用场景是在项目打包时使用(上线),在项目开发阶段用不到。

这里简单介绍一下3种不同的hash表示方式。

(1)hash:与整个项目的构建相关,当有文件修改时,整个项目构建的hash值就会更新。

(2)chunkhash:和Webpack打包的chunk相关,不同的entry会生成不同的chunkhash,一般用于.js文件的打包。

(3)contenthash:根据文件内容来定义hash,如果文件内容不变,则contenthash不变。例如.css文件的打包,当修改了.js或.html文件但没有修改引入的.css样式时,文件不需要生成新的hash值,所以可适用于.css文件的打包。

注意: 文件指纹不能和热更新一起使用。

1).js文件指纹设置:chunkhash

代码如下:

2).css文件指纹:contenthash

由于上面方式通过style标签将css插入head中并没有生成单独的.css文件,因此可以通过min-css-extract-plugin插件将css提取成单独的.css文件,并添加文件指纹。

安装依赖mini-css-extract-plugin,命令如下:

配置.css文件指纹,配置如下:

3)图片文件指纹设置:hash

其中,hash对应的是文件内容的hash值,默认由md5生成,不同于前面所讲的hash值,配置如下:

代码压缩,这里介绍两个插件:.css文件压缩和.html文件压缩。

(1).css文件压缩:optimize-css-assets-webpack-plugin。

安装optimize-css-assets-webpack-plugin和预处理器cssnano,命令如下:

配置Webpack,配置如下:

(2).html文件压缩:html-webpack-plugin。

安装html-webpack-plugin插件,命令如下:

配置Webpack,配置如下:

10.跨应用代码共享(Module Federation)

Module Federation使JavaScript应用得以在客户端或服务器上动态运行另一个包的代码。Module Federation主要用来解决多个应用之间代码共享的问题,可以更加优雅地实现跨应用的代码共享。

1)三个概念

首先,要理解三个重要的概念,如表3-3所示。

表3-3 三个重要的概念

一个Webpack构建可以是remote(服务的提供方),也可以是host(服务的消费方),还可以同时扮演服务提供者和服务消费者的角色,这完全看项目的架构。

2)host与remote两个角色的依赖关系

任何一个Webpack构建既可以作为host消费方,也可以作为remote提供方,区别在于职责和Webpack配置的不同。

3)案例讲解

一共有三个微应用:lib-app、component-app、main-app,角色分别如表3-4所示。

表3-4 三个微应用的关系

下面分别创建三个微应用的项目:

lib-app模块提供其他模块所依赖的核心库,如lib-app对外提供react和react-dom两个类库模块。

步骤1:创建lib-app模块的项目,目录结构如图3-10所示。

图3-10 lib-app模块目录结构

该模块需要安装两个类库,即react和react-dom,命令如下:

步骤2:配置Webpack编译配置,配置如下:

这里通过ModuleFederationPlugin插件设置暴露的库,详细信息如表3-5所示。

表3-5 ModuleFederationPlugin属性

步骤3:配置package.json文件中的运行脚本,脚本如下:

步骤4:除去生成的map文件,有4个文件,如图3-11所示,输出具体的编译文件如下:

步骤5:在命令行中,输入npm serve命令启动lib-app项目,启动后在3000端口浏览。

component-app模块对外提供组件库,如Button、Dialog、Logo基础组件。

步骤1:创建component-app模块,目录结构如图3-12所示。

图3-11 lib-app模块打包输出目录

图3-12 component-app模块目录

步骤2:创建Button、Dialog、Logo三个React组件。

Button.jsx组件,代码如下:

Dialog.jsx组件,代码如下:

Logo.jsx组件,代码如下:

步骤3:需要以异步的方式导入index.js模块,所以这里创建了bootstrap.js模块,在index.js文件中通过import导入bootstrap.js文件,代码如下:

在index.js文件中导入bootstrap.js文件,代码如下:

步骤4:webpack.config文件的配置如下:

步骤5:启动模块。

在该子项目下运行npm run start命令打开浏览器:localhost:3001,可以看到组件正常工作。

main-app模块为三个模块中的主模块,该模块依赖component-app模块的基础组件,同时也依赖lib-app模块。

步骤1:创建main-app模块,目录结构如图3-13所示。

图3-13 创建main-app模块

步骤2:创建App.jsx文件,编写界面,代码如下:

步骤3:配置Webpack,配置如下:

步骤4:启动编译器后,通过localhost:3002端口查看,效果如图3-14所示。

图3-14 跨应用代码共享效果

11.开发运行构建

使用Webpack的Webpack-dev-server插件可以帮助开发者快速搭建一个代码运行环境,Webpack-dev-server提供的热更新功能极大地方便了代码编译后进行预览。

1)文件监听:watch

在Webpack-cli中提供了watch工作模式,这种模式下项目中的文件会被监视,一旦这些文件发生变化就会自动重新运行打包任务。

Webpack开启监听模式有以下两种方式:

(1)启动Webpack命令时带上--watch参数。

(2)在配置webpack.config.js文件中设置watch:true。

缺点是每次都需要手动刷新浏览器,需要自己使用一些http服务,例如使用http-server轮询判断文件的最后编辑时间是否发生变化,一开始有个文件的修改时间,这个修改时间先存储起来,下次再有修改时就会和上次修改时间进行比对,发现不一致时不会立即告诉监听者,而是把文件缓存起来,等待一段时间,等待期间内如果有其他变化,则会把变化列表一起构建,并生成到bundle文件夹。

可通过Webpack添加配置或者CLI添加配置的方式开启监听模式,该方式在源码变化时需要每次手动刷新浏览器。

Webpack配置的代码如下:

除了可通过watch参数的配置方式开启监听外,也可通过定制watch模式选项的形式watchOptions来定制监听配置,配置如下:

Webpack-dev-server是Webpack官方推出的一个开发工具,它提供了一个开发服务器,并且集成了自动编译和自动刷新浏览器等一系列功能的安装指令,如图3-15所示。

Webpack Compiler将JavaScript编译成输出的bundle.js文件。

HMR Server将热更新的文件输出到HMR Runtime。

Bundle Server通过提供服务器的形式,提供浏览器对文件的访问。

HMR Runtime在开发打包阶段将构建输出文件注入浏览器,更新文件的变化。

图3-15 热更新的大概流程

当启动Webpack-dev-server阶段时,将源码在文件系统进行编译,通过Webpack Compiler编译器打包,并将编译好的文件提交给Bundle Server服务器,Bundle Server即可以服务器的方式供浏览器访问。

当监听到源码发生变化时,经过Webpack Compiler的编译后提交给HMR Server,一般通过websocket实现监听源码的变化,并通过JSON数据的格式通知HMR Runtime,HMR Runtime对bundle.js文件进行改变并刷新浏览器。

相比于watch不能自动刷新浏览器,Webpack-dev-server的优势就明显了。Webpack-dev-server构建的内容会存放在内存中,所以构建速度更快,并且可自动地实现浏览器的自动识别并做出变化,其中Webpack-dev-server需要配合Webpack内置的HotModuleReplacementPlugin插件一起使用。

安装依赖Webpack-dev-server并配置启动项,命令如下:

配置Webpack,其中Webpack-dev-server一般在开发环境中使用,所以需将mode模式设置为development,配置如下:

2)清理构建目录:clean-webpack-plugin

由于每次构建项目前并不会自动地清理目录,会造成输出文件越来越多。这时就得手动清理输出目录的文件。

借助clean-webpack-plugin插件清除构建目录,默认会执行删除output值的输出目录。

安装clean-webpack-plugin插件并配置,命令如下:

在webpack.config.js文件中配置,代码如下:

3.2.4 Webpack进阶

可以通过Webpack插件进行打包内容分析,优化编译速度,减少构建包体积,同时通过接口编写自己的Loader和Plugin插件。

1.项目分析

通过Webpack-bundle-analyzer可以看到项目各模块的大小,对各模块可以按需优化。

该插件通过读取输出文件夹(通常是dist)中的stats.json文件,把该文件可视化展现。便于直观地比较各个包文件的大小,以达到优化性能的目的。

安装Webpack-bundle-analyzer,命令如下:

在webpack.config.js文件中导入Webpack-bundle-analyzer模块,在plugins数组中实例化插件,配置如下:

2.编译阶段提速

编译模块阶段的效率提升,下面的操作都是在Webpack编译阶段实现的。

1)IgnorePlugin:忽略第三方包指定目录

IgnorePlugin是Webpack的内置插件,其作用是忽略第三方包指定目录。

有的依赖包,除了项目所需的模块内容外,还会附带一些多余的模块,例如moment模块会将所有本地化内容和核心功能一起打包。

下面案例通过配置的Webpack-bundle-analyzer来查看使用IgnorePlugin后对moment模块的影响,Webpack的配置如下:

2)DllPlugin和DllReferencePlugin提高构建速度

在使用Webpack进行打包时,对于依赖的第三方库,例如React、Redux等不会修改的依赖,可以让它和自己编写的代码分开打包,这样做的好处是每次更改本地代码文件时,Webpack只需打包项目本身的文件代码,而不会再去编译第三方库,第三方库在第一次打包时只打包一次,以后只要不升级第三方包,Webpack就不会对这些库打包,这样可以快速地提高打包速度,因此为了解决这个问题,DllPlugin和DllReferencePlugin插件就产生了。

DLLPlugin能把第三方库与自己的代码分离开,并且每次文件更改时,它只会打包该项目自身的代码,所以打包速度会更快。

DLLPlugin插件在一个额外独立的Webpack设置中创建一个只有dll的bundle,也就是说,在项目根目录下除了有webpack.config.js文件,还会新建一个webpack.dll.config.js文件。webpack.dll.config.js的作用是除了把所有的第三方库依赖打包到一个bundle的dll文件里面,还会生成一个名为manifest.json的文件。该manifest.json文件的作用是让DllReferencePlugin映射到相关的依赖上去。

DllReferencePlugin插件在webpack.config.js文件中使用,该插件的作用是把刚刚在webpack.dll.config.js文件中打包生成的dll文件引用到需要的预编译的依赖上来。什么意思呢?就是说在webpack.dll.config.js文件中打包后会生成vendor.dll.js文件和vendormanifest.json文件,vendor.dll.js文件包含所有的第三方库文件,vendor-manifest.json文件会包含所有库代码的一个索引,当在使用webpack.config.js文件打包DllReferencePlugin插件时,会使用该DllReferencePlugin插件读取vendor-manifest.json文件,看一看是否有该第三方库。vendor-manifest.json文件只有一个第三方库的一个映射。

第一次使用webpack.dll.config.js文件时会对第三方库打包,打包完成后就不会再打包它了,然后每次运行webpack.config.js文件时,都会打包项目中本身的文件代码,当需要使用第三方依赖时,会使用DllReferencePlugin插件去读取第三方依赖库,所以说它的打包速度会得到一个很大的提升。

在项目中使用DllPlugin和DllReferencePlugin,其使用步骤如下。

在使用之前,首先看一下项目现在的整个目录架构,架构如下:

因此需要在项目根目录下创建一个webpack.dll.config.js文件,配置的代码如下:

切换到webpack.config.js配置,引入文件,代码如下:

然后在插件中使用该插件,代码如下:

最后一步就是构建代码了,先生成第三方库文件,运行命令如下:

3.优化构建体积
1)摇树优化(Tree Shaking)

Webpack 4.0后通过开启mode:production即可开启摇树优化功能。

Tree Shaking摇掉代码中未引用部分(dead-code),production模式下会自动在使用Tree Shaking打包时除去未引用的代码,其作用是优化项目代码。

2)删除无效的CSS

PurgeCSS是一个能够通过字符串对比来决定移除不需要的CSS的工具。PurgeCSS通过分析内容和CSS文件,首先将CSS文件中使用的选择器与内容文件中的选择器进行匹配,然后会从CSS中删除未使用的选择器,从而生成更小的CSS文件。对于PurgeCSS的配置因项目的不同而不同,它不仅可以作为Webpack的插件,还可以作为postcss的插件。一般与glob、glob-all配合使用。

安装purgecss-webpack-plugin,命令如下:

配置Webpack,配置如下:

4.编写自定义Loader

Loader是一种打包的方案。可以定义一种规则,告诉Webpack当它遇到某种格式的文件后,去求助相应的Loader。有些时候需要一些特殊的处理方式,这就需要自定义一些Loader。

一个简单的Loader通过编写一个简单的JavaScript模块,并将模块导出即可。接收一个source当前源码,并返回新的源码即可。

Loader分为同步Loader和异步Loader。如果单个处理,则可以直接使用同步模式处理后直接返回,但如果需要多个处理,就必须在异步Loader中使用this.async()告诉当前的上下文context,这是一个异步的Loader,需要Loader Runner等待异步处理的结果,在异步处理完之后再调用this.callback()传递给下一个Loader执行。

编写同步Loader,代码如下:

对于异步Loader,使用this.async获取callback()函数。

编写异步Loader,代码如下:

执行构建,Webpack会等待一秒,然后输出构建内容,通过Node.js执行构建后的文件,输出如下:

1)编写一个简单的Loader

Webpack默认只能识别JavaScript模块,在实际项目中会有.css.less.scss.txt.jpg.vue等文件,这些都是Webpack无法直接识别打包的文件,都需要使用Loader来直接或者间接地进行转换成可以供Webpack识别的JavaScript文件。

创建一个简单的Loader,命名为hello-loader。hello-loader的作用是让Webpack识别.hello扩展名的模块,并进行转换打包。

第1步:创建目录loader-demo。创建文件如图3-16所示,这里创建一个test.hello自定义文件,hello-loader需要能够识别该模块,并进行打包。

图3-16 自定义loader目录

第2步:编写hello-loader,代码如下:

第3步:编写main.js打包入口代码。main.js是打包的入口文件,因为要尽可能简单,所以这个文件只做一件事,即加载.hello文件,并显示到页面,代码如下:

第4步:编写index.html文件。main.js文件的代码逻辑很简单,就是获取页面中的一个id为app的元素,并将.hello中的值显示在元素中,代码如下:

第5步:这里暂时不对test.hello文件进行特殊处理,所以test.hello文件里面的代码可以随便写,输出如下:

第6步:在webpack.config.js文件中配置规则,完整的打包配置如下:

第7步:编译打包。在项目目录下直接执行webpack命令,如图3-17所示,打包的文件输出在output目录下,输出如下:

图3-17 编译自定义Loader

第8步:在浏览器中运行index.html文件,效果如图3-18所示。

图3-18 Loader的运行效果

2)编写一个自定义的less-loader

下面自定义一个less-loader和style-loader。将编写的less经过这两个Loader处理之后使用style标签插入页面的head标签内。

为了测试自定义Loader,创建测试目录,结构如图3-19所示。

图3-19 自定义style-loadert和less-loader

第1步:定义一个.less文件,这里命名为index.less,代码如下:

第2步:新建一个less-loader.js文件,用于处理.less文件,代码如下:

这里处理的业务很简单,就是获得原始的.less文件的内容,将.less文件通过less.render编译为.css文件并传递给下一个Loader即可。

注意: 这里需要安装less模块,命令为npm install less-D。

关键的代码如下:

this.async告诉当前上下文这是一个异步的Loader,需要loader runner等待less.render异步处理的结果。

less.render接收less源码,并返回一个promise,在返回的promise中等待less.render处理完.less文件之后,使用callback将处理的结果返给下一个Loader。

第3步:新建一个style-loader.js文件,代码如下:

使用JSON.stringify将接收到的.css文件变为一个可编辑字符串。process.cwd文件用于获取当前工程根目录。this.resource通过this上的该属性可获取当前处理的源文件的绝对路径。

之后创建一个style标签,将编译完之后的.css代码插入style标签内,并自定义一个data-origin属性,用来标记当前文件在工程中的相对路径。

最后返回一个可执行的JS字符串给bundle.js。

第4步:新建index.html文件,引用bundle.js文件,代码如下:

第5步:编译并运行。在项目目录下直接执行webpack命令,如图3-20所示,打包的文件输出在dist目录下。

图3-20 编译输出

通过浏览器查看效果,如图3-21所示。

5.编写自定义插件Plugin

插件件随Webpack构建的初始化到最后文件生成的整个生命周期,插件的目的是解决Loader无法实现的其他事情。另外,插件没有像Loader那样的独立运行环境,所以插件只能在Webpack里面运行。

图3-21 使用自定义style-loader的效果

Webpack通过Plugin机制让其更加灵活,以适应各种应用场景。在Webpack运行的生命周期中会广播出许多事件,Plugin可以监听这些事件,在合适的时机通过Webpack提供的API改变输出结果。

(1)创建一个最基础的Plugin,代码如下:

以上就是一个最基本的Plugin结构。Webpack Plugin最为核心的便是这个apply()方法。

Webpack执行时,首先会生成插件的实例对象,之后会调用插件上的apply()方法,并将compiler对象(Webpack实例对象,包含Webpack的各种配置信息等)作为参数传递给apply()方法。

之后便可以在apply()方法中使用compiler对象去监听Webpack在不同时刻触发的各种事件进行想要的操作了。

接下来看一个简单的示例,代码如下:

定义Webpack配置文件,代码如下:

编译后输出的结果如下:

首先Webpack会按顺序实例化plugin对象,之后再依次调用plugin对象上的apply()方法。

也就是对应输出:plugin1初始化、plugin2初始化、plugin1 apply被调用、plugin2 apply被调用。

Webpack在运行过程中会触发各种事件,而在apply()方法中能接收一个compiler对象,可以通过这个对象监听到Webpack触发各种事件的时刻,然后执行对应的操作函数。这套机制类似于Node.js的EventEmitter,总体来讲就是一个发布订阅模式。

在compiler.hooks中定义了各式各样的事件钩子,这些钩子会在不同的时机被执行,而上面代码中的compiler.hooks.emit和compiler.hooks.afterPlugin这两个生命周期钩子,分别对应了设置完初始插件及生成资源到output目录之前这两个时间节点,afterPlugin是在emit之前被触发的,所以输出的顺序更靠前。

(2)编写一个输出所有打包目录文件列表的插件,这个插件在构建完相关的文件后,会输出一个记录所有构建文件名的list.md文件,代码如下:

在Webpack的emit事件被触发之后,插件会执行指定的工作,并将包含了编译生成资源的compilation作为参数传入函数。可以通过compilation.assets获得生成的文件,并获取其中的filename值。

(3)Compiler和Compilation。上面在开发Plugin时最常用的两个对象就是Compiler和Compilation,它们是Plugin和Webpack之间的桥梁。

Compiler和Compilation的含义如下:

■ Compiler对象包含了Webpack环境所有的配置信息,包含options、loaders、plugins信息,这个对象在Webpack启动时被实例化,它是全局唯一的,可以简单地把它理解为Webpack实例;

■ Compilation对象包含了当前的模块资源、编译生成资源、变化的文件等。当Webpack以开发模式运行时,每当检测到一个文件变化,一次新的Compilation将被创建。Compilation对象也提供了很多事件回调供插件进行扩展。通过Compilation也能读取Compiler对象。

Compiler和Compilation的区别在于:Compiler代表整个Webpack从启动到关闭的生命周期,而Compilation只代表一次新的编译。 3L+FXhXjS/uorsX3wgdpBZcJgHeIJP1gV9uM56REMzAUuMifpvpO/4aVA3yQZsnZ

点击中间区域
呼出菜单
上一章
目录
下一章
×