免費商品列表網(wǎng)頁模板源代碼seo快速排名上首頁
前言
????????基于公司的業(yè)務(wù)以及今年接觸到的項目大部分都是APP混合開發(fā),即原生Android/ios +H5頁面開發(fā)APP。項目從產(chǎn)品需求的評審到方案的評審再到開發(fā)提測...這一套流程下來讓我收貨頗多??傁胝覀€時間好好記錄一番,大概還是自己懶惰了,一直拖到現(xiàn)在。想記錄的東西太多了,一次講完也沒有突出的重點,我今天最想記錄的是關(guān)于H5資源打包優(yōu)化的問題。以前常??吹揭恍┐骎長篇大論的在講各種優(yōu)化問題,自己沒有真正動手去實踐過的話也不明所以,也不明白優(yōu)化的重要性?,F(xiàn)在還真想感嘆一句:小小的優(yōu)化,真的能大大提高用戶體驗,何樂而不為呢嘿嘿~,好了,廢話說的有點多,接下來我們就直接進入主題吧
現(xiàn)有的問題
基于我實際基礎(chǔ)的混合APP開發(fā)中H5打包資源,得出了如下問題結(jié)論:
1.第三方庫為分包,全部資源打包成一個js文件,導(dǎo)致文件過大。雖然說現(xiàn)在手機的更新迭代很快,在性能好的手機上能夠快速加載,但是開發(fā)APP,我們肯定也是需要兼容一些老舊的設(shè)備機的,那么在性能較差的設(shè)備,加載的時間就會過長,甚至?xí)霈F(xiàn)等待時間過長而顯示白屏的情況。
2.第三方組件庫沒有按需導(dǎo)入
- Vant組件庫全引入,全資源包200kb+
- Echart全引入,全資源包 800kb+
3.引入的圖片沒有進行壓縮處理
4.過多的注釋代碼沒有被移除,全部打包到了靜態(tài)資源中
?準(zhǔn)備工作
由于我的項目中用到的打包工具是webpack,所以這里引入webpack-bundle-analyzer第三方插件,以方便分析打包資源各個模塊的占比情況。
webpack-bundle-analyzer插件是什么?
webpack-bundle-analyzer是webpack的插件,需要配合webpack和webpack-cli一起使用。這個插件可讀取輸出文件夾(通常是dist)中的state.json文件,把該文件可視化展示,生產(chǎn)代碼分析本報告,可以直觀地分析打包出的文件有哪些,以及它們的大小、占比情況,各文件Gzippped后的大小、模塊包含關(guān)系、依賴項等。從而我們可以從其中的數(shù)據(jù)進行分析,做出對應(yīng)的優(yōu)化,從而幫助提升代碼質(zhì)量和軟件性能。
安裝
# NPM
npm install --save-dev webpack-bundle-analyzer
# Yarn
yarn add -D webpack-bundle-analyzer
使用方法
webpack-bundle-analyzer的使用方式可以分為兩種 ,分別是作為插件使用和作為一個cli的一個工具使用,我這里主要將其作為插件的使用方式?
作為插件使用
1.在vue.config.js中配置webpack-bundle-analyer
....
chainWebpack(config){....if (process.env.NODE_ENV === "development") {config.plugin("webpack-bundle-analyzer").use(require("webpack-bundle-analyzer").BundleAnalyzerPlugin);}....
}
2.運行 npm run serve/dev 查看可視化的靜態(tài)資源包
- 分析依賴包
- 移除 無用依賴
- 抽取三方依賴
從上圖我們也可以看出,暴露出的問題也就是我在文章最開始的時候說的那幾個問題,接下來為我們將對癥下藥,針對項目中存在的問題進行優(yōu)化
優(yōu)化方案
第三方庫使用CDN加載
????????我們都知道,使用@vue/cli腳手架構(gòu)建Vue的全家桶項目,打包后會吧vue、vue-router、axios、vuex、第三方組件庫例如vant、echart等打包在一起,導(dǎo)致基礎(chǔ)chunk、vender包體積特別大,有時一個文件能達(dá)到3-5mb,這會大大影響首次加載速度,因此需要抽離第三方公共庫,配合CDN加速。
首先我們先看一下,我在項目中所用到的依賴:
分析: 項目整體使用了vant、axios、數(shù)據(jù)可視化引入了Echart等,這些庫本身體積就不小,打包到一起后體積更大
優(yōu)化配置:
為了方便以后管理,將CDN相關(guān)所有配置寫入cdn.config.js(與vue.config.js同級)
1.cdn.config.js配置
module.exports = {// 是否使用cdnuseCDN: true,// key是'包名', value是靜態(tài)資源引入后全局的名稱 import Vue from 'vue'// 忽略打包的第三方庫externals: {'vue': 'Vue','vuex': 'Vuex','vue-router': 'VueRouter','axios': 'axios','echarts': 'echarts',// 必須是ELEMENT,否則會報‘elementUI is not defined’'vant': 'vant'},//通過cdn方式引入cdn: {// CDN鏈接地址:https://www.jsdelivr.com/css: [//由于項目中沒有引入第三方css,所以這里舉例的是element-ui的css'https://cdn.jsdelivr.net/npm/element-ui@2.15.3/lib/theme-chalk/index.css'],js: ['https://cdn.jsdelivr.net/npm/vue@2.6.11','https://cdn.jsdelivr.net/npm/vue-router@3.2.0/dist/vue-router.min.js','https://cdn.jsdelivr.net/npm/vuex@3.4.0/dist/vuex.min.js','https://cdn.jsdelivr.net/npm/echarts@5.2.1/dist/echarts.min.js','https://cdn.jsdelivr.net/npm/vant@2.15.3/lib/vant.min.js','https://cdn.jsdelivr.net/npm/axios@0.21.1/dist/axios.min.js',']}
}
注意:上方的js文件cdn連接也可以通過下載下來(下載壓縮版)放在public文件夾中
2.配置vue.config.js第三方庫的externals、設(shè)置生成html
const cdnConfig = require('./cdn.config.js');
const isProduction = process.env.NODE_ENV === 'production';....
configureWebpack:{....// 打包忽略以下第三方庫externals: isProduction && cdnConfig.useCDN ? cdnConfig.externals : {}....
}
...
chainWebpack(config){....// ============注入cdn start============config.plugin('html').tap(args => {args[0].minify.removeAttributeQuotes = false; // 引入打包的雙引號,否則本地靜態(tài)資源讀取不到// 生產(chǎn)環(huán)境或本地需要cdn時,才注入cdnif (isProduction) args[0].cdn = cdnreturn args;})// ============注入cdn end============....
3.設(shè)置public/index.html
<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width,initial-scale=1.0" /><link rel="icon" href="<%= BASE_URL %>favicon.ico" /><!-- 使用CDN的CSS文件 --><% for (var i in htmlWebpackPlugin.options.cdn &&htmlWebpackPlugin.options.cdn.css) { %><link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet" /><% } %><!-- 使用CDN的CSS文件 --><title>APP</title></head><body><noscript><strong>errorTip</strong></noscript><div id="app"></div><!-- 使用CDN的JS文件 --><% for (var i in htmlWebpackPlugin.options.cdn &&htmlWebpackPlugin.options.cdn.js) { %><script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script><% } %><!-- 使用CDN的JS文件 --><!-- built files will be auto injected --></body>
</html>
使用splitChunks進行代碼分割
? ? ? ? 在說splitChunks之前,我們需要明白module、chunk和bundle這三個名詞是什么意思:
- module:就是js的模塊化webpack支持commonJs、ES6等模塊化規(guī)范,簡單來說就是你通過import語句引入的代碼
- chunk:chunk是webpack根據(jù)功能拆分出來的,包含三種情況
- 你項目的入口(entry)
- 通過import()動態(tài)引入的的代碼
- 通過splitChunks拆分出來的代碼,chunk包含 module,可能一對多也可能是一對一
- bundle:bundle是webpack打包之后的各個文件,一般就是和chunk是一對一的關(guān)系,bundle就是對chunk進行編譯壓縮打包等處理之后的產(chǎn)出
?為了避免一次加載過大的文件和充分利用瀏覽器緩存,我們需要將不經(jīng)常被修改的文件單獨打包,即對項目中的代碼進行分割。其中,第三方依賴庫,通用組件、靜態(tài)資源、工具庫等都可以金秀賢代碼分割。在webpack中,不進行配置的話默認(rèn)打包是將所有業(yè)務(wù)代碼和第三方庫都是打包到bundle.js文件中的。預(yù)想將代碼或者第三方庫在打包的時候進行分割處理,那么就要用到optimization.splitChunks配置項,不同的配置方式將得到不同的代碼分割效果。
....
chainWebpack(config) {....config.optimization.splitChunks({chunks: "all",//表示選擇哪些模塊進行優(yōu)化。有效值為all、async和initial,默認(rèn)是僅對異步加載的塊進行分割minSize: 100000, // 模塊大于minSize時才會被分割出來。默認(rèn)100kmaxSize: 200000, // 生成的塊的最大大小,如果超過了這個限制,大塊會被拆分成多個小塊。minChunks: 1, // 拆分前必須共享模塊的最小塊數(shù)。maxAsyncRequests: 5, // 按需加載時并行請求的最大數(shù)目。maxInitialRequests: 3, // 入口點的最大并行請求數(shù)automaticNameDelimiter: '~', // 默認(rèn)情況下,webpack將使用塊的來源和名稱(例如vendors~main.js)生成名稱。此選項允許您指定要用于生成的名稱的分隔符。automaticNameMaxLength: 30, // 允許為SplitChunksPlugin生成的塊名稱的最大長度cacheGroups: {libs: {// 第三方庫name: "chunk-libs",test: /[\\/]node_modules[\\/]/,// 控制此緩存組選擇的模塊,省略它將選擇所有模塊,它可以匹配絕對模塊資源路徑名稱,匹配快名稱時,將選擇模塊中所有模塊priority: 10, // 一個模塊可以屬于多個緩存組,模塊出現(xiàn)在優(yōu)先級最高的緩存組中chunks: "initial", // 只打包初始時依賴的第三方},commons: {// 公共模塊包name: "chunk-commons",test: resolve("src/components"),minChunks: 3, // minimum common numberpriority: 5,reuseExistingChunk: true,// 如果當(dāng)前快包含已經(jīng)從主包中分離出來的模塊,那么該模塊將被重用,而不是生成新的模塊},},});....
}
....
使用代碼壓縮錢打包的文件
使用代碼分割后打包后的文件
?按需引入第三方組件庫
- Vant的按需引入:Vant 2 - Mobile UI Components built on Vue
- ?Echart的按需引入:在項目中引入 ECharts - 入門篇 - Handbook - Apache ECharts
?壓縮圖片
????????在webpack的思想中,一切皆模塊,所有模塊、樣式、圖片等等這些資源都是模塊、因為這些資源也具備模塊的特性--他們都復(fù)制特定的職能,并且具有可復(fù)用性。因此,我們可以使用webpack去管理所有這些資源,并且它們都當(dāng)作模塊來處理。
??????? 靜態(tài)資源指前端中常用的圖片,富媒體(Video、Audio)、字體文件等。webpack中靜態(tài)資源也可以作為模塊直接使用的。webpack提供了很多插件和loader對圖片進行壓縮、合并(CSS Sprite)。webpack還會使用url-loader等插件,將較小的資源通過Base64的方式引入。當(dāng)項目足夠大了之后,配置太多的靜態(tài)資源處理流程也會影響webpack的打包速度。
??????? 針對圖片資源,通常有jpg|jpeg|png|gif|ico等格式,
靜態(tài)資源指前端中常用的圖片,針對大圖片通常使用image-webpack-loader插件壓縮一下、小圖片使用url-loader轉(zhuǎn)成base64,并比較前后優(yōu)化差別。
url-loader的使用
首先,url-loader和image-webpack-loader都依賴于file-loader,file-loader簡言之就是一個資源加載模塊,去找文件資源的loader,然后也可以給靜態(tài)資源生成哈希值,即唯一識別身份證。我們主要通過url-loader和image-webpack-loader做相關(guān)對應(yīng)的配配置。
1 安裝url-loader
npm i url-loader file-loader --save
2 使用url-loader轉(zhuǎn)成base64格式的效果
image-weback-loader的使用
1.安裝image-weback-loader
注意點:若有報錯大家可以下載指定的穩(wěn)定版本以使用
npm install image-webpack-loader --save-dev
2.壓縮前后效果對比
兩個loader的完整配置
我這里使用vue項目,所以在配置文件vue.config.js中的chainWebpack加上以下代碼即可
chainWebpack(config) {config.module.rule("images").test(/\.(jpg|jpeg|png|gif|ico)$/) // 給這些圖片類型做壓縮.use("url-loader") // url-loader要搭配file-loader做圖片壓縮.loader("url-loader").options({limit: 1024 * 12,// 小于12kb的圖片壓縮成base64,圖片太大轉(zhuǎn)成base64反而不太合適name: "static/img/[name].[ext]"//指定打包后的圖片存放的位置,一般放在static下img文件夾里 name.ext分別為:文件名.文件后綴(按照原圖片名)}).end() // 返回上一級 以便于繼續(xù)添加loader.use('image-webpack-loader').loader("image-webpack-loader").options({disable: process.env.NODE_ENV == 'development' ? true : false, // 開發(fā)環(huán)境禁用壓縮,生產(chǎn)環(huán)境才做壓縮,提升開發(fā)調(diào)試速度mozjpeg: { quality: 60 }, // 壓縮JPEG圖像,壓縮質(zhì)量quality為60,范圍0到100optipng: { enabled: true }, // 壓縮PNG圖像,enabled為true開啟壓縮pngquant: { quality: [0.65, 0.90], speed: 4 }, // 質(zhì)量區(qū)間和速度就使用默認(rèn)值吧gifsicle: { interlaced: false }, // Interlace gif for progressive rendering 默認(rèn)falsewebp: { quality: 60 } // 壓縮webp圖片,壓縮質(zhì)量quality為60,范圍0到100}).end() // 返回上一級 繼續(xù)添加loader.enforce('post') // 表示先執(zhí)行配置在下面那個loader,即image-webpack-loader
},
Tree Shaking
Tree Shaking指的是當(dāng)我們引入一個模塊的時候,我不引入這個模塊的所有代碼,我只因日我需要的代碼,那么就需要Tree Shaking這個功能來幫我們實現(xiàn)。
官方有標(biāo)準(zhǔn)的說法:Tree-shaking的本質(zhì)是消除無用的js代碼。無用代碼消除在廣泛存在于傳統(tǒng)的編程語言編譯器中,編譯器可以判斷出某些代碼根本不影響輸出,然后消除這些代碼,這個稱之為DCE(dead code elimination)
在weebpack項目中,有一個入口文件,這個入口文件就相當(dāng)于一棵樹 的主干,入口文件中有很多依賴的模塊,相當(dāng)于樹枝5.實際情況下,雖然依賴了某個模塊,但其實只使用了其中的某些功能。通過Tree-Shaking,將沒有使用的模塊搖掉,以達(dá)到刪除無用代碼的目的。
而webapck5已經(jīng)自帶了這個功能,當(dāng)打包環(huán)境是production時,默認(rèn)開啟tree-shaking,若想在開發(fā)模式下配置tree shaking,在vue.config.js中加上如下即可
optimization: {usedExports: true
}
注意:也可以通過sideEffects設(shè)置不需要被tree shaking的模塊,那么我們肯定會想到,為什么需要對一些特定的模塊不做哦tree-shaking處理呢?不是所有的模塊都精簡刪除無用的代碼是最好的效果嗎?其實不是的。因為在項目中,我們肯定也會自定義去規(guī)劃一些css文件,但是由于css文件沒有導(dǎo)出任何模塊,那么就有可能在打包的時候該引入的模塊就被搖晃掉了,導(dǎo)致bug,那么此時我們就可以在package.json中設(shè)置如下,即匹配到的任何css文件都不進行Tree Shaking
webpack的Gzip和服務(wù)端的Gzip
一般來水,Gzip壓縮是服務(wù)器的活兒:服務(wù)器了解到我們這邊有一個Gzip壓縮的需求,它會啟動自己的CPU去為我們完成這個任務(wù)。而壓縮 文件這個過程本身是需要耗費時間的,可以理解為我們以服務(wù)器壓縮的時間開銷和CPU開銷(以及瀏覽器解析解析壓縮文件的開銷)為代價,省下了一些傳輸過程中的時間開銷。
既然存在這樣的交換,那么就要求我們學(xué)會權(quán)衡。服務(wù)器的CPU性能不是無限的,如果存在大量的壓縮需求,服務(wù)器也是扛不住的。服務(wù)器一旦因此慢下來了,用戶還是要等,webpack中的gzip壓縮操作的存在,事實上就是為了構(gòu)建過程中去做一部分服務(wù)器的工作,為服務(wù)器分壓。
因此,不管是webpack的Gzip還是服務(wù)器的Gzip,誰也不能替代誰,應(yīng)該結(jié)合業(yè)務(wù)壓力的實際強度情況,去做好其中的權(quán)衡。
實現(xiàn)
不是每個瀏覽器都支持Gzip,如何知道客戶端是否支持Gzip呢,請求頭中有個Accept-Encoding:gzip來標(biāo)識對壓縮的支持??蛻舳薶ttp請求頭聲明瀏覽器支持的壓縮發(fā)方式,服務(wù)器配置啟動壓縮,壓縮的文件類型,壓縮方式。當(dāng)客戶端請求到服務(wù)端的時候,服務(wù)器解析請求頭,如果客戶端支持Gzip壓縮,響應(yīng)式對請求的資源進行壓縮并返回客戶端,瀏覽器按照自己的方式解析,在http響應(yīng)頭,我們可以看到content-encoding:gzip,這是指的服務(wù)端使用了Gzip的壓縮方式
配置Gzip
1.安裝compression插件
npm install compression-webpack-plugin --s
2.配置vue.config.js
const CompressionPlugin = require("compression-webpack-plugin"); // 引入....config.plugin('compressionPlugin').use(new CompressionPlugin({algorithm: 'gzip',test: /\.js$|\.css$|\.html$/, // 匹配文件名threshold: 10240, // 對超過10k的數(shù)據(jù)壓縮minRatio: 0.8,//壓縮比deleteOriginalAssets: false // 不能刪除源文件,不然報錯"Uncaught SyntaxError: Unexpected token <"}))
....
?3.配置nginx支持gzip的操作
# 前端將文件打包成.gz文件,然后通過nginx的配置,讓瀏覽器直接解析.gz文件,可以大大提升文件加載的速度。
http {# nginx開啟Gzip:若沒有找到.gz,會動態(tài)壓縮,因此建議前端打包成.gz文件# 是否啟用Gzip(on為啟用,off為關(guān)閉)gzip on;# 設(shè)置允許壓縮的頁面最小字節(jié)數(shù),頁面字節(jié)數(shù)從header頭中的Content-Length中進行獲取。默認(rèn)值是0,不管頁面多大都壓縮。建議設(shè)置成大于1k的字節(jié)數(shù),小于1k可能會越壓越大。gzip_min_length 1k;# 獲取多少內(nèi)存用于緩存壓縮結(jié)果,‘4 16k’表示以16k*4為單位獲得gzip_buffers 4 16k;# Gzip壓縮比(1~9),越小壓縮效果越差,但是越大處理越慢,所以一般取中間值;gzip_comp_level 5;# 對特定的MIME類型生效,其中'text/html'被系統(tǒng)強制啟用(少啥類型就添加啥)gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;# 識別http協(xié)議的版本,早起瀏覽器可能不支持Gzip自解壓,用戶會看到亂碼gzip_http_version 1.1;# 啟用應(yīng)答頭"Vary: Accept-Encoding"gzip_vary on;# 配置禁用 gzip 條件,支持正則,此處表示 ie6 及以下不啟用 gzip(因為ie低版本不支持)gzip_disable "MSIE [1-6]\.";
}
gzip的優(yōu)缺點
?優(yōu)點
減少文件大小,Gzip壓縮比率在3-10倍左右,可以大大節(jié)省服務(wù)器的網(wǎng)絡(luò)帶寬。而在實際應(yīng)用中,并不是對所有的文件都進行壓縮,通常只是壓縮靜態(tài)文件。與此同時,減少文件大小有兩個明顯的好處:
- 減少存儲空間
- 通過網(wǎng)絡(luò)傳輸文件時,可以減少傳輸事件,以加快網(wǎng)站的打開速度
缺點
- 需要nginx、服務(wù)端的支持,占用了一些服務(wù)器和客戶端的CPU
- 操作失誤會造成網(wǎng)站無法訪問
- 蜘蛛無法進行爬行,造成收錄不佳
- 谷歌可以完美支持Gzipp壓縮,百度支持的并不是很友好