webpack性能优化

简介: webpack 性能优化

在这里插入图片描述

当我们不用cli,而是自己搭建项目架子的时候,会用到webpack构建我们的项目,在用webpack构建项目的时候,过长的打包编译时间和庞大冗余的代码会让我们感到头疼。所以优化webpack性能成为了不可或缺的一部分。下面我们一起来探讨webpack性能优化细节。

影响webpack性能的因素

如果我们在构建项目中使用了大量的loader和第三方库,会使我们构建项目的时间过长,打包之后的代码体积过大。于是乎,就遇到了webpack 的优化瓶颈,总结webpack影响性能主要是两个方面:

1 webpack 的构建过程太花时间

2 webpack 打包的结果体积太大

webpack 优化解决方案

针对影响webpack性能的因素,有了对应的解决方案。

1 合理使用loader

用 include 或 exclude 来帮我们避免不必要的转译,优化loader的管辖范围,比如 webpack 官方在介绍 babel-loader 时给出的示例:

module: {
   
   
  rules: [
    {
   
   
      test: /\.js$/,
      exclude: /(node_modules|bower_components)/,
      use: {
   
   
        loader: 'babel-loader',
        options: {
   
   
          presets: ['@babel/preset-env']
        }
      }
    }
  ]
}

2 缓存babel编译过的文件

loader: 'babel-loader?cacheDirectory=true'
如上,我们只需要为 loader 增加相应的参数设定。选择开启缓存将转译结果缓存至文件系统,则至少可以将 babel-loader 的工作效率提升两倍。

3 DllPlugin类库引入

处理第三方库的姿势有很多,其中,Externals 会引发重复打包的问题;而CommonsChunkPlugin 每次构建时都会重新构建一次 vendor;出于对效率的考虑,我DllPlugin是最佳选择。

DllPlugin 是基于 Windows 动态链接库(dll)的思想被创作出来的。这个插件会把第三方库单独打包到一个文件中,这个文件就是一个单纯的依赖库。这个依赖库不会跟着你的业务代码一起被重新打包,只有当依赖自身发生版本变化时才会重新打包。

用 DllPlugin 处理文件,要分两步走:

基于 dll 专属的配置文件,打包 dll 库

基于 webpack.config.js 文件,打包业务代码

以一个基于 React 的简单项目为例,我们的 dll 的配置文件可以编写如下:

const path = require('path')
const webpack = require('webpack')

module.exports = {
   
   
    entry: {
   
   
      // 依赖的库数组
      vendor: [
        'prop-types',
        'babel-polyfill',
        'react',
        'react-dom',
        'react-router-dom',
      ]
    },
    output: {
   
   
      path: path.join(__dirname, 'dist'),
      filename: '[name].js',
      library: '[name]_[hash]',
    },
    plugins: [
      new webpack.DllPlugin({
   
   
        // DllPlugin的name属性需要和libary保持一致
        name: '[name]_[hash]',
        path: path.join(__dirname, 'dist', '[name]-manifest.json'),
        // context需要和webpack.config.js保持一致
        context: __dirname,
      }),
    ],
}

编写完成之后,运行这个配置文件,我们的 dist 文件夹里会出现这样两个文件:

vendor-manifest.json
vendor.js
vendor.js 不必解释,是我们第三方库打包的结果。这个多出来的 vendor-manifest.json,则用于描述每个第三方库对应的具体路径,我这里截取一部分给大家看下:

{
   
   
  "name": "vendor_397f9e25e49947b8675d",
  "content": {
   
   
    "./node_modules/core-js/modules/_export.js": {
   
   
      "id": 0,
        "buildMeta": {
   
   
        "providedExports": true
      }
    },
    "./node_modules/prop-types/index.js": {
   
   
      "id": 1,
        "buildMeta": {
   
   
        "providedExports": true
      }
    },
    ...
  }
}

随后,我们只需在 webpack.config.js 里针对 dll 稍作配置:

const path = require('path');
const webpack = require('webpack')
module.exports = {
   
   
  mode: 'production',
  // 编译入口
  entry: {
   
   
    main: './src/index.js'
  },
  // 目标文件
  output: {
   
   
    path: path.join(__dirname, 'dist/'),
    filename: '[name].js'
  },
  // dll相关配置
  plugins: [
    new webpack.DllReferencePlugin({
   
   
      context: __dirname,
      // manifest就是我们第一步中打包出来的json文件
      manifest: require('./dist/vendor-manifest.json'),
    })
  ]
}

像dll第三方类库的本质也是减少打包类库次数 , 实现代码抽离 ,减少打包以后的文件体积。

4 happypack多进程编译

我们都知道nodejs是单线程。无法一次性执行多个任务。这样会使得所有任务都排队执行。happypack可以根据cpu核数优势,建立子进程child_process,充分利用多核优势解决这个问题。提高了打包的效率。

const HappyPack = require('happypack')
// 手动创建进程池
const happyThreadPool =  HappyPack.ThreadPool({
   
    size: os.cpus().length })

module.exports = {
   
   
  module: {
   
   
    rules: [
      ...
      {
   
   
        test: /\.js$/,
        // 问号后面的查询参数指定了处理这类文件的HappyPack实例的名字
        loader: 'happypack/loader?id=happyBabel',
        ...
      },
    ],
  },
  plugins: [
    ...
    new HappyPack({
   
   
      // 这个HappyPack的“名字”就叫做happyBabel,和楼上的查询参数遥相呼应
      id: 'happyBabel',
      // 指定进程池
      threadPool: happyThreadPool,
      loaders: ['babel-loader?cacheDirectory']
    })
  ],
}

happypack成功,启动了三个进程编译。加快了loader的加载速度。

5 scope Hoisting

scope Hoisting的作用是分析模块之前的依赖关系 , 把打包之后的公共模块合到同一个函数中去。它会代码体积更小,因为函数申明语句会产生大量代码;代码在运行时因为创建的函数作用域更少了,内存开销也随之变小。

const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');

module.exports = {
   
   
  resolve: {
   
   
    // 针对 Npm 中的第三方模块优先采用 jsnext:main 中指向的 ES6 模块化语法的文件
    mainFields: ['jsnext:main', 'browser', 'main']
  },
  plugins: [
    // 开启 Scope Hoisting
    new ModuleConcatenationPlugin(),
  ],
};

6 tree Shaking 删除冗余代码

Tree-Shaking可以通过分析出import/exports依赖关系。对于没有使用的代码。可以自动删除。这样就减少了项目的体积。

举个例子:

import { a, b } from './pages'

a()
pages 文件里,我虽然导出了两个页面:

export const a = ()=>{ console.log(666) }

export const b = ()=>{ console.log(666) }
所以打包的结果会保留这部分:

export const a = ()=>{ console.log(666) }

b方法直接删掉,这就是 Tree-Shaking 帮我们做的事情。删掉了没有用到的代码。

7 按需加载

像vue 和 react spa应用,首次加载的过程中,由于初始化要加载很多路由,加载很多组件页面。会导致 首屏时间 非常长。一定程度上会影响到用户体验。所以我们需要换一种按需加载的方式。一次只加载想要看到的内容

require.ensure 形式

当我们不需要按需加载的时候,我们的代码是这样的:

import AComponent from '../pages/AComponent'

<Route path="/a" component={
   
   AComponent}>

为了开启按需加载,我们要稍作改动。

首先 webpack 的配置文件要走起来:

output: {
   
   
    path: path.join(__dirname, '/../dist'),
    filename: 'app.js',
    publicPath: defaultSettings.publicPath,
    // 指定 chunkFilename
    chunkFilename: '[name].[chunkhash:5].chunk.js',
},

路由处的代码也要做一下配合:

const getComponent => (location, cb) {
   
   
  require.ensure([], (require) => {
   
   
    cb(null, require('../pages/AComponent').default)
  }, 'a')
}
<Route path="/a" getComponent={
   
   getComponent}>

对,核心就是这个方法:

require.ensure(dependencies, callback, chunkName)
import形式

上面以react为例子,下面以vue为例子,不按需加载的时候这么写:

import B from '@/pages/business/b.vue'
按需加载变成了:

const B = () => import('@/pages/business/b.vue')

无论是require.ensure形式,还是import 形式的按需加载。都是采取异步模式,跳转 对应这个路由的时候,异步方法的回调才会生效,才会真正地去获取组件页面的内容。做到了按需加载的目的。

8 按需引入

不知道大家有没有体会到,当我们用antd等这种UI组件库的时候。明明只想要用其中的一两个组件,却要把整个组件库连同样式库一起引进来,就会造成打包后的体积突然增加了好几倍。为了解决这个问题,我们可以采取按需引入的方式。

拿antd为例,需要我们在.babelrc文件中这样声明,

{
   
   
"presets": [
   [
    "@babel/preset-env",
    {
   
   
      "targets": {
   
   
          "chrome": "67"
      },
    "useBuiltIns": "usage",
     "corejs": 2
    }
   ],
    "@babel/preset-react"
 ],
  "plugins": [
  [
   "@babel/plugin-transform-runtime",
  ],
  //重点按需引入antd里面的style
  [  "import", {
   
   
   "libraryName": "antd",
   "libraryDirectory": "es",
   "style": true
  }]
 ]
}

经过如上配置之后,我们会发现体积比没有处理的要小很多。

总结

希望读过此篇文章的朋友们,都能在实际的项目中,试着用这些方法去优化项目,提高用户体验,具体实践还需我们在项目中不断摸索和尝试。敢迈出第一步显得至关重要。

相关文章
|
2月前
|
缓存 前端开发 JavaScript
Webpack技术深度解析:模块打包与性能优化
【10月更文挑战第13天】Webpack技术深度解析:模块打包与性能优化
|
7月前
|
缓存 资源调度 监控
Webpack 5新特性详解与性能优化实践
Webpack 5通过确定性的Chunk ID、模块ID和导出ID实现了长期缓存,这意味着相同的输入将始终产生相同的输出。这样,当你的用户再次访问更新后的网站时,浏览器可以重用旧的缓存,而不是重新下载所有资源。
92 2
|
7月前
|
缓存 监控 JavaScript
《Webpack5 核心原理与应用实践》学习笔记-> webpack极致性能优化
《Webpack5 核心原理与应用实践》学习笔记-> webpack极致性能优化
87 1
|
7月前
|
监控 IDE 开发工具
《Webpack5 核心原理与应用实践》学习笔记-> webpack性能优化技巧
《Webpack5 核心原理与应用实践》学习笔记-> webpack性能优化技巧
129 0
|
缓存 前端开发 数据可视化
Webpack4 性能优化实践 #108
Webpack4 性能优化实践 #108
115 0
|
缓存 Dart 前端开发
webpack 性能优化方案
`webpack` 作为前端目前使用最广泛的打包工具,在面试中也是经常会被问到的。 比较常见的面试题包括: - 可以配置哪些属性来进行 **`webpack` 性能优化?** - **前端有哪些常见的性能优化?**(除了其他常见的,也完全可以从 `webpack` 来回答) `webpack` 的性能优化比较多,我们可以对其进行分类: 1. **打包后的结果**,上线时的性能优化。(比如分包处理、减小包体积、CDN服务器等) 2. **优化打包速度**,开发或者构建时优化打包速度。(比如 `exclude`、`cache-loader` 等) 大多数情况下,我们会更加侧重于 **第一种
webpack 性能优化方案
|
前端开发 JavaScript 应用服务中间件
【前端】性能优化——webpack 打包压缩
【前端】性能优化——webpack 打包压缩
268 0
|
JavaScript 区块链
项目性能优化之用url-loader把小图片转base64,大图片使用image-webpack-loader压缩
项目性能优化之用url-loader把小图片转base64,大图片使用image-webpack-loader压缩
367 0
|
JavaScript 前端开发 应用服务中间件
项目性能优化之用compression-webpack-plugin插件开启gzip压缩,以vue为例
项目性能优化之用compression-webpack-plugin插件开启gzip压缩,以vue为例
749 0
|
JavaScript 前端开发 CDN
webpack性能优化配置与实战(二)
webpack性能优化配置与实战(二)
221 0
webpack性能优化配置与实战(二)