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+语法
babel
是ES6
语法的转换工具
脚本合并
- 模块管理和文件合并这两个功能是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
多页面应用
源码的依赖关系为:
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
配置项设置为all
或initial
时,就会有问题,例如上面示例中,通过在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