5.2.1 优化打包构建速度

5.2.1.1 oneOf

oneOf:匹配到 loader 后就不再向后进行匹配,优化生产环境的打包构建速度

  1. module: {
  2. rules: [
  3. {
  4. // js 语法检查
  5. test: /\.js$/,
  6. exclude: /node_modules/,
  7. // 优先执行
  8. enforce: 'pre',
  9. loader: 'eslint-loader',
  10. options: {
  11. fix: true
  12. }
  13. },
  14. {
  15. // oneOf 优化生产环境的打包构建速度
  16. // 以下loader只会匹配一个(匹配到了后就不会再往下匹配了)
  17. // 注意:不能有两个配置处理同一种类型文件(所以把eslint-loader提取出去放外面,新版本不用eslint-loader)
  18. oneOf: [
  19. {
  20. test: /\.css$/,
  21. use: [...commonCssLoader]
  22. },
  23. {
  24. test: /\.less$/,
  25. use: [...commonCssLoader, 'less-loader']
  26. },
  27. {
  28. // js 兼容性处理
  29. test: /\.js$/,
  30. exclude: /node_modules/,
  31. loader: 'babel-loader',
  32. options: {
  33. presets: [
  34. [
  35. '@babel/preset-env',
  36. {
  37. useBuiltIns: 'usage',
  38. corejs: {version: 3},
  39. targets: {
  40. chrome: '60',
  41. firefox: '50'
  42. }
  43. }
  44. ]
  45. ]
  46. }
  47. },
  48. {
  49. test: /\.(jpg|png|gif)/,
  50. loader: 'url-loader',
  51. options: {
  52. limit: 8 * 1024,
  53. name: '[hash:10].[ext]',
  54. outputPath: 'imgs',
  55. esModule: false
  56. }
  57. },
  58. {
  59. test: /\.html$/,
  60. loader: 'html-loader'
  61. },
  62. {
  63. exclude: /\.(js|css|less|html|jpg|png|gif)/,
  64. loader: 'file-loader',
  65. options: {
  66. outputPath: 'media'
  67. }
  68. }
  69. ]
  70. }
  71. ]
  72. },

5.2.1.2 缓存

获取资源是比较耗费时间的,这就是为什么浏览器使用一种名为 缓存 的技术。可以通过命中缓存,以降低网络流量,使网站加载速度更快,然而, 如果我们在部署新版本时不更改资源的文件名,浏览器可能会认为它没有被更新,就 会使用它的缓存版本。

模拟生产环境下缓存问题实现过程:

  1. server.js(与webpack.config同级) node构建服务器并启动
  2. webpack打包
  3. 修改对应文件并刷新网页查看结果

效果: 修改css文件后刷新, js文件依旧时缓存状态(size),css文件命名被修改重新加载
image.png
实现过程如下
1.babel 缓存:类似 HMR,将 babel 处理后的资源缓存起来(哪里的 js 改变就更新哪里,其他 js 还是用之前缓存的资源),让第二次打包构建速度更快
实现代码: 给babel-loder配置 cacheDirectory: true

  1. {
  2. // js 兼容性处理
  3. test: /\.js$/,
  4. exclude: /node_modules/,
  5. loader: 'babel-loader',
  6. options: {
  7. presets: [
  8. [
  9. '@babel/preset-env',
  10. {
  11. useBuiltIns: 'usage',
  12. corejs: {version: 3},
  13. targets: {
  14. chrome: '60',
  15. firefox: '50'
  16. }
  17. }
  18. ]
  19. ],
  20. // 开启babel缓存
  21. // 第二次构建时,会读取之前的缓存
  22. cacheDirectory: true
  23. }
  24. }

这样结果是文件名不变,就永远不会重新请求,而是再次用之前缓存的资源, 想办法修改文件名方法如下

2.修改文件名以达到文件资源缓存

  1. plugins: [
  2. new MiniCssExtractPlugin({
  3. // 对输出的css文件进行重命名
  4. //filename: 'css/built.[hash:10].css' //一直变化一直重加载
  5. //filename: 'css/built.[chunkhash:10].css' //一直变化一直重加载
  6. filename: 'css/built.[contenthash:10].css' //对应修改变化加载
  7. })
  8. ]

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写法

  1. output: {
  2. //都放到js目录下
  3. filename: 'js/[name].[contenthash:10].js',
  4. path: resolve(__dirname, 'build'),
  5. chunkFilename: 'js/[name].[contenthash:10]_chunk.js' // 指定非入口文件的其他chunk的名字加_chunk
  6. },
  7. optimization: {
  8. splitChunks: {
  9. chunks: 'all', //将 node_modules 中的代码单独打包
  10. //以下都是splitChunks默认配置,可以不写chunks: 'all'即可
  11. cacheGroups: { // 分割chunk的组
  12. vendors: {
  13. // node_modules中的文件会被打包到vendors组的chunk中,--> vendors~xxx.js
  14. // 满足上面的公共规则,大小超过30kb、至少被引用一次
  15. test: /[\\/]node_modules[\\/]/,
  16. // 优先级
  17. priority: -10
  18. },
  19. }
  20. },
  21. },
  22. mode: 'development',

5.0写法?

  1. optimization: {
  2. splitChunks: {
  3. cacheGroups: {
  4. vendor: {
  5. test: /[\\/]node_modules[\\/]/,
  6. name: 'vendors',
  7. chunks: 'all',
  8. },
  9. },
  10. }


5.2.1.3 多进程打包

多进程打包(提升打包性能):某个任务消耗时间较长会卡顿,多进程可以同一时间干多件事,效率更高。
优点是提升打包速度,缺点是每个进程的开启和交流都会有开销(babel-loader消耗时间最久,所以使用thread-loader针对其进行优化)

不要使用太多的 worker,因为 Node.js 的 runtime 和 loader 都有启动开销。 最小化 worker 和 main process(主进程) 之间的模块传输。进程间通讯(IPC, inter process communication)是非常消耗资源的。

  1. {
  2. test: /\.js$/,
  3. exclude: /node_modules/,
  4. use: [
  5. /*
  6. thread-loader会对其后面的loader(这里是babel-loader)开启多进程打包。
  7. 进程启动大概为600ms,进程通信也有开销。(启动的开销比较昂贵,不要滥用)
  8. 只有工作消耗时间比较长,才需要多进程打包
  9. */
  10. {
  11. loader: 'thread-loader',
  12. options: {
  13. workers: 2 // 进程2个
  14. }
  15. },
  16. {
  17. loader: 'babel-loader',
  18. options: {
  19. presets: [
  20. [
  21. '@babel/preset-env',
  22. {
  23. useBuiltIns: 'usage',
  24. corejs: { version: 3 },
  25. targets: {
  26. chrome: '60',
  27. firefox: '50'
  28. }
  29. }
  30. ]
  31. ],
  32. // 开启babel缓存
  33. // 第二次构建时,会读取之前的缓存
  34. cacheDirectory: true
  35. }
  36. }
  37. ]
  38. },

5.2.1.4 externals 打包时排除包

externals:让某些库不打包,通过 cdn 引入(webpack5.0可以设置自动引入)

webpack.config.js

  1. externals: {
  2. // 拒绝jQuery被打包进来(通过cdn引入,速度会快一些)
  3. // 忽略的库名 -- npm包名('jQuery'暴露的别名 import 引入时的)
  4. jquery: 'jQuery'
  5. }

此时打包后JS中就没有JQ,需要在 手动在index.html 中通过 cdn 引入:

  1. <script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>

配置自动引入到打包后的JS中

webpack5.0

  1. //引入类型
  2. externalsType:'script',
  3. externals: {
  4. jquery: [
  5. 'https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js',
  6. 'jQuery'
  7. ]
  8. }


5.2.1.5 dll

dll:让某些库单独打包,后直接引入到 build 中。可以在 code split 分割出 node_modules 后再用 dll 更细的分割,优化代码运行的性能。
可以配合code split事先将node_modules的包单独打包再单独引入,而不是将node_modules都打包成一个再引入.
webpack.dll.js 配置:(将 jquery 单独打包)
执行过程:

  1. 配置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’ };

  1. 2. 执行命令 webpack --config webpack.dll.js 此时生成dll目录及目录下会生成jquery+[hash].js manifest.json此文件是一些规则和映射
  2. 2. 修改webpack.config.js 配置,此时使用webpack打包则不会打包jquerybuild中。
  3. webpack.config.js 配置:(告诉 webpack 不需要再打包 jquerywebpack插件),并将之前打包好的 jquery 跟其他打包好的资源一同输出到 build 目录下(add-asset-html-webpack-plugin插件))
  4. ```javascript
  5. // 引入插件
  6. const webpack = require('webpack');
  7. const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
  8. // plugins中配置:
  9. plugins: [
  10. new HtmlWebpackPlugin({
  11. template: './src/index.html'
  12. }),
  13. // 1.告诉webpack哪些库不参与打包,同时使用时的名称也得变
  14. new webpack.DllReferencePlugin({
  15. manifest: resolve(__dirname, 'dll/manifest.json')
  16. }),
  17. // 2.将某个文件打包输出到build目录下,并在html中自动引入该资源
  18. new AddAssetHtmlWebpackPlugin({
  19. filepath: resolve(__dirname, 'dll/jquery.js')
  20. })
  21. ],

5.2.1.6 externals和dll区别

  1. externals 对某些包直接不打包, 自己用cdn引入
  2. dll 事先打包好,再引入

    5.2.2 优化代码运行的性能

    5.2.2.1 缓存

    babel缓存第二点开始

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.多入口拆分
    1. entry: {
    2. // 多入口:有一个入口,最终输出就有一个bundle
    3. index: './src/js/index.js',
    4. test: './src/js/test.js'
    5. },
    6. output: {
    7. //多入口不能只有一个出口 所有这里修改成不同文件名的出口文件
    8. filename: 'js/[name].[contenthash:10].js',
    9. path: resolve(__dirname, 'build')
    10. },
    输出结果 :
    image.png
  1. 多入口下 - 公共依赖抽离为单独chunk

如果多个文件都引用jquery打包时每个js都会引入JQ , 这样就不利于达到公用一个JQ的效果.

  • 方法一: optimization:webpack.config.js中添加配置

    1. optimization: {
    2. splitChunks: {
    3. chunks: 'all'
    4. }
    5. }
    • 将 node_modules 中的代码单独打包(大小超过30kb)
    • 自动分析多入口chunk中,有没有公共的文件。如果有会打包成单独一个chunk(比如两个模块中都引入了jquery会被打包成单独的文件)(大小超过30kb)

image.png

  • 方法二: 设置entry ```javascript module.exports = { entry: { index: {
    1. import: './src/index.js',
    2. dependOn: 'jquery',
    }, another: {
    1. import: './src/test.js',
    2. dependOn: 'jquery',
    }, shared: ‘jquery’, }, //… }
  1. index.bundle.js test.js 共享的模块 jquery.js 被打包到一 个单独的文件中。
  2. **3.import (index.js)动态导入语法**:<br />指定某个文件实现单独打包(单多入口都可以使用import)
  3. ```javascript
  4. /*
  5. 通过js代码,让某个文件被单独打包成一个chunk
  6. import动态导入语法:能将某个文件单独打包(test文件不会和index打包在同一个文件而是单独打包)
  7. webpackChunkName:指定test单独打包后文件的名字
  8. */
  9. import(/* webpackChunkName: 'test' */'./test')
  10. .then(({ mul, count }) => {
  11. // 文件加载成功~
  12. // eslint-disable-next-line
  13. console.log(mul(2, 5));
  14. })
  15. .catch(() => {
  16. // eslint-disable-next-line
  17. console.log('文件加载失败~');
  18. });

如果开启 eslint-webpack-plugin 会一直报错, 需要安装babel-plugin-dynamic-import-webpack
PS :https://blog.csdn.net/Beamon__/article/details/85100475

5.2.2.4 lazy loading(JS文件懒加载/预加载)

  1. 懒加载( import() ):当文件需要使用时才加载(需要代码分割,不然只有一个js文件)。但是如果资源较大,加载时间就会较长,有延迟。
  2. 正常加载(直接import导入):可以认为是并行加载(同一时间加载多个文件)没有先后顺序,先加载了不需要的资源就会浪费时间。
  3. 预加载 prefetch(webpackPrefetch: true 兼容性很差,webpack4.6.0+加入):会在使用之前,提前加载。等其他资源加载完毕,浏览器空闲了,再偷偷加载这个资源。这样在使用时已经加载好了,速度很快。所以在懒加载的基础上加上预加载会更好。

代码:

  1. document.getElementById('btn').onclick = function() {
  2. // 将import的内容放在异步回调函数中使用,点击按钮,test.js才会被加载(不会重复加载)
  3. // webpackPrefetch: true表示开启预加载
  4. import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => {
  5. console.log(mul(4, 5));
  6. });
  7. import('./test').then(({ mul }) => {
  8. console.log(mul(2, 5))
  9. })
  10. };

5.2.2.5 pwa(离线可访问技术)

pwa:离线可访问技术(渐进式网络开发应用程序),使用 serviceworker 和 workbox 技术。优点是离线也能访问,缺点是兼容性差。
过程:

  1. 添加配置加载包 workbox-webpack-plugin
  2. 该技术需要在服务器进行,所有安装一个本地node服务器npm i serve -g
  3. 服务器启动方法 : 在对应文件夹下开启serve -s build
  4. 测试: chrome调为offline(离线) -> 刷新 ->看到离线缓存界面

webpack.config.js 中配置:

  1. const WorkboxWebpackPlugin = require('workbox-webpack-plugin'); // 引入插件
  2. // plugins中加入:
  3. new WorkboxWebpackPlugin.GenerateSW({
  4. /*
  5. 1. 帮助serviceworker快速启动
  6. 2. 删除旧的 serviceworker
  7. 生成一个 serviceworker 配置文件
  8. */
  9. clientsClaim: true,
  10. skipWaiting: true
  11. })

index.js 中还需要写一段代码来激活它的使用:

  1. /*
  2. 1. eslint不认识 window、navigator全局变量
  3. 解决:需要修改package.json中eslintConfig配置
  4. "env": {
  5. "browser": true // 支持浏览器端全局变量
  6. }
  7. 2. sw代码必须运行在服务器上
  8. --> nodejs
  9. 或-->
  10. npm i serve -g
  11. serve -s build 启动服务器,将打包输出的build目录下所有资源作为静态资源暴露出去
  12. */
  13. if ('serviceWorker' in navigator) { // 处理兼容性问题
  14. window.addEventListener('load', () => {
  15. navigator.serviceWorker
  16. .register('/service-worker.js') // 注册serviceWorker ,service-worker.js是打包时自动生成的
  17. .then(() => {
  18. console.log('sw注册成功了~');
  19. })
  20. .catch(() => {
  21. console.log('sw注册失败了~');
  22. });
  23. });
  24. }