Vue2中項目打包優(yōu)化的方法詳解
前言
之前的開發(fā)工作中對vue2項目也有做過不同的優(yōu)化,但是都比較零散且沒有系統(tǒng)化的記錄下來。
正好最近接手了一個老項目,雖然項目不大性能也還過得去,但是我覺得可優(yōu)化的空間還是比較多的。
所以也趁此機會能把vue打包,編譯,啟動時間的一些優(yōu)化方法記錄下。在閱讀之前你需要確保以下幾點:
- 本項目是基于vue2.x + vue-cli4.x的,不適用于vue3.x+vite項目
- 本文一些配置的寫法可能因插件或者loader版本差異不同而報錯,如出現(xiàn)錯誤,請查閱官方文檔最新配置信息
- 文章不會一次性把全部配置代碼貼出來,目的是為了讓讀者們能夠真正熟悉每一項優(yōu)化配置
- 文章介紹了多種優(yōu)化方式,希望讀者不要全部照抄,而是應該根據(jù)自己項目和實際情況進行選擇配置。沒有最完美的配置,只有最適合自己的配置~
好了,閑話不多說了。在開始之前我們先看下打包體積大小和時間,這樣有利于我們優(yōu)化后進行對比。
優(yōu)化前
1. 打包體積:6.38M
2. 冷啟動耗費時間:61.39秒
3. 編譯耗費時間:50.13秒
準備工作
1. 安裝speed-measure-webpack-plugin
npm i speed-measure-webpack-plugin const SpeedMeasurePlugin = require("speed-measure-webpack-plugin"); module.exports = { configureWebpack: { plugins: [ new BundleAnalyzerPlugin(), new SpeedMeasurePlugin(), ] } };
2. 安裝webpack-bundle-analyzer
npm i webpack-bundle-analyzer const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; module.exports = { configureWebpack: { plugins: [ + new BundleAnalyzerPlugin(), new SpeedMeasurePlugin(), ] } };
一、減少打包代碼體積
1. 使用externals和CDN
externals
允許我們在編譯過程中排除那些已經(jīng)在運行時環(huán)境存在的全局變量或CDN加載的庫,而不是將它們包含在最終的bundle文件內(nèi)。
當配置了externals
字段后,Webpack會在生成的bundle中引用這些全局變量,而不是把它們實際打包進去。這樣做的好處有:
(1)減小bundle體積:如果你的應用依賴了一些大型第三方庫(如jQuery、Vue自身、React、大型UI框架等),并且你確定用戶在訪問你的應用時已經(jīng)通過其他方式(比如在HTML頭部引入CDN鏈接)加載了這些庫,那么就沒有必要在每個用戶的請求中重復加載這些庫。
(2)加快頁面加載速度:由于第三方庫直接從CDN加載,通常情況下CDN會提供更快的加載速度和更好的緩存機制。
externals缺點:直接在html內(nèi)引入的,失去了按需引入的功能,只能引入組件庫完整的js和css
// vue.config.js configureWebpack: { // provide the app's title in webpack's name field, so that // it can be accessed in index.html to inject the correct title. name, resolve: { alias: { '@': resolve('src'), }, }, externals: { axios: 'axios', 'vue-router': 'VueRouter', 'vue': 'Vue', 'Vuex': 'Vuex', 'element-ui': 'ELEMENT', } }
// index.html <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.18.1/axios.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14"></script> <script src="https://unpkg.com/vuex@3.1.0/dist/vuex.global.js"></script> <script src="https://unpkg.com/vue-router@3.0.6/dist/vue-router.js"></script> <script src="https://unpkg.com/element-ui@2.15.12/lib/index.js"></script>
2. 圖片壓縮
使用image-webpack-loader
npm i image-webpack-loader chainWebpack: config => { config.module .rule('images') .test(/.(png|jpe?g|gif|svg)(?.*)?$/) .use('image-webpack-loader') .loader('image-webpack-loader') .options({ bypassOnDebug: true }) .end() },
在安裝image-webpack-loader會遇到一個大坑就是安裝失敗,如果安裝或者使用過程中遇到錯誤請嘗試以下步驟解決
1.刪除: npm uninstall image-webpack-loader
2.使用cnpm重新安裝:cnpm install image-webpack-loader
兩種方式對比
壓縮網(wǎng)站:一個從600KB壓縮到了144KB,一個從98KB壓縮到了18KB
image-webpack-loader插件: 一個從600KB壓縮到了148KB,一個從98KB壓縮到了10KB
3.啟用gzip壓縮
在Vue.js項目中開啟Gzip壓縮是一種常見的性能優(yōu)化手段,它能夠顯著減小靜態(tài)資源(如JavaScript、CSS和HTML文件)在網(wǎng)絡傳輸過程中的大小,從而提高頁面加載速度。
線上的項目,一般都會結(jié)合構(gòu)建工具 webpack 插件或服務端配置 nginx,來實現(xiàn) http 傳輸?shù)?gzip 壓縮,目的就是把服務端響應文件的體積盡量減小,優(yōu)化返回速度。html、js、css資源,使用 gzip 后通常可以將體積壓縮70%以上
const CompressionWebpackPlugin = require("compression-webpack-plugin"); chainWebpack: config => { // 生產(chǎn)環(huán)境,開啟js\css壓縮 if (process.env.NODE_ENV === "production") { config.plugin("compressionPlugin").use( new CompressionWebpackPlugin({ test: /.(js|css|less)$/, // 匹配文件名 threshold: 10240, // 對超過10k的數(shù)據(jù)壓縮 minRatio: 0.8, deleteOriginalAssets: true // 刪除源文件 }) ); } }
服務器配置: Gzip壓縮后的文件需要服務器支持并正確返回給客戶端。對于Nginx服務器,你需要在Nginx的配置文件中添加如下內(nèi)容以啟用Gzip:
//nginx.conf http { gzip on; gzip_vary on; gzip_min_length 1k; gzip_comp_level 6; gzip_buffers 16 8k; gzip_proxied any; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; }
4.uglyifyjs
打包后的 JavaScript 代碼進行混淆壓縮,從而減少文件大小以優(yōu)化網(wǎng)頁加載速度。這個插件基于 UglifyJS 庫,可以執(zhí)行多種優(yōu)化操作,包括但不限于刪除無用代碼、簡化變量名、內(nèi)聯(lián)函數(shù)調(diào)用等。
const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); config.plugins.push( new UglifyJsPlugin({ uglifyOptions: { compress: { drop_debugger: true,//生產(chǎn)環(huán)境自動刪除debugger drop_console: true, //生產(chǎn)環(huán)境自動刪除console }, warnings: false, }, sourceMap: false, //關(guān)掉sourcemap 會生成對于調(diào)試的完整的.map文件,但同時也會減慢打包速度 parallel: true, //使用多進程并行運行來提高構(gòu)建速度。默認并發(fā)運行數(shù):os.cpus().length - 1。 }),
5.路由懶加載
Vue.js 中的路由懶加載是一種優(yōu)化技術(shù),它允許我們在用戶導航到特定路由時異步加載對應的組件,而不是在應用程序啟動時一次性加載所有組件。這樣可以顯著減少初始加載時間、減小打包后的文件體積,并提升用戶體驗。
// 使用 `import()` 動態(tài)導入 // router/index.js import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) export default new Router({ routes: [ { path: '/about', component: () => import('@/views/About.vue'), // 懶加載 About 組件 }, { path: '/contact', component: () => import(/* webpackChunkName: "contact" */ '@/views/Contact.vue'), // 命名代碼塊 }, ], })
// 使用異步組件聲明 // router/index.js import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) export default new Router({ routes: [ { path: '/example', component: { // 使用異步組件定義 async component() { const { default: ExampleComponent } = await import('@/views/Example.vue'); return ExampleComponent; }, }, }, // 其他路由... ], })
這兩種方式都可以確保當用戶首次訪問相應路由時,Vue Router 才會去加載對應的組件資源。這樣不僅提高了應用的響應速度,也降低了初次加載時對網(wǎng)絡帶寬的需求。
6. preload預加載
瀏覽器自己提供的一種機制,在頁面加載的時候,預先請求資源,一邊在需要的時候可以更快的獲取這些資源。
也就是設(shè)置文件中的黑名單,配置文件,添加一個數(shù)組,用來儲存黑名
npm install --save-dev script-ext-html-webpack-plugin const blackList = ['script1.js', 'script2.js']; plugins: [ new ScriptExtHtmlWebpackPlugin({ preload: { test: /.js$/, excludeChunks: blackList } }) ] // 刪除文件中各個腳本都在使用的腳本命令,然后統(tǒng)一在html頁面中進行封裝,需要用到的時候,去html頁面中進行獲取,這樣就可以減少http的請求 config .plugin('ScriptExtHtmlWebpackPlugin') .after('html') .use('script-ext-html-webpack-plugin', [{ // `runtime` must same as runtimeChunk name. default is `runtime` inline: /runtime\..*\.js$/, }, ]) .end();
7. DllPlugin 動態(tài)鏈接庫
DllPlugin
與 externals 的作用相似,都是將依賴抽離出去,節(jié)約打包時間。區(qū)別是 DllPlugin 是將依賴單獨打包,這樣以后每次只構(gòu)建業(yè)務代碼,而 externals 是將依賴轉(zhuǎn)化為 CDN 的方式引入
當公司沒有很好的 CDN 資源或不支持 CDN 時,就可以考慮使用 DllPlugin ,替換掉 externals
//1. 創(chuàng)建 dll.config.js 配置文件 import { DllPlugin } from "webpack"; export default { // 需要抽離的依賴 entry: { vendor: ["vue", "vue-router", "axios", "echarts"] }, mode: "production", optimization: { splitChunks: { cacheGroups: { vendor: { chunks: "all", name: "vendor", test: /node_modules/ } } } }, output: { filename: "[name].dll.js", // 輸出路徑和文件名稱 library: "[name]", // 全局變量名稱:其他模塊會從此變量上獲取里面模塊 path: AbsPath("dist/static") // 輸出目錄路徑 }, plugins: [ new DllPlugin({ name: "[name]", // 全局變量名稱:減小搜索范圍,與output.library結(jié)合使用 path: AbsPath("dist/static/[name]-manifest.json") // 輸出目錄路徑 }) ] };
// package.json 配置腳本 "build:dll": "webpack --config ./dll.config.js",
// 使用 `DllReferencePlugin` 將打包生成的dll文件,引用到需要的預編譯的依賴上來,并通過 `html-webpack-tags-plugin` 在打包時自動插入dll文件 // vue.config.js 配置如下 import { DllReferencePlugin } from "webpack"; import HtmlTagsPlugin from "html-webpack-tags-plugin"; export default { configureWebpack: { plugins: [ new DllReferencePlugin({ manifest: AbsPath("dist/static/vendor-manifest.json") // manifest文件路徑 }), new HtmlTagsPlugin({ append: false, // 在生成資源后插入 publicPath: "/", // 使用公共路徑 tags: ["static/vendor.dll.js"] // 資源路徑 }) ] } };
先運行 npm run build:dll
打包生成依賴文件,以后只用運行 npm run build
構(gòu)建業(yè)務代碼即可
8. 處理Moment中語言包
一般如果項目引入了moment.js或者引入的某個第三方插件包使用了moment.js, 通常情況下,會全部引入了所有語言包,體積過大,我們需要對其進行處理。
configureWebpack: config => { config.plugins.push(new webpack.ContextReplacementPlugin(/moment[/\]locale$/, /zh-cn/)) }
9. 使用dayjs代替momentjs
Day.js 和 Moment.js 是 JavaScript 中兩個非常流行的時間日期處理庫,它們具有相似的API設(shè)計和功能特性,但又存在一些關(guān)鍵的區(qū)別:
Moment.js:
- 功能全面:Moment.js 提供了豐富的日期時間操作功能,包括解析、格式化、比較、計算(加減日期)、時區(qū)轉(zhuǎn)換等。
- 體積較大:Moment.js 的大小相對較大,尤其是包含所有功能的情況下。在資源優(yōu)化方面可能存在劣勢,特別是在移動應用或?qū)虞d速度有嚴格要求的項目中。
- 國際化支持:Moment.js 提供強大的國際化支持,可以方便地處理不同語言環(huán)境下的日期格式和顯示問題。
Day.js:
- 輕量級:Day.js 設(shè)計初衷是為了提供一個與 Moment.js API 兼容但更輕量的替代方案,其體積比 Moment.js 小得多,有助于減少打包后的文件大小,提高頁面加載速度。
- 性能優(yōu)化:由于其輕量化的設(shè)計,Day.js 在性能上通常會優(yōu)于 Moment.js,尤其是在內(nèi)存占用和執(zhí)行效率方面。
- 同樣全面的功能:盡管 Day.js 更輕巧,但它仍然提供了大部分 Moment.js 的核心功能,如解析、格式化、比較等操作,并且也支持插件擴展以實現(xiàn)更多功能。
- 國際化支持:同樣,Day.js 也具備良好的國際化支持,通過引入相應的語言包即可實現(xiàn)多種語言環(huán)境下的日期處理。
總結(jié)起來,如果你需要一個功能強大、歷史悠久且廣泛使用的日期庫,Moment.js 是一個很好的選擇。然而,如果對代碼大小和性能有較高要求,或者只是進行基礎(chǔ)的日期時間操作,Day.js 可能更適合你的項目需求。隨著技術(shù)的發(fā)展,越來越多的新項目可能會傾向于使用 Day.js 這種更為現(xiàn)代和輕量的解決方案。
10. splitChunks單獨打包第三方包
項目中的第三方庫默認會被打包到一個文件名含vendors的bundle中,如果你的項目里面引用的第三方庫過多,那么你的vendors就會很臃腫,文件也會變大,網(wǎng)站加載該文件的時候就越耗時,從而影響網(wǎng)站性能。
這個時候我們可以考慮把一些比較大的第一方庫從vendors中分離出來,或者直接配置cdn。這里我們主要來講如何在vue-cli4中單獨打包第三方庫文件從而實現(xiàn)bundle分割,減小vendors文件體積的目的。
常用參數(shù):
- - minSize(默認是30000):形成一個新代碼塊最小的體積
- - minChunks(默認是1):在分割之前,這個代碼塊最小應該被引用的次數(shù)(譯注:保證代碼塊復用性,默認配置的策略是不需要多次引用也可以被分割)
- - maxInitialRequests(默認是3):一個入口最大的并行請求數(shù)
- - maxAsyncRequests(默認是5):按需加載時候最大的并行請求數(shù)。
- - chunks (默認是async) :initial、async和all
- - test: 用于控制哪些模塊被這個緩存組匹配到。原封不動傳遞出去的話,它默認會選擇所有的模塊??梢詡鬟f的值類型:RegExp、String和Function
- - name(打包的chunks的名字):字符串或者函數(shù)(函數(shù)可以根據(jù)條件自定義名字)
- - priority :緩存組打包的先后優(yōu)先級。
module.exports = { configureWebpack: config => { if (progress.env.NODE_ENV === 'production') { config.optimization = { splitChunks: { cacheGroups: { common: { name: "chunk-common", chunks: "initial", minChunks: 2, maxInitialRequests: 5, minSize: 0, priority: 1, reuseExistingChunk: true, enforce: true }, vendors: { name: "chunk-vendors", test: /[\\/]node_modules[\\/]/, chunks: "initial", priority: 2, reuseExistingChunk: true, enforce: true }, elementUI: { name: "chunk-hui", test: /[\\/]node_modules[\\/]hui[\\/]/, chunks: "all", priority: 3, reuseExistingChunk: true, enforce: true }, echarts: { name: "chunk-echarts", test: /[\\/]node_modules[\\/](vue-)?echarts[\\/]/, chunks: "all", priority: 4, reuseExistingChunk: true, enforce: true } } } }; } }, chainWebpack: config => { if (progress.env.NODE_ENV === 'production') { config.optimization.delete("splitChunks"); } return config; } };
11.production環(huán)境不生成SourceMap
module.exports = { lintOnSave: false, productionSourceMap: process.env.NODE_ENV !== "production", //打包不生成map文件 }
12. 添加別名 alias
通常項目較大的時候我們會引入別名來方便引入,添加別名方法如下:
module.exports = { chainWebpack: config => { // 添加別名 config.resolve.alias .set("vue$", "vue/dist/vue.esm.js") .set("@", resolve("src")) .set("@assets", resolve("src/assets")) .set("@scss", resolve("src/assets/scss")) .set("@components", resolve("src/components")) .set("@plugins", resolve("src/plugins")) .set("@views", resolve("src/views")) .set("@router", resolve("src/router")) .set("@store", resolve("src/store")) .set("@layouts", resolve("src/layouts")) .set("@static", resolve("src/static")); } };
二、編譯時間提升
1. 緩存 hard-source-webpack-plugin
npm i hard-source-webpack-plugin const HardSourceWebpackPlugin = require("hard-source-webpack-plugin") plugins: [ new BundleAnalyzerPlugin(), new SpeedMeasurePlugin(), new HardSourceWebpackPlugin() ], //提升第二次運行時間
2. Happywebpack
由于運行在 Node.js 之上的 webpack 是單線程模型的,我們需要 webpack 能同一時間處理多個任務,發(fā)揮多核 CPU 電腦的威力
HappyPack
就能實現(xiàn)多線程打包,它把任務分解給多個子進程去并發(fā)的執(zhí)行,子進程處理完后再把結(jié)果發(fā)送給主進程,來提升打包速度
npm install HappyPack -D const HappyPack = require('happypack'); const os = require('os'); // 開辟一個線程池,拿到系統(tǒng)CPU的核數(shù),happypack 將編譯工作利用所有線程 const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length }); module.exports = { configureWebpack: { plugins: [ new HappyPack({ id: 'happybabel', loaders: ['babel-loader'], threadPool: happyThreadPool }) ] } }
注意:
1.由于測試項目較小,打包時間縮短的不算太多。實測發(fā)現(xiàn)越是復雜的項目,HappyPack 對打包速度的提升越明顯.
2.由于HappyPack作者已經(jīng)停止了更新維護,現(xiàn)在更多的是推薦使用 thread-loader
3. thread-loader —— 開啟多線程優(yōu)化
thread-loader
是官方維護的多進程loader,功能類似于happypack,也是通過開啟子任務來并行解析文件,從而提高構(gòu)建速度。
把這個loader放在其他loader前面。不過該loader是有限制的。示例:
- loader無法發(fā)出文件。
- loader不能使用自定義加載器API。
- loader無法訪問網(wǎng)頁包選項。
每個worker都是一個單獨的node.js進程,其開銷約為600毫秒。還有進程間通信的開銷。在小型項目中使用thread-loader
可能并不能優(yōu)化項目的構(gòu)建速度,反而會拖慢構(gòu)建速度,所以使用該loader時需要明確項目構(gòu)建構(gòu)成中真正耗時的過程。
使用 thread-loader
時,通常會將其配置在需要進行大量計算或者時間消耗較大的Loader之前,如Babel這樣的轉(zhuǎn)譯Loader。通過創(chuàng)建額外的子進程處理這些耗時任務,主線程能夠更好地并發(fā)執(zhí)行其他構(gòu)建步驟,從而提升整體性能。
// vue.config.js module.exports = { chainWebpack: config => { config.module.rule('vue') .use('thread-loader') .loader('thread-loader') .end() // 在目標 loader 前插入 thread-loader .use('vue-loader') .loader('vue-loader') .end() } }
優(yōu)化后
1. 打包體積:1.41M
2. 添加HardSourceWebpackPlugin后,第二次啟動耗費時間:3.8秒
3. 編譯耗費時間: 13.38s
以上就是Vue2中項目打包優(yōu)化的方法詳解的詳細內(nèi)容,更多關(guān)于Vue2打包優(yōu)化的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue3中g(shù)etCurrentInstance、頁面中route和router的獲取實現(xiàn)方式
這篇文章主要介紹了Vue3中g(shù)etCurrentInstance、頁面中route和router的獲取實現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-04-04解決Vue-cli3沒有vue.config.js文件夾及配置vue項目域名的問題
這篇文章主要介紹了解決Vue-cli3沒有vue.config.js文件夾及配置vue項目域名的問題,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-12-12Vue實例中生命周期created和mounted的區(qū)別詳解
這篇文章主要給大家介紹了關(guān)于Vue實例中生命周期created和mounted區(qū)別的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面跟著小編來一起學習學習吧。2017-08-08關(guān)于Vue在ie10下空白頁的debug小結(jié)
這篇文章主要給大家介紹了關(guān)于Vue在ie10下空白頁的debug相關(guān)資料,這是最近在工作中遇到的一個問題,通過查找相關(guān)的資料終于解決了,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考下2018-05-05利用Vue+intro.js實現(xiàn)頁面新手引導流程功能
在同學們使用某些網(wǎng)站的新版本頁面的時候,經(jīng)常會出現(xiàn)一個類似于新手引導一樣的效果,來幫助同學們更好的熟悉新版本頁面的功能和使用,這篇文章主要給大家介紹了關(guān)于如何利用Vue+intro.js實現(xiàn)頁面新手引導流程功能的相關(guān)資料,需要的朋友可以參考下2023-11-11vue解析Json數(shù)據(jù)獲取Json里面的多個id問題
這篇文章主要介紹了vue解析Json數(shù)據(jù)獲取Json里面的多個id問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12