前端工程化

webpack5 优化产出代码

2021-07-06  本文已影响0人  miao8862

webpack产出代码做性能优化的效果无非是下面三种效果:

下面将通过介绍以下方案来达到以上目的:

  1. 小图片使用base64编码:url-loader
  2. bundlehash:应用缓存
  3. 使用import懒加载模块
  4. 使用splitChunks提取公共组件
  5. 使用IgnorePlugin忽略无用的模块
  6. 使用CDN加速:添加publicPath
  7. 使用production模式:
    • 自动开启代码压缩,使得打包体积更小
    • Vue,React等会自动删除调试代码(比如开发环境的warning警告),体积会比开发时更小
    • 启动tree-shaking,删除无用的代码
  8. 使用作用域提升:Scope Hosting

其实里面很多方案,我们在基础配置和高级配置时已经提及过:

小图片base64编码

通过设置url-loader的阀值来控制,小于阀值的图片使用base64编码,以此来减少http请求

  module: {
    noParse: /jquery|lodash/,
    rules: [
      {
        test: /\.(jpg|png|jpeg|gif)$/,
        use: {
          loader:'url-loader',
          options: {
            limit: 8 * 1024,    //限制 8kb 以下使用base64
            esMoudle: false,
            name: '[name]-[hash:10].[ext]',
            // 打包到/images目录下
            outputPath: 'images'
          }
        }
      }
    ]
  },

bundle 加 hash 值

给产出的文件增加hash值,当内容没有变化时,生成的文件名将不会变化,浏览器就会应用缓存,从而提升加载速度

  output: {
    filename: '[name].[contenthash:8].js',
    path: distPath  // 输出目录
  },

懒加载

使用import方式懒加载组件

// index.js
setTimeout(() => {
  // 直接使用import导入即可,这样加载的模块,相当于一个独立的chunk存在
  import('./common/dynamicData.js').then(res => {
    console.log(res.default.msg);
  })
}, 1000)

提取公共代码

optimization中配置splitChunks属性,设置cacheGroups分组,一般分为两个组:verdors(用于提取第三方库)和common(用于提取自定义的公共模块)
针对第三方库,几乎不会改变,提取出来后,改动业务代码不会动到第三方库的打包文件,这样,就能命中缓存,加快加载速度
针对公共模块,只用加载一次,就可以在多个模块中使用

// webpack.prod.js
  optimization: {
    splitChunks: {
      chunks: 'all', // 表示要分割的chunk类型:initial只处理同步的; async只处理异步的;all都处理
      // 缓存分组
      cacheGroups: {
        // 第三方模块
        verdors: {
          name: 'verdor', // chunk名称
          test: /node_modules/,  // 设置命中目录规则
          priority: 1, // 优先级,数值越大,优先级越高
          minSize: 0, // 小于这个大小的文件,不分割
          minChunks: 1 // 最少复用几次,这里意思是只要用过一次就分割出来
        },
        // 公共模块
        common: {
          name: 'common',
          minChunks: 2,
          priority: 0,
          minSize: 0,
          minChunks: 2  // 只要引用过2次,就分割成公共代码
        }
      }
    }
  }

IgnorePlugin

IgnorePlugin可以帮助我们忽略某些库不需要的模块,从而实现按需加载,减少打包体积

// webpack.prod.js或webpack.common.js都可以
const webpack = require('webpack')
const prodConfig = {
  plugins: [
    // webpack4写法
    // new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)

    // webpack5写法
    new webpack.IgnorePlugin({
      resourceRegExp: /^\.\/locale$/,   // 忽略content设置的库中的某个文件夹
      contextRegExp: /moment$/, // 要被忽略某部分内容的库
    })
  ]
}

使用 CDN 加速

在输出文件配置中将CDN路径添加至publicPath中(针对js和css文件);
针对图片,也可以在loader的配置中添加publicPath

  output: {
    // filename: 'bundle.[chunkhash].js',  // 输出文件名,一般要加上hash
    filename: '[name].[contenthash:8].js',
    path: distPath,  // 输出目录
    publicPath: 'http://cdn.xxx.com'
  },

// 或者是将图片放到cdn中
module: { 
  rules: [
    {
        test: /\.(jpg|png|jpeg|gif)$/,
        use: {
          loader:'url-loader',
          options: {
            limit: 8 * 1024,    //限制 8kb 以下使用base64
            esMoudle: false,
            name: '[name]-[hash:10].[ext]',
            // 打包到/images目录下
            // outputPath: 'images', // 有了publicPath,会自动忽略outputPath,这是针对放在静态资源服务器上的目录
            publicPath: 'http://cdn.xxx.com'
          }
        }
      }
  ]
}

这样打包出来的index.html中的 静态资源,会自动添加CND地址前缀

<script defer="defer" src="http://cdn.xxx.com/verdor.03535a89.js"></script>
<script defer="defer" src="http://cdn.xxx.com/index.c6d170da.js"></script>
图片放至cdn服务器

这一步还需要我们将对应的资源文件给放至对应的CND服务器地址中

启用production模式

mode: 'production', // 生产环境

启用production模式,会使打包的体积更小,webpack4以后,只要开启生产模式,就会自己帮我们实现以下功能:

  1. 自动开启代码压缩,比如删除注释、空格等,当然可以视项目情况,来判断要不要使用webpack-parallel-uglify-plugin开启多进程压缩
  2. Vue,React等会自动删除调试代码(比如开发环境的warning警告),体积会比开发时更小
  3. 启用tree-shaking,删除无用的代码,当注意,只有在ES Module才可以使用
    • ·ES6 Module是静态引入,编译时时就引入模块的,所以才可以做静态分析,实现tree-shaking
    • Commonjs是动态引入的,是执行时才引入,需要执行代码时,才知道引不引入,所以没法做静态分析,也就无法实现tree-shaking

Scope Hosting

webpack分析依赖打包出来的文件,一般是一个模块生成一个函数,比如:

// test.js
export default 'module test'
// index.js
import test from './module-test'
console.log(test)

打包后大概是这样的:

[
  function (module, exports, require) {
    var module_test = require(1)
    console.log(module_test['default'])
  },
  function (module, exports, require) {
    exports['default'] = 'module test'
  }
]

这样,当我们引用的模块很多时,我们打包时就会产生很多个函数,一个函数会生成一个函数作用域,当多个模块时,就会产生多个函数作用域,这样步骤创建函数作用域和执行函数、销毁函数,对js代码的执行和内存是很不友好的

但如果我们按引用顺序,把它们合成一个函数来执行,所有操作都在一个函数执行,那么性能就会好很多,比如

// bundle.js
[
  function (module, exports, require) {
    // ./module-a.js
    var module_test_defaultExport = 'module test'

    // ./index.js
    console.log(module_test_defaultExport)
  }
]

它带来的好处有:

但是,Scope Hosting的实现也是有限制的,它必须是在ES Module环境下使用

知道了原理,我们来看下Scope Hosting如何配置,它是webpack内置的一个模块,可以直接使用:

const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin')
  resolve: {
    // 针对npm中的第三方库模块,优先采用jsnext:main中指向ES6模块化语法的文件
    mainFields: ['jsnext:main', 'browser', 'main']
  },
  plugins: [
    // 开启scope hoisting,作用域提升
    new ModuleConcatenationPlugin()
  ]
上一篇下一篇

猜你喜欢

热点阅读