webpack优化策略

  • 速度分析

    • webpack有时侯打包很慢,而我们在项目中可能用了很多的pluginloader,想知道那个环节慢,下面这个插件可以计算pluginloader的耗时
    yarn add -D speed-measure-webpack-plugin
    
  • 体积分析

    • 打包后的体积优化是一个很重要的点,譬如引入的一些第三方组件库过大,这是就要考虑是否需要寻找替代品了。这里可以采用webpack-bundle-analyzer,它可以用交互式可缩放树形图显示 webpack输出文件的大小。
    yarn add -D webpack-bundle-analyzer
    
    • 安装完后,配置webpcak.config.js,配置好后,yarn run dev启动后,会默认起一个端口号为8888的本地服务器。这样就能看到每个模块的的文件大小,进行针对性的优化。
  • 多线程/多实例构建

    • 大家都知道webpack是运行在node环境中,二node,是单线程的。webpack的打包过程是io密集和计算机密集型操作,如果能fork,多个进程并行处理各个任务,将会有效缩短构建时间。一般使用较多的两个是thread-loaderHappyPack

      • thread-loader(官方推荐)
       yarn add -D thread-loader
      

      thread-loader会将你的loader放置在一个worker池里面运行,以达到多线程构建。
      注:放置在这个thread-loader之后的loader会在一个单独的worker池(worker pool)中运行。

      • HappyPack(该插件作者不在维护)
      yarn add -    D happypack
      

      HappyPack可以让Webpack同一时间处理多个任务,发挥多喝CPU的能力,将任务分解给多个子进程去并发的执行,子进程处理完成后,再把结果发送给主进程。通过多进程模型,来加速代码的构建。

  • 多进程并行压缩代码

    • 通常我们在开发环境,代码构建时间比较快,而构建用于发布到线上的代码时会添加压缩代码这一流程,则会道济计算量大耗时多
    • webpack默认提供了UglifyJS插件来压缩JS代码,但是使用的是单线程压缩代码,也就是说多个js文件需要被压缩,它需要一个个文件进行压缩。所以说在正式环境打包压缩代码速度非常慢(因为压缩JS代码需要先把代码解析成用Object抽象表示的AST语法树,再应用各种规则分析处理AST,导致这个过程耗时非常大)。所以我们要对压缩代码这里一步骤进行优化,常用的做法是多进程并行压缩,目前主要有三种主流的压缩方案。
      • paralle-uglify-plugin

      • uglifyjs-webpack-plugin

      • terser-webpack-plugin

        • parallel-uglify-plugin
          上面介绍的HappyPack的思想是使用多个子进程去解析和编译JS,CSS等,这样就可以并行处理多个子任务,多个子任务完成后,再将结果发到主进程中,有了这个思想后,ParallelUglifyPlugin插件就产生了。

          webpack有多个JS文件需要输出和压缩时,原来会使用UglifyJS去一个个压缩并且输出,而ParallelUglifyPlugin插件则会开启多个子进程,把对多个文件压缩的工作分给多个子进程去完成,但是每个子进程还是通过UglifyJS去压缩代码。并行压缩可以显著的提升效率

          yarn add -D webpack-parallel-uglify-plugin
          

          注:webpack-parallel-uglify-plugin已不再维护,这里不推荐使用

        • uglifyjs-webpack-plugin

          yarn add -D uglifyjs-webpack-plugin
          

          注:其实它和上面的parallel-uglify-plugin类似,也可通过设置parallel: true开启多进程压缩。

        • terser-webpack-plugin
          不知道你有没有发现:webpack4 已经默认支持 ES6语法的压缩。而这离不开terser-webpack-plugin.

        yarn add -D uglifyjs-webpack-plugin
        

        注:其实它和上面的parallel-uglify-plugin类似,也可通过设置parallel: true开启多进程压缩。

  • 预编译资源模块

    • 什么是预编译资源模块?
      在使用webpack进行打包时候,对于依赖的第三方库,比如vuevuex等这些不会修改的依赖,我们可以让它和我们自己编写的代码分开打包,这样做的好处是每次更改我本地代码的文件的时候,webpack只需要打包我项目本身的文件代码,而不会再去编译第三方库。那么第三方库在第一次打包的时候只打包一次,以后只要我们不升级第三方包的时候,那么webpack就不会对这些库去打包,这样的可以快速的提高打包的速度。其实也就是预编译资源模块。webpack中,我们可以结合DllPluginDllReferencePlugin插件来实现。
    • DllPlugin是什么?
      它能把第三方库代码分离开,并且每次文件更改的时候,它只会打包该项目自身的代码。所以打包速度会更快。DLLPlugin插件是在一个额外独立的webpack设置中创建一个只有dllbundle,也就是说我们在项目根目录下除了有webpack.config.js,还会新建一个webpack.dll.js文件。webpack.dll.js的作用是把所有的第三方库依赖打包到一个bundledll文件里面,还会生成一个名为manifest.json文件。该manifest.json的作用是用来让DllReferencePlugin映射到相关的依赖上去的。
    • DllReferencePlugin又是什么?
      这个插件是在webpack.config.js中使用的,该插件的作用是把刚刚在webpack.dll.js中打包生成的dll文件引用到需要的预编译的依赖上来。什么意思呢?就是说在webpack.dll.js中打包后比如会生成 vendor.dll.js文件和vendor-manifest.json文件,vendor.dll.js文件包含了所有的第三方库文件,vendor-manifest.json文件会包含所有库代码的一个索引,当在使用webpack.config.js文件打包DllReferencePlugin插件的时候,会使用该DllReferencePlugin插件读取vendor-manifest.json文件,看看是否有该第三方库。vendor-manifest.json文件就是一个第三方库的映射而已。
    • 这么在项目中使用
    • 上面说了这么多,主要是为了方便大家对于预编译资源模块和DllPluginDllReferencePlugin插件作用的理解。先来看下完成的项目目录结构,主要在两块配置,分别是webpack.dll.jswebpack.config.js(对应这里我是webpack.base.js
      image.png
    • webpack.dll.js
      const path = require('path');
      const webpack = require('webpack');
    
      module.exports = {
        mode: 'production',
        entry: {
        vendors: ['lodash', 'jquery'],
        react: ['react', 'react-dom']
      },
     output: {
      filename: '[name].dll.js',
      path: path.resolve(__dirname, './dll'),
      library: '[name]'
    },
    plugins: [
      new webpack.DllPlugin({
        name: '[name]',
        path: path.resolve(__dirname, './dll/[name].manifest.json')
        })
      ]
    }
    

    这里我拆了两部分:vendors(存放了lodashjquery等)和react(存放了 react相关的库,reactreact-dom等)

    • webpack.config.js(对应我这里就是webpack.base.js)
    const path = require("path");
    const fs = require('fs');
    // ...
    const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
    const webpack = require('webpack');
    
    const plugins = [
     // ...
    ];
    
    const files = fs.readdirSync(path.resolve(__dirname, './dll'));
    files.forEach(file => {
     if(/.*\.dll.js/.test(file)) {
       plugins.push(new AddAssetHtmlWebpackPlugin({
         filepath: path.resolve(__dirname, './dll', file)
       }))
     }  
     if(/.*\.manifest.json/.test(file)) {
       plugins.push(new webpack.DllReferencePlugin({
         manifest: path.resolve(__dirname, './dll', file)
       }))
     }
    })
    
    module.exports = {
     entry: {
       main: "./src/index.js"
     },
     module: {
       rules: []
     },
     plugins,
    
     output: {
       // publicPath: "./",
       path: path.resolve(__dirname, "dist")
       }
     }
    

    由于上面我把第三方库做了一个拆分,所以对应生成也就会是多个文件,这里读取了一下文件,做了一层遍历。最后在package.json里面再添加一条脚本就可以了:

    "scripts": {
      "build:dll": "webpack --config ./webpack.dll.js",
    },
    

    运行yarn build:dll就会生成本小节开头贴的那张项目结构图了~

  • 利用缓存提升二次构建速度

    • 一般来说,对于静态资源,我们都希望浏览器能够进行缓存,那样以后进入页面就可以直接使用缓存资源,页面打开速度会显著加快,既提高了用户的体验也节省了宽带资源。当然浏览器缓存方法有很多种,这里只简单讨论下在webpack中如何利用缓存来提升二次构建速度。在webpack中利用缓存一般有以下几种思路:
      • babel-loader开启缓存
      • 使用cache-loader
      • 使用hard-source-webpack-plugin
    • babel-loader
      • babel-loader在执行的时候,可能会产生一些运行期间重复的公共文件,造成代码体积冗余,同时也会减慢编译效率。可以加上cacheDirectory参数开启缓存:
       {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [{
          loader: "babel-loader",
          options: {
            cacheDirectory: true
          }
        }],
      },
      
    • cache-loader
      • 在一些性能开销较大的loader之前添加此loader,以将结果缓存到磁盘里。
      yarn add -D cache-loader
      
      • 使用cache-loader的配置很简单,放在其他loader之前即可。修改Webpack的配置如下:
      // webpack.config.js
      module.exports = {
        module: {
          rules: [
            {
              test: /\.ext$/,
              use: [
                'cache-loader',
                ...loaders
              ],
              include: path.resolve('src')
             }
          ]
        }
      }
      
      注:请注意,保存和读取这些缓存文件会有一些时间开销,所以请只对性能开销较大的loader使用此loader
    • hard-source-webpack-plugin
      • HardSourceWebpackPlugin为模块提供了中间缓存,缓存默认的存放路径是: node_modules/.cache/hard-source。配置hard-source-webpack-plugin后,首次构建时间并不会有太大的变化,但是从第二次开始,构建时间大约可以减少 80%左右。
        yarn add -D hard-source-webpack-plugin
        
      • 使用
        // webpack.config.js
        var HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
        
        module.exports = {
          entry: // ...
          output: // ...
          plugins: [
              new HardSourceWebpackPlugin()
           ]
        }
        
      注:webpack5中会内置hard-source-webpack-plugin
  • 缩小构建目标

    • 有时候我们的项目中会用到很多模块,但有些模块其实是不需要被解析的。这时我们就可以通过缩小构建目标或者减少文件搜索范围的方式来对构建做适当的优化。
    • 主要是exclude 与 include的使用:
      • exclude: 不需要被解析的模块
      • include: 需要被解析的模块
      const path = require('path');
      module.exports = {
        ...
        module: {
        rules: [
         {
            test: /\.js$/,
            exclude: /node_modules/,
            // include: path.resolve('src'),
            use: ['babel-loader']
          }
        ]
      }
      
      这里babel-loader就会排除对node_modules下对应js的解析,提升构建速度。
  • 减少文件搜索范围

    • 这个主要是resolve相关的配置,用来设置模块如何被解析。通过resolve的配置,可以帮助Webpack快速查找依赖,也可以替换对应的依赖。
      • resolve.modules:告诉webpack解析模块时应该搜索的目录
      • resolve.mainFields:当从npm包中导入模块时(例如,import * as React from 'react'),此选项将 决定在package.json中使用哪个字段导入模块。根据webpack配置中指定的target不同,默认值也会有所不同
      • resolve.mainFiles:解析目录时要使用的文件名,默认是index
      • resolve.extensions:文件扩展名
       // webpack.config.js
      const path = require('path');
      module.exports = {
        ...
        resolve: {
          alias: {
            react: path.resolve(__dirname, './node_modules/react/umd/react.production.min.js')
          }, //直接指定react搜索模块,不设置默认会一层层的搜寻
          modules: [path.resolve(__dirname, 'node_modules')], //限定模块路径
          extensions: ['.js'], //限定文件扩展名
          mainFields: ['main'] //限定模块入口文件名
        }
        ...
      }
      
  • 动态 Polyfill 服务

    • 什么是 babel-polyfill?
      babel只负责语法转换,比如将ES6的语法转换成ES5。但如果有些对象、方法,浏览器本身不支持,比如:
      • 全局对象:PromiseWeakMap 等。
      • 全局静态函数:Array.fromObject.assign 等。
      • 实例方法:比如 Array.prototype.includes 等。
        此时,需要引入babel-polyfill来模拟实现这些对象、方法。这种一般也称为垫片
    • 怎么使用 babel-polyfill
      • 使用也非常简单,在webpack.config.js文件作如下配置就可以了:
        module.exports = {
          entry: ["@babel/polyfill", "./app/js"],
        };
        
      babel-polyfill由于是一次性全部导入整个polyfill,所以用起来很方便,但与此同时也带来了一个大问题:文件很大,所以后续的方案都是针对这个问题做的优化。
    • 动态Polyfill服务
      image.png

      每次打开页面,浏览器都会向Polyfill Service发送请求,Polyfill Service识别 User Agent,下发不同的 Polyfill,做到按需加载Polyfill的效果。
    • 怎么使用动态Polyfill服务?
      //访问url,根据User Agent 直接返回浏览器所需的 polyfills
      https://polyfill.io/v3/polyfill.min.js
      
  • Scope Hoisting

    • 什么是Scope Hoisting?
      Scope hoisting 直译过来就是「作用域提升」。熟悉 JavaScript 都应该知道「函数提升」和「变量提升」,JavaScript 会把函数和变量声明提升到当前作用域的顶部。「作用域提升」也类似于此,webpack 会把引入的 js 文件“提升到”它的引入者顶部。Scope Hoisting 可以让 Webpack 打包出来的代码文件更小、运行的更快。

    • 启用Scope Hoisting

      • 要在 Webpack 中使用 Scope Hoisting 非常简单,因为这是 Webpack 内置的功能,只需要配置一个插件,相关代码如下:
      // webpack.config.js
      const webpack = require('webpack')
      
      module.exports = mode => {
        if (mode === 'production') {
          return {}
        }
      
        return {
          devtool: 'source-map',
          plugins: [new webpack.optimize.ModuleConcatenationPlugin()],
        }
      }
      
      • 好处
        • 代码体积更小,因为函数申明语句会产生大量代码;
        • 代码在运行时因为创建的函数作用域更少了,内存开销也随之变小。

      Scope Hoisting 的实现原理其实很简单:分析出模块之间的依赖关系,尽可能的把打散的模块合并到一个函数中去,但前提是不能造成代码冗余。因此只有那些被引用了一次的模块才能被合并。

      注意:由于 Scope Hoisting 需要分析出模块之间的依赖关系,因此源码必须采用 ES6模块化语句,不然它将无法生效。

文章转自:https://mp.weixin.qq.com/s?__biz=MzU2MzM2NzU0NA==&mid=2247486539&idx=1&sn=7484798f6c42611c1e81447fc5a51739&chksm=fc5a175ccb2d9e4aa4f33f049bee8fa18e5a1c27ed315be2911ca5c8a1802760701b818c500e&mpshare=1&scene=1&srcid=0915Ld1r2gSXJFB9I7tNdaa5&sharer_sharetime=1600137668291&sharer_shareid=a8f4498c49789458cc5918ae43d0aed2&key=7f54b3443b683033173cffe97dc7047f2921f7d585f87442c5b17eb942738599ea5f40012f9783eacf8c4f1a5789472fca6fa37449cd0fcbeb9dbd7bd6c184e8c6a24f652623e6d12b6b414b310815b80f59947220ee0919dbd2ec4e0a9f3b463e09cebee1ab37d6d7ccbc29b4def16a8854304e29ba208adbd3b98dbe475920&ascene=1&uin=MjIxNzU2Mjg4NA%3D%3D&devicetype=Windows+10+x64&version=62090538&lang=zh_CN&exportkey=A4LNOZ5CDhlUAgdwZU%2Bg4Vc%3D&pass_ticket=sU%2F%2FRSgUTGKQVQgrUbMNdKmZ6urS7BLB4AhRGceH6Mq8KLX6W06k8Zdvf2hhC9RU&wx_header=0

你可能感兴趣的:(webpack优化策略)