刚刚,发布Webpack中级教程系列

webpack是什么?

webpack是前端最火的打包工具,是大前端自动化工厂的重要组成部分。

webpack关于HTML的部分

- 对于浏览器而言,html文件是用户访问的入口点,也是所有资源的挂载点,所有资源都是通过html中的标记来进行引用的。

- 在webpack的构建世界里,html只是一个展示板,而entry参数中指定的javascript入口文件才是真正在构建过程中管理和调度资源的挂载点,html文件中最终展示的内容,都是webpack在加工并为所有资源打好标记以后传递给它的,业界将这种有别与浏览器的模式称之为“webpack的逆向注入”

- 前端项目可以大致分为 单页面应用多页面应用

- html文件主要作为访问入口文件,是<style> 样式标签和<script>脚本标签的挂载点

打包中需要注意:

- 第一,个性化内容填充,如页面标题,描述,关键字;

- 第二,多余空格删除,连续多个空白字符的合并;

- 第三,代码压缩,多余空白字符的合并;

- 第四,去除注解

入口html文件的处理

- 单页面应用打包

入口html文件的处理使用 html-webpack-plugin 插件来设置一定的配置参数。

webpack.config.js配置

index.html 模板文件(构建生成的入口页面是以此为模板的):

多页面应用打包

项目中有多个页面,考虑两个基本问题:

- 如何自动生成多个页面

- 如果引用中存在公共的模块,怎么样才能提取公共模块

> 多页面应用的基本结构理解起来并不复杂,可以将其看做是多个单页面应用的组合

- entry参数需要配置多个依赖入口文件

html文件则需要分别引用对应的入口文件并生成对应的访问入口:

可以看到在生成html文件时已经为其单独引用了chunks数组中指定的模块,这使得对应的页面生成时只依赖自己需要的脚本。

html-webpack-plugin插件是依赖于html-loader而工作的,当你显式使用/\.html$/作为规则来筛选文件时,同样会选择到作为入口文件的html资源,从而造成冲突报错。

webpack中关于CSS的部分

CSS文件的处理,需要处理的基本问题:

- 预编译语言转换

- 样式文件挂载方式选择

- 代码优化(合并以及压缩)

- 去除或保留指定格式的注解

- 资源定位路径的转换

- 响应式布局单位转换

- 模块化

- 处理浏览器兼容

> 解决方案

- 旧的解决方案预编译语言 + 命名方法论

- 新的解决方案预编译语言 + 构建工具 + BEM + ACSS全局样式+CSSModule组件样式+ POSTCSS

旧:例如编写简单的@mixin px2rem( )函数来将开发中使用的px单位转换为rem单位,达到移动端自适应的目的,或是编写一些处理兼容性的函数来处理浏览器兼容性。

新:构建工具可以通过自动化检测将预编译语言转换为CSS,基于现代化构建工具的CSS-Module功能,可以通过特定的语法解决CSS模块化的问题,而基于POSTCSS实现的autoprefixer插件,可以依据CanIUse网站提供的浏览器支持度数据实现代码的跨浏览器前缀自动补齐。

常用的插件:

- style-loader——将处理结束的CSS代码存储在js中,运行时嵌入<style>后挂载至html页面上

- css-loader——加载器,使webpack可以识别css模块

- postcss-loader——加载器

- sass-loader——加载器,使webpack可以识别scss/sass文件,默认使用node-sass进行编译

- mini-css-extract-plugin——插件,4.0版本启用的插件,替代原extract-text-webpack-plugin插件,将处理后的CSS代码提取为独立的CSS文件

- optimize-css-assets-webpack-plugin——插件,实现CSS代码压缩

- autoprefixer——自动化添加跨浏览器兼容前缀

使用SCSS作为预编译语言

可以看到转换后的结果:

代码压缩等优化功能在 默认当mode: 'production'时有效

使用CSS-Modules

CSS Module在CSS中使用类选择器,其基本原理是将CSS代码中的样式名替换为哈希值,并建立一个json对照表,在js文件中对于属性名选择器的使用均被替换为哈希字符串,以此来解决CSS模块化的问题。

在webpack中使用CSS Modules功能非常简单,只需要在css-loader的配置参数中设置:{modules:true}即可激活模块化功能。

开启模块化功能后再进行打包,可以看到同样的main.css文件变成了如下样子:

进一步了解 Css-Process-Chain

webpack中关于Assets部分

Assets资源的基本处理需求

Assets,指项目中被引用的资源,通常为各种格式的图片和字体文件,当然也可能包含各式各样其他扩展名的文件(.json,.xml等),常见的图片和文字资源的处理包括:

- 体积压缩

- 雪碧图合并及引用修正

- 资源的引用路径自动替换

webpack处理引用资源

资源打标

webpack通过file-loader处理资源文件,它会将rules规则命中的资源文件按照配置的信息(路径,名称等)输出到指定目录,并返回其资源定位地址(输出路径,用于生产环境的publicPath路径),默认的输出名是以原文件内容计算的MD5 Hash命名的。

引用优化

构建工具通过url-loader来优化项目中对于资源的引用路径,并设定大小限制,当资源的体积小于limit时将其直接进行Base64转换后嵌入引用文件,体积大于limit时可通过fallback参数指定的loader进行处理。

sprites雪碧图合成

雪碧图合成,听起来是一个显得略高端的知识点,但它并不是必须进行的,任何一种技术都有其使用场景。有的场景下需要将图片资源合并为独立的雪碧图而减少http请求的次数,有的时候或许通过url-loader直接将其嵌入文档就可以。矢量图在不同场景下的处理方式也不相同。

采用url-loader + file-loader作为资源处理的一般通用方案

位图处理

矢量图处理

开发中常用的矢量图为svg格式,既可以使用inline-svg-loader进行资源嵌入,也可以使用svg-sprite-loader将矢量图资源合并为雪碧图,具体采用哪种方案,需要由项目的实际情况来判断。

图片压缩

- 图片资源是可以以清晰度为量化参考进行体积压缩的‍

webpack中关于JavaScript和splitChunk

javascript之所以需要打包合并,是因为模块化开发的存在。开发阶段我们需要将js文件分开写在很多零碎的文件中,方便调试和修改,但如果就这样上线,那首页的http请求数量将直接爆炸。同一个项目,别人2-3个请求就拿到了需要的文件,而你的可能需要20-30个,结果就不用多说了。

但是合并脚本可不是“把所有的碎片文件都拷贝到一个js文件里”这样就能解决的,不仅要解决命名空间冲突的问题,还需要兼容不同的模块化方案,更别提根据模块之间复杂的依赖关系来手动确定模块的加载顺序了,所以利用自动化工具来将开发阶段的js脚本碎片进行合并和优化是非常有必要的。

JS文件的打包:

- 代码编译(TS或ES6代码的编译)

- 脚本合并

- 公共模块识别

- 代码分割

- 代码压缩混淆

使用webpack处理js文件

使用babel转换ES6+语法

babelES6语法的转换工具

脚本合并

- 模块管理文件合并这两个功能是webpack最初设计的主要用途

- webpack默认支持的是CommonJs规范

公共模块识别

代码分割

为什么要进行代码分割?

代码分割最基本的任务是分离出第三方依赖库,因为第三方库的内容可能很久都不会变动,所以用来标记变化的摘要哈希contentHash也很久不变,这也就意味着我们可以利用本地缓存来避免没有必要的重复打包,并利用浏览器缓存避免冗余的客户端加载。另外当项目发布新版本时,如果第三方依赖的contentHash没有变化,就可以使用客户端原来的缓存文件(通用的做法一般是给静态资源请求设置一个很大的max-age),提升访问速度。另外一些场景中,代码分割也可以提供对脚本在整个加载周期内的加载时机的控制能力。

代码分割的使用场景

举个很常见的例子,比如你在做一个数据可视化类型的网站,引用到了百度的Echarts作为第三方库来渲染图表,如果你将自己的代码和Echarts打包在一起生成一个main.bundle.js文件,这样的结果就是在一个网速欠佳的环境下打开你的网站时,用户可能需要面对很长时间的白屏,你很快就会想到将Echarts从主文件中剥离出来,让体积较小的主文件先在界面上渲染出一些动画或是提示信息,然后再去加载Echarts,而分离出的Echarts也可以从速度更快的CDN节点获取,如果加载某个体积庞大的库,你也可以选择使用懒加载的方案,将脚本的下载时机延迟到用户真正使用对应的功能之前。这就是一种人工的代码分割。

从上面的例子整个的生命周期来看,我们将原本一次就可以加载完的脚本拆分为了两次,这无疑会加重服务端的性能开销,毕竟建立TCP连接是一种开销很大的操作,但这样做却可以换来对渲染节奏的控制和用户体验的提升异步模块懒加载模块从宏观上来讲实际上都属于代码分割的范畴。code splitting最极端的状况其实就是拆分成打包前的原貌,也就是源码直接上线

代码分割的本质

源代码直接上线

代码分割:优点是过程可控,可减少首屏空白时长;缺点是http请求多,性能开销大。

Code Splitting与平衡(请求可合并的脚本;某较大的第三方库;工具型第三方库;某个按钮点击后加载。

客户端-》缓存命中率高-》性能开销和用户体验的平衡

打包为一个脚本上线(main.bundle.js)

优点:一把搞完,省事,服务器压力小;缺点:时间长,页面空白期长

代码混淆压缩

- webpack4中已经内置了UglifyJs插件,当打包模式参数mode设置为production时就会自动开启

- babel的插件中也能提供代码压缩的处理

splitChunks技术

参数配置

代码分割实例

单页面应用

单页面应用只有一个入口文件,splitChunks的主要作用是将引用的第三方库拆分出来。从下面的分包结果就可以看出,node_modules中的第三方引用被分离了出来,放在了vendors-main.[hash].js中。

webpack --config webpack.spa.config.js

多页面应用

源码的依赖关系为:

代码语言:javascript
复制
entryA.js: vue vuex component10k
entryB.js: vue axios component10k
entryC.js: vue vuex axios component10k

splitChunks提供了更精确的分割策略,但是似乎无法直接通过html-webpack-plugin配置参数来动态解决分割后代码的注入问题,因为分包名称是不确定的。这个场景在使用chunks:'async'默认配置时是不存在的,因为异步模块的引用代码是不需要以<script>标签的形式注入html文件的。

chunks配置项设置为allinitial时,就会有问题,例如上面示例中,通过在html-webpack-plugin中配置excludeChunks可以去除page和about这两个chunk,但是却无法提前排除vendors-about-page这个chunk,因为打包前无法知道是否会生成这样一个chunk。

webpack中的关于Module

大前端模块化

CMD规范:引用Sea.js;浏览器

Webpack可识别:

UMD规范:AMD规范(引用Require.js);浏览器

CommonJs规范:原生支持,node

ESHarmony规范:支持度暂不完善,统一JS全环境

- 脚本合并是基于模块化规范的

webpack与模块化

webpack默认支持的是CommonJs规范,毕竟它是nodejs支持的模块管理方式,而没有node哪来的webpack。但同时为了扩展其使用场景,webpack在版本迭代中也加入了对ES harmony规范和AMD规范的兼容。

webpack如何识别CommonJs模块

webpack如何识别ES Harmony模块

webpack是一个JS代码模块化的打包工具。

资料官网:www.webpackjs.com