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