性能文章>webpack打包优化的3种方法>

webpack打包优化的3种方法转载

2年前
436911

导语

webpack作为一款主流的打包工具,给用户预留了足够多的配置自由度,也存在很多优化空间,今天我们就一下几点讨论webpack打包优化。

 

正文

抽取公共代码

目标:把功能逻辑相同的代码进行单独打包,缩小各业务模块打包文件体积,实现打包文件并行加载,优化页面性能


适用场景:多页应用,抽离各页面模块公共代码(包括第三方依赖)

webpack提供了三种方式抽离公共代码:

方法一:optimization.splitChunks

假设我们的项目结构是这样的

webpack打包优化的3种方法数据图表-heapdump性能社区


index 和 other 分别作为两个入口文件,且都引用了a,b两个模块。我们可以使用webpack的optimization配置抽离a,b到单独的模块,index 和 other 只需要引入抽离的公共模块即可,不需要再单独引入a,b。配置如下:

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
  mode: 'development',
  entry: { index: './src/index.js', other: './src/other.js' },
  output: {
    filename: '[name][hash:8].js',
    path: path.resolve(__dirname, 'dist'),
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        common: {
          //开始抽离起点,initial表示入口处开始
          chunks: 'initial',
          //生成 chunk 的最小体积
          minSize: 0,
          //拆分前最小引用次数
          minChunks: 2,
          //公共模块名称
          name: 'common',
        }
      },
    },
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: 'babel-loader',
      },
    ],
  },
  resolve: {
    extensions: ['.jsx', '.mjs', '.js', '.json'],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      filename: 'index.html',
      chunks: ['index'],
    }),
    new CleanWebpackPlugin(),
  ],
}


然后 yarn build 打包,查看打包文件,你会发现公共模块被抽取,生成了index.js,other.js以及common.js

webpack打包优化的3种方法数据图表-heapdump性能社区


此刻,如果我们还引用了第三方模块,如jquery,moment,lodash等,也可以对第三方模块进行单独抽取。optimization配置如下:

 optimization: {
    splitChunks: {
      cacheGroups: {
        common: {
          chunks: 'initial',
          minSize: 0,
          minChunks: 2,
          name: 'common',
        },
        vendor: {
          chunks: 'initial',
          //抽取优先级,默认值是-20,意思是先抽取第三方模块,再抽取自己的公共代码,不然第三方模块也会被抽取到公共代码
          priority: 1,
          //正则匹配只抽取mode_modules中的模块
          test: /node_modules/,
          minSize: 0,
          name: 'vendor',
          minChunks: 2
        }
      },
    },
  }


然后我们打包,第三方模块也被成功抽取

webpack打包优化的3种方法数据图表-heapdump性能社区


方法二:entry手动配置

利用entry的多入口配置方式和dependOn属性,指定各入口的依赖项。如:

module.exports = {
  mode: 'production',
  entry: {
    index: { import: './src/index.js', dependOn: ['share', 'react'] },
    other: { import: './src/other.js', dependOn: ['share', 'react'] },
    share: 'lodash',
    react: 'react'
  },
  output: {
    filename: '[name].[contenthash:6].js',
    path: path.resolve(__dirname, 'dist'),
    chunkFilename: '[name].[contenthash:6].js'
  }
}


打包时webpack把react和lodash作为单独的入口进行打包,index和other中包含对react和lodash的引用

webpack打包优化的3种方法数据图表-heapdump性能社区


方法三:Dynamic Import

当我们在代码中动态引入其他模块,webpack打包时自动抽离动态引入的模块,例如:图片打包时会多一个文件(默认名字是[id].js)

webpack打包优化的3种方法数据图表-heapdump性能社区

如果我们想自定义动态模块的名字,需配置output.chunkFilename

Dll动态链接库

实际开发中,为了提升打包速度,我们可以把一些不常修改的依赖(如react,react-dom,vue,typescrity等)打包成dll动态链接库,下次build时不再打包这些依赖,达到加速打包的效果。下面以react为例,先看未使用dll的打包效果

webpack打包优化的3种方法数据图表-heapdump性能社区


我们可以看到入口文件包含了react的打包内容,比较大,而且每次build,都会再次对react进行打包
下面我们把react,react-dom打包成dll动态链接库:

创建dll打包配置

如webpack.config.dll.js

const path = require('path')
const webpack = require('webpack')
module.exports = {
  mode: 'development',
  entry: {
    react: ['react', 'react-dom'],
  },
  output: {
    filename: '_dll_[name].js',
    path: path.resolve(__dirname, 'dist'),
    //导出模块名称
    library: '_dll_[name]',
    libraryTarget: 'umd',
  },
  plugins: [
    new webpack.DllPlugin({
      //输出文件名称
      name: '_dll_[name]',
      //指定manifest文件绝对路径
      path: path.resolve(__dirname, 'dist/manifest')
    })
  ]
}


打包dll

npx webpack --config webpack.config.dll.js

也可以在scripts中配置

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


在dist目录下会打包生成_dll_react.js和manifest文件。manifest文件记录了react依赖的映射

webpack打包优化的3种方法数据图表-heapdump性能社区


在html模版文件中引入打包后的dll文件

<!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>
  //写死文件路径
  <script src="/_dll_react.js"></script>
</head>
<body>
  <div id="root"></div>

</body>
</html>


打包我们自己的项目代码

在打包项目代码之前,需要在webpack主配置中添加react依赖映射,如:

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const webpack = require('webpack')
module.exports = {
  mode: 'development',
  devServer: {
    port: 3000,
    hot: true,
    open: true,
    contentBase: path.resolve(__dirname, 'dist'),
    compress: true,
    proxy: {
      '/api': {
        target: 'http://localhost:8888',
        pathRewrite: { '^/api': '' },
      },
    },
  },
  entry: { index: './src/index.js'},
  output: {
    filename: '[name][hash:8].js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: 'babel-loader',
      },
    ],
  },
  resolve: {
    extensions: ['.jsx', '.mjs', '.js', '.json'],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      filename: 'index.html',
      chunks: ['index'],
    }),
    // new CleanWebpackPlugin(),
    new webpack.DllReferencePlugin({
      //映射文件路径
      manifest: path.resolve(__dirname, 'dist/manifest')
    })
  ],
}


配置完成后,当我们打包项目时,webpack会首先根据配置的manifest映射文件查找react依赖,如果找到了,便不再对react进行打包;如果找不到,会打包react。我们可以查看一下打包后的文件大小

webpack打包优化的3种方法数据图表-heapdump性能社区

可以看到,使用dll打包后,项目入口文件小了很多(因为不再包含react打包内容)

多线程打包

由于webpack打包是单进程,当项目文件和依赖很多时,webpack打包速度会很慢。为此,我们可以引入happypack多线程(Thread)打包。happypack会把webpack的打包进程拆分成多个线程,各线程完成文件解析和打包任务,把结果返回给主进程。


安装happypack

yarn add happypack -D


配置happypack

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const Happypack = require('happypack')
const webpack = require('webpack')
module.exports = {
  mode: 'development',
  devServer: {
    port: 3000,
    hot: true,
    open: true,
    contentBase: path.resolve(__dirname, 'dist'),
    compress: true,
    proxy: {
      '/api': {
        target: 'http://localhost:8888',
        pathRewrite: { '^/api': '' },
      },
    },
  },
  entry: { index: './src/index.js' },
  output: {
    filename: '[name][hash:8].js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: 'Happypack/loader?id=js',
      },
      {
        test: /\.less$/,
        use: 'Happypack/loader?id=style',
      },
    ],
  },
  resolve: {
    extensions: ['.jsx', '.mjs', '.js', '.json'],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      filename: 'index.html',
      chunks: ['index'],
    }),
    // new CleanWebpackPlugin(),
    new webpack.DllReferencePlugin({
      manifest: path.resolve(__dirname, 'dist/manifest'),
    }),
    new Happypack({
      id: 'js',
      use: ['babel-loader'],
    }),
    new Happypack({
      id: 'style',
      use: ['style-loader', 'css-loader', 'less-loader'],
    }),
  ],
}


happypack参数说明:

  • id:happy plugin的唯一标识,module.rules中使用plugin时需传入id;
  • loaders:loader数组,也可以跟module.rules写法一样用use;
  • threads:happy plugin模块开启的线程数,默认是3个
  • 其他优化项

tree-shaking

webpack打包时,检测未引用代码(dead code),并安全地删除,不打包到bundle的优化机制

发挥webpack的tree shaking优势,必须满足以下条件:


1、使用 ES2015 模块语法(即 import 和 export),因为ES Module是静态分析,代码解析的时候就知道有哪些import和export,而commonJs是动态加载,只有在执行的时候才能加载引入的模块。所以tree shaking机制对commonJs模块无效;

2、必须是生产环境(production);
3、如果不确定未引用代码是否产生副作用,需使用sideEffects指定,默认值是false,即所有未引用的代码都不会产生副作用


ps:sideEffects可以在package.json中指定,也可以在webpack的module配置中指定
scope hosting

webpack中自动省略可以简化的代码,如

const a = 1
const b = 2
const c = 3
console.log(a+b+c)

//打包后
console.log(6)


noParse

防止 webpack 解析那些任何与给定正则表达式相匹配的文件。忽略的文件中 不应该含有 import, require, define 的调用,或任何其他导入机制。忽略大型的 library 可以提高构建性能


简单来说就是明确告诉webpack,哪些依赖不需要解析,从而提升构建速度。如:

module.exports = {
  //...
  module: {
    noParse: /jquery|lodash/,
  },
}


IgnorePlugin

忽略正则表达式或过滤器函数匹配的模块
在我们引用一些第三方模块的时候,其实很多功能我们并不关心,也没有用到,在webpack打包的时候,也会把这些没用的功能打包,从而使构建速度变慢。


这时候,我们就可以使用webpack.IgnorePlugin明确告诉webpack,哪些依赖不需要打包。如moment的locale语言包,我们只用到了中英文,而不想引入其他没用到的语言。我们可以这样配置:

module.exports = {
  //...
  plugins: [
    new webpack.IgnorePlugin({
      resourceRegExp: /^\.\/locale$/,
      contextRegExp: /moment$/,
    })
  ]
}



然后我们只需要在使用moment多语言的地方手动设置即可

import moment from 'moment'
import 'moment/locale/zh-cn'
moment.locale('zh-cn')
console.log(moment().endOf('day').fromNow())


以上就是近期整理的webpack打包优化,不尽之处,欢迎指摘!!!

 

更多思考

本篇主要介绍了webpack打包优化的3种方法,更多关于webpack的优化内容大家可以阅读以下内容学习

用Webpack这15个点,速度提升70%,体积减小80%!

webpack构建速度和体积优化策略

 

点赞收藏
分类:标签:
Meguro
请先登录,查看1条精彩评论吧
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步
1
1