webpack打包压缩混淆_webpack打包优化效果可视化

本文介绍了webpack打包的优化方法,包括code splitting、DllPlugin、CommonsChunkPlugin、speed-measure-webpack-plugin和webpack-bundle-analyzer的使用。通过实践,作者分析了打包过程中的时间消耗和文件大小,探讨了提供性能的策略,如动态导入、提取公共模块和避免无用模块解析。此外,还讨论了优化带来的速度和体积变化,以及未来尝试升级到webpack 4的计划。
摘要由CSDN通过智能技术生成

可视化主要是使用两个插件做耗时和体积上的前后对比,新手上岸,有问题希望能够指出,感激不尽...

目前基于webpack 3.12.0 ,当然webpack4相对来说改动较大做了不少优化,在本次优化后会连载直接升级至4到底提升了多少,做个 diff

目录:为什么要打包

webpack构建流程图

如何进行 code spliting

优化实战与效果比对

其他考虑到但未选择的优化方案

为什么要打包

打包之前或许应该明确一下为什么要打包,在 模块化学习 里有说到,es6 在语言层面上解决了js模块化的问题,但是浏览器无法识别 import export 等新的语法,必须经过babel转化,而babel 转化之后最终输出的是 commonJs,而 commonJs 只能在 node 环境里才能运行,所以我们必须做点事情可以使得浏览器能够运行我们的项目文件,所以此时 parcel webpack rollup snowpack 等打包器越来越多,生态越来越庞大,webpack里各种妖魔鬼怪。。。在编译转化,压缩,合并,混淆,速度,耗时优化等方面越走越远。

webpack 构建流程是怎样的

webpack 构建分为三部分:初始化 - 编译 - 输出初始化:读取与合并配置参数,加载 Plugin,实例化 Compiler,注册和调用插件

编译:从 Entry 发出,针对每个 Module 串行调用对应的 Loader 去翻译文件内容,再找到该 Module 依赖的 Module,递归地进行编译处理

输出:对编译后的 Module 组合成 Chunk,把 Chunk 转换成文件,输出到本地。

webpack打包是一个串行的过程,以 vue 为例演示打包流程图:

loader 作用:转换不能识别的语法,主在编译,比如babel-loader转化 js 高级语法;vue-loader 将 .vue 文件中的语言块应用在相应的 rules;less-loader 处理less语法

plugin 作用:压缩、合并、拆分、混淆,主在优化,UglifyJsPlugin 压缩混淆js,OptimizeCSSPlugin 优化压缩 css

构建的过程中 plugin会对自己感兴趣的步骤上执行特定的逻辑,从而改变输出内容

(有人建议使用 tapable 解释构建流程会更好一点,但确实不太有时间,总之tapable 是webpack的核心,本以为compiler就是底层了,没想到他是 tapable的实例,这篇文章可以看一下 webpack4.0源码分析之Tapable)

如何进行 code spliting

一般的拆包方式:分离 第三方模块

抽取公共代码

动态加载

说明一下当前项目情况:vue项目,两个入口,里面有不少的第三方模块,比如 lodash,jquery,aixos,图表类,裁剪图片的,编辑器的,素材库等,使用 vue-router 划分出20个异步加载模块;

1、分离第三方模块:就是将引入的三方库单独打包,因为这些可识别且不会经常变化,一般名为 vendor

2、抽取公共代码:我们有两个入口,直接打包会生成两个文件,包含着大量重复的代码包的体积也比较大,可以从 entry1 和 entry2 作为 source chunk 提取一个common chunk;也可以是从多个异步模块中提取被引用了 n 次的模块

3、动态加载:也就是在需要的时候加载一些代码,凡是使用 import() 或 require.ensure() 加载的模块都会被打成异步包,webpack内部通过 jsonp 的方式引入这些模块

通过以上方式基本可以拆分出来一些,但有时会发现某些包还是比其他看起来大很多,这一般是应用程序的核心页面,此时可以采用 CommonsChunkPlugin的 minChunks 设置规则将其单独提取出来

优化实战

使用 speed-measure-webpack-plugin 测试 plugin 与 loader 花费时间

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

const smp = new SpeedMeasureWebpack(); // 监控面板,现在是各个插件loader花费时间

module.exports = smp.wrap(webpackConfig);

使用 webpack-bundle-analyzer 测试打包情况

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

webpackConfig.plugins.push(new BundleAnalyzerPlugin({ analyzerPort: 8899 }));npm run build --report 后会打开 localhost:8899 查看当前各个chunk的size与占比,具体的就不贴图了,非个人项目

1、优化前应该先整体审视一下,深入项目代码或许会发现有很多三方库引入之后并没有使用,或者很多的图片和字体并没有使用,这时候可以选择性删除掉这些无用的依赖,可能会发现已经有明显提升,这一步非常重要

2、从 webpack-bundle-analyzer 里你会发现第三方库导致打出来的包非常大,这时候去网上搜各种方法,先试一下炒的最热的 dllPlugin(webpack内置插件),据说可以明显提升编译速度,dll 对某些三方库做了动态链接,在打包的时候无需编译可以提升编译速度

// 新建 webpack.dll.conf.jsconst path = require("path");

const webpack = require("webpack");

module.exports = {

entry: {

"vendor_app": [ // 入口1需要的三方 "***",

"***"

],

"vendor_common": [ // 入口2需要的三方 "vue",

"****",

"vue-router",

"vuex",

"lodash",

"axios",

"jquery"

]

},

output: {

path: path.resolve(__dirname, "..", "static/dll"), // 打包后文件输出的位置 filename: "[name].dll.js",

library: "[name]_library" // 和 DllPlugin的name保持一致 },

plugins: [

new webpack.DllPlugin({

path: path.resolve(__dirname, "..", "static/dll/[name]-manifest.json"),

name: "[name]_library",

context: __dirname // 必填 })

]

};

// webpack.base.conf.jsconst manifestApp = require("../static/dll/vendor_app-manifest.json");

const manifestCommon = require("../static/dll/vendor_common-manifest.json");

const webpack = require('webpack');

plugins: [

new webpack.DllReferencePlugin({ // 动态链接 manifest: manifestApp

}),

new webpack.DllReferencePlugin({

manifest: manifestCommon

}),

]

// 将打包出来的文件动态插入htmlnew HtmlWebpackPlugin({

filename: 'index.html',

template: 'index.html',

inject: true,

chunks: ['manifest', 'app', 'common'],

dllPath: path.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory,'/dll/')

}),

// 修改index.html

// package.json 添加"dll": "webpack --config build/webpack.dll.conf.js",

npm run dll, 在 static/dll 下多了四个文件: 两个js, 两个manifest文件

npm run build 看下效果

速度提升约3s,打出来的包没有变化,但在最终的体积上加了 3.34 + 1.58 MB,所以有些得不偿失

这里本想喷一下dllplugin 的,结果这次还真的快了些,之前测了几次都是变慢的,可能和其他优化手段一起产生作用的效果,还好我有备案,群里也问了下,有几个人都说测了变慢了

因此我打算尝试其他的方法...

使用 Entry + commonPlugin 将三方库单独拿出来:

var packagejson = require('./package.json');

entry: {

vendor: [

'vue',

'vuex',

'vue-router',

'jquery',

'axios',

'lodash',

'element-ui'

],

// 或者 Object.keys(packagejson.dependencies) 将 生产环境要用的一股脑都放在vendors app1: ..

app2: ..

}

....

// 将 vendor 引入模板new HtmlWebpackPlugin({

filename: config.build.index,

template: 'index.html',

inject: true,

minify: {

removeComments: true,

collapseWhitespace: true,

removeAttributeQuotes: true

},

chunksSortMode: 'manual',

chunks: ['manifest', 'common', 'vendor', 'app']

}),

new webpack.optimize.CommonsChunkPlugin({

name: ['vendor'],

filename: '[name].js'

}),

时间上多了1s,可以看到是 uglifyjsplugin 的时间长了

压缩太慢了,试一下 resolve.modules 看会不会有变化

resolve: {

..

modules: [resolve('node_modules')]

// 指定第三方库的位置, 这里遇到第三方就从 node_modules 里找,而不是一层一层向上查找}

效果要不要这么明显..

再试一下 module.noParse 会不会有提升

module: {

// 避免解析未使用模块化的库

noParse: /jquery|lodash|axios/

}

报错了。。

一个一个删掉看下谁不对,先删掉 axios 试下

呀,成了,提升了3s!

使用一下 excludes 直接排除掉不需要loader的模块

rules: [

{

exclude: resolve('node_modules') // 所有的规则下我都加了 这句

}

]

结果 有个字体文件和js文件报错,那就把你们删掉吧,效果如下

到此,感觉快了不少, npm run dev试一下,报错啦

找了一通发现是 noParse 了 lodash ,但是 xx 模块 require("lodash/cloneDeep"). 避免lodash的解析报错,那也删掉好了

慢了3s。。。 那我把jquery 也干掉看看

看来还是 jquery还是产生了一丁点的影响的,

回过头来发现项目里到处都是 import $ from 'jquery', import _ form 'lodash' 的字眼,lodash 被47个文件引入,jquery 被82个文件引入, axios 被 12个文件引入

此时可以试下 providePlugin,并且删掉几个 import 测试下

new webpack.ProvidePlugin({

$: "jquery",

_: "lodash",

axios: "axios"

})

// .eslintrc.jsglobals: {

'$': true,

'_': true,

'axios': true

},

这里的 1min 5.92secs 是因为在测试providePlugin 之前测了点其他东西,不过最后又改回上一步的配置了,感觉回到解放前,前面那么顺利会有编译缓存的问题,这里主要对比 ProvidePlugin的作用,发现多了 2s,不过我是可以妥协的

npm run dev 发现也没啥问题,此时部署到测试环境看看还能不能运行,因为之前出过问题,我可不想到优化完线上一看一堆报错还不知道是哪的问题。。。。

推代码,隐隐感觉有些不妙...

...

果然,一下回到解放前

我不相信这是真的,我再推一遍

...

...

更多了...

我得试下其他分支,是不是docker的问题

...

对比一下,没白干就行

至此,这些优化手段反反复复,好在最终速度有所提升

现在试下 happyPack吧,虽然貌似没有再维护了

npm i happypack -D

const HappyPack = require("happypack");

const HappyPackThreadPool = HappyPack.ThreadPool({ size: 5 });

module: {

noParse: /jquery/,

rules: [

...(config.dev.useEslint ? [createLintingRule()] : []),

{

test: /\.vue$/,

// 将对.vue 文件的处理转交给 id 为 vue 的HappyPack实例 // loader: 'vue-loader', use: ['happypack/loader?id=vue'],

exclude: resolve('node_modules')

},

{

test: /\.js$/,

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

include: [resolve('src')],

}

],

plugins: [

new HappyPack({

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

loaders:['vue-loader'],

threadPool: HappyPackThreadPool

}),

new HappyPack({

id:'babel',

// babel-loader支持缓存转换出的结果,通过cacheDirectory选项开启 loaders:['babel-loader?cacheDirectory'],

threadPool: HappyPackThreadPool

})

...

]

显著提升。。

对比一下 开发环境,

我不该看的... 我什么也没看见...

...

算了,再给一次机会

看来缓存生效了

到这里,看了下打包的各chunk的占比,发现 vendor 里竟然有业务代码,我明明只添加了三方库,发现是提取 vendor写的有问题

new webpack.optimize.CommonsChunkPlugin({

// name: ['vendor'], name: 'vendor',

filename: '[name].js'

}),现在vendor里只有 三方了

再看占比图,此时发现一个异步包非常醒目,整个 4.44M,里面的node_modules 3.34M,

那可以把它分离出来看看

new webpack.optimize.CommonsChunkPlugin({

async: "editor-async",

chunks: ["editor"],

minChunks: function(module) {

return module.context && module.context.includes("node_modules");

}

}),此时editor.js 被分为 editor-async-app 和 editor.js

拆出来的 editor-async-app 包依然很大,那就多加个异步包吧,抽离下editor-async-app 的 material-library

// import { Material } from 'material-library';import(/* webpackChunkName: "material" */ "material-library").then(res => {

Vue.component("material", res.Material);

});

写完发现不对,import() 的异步组件必须放在入口文件,但是我这个组件只是在某个路由下才会使用,这样写无故多了个请求,让首屏变慢,并且 editor-async-app gzip 后 354k 勉勉强强能接受吧,放弃。。。 等会,又逮到一个更大的 ,text-editor gzip 后 119.98KB

查了一下,在页面 import 了三次,虽然目前只有一个异步chunk在用,但是这个东西还是比较通用性的库,不像上一个很私有,只有在特定的页面才有用,那分出来看看好了

// import Editor from 'text-editor'import(/* webpackChunkName: "textEditor" */ 'text-editor').then(editor => {

window.Editor = editor;

});

这样页面就不用多处import了 , 此时 npm run build --report 一下

并且发现时间上少了,我已经不再相信了,耗时优化是个玄学,我只关注最终效果

再看下整体体积

其实 13.38MB 早有了,当时比较耗时优化来着,所以没贴出来 ,现在拉出来主要为了 说明拆包并不能很明显的减少体积,因为之前已经打过公共包,这样做只是为了减少某个chunk的体积,有资料说 打出来的包最好控制在每个 500K左右,虽然会多一次http请求,但充分利用浏览器的并发请求不好吗

再看看其他的包吧..

发现还有两个包莫名的像:

左边 1.07M, 从 app里的异步模块抽出来引用了至少3次的包

new webpack.optimize.CommonsChunkPlugin({

name: 'app', // 第一个入口,entry1 async: 'vendor-async',

children: true,

minChunks: 3

}),

右边1.05M,是另一个入口 entry2打出来的包

有必要说一下,项目之前从 entry1 和 entry2 抽离了一个common

new webpack.optimize.CommonsChunkPlugin({

name: 'common',

chunks: ['entry1', 'entry2']

}),

此时再捋一遍项目:两个入口 entry1,entry2

entry1 分两大块:一个 管理页 A,并且分为多个路由,几个具体业务页面 B C D E F G ...;这里从 entry1 里抽离公共模块 vendor-async, 如左图

entry2 就是右图,它和 B C D E 共用很多东西,因此上面左右两图 的models 里的很多东西被重复打包

所以如果可以把 异步包 vendor-async 里的models抽离出来,在 B C D E entry2 里异步加载就好了,但目前找不到一种方法可以直接打出来。。还希望评论区见...

但可以预想到 models 拆分出来,entry2会多一个请求,还有一点,models2 里不是所有东西entry2都要,并且entry2 也是很重要的页面,且因为一些静态资源本来就很慢,所以随着models里无用的东西(也就是entry2不需要的)变大,这又是一个新的问题。

综上,1、无法从webpack层面分离models; 2、即使分离也会带来新的问题

所以暂时放弃...

到这里,我觉得应该跳出webpack的层面反观项目代码,毕竟优化本身无法脱离业务

....

这不又让我逮到一个无用的 css,里面包含了大量的字体文件,并且一些静态图片略大,还是一些不怎么重要的图片,打算找个压缩工具压缩一下,走起...

所以 UI 给图片的时候一定要注意,不是给啥我就必须用啥...

下面再看下缓存问题:

之前我将三方库全部打出来一个vendor,3.22MB,gzip 331KB

上缓存:

// webpack.dev.conf.jsnew webpack.optimize.CommonsChunkPlugin({

name: 'vendor',

filename: utils.assetsPath('js/[name]_[hash].js')

})

// webpack.prod.conf.jsnew webpack.optimize.CommonsChunkPlugin({

name: 'vendor',

filename: utils.assetsPath('js/[name]_[chunkhash].js')

}),

可能你想问为什么 dev 要用 hash,prod要用chunkhash

开发环境中使用[chunkhash],会增加编译时间,并且webpack-dev-server 禁止这样操作

那为什么不写在 webpack.base.conf.js ,使用 process.env.NODE_ENV !== 'production' 来判断用哪个,不用写两遍?

我试过了,因为 webpack-merge的缘故 使 commonChunkPlugin 顺序不对 导致在生产环境vendor 里打包了公共模块,这显然不是想要的。正确的应该是, 先从两个入口提取 common,然后从 vendor出口提取vendor

new webpack.optimize.CommonsChunkPlugin({

name: 'common',

chunks: ['entry1', 'entry2']

}),

new webpack.optimize.CommonsChunkPlugin({

name: 'vendor',

filename: utils.assetsPath('js/[name]_[chunkhash].js')

}),

生产环境下却是需要服务器支持...

让我找人看下,后面补充截图 todo

到此为止,好像优化了不少了,来看下整体优化前后的效果

npm run dev : 提升约10s,提升23.8%

production 下对比:

git clone 放在另一个目录下,因为没有缓存 1min 43,记得刚开始优化前是 1min16来着

这次又 大于 1min,因为 我在build 前清理了一些东西导致缓存失效,重新打包了

loader和plugin上提升了 30s,提升32%

两个再来一次,缓存的原因 同时少了10s

现在我不得不对之前时间上的对比差异产生质疑,因为根本无法确定因为我添加的配置生效还是因为缓存让它变快了...

那现在部署到测试环境看看

对比一下总耗时:提升28%需要说明的是部署时间包含 npm install 的时间,因此 /dev?Dependencies/i 越多越慢

再看下总chunk,可以确认它是不会骗我的:只看gzip还是挺明显的,毕竟删了很多东西

体积压缩 33.3%

其它看到的优化方案

externals:在自己开发一个库的时候会用到,假如我们引用了 vue,但我们不希望webpack 打包 vue,可以使用externals 排除 bundle里的vue,此时用户使用我们的库时就需要手动引入 vue,比如使用elementui 的用户必须引入 vue,它在打包的时候排除了vue;

// config.jsexports.vue = {

root: 'Vue',

commonjs: 'vue',

commonjs2: 'vue',

amd: 'vue'

};

// webpack.conf.jsexternals: {

vue: config.vue

}

项目上会联合 cdn 一起使用,index.html 引入几个cdn,在 externals 里排除在打包范围外

因为项目不会只引入一两个库,可能会有五六个cdn,听人说可以使用 cdn + combo的形式,合并http请求,vue+vue-router+vuex一个,element等UI系列一个,说的很好听,你倒是搞啊....

总结与反思一边分秒必争,一边观察包的体积,一遍考虑着实际应用时的场景,这无疑是一场体积,耗时与http请求数之间的博弈

良好的编码习惯很重要... 及时删除引入不必要的包,清理掉不使用字体图片,冗余的代码,适当重构,就是简简单单的优化

前期设计时注重文件组织方式,组件间分工明确,注重功能解耦

网上的花式教程,适合自己的才是最有效的

这两篇文章对比理解分包和优化还是比较好的:

这里无论是在体积和耗时上只提升了百分之二三十,听说 webpack4 + babel7 可以提升98% 的速度,总之就是会提升吧,那可真是要试试了

趁热打铁,预留周末 webpack 3 升 4,babel6升7 可视化....

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值