BeautyWe.js 一套專注於微信小程式的開發正規化
- 作者:JerryC
- 原文:BeautyWe.js 一套專注於微信小程式的開發正規化
Fundebug經授權轉載,版權歸原作者所有。
官網:beautywejs.com Repo: beautywe
一個簡單的介紹
BeautyWe.js 是什麼?
它是一套專注於微信小程式的企業級開發正規化,它的願景是:
讓企業級的微信小程式專案中的程式碼,更加簡單、漂亮。
為什麼要這樣命名呢?
Write beautiful code for wechat mini program by the beautiful we!
「We」 既是我們的 We,也是微信的 We,Both beautiful!
那麼它有什麼賣點呢?
- 專注於微信小程式環境,寫原汁原味的微信小程式程式碼。
- 由於只專注於微信小程式,它的原始碼也很簡單。
- 外掛化的程式設計方式,讓複雜邏輯更容易封裝。
- 再加上一些配套設施:
- 一些官方外掛。
- 一套開箱即用,包含了工程化、專案規範以及微信小程式環境獨特問題解決方案的框架。
- 一個CLI工具,幫你快速建立應用,頁面,元件等。
它由以下幾部分組成:
- 一個外掛化的核心 - BeautyWe Core 對 App、Page 進行抽象和包裝,保持傳統微信小程式開發姿勢,同時開放部分原生能力,讓其具有「可外掛化」的能力。
- 一些官方外掛 — BeautyWe Plugins 得益於 Core 的「可外掛化」特性,封裝複雜邏輯,實現可插拔。官方對於常見的需求提供了一些外掛:如增強儲存、釋出/訂閱、狀態機、Logger、快取策略等。
- 一套開箱即用的專案框架 - BeautyWe Framework
描述了一種專案的組織形式,開箱即用,集成了
BeautyWe Core
,並且提供瞭如:全域性視窗、開發規範、多環境開發、全域性配置、NPM 等解決方案。 - 一個CLI工具 - BeautyWe Cli 提供快速建立應用、頁面、外掛,以及專案構建功能的命令列工具。並且還支援自定義的建立模板。
一個簡單的例子
下載
用 BeautyWe 包裝你的應用
之後,你就能使用 BeautyWe Plugin 提供的能力了。
開放原生App/Page,支援外掛化
new BtApp({...})
的執行結果是對原生的應用進行包裝,其中包含了「外掛化」的處理,然後返回一個新的例項,這個例項適配原生的 App()
下面來講講「外掛化」到底做了什麼事情。
首先,外掛化開放了原生 App 的四種能力:
- Data 域 把外掛的 Data 域合併到原生 App 的 Data 域中,這一塊很容易理解。
- 原生鉤子函式
使原生鉤子函式(如
onShow
,onLoad
)可外掛化。讓原生App與多個外掛可以同時監聽同一個鉤子函式。如何工作的,下面會細說。 - 事件鉤子函式 使事件鉤子函式(與 view 層互動的鉤子函式),儘管在實現上有一些差異,但是實現原理跟「原生鉤子函式」一樣的。
- 自定義方法
讓外掛能夠給使用者提供 API。為了保證外掛提供的 API 足夠的優雅,支援當呼叫外掛 API 的時候(如 event 外掛
this.event.on(...)
),API 方法內部仍然能通過this
獲取到原生例項。
鉤子函式的外掛化
原生鉤子函式,事件鉤子函式我們統一稱為「鉤子函式」。
對於每一個鉤子函式,內部是維護一個以 Series Promise 方式執行的執行佇列。
以 onShow
為例,將會以這樣的形式執行:
native.onShow → pluginA.onShow → pluginB.onShow → ...
下面深入一下外掛化的原理:
工作原理是這樣的:
- 經過
new BtApp(...)
包裝,所有的鉤子函式,都會有一個獨立的執行佇列, - 首先會把原生的各個鉤子函式
push
到對應的佇列中。然後每use
外掛的時候,都會分解外掛的鉤子函式,往對應的佇列push
。 - 當
Native App
(原生)觸發某個鉤子的時候,BtApp
會以 Promise Series 的形式按循序執行對應佇列裡面的函式。 - 特殊的,
onLaunch
和onLoad
的執行佇列中,會在佇列頂部插入一個初始化的任務(initialize
),它會以同步的方式按循序執行Initialize Queue
裡面的函式。這正是外掛生命週期函式中的plugin.initialize
。
這種設計能提供以下功能:
- 可外掛化。 只需要往對應鉤子函式的事件佇列中插入任務。
- 支援非同步。
由於是以 Promise Series 方式執行的,其中一個任務返回一個 Promise,下一個任務會等待這個任務完成再開始。如果發生錯誤,會流轉到原生的
onError()
中。 - 解決了微信小程式
app.js
中getApp() === undefinded
問題。 造成這個問題,本質是因為App()
的時候,原生例項未建立。但是由於 Promise 在 event loop 中是一個微任務,被註冊在下一次迴圈。所以 Promise 執行的時候App()
早已經完成了。
一些官方外掛
BeautyWe 官方提供了一系列的外掛:
- 增強儲存: Storage
- 資料列表:List Page
- 快取策略:Cache
- 日誌:Logger
- 事件釋出/訂閱:Event
- 狀態機:Status
它們的使用很簡單,哪裡需要插哪裡。 由於篇幅的原因,下面挑幾個比較有趣的來講講,更多的可以看看官方文件:BeautyWe
增強儲存 Storage
該功能由 @beautywe/plugin-storage 提供。
由於微信小程式原生的資料儲存生命週期跟小程式本身一致,即除使用者主動刪除或超過一定時間被自動清理,否則資料都一直可用。
所以該外掛在 wx.getStorage/setStorage
的基礎上,提供了兩種擴充套件能力:
- 過期控制
- 版本隔離
一些簡單的例子
安裝
import { BtApp } from '@beautywe/core';
import storage from '@beautywe/plugin-storage';
const app = new BtApp();
app.use(storage());
過期控制
// 7天后過期
app.storage.set('name', 'jc', { expire: 7 });
版本隔離
app.use({ appVersion: '0.0.1' });
app.set('name', 'jc');
// 返回 jc
app.get('name');
// 當版本更新後
app.use({ appVersion: '0.0.2' });
// 返回 undefined;
app.get('name');
更多的檢視 @beautywe/plugin-storage 官方文件
資料列表 List Page
對於十分常見的資料列表分頁的業務場景,@beautywe/plugin-listpage
提供了一套打包方案:
- 滿足常用「資料列表分頁」的業務場景
- 支援分頁
- 支援多個數據列表
- 自動捕捉下拉過載:
onPullDownRefresh
- 自動捕捉上拉載入:
onReachBottom
- 自帶請求鎖,防止帕金森氏手抖使用者
- 簡單優雅的 API
一個簡單的例子:
import BeautyWe from '@beautywe/core';
import listpage from '@beautywe/plugin-listpage';
const page = new BeautyWe.BtPage();
// 使用 listpage 外掛
page.use(listpage({
lists: [{
name: 'goods', // 資料名
pageSize: 20, // 每頁多少條資料,預設 10
// 每一頁的資料來源,沒次載入頁面時,會呼叫函式,然後取返回的資料。
fetchPageData({ pageNo, pageSize }) {
// 獲取資料
return API.getGoodsList({ pageNo, pageSize })
// 有時候,需要對伺服器的資料進行處理,dataCooker 是你定義的函式。
.then((rawData) => dataCooker(rawData));
},
}],
enabledPullDownRefresh: true, // 開啟下拉過載, 預設 false
enabledReachBottom: true, // 開啟上拉載入, 預設 false
}));
// goods 資料會被載入到,goods 為上面定義的 name
// this.data.listPage.goods = {
// data: [...], // 檢視層,通過該欄位來獲取具體的資料
// hasMore: true, // 檢視層,通過該欄位來識別是否有下一頁
// currentPage: 1, // 檢視層,通過該欄位來識別當前第幾頁
// totalPage: undefined,
// }
只需要告訴 listpage
如何獲取資料,它會自動處理「下拉過載」、「上拉翻頁」的操作,然後把資料更新到 this.data.listPage.goods
下。
View 層只需要描述資料怎麼展示:
<view class="good" wx:for="listPage.goods.data">
...
</view>
<view class="no-more" wx:if="listPage.goods.hasMore === false">
沒有更多了
</view>
listpage
還支援多資料列表等其他更多配置,詳情看:@beautywe/plugin-listpage
快取策略 Cache
@beautywe/plugin-cache
提供了一個微信小程式端快取策略,其底層由 super-cache 提供支援。
特性
- 提供一套「服務端介面耗時慢,但載入效能要求高」場景的解決方案
- 滿足最基本的快取需求,讀取(get)和儲存(set)
- 支援針對快取進行邏輯代理
- 靈活可配置的資料儲存方式
How it work
一般的請求資料的形式是,頁面載入的時候,從服務端獲取資料,然後等待資料返回之後,進行頁面渲染:
但這種模式,會受到服務端介面耗時,網路環境等因素影響到載入效能。
對於載入效能要求高的頁面(如首頁),一般的 Web 開發我們有很多解決方案(如服務端渲染,服務端快取,SSR 等)。 但是也有一些環境不能使用這種技術(如微信小程式)。
Super Cache 提供了一箇中間資料快取的解決方案:
思路:
- 當你需要獲取一個數據的時候,如果有快取,先把舊的資料給你。
- 然後再從服務端獲取新的資料,重新整理快取。
- 如果一開始沒有快取,則請求服務端資料,再把資料返回。
- 下一次請求快取,從第一步開始。
這種解決方案,捨棄了一點資料的實時性(非第一次請求,只能獲取上一次最新資料),大大提高了前端的載入效能。 適合的場景:
- 資料實時性要求不高。
- 服務端介面耗時長。
使用
import { BtApp } from '@beautywe/core';
import cache from '@beautywe/plugin-cache';
const app = new BtApp();
app.use(cache({
adapters: [{
key: 'name',
data() {
return API.fetch('xxx/name');
}
}]
}));
假設 API.fetch('xxx/name')
是請求伺服器介面,返回資料:data_from_server
那麼:
app.cache.get('name').then((value) => {
// value: 'data_from_server'
});
更多的配置,詳情看:@beautywe/plugin-cache
日誌 Logger
由 @beautywe/logger-plugin
提供的一個輕量的日誌處理方案,它支援:
- 可控的 log level
- 自定義字首
- 日誌統一處理
使用
import { BtApp } from '@beautywe/core';
import logger from '@beautywe/plugin-logger';
const page = new BtApp();
page.use(logger({
// options
}));
API
page.logger.info('this is info');
page.logger.warn('this is warn');
page.logger.error('this is error');
page.logger.debug('this is debug');
// 輸出
// [info] this is info
// [warn] this is warn
// [error] this is error
// [debug] this is debug
Level control
可通過配置來控制哪些 level 該列印:
page.use(logger({
level: 'warn',
}));
那麼 warn
以上的 log (info
, debug
)就不會被列印,這種滿足於開發和生成環境對 log 的不同需求。
level 等級如下:
Logger.LEVEL = {
error: 1,
warn: 2,
info: 3,
debug: 4,
};
更多的配置,詳情看:@beautywe/plugin-logger
BeautyWe Framework
@beautywe/core
和 @beautywe/plugin-...
給小程式提供了:
- 開放原生,支援外掛化 —— by core
- 各種外掛 —— by plugins
但是,還有很多的開發中實際還會遇到的痛點,是上面兩個解決不到的。 如專案的組織、規範、工程化、配置、多環境等等
這些就是,「BeautyWe Framework」要解決的範疇。
它作為一套開箱即用的專案框架,提供了這些功能:
- 整合 BeautyWe Core
- NPM 支援
- 全域性視窗
- 全域性 Page,Component
- 全域性配置檔案
- 多環境開發
- Example Pages
- 正常專案需要的標配:ES2015+,sass,uglify,watch 等
- 以及我們認為良好的專案規範(eslint,commit log,目錄結構等)
也是由於篇幅原因,挑幾個有趣的來講講,更多的可以看看官方文件:BeautyWe
快速建立
首先安裝 @beautywe/cli
$ npm i @beautywe/cli -g
建立應用
$ beautywe new app
> appName: my-app
> version: 0.0.1
> appid: 123456
> 這樣可以麼:
> {
> "appName": "my-app",
> "version": "0.0.1",
> "appid": "123456"
> }
回答幾個問題之後,專案就生成了:
my-app
├── gulpfile.js
├── package.json
└── src
├── app.js
├── app.json
├── app.scss
├── assets
├── components
├── config
├── examples
├── libs
├── npm
├── pages
└── project.config.json
建立頁面、元件、外掛
頁面
- 主包頁面:
beautywe new page <path|name>
- 分包頁面:
beautywe new page --subpkg <subPackageName> <path|name>
元件
beautywe new component <name>
外掛
beautywe new plugin <name>
自定義模板
在 ./.templates
目錄中,存放著快速建立命令的建立模板:
$ tree .templates
.templates
├── component
│ ├── index.js
│ ├── index.json
│ ├── index.scss
│ └── index.wxml
├── page
│ ├── index.js
│ ├── index.json
│ ├── index.scss
│ └── index.wxml
└── plugin
└── index.js
可以修改裡面的模板,來滿足專案級別的自定義模板建立。
全域性視窗
我們都知道微信小程式是「單視窗」的互動平臺,一個頁面對應一個視窗。 而在業務開發中,往往會有諸如這種述求:
- 自定義的 toast 樣式
- 頁面底部 copyright
- 全域性的 loading 樣式
- 全域性的懸浮控制元件
......
稍微不優雅的實現可以是分別做成獨立的元件,然後每一個頁面都引入進來。 這種做法,我們會有很多的重複程式碼,並且每次新建頁面,都要引入一遍,後期維護也會很繁瑣。
而「全域性視窗」的概念是:希望所有頁面之上有一塊地方,全域性性的邏輯和互動,可以往裡面擱。
global-view 元件
這是一個自定義元件,原始碼在 /src/components/global-view
每個頁面的 wxml 只需要在頂層包一層:
<global-view id="global-view">
...
</global-view>
需要全域性實現的互動、樣式、元件,只需要維護這個元件就足夠了。
全域性配置檔案
在 src/config/
目錄中,可以存放各種全域性的配置檔案,並且支援以 Node.js 的方式執行。(得益於 Node.js Power 特性)。
如 src/config/logger.js
:
const env = process.env.RUN_ENV || 'dev';
const logger = Object.assign({
prefix: 'BeautyWe',
level: 'debug',
}, {
// 開發環境的配置
dev: {
level: 'debug',
},
// 測試環境的配置
test: {
level: 'info',
},
// 線上環境的配置
prod: {
level: 'warn',
},
}[env] || {});
module.exports.logger = logger;
然後我們可以這樣讀取到 config 內容:
import { logger } from '/config/index';
// logger.level 會根據環境不同而不同。
Beautywe Framework 預設會把 config 整合到 getApp()
的示例中:
getApp().config;
多環境開發
BeautyWe Framework 支援多環境開發,其中預設了三套策略:
- dev
- test
- prod
我們可以通過命令來執行這三個構建策略:
beautywe run dev
beautywe run test
beautywe run prod
三套環境的差異
Beautywe Framework 原始碼預設在兩方面使用了多環境:
- 構建任務(
gulpfile.js/env/...
) - 全域性配置(
src/config/...
)
構建任務的差異
構建任務 | 說明 | dev | test | prod |
---|---|---|---|---|
clean | 清除dist檔案 | √ | √ | √ |
copy | 複製資原始檔 | √ | √ | √ |
scripts | 編譯JS檔案 | √ | √ | √ |
sass | 編譯scss檔案 | √ | √ | √ |
npm | 編譯npm檔案 | √ | √ | √ |
nodejs-power | 編譯Node.js檔案 | √ | √ | √ |
watch | 監聽檔案修改 | √ | ||
scripts-min | 壓縮JS檔案 | √ | ||
sass-min | 壓縮scss檔案 | √ | ||
npm-min | 壓縮npm檔案 | √ | ||
image-min | 壓縮圖片檔案 | √ | ||
clean-example | 清除示例頁面 | √ |
Node.js Power
Beautywe Framework 的程式碼有兩種執行環境:
- Node.js 執行環境,如構建任務等。
- 微信小程式執行環境,如打包到
dist
資料夾的程式碼。
執行過程
Node.js Power 本質是一種靜態編譯的實現。 把某個檔案在 Node.js 環境執行的結果,輸出到微信小程式執行環境中,以此來滿足特定的需求。
Node.js Power 會把專案中 src
目錄下類似 xxx.nodepower.js
命名的檔案,以 Node.js 來執行,
然後把執行的結果,以「字面量物件」的形式寫到 dist
目錄下對應的同名檔案 xxx.nodepower.js
檔案去。
以 src/config/index.nodepower.js
為例:
const fs = require('fs');
const path = require('path');
const files = fs.readdirSync(path.join(__dirname));
const result = {};
files
.filter(name => name !== 'index.js')
.forEach((name) => {
Object.assign(result, require(path.join(__dirname, `./${name}`)));
});
module.exports = result;
該檔案,經過 Node.js Power 構建之後:
dist/config/index.nodepower.js
:
module.exports = {
"appInfo": {
"version": "0.0.1",
"env": "test",
"appid": "wx85fc0d03fb0b224d",
"name": "beautywe-framework-test-app"
},
"logger": {
"prefix": "BeautyWe",
"level": "info"
}
};
這就滿足了,隨意往 src/config/
目錄中擴充套件配置檔案,都能被自動打包。
Node.js Power 已經被整合到多環境開發的 dev, test, prod 中去。
當然,你可以手動執行這個構建任務:
$ gulp nodejs-power
NPM
BeautyWe Framework 實現支援 npm 的原理很簡單,總結一句話:
使用 webpack 打包
src/npm/index.js
,以 commonjs 格式輸出到dist/npm/index.js
這樣做的好處:
- 實現簡單。
- 讓 npm 包能集中管理,每次引入依賴,都好好的想一下,避免氾濫(尤其在多人開發中)。
- 使用
ll dist/npm/index.js
命令能快速看到專案中的 npm 包使佔了多少容量。
新增 npm 依賴
在 src/npm/index.js
檔案中,進行 export:
export { default as beautywe } from '@beautywe/core';
然後在其他檔案 import:
import { beautywe } from './npm/index';
更多
總的來說,BeautyWe 是一套微信小程式的開發正規化。
core
和 plugins
擴充套件原生,提供複雜邏輯的封裝和插拔式使用。
而 framework
則負責提供一整套針對於微信小程式的企業級專案解決方案,開箱即用。
其中還有更多的內容,歡迎瀏覽官網:beautywejs.com
關於Fundebug
Fundebug專注於JavaScript、微信小程式、微信小遊戲、支付寶小程式、React Native、Node.js和Java線上應用實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了10億+錯誤事件,付費客戶有陽光保險、核桃程式設計、荔枝FM、掌門1對1、微脈、青團社等眾多品牌企業。歡迎