工程化是使用軟件工程的技術(shù)和方法對(duì)項(xiàng)目的開發(fā)、上線和維護(hù)進(jìn)行管理。CML 的工程化包含如下幾個(gè)大方面:
如果需要 Mock 多個(gè)域名的 API 請(qǐng)參見 API 多域名 Mock。
1、 使用內(nèi)置網(wǎng)絡(luò)請(qǐng)求接口發(fā)起網(wǎng)絡(luò)請(qǐng)求。例如:
import cml from 'chameleon-api';
cml
.get({
url: '/api/getdriver',
})
.then(
(res) => {
cml.showToast({
message: JSON.stringify(res),
duration: 2000,
});
},
(err) => {
cml.showToast({
message: JSON.stringify(err),
duration: 2000,
});
},
);
調(diào)用方法的參數(shù) url 中只需要寫 api 的路徑。那么本地 dev 開發(fā)模式如何 mock 這個(gè) api 請(qǐng)求以及 build 線上模式如何請(qǐng)求線上地址,就需要在配置文件中配置 apiPrefix。
2、配置 apiPrefix dev 開發(fā)模式和 build 模式配置的 apiPrefix 會(huì)拼接到網(wǎng)絡(luò)請(qǐng)求的 url 前,dev 模式不配置時(shí),默認(rèn)為當(dāng)前啟動(dòng) Web 服務(wù)的 ip+端口。上面的例子中如果本地 ip 為 198.168.1.1 啟動(dòng)端口為 8000。dev 模式發(fā)起的網(wǎng)絡(luò)請(qǐng)求為 198.168.1.1:8000/api/getdriver, build 模式發(fā)起的網(wǎng)絡(luò)請(qǐng)求為 http://api.chameleon.com/api/getdriver。
// 設(shè)置 API 請(qǐng)求前綴
const apiPrefix = 'http://api.chameleon.com';
cml.config.merge({
wx: {
dev: {},
build: {
apiPrefix,
},
},
});
3、 配置本地 Mock 數(shù)據(jù)
前兩步操作實(shí)現(xiàn)了網(wǎng)絡(luò)請(qǐng)求 dev 模式請(qǐng)求本地,build 模式請(qǐng)求線上,這一步就講解如何 mock 本地請(qǐng)求數(shù)據(jù)。
在/mock/api/文件夾下創(chuàng)建 Mock 數(shù)據(jù)的 js 文件。文件內(nèi)容格式如下:
module.exports = [
{
method: 'get',
path: '/api/getdriver',
controller: function(req, res, next) {
console.log('/api/driver/getList');
res.json({
total: 100,
driverList: [],
});
},
},
];
啟動(dòng) dev 模式后,通過(guò) ip+端口+path 即可訪問(wèn)配置的 api 請(qǐng)求。結(jié)合上面講到的網(wǎng)絡(luò)請(qǐng)求方法,即可實(shí)現(xiàn)本地的 API 數(shù)據(jù) Mock。
擴(kuò)展 如何在本地 dev 模式請(qǐng)求線上數(shù)據(jù)?
可以在 mock 文件的 controller 中請(qǐng)求對(duì)應(yīng)的線上數(shù)據(jù)
/mock/template/文件夾下存放的 php 文件是下發(fā)的模板數(shù)據(jù),php 文件內(nèi)將下發(fā)的數(shù)據(jù)賦值給$CML 對(duì)象,例如:
<?php
$chameleon = array(
"errno" => 0,
"errmsg" => "",
"pageData" => array(
"pageInfo" => array(
"title" => "chameleon",
"content" => "chameleon跨端"
)
)
);
?>
在模板中通過(guò)變量pageData,errno,errmsg接收。
<script>
var pageData = {json_encode($pageData)}
var errno = {json_encode($errno)}
var errmsg = {json_encode($errmsg)}
</script>
同時(shí)還模擬了與模板下發(fā)的 pageData 相同的 ajax 請(qǐng)求,只需在當(dāng)前訪問(wèn)頁(yè)面的 url 上添加 puredata=1 參數(shù)。
{
errno: 0,
errmsg: '',
pageData: {
pageInfo: {
title: 'chameleon',
content: 'chameleon跨端'
}
}
}
在數(shù)據(jù) Mock 一節(jié)講述了如何進(jìn)行 API 數(shù)據(jù)的 mock,但是只局限于所有 api 請(qǐng)求都是相同域名的情況,工作中可能出現(xiàn)一個(gè)項(xiàng)目請(qǐng)求多個(gè)域名的 api 接口,本節(jié)將講解如和進(jìn)行多域名的 mock。
domain 對(duì)象配置多域名的信息。 domain, Object 類型。 配置在 base 對(duì)象中,可以作為所有平臺(tái)的公共配置,dev 模式中配置的localhost會(huì)替換成當(dāng)前 dev 模式啟動(dòng)的 web 服務(wù) ip+端口。
例如:
cml.config.merge({
base: {
dev: {
domain: {
domain1: "localhost",
domain2: "localhost"
},
},
build: {
domain: {
domain1: "http://api.cml.com",
domain2: "http://api2.cml.com"
},
}
},
})
chameleon-api的網(wǎng)絡(luò)請(qǐng)求get、post、request方法中添加 domain 參數(shù)。 chameleon.config.js中添加的domain對(duì)象配置,在項(xiàng)目中可以通過(guò)process.env.domain變量訪問(wèn)。
例如:
import cml from 'chameleon-api';
cml
.get({
domain: process.env.domain.domain1,
url: '/api/getMessage',
})
.then(
(res) => {
cml.showToast({
message: JSON.stringify(res),
duration: 2000,
});
},
(err) => {
cml.showToast({
message: JSON.stringify(err),
duration: 2000,
});
},
);
前兩步操作實(shí)現(xiàn)了網(wǎng)絡(luò)請(qǐng)求 dev 模式請(qǐng)求本地,build 模式請(qǐng)求線上,這一步就講解如何 mock 本地多域名的請(qǐng)求數(shù)據(jù)。
在/mock/api/文件夾下創(chuàng)建 Mock 數(shù)據(jù)的 js 文件。文件內(nèi)容格式如下:
module.exports = [
{
domainKey: 'domain1',
request: [
{
method: ['get', 'post'],
path: '/api/getMessage',
controller: function(req, res, next) {
res.json({
total: 0,
message: [
{
name: 'HelloCML domain1',
},
],
});
},
},
],
},
{
domainKey: 'domain2',
request: [
{
method: ['get', 'post'],
path: '/api/getMessage',
controller: function(req, res, next) {
res.json({
total: 0,
message: [
{
name: 'domain2!',
},
],
});
},
},
],
},
];
模板中引用靜態(tài)資源,不能直接將資源的路徑寫在模板中,而是要通過(guò) js 中 require 該靜態(tài)資源得到變量,在模板中引用該變量。 該路徑會(huì)根據(jù)項(xiàng)目配置的 publicPath自動(dòng)替換成正確路徑。利用該功能可以實(shí)現(xiàn) 靜態(tài)資源 開發(fā)路徑和部署路徑之間的 分離,開發(fā)者只需要寫相對(duì)路徑,線上可以通過(guò)設(shè)置publicPath指定任意路徑。
<template>
<!--
錯(cuò)誤形式
<image src="./images/logo.png" />
-->
<!-- 正確形式 -->
<image src="{{imgPath}}" />
</template>
<script>
class Index {
data = {
imgPath: require('./images/logo.png'),
};
}
export default new Index();
</script>
支持在引用圖片 url 后面添加inline參數(shù),以指定圖片的 base64 格式,例如:
<script>
class Index {
data = {
imgPath: require('./images/logo.png?__inline'),
};
}
export default new Index();
</script>
調(diào)試線上編譯處理過(guò)的非可讀性代碼,可以使用本功能
通過(guò)簡(jiǎn)單的 CML 配置將線上文件代理到線下的開發(fā)環(huán)境,這樣就可以通過(guò)修改線下的源碼 debug 線上頁(yè)面了,使用方法如下:
chameleon.config.js 中開啟代理模式:
{
...
proxy: {
enable: true,
}
...
}
執(zhí)行以下命令
cml dev
根據(jù)調(diào)試面板打印的信息給手機(jī)安裝證書
根據(jù)上圖提示將手機(jī)代理到相應(yīng)的端口
完成以上步驟就可以進(jìn)行代理開發(fā)了。
默認(rèn)代理了 Weex 和 Web 端的 js 和 css 文件,如需代理更多文件,可以 添加 mapremote 配置,方法如下:
{
...
proxy: {
enable: true,
mapremote: [{
from: 'https://a.b.com/weex/aaa_(.+).js',
to: 'http://localhost:8000/weex/aaa.js'
},{
from: 'https://a.b.com/weex/bbb_(.+).js',
to: 'http://localhost:8000/weex/bbb.js'
}]
}
...
}
一個(gè) cmlUrl 能在多端運(yùn)行,在普通瀏覽器/webview 運(yùn)行 Web 端,小程序運(yùn)行小程序端,Native 渲染(weex)則拉取對(duì)應(yīng)的 JS Bundle 并展現(xiàn),完整地址如下,使用場(chǎng)景包含:
https://h5地址? cml_addr=jsbundle地址& path=路由path(通用字段)& envVersion=要打開的小程序版本(通用字段)& weixin_appid=123456& weixin_path=微信小程序路由path& weixin_envVersion=要打開的微信小程序版本& baidu_appid=123456& baidu_path=百度小程序路由path& baidu_envVersion=要打開的百度小程序版本& alipay_appid=123456& alipay_path=支付寶小程序路由path
參數(shù) | 作用 | 說(shuō)明 |
---|---|---|
h5地址 | H5端的地址或者用于提示bundle出錯(cuò)的h5地址 | 如果你沒有h5地址,可以選擇將h5地址寫為jsbundle地址(后面的cml_addr=jsbundle地址依然需要)。 |
cml_addr | 描述weex/rn js bundle地址 | 內(nèi)部非使用sdk開發(fā)者暫時(shí)使用cml_addr字段 |
path | 描述應(yīng)用里面的頁(yè)面路由, 即路由里面的 path 值 | 若未填寫weixin_path, baidu_path, alipay_path時(shí), 統(tǒng)一使用該字段 |
envVersion | 要打開的小程序版本 | 有效值 develop(開發(fā)版),trial(體驗(yàn)版),release(正式版) ,僅在當(dāng)前小程序?yàn)殚_發(fā)版或體驗(yàn)版時(shí)此參數(shù)有效(僅支持微信小程序和支付寶小程序) |
weixin_appid | 描述微信小程序的app id | 微信小程序跳轉(zhuǎn)需要 appid |
weixin_path | 描述應(yīng)用里面的頁(yè)面路由(目標(biāo)微信小程序?yàn)榉?CML 項(xiàng)目時(shí)可用) | |
weixin_envVersion | 要打開的小程序版本 | 有效值 develop(開發(fā)版),trial(體驗(yàn)版),release(正式版) ,僅在當(dāng)前小程序?yàn)殚_發(fā)版或體驗(yàn)版時(shí)此參數(shù)有效(該字段僅對(duì)微信小程序跳轉(zhuǎn)生效) |
baidu_appid | 描述百度小程序的appKey | 百度小程序跳轉(zhuǎn)需要 appKey |
baidu_path | 描述應(yīng)用里面的頁(yè)面路由(目標(biāo)百度小程序?yàn)榉?CML 項(xiàng)目時(shí)可用) | |
alipay_appid | 描述支付寶小程序的app id | 支付寶小程序跳轉(zhuǎn)需要 appid |
alipay_path | 描述應(yīng)用里面的頁(yè)面路由(目標(biāo)支付寶小程序?yàn)榉?CML 項(xiàng)目時(shí)可用) | |
alipay_envVersion | 要打開的小程序版本 | 有效值 develop(開發(fā)版),trial(體驗(yàn)版),release(正式版) ,僅在當(dāng)前小程序?yàn)殚_發(fā)版或體驗(yàn)版時(shí)此參數(shù)有效(該字段僅對(duì)支付寶小程序跳轉(zhuǎn)生效) |
按照框架定義,有相應(yīng)的目錄規(guī)范,文件規(guī)范,文件內(nèi)容規(guī)范等,按照規(guī)范編寫代碼,可以最大程度的減少開發(fā)、調(diào)試時(shí)間。
另,框架提供了校驗(yàn)工具,讓開發(fā)者可以提前發(fā)現(xiàn)不符合規(guī)范的問(wèn)題,提高效率。
接口是一系列方法的聲明,是一些方法特征的集合,一個(gè)接口只有方法的特征沒有方法的實(shí)現(xiàn),因此這些方法可以在不同的地方被不同的類實(shí)現(xiàn),而這些實(shí)現(xiàn)可以具有不同的行為(功能)進(jìn)行溝通。
通過(guò)配置決定是否開啟接口的校驗(yàn)。 Object、Array、Nullable這三個(gè)類型默認(rèn)是不支持的,因?yàn)槲覀兘ㄗh更精確的校驗(yàn),可以通過(guò)配置文件開啟這三個(gè)類型。 具體參見多態(tài)校驗(yàn)控制的配置
注意:建議定義類型的時(shí)候取值為 Number String Boolean Null Undefined(Void) Object Array Function Date RegExp
目前 CML 接口定義支持簡(jiǎn)單類型和復(fù)合類型。
其中簡(jiǎn)單類型包括以下類型:
復(fù)合類型包括以下類型:
接口的使用分兩個(gè)過(guò)程:
范式
interface [接口名稱] {
// 接口中的屬性
[屬性名稱]: [類型],
// 接口中的方法
[方法名稱]([傳入?yún)?shù)1名稱]: [傳入?yún)?shù)1類型], [傳入?yún)?shù)2名稱]: [傳入?yún)?shù)2類型], ...): [返回類型]
}
舉例
// 一個(gè)名為interface1的接口
interface interface1 {
// foo1: 傳入分別為string和number的兩個(gè)數(shù)據(jù),返回值類型為string值
foo1(a: string, b: number): string;
// foo2: 傳入分別為string和Callback(上文定義)的兩個(gè)數(shù)據(jù),返回值類型為bool值
foo2(c: string, d: Callback): Boolean;
}
范式
class [類名稱] implaments [接口名稱] {
// 實(shí)現(xiàn)接口中的屬性
[屬性名稱]: [類型]
// 實(shí)現(xiàn)接口中的方法
[方法名稱]([傳入?yún)?shù)1名稱], [傳入?yún)?shù)2名稱], ...){
return [返回值];
}
}
舉例
// 實(shí)現(xiàn)一個(gè)名稱為Clazz,實(shí)現(xiàn)上文定義的interface1接口
class Clazz implaments interface1 {
// 實(shí)現(xiàn)interface1定義的foo1方法,輸入值和輸出值要滿足定義
foo1(a, b) {
return 'hello ' + a + ' : ' + (b + 1);
}
// 實(shí)現(xiàn)interface1定義的foo2方法,輸入值和輸出值要滿足定義
foo2(c, d) {
return 'balabala...';
}
}
type [類型名稱] = [類型定義]
不同的復(fù)合類型,類型定義也不相同,下面會(huì)對(duì)三種復(fù)合類型做詳細(xì)說(shuō)明。
范式
type [Function類型名稱] = ([傳入?yún)?shù)1名稱]: [傳入?yún)?shù)1類型], [傳入?yún)?shù)2名稱]: [傳入?yún)?shù)2類型], ...) => [返回類型]
舉例
// 定義一個(gè)傳參分別為number,string,bool類型的三個(gè)參數(shù),返回值為number的函數(shù)類型
type Callback = (a: number, b: string, c: bool) => number;
范式
type [Object類型名稱] = {
[屬性名稱1]: [類型1],
[屬性名稱2]: [類型2]
}
舉例
// 定義含有a,b,c三個(gè)屬性的復(fù)合類型
type Scheme = {
a: string,
b: bool,
c: number
}
范式
type [Array類型名稱] = [
[類型1]
]
舉例
// 定義名稱為arrayType1的數(shù)組類型,數(shù)組元素為number類型
type arrayType1 = [
number
]
interface EntryInterface {
handleDate(arr: Array, o: Object): Array;
}
class Method implements EntryInterface {
let arr = [1,2,3,'str'];
let obj = {address:'China'}
handleDate(arr,obj){
return ['this is str',{name:"jhon"}];
}
}
此時(shí)校驗(yàn)就只會(huì)校驗(yàn)入?yún)⒒蛘叻祷刂档脭?shù)據(jù)類型是否是 Array 或者 Object ,而不會(huì)深入校驗(yàn)數(shù)組或者對(duì)象中的元素;
Function、Object、Array 三種復(fù)合類型可以互相嵌套:
// 定義一個(gè)傳參分別為number,string,bool類型的三個(gè)參數(shù),返回值為number的函數(shù)類型
type Callback = (a: number, b: string, c: bool) => number;
// 定義名稱為arrayType1的數(shù)組類型,數(shù)組元素為number類型
type arrayType1 = [
number
]
// 定義名稱為Scheme的,含有Array類型和Function類型屬性的Object類型
type Scheme = {
a: arrayType1,
b: Callback,
}
// 定義名稱為Plan,含有Scheme類型和Callback的屬性的Object類型
type Plan = {
a: string,
b: Scheme,
c: Callback
}
// 定義名稱為arrayType1類型,元素為Plan類型
type arrayType1 = [
Plan
]
如果函數(shù)參數(shù)或者返回值是 Date 數(shù)據(jù)類型,那么可以按照下面的方式進(jìn)行定義;
interface EntryInterface {
handleDate(d: Date): Date;
}
class Method implements EntryInterface {
handleDate(d) {
return new Date();
}
}
如果函數(shù)參數(shù)或者返回值是 RegExp 數(shù)據(jù)類型,那么可以按照下面的方式進(jìn)行定義;
interface EntryInterface {
handleDate(d: RegExp): RegExp;
}
class Method implements EntryInterface {
handleDate(r) {
return new RegExp();
}
}
注意如果要定義 Nullable(?Number)這樣的參數(shù),那么該參數(shù)的占位符是必須的
interface EntryInterface {
acceptsMaybeNumber(a: ?Number, b: String, c: Boolean): Undefined;
}
class Method implements EntryInterface {
acceptsMaybeNumber(a, b, c) {}
}
acceptsMaybeNumber(42, 'str', true); // Works!
acceptsMaybeNumber(undefined, 'str', true); // Works!
acceptsMaybeNumber(null, 'str', true); // Works!
acceptsMaybeNumber('42', 'str', true); // Error!
//**注意如果要定義Nullable(?Number)這樣的參數(shù),那么該參數(shù)的占位符是必須的,在校驗(yàn)入?yún)⒌臅r(shí)候,會(huì)按照interface中定義的順序,有序的校驗(yàn)傳入的參數(shù)是否和interface中定義的數(shù)據(jù)參數(shù)類型一直,(?Number)這種定義的校驗(yàn)其實(shí)只是說(shuō)明這個(gè)參數(shù)可以是 null undefined number類型的數(shù)據(jù),但是是必須傳遞的**
acceptsMaybeNumber('str', true); // Error!
CML 的代碼最終會(huì)運(yùn)行在多端框架中,每一個(gè)端都會(huì)有一些特有的全局變量,chameleon 內(nèi)部維護(hù)了一個(gè)各端全局變量的散列表如下表所示。
全局變量校驗(yàn)校驗(yàn)的是某一端運(yùn)行的代碼中存在其他端的全局變量并且不是當(dāng)前端的全局變量。例如非微信小程序端的 代碼中不能出現(xiàn)wx全局變量,非百度小程序的代碼中不能出現(xiàn)swan全局變量。
可以通過(guò)項(xiàng)目配置決定是否開啟全局變量校驗(yàn),還可以配置哪些文件是白名單文件進(jìn)行校驗(yàn)。
端 | 全局變量 |
---|---|
微信小程序 | ["wx"] |
百度小程序 | ["swan"] |
weex | ["weex"] |
支付寶小程序 | ["my"] |
web | ["postMessage","blur","focus","close","frames","self","window","parent","opener","top","length","closed","location","document","origin","name","history","locationbar","menubar","personalbar","scrollbars","statusbar","toolbar","status","frameElement","navigator","customElements","external","screen","innerWidth","innerHeight","scrollX","pageXOffset","scrollY","pageYOffset","screenX","screenY","outerWidth","outerHeight","devicePixelRatio","clientInformation","screenLeft","screenTop","defaultStatus","defaultstatus","styleMedia","onanimationend","onanimationiteration","onanimationstart","onsearch","ontransitionend","onwebkitanimationend","onwebkitanimationiteration","onwebkitanimationstart","onwebkittransitionend","isSecureContext","onabort","onblur","oncancel","oncanplay","oncanplaythrough","onchange","onclick","onclose","oncontextmenu","oncuechange","ondblclick","ondrag","ondragend","ondragenter","ondragleave","ondragover","ondragstart","ondrop","ondurationchange","onemptied","onended","onerror","onfocus","oninput","oninvalid","onkeydown","onkeypress","onkeyup","onload","onloadeddata","onloadedmetadata","onloadstart","onmousedown","onmouseenter","onmouseleave","onmousemove","onmouseout","onmouseover","onmouseup","onmousewheel","onpause","onplay","onplaying","onprogress","onratechange","onreset","onresize","onscroll","onseeked","onseeking","onselect","onstalled","onsubmit","onsuspend","ontimeupdate","ontoggle","onvolumechange","onwaiting","onwheel","onauxclick","ongotpointercapture","onlostpointercapture","onpointerdown","onpointermove","onpointerup","onpointercancel","onpointerover","onpointerout","onpointerenter","onpointerleave","onafterprint","onbeforeprint","onbeforeunload","onhashchange","onlanguagechange","onmessage","onmessageerror","onoffline","ononline","onpagehide","onpageshow","onpopstate","onrejectionhandled","onstorage","onunhandledrejection","onunload","performance","stop","open","alert","confirm","prompt","print","requestAnimationFrame","cancelAnimationFrame","requestIdleCallback","cancelIdleCallback","captureEvents","releaseEvents","getComputedStyle","matchMedia","moveTo","moveBy","resizeTo","resizeBy","getSelection","find","webkitRequestAnimationFrame","webkitCancelAnimationFrame","fetch","btoa","atob","createImageBitmap","scroll","scrollTo","scrollBy","onappinstalled","onbeforeinstallprompt","crypto","ondevicemotion","ondeviceorientation","ondeviceorientationabsolute","indexedDB","webkitStorageInfo","sessionStorage","localStorage","chrome","visualViewport","speechSynthesis","webkitRequestFileSystem","webkitResolveLocalFileSystemURL","openDatabase","applicationCache","caches","whichAnimationEvent","animationendEvent","infinity","SETTING","AppView","ExtensionOptions","ExtensionView","WebView","iconPath","_app","_ZOOM_","Feed","md5","$","jQuery","Search","windmill","Lethargy","alertTimeOut","supportApps","lethargyX","lethargyY","iView","onModuleResLoaded","iEditDelete","infinityDrag","i","array","TEMPORARY","PERSISTENT","addEventListener","removeEventListener","dispatchEvent"] |
根據(jù)chameleon目錄結(jié)構(gòu),確定核心文件的位置,保證 CML 項(xiàng)目能夠正常運(yùn)行。
├── chameleon.config.js // 項(xiàng)目的配置文件
├── dist // 打包產(chǎn)出目錄
├── mock // 模擬數(shù)據(jù)目錄
├── node_modules // npm包依賴
├── package.json
└── src // 項(xiàng)目源代碼
├── app // app入口
├── components // 組件文件夾
├── pages // 頁(yè)面文件夾
├── router.config.json // 路由配置文件
└── store // 全局狀態(tài)管理
會(huì)對(duì)以下核心文件進(jìn)行檢查:
chameleon.config.js
src/app/app.cml
src/router.config.json
CML 文件規(guī)范校驗(yàn)包括校驗(yàn)以下三個(gè)規(guī)范:
以 cml 后綴結(jié)尾的文件分兩種情況:
多端實(shí)現(xiàn)完全一致組件命名格式
[component name].cml
組件所有邏輯實(shí)現(xiàn)在同一文件中
舉例:
demo.cml
多端實(shí)現(xiàn)不一致組件命名格式
[component name].[weex|wx|alipay|baidu|web].cml
組件文件名按照適配端命名,需要同一目錄下的 interface 文件組合使用
[component name].interface
舉例:
demo.interface
demo.weex.cml
demo.wx.cml
demo.alipay.cml
demo.baidu.cml
demo.web.cml
cml 文件中可能包括以下幾個(gè)字段標(biāo)簽
舉例:
// demo.cml code
<template>
<view id="banner"> </view>
</template>
<script>
class Index {}
export default new Index();
</script>
<style scoped>
#banner {
background-color: #ff0000;
}
</style>
<script cml-type="json">
{
"base":{
"usingComponents": {
}
},
"wx": {
"navigationBarTitleText": "index",
"backgroundTextStyle": "dark",
"backgroundColor": "#E2E2E2"
},
"alipay": {
"defaultTitle": "index",
"pullRefresh": false,
"allowsBounceVertical": "YES",
"titleBarColor": "#ffffff"
},
"baidu": {
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "white",
"navigationBarTitleText": "index",
"backgroundColor": "#ffffff",
"backgroundTextStyle": "dark",
"enablePullDownRefresh": false,
"onReachBottomDistance": 50
},
"web": {
},
"weex": {
}
}
</script>
.interface 后綴文件用于定義多態(tài)組件的接口。
接口是一系列方法的聲明,是一些方法特征的集合,一個(gè)接口只有方法的特征沒有方法的實(shí)現(xiàn),因此這些方法可以在不同的地方被不同的類實(shí)現(xiàn),而這些實(shí)現(xiàn)可以具有不同的行為(功能)進(jìn)行溝通。
類型說(shuō)明
注意:建議定義類型的時(shí)候取值為 Number String Boolean Null Undefined(Void) Object Array Function Date RegExp
目前 CML 接口定義支持簡(jiǎn)單類型和復(fù)合類型。
其中簡(jiǎn)單類型包括以下類型:
復(fù)合類型包括以下類型:
接口語(yǔ)法
接口的使用分兩個(gè)過(guò)程:
接口定義
范式:
interface [接口名稱] {
// 接口中的屬性
[屬性名稱]: [類型],
// 接口中的方法
[方法名稱]([傳入?yún)?shù)1名稱]: [傳入?yún)?shù)1類型], [傳入?yún)?shù)2名稱]: [傳入?yún)?shù)2類型], ...): [返回類型]
}
舉例:
// 一個(gè)名為interface1的接口
interface interface1 {
// foo1: 傳入分別為string和number的兩個(gè)數(shù)據(jù),返回值類型為string值
foo1(a: string, b: number): string;
// foo2: 傳入分別為string和Callback(上文定義)的兩個(gè)數(shù)據(jù),返回值類型為bool值
foo2(c: string, d: Callback): Boolean;
}
實(shí)現(xiàn)接口(定義類)
范式:
class [類名稱] implaments [接口名稱] {
// 實(shí)現(xiàn)接口中的屬性
[屬性名稱]: [類型]
// 實(shí)現(xiàn)接口中的方法
[方法名稱]([傳入?yún)?shù)1名稱], [傳入?yún)?shù)2名稱], ...){
return [返回值];
}
}
舉例:
// 實(shí)現(xiàn)一個(gè)名稱為Clazz,實(shí)現(xiàn)上文定義的interface1接口
class Clazz implaments interface1 {
// 實(shí)現(xiàn)interface1定義的foo1方法,輸入值和輸出值要滿足定義
foo1(a, b) {
return 'hello ' + a + ' : ' + (b + 1);
}
// 實(shí)現(xiàn)interface1定義的foo2方法,輸入值和輸出值要滿足定義
foo2(c, d) {
return 'balabala...';
}
}
復(fù)合類型的定義范式
type[類型名稱] = [類型定義];
不同的復(fù)合類型,類型定義也不相同,下面會(huì)對(duì)三種復(fù)合類型做詳細(xì)說(shuō)明。
Function 類型定義
范式:
type [Function類型名稱] = ([傳入?yún)?shù)1名稱]: [傳入?yún)?shù)1類型], [傳入?yún)?shù)2名稱]: [傳入?yún)?shù)2類型], ...) => [返回類型]
舉例:
// 定義一個(gè)傳參分別為number,string,bool類型的三個(gè)參數(shù),返回值為number的函數(shù)類型
type Callback = (a: number, b: string, c: boolean) => number;
Object 類型定義
范式:
type[Object類型名稱] = {
[屬性名稱1]: [類型1],
[屬性名稱2]: [類型2],
};
舉例:
// 定義含有a,b,c三個(gè)屬性的復(fù)合類型
type Scheme = {
a: string,
b: boolean,
c: number,
};
Array 類型定義
范式:
type[Array類型名稱] = [[類型1]];
舉例:
// 定義名稱為arrayType1的數(shù)組類型,數(shù)組元素為number類型
type arrayType1 = [number];
復(fù)合類型中的相互嵌套
Function、Object、Array 三種復(fù)合類型可以互相嵌套:
// 定義一個(gè)傳參分別為number,string,bool類型的三個(gè)參數(shù),返回值為number的函數(shù)類型
type Callback = (a: number, b: string, c: boolean) => number;
// 定義名稱為arrayType1的數(shù)組類型,數(shù)組元素為number類型
type arrayType1 = [number];
// 定義名稱為Scheme的,含有Array類型和Function類型屬性的Object類型
type Scheme = {
a: arrayType1,
b: Callback,
};
// 定義名稱為Plan,含有Scheme類型和Callback的屬性的Object類型
type Plan = {
a: string,
b: Scheme,
c: Callback,
};
// 定義名稱為arrayType1類型,元素為Plan類型
type arrayType1 = [Plan];
Promise 類型的定義
對(duì)于 async 函數(shù),由于該函數(shù)調(diào)用之后的返回值是 Promise 對(duì)象,所以這樣的函數(shù)的返回值要聲明成 Promise;
interface EntryInterface {
appEntry(): Promise;
appEntry2(): Promise;
}
在 methods 中
class Method implements EntryInterface {
async appEntry(num) {}
appEntry2() {
return new Promise((resolve, reject) => {
setTimeout(resolve, 2000);
});
}
}
Date 類型的定義
如果函數(shù)參數(shù)或者返回值是 Date 數(shù)據(jù)類型,那么可以按照下面的方式進(jìn)行定義;
interface EntryInterface {
handleDate(d: Date): Date;
}
class Method implements EntryInterface {
handleDate(d) {
return new Date();
}
}
RegExp 類型的定義
如果函數(shù)參數(shù)或者返回值是 RegExp 數(shù)據(jù)類型,那么可以按照下面的方式進(jìn)行定義;
interface EntryInterface {
handleDate(d: RegExp): RegExp;
}
class Method implements EntryInterface {
handleDate(r) {
return new RegExp();
}
}
該文檔匯集模板校驗(yàn)支持所有檢查點(diǎn),附錄有模板格式規(guī)范
模板可以指定模板語(yǔ)言,指定方式為在 template 標(biāo)簽上指定 lang 屬性, 其合法值為 "cml" 和 "vue"。
校驗(yàn)點(diǎn):
報(bào)錯(cuò)信息:'the tag template lang attribute: "<%= lang %>" is not valid'.
校驗(yàn)點(diǎn):每個(gè)模板只能 且必須有一對(duì) template 根標(biāo)簽。
報(bào)錯(cuò)信息:"Each template can only have one group of template tags."
每個(gè)模板都有一個(gè)模板語(yǔ)言和一個(gè)平臺(tái)類型,其中模板語(yǔ)言由 template 的 lang 屬性指定,平臺(tái)類型由模板文件的文件名解析出來(lái)。 對(duì)于多態(tài)組件平臺(tái)類型可以直接從文件名解析出來(lái), 比如 index.web.cml, index.weex.cml, index.wx.cml, index.alipay.cml, index.baidu.cml, 對(duì)應(yīng)的平臺(tái)類型分別為 web, weex, wx, alipay, baidu。 對(duì)于單文件組件,由于其模板要跨三端,故模板中只能使用 CML 原生支持的內(nèi)建標(biāo)簽。
校驗(yàn)點(diǎn):
報(bào)錯(cuò)信息:'tag: "<%= tag %>" is either not allowed in this template or not referenced as a component'
除引入的 平臺(tái)原生組件對(duì)應(yīng)的標(biāo)簽和'origin-'為前綴的原生標(biāo)簽,每個(gè)模板只能夠使用 template lang 指定 模板語(yǔ)言對(duì)應(yīng)的指令。Chameleon 現(xiàn)只提供兩種模板語(yǔ)言 'vue' 和 'cml'。其對(duì)應(yīng)的指令列舉如下:
校驗(yàn)點(diǎn):
報(bào)錯(cuò)信息:'directive "<%= attribute %>" is not allowed to be used in this template, as the template language is set to "<%= lang %>"'
報(bào)錯(cuò)信息:'tag "<%= name %>" is prefixed with "origin-" directive, so it's not allowed to use a CML built-in directive:"<%= directive %>"'
報(bào)錯(cuò)信息:'tag "<%= name %>" is a third party imported component, so it's not allowed to use a CML built-in directive:"<%= directive %>"'
在使用組件的時(shí),會(huì)對(duì)使用過(guò)程中屬性名和綁定的事件名稱進(jìn)行校驗(yàn)。組件屬性校驗(yàn)分為內(nèi)建組件與自定義組件兩部分。
校驗(yàn)點(diǎn):
報(bào)錯(cuò)信息:'component "<%= name %>" doesn't have a defined event named "<%= prop %>"'
報(bào)錯(cuò)信息: "The property "propName" is not a property of component "compName" which path is: path/to/component"
報(bào)錯(cuò)信息: "The event "eventName" is not defined in component "compName" which path is: path/to/component"
在使用 CML 內(nèi)置組件時(shí),內(nèi)置組件之間需要遵循一定的嵌套關(guān)系。
校驗(yàn)點(diǎn):
報(bào)錯(cuò)信息 'tag "<%= parent %>" can not have any child elements, therefor tag "<%= forbiddenTag %>" is not allowed as it's children'
報(bào)錯(cuò)信息 'tag "<%= parent %>" can only have "<%= elements %>" as it's child elements, therefor tag "<%= forbiddenTag %>" is not allowed as it's children'
報(bào)錯(cuò)信息 'tag "<%= parent %>" can not have "<%= forbiddenTag %>" as it's child elements, and element in this list: "<%= elements %>" is forbidden附:模板格式規(guī)范
模板書寫規(guī)范
chameleon 模板書寫規(guī)范尊從 HTML5 基本規(guī)范。
模板目錄規(guī)范
CML 支持三端(三種 Native 環(huán)境),每個(gè)組件在每個(gè)環(huán)境對(duì)應(yīng)有一個(gè)模板。模板命名格式 組件名稱+端名稱.cml 比如:c-title 組件
├── components
│ ├── c-title
│ │ ├── c-title.web.cml
│ │ ├── c-title.weex.cml
│ │ └── c-title.wx.cml
其中: c-title.web.cml 為 Web 端模板,c-title.weex.cml 為 iOS、Android 端,c-title.wx.cml 為微信小程序端。
本節(jié)模板規(guī)范就是指對(duì)這三個(gè)模板文件的編寫規(guī)范。
模板語(yǔ)言指定
每個(gè)端的模板都可以并且必須選擇兩種語(yǔ)法規(guī)范中的一個(gè),cml 語(yǔ)法規(guī)范 或者 類 vue 語(yǔ)法規(guī)范。指定語(yǔ)法規(guī)范的方式為在根節(jié)點(diǎn) template 標(biāo)簽上給屬性 lang 指定 "cml" 或者 "vue"。
列如指定模板為 cml 語(yǔ)法規(guī)范
<template lang="cml"></template>
注意:每個(gè)模板只能夠有一個(gè)根節(jié)點(diǎn)并且必須為 template 標(biāo)簽,template 便簽每個(gè)模板只能有一個(gè)。
模板標(biāo)簽使用規(guī)范
每個(gè)模板內(nèi)可以使用的標(biāo)簽由三部分組成:
舉例
仍以 c-title 組件為例,假設(shè)各個(gè)模板都有自定義組件配置
<script cml-type="json">
{
"base": {
"usingComponents": {
"tickets": "/components/ticket/index"
}
}
}
</script>
模板指令使用規(guī)范
除引用平臺(tái)原生組件 對(duì)應(yīng)的標(biāo)簽外,每個(gè)模板必須使用模板 語(yǔ)言(由 template 標(biāo)簽 的 lang 屬性指定) 所對(duì)應(yīng)的指令集。
類 vue 語(yǔ)法支持上述列表中的指令,其他 vue.js 的指令如 v-cloak 是不支持的。
舉例
若模板語(yǔ)言為 "cml" 即 template 標(biāo)簽 lang 屬性為 "cml",native 環(huán)境為微信小程序。還是以 c-title 組件為例,那么此時(shí)對(duì)應(yīng)的是 c-title.wx.cml 模板。 c-title.wx.cml:
<template lang="cml">
<view c-if="{{showMessage}}">{{messageText}}</view>
<picker-view></picker-view>
</template>
那么模板里可以使用 CML 支持的指令:
c-if、c-else、c-else-if、c-for、c-for-index、c-for-item、c-model、c-text、c-show、c-bind、c-catch
引用平臺(tái)原生組件
Chameleon 提供兩種方式引入平臺(tái)原生 組件和平臺(tái)第三方原生組件:
引用的原生組件上只能夠使用平臺(tái)支持的原生指令,不能使用 CML 內(nèi)置指令。 改限制只限于組件本身,對(duì)其子組件沒有影響。
舉例
若模板語(yǔ)言為 "cml" 即 template 標(biāo)簽 lang 屬性為 "cml",native 環(huán)境為微信小程序。還是以 c-title 組件為例,那么此時(shí)對(duì)應(yīng)的是 c-title.wx.cml 模板。 c-title.wx.cml:
<template lang="cml">
<view>{{messageText}}</view>
<origin-picker-view></origin-picker-view>
</template>
那么模板里可以使用 CML 支持的指令:
c-if、c-else、c-else-if、c-for、c-for-index、c-for-item、c-model、c-text、c-show、c-bind、c-catch
origin-picker-view 組件可以使用微信小程序原生支持的指令:
wx:if、wx:elif、wx:else、wx:for、wx:for-item、wx:for-index、wx:key、bindtap、catchtap
組件邏輯層響應(yīng)頁(yè)面操作的代碼,需要導(dǎo)出以下規(guī)范的對(duì)象。
{
// 數(shù)據(jù)
data: {
dataKey1: dataValue1,
dataKey2: dataValue2
},
// 屬性
props: {
propKey1: propValue1,
propKey2: propValue2
},
// 計(jì)算屬性
computed: {
computedKey1: () => {
},
computedKey2: () => {
}
},
// 監(jiān)聽屬性
watch: {
watchKey1: () => {
},
watchKey2: () => {
}
},
// 實(shí)例初始化之后,數(shù)據(jù)和方法掛在到實(shí)例之前
beforeCreate: () => {
},
// 數(shù)據(jù)及方法掛載完成
created: () => {
},
// 開始掛載已經(jīng)編譯完成的html,到對(duì)應(yīng)的dom節(jié)點(diǎn)時(shí)
beforeMount: () => {
},
// 模板或者h(yuǎn)tml編譯完成,且渲染到dom中完成
mounted: () => {
},
// 實(shí)例銷毀之前
beforeDestroy: () => {
},
// 實(shí)例銷毀后
destroyed: () => {
}
}
鉤子 | 執(zhí)行時(shí)機(jī) | 詳細(xì) |
---|---|---|
beforeCreate | 實(shí)例初始化之后,數(shù)據(jù)和方法掛在到實(shí)例之前 | 在該鉤子函數(shù)中會(huì)傳入當(dāng)前頁(yè)面 query 參數(shù) |
created | 數(shù)據(jù)及方法掛載完成 | |
beforeMount | 開始掛載已經(jīng)編譯完成的 html,到對(duì)應(yīng)的 dom 節(jié)點(diǎn)時(shí) | |
mounted | 模板或者 html 編譯完成,且渲染到 dom 中完成 | |
beforeDestroy | 實(shí)例銷毀之前 | |
destroyed | 實(shí)例銷毀后 |
編寫一端代碼邏輯時(shí),如果使用其他端的全局變量,會(huì)校驗(yàn)失敗。
按照端類型區(qū)分可用的全局變量:
weex
weex , global
wx
wx, global
alipay
my, global
baidu
swan, global
web
postMessage, blur, focus, close, frames, self, window, parent, opener, top, length, closed, location, document, origin, name, history, locationbar, menubar, personalbar, scrollbars, statusbar, toolbar, status, frameElement, navigator, customElements, external, screen, innerWidth, innerHeight, scrollX, pageXOffset, scrollY, pageYOffset, screenX, screenY, outerWidth, outerHeight, devicePixelRatio, clientInformation, screenLeft, screenTop, defaultStatus, defaultstatus, styleMedia, onanimationend, onanimationiteration, onanimationstart, onsearch, ontransitionend, onwebkitanimationend, onwebkitanimationiteration, onwebkitanimationstart, onwebkittransitionend, isSecureContext, onabort, onblur, oncancel, oncanplay, oncanplaythrough, onchange, onclick, onclose, oncontextmenu, oncuechange, ondblclick, ondrag, ondragend, ondragenter, ondragleave, ondragover, ondragstart, ondrop, ondurationchange, onemptied, onended, onerror, onfocus, oninput, oninvalid, onkeydown, onkeypress, onkeyup, onload, onloadeddata, onloadedmetadata, onloadstart, onmousedown, onmouseenter, onmouseleave, onmousemove, onmouseout, onmouseover, onmouseup, onmousewheel, onpause, onplay, onplaying, onprogress, onratechange, onreset, onresize, onscroll, onseeked, onseeking, onselect, onstalled, onsubmit, onsuspend, ontimeupdate, ontoggle, onvolumechange, onwaiting, onwheel, onauxclick, ongotpointercapture, onlostpointercapture, onpointerdown, onpointermove, onpointerup, onpointercancel, onpointerover, onpointerout, onpointerenter, onpointerleave, onafterprint, onbeforeprint, onbeforeunload, onhashchange, onlanguagechange, onmessage, onmessageerror, onoffline, ononline, onpagehide, onpageshow, onpopstate, onrejectionhandled, onstorage, onunhandledrejection, onunload, performance, stop, open, alert, confirm, prompt, print, requestAnimationFrame, cancelAnimationFrame, requestIdleCallback, cancelIdleCallback, captureEvents, releaseEvents, getComputedStyle, matchMedia, moveTo, moveBy, resizeTo, resizeBy, getSelection, find, webkitRequestAnimationFrame, webkitCancelAnimationFrame, fetch, btoa, atob, createImageBitmap, scroll, scrollTo, scrollBy, onappinstalled, onbeforeinstallprompt, crypto, ondevicemotion, ondeviceorientation, ondeviceorientationabsolute, indexedDB, webkitStorageInfo, sessionStorage, localStorage, chrome, visualViewport, speechSynthesis, webkitRequestFileSystem, webkitResolveLocalFileSystemURL, openDatabase, applicationCache, caches, whichAnimationEvent, animationendEvent, infinity, SETTING, AppView, ExtensionOptions, ExtensionView, WebView, iconPath, _app, _ZOOM_, Feed, md5, $, jQuery, Search, windmill, Lethargy, alertTimeOut, supportApps, lethargyX, lethargyY, iView, onModuleResLoaded, iEditDelete, infinityDrag, i, array, TEMPORARY, PERSISTENT, addEventListener, removeEventListener, dispatchEvent
CMSS 規(guī)則由兩個(gè)主要的部分構(gòu)成:選擇器,以及一條或多條聲明。
selector { declaration1; declaration2; ... declarationN } 每條聲明由一個(gè)屬性和一個(gè)值組成。
屬性(property)是你希望設(shè)置的樣式屬性(style attribute)。每個(gè)屬性有一個(gè)值。屬性和值被冒號(hào)分開,一條聲明以分號(hào)結(jié)尾。
selector {
property1: value1;
property2: value2;
}
selector 應(yīng)為一條獨(dú)立的 id 名稱或者 class 名稱,不支持級(jí)聯(lián)
// 正確
.selector {
property1: value1;
property2: value2;
}
#selector {
property1: value1;
property2: value2;
}
// 錯(cuò)誤
#selector1 .selector {
property1: value1;
property2: value2;
}
CSS屬性 | H5 | 小程序 | weex |
---|---|---|---|
布局 | all | all | flexbox |
盒模型 | all | all | 只支持display:border-box |
float浮動(dòng) | ? | ? | ? |
display:inline-block|none | ? | ? | ? |
ID選擇器 | ? | ? | ? |
類選擇器 | ? | ? | ? |
屬性選擇器 | ? | ? | ? |
級(jí)聯(lián)選擇器、派生選擇器(后代、子元素、相鄰兄弟) | ? | ? | ? |
選擇器分組 | ? | ? | ? |
偽類(:active|:focus) | ? | ? | ? |
偽類(:hover|:link|:visited|:first-child|:lang) | ? | ? | ? |
偽元素(:first-letter|:first-line|:before|:after) | ? | ? | ? |
百分比定值 | ? | ? | ? |
line-height:1 | ? | ? | ? |
尺寸 | px|rem|em|vw|vh | px|rpx | px |
!important | ? | ? | ? |
**注意:**框架會(huì)根據(jù)上表所述的多端之間的差異做校驗(yàn)。
CML 文件配置規(guī)范的校驗(yàn),包括語(yǔ)法格式,組件的引用等。
wx、alipay、baidu、web、weex 分別對(duì)應(yīng)各端構(gòu)建時(shí)所應(yīng)用的配置信息,base 的配置會(huì)應(yīng)用到每一端的配置,內(nèi)部做的 result = merge(base,target)。
wx 字段配置的內(nèi)容最終會(huì)生成到微信小程序所需要的 json 文件。
wx.component 字段,如果在微信中該 cml 文件是組件必須聲明該字段。 usingComponents 字段是目前最重要的配置,三端都會(huì)使用,微信小程序規(guī)定頁(yè)面的 json 文件中配置使用到的組件。web 和 Weex 端的構(gòu)建時(shí)也是根據(jù)該字段去找到相應(yīng)的組件進(jìn)行自動(dòng)化的注冊(cè)。所以用到組件必須進(jìn)行配置 usingComponents 中組件的引用地址支持引用 src 和 node_modules 下的組件,src 下的,可以寫相對(duì)路徑,也可以寫相對(duì)于 src 的絕對(duì)路徑,例如/components/**, node_modules 下的組件,不需要寫 node_modules,直接從 npm 的包名稱開始寫例如 cml-test-ui/navi/navi。 路徑寫到.cml 文件所在層級(jí),不寫后綴。 例子:
{
"base": {
"usingComponents": {
"c-scroller": "chameleon-ui-builtin/components/scroller/scroller",
"c-checkbox": "/components/c-checkbox/c-checkbox"
}
},
"wx": {
"navigationBarTitleText": "index",
"backgroundTextStyle": "dark",
"backgroundColor": "#E2E2E2"
},
"alipay": {
"defaultTitle": "index",
"pullRefresh": false,
"allowsBounceVertical": "YES",
"titleBarColor": "#ffffff"
},
"baidu": {
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "white",
"navigationBarTitleText": "index",
"backgroundColor": "#ffffff",
"backgroundTextStyle": "dark",
"enablePullDownRefresh": false,
"onReachBottomDistance": 50
},
}
##### CML JSON 規(guī)范
包括以下三條:
1. 滿足標(biāo)準(zhǔn)的 JSON 格式規(guī)范,滿足 JSON.parse 的解析
- 數(shù)據(jù)在名稱/值對(duì)中
- 數(shù)據(jù)由逗號(hào)分隔
- 花括號(hào)保存對(duì)象
- 方括號(hào)保存數(shù)組
2. CML json為對(duì)象,必須包括base字段,根據(jù)適配的平臺(tái)可配置wx|alipay|baidu|web|weex字段,作為單一端上的特殊配置
```javascript
// 適配微信小程序和weex
{
"base": {
},
"wx": {
},
"alipay": {
},
"baidu": {
},
"weex": {
}
}
多端實(shí)現(xiàn)完全一致組件:
usingComponents 字段必須放置在 base 下,不應(yīng)出現(xiàn) wx|alipay|baidu|web|weex 字段中
多端實(shí)現(xiàn)不一致組件:
usingComponents 字段可以放置在 base 下,也可以在出現(xiàn)當(dāng)前端對(duì)應(yīng)的 wx|alipay|baidu|web|weex 字段中
注:多端實(shí)現(xiàn)完全一致組件 和 多端實(shí)現(xiàn)不一致組件 的說(shuō)明可參見這
更多建議: