Webpack 模块化打包优化
一、 早期的前端打包工具有很多,主要是Grunt和Gulp等,他们早期的打包思路是通过一个配置文件告诉工具,哪些文件应该被绑定在一起输出到一个文件中,这样可以减少了http请求,还可以做一些压缩。
而webpack的核心优势在于它从入口文件出发,递归构建依赖关系图。通过这样的依赖梳理,webpack打包出的bundle不会包含重复或未使用的模块,实现了按需打包,极大的减少了冗余。
二、webpack做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(less、Sass/Scss,TypeScript,ES6+等),并将其转换和打包为合适的格式或版本供浏览器使用。
三、 webpack最核心的理念是
- 一切皆模块:JS,CSS,Image,Html,Font
- 按需加载:默认会打包出一个bundle.js,可能很大,所以首屏速度很慢,所以webpack还可以使用一些特性对代码分割出多个“bundle”小文件,还可以实现按需异步加载等。
四、本文主要记录webpack优化,主要有两个大的维度,一是打包速度,二是打包出来的体积。
打包速度
首先可以通过一些webpack自带的stats统计分析,还可以通过speed-measure-webpack-plugin专业插件进行分析,他可以分析各个plugin和loader具体耗时。
也可以通过wbpack-bundle-analyzer工具启动一个图形化工具来进行标注,面积越大的区域,说明体积占用越多,比如很多项目引入全量的babel-ployfill,往往会占很大的面积,可通过user-agent去请求在线服务里的包,不同版本的浏览器可能需要的包大小不同。
1. 首先应该使用更高版本的webpack,如现在是V4,它比V3版本提高了50%-98%的速度。
- nodejs版本更高,使用了更高版本的V8引擎
- ES6新的更优秀的语法,for of代替foreach, includes代替index of,map set代替 object, 字符串方式代替正则,多进程打包V4的thread-loader代替happy-pack
- mode:production方式,默认自动内置了10来中优化插件。
2. 充分利用缓存Cache
- babel-loader,加入参数Cache-Directory=true
- terser-webpack-plugin,配置cache:true,V3中使用uglifyJS-webpack-plugin(它也可以开启并行压缩,也有并行压缩的版本)压缩,但V4中不推荐使用了。
- hard-source-webpack-plugin,对模块转换缓存
他们都会在node_modules目录下.cache文件夹来缓存结果,这样就加快第二次打包速度。
3. 缩小构建目标,减少对JS/JSX的分析,减少文件的搜索范围。
在loader中使用include、exclude来指定只分析某些目录,或者不分析某些目录
webpack4有一个resolve节点,比如alias,可以直接告诉webpack某些第三方包(react,react-dom...)从哪里去获取,不用一层一层的遍历查找, extendsion,指定为JS,mainField指定为 main。
4. 并行打包/压缩
v3中使用的HappyPackPlugin(作者已经不维护升级了),ParallelUglifyJS并行压缩(对ES6支持不好),因此V4中推荐使用thread-loader(多进程)和terser-webpack-plugin(并行压缩)
5. DllPlugin和DLLReferencePlugin
官方维护的两个插件,他们支持分离基础包,提前把一些第三方基础架构包分离出来,比如生成一个vendor.js, 放到html模块中添加相对路径引用,同时也可以引用放到CDN缓存中的包。
打包体积优化
1. mode:production
生产模式会默认使用10来个优化插件,比如TreeShaking(对代码静态分析,把没用的代码除去,减少体积),如没用使用的JS方法,变量,返回值,if(false){ xxx }语句等筛除,他还可以通过scop-hoisting减少闭包代码的生成。
css需要通过uncss和purifycssplugin做TreeShaking。
2. 图片压缩,会对图片再次压缩,这里和(url-loader)小图片base64编码内嵌到源文件中方式不同。
image-webpack-loader。
使用webp格式的图片,使用合适尺寸的缩略小图,工具(脚本)压缩tinypng
3. ployfill在线方法, 它很大,可以使用在线的CDN服务,它是一个在线服务器,根据user-agent只下载当前浏览器不支持的特性,相对包很小。
4. 压缩,出掉空格,注释,换行符,长方法名混淆压缩为短方法名
mini-css-extract-plugin,parallelUglifyJSPlugin, terser-webpack-plugin
5. 代码分割
- 首屏只加载首屏需要的JS
- 按需动态import,这个需要babel插件(babel-plugin-syntax-dynamic-import),可以把一些第三方库,比如pdf,使用React.lazy和Suspense进行组件延迟加载,这里使用的是promise技术; 还可以使用react-loader等技术。
- 使用splitChunksPlugin,对调用n次的代码,分成一个公共文件,webpack4中,它被内置放到optimization节点中进行配置。
- entry多入口,多页面,SEO更好,页面解耦。
- require.ensure(), commonJS中使用等。
webpack其他碎片知识
loader/plugin
babel-loader,ts-loader
less/scss-loader(less/scss转换为css) -> css-loader(css转换为CommonJSObject) -> style-loader(内嵌到Html头部的style区域中),注意他们的顺序需要正确
file-loader, 处理图片(jpg,jpeg,png,gif),字体等
url-loader,可以把小文件内置到元素中,不用多一个请求。
postcss-loader + autoprefixer自动给css加前缀(-moz-,-webkit-,-ms-,-o-)
cleanwebpackplugin, 先清理生成目录
raw-loader,减少http请求,比如一些初始化的css使用<script>require('raw-loader')内联进来,避免闪动。
WDS (webpack-dev-server)
主要模块是HMR-Server和HMR Runtime(浏览器端),HMR Server增量编译,得出差异,通知HMR-Runtime,HMR-Runtime通过jsonp方式传输变化的数据,再对浏览器局部刷新。
文件指纹,版本管理
hash,一个项目一个
chunkHash,一个入口一个,一个页面入口一个,页面的css,js共有这个指纹
contentHash,一个文件一个,文件内容不变,它的值不变。
JS用chunkHash,如在output节点中的文件名使用:[name][chunkhash:8].js
css用contentHash,在miniCssExtractPlugin配置,fileName:[name][contenthash:8].css
注意:图片文件的指纹,他与前面几个不同,它的hash是它自身的md5值,所以options:{name:image/[name][hash:8].[ext]}
sourceMap
eval: 使用eval包裹模块代码
souce map:分离的一个个map文件
cheap: 不包括列信息
inline: JS和source map文件在一起
module,包含loader的source map
他们还可以排列组合出10多种类别,production模式下,设置为none
服务端渲染 SSR,同构应用
第一个请求就返回了首屏必要的data,由后端(Node)计算出Data后,renderToString插入(替换)到html模块的placeholder占位符中,再返回给客户端直接展示,常把数据挂在window.__initData__变量中。
good:减少请求数量,加快显示,减少首屏白屏时间;SEO优化,有利于爬虫分析;
bad:window,document等很多对象不能识别,需要hack处理,css不生效,需要使用css-in-jss等方式;fetch,ajax需要改成服务端兼容的axios,isomorphic-fetch;mode不能直接使用production,因为它会吃掉占位符;