webpack 项目性能优化随笔

项目打包优化思路
开发模式优化
项目构建速度优化
快速调试优化
生产模式优化
项目构建速度优化
代码运行性能优化

开发模式性能优化之HMR (hot module reolacement)
热模替换 它允许在运行时更新所有类型的模块,而无需完全刷新。

从 webpack-dev-server v4.0.0 开始,热模块替换是默认开启的。

一定不要再生产模式中使用hot

js的启动方案

// webpack.config.js
const webpack = require('webpack')

{
   devserver: {
       hot: true
   },
   plugins: [
       new webpack.HotModuleReplacementPlugin()
   ]
}

有些loader中自带hot 例如style-loader

html 不建议使用webpack的热重载功能 可以再 entry中路径进行热重载

传统html页面的热重载可以使用 livereload
https://www.cnblogs.com/alex-zen/p/9811695.html


开发模式性能优化之sourcemap
sourceMap是一个静态资源映射文件,map文件的作用在于:项目打包后,代码都是经过压缩加密的,
如果运行时报错,输出的错误信息无法准确得知是哪里的代码报错,有了map就可以像未加密的代码一样,
准确的输出是哪一行哪一列有错。

处于安全性考虑 sourcemaps 不应该出现在服务器中 即不应该在生产环境使用sourcemap

// 有两种解决方案
//1. 使用Devtools进行配置
//此选项控制是否生成,以及如何生成 source map。
// https://webpack.docschina.org/configuration/devtool/
{
    devtool: '[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map'
}


//2. 使用SourceMapDevToolPlugin 进行更细粒度的控制;
// https://webpack.docschina.org/plugins/source-map-dev-tool-plugin/




webpack性能优化之oneof

webpack原本的loader是将每个文件都过一遍,比如有一个js文件 rules中有10个loader,
第一个是处理js文件的loader,当第一个loader处理完成后webpack不会自动跳出,而是会继续
拿着这个js文件去尝试匹配剩下的9个loader,相当于没有break。而oneOf就相当于这个break

webpack.config.js
{
    rules: [
        oneOf: [
            {
                test: /\.css$/,
                use: [
                    // 'style-loader',
                    // MiniCssExtractPlugin.loader,
                    'css-loader',
                    {
                        loader: 'postcss-loader',
                        options: {
                            // postcssOptions: {
                            //     // options: {
                            //     // indent: 'postcss',
                            //     plugins: () => [
                            //         require('post-css-preset-env')()
                            //     ]
                            // }
                            plugins: () => [
                                require('postcss-preset-env')()
                            ]
                        }
                    },
                ],
                // outputPath: 'css/'
                // options: {
                //     outputPath: "css/"
                // }
            }
            ,
            {
                test: /\.less$/,
                use: [
                    'style-loader',
                    // MiniCssExtractPlugin.loader,
                    'css-loader',
                    {
                        loader: 'postcss-loader',
                        options: {
                            // postcssOptions: {
                            // options: {
                            // indent: 'postcss',

                            // }
                            plugins: () => [
                                require('postcss-preset-env')()
                            ]
                        }
                    },
                    'less-loader'
                ],
                // outputPath: 'less/'
                // options: {
                //     outputPath: 'less/'
                // }
            },
            {
                test: /\.(jpg|png)$/,
                loader: 'url-loader',
                options: {
                    limit: 25 * 1024,
                    name: "[hash:10].[ext]",
                    esModule: false,
                    outputPath: 'imgs/'
                },
            }
        ]
    ]
}

enforce: loader 的执行顺序是由下往上的,enforce是为了在设置loader的执行顺序
有以下几个配置项

  1. pre -- 优先处理
  2. normal -- 默认顺序处理(default)
  3. inline -- 其次处理
  4. post -- 最后处理

性能优化之缓存

缓存就是把我们之前所生成的 无修改的文件资源(源代码和编译后代码)都进行缓存,
在未发生修改时直接复用之前的缓存文件。 提高代码的编译和上线后的运行速度

打包编译缓存

作用 让第二次打包编译速度更快
babel缓存 在babel-loader 的 options中开启
https://webpack.docschina.org/loaders/babel-loader/

{
    test: /\.js$/,
    loader: 'babel-loader',
    options: {
        ...
        cacheDirectory: true
    }
}

文件资源缓存(生产环境版本更新缓存)

作用 让代码上线运行更好的使用缓存

在使用webpack静态资源缓存方式前 应了解
浏览器的缓存策略 强缓存| 协商缓存

强缓存 : 对文件资源设置缓存失效时间 缓存类型来控制

在 response header中 设置cache-control

可选值
max-age public private no-cache no-store immutable

max-age 表示文件的失效时间 单位为秒

public 表示文件可以被客户端和代理服务器缓存

private 表示文件只能被客户端缓存

no-chche 表示跳过设置强缓存 当时可以设置协商缓存,
如果做了强缓存就会等到max-age失效才会走协商缓存,
设置了no-cache就不会在走强缓存了。每次请求都会走协商缓存

no-store 文件每次都会重新请求服务器, 不会协商缓存

immutable 由于以上的缓存设置就算设置了强缓存后仍旧会在用户点击刷新按钮时失效,
所以facebook公司团队向 HTTP标准团队提出建议,希望有一个配置可以表示资源永不过期。

协商缓存就是,每次的请求都会返回到服务器,服务器通过客户端返回的 etag和lastmodified的
时间与文件新的数据进行比对,查看是否有变换,如果变换了就返回200状态码和真正的数据,
客户端重新记录max-age,etag与lastmodified
如果没有过期就返回304状态码,浏览器从缓存中读取数据

// 响应头中的key
// response header
etag: '5c20abbd-e2e8'
last-modified: Mon, 24 Dec 2018 09:49:49 GMT

// 请求头中的key
// request header 变为
if-none-matched: '5c20abbd-e2e8'
if-modified-since: Mon, 24 Dec 2018 09:49:49 GMT

设置方式
通过服务器代码进行设置如Nodejs 中的 setResponseHeader
或在nginx中进行配置

这是通过浏览器的缓存方式。
webpack中的缓存时通过修改文件名的hash来设置的
因为 浏览器每次加载完的资源都会在 memorycache中进行缓存,当 tab页被关闭或者被强制刷新时才会更新
所以可以通过设置资源的max-age 与 修改文件的hash进行文件修改

output: {
        filename: "js/built-[hash|chunkhash|contenthash].js",
    },

hash有三种定义方式 hash|chunkhash|contenthash

hash: 每次文件重新构建的 时候都会生成新的hash值
问题,每次打包后都会生成新的文件,所有文件都要重新加载
chunkhash : 会以chunk为一个更新标准进行hash生成。
问题: 所有的文件都是从一个入口进入的, 所以属于一个chunk,还是会重新加载所有文件
contenthash: 根据文件的内容生成hash, 不同的文件hash值不一样


性能优化之tree shaking

tree shaking 的开启条件为

  1. mode 为 production环境
  2. webpack 4 为 必须使用es6模块化。 webpack5 中commonjs 也可以使用

作用:减小代码体积, 不会去打包没有使用的代码


https://www.webpackjs.com/guides/code-splitting/ 参考文档
性能优化之 代码分割
把代码分割成不同的chunk块,让代码可以通过不同的页面进行不同的加载。提高页面性能

配置不同chunk的方式有
配置多入口 即 在webpack-entry下使用对象进行配置。webpack会根据入口文件进行代码分割

entry:{
     main: './src/index.js',
     test: './src/test.js',
   }

构建后的文件会生成两个chunk

问题:如果入口chunk中存在重复模块。重复模块会被引入到不同bundle中; 不够灵活,只能从入口出控制,无法根据核心业务逻辑动态拆分代码。

动态导入。
顾名思义,在用到文件的位置进行引入。而不是一股脑全部加载。
使用import() 方法进行动态引入 文件或者webpack提供的 require.ensure方法

webpack会把使用动态加载语法的代码块单独打包成一个chunk
例如在vue中把不同的路由对应的组件进行分包处理
router.js

[{
  path: "/login",
  name: "login",
  components: () => import(/*webpackChunkName:"login", webpackPrefetch:true*/'@/page/login')
}]

同样也可以对某个依赖进行代码拆分。

document.getElementById("btn").onclick= function() {
 import('@/src/utils').then(({clickFn}) => {
  clickFn()
 }).catch(err => console.log(err))
}

webpackChunkName 给chunk设置为固定名字,方便确定打包后的文件。

webpackPrefetch:true 启动之后会在使用之前,浏览器空闲了,提前加载js文件。
不启动但是使用import的加载方式为懒加载,即使用时才加载。 同一时间加载多个,并行加载。

预加载有浏览器限制。谨慎使用

chunkFileName用来对项目中的非入口bundle的文件命名规则定义
webpack.config.js

output:{
  ......
  chunkFileName: '[name].chunk.js'
}

optimization.splitChunks 用于把node_modules的依赖单独打包。
webpack.config.js

optimization:{
  //......
  spliChunks: {

      //用于规定哪些代码块需要被打包
      //all    async   initial
      chunks: all ,
      cacheGroups {
             //将全部nodemodules 依赖需要打包的块进 行拆分

            chunkA: {
               name:'chunkA', //块名
               test:'/node_modules/', //文件匹配规则
               priority: 10, // 文件权重
               chunks: 'initial' // all |   async |  initial
            }
       }
  }
}

性能优化之 externals
对于一些模块内引入,但是使用的是cdn加速,或者不想让他打包到其他模块内的代码块。可以添加
externals 拒绝这些应用被打包

webpack.config.js

{
    externals: {
         jquery:'jQuery'
    }
}

性能优化之 多进程打包
使用thread-loader开启多线程

开启多进城打包需要进行评估
开启进城消耗 大概为6000毫秒
进程通讯 也有一定的开销
所以应对使用的代码进行评估是否使用

webpack.config.js

{
    test: /\.js$/,
    exclude: /node_modules/,
    use: [
      {
         loader: 'thread-loader',
         option: {
            worker: 2 //开启两个进城
         }
      },
      {
          loader: 'babel-loader',
          option: {
              presets:[
                '@babel/preset-env',
                {
                    useBuiltIns: 'usage',
                    targets:{
                        chrome: '60',
                        firefox: '48',
                        ie: '8',
                        safari: '10',
                    }
                },
              ]
          }
      }
    ]
}

性能优化之 dll
dll 就是把本地的不会修改的依赖 如(vue,jquery, vue-router)等,把这写文件进行预打包
并把真正的 文件路径指向打包好的文件目录。在我们后期的打包过程中就不在需要对文件进行重复打包,
提高代码的运行速度
与 externals的区别就是,一个是用来拒绝无需打包的线上资源的, 一个是对本地的node_modules依赖进行预打包
与代码分割 的区别就是,代码分割只是对node——modules进行单独的chunk划分,且划分后默认只生成一个chunk
而dll 可以对依赖进行预打包。 且可以对不同的依赖进行不同的chunk分包处理(代码分割可以通过划分cacheGroups进行分割),

具体实现逻辑为
https://segmentfault.com/a/1190000016567986

配置webpack.dll.config.js文件

const {resolve, join} = require('path');
const webpack = require('webpack');
module.exports = {
    entry: {
        vue: ['vue', 'vue-router', 'vuex']
    },
    output: {
        filename: '[name].dll.js',
        path: resolve('./dist/dll'),
        // library 必须与后面 DllPlugin 的name值一样
        library: '[name]_dll_[hash:10]'
    },
    plugins: [
        new webpack.DllPlugin({
            name: '[name]_dll_[hash:10]',
            path: join(__dirname, 'dist/dll', '[name]:mainfest.json')
        })
    ]
}

执行:webapck --config webpack.dll.config

在主配置文件 webpack.config.js 中 配置引入生成 的 nainfest.json文件

const {resolve} = require('path');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
const webpack = require('webpack');
module.exports = {
    ...,
    plugins: [
        new AddAssetHtmlWebpackPlugin({
            filepath: resolve(__dirname, 'dll/vue.dll.js')
        }) ,
        new webpack.DllReferencePlugin({    
            manifest: resolve(__dirname, 'dll/vue.mainfest.js')
        })
    ]
}

参考文档 https://blog.csdn.net/hjc256/article/details/100652033

posted @ 2021-12-14 00:04  _z_sy_haha  阅读(89)  评论(0)    收藏  举报