webpack打包vue反编译_2020年你必须知道的webpack打包优化方法
本文字數:3534字
預計閱讀時間:10分鐘
隨著我們的項目項目越做越大,引入的第三方庫會越來越多,打包的依賴也越來越多,每次 build 的時間越來越長,打包出來的文件會越來越大。最糟糕的是單頁面應用首頁白屏時間長,用戶體驗差。此時優化 webpack 打包方法不可回避。下面我們來整理一下常用的webpack打包優化方法。關于優化,首先我們要明確幾個問題:優化的目的
很明顯,我們優化的最終目的就是提高頁面加載速度,提高產品用戶體驗,主要包括以下幾個方面:
減小打包后的文件大小
首頁按需引入文件,減少白屏時間
優化 webpack 打包時間
分析 webpack 打包性能瓶頸
明確目標之后,首先我們來分析一下 webpack 打包性能瓶頸,找出問題所在,然后才能對癥下藥。
1、webpack-bundle-analyzer 分析體積
webpack-bundle-analyzer 可以掃碼打包后內容并構建其成可視化的界面,我們可以在可視化的界面中找出不必要的依賴或者體積過大的包,有針對性地進行優化。
vue-cli3 需要安裝依賴 webpack-bundle-analyzer
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
plugins:[
new BundleAnalyzerPlugin(),
]
- vue-cli2 直接在命令行輸入 npm run build --report, 構建完成后會在 8888 端口展示大小
- 打包出的文件中都包含了什么,以及模塊之間的依賴關系
- 每個文件的大小在總體中的占比,找出較大的文件,思考一下為什么,是否有替換方案,是否使用了它包含了不必要的依賴?
- 是否有重復的依賴項,對此可以如何優化?
- 每個文件的壓縮后的大小。
2、測量構建時間
我們可以通過 speed-measure-webpack-plugin 測量你的 webpack 構建期間各個階段花費的時間。步驟一:安裝依賴包
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin')
const smp = new SpeedMeasurePlugin()
// ...
module.exports = {
configureWebpack: smp.wrap({
plugins: [new BundleAnalyzerPlugin()]
})
}
打包構建后會看到以下輸出
從以上的界面中,我們可以得到以下信息:
- 分析整個打包總耗時;
- 每個插件和 loader 的耗時情況;
找出問題所在后我們開始來總結一下優化方法。
1、 按需加載
單頁面應用最大的一個問題就是,他把整個工程作為一個入口打包成一個模塊,所以在首頁會加載了一些沒用到的資源,造成首頁渲染速度慢,“白屏時間”過長,給用戶不好的體驗。我們可以從以下幾個方面進行按需加載:
1.1 路由組件按需加載
const router = [{
path: '/index',
component: resolve => require.ensure([], () => resolve(require('@/components/index')))
},
{
path: '/about',
component: resolve => require.ensure([], () => resolve(require('@/components/about')))
}
]
1.2 第三方組件和插件。按需加載需引入第三方組件
// 引入全部組件import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElementUI)
// 按需引入組件
import { Button } from 'element-ui'
Vue.component(Button.name, Button)
1.3 對于一些插件,如果只是在個別組件中用的到,也可以不要在 main.js 里面引入,而是在組件中按需引入
// 在main.js引入import Vue from vue
import Vuelidate from 'vuelidate'
Vue.use(Vuelidate)
// 按組件按需引入
import { Vuelidate } from 'vuelidate'
2、優化 loader 配置
- 優化正則匹配——減少文件查詢時間
- 通過cacheDirectory選項開啟緩存——減少再次打包時間
- 通過 include、exclude 來減少被處理的文件。
rules: [
{
test: /\.js$/,
loader: 'babel-loader?cacheDirectory',
include: [resolve('src')]
}
]
}
注意:保存和讀取這些緩存文件會有一些時間開銷,所以請只對性能開銷較大的 loader 使用此 loader。
3、優化文件路徑——省下搜索文件的時間
- extension 配置之后可以不用在 require 或是 import 的時候加文件擴展名,會依次嘗試添加擴展名進行匹配。
- alias 通過配置別名可以加快 webpack 查找模塊的速度。
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
}
},
- module.noParse:讓 webpack 忽略對部分沒采用模塊化的文件的遞歸解析處理,這樣做的好處是能提高構建性能。因為如 jQuery 、echart 等庫龐大又沒有采用模塊化標準,讓 webpack 去解析這些文件耗時又沒有意義。
noParse:/jquery/, //不去解析jquery中的依賴庫
...
},
4、生產環境關閉 sourceMap
- sourceMap 本質上是一種映射關系,打包出來的 js 文件中的代碼可以映射到代碼文件的具體位置,這種映射關系會幫助我們直接找到在源代碼中的錯誤。
- 打包速度減慢,生產文件變大,所以開發環境使用 sourceMap,生產環境則關閉。
5、代碼壓縮
uglifyJsPlugin 是 vue-cli 默認使用的壓縮代碼方式,用來對js文件進行壓縮,從而減小js文件的大小,加速load速度。它使用的是單線程壓縮代碼,打包時間較慢,所以可以在開發環境將其關閉,生產環境部署時再把它打開。
plugins: [new UglifyJsPlugin({
uglifyOptions: {
compress: {
warnings: false
}
},
sourceMap: true,
parallel: true
})
ParallelUglifyPlugin 開啟多個子進程,把對多個文件壓縮的工作分別給多個子進程去完成,每個子進程其實還是通過 UglifyJS 去壓縮代碼,但是變成了并行執行。
ParallelUglifyPlugin 還可以緩存壓縮后的結果,下次遇到一樣的輸入時直接從緩存中獲取壓縮后的結果并返回。
plugins: [new ParallelUglifyPlugin({
//cacheDir 用于配置緩存存放的目錄路徑。
cacheDir: '.cache/',
sourceMap: true,
uglifyJS: {
output: {
comments: false
},
compress: {
warnings: false
}
}
})
]
打包速度和打包后的文件大小對比
| 不用插件 | 14.6M | 32s |
| UglifyJsPlugin | 12.9M | 33s |
| ParallelUglifyPlugin | 7.98M | 17s |
從上面可以看出,無論是打包時間還是打包后的文件大小,ParallelUglifyPlugin的方法都是最優的。
6、提取公共代碼
在用 webpack 打包的時候,對于一些不經常更新的第三方庫,比如 react,lodash,vue 我們希望能和自己的代碼分離開,這樣既能減小打包的總體積,也能避免單個包體積過大。
webpack 社區有以下兩種方案:
6.1、CommonsChunkPlugin 及 splitChunks
webpack3 使用 CommonsChunkPlugin 的實現:
CommonsChunkPlugin主要是用來提取第三方庫和公共模塊,避免首屏加載的bundle文件或者按需加載的bundle文件體積過大,從而導致加載時間過長,這是webpack 優化打包的一大利器。
plugins: [new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function(module, count) {
console.log(module.resource, `引用次數${count}`)
//"有正在處理文件" + "這個文件是 .js 后綴" + "這個文件是在 node_modules 中"
return module.resource && /\.js$/.test(module.resource) && module.resource.indexOf(path.join(__dirname, './node_modules')) === 0
}
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'common',
chunks: 'initial',
minChunks: 2
})
]
CommonsChunkPlugin 的配置參數
- name:可以是已經存在的chunk(一般指入口文件)對應的name,那么就會把公共模塊代碼合并到這個chunk上;否則,會創建名字為name的commons chunk進行合并
- filename:指定commons chunk的文件名。
- chunks:指定source chunk,即指定從哪些chunk當中去找公共模塊,省略該選項的時候,默認就是entry chunks
- minChunks:既可以是數字,也可以是函數,還可以是Infinity。
webpack4 使用 splitChunks 的實現:splitChunks 是webpack有一個默認配置,這也符合webpack4的開箱即用的特性
module.exports = {optimization: {
splitChunks: {
cacheGroups: {
vendor: {
priority: 1, //添加權重
test: /node_modules/, //把這個目錄下符合下面幾個條件的庫抽離出來
chunks: 'initial', //剛開始就要抽離
minChunks: 2 //重復2次使用的時候需要抽離出來
},
common: {
//公共的模塊
chunks: 'initial',
minChunks: 2
}
}
}
}
}
6.2、DLLPlugin
這是在一個額外的獨立的 webpack 設置中創建一個只有 dll 的 bundle(dll-only-bundle)。這個插件會生成一個名為 manifest.json 的文件,這個文件是用來讓 DLLReferencePlugin 映射到相關的依賴上去的。
使用步驟如下
6.2.1、在build下創建 webpack.dll.config.js
const path = require('path')const webpack = require('webpack')
module.exports = {
entry: {
vendor: [
'vue-router',
'vuex',
'vue/dist/vue.common.js',
'vue/dist/vue.js',
'vue-loader/lib/component-normalizer.js',
'vue',
'axios',
'echarts'
]
},
output: {
path: path.resolve('./dist'),
filename: '[name].dll.js',
library: '[name]_library'
},
plugins: [
new webpack.DllPlugin({
path: path.resolve('./dist', '[name]-manifest.json'),
name: '[name]_library'
}),
// 建議加上代碼壓縮插件,否則dll包會比較大。
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
]
}
6.2.2、在 webpack.prod.conf.js 的 plugin 后面加入配置
new webpack.DllReferencePlugin({manifest: require('../dist/vendor-manifest.json')
})
6.2.3、package.json文件中添加快捷命令(build:dll)
"scripts": {"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"start": "npm run dev",
"lint": "eslint --ext .js,.vue src",
"build": "node build/build.js",
"build:dll": "webpack --config build/webpack.dll.conf.js"
}
生產環境打包的時候先npm run build:dll命令會在打包目錄下生成 vendor-manifest.json 文件與 vendor.dll.js 文件。然后npm run build生產其他文件。
6.2.4、根目錄下的入口index.html加入引用
<script type="text/javascript" src="./vendor.dll.js">script>7、CDN 優化
CDN 的全稱是 Content Delivery Network,即內容分發網絡。CDN 是構建在網絡之上的內容分發網絡,依靠部署在各地的邊緣服務器,通過中心平臺的負載均衡、內容分發、調度等功能模塊,使用戶就近獲取所需內容,降低網絡擁塞,提高用戶訪問響應速度和命中率。CDN 的關鍵技術主要有內容存儲和分發技術。
隨著項目越做越大,依賴的第三方 npm 包越來越多,構建之后的文件也會越來越大。再加上又是單頁應用,這就會導致在網速較慢或者服務器帶寬有限的情況出現長時間的白屏。此時我們可以使用CDN的方法,優化網絡加載速度。
7.1、將 vue、vue-router、vuex、element-ui 和 axios 這五個庫,全部改為通過 CDN 鏈接獲取,在 index.html里插入 相應鏈接。
<head><link rel="stylesheet" href="https://cdn.bootcss.com/element-ui/2.0.7/theme-chalk/index.css" />
head>
<body>
<div id="app">div>
<script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js">script>
<script src="https://cdn.bootcss.com/axios/0.19.0-beta.1/axios.min.js">script>
<script src="https://cdn.bootcss.com/vuex/3.1.0/vuex.min.js">script>
<script src="https://cdn.bootcss.com/vue-router/3.0.2/vue-router.min.js">script>
<script src="https://cdn.bootcss.com/element-ui/2.6.1/index.js">script>
body>
7.2、在 webpack.config.js 配置文件
module.exports = {···
externals: {
'vue': 'Vue',
'vuex': 'Vuex',
'vue-router': 'VueRouter',
'element-ui': 'ELEMENT',
'Axios':'axios'
}
},
7.3、卸載依賴的 npm 包
npm uninstall axios element-ui vue vue-router vuex7.4、修改 main.js 文件里之前的引包方式
// import Vue from 'vue'// import ElementUI from 'element-ui'
// import 'element-ui/lib/theme-chalk/index.css'
// import VueRouter from 'vue-router'
import App from './App.vue'
import routes from './router'
import utils from './utils/Utils'
Vue.use(ELEMENT)
Vue.use(VueRouter)
const router = new VueRouter({
mode: 'hash', //路由的模式
routes
})
new Vue({
router,
el: '#app',
render: h => h(App)
})
8、多進程解析和處理文件
由于運行在 Node.js 之上的 webpack 是單線程模型的,所以 webpack 需要處理的事情需要一件一件的做,不能多件事一起做。當 webpack 需要打包大量文件時,打包時間就會比較漫長。
以下兩個方法能讓 webpack 在同一時刻處理多個任務發揮多核 CPU 電腦的功能,提升構建速度。
8.1、thread loader
把這個 thread loader 放置在其他 loader 之前, 放置在這個 loader 之后的 loader 就會在一個單獨的 worker 池(worker pool)中運行。
在 worker 池(worker pool)中運行的 loader 是受到限制的。例如:
- 這些 loader 不能產生新的文件。
- 這些 loader 不能使用定制的 loader API(也就是說,通過插件)。
- 這些 loader 無法獲取 webpack 的選項設置。
每個 worker 都是一個單獨的有 600ms 限制的 node.js 進程。同時跨進程的數據交換也會被限制。
module.exports = {module: {
rules: [
{
test: /\.js$/,
include: path.resolve('src'),
use: ['thread-loader', 'expensive-loader']
}
]
}
}
8.2、HappyPack
HappyPack 能讓 webpack 把任務分解給多個子進程去并發的執行,子進程處理完后再把結果發送給主進程。要注意的是 HappyPack 對 file-loader、url-loader 支持的不友好,所以不建議對該 loader 使用。
使用方法如下:
8.2.1. HappyPack 插件安裝
npm i -D happypack8.2.2. webpack.base.conf.js 文件對 module.rules 進行配置
module: {rules: [
{
test: /\.js$/,
use: ['happypack/loader?id=babel'],
include: [resolve('src'), resolve('test')],
exclude: path.resolve(__dirname, 'node_modules')
},
{
test: /\.vue$/,
use: ['happypack/loader?id=vue']
}
]
}
8.2.3. 在生產環境 webpack.prod.conf.js 文件進行配置
const HappyPack = require('happypack')// 構造出共享進程池,在進程池中包含5個子進程
const HappyPackThreadPool = HappyPack.ThreadPool({ size: 5 })
plugins: [
new HappyPack({
// 用唯一的標識符id,來代表當前的HappyPack是用來處理一類特定的文件
id: 'babel',
// 如何處理.js文件,用法和Loader配置中一樣
loaders: ['babel-loader?cacheDirectory'],
threadPool: HappyPackThreadPool
}),
new HappyPack({
id: 'vue', // 用唯一的標識符id,來代表當前的HappyPack是用來處理一類特定的文件
loaders: [
{
loader: 'vue-loader',
options: vueLoaderConfig
}
],
threadPool: HappyPackThreadPool
})
]
注意,當項目較小時,多線程打包反而會使打包速度變慢。
總結
聚焦全棧,專注分享 TypeScript、Web API、Node.js、Deno 等全棧干貨。
總結
以上是生活随笔為你收集整理的webpack打包vue反编译_2020年你必须知道的webpack打包优化方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: myisam为什么比innodb查询快_
- 下一篇: ffmpeg rtmp 不清晰_知识储备