小程序代码压缩实践
起因
隨著業(yè)務(wù)的發(fā)展,項(xiàng)目不斷地迭代,功能模塊越加越多,項(xiàng)目代碼和靜態(tài)資源文件體積已經(jīng)超過(guò)了微信小程序限定的 2M 范圍。雖說(shuō)小程序支持分包操作,然而用戶進(jìn)入分包模塊時(shí)會(huì)有一個(gè)比較長(zhǎng)的加載時(shí)間,整體體驗(yàn)還是不友好的,萬(wàn)不得已不要分包。既然不分包,那么我們可以從哪方面來(lái)進(jìn)行項(xiàng)目體積的“瘦身”呢?
文件層
通過(guò)分析小程序的項(xiàng)目目錄我們可知,小程序的文件主要有 .js .wxs .wxss .wxml .json 后綴的文件和一些圖片資源文件,如果對(duì)這些文件進(jìn)行壓縮,項(xiàng)目的體積至少能縮減 10%。微信開(kāi)發(fā)者工具有提供代碼壓縮的配置,只要上傳代碼前勾選對(duì)應(yīng)配置即可。這對(duì)于一般小程序項(xiàng)目來(lái)說(shuō)是挺方便的,然而對(duì)于使用了第三方框架(如:wepy、taro)的項(xiàng)目,這些功能基本框架代碼編譯層面就進(jìn)行來(lái)處理,無(wú)需在開(kāi)發(fā)者工具中勾選。
既然這么完備了,我們還有必要自行編寫代碼來(lái)對(duì)這些文件進(jìn)行壓縮么?其實(shí),實(shí)際上上面提到的都會(huì)有些不足的地方,如官方提供的壓縮做不到圖片的壓縮,第三方方框架不會(huì)對(duì)部分文件進(jìn)行壓縮。所以根據(jù)我們的需求,自行寫一套自動(dòng)化壓縮流程才是最穩(wěn)妥的。
自動(dòng)化壓縮
要實(shí)現(xiàn)自動(dòng)化壓縮流程,可使用 gulp、grunt、webpack 等構(gòu)建工具來(lái)進(jìn)行構(gòu)建。由于自己比較熟悉 gulp 故使用它來(lái)進(jìn)行構(gòu)建流程的編寫。gulp 的使用比較簡(jiǎn)單,只要在項(xiàng)目根目錄新建一個(gè) gulpfile.js 文件,安裝相關(guān)依賴后就能運(yùn)行。
// 安裝
npm install gulp-cli -g
npm install --save-dev gulp
// 執(zhí)行(默認(rèn)執(zhí)行根目錄 gulpfile.js 文件)
gulp
因?yàn)?gulpfile.js 文件還沒(méi)寫內(nèi)容,所以打印只能看到 ‘default’ 任務(wù)的執(zhí)行記錄。下面我們使用 gulp 4.x 的版本進(jìn)行開(kāi)發(fā),舊版本寫法可能會(huì)有些出入,不過(guò)大致原理是一樣的。
// gulpfile.js
'use strict'
const { task, dest, src, series } = require('gulp')
task('js', function(){
// 壓縮 .js
})
task('wxml', function(){
// 壓縮 .wxml
})
// 省略...
task(
'default',
series([
'js',
'wxml',
//...
])
)
.js 壓縮
原本是使用 gulp-uglify 這個(gè)庫(kù)進(jìn)行壓縮的,后發(fā)現(xiàn)其不支持 es6語(yǔ)法,所以轉(zhuǎn)用 gulp-uglify-es,兩者配置有些出入。小程序的 js 文件因其語(yǔ)法和標(biāo)準(zhǔn)的 JavaScript 語(yǔ)法是一樣的,故無(wú)需額外配置,直接默認(rèn)配置壓縮即可。
const uglify = require('gulp-uglify-es').default
task('js', function () {
return src('dist/**/*.js')
.pipe(uglify())
.pipe(dest('dist/'))
})
第三方框架 wepy 或者 taro 都會(huì)對(duì) .js 文件進(jìn)行壓縮,正常情況下我們無(wú)需二次壓縮。
.wxs 壓縮
WXS(WeiXin Script)是小程序的一套腳本語(yǔ)言,結(jié)合 WXML,可以構(gòu)建出頁(yè)面的結(jié)構(gòu)。
WXS 與 JavaScript 是不同的語(yǔ)言,有自己的語(yǔ)法,并不和 JavaScript 一致。
正常情況下 .wxs 文件可以和 .js 一樣可通過(guò) gulp-uglify-es 來(lái)進(jìn)行壓縮,不過(guò)由于微信坑爹地搞特殊化,wxs 就像嚴(yán)重閹割版的 js,在壓縮后會(huì)出現(xiàn)各種問(wèn)題。
task('wxs', function () {
return src(['dist/**/*.wxs', '!dist/components/vant/wxs/add-unit.wxs'])
.pipe(uglify({
compress: {
ie8: true, // 支持 ie8,為了禁止 a === undefined 自動(dòng)轉(zhuǎn)換為 void 0 === a
join_vars: false // 禁止合并 var
}
}))
.pipe(dest('dist/'))
})
代碼中 "!dist/components/vant/wxs/add-unit.wxs" 是排除掉了此文件,原因是暫無(wú)方法解決壓縮后部分正則無(wú)法正常使用的問(wèn)題。
// 壓縮前
var REGEXP = getRegExp('^d+(.d+)?$');
// 壓縮后
var REGEXP = getRegExp('^d+(.d+)?$');
微信的 getRegExp 并不像 new RegExp 那樣支持雙反斜桿進(jìn)行字符轉(zhuǎn)義,巨坑!!!老實(shí)說(shuō),如果官方不出專門的壓縮庫(kù),感覺(jué)最好還是不要壓縮這個(gè)東西。
.wxss 壓縮
樣式和常規(guī)的的 css 文件的差不多,因此可以通過(guò)一般的樣式工具進(jìn)行壓縮,比如 gulp-clean-css 第三方庫(kù)。
const cleanCss = require('gulp-clean-css')
const replaceimport = require('./replaceImport')
task('wxss', function () {
return src('dist/**/*.wxss')
.pipe(cleanCss({ compatibility: '*', inline: false }))
.pipe(replaceimport())
.pipe(dest('dist/'))
})
由于壓縮老版本 wepy 項(xiàng)目樣式時(shí),組件引入樣式 @import "xxx" 會(huì)被轉(zhuǎn)成 @import url('xxx') 在開(kāi)發(fā)者工具中會(huì)報(bào)錯(cuò),故寫了 replaceimport 轉(zhuǎn)回來(lái),taro 項(xiàng)目無(wú)需加。
// replaceimport.js
'use strict';
/**
* 由于使用 gulp-clean-css 進(jìn)行樣式壓縮時(shí)
* 會(huì)將 `@import "xxx";` 轉(zhuǎn)成 `@import url(xxx);`
* 而轉(zhuǎn)化后的 import 在小程序開(kāi)發(fā)者工具會(huì)報(bào)錯(cuò)
* 所以用此方法,將 @import url(xxx) 轉(zhuǎn)回 @import 'xxx'
*/
const through = require('through2')
module.exports = () => through.obj(function (file, enc, next) {
if (file.isNull()) {
next(null, file);
return;
}
try {
const reg = /(?:^|s)?(?:@import)(?:s)(?:url)?(?:(?:(?:()(["'])?(?:https?:)?([^"')]+)1(?:))|(["'])(?:.+)2)(?:[A-Zs])*)+(?:;)/ig
file.contents = new Buffer.from(file.contents.toString().replace(reg, '@import "$2";'))
next(null, file)
} catch (error) {
next(null, file)
}
});
.wxml 壓縮
WXML(WeiXin Markup Language)是框架設(shè)計(jì)的一套標(biāo)簽語(yǔ)言,結(jié)合基礎(chǔ)組件、事件系統(tǒng),可以構(gòu)建出頁(yè)面的結(jié)構(gòu)。
其實(shí)跟 HTML 類似的標(biāo)簽語(yǔ)言,我們可以通過(guò)現(xiàn)有的 HTML 壓縮庫(kù)來(lái)對(duì)其進(jìn)行壓縮。這里我們可以用 gulp-htmlmin 。
const htmlmin = require('gulp-htmlmin')
task('wxml', function () {
return src('dist/**/*.wxml').pipe(htmlmin({
caseSensitive: true, // 大小寫敏感
removeComments: true, // 刪除 HTML 注釋
keepClosingSlash: true, // 單標(biāo)簽上保留斜線
collapseWhitespace: true,// 壓縮 HTML
ignoreCustomFragments: [
/<input([sS]*?)</input>/
],
})).pipe(dest('dist/'))
})
用來(lái)壓縮 taro 的 base.wxml 文件時(shí)會(huì)報(bào)錯(cuò),原因是 <input></input> 標(biāo)簽被壓縮成 <input> 了,小程序 input 標(biāo)簽必須要有斜杠結(jié)尾,否則會(huì)報(bào)錯(cuò)。因此,我們?cè)?ignoreCustomFragments 通過(guò)設(shè)置正則排除掉 <input></input> 這種情況。
.json 壓縮
要壓縮 json 文件,最簡(jiǎn)單的方法是直接 JSON.parse(JSON.stringify('jsonString')) ,既然我們用了 gulp ,那就用第三方庫(kù) gulp-jsonminify 吧,方便維護(hù)。
const jsonminify = require('gulp-jsonminify')
task('json', function () {
return src('dist/**/*.json')
.pipe(jsonminify())
.pipe(dest('dist/'))
})
圖片壓縮
一般情況下第三方框架都會(huì)有對(duì)圖片進(jìn)行壓縮處理的,如果是小程序原生的項(xiàng)目,可以安裝 gulp-imagemin 對(duì)圖片進(jìn)行壓縮。
const imagemin = require('gulp-imagemin')
task('json', function () {
return src('dist/*')
.pipe(jsonminify())
.pipe(dest('dist/'))
})
代碼層
微信開(kāi)發(fā)者工具自帶的代碼分析工具,我們可以很直觀地知道哪個(gè)文件夾、哪個(gè)文件比較大,分析后專門對(duì)比較大的文件進(jìn)行優(yōu)化即可。
替換較小第三方庫(kù)
有些第三方包是可以替換的,比如早期常用的 moment.js 可用 dayjs 替換,只需要做一些轉(zhuǎn)化,體積能減少將近 40K。再比如加解密用的 crypto-js,我們通常只用到其中部分模塊,舊版本 wepy 是沒(méi)有做 tree-shaking 的,需要手動(dòng)移除無(wú)用模塊。還有些第三方庫(kù),項(xiàng)目中只用到其中一些簡(jiǎn)單的功能,大可自行寫一個(gè),不必使用第三方庫(kù)。
樣式引入方式更改
通過(guò)分析打包后的 .wxss 文件,發(fā)現(xiàn) wepy 編譯壓縮 less 樣式時(shí),less 會(huì)將 @import 引用的文件直接編譯進(jìn)來(lái),造成文件體積的增大,比如
/* a.less */
.a{
color: red;
}
/* b.less */
@imoprt 'a.less';
.b{
color: black;
}
/* output: b.wxss */
.a{
color: red;
}
.b{
color: black;
}
要解決這個(gè)問(wèn)題,只需改成 [@import (css)](https://lesscss.org/features/#import-atrules-feature)
/* b.less */
@imoprt (css) 'a.less';
.b{
color: black;
}
/* output: b.wxss */
@import 'a.wxss';
.b{
color: black;
}
改完這個(gè),估計(jì)項(xiàng)目體積又減少了 200K 左右。在改的時(shí)候頁(yè)面眾多,要進(jìn)行更改,比較合理的做法是通過(guò)正則表達(dá)式進(jìn)行全局替換。還可能涉及到批量重命名文件后綴,由于不同的編輯器和操作系統(tǒng)不同,就不展開(kāi)說(shuō)了。
刪減無(wú)關(guān)配置
刪除某個(gè)頁(yè)面或者模塊后,記得同時(shí)刪除其頁(yè)面配置路徑。由于 wepy 會(huì)根據(jù)頁(yè)面路徑配置去生成頁(yè)面,如果頁(yè)面已刪除,頁(yè)面配置 pages 中的路徑?jīng)]刪除,執(zhí)行 build 打包后它會(huì)自動(dòng)生成一個(gè)空白的基礎(chǔ)頁(yè)面。
簡(jiǎn)化文件層級(jí)
這個(gè)其實(shí)既是文件層亦是代碼層,原因是,如果你的文件層級(jí)越多,在引入的時(shí)候路徑就越長(zhǎng),占用的字節(jié)數(shù)就會(huì)越多。如果有上百處引用的化,還是挺占體積的。
字體圖標(biāo)提取
分析樣式文件還發(fā)現(xiàn)一個(gè)問(wèn)題,就是存在比較多的零散的字體圖標(biāo),其使用方式是通過(guò) @font-face 以字體 base64 的形式進(jìn)行引入。這樣做的好處是圖標(biāo)不會(huì)失真,存放在樣式中頁(yè)面樣式加載完成即可顯示,不好的地方是不方便管理,不好復(fù)用。要解決這個(gè)問(wèn)題常用的方法是使用第三方的 UI 組件庫(kù),一般成熟的組件庫(kù)都會(huì)有配套的圖標(biāo)組件,還有一種做法是自行在阿里圖標(biāo)網(wǎng)站新建一個(gè)圖標(biāo)目錄將用到的字體圖標(biāo)上傳上去,然后通過(guò)阿里圖標(biāo)網(wǎng)站生成字體圖標(biāo)文件,將文件下載下來(lái)放到服務(wù)器存著,用的時(shí)候使用鏈接引入就好。當(dāng)然,為了方便管理最好是在本地寫腳本去自動(dòng)執(zhí)行下載和上傳,同時(shí)做版本管理方便后續(xù)維護(hù)更新。
總結(jié)
經(jīng)過(guò)一頓操作,老項(xiàng)目的體積從 2M 變成了 1.5M,既興奮又有點(diǎn)不好意思,不好意思是因?yàn)橹皩懙拇a太多不足了。不過(guò),好在如今小程序的生態(tài)越來(lái)越完備了,很多東西都無(wú)需從零開(kāi)始,現(xiàn)在在做的新項(xiàng)目都會(huì)考慮比較全面。雖然時(shí)常想吐槽微信小程序的各種不友好,可回過(guò)頭來(lái)看看這幾年的小程序開(kāi)發(fā),自己何不嘗是從一個(gè)編程小白一步步地在成長(zhǎng),說(shuō)起來(lái)還是要感謝微信賞飯吃呢。
作者:五更
出處:http://www.cnblogs.com/teemwu/
原文:http://www.cnblogs.com/teemwu/
歡迎交流,轉(zhuǎn)載請(qǐng)標(biāo)明出處,謝謝。
總結(jié)
- 上一篇: Android闹钟动画,学习Androi
- 下一篇: 网络通信协议之粘包问题