1. 程式人生 > >BeautyWe.js 一套專注於微信小程式的開發正規化

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!

那麼它有什麼賣點呢?

  1. 專注於微信小程式環境,寫原汁原味的微信小程式程式碼。
  2. 由於只專注於微信小程式,它的原始碼也很簡單。
  3. 外掛化的程式設計方式,讓複雜邏輯更容易封裝。
  4. 再加上一些配套設施:
    1. 一些官方外掛。
    2. 一套開箱即用,包含了工程化、專案規範以及微信小程式環境獨特問題解決方案的框架。
    3. 一個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 的四種能力:

  1. Data 域 把外掛的 Data 域合併到原生 App 的 Data 域中,這一塊很容易理解。
  2. 原生鉤子函式 使原生鉤子函式(如 onShow, onLoad)可外掛化。讓原生App與多個外掛可以同時監聽同一個鉤子函式。如何工作的,下面會細說。
  3. 事件鉤子函式 使事件鉤子函式(與 view 層互動的鉤子函式),儘管在實現上有一些差異,但是實現原理跟「原生鉤子函式」一樣的。
  4. 自定義方法 讓外掛能夠給使用者提供 API。為了保證外掛提供的 API 足夠的優雅,支援當呼叫外掛 API 的時候(如 event 外掛 this.event.on(...)),API 方法內部仍然能通過 this 獲取到原生例項。

鉤子函式的外掛化

原生鉤子函式,事件鉤子函式我們統一稱為「鉤子函式」。

對於每一個鉤子函式,內部是維護一個以 Series Promise 方式執行的執行佇列。

onShow 為例,將會以這樣的形式執行:

native.onShow → pluginA.onShow → pluginB.onShow → ...

下面深入一下外掛化的原理

工作原理是這樣的:

  1. 經過 new BtApp(...) 包裝,所有的鉤子函式,都會有一個獨立的執行佇列,
  2. 首先會把原生的各個鉤子函式 push 到對應的佇列中。然後每 use 外掛的時候,都會分解外掛的鉤子函式,往對應的佇列 push
  3. Native App(原生)觸發某個鉤子的時候,BtApp 會以 Promise Series 的形式按循序執行對應佇列裡面的函式。
  4. 特殊的,onLaunchonLoad 的執行佇列中,會在佇列頂部插入一個初始化的任務(initialize),它會以同步的方式按循序執行 Initialize Queue 裡面的函式。這正是外掛生命週期函式中的 plugin.initialize

這種設計能提供以下功能:

  1. 可外掛化。 只需要往對應鉤子函式的事件佇列中插入任務。
  2. 支援非同步。 由於是以 Promise Series 方式執行的,其中一個任務返回一個 Promise,下一個任務會等待這個任務完成再開始。如果發生錯誤,會流轉到原生的 onError() 中。
  3. 解決了微信小程式 app.jsgetApp() === undefinded問題。 造成這個問題,本質是因為 App() 的時候,原生例項未建立。但是由於 Promise 在 event loop 中是一個微任務,被註冊在下一次迴圈。所以 Promise 執行的時候 App() 早已經完成了。

一些官方外掛

BeautyWe 官方提供了一系列的外掛:

  1. 增強儲存: Storage
  2. 資料列表:List Page
  3. 快取策略:Cache
  4. 日誌:Logger
  5. 事件釋出/訂閱:Event
  6. 狀態機:Status

它們的使用很簡單,哪裡需要插哪裡。 由於篇幅的原因,下面挑幾個比較有趣的來講講,更多的可以看看官方文件:BeautyWe

增強儲存 Storage

該功能由 @beautywe/plugin-storage 提供。

由於微信小程式原生的資料儲存生命週期跟小程式本身一致,即除使用者主動刪除或超過一定時間被自動清理,否則資料都一直可用。

所以該外掛在 wx.getStorage/setStorage 的基礎上,提供了兩種擴充套件能力:

  1. 過期控制
  2. 版本隔離

一些簡單的例子

安裝

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 提供了一套打包方案:

  1. 滿足常用「資料列表分頁」的業務場景
  2. 支援分頁
  3. 支援多個數據列表
  4. 自動捕捉下拉過載:onPullDownRefresh
  5. 自動捕捉上拉載入:onReachBottom
  6. 自帶請求鎖,防止帕金森氏手抖使用者
  7. 簡單優雅的 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 提供支援。

特性
  1. 提供一套「服務端介面耗時慢,但載入效能要求高」場景的解決方案
  2. 滿足最基本的快取需求,讀取(get)和儲存(set)
  3. 支援針對快取進行邏輯代理
  4. 靈活可配置的資料儲存方式
How it work

一般的請求資料的形式是,頁面載入的時候,從服務端獲取資料,然後等待資料返回之後,進行頁面渲染:

但這種模式,會受到服務端介面耗時,網路環境等因素影響到載入效能。

對於載入效能要求高的頁面(如首頁),一般的 Web 開發我們有很多解決方案(如服務端渲染,服務端快取,SSR 等)。 但是也有一些環境不能使用這種技術(如微信小程式)。

Super Cache 提供了一箇中間資料快取的解決方案:

思路:

  1. 當你需要獲取一個數據的時候,如果有快取,先把舊的資料給你。
  2. 然後再從服務端獲取新的資料,重新整理快取。
  3. 如果一開始沒有快取,則請求服務端資料,再把資料返回。
  4. 下一次請求快取,從第一步開始。

這種解決方案,捨棄了一點資料的實時性(非第一次請求,只能獲取上一次最新資料),大大提高了前端的載入效能。 適合的場景:

  1. 資料實時性要求不高。
  2. 服務端介面耗時長。
使用
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 提供的一個輕量的日誌處理方案,它支援:

  1. 可控的 log level
  2. 自定義字首
  3. 日誌統一處理
使用
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-... 給小程式提供了:

  1. 開放原生,支援外掛化 —— by core
  2. 各種外掛 —— 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
建立頁面、元件、外掛

頁面

  1. 主包頁面:beautywe new page <path|name>
  2. 分包頁面:beautywe new page --subpkg <subPackageName> <path|name>

元件

  1. beautywe new component <name>

外掛

  1. 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

可以修改裡面的模板,來滿足專案級別的自定義模板建立。

全域性視窗

我們都知道微信小程式是「單視窗」的互動平臺,一個頁面對應一個視窗。 而在業務開發中,往往會有諸如這種述求:

  1. 自定義的 toast 樣式
  2. 頁面底部 copyright
  3. 全域性的 loading 樣式
  4. 全域性的懸浮控制元件

......

稍微不優雅的實現可以是分別做成獨立的元件,然後每一個頁面都引入進來。 這種做法,我們會有很多的重複程式碼,並且每次新建頁面,都要引入一遍,後期維護也會很繁瑣。

而「全域性視窗」的概念是:希望所有頁面之上有一塊地方,全域性性的邏輯和互動,可以往裡面擱。

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/...
構建任務的差異
構建任務說明devtestprod
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 的程式碼有兩種執行環境:

  1. Node.js 執行環境,如構建任務等。
  2. 微信小程式執行環境,如打包到 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

這樣做的好處:

  1. 實現簡單。
  2. 讓 npm 包能集中管理,每次引入依賴,都好好的想一下,避免氾濫(尤其在多人開發中)。
  3. 使用 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 是一套微信小程式的開發正規化。

coreplugins 擴充套件原生,提供複雜邏輯的封裝和插拔式使用。

framework 則負責提供一整套針對於微信小程式的企業級專案解決方案,開箱即用。

其中還有更多的內容,歡迎瀏覽官網:beautywejs.com

關於Fundebug

Fundebug專注於JavaScript、微信小程式、微信小遊戲、支付寶小程式、React Native、Node.js和Java線上應用實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了10億+錯誤事件,付費客戶有陽光保險、核桃程式設計、荔枝FM、掌門1對1、微脈、青團社等眾多品牌企業。歡迎