CML 作為真正讓一套代碼運行多端的框架,提供標準的 MVVM 模式,統(tǒng)一開發(fā)各類終端。
同時,擁有各端獨立的運行時框架(Runtime)、數據管理(Store)、組件庫(UI)、接口(API)。
此外,CML 在跨端能力加強、能力統(tǒng)一、表現一致等方面做了許多工作。
今天,為了讓大家的項目優(yōu)雅升級,快速接入,給你帶來一份豐盛的CML 遷移指南~
和微信小程序一樣,CML 包含一個描述整體程序的 app 和多個描述各自頁面的 pages。
.
├── components // 包含各個組件
├── pages // 包含各個頁面
├── app.js // 應用啟動入口
├── app.json // 全局配置
├── app.wxss // 全局樣式
└── project.config.json // 項目配置文件
.
├── dist // 各個端構建結果
│ ├── tt
│ ├── qq
│ ├── alipay
│ ├── baidu
│ ├── wx
│ ├── web
│ ├── weex
│ └── config.json // 跨端配置map映射表
├── node_modules // 第三方庫
├── mock // 模擬 接口數據 和 模板數據
├── src // 源代碼開發(fā)目錄
│ ├── app // 應用啟動入口
│ ├── assets // 靜態(tài)資源
│ ├── components // 包含組件
│ ├── pages // 包含頁面
│ ├── store //數據管理
│ └── router.config.json // 路由配置文件
├── chameleon.config.js // 項目配置文件
└── package.json // npm包配置文件
在小程序項目里面,分為:
可以在項目根目錄使用 project.config.json 文件對項目進行配置。
配置示例:
{
"miniprogramRoot": "./src",
"debugOptions": {}
}
小程序根目錄下的 app.json 文件用來對微信小程序進行全局配置,決定頁面文件的路徑、窗口表現、設置網絡超時時間、設置多 tab 等
配置示例:
{
"pages": ["pages/index/index", "pages/logs/index"],
"window": {
"navigationBarTitleText": "Demo"
},
"networkTimeout": {
"request": 10000,
"downloadFile": 10000
}
}
每一個小程序頁面也可以使用 .json 文件來對本頁面的窗口表現進行配置。
頁面的配置只能設置 app.json 中部分 window 配置項的內容,頁面中配置項會覆蓋 app.json 的 window 中相同的配置項。
配置示例:
{
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"navigationBarTitleText": "微信接口功能演示",
"backgroundColor": "#eeeeee",
"backgroundTextStyle": "light"
}
同樣,在 CML 項目里面,分為:
chameleon.config.js 為項目的配置文件,你可以定制化構建,比如是否帶 hash,是否 壓縮等等。
配置示例:
// 設置靜態(tài)資源的線上路徑
const publicPath = '//www.static.chameleon.com/static';
// 設置 API 請求前綴
const apiPrefix = 'https://api.chameleon.com';
// 合并配置
cml.config.merge({
wx: {
build: { apiPrefix },
},
alipay: {
build: { apiPrefix },
},
baidu: {
build: { apiPrefix },
},
web: {
dev: {
hot: true,
console: true,
},
build: {
publicPath: `${publicPath}/web`,
apiPrefix,
},
},
weex: {
build: {
publicPath: `${publicPath}/weex`,
apiPrefix,
},
},
});
CML 項目 app 目錄下的 app.cml 文件的 <script cml-type="json" /> 用來對 CML 應用 進行全局配置,具有跨端配置和差異化的能力
配置示例:
<script cml-type="json">
{
"base": {
"window": {
"navigationBarTitleText": "各個端共同title",
},
"permission": {
"scope.userLocation": {
"desc": "你的位置信息將用于小程序位置接口的效果展示"
}
}
},
"wx": {
"window": {
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "差異化 title",
"navigationBarTextStyle":"black"
}
},
"baidu": {
"window": {
"backgroundTextStyle": "light"
}
},
"alipay": {
"window": {
"defaultTitle": "Chameleon"
}
}
}
</script>
通過 usingComponents 配置 組件路徑 注冊引用的組件。
配置示例:
<script cml-type="json">
{
"base": {
"usingComponents": {
"navi": "/components/navi/navi",
"navi-npm": "cml-test-ui/navi/navi"
}
},
"wx": {
},
"alipay": {
},
"baidu": {
},
"web": {
},
"weex": {
}
}
</script>
app.json 配置項列表的 pages 字段用于指定小程序由哪些頁面組成,每一項都對應一個頁面的 路徑+文件名 信息。
數組的第一項代表小程序的初始頁面(首頁)。新增/減少頁面,需要對 pages 數組進行修改。
如果項目有 pages/index/index.wxml、pages/logs/logs.wxml 兩個頁面,則需要在 app.json 中寫
{
"pages": ["pages/index/index", "pages/logs/logs"]
}
src/router.config.json 是路由的配置文件,CML 內置了一套各端統(tǒng)一的路由配置方式。相應有 CML 路由配置映射如下:
{
"mode": "history",
"domain": "https://www.chameleon.com",
"routes":[
{
"url": "/cml/h5/index",
"path": "/pages/index/index",
"mock": "index.php"
},
{
"url": "/cml/h5/logs",
"path": "pages/logs/logs",
"mock": "logs.php"
}
]
}
文件名不需要寫文件后綴,CML 框架會自動去尋找對于位置的 .cml 文件進行處理。
依據統(tǒng)一資源索引 URI,自適應打開不同環(huán)境同一路由 PATH:
在小程序項目里面,App() 函數用來注冊一個小程序。接受一個 Object 參數,其指定小程序的生命周期回調等。
示例代碼
App({
onLaunch(options) {
// Do something initial when launch.
},
globalData: 'I am global data',
});
示例代碼
<script>
import store from '../store/index.js';
import routerConfig from '../router.config.json';
class App {
data = {
store,
routerConfig,
};
created(res) {}
}
export default new App();
</script>
細心的你會發(fā)現,
小程序中app.json app.js app.wxss和 src/app/app.cml的對應關系如下
小程序 app.js | CML 項目 src/app/app.cml |
---|---|
app.js | <script></script> |
app.wxss | <style></style> |
app.json | <script cml-type="json"></script> |
在小程序項目里面,Page(Object) 函數用來注冊一個頁面。接受一個 Object 類型參數,其指定頁面的初始數據、生命周期回調、事件處理函數等。
示例代碼:
// index.js
Page({
data: {
text: 'This is page data.',
},
changeText: function(e) {
// sent data change to view
this.setData({
text: 'CML',
});
},
});
示例代碼
<script>
class Index {
data = {
text: 'Chameleon',
};
methods = {
changeText: function(e) {
// sent data change to view
this.text = 'CML';
},
};
computed = {};
watch = {};
}
export default new Index();
</script>
在小程序項目里面, Component(Object) 構造器可用于定義組件,調用 Component 構造器時可以指定組件的屬性、數據、方法等。
示例代碼
Component({
properties: {
myProperty: {
// 屬性名
type: String, // 類型(必填)
value: '', // 屬性初始值(可選)
},
myProperty2: String, // 簡化的定義方式
},
data: {
text: '',
}, // 私有數據,可用于模板渲染
// 生命周期函數,可以為函數,或一個在methods段中定義的方法名
attached() {},
ready() {},
methods: {
onMyButtonTap() {
this.setData({
// 更新屬性和數據的方法與更新頁面數據的方法類似
text: 'wx',
});
},
},
});
示例代碼
<script>
class MyComponent {
props = {
myProperty: {
// 屬性名
type: String, // 類型(必填)
default: '', // 屬性初始值(可選)
},
myProperty2: String, // 簡化的定義方式
};
data = {
text: '',
}; // 私有數據,可用于模板渲染
beforeMount() {}
mounted() {}
methods = {
onMyButtonTap() {
this.text = 'cml';
},
};
computed = {};
watch = {};
}
export default new MyComponent();
</script>
統(tǒng)一各端應用生命周期的定義,是跨端框架的重要組成,也是遷移的必經之路。
可以在 App(Object)、Page(Object)、Component(Object) 傳入Object參數,其指定小程序的生命周期回調等
代碼示例
// index.js
Page({
onLoad(options) {
// Do some initialize when page load.
},
onReady() {
// Do something when page ready.
},
onShow() {
// Do something when page show.
},
onHide() {
// Do something when page hide.
},
onUnload() {
// Do something when page close.
},
onShareAppMessage() {
// return custom share data when user share.
},
});
在.cml 文件 <script /> 代碼塊返回的對象實例,其指定生命周期回調
示例代碼
<script>
class Index {
beforeCreate(query) {
// data數據掛載到this根節(jié)點上之前,以及methods所有方法掛載到實例根節(jié)點之前
// 注意:只用頁面的 beforeCreate鉤子 會返回頁面query
console.log('App beforeCreate: 打開當前頁面路徑中的參數是 ', query);
}
created() {
// data,methods里面的這些events掛載完成
console.log('App created');
}
beforeMount() {
// 開始掛載已經編譯完成的cml到對應的節(jié)點時
console.log('App beforeMount');
}
mounted() {
// cml模板編譯完成,且渲染到dom中完成,在整個生命周期中只執(zhí)行一次
console.log('App mounted');
}
beforeDestroy() {
// 實例銷毀前
console.log('App beforeDestroy');
}
destroyed() {
// 實例銷毀后
console.log('App destroyed');
}
}
export default new Index();
</script>
小程序 app.js中的生命周期 -> cml src/app/app.cml
小程序 | CML |
---|---|
onLaunch | beforeCreate |
onShow | mounted |
onHide | destroyed |
小程序 Page()中的生命周期 -> cml src/pages/mypage/mypage.cml
小程序 | CML |
---|---|
onLoad | beforeCreate |
onShow | mounted |
onUnload | destroyed |
onReady | 生命周期多態(tài) |
onHide | 生命周期多態(tài) |
onShareAppMessage | 生命周期多態(tài) |
小程序 Component()中的生命周期 -> cml src/components/mycomponent/mycomponent.cml
小程序 | CML |
---|---|
created | created |
attached | beforeMount |
ready | mounted |
detached | destroyed |
每個 CML 實例(App、Page、Component)在被創(chuàng)建時都要經過一系列的初始化過程 ————
例如,需要設置數據監(jiān)聽、編譯模板、將實例掛載到 CML 節(jié)點并在數據變化時更新 CML 節(jié)點等。同時在這個過程中也會運行一些叫做生命周期鉤子的函數,這給開發(fā)者在不同階段添加自己的代碼的機會。
CML 為 App、Page、Component 提供了一系列生命周期事件,保障應用有序執(zhí)行。
另外,如果你想使用某個端特定的生命周期,你可以從業(yè)務出發(fā)使用生命周期多態(tài)。
如今,雙向數據綁定&單向數據流 已深入開發(fā)者日常,MVMM 開發(fā)模式算是框架標配。
數據綁定、條件渲染、列表渲染是如何書寫的呢?
示例代碼
<!--wxml-->
<view class="scroller-wrap">
<!--數據綁定-->
<view>{{message}}</view>
<!--條件渲染-->
<view wx:if="{{view == 'WEBVIEW'}}">WEBVIEW</view>
<view wx:elif="{{view == 'APP'}}">APP</view>
<view wx:else="{{view == 'MINA'}}">MINA</view>
<!--列表渲染-->
<view wx:for="{{array}}" wx:for-index="index" wx:for-item="item">{{item}}</view>
</view>
// page.js
Page({
data: {
message: 'Hello MINA!',
view: 'MINA',
array: [1, 2, 3, 4, 5],
},
onLoad() {
this.setData({
message: 'wx',
});
},
});
<template>
<!--index.cml-->
<view class="scroller-wrap">
<!--數據綁定-->
<view>{{ message }}</view>
<!--條件渲染-->
<view c-if="{{view == 'WEBVIEW'}}">WEBVIEW</view>
<view c-else-if="{{view == 'APP'}}">APP</view>
<view c-else="{{view == 'MINA'}}">MINA</view>
<!--列表渲染-->
<view c-for="{{array}}" c-for-index="index" c-for-item="item">{{ item }}</view>
</view>
</template>
<script>
class Index {
data = {
message: 'Hello MINA!',
view: 'MINA',
array: [1, 2, 3, 4, 5],
};
beforeCreate() {
this.message = 'cml';
}
}
export default new Index();
</script>
CML 運行時框架 提供了跨端響應式數據綁定系統(tǒng),當做數據修改的時候,只需要在邏輯層修改數據,視圖層就會做相應的更新。
只需要將 view<-->model 交互部分邏輯,作簡單遷移,便可使它成為跨多端的數據響應系統(tǒng)。
CML 支持一些基礎的事件,保障各端效果(類型、綁定、事件對象)一致運行。
示例代碼
<!--wxml-->
<view id="tapTest" data-hi="WeChat" bindtap="tapName">Click me!</view>
// page.js
Page({
tapName(event) {
console.log(event);
},
});
<template>
<view id="tapTest" data-hi="WeChat" c-bind:tap="tapName">
<text>Click me!</text>
</view>
</template>
<script>
class Index {
methods = {
tapName(e) {
// 打印事件對象
console.log('事件對象:', e);
},
};
}
export default new Index();
</script>
同時,還支持自定義事件,用于父子組件之間的通信。
另外,如果你想要使用某個端特定的事件,CML 并不會限制你的自由發(fā)揮,你可以從業(yè)務出發(fā)使用多態(tài)組件 或者多態(tài)接口差異化實現功能。
各端描述 布局和外觀 的層疊樣式表(CSS)實現存在差異,包括不限于 布局、盒模型、定位、文本。
所以,CML 框架內置跨端一致性基礎樣式能力。
并且,定義了用于描述頁面的樣式規(guī)范CMSS(Chameleon Style Sheet)。
使用 @import 語句可以導入外聯樣式表,@import 后跟需要導入的外聯樣式表的相對路徑,用 ; 表示語句結束。
示例代碼:
/** common.wxss **/
.small-p {
padding: 5px;
}
/** app.wxss **/
@import 'common.wxss';
.middle-p {
padding: 15px;
}
詳細文檔
示例代碼:
/** common.css **/
.small-p {
padding: 5px;
}
<!-- app.cml -->
<style>
@import './common.css';
.middle-p {
padding: 15 cpx;
}
</style>
同時,為了統(tǒng)一多端尺寸單位,呈現效果一致,同時頁面響應式布局,CML 項目統(tǒng)一采用 cpx 作為尺寸單位,規(guī)定以屏幕 750px(占滿屏幕)視覺稿作為標準。
而且,各端樣式表擁有的能力不盡相同,是項目遷移的主要陣地之一。
另外,如果你想要使用某個端特定的樣式能力,CML 并不會限制你的自由發(fā)揮,你可以從業(yè)務出發(fā)使用樣式多態(tài)
注意:由于 CML 應用是跨多端 Web Native 小程序框架,如果需要跨 Native,必須使用flexbox進行樣式布局?。?!
CML 項目一切皆組件。組件是視圖的基本組成單元。
框架為開發(fā)者提供了一系列基礎組件,開發(fā)者可以通過組合這些基礎組件進行快速開發(fā)。
如:
<template>
<view>
<view>view 基礎組件</view>
<text>text 基礎組件</text>
</view>
</template>
同時,CML 支持簡潔的組件化編程。
開發(fā)者可以將頁面內的功能模塊抽象成自定義組件,以便在不同的頁面中重復使用。自定義組件在使用時與基礎組件非常相似。
代碼示例:
Component({
properties: {
// 這里定義了innerText屬性,屬性值可以在組件使用時指定
innerText: {
type: String,
value: 'default value',
},
},
data: {
// 這里是一些組件內部數據
someData: {},
},
methods: {
// 這里是一個自定義方法
customMethod() {},
},
});
示例代碼
<script>
class MyComponent {
props = {
// 這里定義了innerText屬性,屬性值可以在組件使用時指定
innerText: {
type: String,
value: 'default value',
},
};
data = {
// 這里是一些組件內部數據
someData: {},
};
methods = {
// 這里是一個自定義方法
customMethod() {},
};
computed = {};
watch = {};
}
export default new MyComponent();
</script>
使用已注冊的自定義組件前,首先要進行引用聲明。此時需要提供每個自定義組件的標簽名和對應的自定義組件文件路徑。
代碼示例:
在 page.json 中進行引用聲明
{
"usingComponents": {
"component-tag-name": "path/to/the/custom/component"
}
}
在 page.wxml 中使用
<view>
<!-- 以下是對一個自定義組件的引用 -->
<component-tag-name inner-text="Some text"></component-tag-name>
</view>
代碼示例:
在 page.cml中<script cml-type='json' />進行引用聲明
<script cml-type="json">
{
"base": {
"usingComponents": {
"component-tag-name": "path/to/the/custom/component"
}
}
}
</script>
在 page.cml中<template />使用
<template>
<view>
<!-- 以下是對一個自定義組件的引用 -->
<component-tag-name inner-text="Some text"></component-tag-name>
</view>
</template>
事件系統(tǒng)是組件間通信的主要方式之一。自定義組件可以觸發(fā)任意的事件,引用組件的頁面可以監(jiān)聽這些事件。
代碼示例:
<!-- 頁面 page.wxml -->
<view>
<my-component bindcustomevent="onMyEvent"></my-component>
</view>
// 頁面 page.js
Page({
methods: {
onMyEvent(e) {
console.log(e.detail); // 自定義組件觸發(fā)事件時提供的detail對象
},
},
});
<!-- 組件 my-component.wxml -->
<view>
<button bindtap="onTap">點擊這個按鈕將觸發(fā)“myevent”事件</button>
</view>
// 組件 my-component.js
Component({
methods: {
onTap() {
this.triggerEvent('customevent', {}); // 觸發(fā) 自定義組件事件
},
},
});
代碼示例:
<!-- 頁面 page.cml -->
<template>
<view>
<my-component c-bind:customevent="onMyEvent"></my-component>
</view>
</template>
<script>
class Index {
methods = {
// 這里是一個自定義方法
onMyEvent(e) {
console.log(e.detail); // 自定義組件觸發(fā)事件時提供的detail對象
},
};
}
export default new Index();
</script>
<script cml-type="json">
{
"base": {
"usingComponents": {
"my-component": "path/to/the/custom/component"
}
}
}
</script>
<!-- 頁面 path/to/the/custom/component.cml -->
<template>
<view>
<button c-bind:tap="onTap">點擊這個按鈕將觸發(fā)“myevent”事件</button>
</view>
</template>
<script>
class MyComponent {
methods = {
// 這里是一個自定義方法
onTap() {
this.$cmlEmit('customevent', {}); // 觸發(fā) 自定義組件事件
},
};
}
export default new MyComponent();
</script>
<script cml-type="json">
{
}
</script>
和小程序一樣,cml框架 提供了大量內置組件和擴展組件,抹平多端差異,便于開發(fā)者通過組合這些組件,創(chuàng)建出強大的應用程序。
擴展組件需要額外引入。如:
<script cml-type="json">
{
"base": {
"usingComponents": {
"c-dialog": "cml-ui/components/c-dialog/c-dialog"
}
}
}
</script>
在執(zhí)行 cml build 構建打包時,cml 框架 會按需打包引用的內置組件和擴展組件,為代碼瘦身。
內置組件和擴展組件都是支持跨多端的,對于一些沒有提供的某個端的組件,可以通過多態(tài)組件來實現。
如果希望使用小程序端的原生組件,那么可以在原生標簽前加上 origin-*,CML 框架會渲染原生組件。
注意:origin-* 只能在灰度區(qū)文件中使用??!
如在 map.wx.cml 文件中使用原生地圖組件 <map/>:
<!-- map.wx.cml -->
<template>
<origin-map
id="map"
longitude="113.324520"
latitude="23.099994"
controls="{{controls}}"
bindcontroltap="controltap"
style="width: 100%; height: 300px;"
></origin-map>
</template>
在小程序里面,可以通過微信原生 API,調起如獲取用戶信息,本地存儲,支付功能等。
示例代碼
try {
wx.setStorageSync('name', 'Hanks');
} catch (e) {
console.error(e);
}
同樣,在 CML 項目里面可以這樣調用:
示例代碼
import cml from 'chameleon-api';
cml.setStorage('name', 'Hanks').then(
(res) => {
console.log(res);
},
function(e) {
console.error(e);
},
);
CML 框架提供了豐富的多態(tài)接口,可以調起各端提供的原生能力,如系統(tǒng)信息、元素節(jié)點信息、動畫效果、本地存儲、網絡請求、地理位置等。請參考API文檔。
chameleon-api 提供的接口都是支持跨多端的,對于一些沒有提供的某個端的原生接口,可以通過多態(tài)接口來調用。
CML 作為一端代碼運行多端的框架,所有接口設計都考慮的是具備跨端要求的設計,沒有使用任何一端的接口設計規(guī)范,而是全新一套框架,所以不要”想當然“用微信小程序或者 vue 的接口來開發(fā) CML。
例如,如果你是微信小程序開發(fā)者,當你想使用 tabbar 功能時,可能會在app.json 里面配置,這是錯誤的,這是微信特有模式,只在微信里面有效。在 cml 中請使用c-tabbar來實現,這樣所有端都有效。
下面給出各端(vue、weex、小程序)遷移cml指南 以及 cml 導出組件到各端指南的具體遷移文檔:
更多建議: