cml 作为真正让一套代码运行多端的框架,提供标准的 MVVM 模式,统一开发各类终端。同时,拥有各端独立的运行时框架(runtime)、数据管理(store)、组件库(ui)、接口(api)。此外,cml在跨端能力加强、能力统一、表现一致等方面做了许多工作。
今天,为了让大家的项目优雅升级,快速接入,给你带来一份丰盛的 cml 迁移指南~
源码地址:https://github.com/jalonjs/cml-first-demo
目录结构
和微信小程序一样,cml 包含一个描述整体程序的 app 和多个描述各自页面的 page。
小程序目录结构
.├── components ├── pages ├── app.js ├── app.js ├── app.json ├── app.wxss └── project.config.json
复制代码
cml 目录结构
.├── dist │ ├── alipay │ ├── baidu │ ├── wx │ ├── web │ ├── weex │ └── config.json ├── node_modules ├── mock ├── src │ ├── app │ ├── assets │ ├── components │ ├── pages │ ├── store │ └── router.config.json ├── chameleon.config.js └── package.json
复制代码
如何修改配置
在小程序项目里面,分为:
小程序 —— 项目配置
可以在项目根目录使用 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项目里面,分为以下几种配置方案。
cml —— 项目配置
chameleon.config.js 为项目的配置文件,你可以定制化构建,比如是否带 hash,是否压缩等等。
配置示例:
const publicPath = '//www.static.chameleon.com/static';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 —— 全局配置
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>
复制代码
cml —— 页面/组件配置
通过 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"]}
复制代码
cml 配置路由
src/router.config.json是路由的配置文件,cml 内置了一套各端统一的路由管理方式。相应有 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 文件进行处理。
小程序使用路由
cml 使用路由
依据统一资源索引 URI,自适应打开不同环境同一路由 PATH:
如何注册
小程序注册
在小程序项目里面,App() 函数用来注册一个小程序。接受一个 Object 参数,其指定小程序的生命周期回调等。
示例代码:
App({ onLaunch(options) { }, globalData: 'I am global data'})
复制代码
cml 注册程序
示例代码:
<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>
复制代码
细心的你会发现,
小程序中app.json app.js app.wxss和 src/app/app.cml的对应关系如下:
小程序注册页面
在小程序项目里面,Page(Object) 函数用来注册一个页面。接受一个 Object 类型参数,其指定页面的初始数据、生命周期回调、事件处理函数等。
示例代码:
Page({ data: { text: 'This is page data.' }, changeText: function(e) { this.setData({ text: 'CML' }) }})
复制代码
cml 注册页面
示例代码:
<script>class Index { data = { text: 'Chameleon' } methods = { changeText: function(e) { this.text = 'CML'; } } computed = {} watch = {}};export default new Index();</script>
复制代码
小程序注册组件
在小程序项目里面,
Component(Object) 构造器可用于定义组件,调用 Component 构造器时可以指定组件的属性、数据、方法等。
示例代码:
Component({ properties: { myProperty: { type: String, value: '' }, myProperty2: String }, data: { text: '' },
attached() { }, ready() { }, methods: { onMyButtonTap() { this.setData({ text: 'wx' }) } }})
复制代码
cml 注册组件
示例代码:
<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>
复制代码
如何声明生命周期
统一各端应用生命周期的定义,是跨端框架的重要组成,也是迁移的必经之路。
小程序声明生命周期
可以在 App(Object)、Page(Object)、Component(Object) 传入Object参数,其指定小程序的生命周期回调等。
代码示例:
Page({ onLoad(options) { }, onReady() { }, onShow() { }, onHide() { }, onUnload() { }, onShareAppMessage() { }})
复制代码
cml 声明生命周期
在.cml 文件 <script /> 代码块返回的对象实例,其指定生命周期回调
示例代码:
<script>class Index { beforeCreate(query) { console.log('App beforeCreate: 打开当前页面路径中的参数是 ', query) } created() { console.log('App created') } beforeMount() { console.log('App beforeMount') } mounted() { console.log('App mounted') } beforeDestroy() { console.log('App beforeDestroy') } destroyed() { console.log('App destroyed') }};export default new Index();</script>
复制代码
App 生命周期映射
小程序 app.js中的生命周期 -> cml src/app/app.cml
Page 生命周期映射
小程序 Page()中的生命周期 -> cml src/pages/mypage/mypage.cml
Component 生命周期映射
小程序 Component()中的生命周期 -> cml src/components/mycomponent/mycomponent.cml
生命周期总结
每个 cml 实例(App、Page、Component)在被创建时都要经过一系列的初始化过程。
例如,需要设置数据监听、编译模板、将实例挂载到 CML节点 并在数据变化时更新 CML节点 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给开发者在不同阶段添加自己的代码的机会。
cml 为App、页面Page、组件Component 提供了一系列生命周期事件,保障应用有序执行。
另外,如果你想使用某个端特定的生命周期,你可以从业务出发使用生命周期多态。
数据如何响应到视图
如今,双向数据绑定 &单向数据流 已深入开发者日常,MVMM 开发模式算是框架标配。
数据绑定、条件渲染、列表渲染 是如何书写的呢?
示例代码
小程序使用数据响应
<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({ data: { message: 'Hello MINA!', view: 'MINA', array: [1, 2, 3, 4, 5] }, onLoad() { this.setData({ message: 'wx' }) }})
复制代码
cml 使用数据响应
<template><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运行时框架 提供了跨端响应式数据绑定系统(Data binding),当做数据修改的时候,只需要在逻辑层修改数据,视图层就会做相应的更新。
只需要将 view<-->model 交互部分逻辑,作简单迁移,便可使它成为跨多端的数据响应系统。
事件交互
cml 支持一些基础的事件,保障各端效果(类型、绑定、事件对象)一致运行。
示例代码
小程序使用事件
<view id="tapTest" data-hi="WeChat" bindtap="tapName">Click me!</view>
复制代码
Page({ tapName(event) { console.log(event) }})
复制代码
cml 使用事件
<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 并不会限制你的自由发挥,你可以从业务出发使用 组件多态 或者 接口多态 差异化实现功能。
布局和外观
各端描述 布局和外观 的层叠样式表(CSS)实现存在差异,包括不限于 布局、盒模型、定位、文本。
所以, cml 框架内置跨端一致性基础样式能力。
并且,定义了用于描述页面的样式规范CMSS(Chameleon Style Sheet)。
如何导入外部样式
使用 @import 语句可以导入外联样式表,@import 后跟需要导入的外联样式表的相对路径,用 ; 表示语句结束。
小程序导入外部样式
示例代码:
@import "common.wxss";.middle-p { padding:15px;}
复制代码
cml 导入外部样式
详细文档:https://cmljs.org/doc/api/runtime/@import.html
示例代码:
.small-p { padding: 5px;}
复制代码
样式使用总结
同时,为了统一多端尺寸单位,呈现效果一致,同时页面响应式布局,cml 项目统一采用 cpx 作为尺寸单位,规定以屏幕 750px(占满屏幕)视觉稿作为标准。
而且,各端样式表拥有的能力不尽相同,是项目迁移的主要阵地之一。
另外,如果你想要使用某个端特定的样式能力,cml 并不会限制你的自由发挥,你可以从业务出发使用样式多态
注意:由于 chameleon 应用是 跨多端web native 小程序框架,如果需要跨native,必须使用 flexbox 进行样式布局!!!
组件
cml 项目一切皆组件。组件(Component)是视图的基本组成单元。
框架为开发者提供了一系列基础组件,开发者可以通过组合这些基础组件进行快速开发。
如:
<template> <view> <view>view 基础组件</view> <text>text 基础组件</text> </view></template>
复制代码
同时,cml 支持简洁的组件化编程。
自定义组件
开发者可以将页面内的功能模块抽象成自定义组件,以便在不同的页面中重复使用。自定义组件在使用时与基础组件非常相似。
如何创建自定义组件
小程序创建自定义组件
代码示例:
Component({ properties: { innerText: { type: String, value: 'default value', } }, data: { someData: {} }, methods: { customMethod() {} }})
复制代码
cml 创建自定义组件
示例代码
<script>class MyComponent { props = { 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>
复制代码
cml 使用自定义组件
代码示例:
在 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>
复制代码
如何实现父子组件事件通信
事件系统是组件间通信的主要方式之一。自定义组件可以触发任意的事件,引用组件的页面可以监听这些事件。
小程序组件通信
代码示例:
<view> <my-component bindcustomevent="onMyEvent"></my-component></view>
复制代码
Page({ methods: { onMyEvent(e) { console.log(e.detail) } }})
复制代码
<view> <button bindtap="onTap">点击这个按钮将触发“myevent”事件</button></view>
复制代码
Component({ methods: { onTap() { this.triggerEvent('customevent', {}) } }})
复制代码
cml 组件通信
代码示例:
<template> <view> <my-component c-bind:customevent="onMyEvent"></my-component> </view></template><script>class Index { methods = { onMyEvent(e) { console.log(e.detail) } }};export default new Index();</script><script cml-type="json">{ "base": { "usingComponents": { "my-component": "path/to/the/custom/component" } }}</script>
复制代码
<template> <view> <button c-bind:tap="onTap">点击这个按钮将触发“myevent”事件</button> </view></template><script>class MyComponent { methods = { onTap() { this.$cmlEmit('customevent', {}) } }};export default new MyComponent();</script><script cml-type="json"></script>
复制代码
组件使用总结
和小程序一样,cml框架 提供了大量内置组件和扩展组件,抹平多端差异,便于开发者通过组合这些组件,创建出强大的应用程序。
扩展组件需要额外引入。如:
<script cml-type="json">{ "base": { "usingComponents": { "c-dialog": "cml-ui/components/c-dialog/c-dialog" } }}</script>
复制代码
在执行 cml build 构建打包时,cml 框架 会按需打包引用的内置组件和扩展组件,为代码瘦身。
内置组件和扩展组件都是支持跨多端的,对于一些没有提供的某个端的组件,可以通过组件多态来实现。
如果希望使用小程序端的原生组件,那么可以在原生标签前加上 origin-*,cml框架会渲染原生组件参考
注意:origin-* 只能在灰度区文件中使用!!
如在 map.wx.cml 文件中使用原生地图组件 <map/>:
<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 框架提供了丰富的多态接口,可以调起各端提供的原生能力,如系统信息、元素节点信息、动画效果、本地存储、网络请求、地理位置等。请参考 API 文档。
chameleon-api提供的接口都是支持跨多端的,对于一些没有提供的某个端的原生接口,可以通过接口多态来调用。
迁移实例
下面给出各端(vue、weex、小程序)迁移cml指南 以及 cml 导出组件到各端指南的具体迁移文档:
更多内容,请关注前端之巅。
评论 1 条评论