之前两篇我们分别谈到了对无线电商动态化的理解,以及我们自己的技术方案:Weex,都比较宏观和抽象,今天我们分享一些其中的技术细节
data-binding
首先要介绍一下我们是怎么做到数据绑定的,首先,我们对上层撰写的代码遵循了传统的 mustache 书写习惯。即:
<template>
<text style="color: {{mainColor}}">{{title}}</text>
</template>
<script>
module.exports = {
mainColor: '#FF9900',
title: 'Hello Weex!'
}
</script>
我们的 transformer 对这样的语法的解析思路非常简单直接,即:
- 不含 mustache 语法的值,直接做为最终返回值
- 含 mustache 语法的值 (如
{{xxx}}
),转换为:function () {return xxx}
所以上面的模板最终会被转换成:
{
type: 'text',
style: {
color: function () {
return this.mainColor
}
},
content: function () {
return this.title
}
}
在客户端的 JavaScript 引擎中,我们会进行这样的判断:
// parent, key, value, updater, data
if (typeof value === 'function') {
updater = value
parent._bindKey(data, null, key, updater)
}
else {
data[key] = value
}
只要是函数类型,就进行数据绑定,否则一次性赋值,简单易懂易用。
这样在相应的数据发生改写的时候,界面结构和样式会通过 _bindKey
和 updater
自动触发更新。
关于如何在 JavaScript 上实现数据监听,也算是一个很重要的细节,不过市面上已经有大把优秀的实现案例了,我们也没有在这个环节上做特别的事情,所以不做赘述。
transformer
在 transformer 中,我们主要的工作就是对 HTML、CSS、JavaScript 代码进行解析和重组。这里我们用到了三个非常重要的库:
- HTML 解析工具:htmlparser
- CSS 解析工具:cssom
- JavaScript 解析工具:uglify-js
用 htmlparser 可以把 HTML 转换成 JSON
var fragment
var handler = new htmlparser.DefaultHandler(function (err, data){
fragment = data
})
var parser = new htmlparser.Parser(handler)
parser.parseComplete(content)
...
用 cssom 可以把 CSS 转换成对象供二次处理
var css = cssom.parse(content)
var rules = css.cssRules
rules.forEach(function (rule) {
rule.selectorText
rule.style
...
})
用 uglify-js 可以把 JavaScript 代码进行细节解析处理
// 遍历语法树
new uglifyjs.TreeWalker(function(node, descend) {...})
...
// 格式化代码
uglifyjs.parse(code)
...
而且因为有了 transformer,我们可以把传统 mvvm 需要在客户端甚至 DOM 上完成的模板解析、数据绑定语法解析等工作提前处理完毕。所以免去了客户端运行时现解析模板源文件的负担。更酷的是,因为模板解析是不依赖真实 DOM 的,所以我们可以大大方方的把语法设计成 <img src="{{xxx}}">
而不必担心任何副作用。而高级的表达式、过滤器等数据绑定语法也都可以在 transformer 这一层提前处理好,这样在撰写体验持续增强的情况下,运行时并不会产生额外的负担——这都要归功于我们引入了这一层 transformer
debugger tools
我们为 native 界面调试设计了贴心的远程调试工具,主要解决三个痛点问题:
- 调试 JavaScript 代码,任意设置断点 debug
- 运行命令行代码 (Console) 对程序做实时的判断
- 渲染结构的树形审查
解决问题的办法是:
- 客户端设置一个开关,可以把 JS Bridge 对接到一个远程的 websocket 服务器,而不是对接到本地的 JavaScript 引擎
- 本地准备一个网页,其中运行了完整的 JavaScript 引擎的代码,并且也可以链接到一个远程的 websocket 服务器,这样客户端的 native 层和本地网页里的 JavaScript 引擎就串联起来了
- 原本通过 JS Bridge 的双向通信内容可以被 websocket 连接记录下来
- JavaScript 引擎里的所有代码都可以通过本地浏览器的开发者工具进行 debug 和 console 控制
- 开发一个简易的 Chrome Devtools Extension,可以得到 Weex 实例的界面结构,并以目录树的方式呈现出来。
这样,一个客户端开关,一个 websocket 服务器,一个本地的 JavaScript 引擎页面,一个开发者工具扩展,我们就实现了 Weex 的远程调试。
unit test and ci
Weex 的 JavaScript 引擎作为一个相对底层的项目,品控需要做到非常严格和极致,否则一个小小的失误在客户端长期运行之后都有可能带来灾难性的后果。
本次 Weex 的开发当中,我们认真实践了基于 mocha、chai 和 sinon 的单元测试,每个源代码的文件夹都放了一个名叫 __test__
的文件夹,里面放了这个目录下所有同名的 JavaScript 文件,每个文件的内容都是当前文件夹内对应文件的测试用例。
在项目中后期,我们还引入了集团内部的一个 ci 系统,每次开发新功能,先开一个分支,然后写测试用例,最后进行代码实现知道跑通所有的 test case,搞定之后发起 merge request,ci 系统会自动在线上运行所有的回归测试,再次验证其正确性和各项指标。
其实有关项目品控的话题还有很多,我们也在逐渐实践当中。
isomorphic (同构)
我们已经在内部版本实现了简单的服务端渲染,有了这个东西会怎样呢?
一旦我们可以在服务端直接渲染出界面的 JSON 结构,客户端就可以绕过 JavaScript 解析过程,直接根据 JSON 结构把界面渲染出来。与其对应的逻辑控制稍后也会初始化好,并和界面效果最终保持同步,但在整个过程中,首屏加载的时间会进一步缩短。而且,更令人兴奋的是,如果当前界面刚好没有交互逻辑,甚至后期的 JavaScript 也不需要参与了,这是一条更短的链路!
反哺 HTML5
通过对 Weex 技术方案的探索,我们把组件化开发、transformer 机制、同构等理念反哺到 HTML5 开发,会有什么样的惊喜呢?
那就是一个极简的针对无线前端的 HTML5 MVVM 库:我暂时取名叫做 v.js
v.js 的名字来自著名的 MVVM 库 Vue.js,我希望这个库更适合移动端,目前它可以具备所有 MVVM 的核心功能,通过 transformer 提前解析模板,运行时更快速,体积是 Vue.js 的 1/3,而且支持服务端的同构。这些都是基于移动端现状的二次改进,目前还在细节构思和研发当中。希望不久的将来可以跟大家见面。
篇幅有限,写了三篇还是觉得不够,期待和大家更多的交流。
阿里无线前端团队更多精彩的内容还在后面排队,我这边对无线电商动态化方案的思考就先写到这里了:)