当咱们不必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  }] ]}

通过如上配置之后,咱们会发现体积比没有解决的要小很多。

总结

心愿读过此篇文章的敌人们,都能在理论的我的项目中,试着用这些办法去优化我的项目,进步用户体验,具体实际还需咱们在我的项目中一直摸索和尝试。敢迈出第一步显得至关重要。

微信扫码关注公众号,定期分享技术文章