zhaobinglong / myBlog

https://zhaobinglong.github.io/myBlog/
MIT License
7 stars 0 forks source link

web性能优化之webpack #86

Open zhaobinglong opened 3 years ago

zhaobinglong commented 3 years ago

webpack性能优化方向

  1. 打包更快
  2. 打包更小

分析webpack打包速度

// 导入速度分析插件
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");

// 实例化插件
const smp = new SpeedMeasurePlugin();

module.exports = {
    configureWebpack: smp.wrap({
        plugins: [
            // 这里是自己项目里需要使用到的其他插件
            new yourOtherPlugin()
        ]
    })
}

image

分析webpack打包体积


// 导入体积分析插件
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;

module.exports = {
    configureWebpack: smp.wrap({
        plugins: [
            // 实例化体积分析插件
            new BundleAnalyzerPlugin()
        ]
    })
}

// 在package中增加如下命令
  "scripts": {
    "dev": "vue-cli-service serve",
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "analyz": "NODE_ENV=production npm_config_report=true npm run build"
  },

// 执行,默认会打开http://127.0.0.1:8888作为展示。
npm run build

参考

https://www.cnblogs.com/lzkwin/p/11878509.html https://www.jianshu.com/p/481e7214a134 https://www.jianshu.com/p/b358a91bdf2d

zhaobinglong commented 3 years ago

打包更小之:第三方包CDN加载【重要】

对于vue,vuex,vue-router,axios,echarts,swiper等我们可以利用webpack的externals参数来配置,这里我们设定只需要在生产环境中才需要使用。

entry: {
        entry: './src/main.js',
        vendor: ['vue', 'vue-router', 'vuex', 'element-ui']
    },

    // externals这里去扩展需要加载的第三方包,这样它们就不会再打包到js中
    externals: {
        echarts: 'echarts',
        _: 'lodash',
       jquery: "jQuery" 
    },

加载CDN后打包报错:Error: Cannot call .tap() on a plugin that has not yet been defined. Call plugin('html').use() first.

打包更小之:函数库按需导入(重要)

// 以loads库为例,如果只是使用到了库中的一个函数
import { name } from 'loadsh'

//不要这样写
import _ from 'loadsh'

打包更小之:tree-shaking

本质上是Webpack 跟踪整个应用程序的 import/export 语句,因此,如果它看到导入的东西最终没有被使用,它会认为那是“死代码”,并会对其进行 tree-shaking 。

在编写支持 tree-shaking 的代码时,导入方式非常重要。应该避免将整个库导入到单个 JavaScript 对象中。当这样做时,是在告诉 Webpack 你需要整个库, Webpack 就不会摇它。以流行的库 Lodash 为例。一次导入整个库是一个很大的错误,但是导入单个的模块要好得多。当然,Lodash 还需要其他的步骤来做 tree-shaking,但这是个很好的起点。

// 全部导入 (不支持 tree-shaking)
import _ from 'lodash';

// 具名导入(支持 tree-shaking)
import { debounce } from 'lodash';

// 直接导入具体的模块 (支持 tree-shaking)
import debounce from 'lodash/lib/debounce';

// 配置webpack启动tree shake优化 Base Webpack Config for Tree Shaking
const config = {
 mode: 'production',
 optimization: {
  usedExports: true,
  minimizer: [
   new TerserPlugin({...})
  ]
 }
};

打包更小之:并行压缩terser-webpack-plugin

打包更小之:polyfill 动态服务

动态 polyfill 指的是根据不同的浏览器,动态载入需要的 polyfill。 Polyfill.io 通过尝试使用 polyfill 重新创建缺少的功能,可以更轻松地支持不同的浏览器,并且可以大幅度的减少构建体积。Polyfill Service 原理:识别 User Agent,下发不同的 Polyfill

// 在html头部插入下面的代码
<script crossorigin="anonymous" src="https://polyfill.io/v3/polyfill.min.js"></script>

代码打包更小之:compression压缩

const CompressionPlugin = require("compression-webpack-plugin")

  chainWebpack: config => {      
    // 开启js、css 文件的 gzip 压缩。
    // 注意,gzip 压缩需要后端配合使用。如果后端没有开发 gzip 。请关闭功能。
    // 更多相关知识请查阅:
    // https://blog.csdn.net/weixin_44869002/article/details/106717640
    if (process.env.NODE_ENV === 'production') {
      config.plugin('compressionPlugin')
      .use(new CompressionPlugin({
        test:/\.js$|\.html$|.\css/, // 匹配文件名
        threshold: 10240, // 对超过10k的数据压缩
        deleteOriginalAssets: false // 不删除源文件
      }))
    }
  },

参考

https://github.com/financial-times/polyfill-service.

zhaobinglong commented 3 years ago

占楼

zhaobinglong commented 3 years ago

打包更快之:DLLPlugin(dynamic link library(动态链接库))

对于vue项目而言,不可避免的会使用一些第三方的库,这些库本身并不会运行,我们也不会修改这些库的代码,但是每当我们修改了业务代码之后,这些库也会被重新打包,极大的浪费了时间,这时我们就需要使用工具预先把静态资源提前打包,以后修改源文件再打包时就不会打包这些静态资源文件了。而webpack在打包的时候,对于一些不经常更新的第三方库,比如 d3,lodash,我们希望能和自己的代码分离开,也就是单独打包。

打包更快之:多进程打包(thread-loader)

使用多进程并行运行来提高构建速度。并发运行的默认数量为 os.cpus().length - 1,可以显著加快构建速度,因此强烈推荐开启多进程,把这个 loader 放置在其他 loader 之前, 放置在这个 loader 之后的 loader 就会在一个单独的 worker【worker pool】 池里运行,一个worker 就是一个nodeJS 进程【node.js proces】,每个单独进程处理时间上限为600ms,各个进程的数据交换也会限制在这个时间内。

rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        // 创建一个 js worker 池
        use: [ 
          'thread-loader',
          'babel-loader'
        ] 
      }
]

打包更快之:利用webpack缓存

module.exports = {
  module: {
    rules: [
      {
        test: /\.ext$/,
        use: ['cache-loader', ...loaders],
        include: path.resolve('src'),
      },
    ],
  },
};

打包更快之:code splitting (代码分割)

如果 插件 1kb,业务逻辑代码 1Kb,改动一次业务代码,重新打包一次,每次打包就会是 2KB,如果我们采用代码分割的方式,把插件打包到一个js中,业务逻辑代码打包到一个js中,每次改动业务逻辑代码,就会只打包业务逻辑 js,只有1Kb,节省打包的时间,这就是 代码分割的主要意思。

optimization: {
    usedExports: true,
    splitChunks: {
      chunks: 'all'  // 代码分割
    }
  },

打包更快之:减少搜索



## 参考
https://juejin.im/post/6844904071736852487
https://www.npmjs.com/package/speed-measure-webpack-plugin
zhaobinglong commented 3 years ago

占楼

zhaobinglong commented 3 years ago

删除没有使用的依赖

很多时候,我们由于项目人员变动比较大,参与项目的人也比较多,在分析项目时,我发现了一些问题,诸如:有些文件引入进来的库没有被使用到也没有及时删除,例如:

import a from 'abc';

在业务中并没有使用到a 模块,但webpack 会针对该import 进行打包一遍,这无疑造成了性能的浪费。

zhaobinglong commented 3 years ago

修复热更新有时候失败

module.exports = {
  chainWebpack: config => {
    // 修复HMR
    config.resolve.symlinks(true);
  }
};
zhaobinglong commented 3 years ago

抽取公共代码

webpack默认打包js代码时,是将从入口js模块开始,将入口js模块所依赖的js以及模块逐层依赖的模块,全部以入口js模块为主体全部打包至生成的对应js文件中。即使是多个js入口依赖链模块依赖了同一个模块也会被重复打包至不同的js文件中,这种打包方式会产生比较大的代码冗余。

实例

const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
new CommonsChunkPlugin({
  // 从哪些 Chunk 中提取
  chunks: ['a', 'b'],
  // 提取出的公共部分形成一个新的 Chunk,这个新 Chunk 的名称
  name: 'common'
})

new webpack.optimize.CommonsChunkPlugin({
      name: 'app',
      async: 'vendor-async',
      children: true,
      minChunks: 3
}),
zhaobinglong commented 3 years ago

占楼