作者 | 滴滴出行Chameleon团队

责编 | 郭芮

如今前端比较流行的 React Native、Weex、Flutter 等跨平台开发框架,对于开发来说属于技术方案的选择,比如,我们会考虑用这个技术开发,性能会不会超过 h5,开发效率会不会超过原生开发等等。

但是从 2017 年微信推出小程序,到至今各大厂商都推出自己的小程序,跨端开发就不仅仅是技术的问题了,其已经成了必争的流量入口。

现在的小程序大战像极了当前的浏览器大战,大战中受苦的是我们一线开发者,同样的应用要开发 N 次,面对了前所未有的挑战,所以跨端框架的产生是大趋势下的必然产物。基于此,本文中将技术性地解读一套开源的跨端标准chameleon,开发者只需要按照标准扩展流程,即可快速扩展任意 MVVM 架构模式的终端,并让已有项目无缝运行新端。所以如果你希望让其快速支持淘宝小程序、React Native?只需按标准实现即可扩展。

跨端原理

跨端框架最核心的工作是统一,chameleon 定义了标准的跨端协议,通过编译时+运行时的手段去实现各端的代码和功能,其实现原理如下图所示。

其中运行时和基础库部分利用多态协议实现各端的独立性与框架的统一性。chameleon 目前支持的端都是采用这种方式,我们定义了扩展一个新端所需要实现的所有标准,用户只需要按照这些标准实现即可完成一个新端的扩展。

跨端标准协议

我们再来看一张 chameleon 的设计图,能够实现标准化的扩展新端,得益于多态协议中对各层代码进行了接口的定义,各端代码按照接口定义进行实现,向用户代码提供统一调用,同时还提供”多态协议“让用户代码保障可维护性的前提下,直接触达各端原生能力的方式。

  • API 接口协议:定义基础接口能力标准;

  • 内置组件协议:定义基础 UI 组件标准;

  • 框架协议:定义生命周期、路由等框架标准;

  • DSL 协议:定义视图和逻辑层的语法标准;

  • 多态实现协议:定义允许用户使用差异化能力标准。

如何扩展新端?

简单来说只需要实现 6 个 npm 包。

实现 API 接口协议

chameleon-api提供了网络请求、数据存储、获取系统信息、交互反馈等方法,用户需要创建一个npm包,结构参考cml-demo-api (https://github.com/chameleon-team/cml-extplatform-demo/tree/master/packages/cml-demo-api)。将chameleon-api中提供的每个方法利用多态接口扩展 (https://cml.js.org/doc/framework/polymorphism/api_extend.html) 语法扩展新端的实现。 以扩展一个alert方法为例,chameleon-api中alert方法的接口定义文件为chameleon-api/src/interfaces/alert.interface,其中的接口定义内容如下:

<script cml-type="interface">type alertOpt = {  message: String,  confirmTitle: String}type successCallBack = (result: String) => void;type failCallBack = (result: String) => void;interface uiInterface {  alert(opt: alertOpt, successCallBack: successCallBack, failCallBack: failCallBack): void,}</script>
type alertOpt = {message: String,confirmTitle: String
}
type successCallBack = (result: String) => void;
type failCallBack = (result: String) => void;
interface uiInterface {alert(opt: alertOpt, successCallBack: successCallBack, failCallBack: failCallBack): void,
}
</script>

用户实现的interface文件中采用<include></include>语法引入chameleon-api中alert方法的 interface 文件, 实现uiInterface。

// 引入官方标准interface文件<include src="chameleon-api/src/interfaces/alert/index.interface"></include>// 扩展实现新端(以头条小程序为例,假设端扩展标识为:tt)<script cml-type="tt">class Method implements uiInterface {  alert(opt, successCallBack, failCallBack) {    // 根据头条小程序实现alert弹窗    let { message, confirmTitle} = opt;    tt.showModal({      content: message,      confirmText: confirmTitle,      ......    });  }}export default new Method();</script><include src="chameleon-api/src/interfaces/alert/index.interface"></include>
// 扩展实现新端(以头条小程序为例,假设端扩展标识为:tt)
<script cml-type="tt">
class Method implements uiInterface {alert(opt, successCallBack, failCallBack) {// 根据头条小程序实现alert弹窗let { message, confirmTitle} = opt;tt.showModal({content: message,confirmText: confirmTitle,......});}
}
export default new Method();
</script>

实现内置组件协议

组件分为内置组件chameleon-ui-builtin (https://github.com/chameleon-team/chameleon-ui-builtin) 和扩展组件cml-ui (https://github.com/chameleon-team/chameleon-ui-builtin)。所以用户需要创建两个 npm 包分别实现这两个组件库,结构参考cml-demo-ui-builtin (https://github.com/chameleon-team/cml-extplatform-demo/tree/master/packages/cml-demo-ui-builtin) 和cml-demo-ui

(https://github.com/chameleon-team/cml-extplatform-demo/tree/master/packages/cml-demo-ui)。利用多态组件扩展 (https://cml.js.org/doc/framework/polymorphism/component_extend.html) 语法,对原有组件库中的每一个组件进行新端的实现。

原有组件库中的组件也分为两种,一种为各端都有分别实现的多态组件,例如chameleon-ui-builtin中的button组件。实现起来新端基本上也是要单独实现。另一种例如chameleon-ui-builtin中的radio组件,各端的实现都是用的chameleon-ui-builtin/components/radio/radio.cml。所以新端基本也可以复用这个实现,还需要测试情况确实是否可以复用。

新端独立实现

例如:

编写 my-ui-builtin/components/button/button.interface

// 引入官方标准interface文件<include src="chameleon-ui-builtin/components/button/button.interface" />
<include src="chameleon-ui-builtin/components/button/button.interface" />

编写 my-ui-builtin/components/button/button.demo.cml

<template>  <origin-button    c-bind:tap="onclick"    open-type="{{openType}}"  </origin-button></template><script>  // js实现部分</script><style scoped>  // 样式部分</style><script cml-type="json">  // json配置</script>

独立实现的my-ui-builtin/components/button/button.demo.cml文件属于多态组件(https://cml.js.org/doc/framework/polymorphism/component.html) 的灰度层,可以调用各端底层组件和 api,具体例子参见button (https://github.com/chameleon-team/chameleon-ui-builtin/tree/master/src/components/button) 和scroller (https://github.com/chameleon-team/chameleon-ui-builtin/tree/master/src/components/scroller) 的实现。

新端复用现有组件

编写 my-ui-builtin/components/radio/button.interface

// 引入官方标准interface文件<include src="chameleon-ui-builtin/components/radio/radio.interface"></include>// 复用官方的实现<script cml-type="demo" src="chameleon-ui-builtin/components/radio/radio.cml"></script><include src="chameleon-ui-builtin/components/radio/radio.interface"></include>
// 复用官方的实现
<script cml-type="demo" src="chameleon-ui-builtin/components/radio/radio.cml"></script>

实现 DSL 协议(编译时插件)

chameleon 内部会将整个项目文件编译为如下编译图结构,节点中的内容经过了标准编译,比如script节点的babel处理,style节点的less与stylus处理等等。

节点的数据结构如下:

class CMLNode {  constructor(options = {}) {    this.realPath; // 文件物理地址  会带参数    this.moduleType; // template/style/script/json/asset    this.dependencies = []; // 该节点的直接依赖       app.cml依赖pages.cml pages.cml依赖components.cml js依赖js    this.childrens = []; // 子模块 cml文件才有子模块    this.source; // 模块源代码    this.output; // 模块输出  各种过程操作该字段    ......  }}constructor(options = {}) {this.realPath; // 文件物理地址  会带参数this.moduleType; // template/style/script/json/assetthis.dependencies = []; // 该节点的直接依赖       app.cml依赖pages.cml pages.cml依赖components.cml js依赖jsthis.childrens = []; // 子模块 cml文件才有子模块this.source; // 模块源代码this.output; // 模块输出  各种过程操作该字段......}
}

用户只需要实现一个编译插件类 (https://github.com/chameleon-team/cml-extplatform-demo/blob/master/packages/cml-demo-plugin/index.js),利用钩子方法实现对节点的编译,所有节点编译完后再进行文件的组织。编译类如下:

module.exports = class DemoPlugin {  constructor(options) {    ......  }  /**   * @description 注册插件   * @param {compiler} 编译对象   * */  register(compiler) {    //编译script节点,比如做模块化    compiler.hook('compile-script', function(currentNode, parentNodeType) {    })    // 编译template节点 语法转义    compiler.hook('compile-template', function(currentNode, parentNodeType) {    })    // 编译style节点  比如尺寸单位转义    compiler.hook('compile-style', function(currentNode, parentNodeType) {    })    // 编译结束进入打包阶段    compiler.hook('pack', function(projectGraph) {      // 遍历编译图的节点,进行各项目的拼接      // 用writeFile方法写入文件      // compiler.writeFile()    })    ......  }}class DemoPlugin {constructor(options) {......}/*** @description 注册插件* @param {compiler} 编译对象* */register(compiler) {//编译script节点,比如做模块化compiler.hook('compile-script', function(currentNode, parentNodeType) {})// 编译template节点 语法转义compiler.hook('compile-template', function(currentNode, parentNodeType) {})// 编译style节点  比如尺寸单位转义compiler.hook('compile-style', function(currentNode, parentNodeType) {})// 编译结束进入打包阶段compiler.hook('pack', function(projectGraph) {// 遍历编译图的节点,进行各项目的拼接// 用writeFile方法写入文件// compiler.writeFile()})......}
}

实现框架协议

运行时主要是对 cml 文件的逻辑对象进行适配,chameleon 内部将 cml 文件的逻辑对象分为三类 App、Page、Component。对应会调用用户运行时 npm 包的createApp、createPage、createComponent方法,所以对外只需要实现这三个方法。

例如一个 Page 的逻辑对象如下:

class PageIndex {  data = {    name: 'chameleon'  }  computed = {    sayName () {      return 'Hello' + this.name;    }  }  mounted() {  }}export default new PageIndex();data = {name: 'chameleon'}computed = {sayName () {return 'Hello' + this.name;}}mounted() {}
}
export default new PageIndex();

编译时就会自动插入cml-demo-runtime处理逻辑对象的方法:

class PageIndex {  ......}export default new PageIndex();// 编译时自动插入用户配置的运行时方法import {createPage} from 'cml-demo-runtime';createPage(exports.default);......
}
export default new PageIndex();// 编译时自动插入用户配置的运行时方法
import {createPage} from 'cml-demo-runtime';
createPage(exports.default);

createApp、createPage、createComponent方法,参考cml-demo-runtime (https://github.com/chameleon-team/cml-extplatform-demo/tree/master/packages/cml-demo-runtime) 的结构进行实现,需要include chameleon-runtime中相应的接口进行实现,才能够实现对chameleon-runtime的扩展。用户的工作量主要在于对逻辑对象的处理,可以参考chameleon-runtime (https://github.com/chameleon-team/chameleon-runtime/tree/master/src/interfaces) 中的实现方式,一般需要大量的适配工作,比如输入Options对象的适配、跨端运行时能力注入等等。

例如: createPage 方法的实现

<include src="chameleon-runtime/src/interfaces/createPage/index.interface"></include><script cml-type="demo">  class Method implements createPageInterface {    createPage(options) {      // 各端自行实现adapter      adapter(options);      //例如调用小程序原生页面构造函数      Page(options);      return {};    }  }  export default new Method();</script></include>
<script cml-type="demo">
  class Method implements createPageInterface {createPage(options) {// 各端自行实现adapteradapter(options);//例如调用小程序原生页面构造函数Page(options);return {};}}export default new Method();
</script>

扩展新端 demo 示例仓库:https://github.com/chameleon-team/cml-extplatform-demo。实现了微信端的基本扩展,用户可以以此为模板

25岁转行人工智能靠谱吗?

https://edu.csdn.net/topic/ai30?utm_source=csdn_bw

进行开发,也期待更多的人加入开源。

注:本文涉及的示例代码均是伪代码。


作为码一代,想教码二代却无从下手:

听说少儿编程很火,可它有哪些好处呢?

孩子多大开始学习比较好呢?又该如何学习呢?

最新的编程教育政策又有哪些呢?

下面给大家介绍CSDN新成员:极客宝宝(ID:geek_baby)

戳他了解更多↓↓↓

 热 文 推 荐 

☞不得了!这个 AI 让企业家、技术人员、投资人同台“互怼”

史上第一代图形浏览器往事

5G NR 标准:下一代无线通信技术

如何使用 Firefox 阻止指纹识别的侵扰?

☞谷歌临时工达 12 万,外包程序员的出路在哪里?

☞直接拿来用!灵跃模组机器人硬核评测(编程篇)

☞IEEE 回应禁止华为系审稿人;WiFi联盟、蓝牙联盟已恢复华为成员资格;中国计算机学会:暂时中止与IEEE通信学会合作……

☞敲诈团伙将黑手伸向宅男, 你在家看不可描述的视频, 竟被骗走100万美元!

☞各方最新回应!如何看待IEEE官方声明“学术禁令”?

☞代码整洁之道-编写 Pythonic 代码

☞敲代码时,程序员戴耳机究竟在听什么?

点击阅读原文,输入关键词,即可搜索您想要的 CSDN 文章。

你点的每个“在看”,我都认真当成了喜欢

直接拿来用!前端如何快速实现跨平台开发?相关推荐

  1. 前端开发者快速入门安卓开发(安卓嵌入式开发课程经验总结)

    目录 1.安卓开发结构 2.xml 3.java 使用xml的标签 Bitmap 4.开发注意 1.安卓开发结构 安卓开发和前端有异曲同工之处,只是js换成了Java,它的界面层是xml格式的,布局全 ...

  2. 前端如何快速上手 Web 3D 游戏的开发

    简介: 本文以「余额宝3D跑酷游戏」为例,介绍了前端如何快速上手 Web 3D 游戏的开发. 作者 | RichLab楺楺 诚空 本文以「余额宝3D跑酷游戏」为例,介绍了前端如何快速上手 Web 3D ...

  3. 在Web 3D 游戏开发的前端如何快速上手进行

    本文以「余额宝3D跑酷游戏」为例,介绍了前端如何快速上手 Web 3D 游戏的开发.跑酷游戏是余额宝七周年的主玩法,用户通过做任务来获取玩游戏的机会并且解锁游戏道具,从而在游戏中获得更多的金币,最终可 ...

  4. WijmoJS 2019V1正式发布:全新的在线 Demo 系统,助您快速上手,开发无忧

    2019独角兽企业重金招聘Python工程师标准>>> 下载WijmoJS 2019 v1 WijmoJS是为企业应用程序开发而推出的一系列包含HTML5和JavaScript的开发 ...

  5. Python+Dash快速web应用开发——基础概念篇

    作者:费弗里 来源:Python大数据分析 ❝本文示例代码与数据已上传至https://github.com/CNFeffery/DataScienceStudyNotes ❞ 1 简介 这是我的新系 ...

  6. 《JavaScript快速全栈开发》作者Azat Mardanov:现在是拥抱Node技术栈的最佳时机

    非商业转载请注明作译者.出处,并保留本文的原始链接:http://www.ituring.com.cn/article/195742 Azat Mardanov是一位有着12年开发经验的资深软件工程师 ...

  7. SpringMVC学习(二)——快速搭建SpringMVC开发环境(注解方式)

    文章目录 说明 1.工程搭建 2.注解配置 2.1.context:annotation-config说明 2.2.context:component-scan配置说明 2.3.mvc:annotat ...

  8. django 套vue 模板_Vue admin template + Django 快速进行Web开发

    本文教大家如何使用Vue admin template和Django快速进行Web开发,旨在帮助我们使用现有的工具.框架及开源UI,让我们在基础较为薄弱的情况下,能进行Web开发.本文不会介绍过多的原 ...

  9. 前端实战:electron+vue3+ts开发桌面端便签应用

    前端时间我的一个朋友为了快速熟悉 Vue3 开发, 特意使用 electron+vue3+ts 开发了一个桌面端应用, 并在 github 上开源了, 接下来我就带大家一起了解一下这个项目, 在文章末 ...

最新文章

  1. 构建高可用服务器之二 Keepalive参数详解
  2. java面试题,将String字符串转换成数字
  3. HDU 1010 Tempter of the Bone DFS(奇偶剪枝优化)
  4. [组件] TopN 排行榜
  5. 贪心(数据结构):COGS 468. [NOI2010]超级钢琴
  6. 【Python】python3编码方式encode介绍
  7. 直线算法(Bresenham)
  8. jqTransform表单美化
  9. Pulling without specifying how to reconcile divergent branches is hint: discouraged. You can squelch
  10. 好消息:部分银行磁条卡更换芯片卡
  11. 区块链/BlockChain+ProofOfWork
  12. 、HTML“计算机输出”标签codekbdsampttvarpre
  13. 波数与波长 matlab,Matlab求解方同轴波导的截止波长和特性阻抗
  14. ZCMU--1930: 帽子戏法(C语言)
  15. 跳出IT运维“死循环” 看河南省统计局如何“运”筹“维”幄
  16. 全志A10s datasheet,A10s 数据手册,A10s规格书
  17. spring:Bean作用域
  18. 我走了,青春留给北京
  19. 翻译:监控生产中的机器学习模型
  20. python热成像_3D热成像技术

热门文章

  1. CentOS 删除OpenJDK并安装OracleJDK
  2. [Linux] 编译 与 链接
  3. 二叉树——基本知识+python实现
  4. 《推荐系统笔记(十二)》聚类生成标签以及基于标签的TopN推荐
  5. redis笔记3 持久化、管道、事务、发布订阅和内存回收
  6. 2021年中国一氧化碳传感器市场趋势报告、技术动态创新及2027年市场预测
  7. 电磁冷坩埚行业调研报告 - 市场现状分析与发展前景预测(2021-2027年)
  8. Serverless 崛起背后的五大挑战
  9. 用包管理python代码,提高开发效率
  10. 解决linux中xorg占用gpu问题