阅读 1424

Chameleon:原来写一个跨多端项目,可以这么容易!

出品 | 滴滴技术

作者 | 许国栋


▍前言:随着各类小程序的百花齐放,业务对跨多端的需求越来越明显。虽然各端环境千变万化,无论各类小程序、Weex、React-Native、Flutter、快应用,它们万变不离其宗的是 MVVM 架构设计思想。今天,给大家带来 Chameleon 迁移指南,一套代码完成各端需求,满满干货~

cml作为真正让一套代码运行多端的框架,提供标准的 MVVM 模式,统一开发各类终端。同时,拥有各端独立的运行时框架 (runtime)、数据管理 (store)、组件库 (ui)、接口 (api)。此外,cml在跨端能力加强、能力统一、表现一致等方面做了许多工作。

今天,为了让大家的项目优雅升级,快速接入,给你带来一份丰盛的 cml 迁移指南。

视频教程源码地址github.com/jalonjs/cml…

视频教程地址:sfwb.didistatic.com/static/wb/5…



阅读索引

  • 目录结构

  • 如何修改配置

  • 如何使用路由能力

  • 如何注册

  • 如何声明生命周期

  • 数据如何响应到视图

  • 事件交互

  • 布局与外观

  • 自定义组件

  • 如何实现父子组件事件通信

  • 组件使用总结

  • 如何调用平台接口能力

  • 迁移实例


▍目录结构

和微信小程序一样,cml 包含一个描述整体程序的 app 和多个描述各自页面的 page

▍小程序目录结构

1.
2├── components // 包含各个组件
3├── pages // 包含各个页面
4├── app.js // 包含各个组件
5├── app.js  // 应用启动入口
6├── app.json // 全局配置
7├── app.wxss // 全局样式
8└── project.config.json // 项目配置文件
复制代码

▍cml目录结构

 1.
 2├── dist // 各个端构建结果
 3│   ├── alipay 
 4│   ├── baidu 
 5│   ├── wx 
 6│   ├── web  
 7│   ├── weex 
 8│   └── config.json // 跨端配置map映射表
 9├── node_modules // 第三方库
10├── mock // 模拟 接口数据 和 模板数据
11├── src  // 源代码开发目录
12│   ├── app // 应用启动入口
13│   ├── assets // 静态资源
14│   ├── components // 包含组件
15│   ├── pages  // 包含页面
16│   ├── store //数据管理
17│   └── router.config.json // 路由配置文件
18├── chameleon.config.js // 项目配置文件
19└── package.json // npm包配置文件
复制代码

▍如何修改配置

在小程序项目里面,分为:

▍小程序 — 项目配置

可以在项目根目录使用 project.config.json 文件对项目进行配置。

配置示例:

1{
2  "miniprogramRoot": "./src",
3  "debugOptions": {}
4}
复制代码

▍小程序 — 全局配置

小程序根目录下的 app.json 文件用来对微信小程序进行全局配置,决定页面文件的路径、窗口表现、设置网络超时时间、设置多 tab 等

配置示例:

 1 {
 2  "pages": ["pages/index/index", "pages/logs/index"],
 3  "window": {
 4    "navigationBarTitleText": "Demo"
 5  },
 6  "networkTimeout": {
 7    "request": 10000,
 8    "downloadFile": 10000
 9  }
10}
复制代码

▍小程序 — 页面配置

每一个小程序页面也可以使用 .json 文件来对本页面的窗口表现进行配置。

页面的配置只能设置 app.json 中部分 window 配置项的内容,页面中配置项会覆盖 app.jsonwindow 中相同的配置项。

配置示例:

1{
2  "navigationBarBackgroundColor": "#ffffff",
3  "navigationBarTextStyle": "black",
4  "navigationBarTitleText": "微信接口功能演示",
5  "backgroundColor": "#eeeeee",
6  "backgroundTextStyle": "light"
7}
复制代码

同样,在 cml项目里面,分为以下几种配置方案:

▍cml — 项目配置

chameleon.config.js 为项目的配置文件,你可以定制化构建,比如是否带hash,是否压缩等等。

配置示例:

1 // 设置静态资源的线上路径
2 const publicPath = '//www.static.chameleon.com/static';
3 // 设置api请求前缀
4 const apiPrefix = 'https://api.chameleon.com';
5 // 合并配置
6 cml.config.merge({
7   wx: {
8    build: {apiPrefix}
9  },
10  alipay: {
11    build: {apiPrefix}
12  },
13  baidu: {
14    build: {apiPrefix}
15  },
16  web: {
17    dev: {
18      hot: true,
19      console: true
20    },
21    build: {
22      publicPath: `${publicPath}/web`,
23      apiPrefix
24    }
25  },
26  weex: {
27    build: {
28      publicPath: `${publicPath}/weex`,
29      apiPrefix
30    }
31  }
32})
复制代码

▍cml — 全局设置

cml 项目 app 目录下的 app.cml 文件的 <script cml-type="json" /> 用来对 cml应用 进行全局配置,具有 跨端配置 和 差异化 的能力。

配置示例:

 1<script cml-type="json">
 2{
 3  "base": {
 4    "window": {
 5      "navigationBarTitleText": "各个端共同title",
 6    },
 7    "permission": {
 8      "scope.userLocation": {
 9        "desc": "你的位置信息将用于小程序位置接口的效果展示"
10      }
11    }
12  },
13  "wx": {
14    "window": {
15      "backgroundTextStyle":"light",
16      "navigationBarBackgroundColor": "#fff",
17      "navigationBarTitleText": "差异化 title",
18      "navigationBarTextStyle":"black"
19    }
20  },
21  "baidu": {
22    "window": {
23      "backgroundTextStyle": "light"
24    }
25  },
26  "alipay": {
27      "window": {
28        "defaultTitle": "Chameleon"
29      }
30  }
31}
32</script>
复制代码

▍cml — 页面 / 组件配置

通过 usingComponents 配置 组件路径 注册引用的组件。

配置示例:

 1  <script cml-type="json">
 2  {
 3  "base": {
 4    "usingComponents": {
 5      "navi": "/components/navi/navi",
 6      "navi-npm": "cml-test-ui/navi/navi"
 7    }
 8  },
 9  "wx": {
10  },
11  "alipay": {
12  },
13  "baidu": {
14  },
15  "web": {
16  },
17  "weex": {
18  }
19}
20</script>
复制代码

▍如何使用路由能力

▍小程序配置路由

app.json 配置项列表的 pages 字段用于指定小程序由哪些页面组成,每一项都对应一个页面的 路径+文件名 信息。

数组的第一项代表小程序的初始页面(首页)。新增/减少页面,需要对 pages 数组进行修改。

如果项目有 pages/index/index.wxmlpages/logs/logs.wxml 两个页面,则需要在 app.json 中写。

1{
2  "pages": ["pages/index/index", "pages/logs/logs"]
3}
复制代码

▍cml 配置路由

src/router.config.json 是路由的配置文件,cml 内置了一套各端统一的路由管理方式。相应有 cml 路由配置映射如下:

 1   {
 2  "mode": "history",
 3  "domain": "https://www.chameleon.com",
 4  "routes":[
 5    {
 6      "url": "/cml/h5/index",
 7      "path": "/pages/index/index",
 8      "mock": "index.php"
 9    },
10    {
11      "url": "/cml/h5/logs",
12      "path": "pages/logs/logs",
13      "mock": "logs.php"
14    }
15  ]
16}
复制代码

文件名不需要写文件后缀,cml框架会自动去寻找对于位置的 .cml 文件进行处理。

▍小程序使用路由

▍cml 使用路由

依据统一资源索引URI,自适应打开不同环境同一路由PATH:

▍如何注册

▍小程序注册应用

在小程序项目里面,App() 函数用来注册一个小程序。接受一个 Object 参数,其指定小程序的生命周期回调等。

示例代码:

1 App({
2  onLaunch(options) {
3    // Do something initial when launch.
4  },
5  globalData: 'I am global data'
6})
复制代码

▍cml 注册应用

示例代码:

 1 <script>
 2 import store from '../store/index.js'
 3 import routerConfig from '../router.config.json';
 4
 5 class App {
 6  data = {
 7    store,
 8    routerConfig
 9  }
10  created(res) {
11  }
12 }
13
14 export default new App();
15 </script>
复制代码

细心的你会发现,小程序中app.json app.js app.wxsssrc/app/app.cml的对应关系如下:


▍小程序注册页面

在小程序项目里面,Page(Object) 函数用来注册一个页面。接受一个 Object 类型参数,其指定页面的初始数据、生命周期回调、事件处理函数等。

示例代码:

 1 // index.js
 2 Page({
 3  data: {
 4    text: 'This is page data.'
 5  },
 6  changeText: function(e) {
 7    // sent data change to view
 8    this.setData({
 9      text: 'CML'
10    })
11  }
12})
复制代码

▍cml 注册页面

示例代码:

 1 <script>
 2 class Index {
 3  data = {
 4    text: 'Chameleon'
 5  }
 6  methods = {
 7    changeText: function(e) {
 8      // sent data change to view
 9      this.text = 'CML';
10    }
11  }
12  computed = {}
13  watch = {}
14 };
15 export default new Index();
16 </script>
复制代码

▍小程序注册组件

在小程序项目里面,

Component(Object) 构造器可用于定义组件,调用 Component 构造器时可以指定组件的属性、数据、方法等。

示例代码:

 1 Component({
 2  properties: {
 3    myProperty: { // 属性名
 4      type: String, // 类型(必填)
 5      value: '' // 属性初始值(可选)
 6    },
 7    myProperty2: String // 简化的定义方式
 8  },
 9  data: {
10    text: ''
11  }, // 私有数据,可用于模板渲染
12
13  // 生命周期函数,可以为函数,或一个在methods段中定义的方法名
14  attached() { }, 
15  ready() { },
16  methods: {
17    onMyButtonTap() {
18      this.setData({
19        // 更新属性和数据的方法与更新页面数据的方法类似
20        text: 'wx'
21      })
22    }
23  }
24})
复制代码

▍cml 注册组件

示例代码:

 1 <script>
 2 class MyComponent {
 3  props = {
 4    myProperty: { // 属性名
 5      type: String, // 类型(必填)
 6      default: '' // 属性初始值(可选)
 7    },
 8    myProperty2: String // 简化的定义方式
 9  }
10  data =  {
11    text: ''
12  } // 私有数据,可用于模板渲染
13
14  beforeMount() {}
15  mounted() {}
16  methods = {
17    onMyButtonTap() {
18      this.text = 'cml'
19    }
20  }
21  computed = {}
22  watch = {}
23 };
24 export default new MyComponent();
25 </script>
复制代码

▍如何声明生命周期

统一各端应用生命周期的定义,是跨端框架的重要组成,也是迁移的必经之路。

▍小程序声明生命周期

可以在 App(Object)Page(Object)Component(Object) 传入Object参数,其指定小程序的生命周期回调等。

代码示例:

 1 // index.js
 2 Page({
 3  onLoad(options) {
 4    // Do some initialize when page load.
 5  },
 6  onReady() {
 7    // Do something when page ready.
 8  },
 9  onShow() {
10    // Do something when page show.
11  },
12  onHide() {
13    // Do something when page hide.
14  },
15  onUnload() {
16    // Do something when page close.
17  },
18  onShareAppMessage() {
19    // return custom share data when user share.
20  }
21})
复制代码

▍cml 声明生命周期

.cml 文件 <script /> 代码块返回的对象实例,其指定生命周期回调。

示例代码:

 1 <script>
 2 class Index {
 3  beforeCreate(query) {
 4    // data数据挂载到this根节点上之前,以及methods所有方法挂载到实例根节点之前
 5    // 注意:只用页面的 beforeCreate钩子 会返回页面query
 6    console.log('App beforeCreate: 打开当前页面路径中的参数是 ', query)
 7  }
 8  created() {
 9    // data,methods里面的这些events挂载完成
10    console.log('App created')
11  }
12  beforeMount() {
13    // 开始挂载已经编译完成的cml到对应的节点时
14    console.log('App beforeMount')
15  }
16  mounted() {
17    // cml模板编译完成,且渲染到dom中完成,在整个生命周期中只执行一次
18    console.log('App mounted')
19  }
20  beforeDestroy() {
21    // 实例销毁前
22    console.log('App beforeDestroy')
23  }
24  destroyed() {
25    // 实例销毁后
26    console.log('App destroyed')
27  }
28 };
29 export default new Index();
30 </script>
复制代码

▍App 生命周期映射

小程序 app.js中的生命周期 -> cml src/app/app.cml

▍Page 生命周期映射

小程序 Page()中的生命周期 -> cml src/pages/mypage/mypage.cml

  1. 小程序                                                            chameleon
  2. onLoad                                                           beforeCreate
  3. onShow                                                          mounted
  4. onUnload                                                       destroyed
  5. onReady                                                         生命周期多态
  6. onHide                                                            生命周期多态
  7. onShareAppMessage                                    生命周期多态

▍Component 生命周期映射

小程序 Component()中的生命周期 -> cml src/components/mycomponent/mycomponent.cml


▍生命周期总结

每个 cml 实例( AppPageComponent )在被创建时都要经过一系列的初始化过程。

例如,需要设置数据监听、编译模板、将实例挂载到 CML节点 并在数据变化时更新 CML节点 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给开发者在不同阶段添加自己的代码的机会。

cmlApp页面 Page组件 Component 提供了一系列生命周期事件,保障应用有序执行。

另外,如果你想使用某个端特定的生命周期,你可以从业务出发使用 生命周期多态

▍数据如何响应到视图

如今,双向数据绑定&单向数据流 已深入开发者日常,MVMM开发模式算是框架标配。

数据绑定条件渲染列表渲染是如何书写的呢?

示例代码:

▍小程序使用数据响应用


▍cml 使用数据响应

 1 <template>
 2 <!--index.cml-->
 3 <view class="scroller-wrap">
 4    <!--数据绑定-->
 5  <view>{{message}}</view>
 6  <!--条件渲染-->
 7  <view c-if="{{view == 'WEBVIEW'}}">WEBVIEW</view>
 8  <view c-else-if="{{view == 'APP'}}">APP</view>
 9  <view c-else="{{view == 'MINA'}}">MINA</view>
10    <!--列表渲染-->
11  <view c-for="{{array}}" c-for-index="index" c-for-item="item">{{item}}</view>
12 </view>
13 </template>
14 <script>
15 class Index {
16  data =  {
17    message: 'Hello MINA!',
18    view: 'MINA',
19    array: [1, 2, 3, 4, 5]
20  }
21
22  beforeCreate () {
23    this.message = 'cml'
24  }
25 };
26 export default new Index();
27 </script>
复制代码

▍数据响应总结

cml运行时框架 提供了跨端响应式数据绑定系统(Data binding),当做数据修改的时候,只需要在逻辑层修改数据,视图层就会做相应的更新。

只需要将 view<-->model 交互部分逻辑,作简单迁移,便可使它成为跨多端的数据响应系统。

▍事件交互

cml 支持一些基础的事件,保障各端效果(类型绑定事件对象)一致运行。

示例代码:

▍小程序使用事件

1<!--wxml-->
2<view id="tapTest" data-hi="WeChat" bindtap="tapName">Click me!</view>
复制代码

1// page.js
2Page({
3  tapName(event) {
4    console.log(event)
5  }
6})
复制代码

▍cml 使用事件

 1 <template>
 2  <view id="tapTest" data-hi="WeChat" c-bind:tap="tapName">
 3    <text>Click me!</text>
 4  </view>
 5 </template>
 6 <script>
 7 class Index {
 8  methods = {
 9    tapName(e) {
10      // 打印事件对象
11      console.log('事件对象:', e);
12    }
13  }
14}
15 export default new Index();
16 </script>
复制代码

▍事件使用总结

同时,还支持自定义事件,用于父子组件之间的通信。

另外,如果你想要使用某个端特定的事件,cml 并不会限制你的自由发挥,你可以从业务出发使用 组件多态 或者 接口多态 差异化实现功能。

▍布局与外观

各端描述 布局和外观 的层叠样式表(CSS)实现存在差异,包括不限于 布局、盒模型、定位、文本。

所以, cml 框架内置跨端一致性基础样式能力。

并且,定义了用于描述页面的样式规范CMSS(Chameleon Style Sheet)

▍如何导入外部样式

使用 @import 语句可以导入外联样式表,@import 后跟需要导入的外联样式表的相对路径,用 ; 表示语句结束。

▍小程序导入外部样式

示例代码:


▍cml 导入外部样式

详细文档

示例代码:


▍样式使用总结

同时,为了统一多端尺寸单位,呈现效果一致,同时页面响应式布局,cml 项目统一采用 cpx 作为尺寸单位,规定以屏幕750px(占满屏幕)视觉稿作为标准。

而且,各端样式表拥有的能力 不尽相同,是项目迁移的主要阵地之一。

另外,如果你想要使用某个端特定的样式能力,cml 并不会限制你的自由发挥,你可以从业务出发使用 样式多态

注意:由于chameleon应用是 跨多端web native 小程序框架,如果需要跨native,必须使用 flexbox 进行样式布局。

▍自定义组件

开发者可以将页面内的功能模块抽象成自定义组件,以便在不同的页面中重复使用。自定义组件在使用时与基础组件非常相似。

▍小程序创建自定义组件

代码示例:

 1 Component({
 2  properties: {
 3    // 这里定义了innerText属性,属性值可以在组件使用时指定
 4    innerText: {
 5      type: String,
 6      value: 'default value',
 7    }
 8  },
 9  data: {
10    // 这里是一些组件内部数据
11    someData: {}
12  },
13  methods: {
14    // 这里是一个自定义方法
15    customMethod() {}
16  }
17})
复制代码

▍cml 创建自定义组件

示例代码:

 1 <script>
 2 class MyComponent {
 3  props = {
 4    // 这里定义了innerText属性,属性值可以在组件使用时指定
 5    innerText: {
 6      type: String,
 7      value: 'default value',
 8    }
 9  }
10  data = {
11    // 这里是一些组件内部数据
12    someData: {}
13  }
14  methods = {
15    // 这里是一个自定义方法
16    customMethod() {}
17  }
18  computed = {}
19  watch = {}
20};
21export default new MyComponent();
22</script>
复制代码

▍如何使用自定义组件

使用已注册的自定义组件前,首先要进行引用声明。此时需要提供每个自定义组件的标签名和对应的自定义组件文件路径。

▍小程序使用自定义组件

代码示例:

page.json 中进行引用声明

1{
2  "usingComponents": {
3    "component-tag-name": "path/to/the/custom/component"
4  }
5}
复制代码

page.wxml 中使用

1 <view>
2  <!-- 以下是对一个自定义组件的引用 -->
3  <component-tag-name inner-text="Some text"></component-tag-name>
4 </view>
复制代码

▍cml 使用自定义组件

代码示例:

page.cml<script cml-type='json' />进行引用声明

1<script cml-type="json">
2{
3  "base": {
4      "usingComponents": {
5        "component-tag-name": "path/to/the/custom/component"
6      }
7  }
8}
9</script>
复制代码

page.cml<template />使用

1<template>
2<view>
3  <!-- 以下是对一个自定义组件的引用 -->
4  <component-tag-name inner-text="Some text"></component-tag-name>
5</view>
6</template>
复制代码

▍如何实现父子组件事件通信

事件系统是组件间通信的主要方式之一。自定义组件可以触发任意的事件,引用组件的页面可以监听这些事件。

▍小程序组件通信

代码示例:


▍cml 组件通信

代码示例:


▍组件使用总结

和小程序一样,cml框架 提供了大量内置组件扩展组件,抹平多端差异,便于开发者通过组合这些组件,创建出强大的应用程序。

扩展组件需要额外引入。如:

1<script cml-type="json">
2{
3  "base": {
4      "usingComponents": {
5        "c-dialog": "cml-ui/components/c-dialog/c-dialog"
6      }
7  }
8}
9</script>
复制代码

在执行 cml build 构建打包时,cml 框架 会按需打包引用的内置组件和扩展组件,为代码瘦身。

内置组件扩展组件 都是支持跨多端的,对于一些没有提供的某个端的组件,可以通过组件多态来实现。

如果希望使用小程序端的原生组件,那么可以在原生标签前加上 origin-*cml框架会渲染原生组件参考

注意:origin-* 只能在灰度区文件中使用。

如在 map.wx.cml 文件中使用原生地图组件 <map/>

 1 <!-- map.wx.cml -->
 2 <template>
 3  <origin-map
 4    id="map"
 5    longitude="113.324520"
 6    latitude="23.099994"
 7    controls="{{controls}}"
 8    bindcontroltap="controltap"
 9    style="width: 100%; height: 300px;"
10  ></origin-map>
11 </template>
复制代码

▍组件如何调用平台接口能力

在小程序里面,可以通过微信原生 API,调起如获取用户信息,本地存储,支付功能等。

示例代码:

1try {
2  wx.setStorageSync('name', 'Hanks')
3} catch (e) {
4  console.error(e)
5}
复制代码

同样,在 cml 项目里面可以这样调用:

示例代码:

1import cml from 'chameleon-api'
2cml.setStorage('name', 'Hanks').then((res)=>{
3  console.log(res)
4},function(e){
5  console.error(e)
6})
复制代码

▍借口使用总结

cml 框架提供了丰富的多态接口,可以调起各端提供的原生能力,如系统信息、元素节点信息、动画效果、本地存储、网络请求、地理位置等。请参考 API 文档。

chameleon-api提供的接口都是支持跨多端的,对于一些没有提供的某个端的原生接口,可以通过接口多态来调用。

▍迁移实例

下面给出各端(vue、weex、小程序)迁移cml指南 以及 cml 导出组件到各端指南的具体迁移文档:



点击图片了解更多:

有关安装、使用过程以及常见问题解答,请查看以下链接:

GitHub:github.com/didi/chamel…

快速开始:cml.js.org/doc/quick_s…


同时欢迎加入「Chameleon 用户交流群」
请在滴滴技术公众号后台回复「Chameleom」即可加入


▍END


                                    


                                                                               许 国 栋

                                                        滴滴 | 高级软件开发工程师

Chameleon 成员,主要负责框架运行时、组件生态化等相关开发工作。喜欢专研前端技术,对领域前沿技术感兴趣。有跨端相关的见解的同学,欢迎来相互探讨。




关注下面的标签,发现更多相似文章
评论