5.2.1 优化打包构建速度
5.2.1.1 oneOf
oneOf:匹配到 loader 后就不再向后进行匹配,优化生产环境的打包构建速度
module: {rules: [{// js 语法检查test: /\.js$/,exclude: /node_modules/,// 优先执行enforce: 'pre',loader: 'eslint-loader',options: {fix: true}},{// oneOf 优化生产环境的打包构建速度// 以下loader只会匹配一个(匹配到了后就不会再往下匹配了)// 注意:不能有两个配置处理同一种类型文件(所以把eslint-loader提取出去放外面,新版本不用eslint-loader)oneOf: [{test: /\.css$/,use: [...commonCssLoader]},{test: /\.less$/,use: [...commonCssLoader, 'less-loader']},{// js 兼容性处理test: /\.js$/,exclude: /node_modules/,loader: 'babel-loader',options: {presets: [['@babel/preset-env',{useBuiltIns: 'usage',corejs: {version: 3},targets: {chrome: '60',firefox: '50'}}]]}},{test: /\.(jpg|png|gif)/,loader: 'url-loader',options: {limit: 8 * 1024,name: '[hash:10].[ext]',outputPath: 'imgs',esModule: false}},{test: /\.html$/,loader: 'html-loader'},{exclude: /\.(js|css|less|html|jpg|png|gif)/,loader: 'file-loader',options: {outputPath: 'media'}}]}]},
5.2.1.2 缓存
获取资源是比较耗费时间的,这就是为什么浏览器使用一种名为 缓存 的技术。可以通过命中缓存,以降低网络流量,使网站加载速度更快,然而, 如果我们在部署新版本时不更改资源的文件名,浏览器可能会认为它没有被更新,就 会使用它的缓存版本。
模拟生产环境下缓存问题实现过程:
- server.js(与webpack.config同级) node构建服务器并启动
- webpack打包
- 修改对应文件并刷新网页查看结果
效果: 修改css文件后刷新, js文件依旧时缓存状态(size),css文件命名被修改重新加载
实现过程如下
1.babel 缓存:类似 HMR,将 babel 处理后的资源缓存起来(哪里的 js 改变就更新哪里,其他 js 还是用之前缓存的资源),让第二次打包构建速度更快
实现代码: 给babel-loder配置 cacheDirectory: true
{// js 兼容性处理test: /\.js$/,exclude: /node_modules/,loader: 'babel-loader',options: {presets: [['@babel/preset-env',{useBuiltIns: 'usage',corejs: {version: 3},targets: {chrome: '60',firefox: '50'}}]],// 开启babel缓存// 第二次构建时,会读取之前的缓存cacheDirectory: true}}
这样结果是文件名不变,就永远不会重新请求,而是再次用之前缓存的资源, 想办法修改文件名方法如下
2.修改文件名以达到文件资源缓存
plugins: [new MiniCssExtractPlugin({// 对输出的css文件进行重命名//filename: 'css/built.[hash:10].css' //一直变化一直重加载//filename: 'css/built.[chunkhash:10].css' //一直变化一直重加载filename: 'css/built.[contenthash:10].css' //对应修改变化加载})]
1.hash: 每次 wepack 打包时会生成一个唯一的 hash 值。
问题:重新打包,所有文件的 hsah 值都改变,会导致所有缓存失效。(可能只改动了一个文件)
2.chunkhash:根据 chunk 生成的 hash 值(来自同一个入口则共享同一个chunk)。来源于同一个 chunk的 hash 值一样
问题:js 和 css 来自同一个chunk(打包时同享一个入口),hash 值是一样的(因为 css-loader 会将 css 文件加载到 js 中,所以同属于一个chunk)
3.contenthash: 根据文件的内容生成 hash 值,文件内容不同则不同。不同文件 hash 值一定不一样(文件内容修改,文件名里的 hash 才会改变)
修改 css 文件内容,打包后的 css 文件名 hash 值就改变,而 js 文件没有改变 hash 值就不变,这样 css 和 js 缓存就会分开判断要不要重新请求资源 —> 让代码上线运行缓存更好使用
3.第三方库缓存
将第三方库(library)(例如 lodash )提取到单独的 vendor chunk 文件中,是比较 推荐的做法,这是因为,它们很少像本地的源代码那样频繁修改。因此通过实现以上 步骤,利用 client 的长效缓存机制,命中缓存来消除请求,并减少向 server 获取资 源,同时还能保证 client 代码和 server 代码版本一致。
可能是4.0写法
output: {//都放到js目录下filename: 'js/[name].[contenthash:10].js',path: resolve(__dirname, 'build'),chunkFilename: 'js/[name].[contenthash:10]_chunk.js' // 指定非入口文件的其他chunk的名字加_chunk},optimization: {splitChunks: {chunks: 'all', //将 node_modules 中的代码单独打包//以下都是splitChunks默认配置,可以不写chunks: 'all'即可cacheGroups: { // 分割chunk的组vendors: {// node_modules中的文件会被打包到vendors组的chunk中,--> vendors~xxx.js// 满足上面的公共规则,大小超过30kb、至少被引用一次test: /[\\/]node_modules[\\/]/,// 优先级priority: -10},}},},mode: 'development',
5.0写法?
optimization: {splitChunks: {cacheGroups: {vendor: {test: /[\\/]node_modules[\\/]/,name: 'vendors',chunks: 'all',},},}
5.2.1.3 多进程打包
多进程打包(提升打包性能):某个任务消耗时间较长会卡顿,多进程可以同一时间干多件事,效率更高。
优点是提升打包速度,缺点是每个进程的开启和交流都会有开销(babel-loader消耗时间最久,所以使用thread-loader针对其进行优化)
不要使用太多的 worker,因为 Node.js 的 runtime 和 loader 都有启动开销。 最小化 worker 和 main process(主进程) 之间的模块传输。进程间通讯(IPC, inter process communication)是非常消耗资源的。
{test: /\.js$/,exclude: /node_modules/,use: [/*thread-loader会对其后面的loader(这里是babel-loader)开启多进程打包。进程启动大概为600ms,进程通信也有开销。(启动的开销比较昂贵,不要滥用)只有工作消耗时间比较长,才需要多进程打包*/{loader: 'thread-loader',options: {workers: 2 // 进程2个}},{loader: 'babel-loader',options: {presets: [['@babel/preset-env',{useBuiltIns: 'usage',corejs: { version: 3 },targets: {chrome: '60',firefox: '50'}}]],// 开启babel缓存// 第二次构建时,会读取之前的缓存cacheDirectory: true}}]},
5.2.1.4 externals 打包时排除包
externals:让某些库不打包,通过 cdn 引入(webpack5.0可以设置自动引入)
webpack.config.js
externals: {// 拒绝jQuery被打包进来(通过cdn引入,速度会快一些)// 忽略的库名 -- npm包名('jQuery'暴露的别名 import 引入时的)jquery: 'jQuery'}
此时打包后JS中就没有JQ,需要在 手动在index.html 中通过 cdn 引入:
<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
配置自动引入到打包后的JS中
webpack5.0
//引入类型externalsType:'script',externals: {jquery: ['https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js','jQuery']}
5.2.1.5 dll
dll:让某些库单独打包,后直接引入到 build 中。可以在 code split 分割出 node_modules 后再用 dll 更细的分割,优化代码运行的性能。
可以配合code split事先将node_modules的包单独打包再单独引入,而不是将node_modules都打包成一个再引入.
webpack.dll.js 配置:(将 jquery 单独打包)
执行过程:
- 配置webpack.dll.js文件(该文件用于配置单独大把的库)如下 ```javascript / node_modules的库会打包到一起,但是很多库的时候打包输出的js文件就太大了 使用dll技术,对某些库(第三方库:jquery、react、vue…)进行单独打包 当运行webpack时,默认查找webpack.config.js配置文件 需求:需要运行webpack.dll.js文件 —> webpack —config webpack.dll.js(运行这个指令表示以这个配置文件打包) / const { resolve } = require(‘path’); const webpack = require(‘webpack’);
module.exports = { entry: { // 最终打包生成的[name] —> jquery // [‘jquery] —> 要打包的库是jquery jquery: [‘jquery’] }, output: { // 输出出口指定 filename: ‘[name].js’, // name就是jquery path: resolve(dirname, ‘dll’), // 打包到dll目录下 library: ‘[name][hash]’, // 打包的库里面向外暴露出去的内容叫什么名字 }, plugins: [ // 打包生成一个manifest.json —> 提供jquery的映射关系(告诉webpack:jquery之后不需要再打包和暴露内容的名称) new webpack.DllPlugin({ name: ‘[name][hash]’, // 映射库的暴露的内容名称 path: resolve(dirname, ‘dll/manifest.json’) // 输出文件路径 }) ], mode: ‘production’ };
2. 执行命令 webpack --config webpack.dll.js 此时生成dll目录及目录下会生成jquery+[hash].js 和 manifest.json此文件是一些规则和映射2. 修改webpack.config.js 配置,此时使用webpack打包则不会打包jquery到build中。webpack.config.js 配置:(告诉 webpack 不需要再打包 jquery(webpack插件),并将之前打包好的 jquery 跟其他打包好的资源一同输出到 build 目录下(add-asset-html-webpack-plugin插件))```javascript// 引入插件const webpack = require('webpack');const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');// plugins中配置:plugins: [new HtmlWebpackPlugin({template: './src/index.html'}),// 1.告诉webpack哪些库不参与打包,同时使用时的名称也得变new webpack.DllReferencePlugin({manifest: resolve(__dirname, 'dll/manifest.json')}),// 2.将某个文件打包输出到build目录下,并在html中自动引入该资源new AddAssetHtmlWebpackPlugin({filepath: resolve(__dirname, 'dll/jquery.js')})],
5.2.1.6 externals和dll区别
5.2.2.2 tree shaking(树摇)
tree shaking:去除无用代码(例如import {a,b} from xx 而a下文用到而时b并没有用到属于无用代码)
前提:
1. 必须使用 ES6 模块化 (import导入 , export导出)
2. 开启 production 环境 (使用该环境默认启动, 这样就自动会把无用代码去掉)
作用:减少代码体积
weboack4.0
哪些文件有副作用的情况下打包代码的风 险,因此webpack4默认地将所有代码视为有副作用。这可以保护你免于删除必要的文件,但这意味着 Webpack 的默认行为实际上是不进行 tree-shaking。
package.json 中配置:
当开启"sideEffects": false表示所有代码都没有副作用(都可以进行 tree shaking)
这样会导致的问题:可能会把 css / @babel/polyfill 文件干掉(因为 css文件是仅导入 被认为未使用)
所以可以配置:"sideEffects": ["*.css", "*.less"]不会对css/less文件tree shaking处理
webpack5.0
开启mode: 'production'即默认tree shaking,可以配合sideEffects更灵活配置。
- webpack4 曾经不进行对 CommonJs 导出和 require() 调用时的导出使用分析。
- webpack 5 增加了对一些 CommonJs 构造的支持,允许消除未使用的 CommonJs 导出,并从 require() 调用中跟踪引用的导出名称。
5.2.2.3 code split(代码分割)
代码分割。将打包输出的一个大的 bundle.js 文件拆分成多个小文件,这样可以并行加载多个文件,比加载一个文件更快。
1.多入口拆分
输出结果 :entry: {// 多入口:有一个入口,最终输出就有一个bundleindex: './src/js/index.js',test: './src/js/test.js'},output: {//多入口不能只有一个出口 所有这里修改成不同文件名的出口文件filename: 'js/[name].[contenthash:10].js',path: resolve(__dirname, 'build')},

- 多入口下 - 公共依赖抽离为单独chunk
如果多个文件都引用jquery打包时每个js都会引入JQ , 这样就不利于达到公用一个JQ的效果.
方法一: optimization:webpack.config.js中添加配置
optimization: {splitChunks: {chunks: 'all'}}
- 将 node_modules 中的代码单独打包(大小超过30kb)
- 自动分析多入口chunk中,有没有公共的文件。如果有会打包成单独一个chunk(比如两个模块中都引入了jquery会被打包成单独的文件)(大小超过30kb)

- 方法二: 设置entry
```javascript
module.exports = {
entry: {
index: {
}, another: {import: './src/index.js',dependOn: 'jquery',
}, shared: ‘jquery’, }, //… }import: './src/test.js',dependOn: 'jquery',
index.bundle.js 与 test.js 共享的模块 jquery.js 被打包到一 个单独的文件中。**3.import (index.js)动态导入语法**:<br />指定某个文件实现单独打包(单多入口都可以使用import)```javascript/*通过js代码,让某个文件被单独打包成一个chunkimport动态导入语法:能将某个文件单独打包(test文件不会和index打包在同一个文件而是单独打包)webpackChunkName:指定test单独打包后文件的名字*/import(/* webpackChunkName: 'test' */'./test').then(({ mul, count }) => {// 文件加载成功~// eslint-disable-next-lineconsole.log(mul(2, 5));}).catch(() => {// eslint-disable-next-lineconsole.log('文件加载失败~');});
如果开启 eslint-webpack-plugin 会一直报错, 需要安装babel-plugin-dynamic-import-webpack
PS :https://blog.csdn.net/Beamon__/article/details/85100475
5.2.2.4 lazy loading(JS文件懒加载/预加载)
- 懒加载( import() ):当文件需要使用时才加载(需要代码分割,不然只有一个js文件)。但是如果资源较大,加载时间就会较长,有延迟。
- 正常加载(直接import导入):可以认为是并行加载(同一时间加载多个文件)没有先后顺序,先加载了不需要的资源就会浪费时间。
- 预加载 prefetch(webpackPrefetch: true 兼容性很差,webpack4.6.0+加入):会在使用之前,提前加载。等其他资源加载完毕,浏览器空闲了,再偷偷加载这个资源。这样在使用时已经加载好了,速度很快。所以在懒加载的基础上加上预加载会更好。
代码:
document.getElementById('btn').onclick = function() {// 将import的内容放在异步回调函数中使用,点击按钮,test.js才会被加载(不会重复加载)// webpackPrefetch: true表示开启预加载import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => {console.log(mul(4, 5));});import('./test').then(({ mul }) => {console.log(mul(2, 5))})};
5.2.2.5 pwa(离线可访问技术)
pwa:离线可访问技术(渐进式网络开发应用程序),使用 serviceworker 和 workbox 技术。优点是离线也能访问,缺点是兼容性差。
过程:
- 添加配置加载包 workbox-webpack-plugin
- 该技术需要在服务器进行,所有安装一个本地node服务器npm i serve -g
- 服务器启动方法 : 在对应文件夹下开启serve -s build
- 测试: chrome调为offline(离线) -> 刷新 ->看到离线缓存界面
webpack.config.js 中配置:
const WorkboxWebpackPlugin = require('workbox-webpack-plugin'); // 引入插件// plugins中加入:new WorkboxWebpackPlugin.GenerateSW({/*1. 帮助serviceworker快速启动2. 删除旧的 serviceworker生成一个 serviceworker 配置文件*/clientsClaim: true,skipWaiting: true})
index.js 中还需要写一段代码来激活它的使用:
/*1. eslint不认识 window、navigator全局变量解决:需要修改package.json中eslintConfig配置"env": {"browser": true // 支持浏览器端全局变量}2. sw代码必须运行在服务器上--> nodejs或-->npm i serve -gserve -s build 启动服务器,将打包输出的build目录下所有资源作为静态资源暴露出去*/if ('serviceWorker' in navigator) { // 处理兼容性问题window.addEventListener('load', () => {navigator.serviceWorker.register('/service-worker.js') // 注册serviceWorker ,service-worker.js是打包时自动生成的.then(() => {console.log('sw注册成功了~');}).catch(() => {console.log('sw注册失败了~');});});}
