Webpack5不完全指南-优化篇

1,830 阅读10分钟

金石有声,不拷不鸣

production模式打包自带优化

production模式 打包自带很多优化,例如 tree shakingscope hoisting代码压缩

tree shaking

tree shaking 指在打包时移除JS中未使用的代码(dead code),它依赖于 ES6模块化规范 的静态结构特性。

  • 当引入一个模块后,只使用了模块的部分功能,打包时只会将使用到的功能打包进bundle中,其他没有使用的功能不会打包进来。
  • 注意 require 是动态导入,使用 CommonJS模块化规范 会影响打包时的 tree shaking 功能

scope hoisting(作用域提升)

scope hoisting 会将模块的结果进行预测,可以让webpack打包出的文件更小运行更快。
scope hoisting也是基于ES6模块化规范,它是由webpack内置插件 ModuleConcateNationPlugin实现的。
production模式下默认配置 ModuleConcateNationPlugin插件,其他模式默认不开启。

示例

  • 新建 src/model.js

    export let a = 1
    export let b = 2
    export let c = 9
    
  • main.jsmodel 模块导出的变量进行计算

    import { a, b, c } from "./model.js";
    const sub = (modelA, modelB, modelC) => ((modelA + modelB) * modelC)
    console.log("a、b、c求和结果", sub(a, b, c));
    
  • npm run build 后查看打包结果

    image.png

由上面例子可以看出 scope hoisting(作用域提升) 的强大之处,通过它webpack在打包时可以将结果推断出来,将模块打散合并为一个函数极大的避免了代码的冗余。

代码混淆、压缩

production模式下,webpack打包时,会通过 UglifyJsPlugin 插件对代码进行混淆、压缩

css优化

css优化--css代码提取到单独文件中

前面的css代码是直接放到 htmlstyle 标签中的,当相同css代码在多个模块多个页面用到时,为提高复用性避免重复编译最好还是将css代码提取到单独文件中。

对当前项目进行调整

1、优化项目目录结构

image.png

2、修改 src/webpack.base.js 配置 打包为单页面应用

module.exports = {
  // 1.只指定一个入口文件
  entry: "./src/main.js",
  // entry: {
  //   index: "./src/main.js",
  //   other: "./src/other.js",
  // },
  output: {
    path: path.resolve(__dirname, "..", "./dist"),
    filename: '[name].js'
  },
  plugins: [
    new HtmlPlugin(
      {
        filename: "index.html",
        template: "./src/index.html",
        // 3.也不要指定chunks 否则css可能会出现问题
        // chunks: ["index", "other"]
      }
    ),
    // 2.单页面应用只生成一个html 删除生成other.html的HtmlPlugin插件
    // new HtmlPlugin(
    //   {
    //     filename: "other.html",
    //     template: "./src/other.html",
    //     chunks: ["other"]
    //   }
    // ),
  ]
}

3、src/main.js 导入css

import "./css/index.css"
import "./less/index.less"

MiniCssExtractPlugin

MiniCssExtractPlugin是将css提取未单独文件的插件,对每个包含css的js文件都单独生成一个css文件,支持按需加载css和sourceMap

  • 安装
    npm i mini-css-extract-plugin

  • 配置plugin和loader

    // webpack.base.js  
    // 1.导入插件构造函数
    const MiniCssExtract = require("mini-css-extract-plugin")  
    module.exports = {
      plugins: [
        // 2.new 一个MiniCssExtract插件实例并 配置生成的css文件名
        new MiniCssExtract({
          filename: "main.css"
        })
      ],
        module: {
      rules: [
        {
          test: /\.css$/i,
          // 3.style-loader将css代码放入style标签中,将所有style-loader替换为MiniCssExtract的loader
          use: [MiniCssExtract.loader, 'css-loader']
        },
        {
          test: /\.less$/i,
          use: [MiniCssExtract.loader, 'css-loader', 'less-loader']
        }
      ]
    }
    
  • npm run build 打包 css被单独提取出来

    image.png

css优化--自动添加css属性前缀

自动添加css属性前缀是通过 postcss 实现的,使用 postcss 需要用到 postcss-loaderautoprefixer 插件

  • 安装
    npm i postcss-loader autoprefixer

  • 配置loader

    // webpack.base.js  
    module: {
      rules: [
        {
          test: /\.css$/i,
          // 在css-loader右侧配置'postcss-loader'  自动添加添加css属性前缀
          use: [MiniCssExtract.loader, 'css-loader', 'postcss-loader']
        },
        {
          test: /\.less$/i,
          // 在css-loader右侧配置'postcss-loader'  自动添加添加css属性前缀
          use: [MiniCssExtract.loader, 'css-loader', 'postcss-loader', 'less-loader']
        }
     ]
    } 
    
  • 项目根目录新建 postcss.config.js

    module.exports = {
      // postcss功能很单一,很多功能都依赖插件
      "plugins": [
        require('autoprefixer')
      ]
    }
    
  • package.json 新增 browserslist 字段

    "browserslist": [
      "defaults",
      "not ie <= 8",
      "last 2 versions",
      "> 1%",
      "iOS >= 7",
      "Android >= 4.0"
    ]
    
  • nom run build 重新构建项目

    image.png

css优化--css代码压缩

通过optimize-css-assets-webpack-plugin 插件完成css代码的压缩
注意:使用这个插件会出现一个问题
用生产模式打包时,webpack默认是开启js代码的压缩混淆功能的,但是如果使用 optimize-css-assets-webpack-plugin 会覆盖掉webpack默认优化配置,导致js代码无法压缩。 所以我们需要导入插件 terser-webpack-plugin 来配置一下js代码压缩功能。

  • 安装
    npm i optimize-css-assets-webpack-plugin terser-webpack-plugin -D

  • 在配置文件中添加配置节点

    // webpack.prod.js
    // 1.导入插件 
    const TerserWebpackPlugin = require("terser-webpack-plugin")
    const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin")
    const prodConfig = merge(baseConfig, {
      // 2.添加优化配置项
      optimization: {
        minimizer: [new TerserWebpackPlugin({}), new OptimizeCSSAssetsPlugin({})]
      }
    })
    
  • 修改 main.js

    import "./css/index.css"
    import "./less/index.less"
    let a = 20
    let b = 30
    function add(a, b) {
      return a + b
    }
    console.log("结果", add(a, b));
    
  • npm run build 把包后js/css都成功压缩

    image.png

JS代码优化

JS代码分离(Code Splitting)

Code SplittingWebpack 重要特性之一,它可以将代码分离打包到不同bundle中,然后按需加载、并行加载加载这些文件。通过合理控制这些bundle资源加载的优先级,会极大影响加加载速度。例如,SPA应用单个bundle文件过大导致首屏加载速度慢的问题。

三种常见代码分离方法:

  • 入口起点
    手动配置多入口,通过 entry 配置分离代码
  • 防止重复
    使用SplitChunksPlugin去重和分离chunk,例如:a模块b模块都是用了jquery,我们要避免jquery被重复打包在两个模块中。
  • 动态导入
    通过模块内联函数调用来分离代码,例如:路由的懒加载功能

手动配置多入口

  • 安装jquery npm i jquery

  • 修改 src/main.js,新建 src/other.js

    // main.js  
    import $ from 'jquery'
    $(function () {
      // 新建div 插入到 body中
      $("<div></div>").html("这是main.js").appendTo("body")
    })
    
    // other.js  
    import $ from 'jquery'
    $(function () {
      // 新建div 插入到 body中
      $("<div></div>").html("这是other.js").appendTo("body")
    })
    
  • 配置多入口

    // webpack.base.js  
    module.exports = {
      entry: {
        // 入口文件分别为  main.js  /  other.js
        main: "./src/main.js",
        other: "./src/other.js"
      }
    }
    
  • npm run dev 查看打包结果

    1634039741(1).png

    image.png 可以看到每个bundle只有几行代码,大小却几百kb显然是重复的JQ模块被引入到了每个bundle中

配置多入口会存在一些问题

  • 如果入口的chunks之间包含重复模块,那些重复模块会被引入到每个bundle中
  • 这个种方法不够灵活,无法将程序核心的逻辑进行动态拆分

抽取公共代码

前面虽然划分了两个bundle,但是存在模块重复引入的问题。SplitChunksPlugin可以将公共的代码抽取出来。

  • 修改配置文件 webpack.base.js

    module.exports = {
      optimization: {
        splitChunks: {
          // 将所有的重复模块抽离出来
          chunks: "all"
        }
      }
    }
      
    
  • npm run build重新构建 JQ模块被单独抽离出来

    image.png

动态导入

在项目开发中,一般以单页面应用程序为主很少需要配置多个入口文件,抽离公共代码也很少使用,经常使用的主要是动态导入(路由懒加载)。
CommonJS模块化 规范支持动态导入的,但是 ES6模块化 规范是静态导入。在 Webpack5 中可以通过 import(module) 实现模块动态导入

import(module)

import(module) 可以动态导入模块,它会返回一个 Promise 实例可以通过 .then 获取模块实例

  • 修改src/main.js

    let a = 1
    if (a === 1) {
      console.log("a", a);
      import("jquery").then(({ default: $ }) => {
        $("<div></div>").html("这是动态导入的main.js").appendTo("body")
      })
    }
    
  • npm run dev 查看结果

    image.png

静态导入存在的问题

使用静态导入打包出的模块,在页面加载时就会全部加载进来非常影响体验

  • 清空 index.html

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <!-- <link rel="stylesheet" href="./css/index.css"> -->
    </head>
    
    <body>
      <button id="btn">插入DOM</button>
    </body>
    
    </html>
    
  • 修改 main.js

    import $ from 'jquery'
    // 点击按钮后 使用JQ创建新节点并插入到页面中
    document.getElementById("btn").onclick = function () {
      $("<div></div>").html("main.js").appendTo("body")
    }
    
  • npm run server运行服务
    可以看到页面加载时JQ就已经加载进来了 此时我们并未使用它

    image.png

动态导入的好处

动态导入可以做到模块的按需加载(懒加载),只有当真正还是用这个模块时再去加载它

  • 应用改为单入口

    // webpack.base.js  
    module.exports = {
      entry: "./src/main.js",
      // entry: {
      //   main: "./src/main.js",
      //   other: "./src/other.js"
      // }
    }
    
  • 修改 src/main.js

    document.getElementById("btn").onclick = function () {
      import("jquery").then(({ default: $ }) => {
        $("<div></div>").html("这是动态导入的main.js").appendTo("body")
      })
    }
    
  • npm run dev 运行服务
    点击按钮之后才会加载JQ模块

    image.png

代码拆分

代码拆分通过 splitChunksPlugins 插件实现的
splitChunksPlugins会根据以下默认配置自动拆分代码:

  • 公共模块或者来自 node_modules 下的组件模块
  • 打包的代码块大小超过30kb(最小压缩之前)
  • 按需加载代码块时,同时发送请求数量不应该超过5
  • 页面初始化,同时发送的请求数量不应该超过3
    注意官网Demo示例为Mac,路径为正斜杠 window要用反斜杠取模块name!!!!
 optimization: {
    splitChunks: {
      // 对异步加载的模块进行拆分 可选值:all(静态动态模块都拆分) | initial(对静态模块导入拆分);
      chunks: 'async',
      // 允许新拆出 chunk 的最小体积 20KB
      minSize: 20000,
      // webpack5新属性,防止0尺寸的chunk
      minRemainingSize: 0,
      // 模块最少引用一次才会拆分
      minChunks: 1,
      // 异步加载的请求数量最大不能超过30,超过5的部分不拆分
      maxAsyncRequests: 30,
      // 页面初始同时发送的请求数量最大不能超过30,超过30的部分不拆分
      maxInitialRequests: 30,
      // 当模块大小大于50KB强行进行拆分忽略其他任何限制
      enforceSizeThreshold: 50000,
      // 缓存组配置,上面的配置读取完成后进行模块拆分,如果多个模块拆分导一个文件上就需要缓存,所以命名为缓存组
      cacheGroups: {
        // 自定义  名为 commons的缓存组
        commons: {
          // 匹配规则
          test: /[\\/]node_modules[\\/]/,
          // 权重,权重越大优先级越高 当模块匹配到多个缓存组中,最终根据权重决定要打包进哪个缓存组
          priority: -10,
          reuseExistingChunk: true,
          // 缓存组生成文件名 动态自定义
          name(module, chunks, cacheGroupKey) {
            console.log("模块路径", module.identifier());
            const moduleFileName = module
              .identifier()
              // window: E:\study\basic\node_modules\jquery\dist\jquery.js
              .split("\\")
              // mac: E:/study/basic/node_modules/jquery/dist/jquery.js
              //  .split("/")
              .reduceRight((item) => item);
            const allChunksNames = chunks.map((item) => item.name).join('~');
            // 缓存组名-[模块的依赖模块]-模块名
            return `${cacheGroupKey}-${allChunksNames}-${moduleFileName}`;
          },
          // 缓存组生成文件名 写死自定义
          filename: "vendors.js"
        },
        // 默认缓存组
        default: {
          // 模块至少引用两次才会进行拆分
          minChunks: 2,
          priority: -20,
          // 是否复用chunk,例如 主入口有a、b两个模块,但是b模块中引用了a模块 这个打包模块时可能会出现重复打包a模块 reuseExistingChunk可以复用重复的chunk 
          reuseExistingChunk: true,
        }
      },
    },
  },
};

提升构建性能

打包项目时,除了对打包文件的优化,还可以提升构建性能减少构建时间。

忽略解析模块(noParse)

一些第三方模块,例如 lodashjquery 时,可以明确知道他们并没有依赖其他模块,所以也没有必要让webpack再去解析他们模块依赖关系浪费构建性能。可以通过module.noParse 来配置不需要解析依赖关系的模块

  • src/main.js 中使用 jquery

    document.getElementById("btn").onclick = function () {
      import("jquery").then(({ default: $ }) => {
        $("<div></div>").html("这是动态导入的main.js").appendTo("body")
      })
    }
    
  • 未配置 noParse 直接打包 npm run build,构建速度约为3664毫秒

    image.png

  • 配置 noParse

    // config/webpack.base.js  
    module.exports = {
      module: {
        // 不对jquery模块进行解析
        noParse: /jquery/// 不对多个模块进行解析
        // noParse: /jquery|lodash/
      }
    }
    
  • npm run build 重新构建项目
    配置noParse后,构建时间减少了300多毫秒!! 在项目中配置多个noParse的模块 项目会更显著
    image.png

忽略打包模块(ignorePlugin)

在引入一些第三方模块时,例如 moment ,每部会做i18n的国际化处理,会包含很多语言包。但是语言包在打包时会占用空间,如果项目只需要少数语言或某种语言,可以忽略其他语言包,这样构建项目的效率更高,打包生成的文件体积更小

ignorePlugin 可以忽略模块内部依赖的其他模块,然后我们再根据需求按需导入需要的模块

  • 首先找到 moment 依赖的语言包
  • 使用 ignorePlugin 忽略其依赖
  • 需要使用某些依赖时自行手动引入

moment

moment 是国际化时间处理的工具库

  • 安装 moment
    npm i moment

  • 使用 moment

    // src/main.js  
    // 导入moment
    import moment from 'moment'
    // 使用中文语言包
    moment.locale("zh-CN")
    // 六天前的日历时间
    const time = moment().subtract(6, 'days').calendar();
    console.log("time", time);
    
  • npm run build 查看构建结果

    image.png

使用 ignorePlugin 进行优化

  • 找到 moment 内部引入的语言模块
    • 查看语言包位置(node_modules/moment/locale

      image.png

    • moment 的入口文件 搜索 require 关键字 查看内部引入的哪些模块

      image.png

  • 配置 ignorePlugin
    module.exports = {
      plugins:[
         /**
           * IgnorePlugin:是webpack内置插件
           * IgnorePlugin(options)
           *   - options:配置对象  
           *     - resourceRegExp:要被忽略的模块,这里要屏蔽moment中的locale语言包模块 
           *     - contextRegExp:使用被忽略的模块的上下文,你在哪个模块中使用了要被忽略的模块。这里语言包模块的上下文是moment模块
           */
         new Webpack.IgnorePlugin({
           // 忽略 "./locale" 模块
           resourceRegExp: /\.\/locale/,
           // 在moment中忽略 "./locale" 模块
            contextRegExp: /moment/
         })
      ]
    }
    
  • 按需导入语言包模块
    前面通过 ignorePlugin 已经忽略掉 moment.js 引入的所有语言包模块。我们需要在业务代码中按需引入语言包模块
    import moment from 'moment'
    // 手动引入中文语言包
    import 'moment/locale/zh-cn'
    moment.locale("zh-CN")
    const time = moment().subtract(6, 'days').calendar();
    console.log("time", time);
    
  • npm run build 重新打包对比之前打包之后文件大小
    之前由于语言包过大 codesplit将其单独分为一个大小为228.kb的bundle。通过 ignorePlugin 处理后这个228.kb的bundle被优化掉了,同时main.js也有所减小。 image.png

谨慎使用 noParse

noParse 会忽略对模块内部依赖关系的解析,盲目使用 noParse 可能会导致模块内部依赖丢失的问题

例如 使用 ignornPlugin 忽略 moment 语言包模块依赖导入,按需导入要用的语言包后,如果将 moment 配置到 noParse 会出现bug。所以在配置 noParse 后一定要重新构建项目查看项目是否正常运行。

在对项目调优时,我们应该在每步优化后重新构建项目,查看项目是否正常运行。不要全部优化操作后再构建项目,以免出现问题后无法定位在哪步调优出现的。

生成动态链接库(DLLPlugin)

在引入一些第三方模块,例如 vuereactangular 等框架,这些框架模块的的代码一般不会发生变化,但是每次打包都会重新解析这些模块非常浪费性能。

可以通过 DLLPlugin 将这些模块单独打包成动态链接库,只构建一次。后面只构建业务层代码复用动态链接库,可以大大提高构建速度。

这个功能涉及两个插件

DllPlugin

使用一个单独的webpack配置文件配置DLLPlugin插件打包创建dll文件,并且还创建一个manifast.json(这个文件就是dll文件使用说明书)。DllReferenceolugin使用该json文件来做依赖映射

  • context(可选):manifast文件中请求的上下文,默认为改webpack文件上下文
  • name:公开的dll函数名称,要与output.library保持一致即可
  • path:mainfast.json生成的路径

DllReferencePlugin

在主webpack配置文件中配置该插件,这个插件通过引用之前构建好的dll文件,自动引入之前被打包好的模块

  • context:mainfast文件中请求的上下文
  • mainfast:DllPlugin插件生成的mainfest.json
  • content(可选):请求的映射模块id(默认为mainfest.content)
  • name(可选):dll暴露的名称
  • scope(可选):前缀用于访问dll的内容
  • sourceType(可选):dll是如何暴露(libraryTarget)

这里结合 VueReact 介绍下这两个插件该如何配置

Vue生成动态链接库

  • 安装
    npm i vue vue-router -S

  • 修改 index.html

    <body>
      <div id="app">{{msg}}
        <div>
          <router-link to="/">home页面</router-link>
          <router-link to="/about">about页面</router-link>
          <router-view />
        </div>
      </div>
    </body>
    
  • 初始化Vue

    // src/main.js  
      // runtime-only版 vue.js:runtime-only版无法解析template 必须配置相关loader
      // import Vue from 'vue'
      // runtime-compiler版 vue.js
    import Vue from 'vue/dist/vue.js'
    new Vue({
      el: "#app",
      data() {
        return {
          msg: "1111"
        }
      }
    })
    
  • 配置路由

    // src/main.js   
    import VueRouter from "vue-router"
    Vue.use(VueRouter)
    // 新建 Home、About组件
    const Home = {
      template: "<h2>这是home</h2>"
    }
    const About = {
      template: "<h2>这是about</h2>"
    }
    // 新建路由匹配规则
    const routes = [
      {
        path: "/",
        component: Home
      },
      {
        path: "/about",
        component: About
      }
    ]
    // 新建路由实例
    const router = new VueRouter({ routes })
    new Vue({
      el: "#app",
      // 挂载路由
      router,
      data() {
        return {
          msg: "1111"
        }
      }
    })
    
  • 新建 config/webpack.vue.js
    config/webpack.vue.js 是专门将Vue全家桶打包成Dll文件

    • 配置入口,将多个要做成dll库的模块都引入进来
    • 配置出口,一定要设置library属性,将打包好的结果暴露到全局
    • 配置plugin,设置打包后的dll文件名和manifest文件所在位置
      // config/webpack.vue.js  
      const path = require("path")
      const webpack = require("webpack")
      module.exports = {
        entry: {
          // vue库相关入口
          vue: [
            // 如果不写全 默认采用的vue.esm.js
            'vue/dist/vue.js',
            'vue-router'
          ]
        },
        output: {
          path: path.resolve(__dirname, "../dist"),
          // 使用占位符语法
          filename: "[name]_dll.js"
        },
        plugins: [
          // 通过DllPlugin 插件将模块打包成 dll文件
          new webpack.DllPlugin({
            // 打包后dll文件对外暴露的变量名
            name: "[name]_dll",
            // dll文件使用说明书
            path: path.resolve(__dirname, "../dist/manifest.json")
          })
        ]
      }
      
  • 新增打包dll文件的脚本

      "scripts": {
        //  打包dll文件
        "build:vue": "npx webpack --config ./config/webpack.vue.js"
      }
    
  • 执行 npm run build:vue 得到dll文件,这Vue全家桶再也不用打包了

    image.png

  • 项目中使用动态链接库
    前面生成了dll文件,需要在webpack配置文件中注册使用dll文件

    // config/webpack.base.js  
    module.exports = {
      plugins: [
        // 使用dll文件时,不能使用CleanWebpackPlugin插件 它会清空dll等所有dist目录
        // new CleanWebpackPlugin(),
        // 使用dll依赖
        new Webpack.DllReferencePlugin({
          // 指定dll使用说明书 路径
          manifest: path.resolve(__dirname, "../dist/manifest.json")
        })
      ]
    }
    

= html引入dll模块依赖 前面我们在业务代码中注册并使用dll模块,webpack打包时默认不会加载dll模块,我们需要借助 add-asset-html-webpack-plugin 插件解决这个问题

  • 安装 npm i add-asset-html-webpack-plugin -D
  • 配置插件
    // webpack.base.js  
    const AddAssetHtmlWebpackPlugin = require("add-asset-html-webpack-plugin")  
    module.exports = {
      plugins: [
        // 在打包后的html中 插入资源,这个插件一定要配置在 HtmlPlugin插件之后
        new AddAssetHtmlWebpackPlugin({
          // 加载dll 资源
          filepath: path.resolve(__dirname, "../dist/vue_dll.js"),
          // 必须加这个字段 否则打包后dll引用路径前会莫名加auto
          publicPath: './',
        })
      ]
    }
    
    • npm run build 重新打包下

      image.png

      对比一下不使用dll的时间,由 3701ms 涨到 4731ms

      image.png

      image.png

React生成动态链接库

  • 安装
    npm i react react-dom -S

  • 修改 index.html

    <body>
      <div id="app"></div>
    </body>
    
  • 新建 **main.react.js **

    import React from 'react'
    import ReactDOM from 'react-dom'
    // 新建h1 虚拟DOM
    const node = React.createElement("h1", null, "学习好累呀,我想睡大觉!")
    // 通过ReactDOM 将虚拟DOM渲染到 app节点中
    ReactDOM.render(node, document.getElementById("app"))
    
  • 修改入口文件、修改dll库引入路径

    // config/webpack.base.js  
    module.exports = {
      entry: {
        // 使用Vue相关库
        // main: "./src/main.js",
        // 使用React相关库
        main: "./src/main.react.js",
      },
      plugins: [
        // // 使用dll依赖
        new Webpack.DllReferencePlugin({
          // 指定dll使用说明书 路径
          manifest: path.resolve(__dirname, "../dist/manifest.json")
        }),
        // 在打包后的html中 插入资源,这个插件一定要配置在 HtmlPlugin插件之后
        new AddAssetHtmlWebpackPlugin({
          // filepath: path.resolve(__dirname, "../dist/vue_dll.js"),
          // 加载react dll资源
          filepath: path.resolve(__dirname, "../dist/react_dll.js"),
          // 必须加这个字段 否则打包后dll引用路径前会莫名加auto
          publicPath: './',
        })
      ]
    }
    
  • 新建打包 react 动态链接库的 webpack 配置文件 (webpackconfig/webpack.react.js)

    const path = require("path")
    const webpack = require("webpack")
    module.exports = {
      mode: "production",
      entry: {
        // react库相关入口
        react: [
          'react',
          'react-dom'
        ]
      },
      output: {
        path: path.resolve(__dirname, "../dist"),
        // 使用占位符语法
        filename: "[name]_dll.js",
        // dll库暴露出的全局变量 供业务模块使用
        library: "[name]_dll"
      },
      plugins: [
        // 通过DllPlugin 插件将模块打包成 dll文件
        new webpack.DllPlugin({
          // 打包后dll文件对外暴露的变量名
          name: "[name]_dll",
          // dll文件使用说明书
          path: path.resolve(__dirname, "../dist/manifest.json")
        })
      ]
    }
    
  • 配置打包react dll库脚本
    "build:react": "npx webpack --config ./config/webpack.react.js"

  • 手动删除dist,执行 npm run build:react 打包react dll库

  • 执行 npm run build,构建整个项目

    image.png

多进程打包

webpack基于node环境工作,使用单进程模式打包。在打包众多资源的情况下,可能会出现效率低下的问题,可以通过 thread-loader 实现多进程打包。

注意:进程的开启(开启大概600ms)、进程间的通讯都有时间成本,不建议小项目使用多进程打包

  • 安装 npm i thread-loader -D
  • 配置loader
    // webpack.base.js  
    module: {
      rules:[
        {
          test: /\.js$/i,
          exclude: /(node_modules|bower_components)/,
          use: [
            // 使用 thread-loader
            "thread-loader",
            
            // {
            //   loader: "thread-loader",
            //   options: {
            //     // 开启进程的数量
            //     workers: 2
            //   }
            // },
            {
              loader: 'babel-loader',
              options: {
                presets: ['@babel/preset-env'],
                plugins: ["@babel/plugin-transform-runtime"]
              }
            }
          ]
        },
      ] 
    }
    

浏览器缓存

在浏览器访问页面时,会将资源缓存到硬盘或内存中,这样再次访问html中的外部资源会从本地获取,这样避免了重复从服务器请求资源极大地提高了效率。

此时会出现一个问题,就是项目开发新版本,更新业务代码上线时。由于浏览器缓存的缘故,浏览器并不会访问最新的js文件等资源,这会导致页面无法更新。

我们最好将项目代码作区分,将第三方模块和业务代码单独打包。通过构建出口 placeholder 语法,为构建输出的js文件名增加随机hash字符串。这样每次打包构建的文件名,每次访问页面也是从最新的文件访问资源。

placeholder语法变量

  • name:使用元文件名
  • hash:每次webpack打包随机生成哈希值
  • chunkhash:不同的chunk的hash值不同,同一次打包可能生成不同的chunk
  • contenthash:不同内容的hash值不同,同一个chunk中可能有不同的内容

image.png

打包分析

项目构建完成后,我们可以借助一下工具对打包完成的bundle进行分析。

通过打包结果信息json文件进行分析

  • 配置script脚本指令,生成打包信息的json文件
    使用 --profile --json指令已json形式将打包信息输出到某个json文件中
    "build:analyse": "npx webpack --profile --json > stats.json --config ./config/webpack.prod.js"

  • 执行 npm run build:analyse 构建项目并生成构建信息文件

  • 打开webpack官方分析工具上传 stats.json

    链接地址:webpack.github.io/analyse/

    image.png

image.png

配置分析插件进行打包分析

  • 安装
    npm i webpack-bundle-analyzer -D

  • 配置插件 (config/webpack.prod.js)

    // 导入bundle分析插件
    const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer")
    const prodConfig = merge(baseConfig, {
    mode: "production",
    plugins: [
      // 初始化插件
      new BundleAnalyzerPlugin()
    ]})
    
  • npm run build重新构建后,会自动运行一个分析服务

    image.png