webpack项目分析和性能优化

在使用webpack进行项目构建的过程中, 当我们的项目需求累积到一定层度的时候, 总是需要做一些构建优化,以及项目结构优化, 来保证页面加载的速度, 更好的用户体验. 以下我们将通过几个方面来了解 webpack项目分析,以及更好的性能优化.

一、构建分析

1.1 打包速度分析

  • speed-measure-webpack-plugin

 npm install speed-measure-webpack-plugin -D

作用:

1. 分析整个打包总耗时

2. 每个 loader 和 plugin的耗时情况

用法:

// 导入速度分析插件

const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");

// 实例化插件

const smp = new SpeedMeasurePlugin();

module.exports = {

configureWebpack: smp.wrap({

plugins: [

// 这里是自己项目里需要使用到的其他插件

new yourOtherPlugin()

]

})

}

1.2 体积分析

  • webpack-bundle-analyzer

 npm install webpack-bundle-analyzer -D

作用:

分析打包之后每个文件以及每个模块对应的体积大小

用法:

// 导入速度分析插件

const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");

// 导入体积分析插件

const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;

// 实例化速度分析插件

const smp = new SpeedMeasurePlugin();

module.exports = {

configureWebpack: smp.wrap({

plugins: [

// 实例化体积分析插件

new BundleAnalyzerPlugin()

]

})

运行:

npm run build --report

二、性能优化

2.1 优化构建速度

2.1.1 合理使用hash值命中缓存

1. hash: 项目每次编译生成一个hash值。如果所有的文件都无变化,则hash值不变 , 反之, 则会发生改变. 所有的文件名. hash相同

2. chunkHash: 根据不同的入口文件(Entry)进行依赖文件解析、构建对应的chunk,生成对应的哈希值. 不同的chunkHash不同, 只有对应的chunk内部发生了改变, chunkHash才会改变

3. contentHash: 针对每个文件内容级别产生的哈希值, 只有文件内容发生改变了, contentHash才会改变

用法:

module.exports = {

...

output: {

// filename: 'bundle.[contentHash:8].js',

// 打包代码时,加上 contentHash

filename: '[name].[contentHash:8].js',

}

...

}

2.1.2 减少检索范围

  • loader 使用include和exclude减少检索范围

module.exports = {

...

module: {

rules: [

//babel-loader处理js文件, 配合的依赖有 @babel/core @@babel/preset-env, 在根目录新增.babelrc文件

{

test: /.js$/,

loader: ['babel-loader'],

include: resolve('src'),

exclude: /node_modules/

},

]

}

...

}

  • resolve.alias减少检索路径

module.exports = {

...

resolve: {

extensions: ['.js', '.vue', '.json'],

alias: {

'vue$': 'vue/dist/vue.esm.js',

'@': resolve('src'),

}

},

...

}

2.1.3 优化babel-loader

1. 添加exclude配置

2. 添加.babelrc文件

3. 开启cacheDirectory 缓存

在babel-loader执行的时候,在运行期间可能会产生一些重复的公共文件,造成代码冗余,减慢编译效率:

//webpack.base.conf.js

module.exports = {

...

module: {

rules: [

//babel-loader处理js文件, 配合的依赖有 @babel/core @@babel/preset-env, 在根目录新增.babelrc文件

{

test: /.js$/,

loader: 'babel-loader?cacheDirectory',

include: resolve('src'),

exclude: /node_modules/

},

]

}

...

}

// .babelrc

// npm install babel-plugin-transform-runtime

{

// "presets": [["@babel/preset-react", {"modules": false }]],

"presets": ["@babel/preset-env", "@babel/preset-react"],

"plugins": ["@babel/plugin-syntax-dynamic-import", "@babel/plugin-proposal-class-properties", "babel-plugin-transform-runtime"]

}

2.1.4 热更新

webpack在本地开发提升效率的最有效的办法就是使用热更新, 热更新模块时在webpack的devServer中配置:

// npm install webpack-dev-server -D

devServer: {

contentBase: '../../dist',

open: true,

port: 8080,

hot: true, //对有变化的文件打包

hotOnly: true,

historyApiFallback: true,

publicPath: '/'

}

配置 hot: true , 每次保存的时候, webpack都会对有变化的文件打包, 然后实时更新到页面

2.1.5 懒加载

  • vue异步组件

{

path: '/index',

component: (resolve) => { require(['../components/index/index'], resolve) }

}

  • ES6 import() 异步引入文件

//路由

{

path:'/index',

component: () => import('../components/index/index')

}

{

path:'/index',

component: resolve => { import('../components/index/index').then(module=>resolve(module)) }

}

//es6

setTimeout(()=> {

import('../components/index/index').then(exports =>{

...

})

},1000)

  • webpack 的 require 和 ensure()

{

path: '/index',

component: r => require.ensure([], () => r(require('../components/index/index')), 'index')

}

2.1.6 webpack自带小插件优化

  • webpack.IgnorePlugin

// 忽略本地文件

module.exports = {

...

plugins:[

// 忽略 moment 下的 /locale 目录

new webpack.IgnorePlugin(/./locale/, /moment/),

]

...

}

  • DllPlugin和DllReferencePlugin预编译

作用: 直接使用打包好的第三方包, 不需要每次都打包

假设项目中使用了Vue, vue-router, element-ui , 首先需要出创建 webpack.dll.conf.js

const path = require('path')

const webpack = require('webpack')

module.exports = {

entry: {

vue: ['vue', 'vue-router'],

ui: ['element-ui']

},

output: {

path: path.join(__dirname, '../src/dll'),

filename: '[name].dll.js',

library: '[name]'

},

plugins: [

new webpack.DllPlugin({

path: path.join(__dirname, '../src/dll', '[name]-manifest.json'),

name: '[name]'

}),

new webpack.optimize.UglifyJsPlugin()

]

}

然后 webpack --config ./build/webpack.dll.conf.js打包:

使用 DllReferencePlugin 在 build/webpack.base.config.js 中添加下列插件:

    ...

plugins: [

new webpack.DllReferencePlugin({

manifest: path.join(__dirname, '../src/dll/ui-manifest.json')

}),

new webpack.DllReferencePlugin({

manifest: path.join(__dirname, '../src/dll/vue-manifest.json')

}),

],

再次执行npm run build , 比较前后两次打包的输出时间:

使用 DllPlugin 之前: npm run build

使用 DllPlugin 之后: npm run dll -> npm run build

2.1.7 多进程并行解析

多线程构建的方案比较知名的有以下三个:

1. thread-loader (推荐使用这个)

2. parallel-webpack

3. HappyPack

  • thread-loader

npm install thread-loader -D

用法:

module.exports = {

...

module: {

rules: [

//babel-loader处理js文件, 配合的依赖有 @babel/core @@babel/preset-env, 在根目录新增.babelrc文件

{

test: /.js$/,

loader: ['thread-loader', 'babel-loader'],

include: resolve('src'),

exclude: /node_modules/

},

]

}

...

}

//带options的配置

use: [

{

loader: "thread-loader",

// loaders with equal options will share worker pools

// 设置同样option的loaders会共享

options: {

// worker的数量,默认是cpu核心数

workers: 2,

// 一个worker并行的job数量,默认为20

workerParallelJobs: 50,

// 添加额外的node js 参数

workerNodeArgs: ['--max-old-space-size=1024'],

// 允许重新生成一个dead work pool

// 这个过程会降低整体编译速度

// 开发环境应该设置为false

poolRespawn: false,

//空闲多少秒后,干掉work 进程

// 默认是500ms

// 当处于监听模式下,可以设置为无限大,让worker一直存在

poolTimeout: 2000,

// pool 分配给workder的job数量

// 默认是200

// 设置的越低效率会更低,但是job分布会更均匀

poolParallelJobs: 50,

// name of the pool

// can be used to create different pools with elsewise identical options

// pool 的名字

//

name: "my-pool"

}

},

...

]

前后编译速度的比较, 提升了 201ms

  • HappyPack

把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程。 对file-loader、url-loader 支持的不友好

npm install happypack -D

用法:

const HappyPack = require('happypack');

const os = require('os');

const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });

module.exports = {

...

module: {

rules: [

{

test: /.js$/,

//把对.js 的文件处理交给id为happyBabel 的HappyPack 的实例执行

use: ['happypack/loader?id=happyBabel'],

include: resolve('src'),

exclude: /node_modules/

},

...

]

},

plugins: [

...

// happyPack 开启多进程打包

new HappyPack({

// 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件

id: 'happyBabel',

// 如何处理 .js 文件,用法和 Loader 配置中一样

loaders: ['babel-loader?cacheDirectory'],

// 使用共享进程池中的子进程去处理任务。

threadPool: happyThreadPool,

//允许 HappyPack 输出日志

verbose: true,

}),

]

}

前后编译速度的比较, 提升了 122ms

2.2 优化产出代码

2.2.1 小图片Base64编码

npm install url-loader image-webpack-loader -D

用法:

//webpack.prod.config.js

module.exports = {

...

module: {

rules: [

{

test: /.(png|jpg|gif|bmp/)$/i,

use: [

{

loader: 'url-loader',

options: {

name:'[name].[ext]',

outputPath: 'images/',

limit: 8192 //小于8192b,就可以转化成base64格式。大于就会打包成文件格式

}

},

{

loader:'image-webpack-loader', //对图片资源进行压缩处理

}

]

}

]

}

...

}

2.2.2 抽离CSS代码和压缩

使用mini-css-extract-plugin 代替 extract-text-webpack-plugin , 更好的支持异步加载,重复编译,性能更好, 只针对CSS, 代码简单, 但不支持HMR

  • mini-css-extract-plugin

作用: 将js中import的css文件提取出来,以link方式插入html, 该方式会产生额外的Http请求

版本: webpack4.0

1. npm install mini-css-extract-plugin -D

2. 在webpack.prod.config.js中:

//抽离css样式 和extract-text-webpack-plugin的差异: 支持异步加载, 不重复编译,性能更好,只针对css

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {

...

module: {

rules: [

//抽离css

{

test: /.css$/,

loader: [

MiniCssExtractPlugin.loader, // 注意,这里不再用 style-loader

'css-loader',

'postcss-loader'

]

},

//抽离less -> css

{

test: /.less$/,

loader: [

MiniCssExtractPlugin.loader, // 注意,这里不再用 style-loader

'css-loader',

'less-loader',

'postcss-loader'

]

}

]

},

plugins: [

new CleanWebpackPlugin(), // 会默认清空 output.path 文件夹

//新增生产环境变量

new webpack.DefinePlugin({

// window.ENV = 'production'

ENV: JSON.stringify('production')

}),

//抽离css文件

new MiniCssExtractPlugin({

filename: 'css/main.[contentHash:8].css'

})

],

}

  • extract-text-webpack-plugin

作用: 处理js中import的css文件 通过css-loader、style-loader、extract-text-webpack-plugin@next将js中import的css文件以link的方式插入到html

版本: webpack3.x以下

1. npm install extract-text-webpack-plugin -D

1. 在webpack.prod.config.js中:

const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin');

module.exports = {

module:{

rules:[

{

test:/.css$/,

use:ExtractTextWebpackPlugin.extract({

fallback:'style-loader',

use:'css-loader'

})

}

]

},

plugins:[

new ExtractTextWebpackPlugin({

filename: '[name]-[contentHash:8].css'

})

]

}

  • optimize-css-assets-webpack-plugin

npm install optimize-css-assets-webpack-plugin -D

作用: 压缩CSS代码

用法:

//用于优化压缩css代码

const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');

module.exports = webpackMerge(webpackBaseConfig, {

mode: 'production',

...

optimization: {

//压缩CSS文件

minimizer: [new OptimizeCssAssetsWebpackPlugin({})],

}

})

2.2.3 分割公共代码

使用场景:

1. 多入口文件,需要抽出公共部分的时候。

2. 单入口文件,但是因为路由异步加载对多个子chunk, 抽离子每个children公共的部分。

3. 把第三方依赖,node_modules下所有依赖抽离为单独的部分。

4. 混合使用,既需要抽离第三方依赖,又需要抽离公共部分。

在 webpack4.0 之前, 采用webpack.optimize.CommonsChunkPlugin来做代码分割, 这里做一个简单的介绍使用:

//webpack.prod.config.js

module.exports = {

...

plugins:[

new webpack.optimize.CommonsChunkPlugin({

name: 'vendor',

//minChunks 可以是数字、函数或者Infinity

minChunks (module) {

// any required modules inside node_modules are extracted to vendor

return (

module.resource &&

/.js$/.test(module.resource) &&

module.resource.indexOf(

path.join(__dirname, '../node_modules')

) === 0

)

}

})

]

...

}

在 webpack4.0 之后, 采用config.optimization.splitChunks 实现代码分割, 抽离公共部分, 具体的使用情况如下:

常见的使用场景:

1. 抽离第三方类库(vue, vue-router等)

2. 抽离项目中相同模块引用的代码

项目结构: (采用多入口模式进行模拟)

项目代码如下:

// common/util.js

exportfunction sum(a, b) {

return a * b;

}

//index.js

import { sum } from './common/util'

const vue = require('vue');

const router = require('vue-router')

console.log(sum(10, 20));

//indexA.js

import { sum } from './common/util'

console.log(sum(40, 60));

//webpack.config.js 简单配置

const path = require('path');

const HtmlWebpackPlugin = require('html-webpack-plugin');

const { CleanWebpackPlugin } = require('clean-webpack-plugin');

const TerserWebpackPlugin = require('terser-webpack-plugin');

module.exports = {

mode: 'production',

entry: {

index: path.join(__dirname, '../src/index.js'),

indexA: path.join(__dirname, '../src/indexA.js'),

},

output: {

filename: '[name].[contentHash:8].js',

path: path.join(__dirname, '../dist')

},

module: {

rules: [{

test: /.js$/,

loader: 'babel-loader',

exclude: /node_modules/,

}]

},

plugins: [

new CleanWebpackPlugin(),

new HtmlWebpackPlugin({

template: 'index.html',

filename: 'index.html',

path: path.join(__dirname, '../dist'),

chunks: ['index']

}),

new HtmlWebpackPlugin({

template: 'indexA.html',

filename: 'indexA.html',

path: path.join(__dirname, '../dist'),

chunks: ['indexA']

})

],

optimization: {

minimizer: [new TerserWebpackPlugin({})],

splitChunks: {

/**

* 需要被分割的模块

* initial 入口 chunk,对于异步导入的文件不处理

* async 异步 chunk,只对异步导入的文件处理

* all 全部 chunk

*/

chunks: 'all',

//缓存的分组

cacheGroups: {

//分割第三方模块

vendor: {

name: 'vendor', // chunk 名称

priority: 1, // 权限更高,优先抽离,重要!!!

test: /node_modules/,

//minSize 规定被提取的模块在压缩前的大小最小值,单位为字节,默认为30000,只有超过了30000字节才会被提取。

//maxSize 把提取出来的模块打包生成的文件大小不能超过maxSize值,如果超过了,要对其进行分割并打包生成新的文件。单位为字节,默认为0,表示不限制大小

minSize: 0, // 大小限制

//

minChunks: 1 // 最少复用过几次

},

//抽离共同代码模块

common: {

name: 'common', // chunk 名称

priority: 0, // 优先级

minSize: 0, // 公共模块的大小限制

minChunks: 2 // 公共模块最少复用过几次 index.js和indexA.js相同部分

}

}

}

}

}

编译后文件:

在整个构建过程中, 可以通过设置 minChunks 的限制来抽离共同代码, 控制common文件的生成, 因为minSize 默认为 3kb , 所以为了测试这个场景, 可以设置为 0

2.2.4 多进程并行压缩

由于 JS 是单线程, 为此可以通过开启多进程的方式, 来加快压缩效率; 目前使用并行压缩比较主流的三个方案如下:

1. 使用webpack-parallel-uglify-plugin, 一般搭配happyPack使用

2. 使用 uglifyjs-webpack-plugin 开启parallel 参数

3. 使用terser-webpack-plugin 开启 parallel 参数 (推荐使用这个,支持 ES6 语法压缩)

  • webpack-parallel-uglify-plugin

npm install webpack-parallel-uglify-plugin -D

//webpack.config.js配置

const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');

module.exports = {

...

plugins:[

// 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码

new ParallelUglifyPlugin({

// 传递给 UglifyJS 的参数

// (还是使用 UglifyJS 压缩,只不过帮助开启了多进程)

uglifyJS: {

output: {

beautify: false, // 最紧凑的输出

comments: false, // 删除所有的注释

},

compress: {

// 删除所有的 `console` 语句,可以兼容ie浏览器

drop_console: true,

// 内嵌定义了但是只用到一次的变量

collapse_vars: true,

// 提取出出现多次但是没有定义成变量去引用的静态值

reduce_vars: true,

}

}

})

]

...

}

  • uglifyjs-webpack-plugin

npm install uglifyjs-webpack-plugin -D

//webpack.config.js配置

const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

module.exports = {

...

optimization: {

minimizer: [new UglifyJsPlugin()],

}

...

}

//options配置项:

module.exports = {

...

optimization: {

minimizer: [

new UglifyJsPlugin({

test: /.js(?.*)?$/i, //测试匹配文件,

include: //includes/, //包含哪些文件

excluce: //excludes/, //不包含哪些文件

//允许过滤哪些块应该被uglified(默认情况下,所有块都是uglified)。

//返回true以uglify块,否则返回false

chunkFilter: (chunk) => {

// `vendor` 模块不压缩

if (chunk.name === 'vendor') {

returnfalse;

}

returntrue;

}

}),

cache: false, //是否启用文件缓存,默认缓存在node_modules/.cache/uglifyjs-webpack-plugin.目录

parallel: true, //使用多进程并行运行来提高构建速度

],

},

...

};

  • terser-webpack-plugin

npm install terser-webpack-plugin -D

//webpack.config.js配置

const TerserWebpackPlugin = require('terser-webpack-plugin');

module.exports = {

...

optimization: {

minimizer: [new TerserWebpackPlugin({

// parallel:2

})],

}

...

}

以上是 webpack项目分析和性能优化 的全部内容, 来源链接: utcz.com/a/28189.html

回到顶部