摘要:注意使用的版本不同,可能會導致打包出的結果不一樣。完整的優化代碼見有用的文章分離第三方庫及公用文件
現在的 web 應用,內容一般都很豐富,站點需要加載的資源也特別多,尤其要加載很多 js 文件。js 文件從服務端獲取,體積大小決定了傳輸的快慢;瀏覽器端拿到 js 文件之后,還需要經過解壓縮、解析、編譯、執行操作,所以,控制 js 代碼的體積以及按需加載對前端性能以及用戶體驗是十分的重要。
本文從 Tree Shaking 和 代碼分割 兩部分介紹 js 打包優化,有興趣的可以跟著一起實踐。
clone 以下項目 https://github.com/jasonintju...,就是個簡單的 React SPA,一看就懂。
Tree Shaking 簡單理解就是:打包時把一些沒有用到的代碼刪除掉,保證打包后的代碼體積最小化。其詳細的介紹可以參考 Tree-Shaking性能優化實踐 - 原理篇。
項目 clone、安裝依賴后,先 npm run build 打包初始代碼,大小及分布如下(其中 src/utils/utils.js 這個文件打包后大小為11.72Kb):
src/containers/About/test.js只引用但是沒有使用到,src/utils/utils.js 這個文件是個工具函數集,有很多很多函數,而我們只用到了其中的一個。默認情況下,整個文件都被打包進 main.js 了,顯然,這是很大的冗余,正好可以使用 Tree Shaking 優化。
修改 .babelrc{ "presets": [["env", { "modules": false }], "react", "stage-0"] }修改 package.json
{ "name": "optimizing-js", "version": "1.0.0", "sideEffects": false }
這樣設置之后,表示所有的 module 都是無副作用的,沒有使用到的 module 都可以刪掉,此時打包結果如下:
import React from "react"; // 只引入了 arraySum, utils.js 中的其他方法不會被打包 import { arraySum } from "@utils/utils"; import "./test"; // 引用,“未使用”,不會被打包 import "./About.scss"; // 引用,“未使用”,不會被打包 class About extends React.Component { render() { const sum = arraySum([12, 3]); return (); } } export default About;About Page
12 plus 3 equals {sum}
如上面注釋所說,Tree Shaking 認為這些是沒有被使用的代碼,所以可以刪掉。但事實上我們知道不是這樣的,test.js 可以刪掉,但是 css、scss 是有用的代碼,我們只需引入即可。因此,需要修改一下 sideEffects 的值:
{ "sideEffects": [ "*.css", "*.scss", "*.sass" ] }
表示,除了[]中的文件(類型),其他文件都是無副作用的,可以放心刪掉。此時打包結果:
可以看到,css 等樣式文件現在如期打包進去了。如果有其他類型的文件有副作用,但是也希望打包進去,在 sideEffects: [] 中添加即可,可以是具體的某個文件或者某種文件類型。
關于為什么修改這兩個地方就可以實現 Tree Shaking 的效果了,可以參考一下https://developers.google.com... 或者其他文章,這里不做詳細解釋了。
代碼分割單頁應用,如果所有的資源都打包在一個 js 里面,毫無疑問,體積會非常龐大,首屏加載會有很長時間白屏,用戶體驗極差。所以,要代碼分割,分成一個一個小的 js,優化加載時間。
分離第三方庫代碼第三方庫代碼多帶帶提取出來,和業務代碼分離,減少 js 文件體積。在 webpack.base.conf.js 中增加:
module: {...}, optimization: { splitChunks: { cacheGroups: { venders: { test: /node_modules/, name: "vendors", chunks: "all" } } } }, plugins: ...動態導入
使用 ECMAScript 提案 的 dynamic import 語法可以異步加載業務中的組件。使用方法如下:
// src/containers/App/App.js // 注釋掉此行代碼 // import About from "@containers/About/About"; // 修改模塊為動態導入形式import(/* webpackChunkName: "about" */ "@containers/About/About").then(module => module.default)}/>
此時打包結果:
能看到,
注意,我們現在只是簡單地使用了 dynamic import,很多邊界情況沒考慮進去,比如:加載進度、加載失敗、超時等處理。可以開發一個高階組件,把這些異常處理都包含進去。社區有個很棒的 react-loadable,大樹底下好乘涼~
npm i react-loadable // src/containers/App/App.js import Loadable from "react-loadable"; // 代碼分割 & 異步加載 const LoadableAbout = Loadable({ loader: () => import(/* webpackChunkName: "about" */ "@containers/About/About"), loading() { returnLoading...; } }); class App extends React.Component { render() { return (); } }
react-loadable 還提供了 preload 功能。假如有統計數據顯示,用戶在進入首頁之后大概率會進入 About 頁面,那我們就在首頁加載完成的時候去加載 about.js,這樣等用戶跳到 About 頁面的時候,js 資源都已經加載好了,用戶體驗會更好。
// src/containers/App/App.js componentDidMount() { LoadableAbout.preload(); }
如果有同學對Network面板不是很熟悉,可以看一下 Chrome DevTools — Network。
提取復用的業務代碼第三方庫代碼已經多帶帶提取出來了,但是業務代碼中也會有一些復用的代碼,典型的比如一些工具函數庫 utils.js。現在,About 組件和 Docs 組件都引用了 utils.js,webpack 只打包了一份 utils.js 在 main.js 里面,main.js 在首頁就被加載了,其他頁面有使用到 utils.js 自然可以正常引用到,符合我們的預期。但是目前我們只是把 About 頁面異步加載了,如果把 Docs 頁面也異步加載了會怎么樣呢?
// src/containers/App/App.js // 注釋掉此行代碼 // import Docs from "@containers/Docs/Docs"; const LoadableDocs = Loadable({ loader: () => import(/* webpackChunkName: "docs" */ "@containers/Docs/Docs"), loading() { returnLoading...; } }); class App extends React.Component { render() { return (); } }
此時打包結果:
能夠看到,about.js 和 docs.js 里面都打包了 utils.js,重復了!
在 webpack.base.conf.js 中增加:
module: {...}, optimization: { splitChunks: { cacheGroups: { venders: { test: /node_modules/, name: "vendors", chunks: "all" }, default: { minSize: 0, minChunks: 2, reuseExistingChunk: true, name: "utils" } } } }, plugins: ...
再打包看結果:
utils.js 也被多帶帶打包出來了,達到了預期。
分離非首頁使用且復用程度小的第三方庫假如,現在 Docs.js 引用了 lodash 這個三方庫:
import React from "react"; import _ from "lodash"; import { arraySum } from "@utils/utils"; import "./Docs.scss"; class Docs extends React.Component { render() { const sum = arraySum([1, 3]); const b = _.sum([1, 3]); return (); } } export default Docs;Docs Page
1 plus 3 equals {sum}
use _.sum, 1 plus 3 equals {b} too.
打包結果:
lodash.js 只在 Docs 頁面使用,而且可能 Docs 頁面訪問量很少,把 lodash.js 打包在首頁就會加載的 venders.js 里面,實在不是明智之舉。
修改 webpack.base.conf.js:
... venders: { test: /node_modules/(?!(lodash)/)/, // 去除 lodash,剩余的第三方庫打成一個包,命名為 vendors-common name: "vendors-common", chunks: "all" }, lodash: { test: /node_modules/lodash//, // lodash 庫多帶帶打包,并命名為 vender-lodash name: "vender-lodash" }, default: { minSize: 0, minChunks: 2, reuseExistingChunk: true, name: "utils" } ...
此時把 lodash 多帶帶打成了一個包,且配合 Docs 頁面的按需加載,達到了理想的加載效果。
緩存項目打包后,資源部署在服務器端,客戶端需要向服務器請求下載這些資源,用戶才能看到內容。使用緩存,客戶端可以大大減少不必要的請求和時間耽擱,只有當資源有更新時,再去下載。區分一個文件是否有更新,使用 文件名 + hash 可以達到目的。本案例中,已經使用了 "[name].[contenthash:8].js"。
然而,在打包的時候,webpack的運行時代碼有時候會導致某些情況出現,如:什么內容都沒改,兩次 build 代碼的 hash 不一樣;或者是,修改了 a 文件的代碼,卻導致了某些未修改代碼文件的 hash 也發生了變化。This is caused by the injection of the runtime and manifest which changes every build.
注意:使用的 webpack 版本不同,可能會導致打包出的結果不一樣。較新的版本或許沒有這種 hash 問題,但為了安全起見,還是建議按照下面的步驟處理一下。分離 webpack runtimeChunk code
// webpack.base.conf.js optimization: { runtimeChunk: { name: "manifest" }, splitChunks: {...} }
此時,能達到:修改某個文件,只有這個文件和 manifest.js 文件的 hash 會發生變化,其他文件的 hash 不變。
打包前:
// About.scss .page-about { padding-left: 30px; color: #545880; // 修改字體顏色 }
修改后:
HashedModuleIdsPlugin增加、刪除一些模塊,可能會導致不相關文件的 hash 發生變化,這是因為 webpack 打包時,按照導入模塊的順序,module.id 自增,會導致某些模塊的 module.id 發生變化,進而導致文件的 hash 變化。
解決方式: 使用 webpack 內置的 HashedModuleIdsPlugin,該插件基于導入模塊的相對路徑生成相應的 module.id,這樣如果內容沒有變化加上 module.id 也沒變化,則生成的 hash 也就不會變化了。
// webpack.prod.conf.js const webpack = require("webpack"); ... plugins: [new webpack.HashedModuleIdsPlugin(), new BundleAnalyzerPlugin()]
完整的優化代碼見 https://github.com/jasonintju...
有用的文章:
webpack分離第三方庫及公用文件
https://developers.google.com...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/97611.html
摘要:一般建議文件最大不超過。按需加載可以減小首屏加載文件的體積,達到提高響應速度的目的。如果你的項目不需要處理靜態資源如圖片,也不需要按需加載,并追求前端高性能的話,可以嘗試。 如何提升前端性能和響應速度 下面大多是從前端工程化的角度給出的優化建議,如果需要了解語法上的優化,可以參考: 如何提高頁面加載速度 編寫高效的JavaScript Web前端性能優化進階 - 完結篇 1. 原生...
摘要:一般建議文件最大不超過。按需加載可以減小首屏加載文件的體積,達到提高響應速度的目的。如果你的項目不需要處理靜態資源如圖片,也不需要按需加載,并追求前端高性能的話,可以嘗試。 如何提升前端性能和響應速度 下面大多是從前端工程化的角度給出的優化建議,如果需要了解語法上的優化,可以參考: 如何提高頁面加載速度 編寫高效的JavaScript Web前端性能優化進階 - 完結篇 1. 原生...
摘要:性能優化利器性能優化性能優化不外乎從三個角度入手開發者在編寫程序時,盡量避免不必要的冗余代碼,包括冗余的第三方庫首先要避免不必要的冗余代碼,包括不必要的閉包不必要的變量與函數聲明不必要的模塊分割等。 js 性能優化利器:prepack 1. js 性能優化 js 性能優化不外乎從三個角度入手: 1.1 開發者在編寫程序時,盡量避免不必要的冗余代碼,包括冗余的第三方庫 首先要避免不必要的...
摘要:感受構建工具給前端優化工作帶來的便利。多多益處邏輯清晰,程序注重數據與表現分離,可讀性強,利于規避和排查問題構建工具層出不窮。其實工具都能滿足需求,關鍵是看怎么用,工具的使用背后是對前端性能優化的理解程度。 這篇主要介紹一下我在玩Webpack過程中的心得。通過實例介紹WebPack的安裝,插件使用及加載策略。感受構建工具給前端優化工作帶來的便利。 showImg(https://se...
閱讀 1673·2021-11-15 11:38
閱讀 4514·2021-09-22 15:33
閱讀 2332·2021-08-30 09:46
閱讀 2176·2019-08-30 15:43
閱讀 827·2019-08-30 14:16
閱讀 2069·2019-08-30 13:09
閱讀 1255·2019-08-30 11:25
閱讀 701·2019-08-29 16:42