BeautyWe.js 是什么?
它是一套專注于微信小程序的企業(yè)級(jí)開發(fā)范式,它的愿景是:
讓企業(yè)級(jí)的微信小程序項(xiàng)目中的代碼,更加簡(jiǎn)單、漂亮。
為什么要這樣命名呢?
Write beautiful code for wechat mini program by the beautiful we!
「We」 既是我們的 We,也是微信的 We,Both beautiful!
那么它有什么賣點(diǎn)呢?
它由以下幾部分組成:
BeautyWe Core
,并且提供了如:全局窗口、開發(fā)規(guī)范、多環(huán)境開發(fā)、全局配置、NPM 等解決方案。下載
用 BeautyWe 包裝你的應(yīng)用
之后,你就能使用 BeautyWe Plugin 提供的能力了。
new BtApp({...})
的執(zhí)行結(jié)果是對(duì)原生的應(yīng)用進(jìn)行包裝,其中包含了「插件化」的處理,然后返回一個(gè)新的實(shí)例,這個(gè)實(shí)例適配原生的 App()
方法。
下面來(lái)講講「插件化」到底做了什么事情。
首先,插件化開放了原生 App 的四種能力:
onShow
, onLoad
)可插件化。讓原生App與多個(gè)插件可以同時(shí)監(jiān)聽同一個(gè)鉤子函數(shù)。如何工作的,下面會(huì)細(xì)說(shuō)。this.event.on(...)
),API 方法內(nèi)部仍然能通過(guò) this
獲取到原生實(shí)例。原生鉤子函數(shù),事件鉤子函數(shù)我們統(tǒng)一稱為「鉤子函數(shù)」。
對(duì)于每一個(gè)鉤子函數(shù),內(nèi)部是維護(hù)一個(gè)以 Series Promise 方式執(zhí)行的執(zhí)行隊(duì)列。
以 onShow
為例,將會(huì)以這樣的形式執(zhí)行:
native.onShow → pluginA.onShow → pluginB.onShow → ...
下面深入一下插件化的原理:
工作原理是這樣的:
new BtApp(...)
包裝,所有的鉤子函數(shù),都會(huì)有一個(gè)獨(dú)立的執(zhí)行隊(duì)列,push
到對(duì)應(yīng)的隊(duì)列中。然后每 use
插件的時(shí)候,都會(huì)分解插件的鉤子函數(shù),往對(duì)應(yīng)的隊(duì)列 push
。Native App
(原生)觸發(fā)某個(gè)鉤子的時(shí)候,BtApp
會(huì)以 Promise Series 的形式按循序執(zhí)行對(duì)應(yīng)隊(duì)列里面的函數(shù)。onLaunch
和 onLoad
的執(zhí)行隊(duì)列中,會(huì)在隊(duì)列頂部插入一個(gè)初始化的任務(wù)(initialize
),它會(huì)以同步的方式按循序執(zhí)行 Initialize Queue
里面的函數(shù)。這正是插件生命周期函數(shù)中的 plugin.initialize
。這種設(shè)計(jì)能提供以下功能:
onError()
中。app.js
中 getApp() === undefinded
問(wèn)題。 造成這個(gè)問(wèn)題,本質(zhì)是因?yàn)?nbsp;App()
的時(shí)候,原生實(shí)例未創(chuàng)建。但是由于 Promise 在 event loop 中是一個(gè)微任務(wù),被注冊(cè)在下一次循環(huán)。所以 Promise 執(zhí)行的時(shí)候 App()
早已經(jīng)完成了。BeautyWe 官方提供了一系列的插件:
它們的使用很簡(jiǎn)單,哪里需要插哪里。 由于篇幅的原因,下面挑幾個(gè)比較有趣的來(lái)講講,更多的可以看看官方文檔:BeautyWe
該功能由 @beautywe/plugin-storage 提供。
由于微信小程序原生的數(shù)據(jù)存儲(chǔ)生命周期跟小程序本身一致,即除用戶主動(dòng)刪除或超過(guò)一定時(shí)間被自動(dòng)清理,否則數(shù)據(jù)都一直可用。
所以該插件在 wx.getStorage/setStorage
的基礎(chǔ)上,提供了兩種擴(kuò)展能力:
一些簡(jiǎn)單的例子
安裝
import { BtApp } from '@beautywe/core';
import storage from '@beautywe/plugin-storage';
?
const app = new BtApp();
app.use(storage());
過(guò)期控制
// 7天后過(guò)期
app.storage.set('name', 'jc', { expire: 7 });
版本隔離
app.use({ appVersion: '0.0.1' });
app.set('name', 'jc');
?
// 返回 jc
app.get('name');
?
// 當(dāng)版本更新后
app.use({ appVersion: '0.0.2' });
?
// 返回 undefined;
app.get('name');
更多的查看 @beautywe/plugin-storage 官方文檔
對(duì)于十分常見的數(shù)據(jù)列表分頁(yè)的業(yè)務(wù)場(chǎng)景,@beautywe/plugin-listpage
提供了一套打包方案:
onPullDownRefresh
onReachBottom
一個(gè)簡(jiǎn)單的例子:
import BeautyWe from '@beautywe/core';
import listpage from '@beautywe/plugin-listpage';
?
const page = new BeautyWe.BtPage();
?
// 使用 listpage 插件
page.use(listpage({
lists: [{
name: 'goods', // 數(shù)據(jù)名
pageSize: 20, // 每頁(yè)多少條數(shù)據(jù),默認(rèn) 10
?
// 每一頁(yè)的數(shù)據(jù)源,沒(méi)次加載頁(yè)面時(shí),會(huì)調(diào)用函數(shù),然后取返回的數(shù)據(jù)。
fetchPageData({ pageNo, pageSize }) {
// 獲取數(shù)據(jù)
return API.getGoodsList({ pageNo, pageSize })
// 有時(shí)候,需要對(duì)服務(wù)器的數(shù)據(jù)進(jìn)行處理,dataCooker 是你定義的函數(shù)。
.then((rawData) => dataCooker(rawData));
},
}],
enabledPullDownRefresh: true, // 開啟下拉重載, 默認(rèn) false
enabledReachBottom: true, // 開啟上拉加載, 默認(rèn) false
}));
?
// goods 數(shù)據(jù)會(huì)被加載到,goods 為上面定義的 name
// this.data.listPage.goods = {
// data: [...], // 視圖層,通過(guò)該字段來(lái)獲取具體的數(shù)據(jù)
// hasMore: true, // 視圖層,通過(guò)該字段來(lái)識(shí)別是否有下一頁(yè)
// currentPage: 1, // 視圖層,通過(guò)該字段來(lái)識(shí)別當(dāng)前第幾頁(yè)
// totalPage: undefined,
// }
只需要告訴 listpage
如何獲取數(shù)據(jù),它會(huì)自動(dòng)處理「下拉重載」、「上拉翻頁(yè)」的操作,然后把數(shù)據(jù)更新到 this.data.listPage.goods
下。
View 層只需要描述數(shù)據(jù)怎么展示:
<view class="good" wx:for="listPage.goods.data">
...
</view>
<view class="no-more" wx:if="listPage.goods.hasMore === false">
沒(méi)有更多了
</view>
listpage
還支持多數(shù)據(jù)列表等其他更多配置,詳情看:@beautywe/plugin-listpage
@beautywe/plugin-cache
提供了一個(gè)微信小程序端緩存策略,其底層由 super-cache 提供支持。
一般的請(qǐng)求數(shù)據(jù)的形式是,頁(yè)面加載的時(shí)候,從服務(wù)端獲取數(shù)據(jù),然后等待數(shù)據(jù)返回之后,進(jìn)行頁(yè)面渲染:
但這種模式,會(huì)受到服務(wù)端接口耗時(shí),網(wǎng)絡(luò)環(huán)境等因素影響到加載性能。
對(duì)于加載性能要求高的頁(yè)面(如首頁(yè)),一般的 Web 開發(fā)我們有很多解決方案(如服務(wù)端渲染,服務(wù)端緩存,SSR 等)。 但是也有一些環(huán)境不能使用這種技術(shù)(如微信小程序)。
Super Cache 提供了一個(gè)中間數(shù)據(jù)緩存的解決方案:
思路:
這種解決方案,舍棄了一點(diǎn)數(shù)據(jù)的實(shí)時(shí)性(非第一次請(qǐng)求,只能獲取上一次最新數(shù)據(jù)),大大提高了前端的加載性能。 適合的場(chǎng)景:
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');
}
}]
}));
假設(shè) API.fetch('xxx/name')
是請(qǐng)求服務(wù)器接口,返回?cái)?shù)據(jù):data_from_server
那么:
app.cache.get('name').then((value) => {
// value: 'data_from_server'
});
更多的配置,詳情看:@beautywe/plugin-cache
由 @beautywe/logger-plugin
提供的一個(gè)輕量的日志處理方案,它支持:
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
可通過(guò)配置來(lái)控制哪些 level 該打?。?/p>
page.use(logger({
level: 'warn',
}));
那么 warn
以上的 log (info
, debug
)就不會(huì)被打印,這種滿足于開發(fā)和生成環(huán)境對(duì) log 的不同需求。
level 等級(jí)如下:
Logger.LEVEL = {
error: 1,
warn: 2,
info: 3,
debug: 4,
};
更多的配置,詳情看:@beautywe/plugin-logger
@beautywe/core
和 @beautywe/plugin-...
給小程序提供了:
但是,還有很多的開發(fā)中實(shí)際還會(huì)遇到的痛點(diǎn),是上面兩個(gè)解決不到的。 如項(xiàng)目的組織、規(guī)范、工程化、配置、多環(huán)境等等
這些就是,「BeautyWe Framework」要解決的范疇。
它作為一套開箱即用的項(xiàng)目框架,提供了這些功能:
也是由于篇幅原因,挑幾個(gè)有趣的來(lái)講講,更多的可以看看官方文檔: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"
> }
回答幾個(gè)問(wèn)題之后,項(xiàng)目就生成了:
my-app
├── gulpfile.js
├── package.json
└── src
├── app.js
├── app.json
├── app.scss
├── assets
├── components
├── config
├── examples
├── libs
├── npm
├── pages
└── project.config.json
頁(yè)面
beautywe new page <path|name>
beautywe new page --subpkg <subPackageName> <path|name>
組件
beautywe new component <name>
插件
beautywe new plugin <name>
在 ./.templates
目錄中,存放著快速創(chuàng)建命令的創(chuàng)建模板:
$ tree .templates
?
.templates
├── component
│ ├── index.js
│ ├── index.json
│ ├── index.scss
│ └── index.wxml
├── page
│ ├── index.js
│ ├── index.json
│ ├── index.scss
│ └── index.wxml
└── plugin
└── index.js
可以修改里面的模板,來(lái)滿足項(xiàng)目級(jí)別的自定義模板創(chuàng)建。
我們都知道微信小程序是「單窗口」的交互平臺(tái),一個(gè)頁(yè)面對(duì)應(yīng)一個(gè)窗口。 而在業(yè)務(wù)開發(fā)中,往往會(huì)有諸如這種述求:
......
稍微不優(yōu)雅的實(shí)現(xiàn)可以是分別做成獨(dú)立的組件,然后每一個(gè)頁(yè)面都引入進(jìn)來(lái)。 這種做法,我們會(huì)有很多的重復(fù)代碼,并且每次新建頁(yè)面,都要引入一遍,后期維護(hù)也會(huì)很繁瑣。
而「全局窗口」的概念是:希望所有頁(yè)面之上有一塊地方,全局性的邏輯和交互,可以往里面擱。
這是一個(gè)自定義組件,源碼在 /src/components/global-view
每個(gè)頁(yè)面的 wxml 只需要在頂層包一層:
<global-view id="global-view">
...
</global-view>
需要全局實(shí)現(xiàn)的交互、樣式、組件,只需要維護(hù)這個(gè)組件就足夠了。
在 src/config/
目錄中,可以存放各種全局的配置文件,并且支持以 Node.js 的方式運(yùn)行。(得益于 Node.js Power 特性)。
如 src/config/logger.js
:
const env = process.env.RUN_ENV || 'dev';
?
const logger = Object.assign({
prefix: 'BeautyWe',
level: 'debug',
}, {
// 開發(fā)環(huán)境的配置
dev: {
level: 'debug',
},
// 測(cè)試環(huán)境的配置
test: {
level: 'info',
},
// 線上環(huán)境的配置
prod: {
level: 'warn',
},
}[env] || {});
?
module.exports.logger = logger;
然后我們可以這樣讀取到 config 內(nèi)容:
import { logger } from '/config/index';
?
// logger.level 會(huì)根據(jù)環(huán)境不同而不同。
Beautywe Framework 默認(rèn)會(huì)把 config 集成到 getApp()
的示例中:
getApp().config;
BeautyWe Framework 支持多環(huán)境開發(fā),其中預(yù)設(shè)了三套策略:
我們可以通過(guò)命令來(lái)運(yùn)行這三個(gè)構(gòu)建策略:
beautywe run dev
beautywe run test
beautywe run prod
Beautywe Framework 源碼默認(rèn)在兩方面使用了多環(huán)境:
gulpfile.js/env/...
)src/config/...
)構(gòu)建任務(wù)說(shuō)明devtestprodclean清除dist文件√√√copy復(fù)制資源文件√√√scripts編譯JS文件√√√sass編譯scss文件√√√npm編譯npm文件√√√nodejs-power編譯Node.js文件√√√watch監(jiān)聽文件修改√scripts-min壓縮JS文件√sass-min壓縮scss文件√npm-min壓縮npm文件√image-min壓縮圖片文件√clean-example清除示例頁(yè)面√
Beautywe Framework 的代碼有兩種運(yùn)行環(huán)境:
dist
文件夾的代碼。運(yùn)行過(guò)程
Node.js Power 本質(zhì)是一種靜態(tài)編譯的實(shí)現(xiàn)。 把某個(gè)文件在 Node.js 環(huán)境運(yùn)行的結(jié)果,輸出到微信小程序運(yùn)行環(huán)境中,以此來(lái)滿足特定的需求。
Node.js Power 會(huì)把項(xiàng)目中 src
目錄下類似 xxx.nodepower.js
命名的文件,以 Node.js 來(lái)運(yùn)行, 然后把運(yùn)行的結(jié)果,以「字面量對(duì)象」的形式寫到 dist
目錄下對(duì)應(yīng)的同名文件 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;
該文件,經(jīng)過(guò) Node.js Power 構(gòu)建之后:
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/
目錄中擴(kuò)展配置文件,都能被自動(dòng)打包。
Node.js Power 已經(jīng)被集成到多環(huán)境開發(fā)的 dev, test, prod 中去。
當(dāng)然,你可以手動(dòng)運(yùn)行這個(gè)構(gòu)建任務(wù):
$ gulp nodejs-power
BeautyWe Framework 實(shí)現(xiàn)支持 npm 的原理很簡(jiǎn)單,總結(jié)一句話:
使用 webpack 打包src/npm/index.js
,以 commonjs 格式輸出到dist/npm/index.js
這樣做的好處:
ll dist/npm/index.js
命令能快速看到項(xiàng)目中的 npm 包使占了多少容量。在 src/npm/index.js
文件中,進(jìn)行 export:
export { default as beautywe } from '@beautywe/core';
然后在其他文件 import:
import { beautywe } from './npm/index';
總的來(lái)說(shuō),BeautyWe 是一套微信小程序的開發(fā)范式。
core
和 plugins
擴(kuò)展原生,提供復(fù)雜邏輯的封裝和插拔式使用。
而 framework
則負(fù)責(zé)提供一整套針對(duì)于微信小程序的企業(yè)級(jí)項(xiàng)目解決方案,開箱即用。
其中還有更多的內(nèi)容,歡迎瀏覽官網(wǎng):beautywejs.com
Fundebug專注于JavaScript、微信小程序、微信小游戲、支付寶小程序、React Native、Node.js和Java線上應(yīng)用實(shí)時(shí)BUG監(jiān)控。 自從2016年雙十一正式上線,F(xiàn)undebug累計(jì)處理了10億+錯(cuò)誤事件,付費(fèi)客戶有陽(yáng)光保險(xiǎn)、核桃編程、荔枝FM、掌門1對(duì)1、微脈、青團(tuán)社等眾多品牌企業(yè)。歡迎大家免費(fèi)試用!