amfe / article

7.58k stars 1.07k forks source link

对无线电商动态化方案的思考(三) #15

Open Jinjiang opened 9 years ago

Jinjiang commented 9 years ago

之前两篇我们分别谈到了对无线电商动态化的理解,以及我们自己的技术方案:Weex,今天我们分享一些其中的技术细节

data-binding

首先要介绍一下我们是怎么做到数据绑定的,首先,我们对上层撰写的代码遵循了传统的 mustache 书写习惯。即:

<template>
  <text style="color: {{mainColor}}">{{title}}</text>
</template>
<script>
  module.exports = {
    mainColor: '#FF9900',
    title: 'Hello Weex!'
  }
</script>

我们的 transformer 对这样的语法的解析思路非常简单直接,即:

所以上面的模板最终会被转换成:

{
  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
}

只要是函数类型,就进行数据绑定,否则一次性赋值,简单易懂易用。

这样在相应的数据发生改写的时候,界面结构和样式会通过 _bindKeyupdater 自动触发更新。

关于如何在 JavaScript 上实现数据监听,也算是一个很重要的细节,不过市面上已经有大把优秀的实现案例了,我们也没有在这个环节上做特别的事情,所以不做赘述。(p.s. 在 Weex 的 JS Framework 中,我们复用了 Vue.js 的数据监听 (observer/*.js) 和依赖收集 (watcher.js) 的代码实现,在此特别要感谢一下!)

transformer

在 transformer 中,我们主要的工作就是对 HTML、CSS、JavaScript 代码进行解析和重组。这里我们用到了三个非常重要的库:

用 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 界面调试设计了贴心的远程调试工具,主要解决三个痛点问题:

  1. 调试 JavaScript 代码,任意设置断点 debug
  2. 运行命令行代码 (Console) 对程序做实时的判断
  3. 渲染结构的树形审查

解决问题的办法是:

devtools

图:远程调试工具工作原理

  1. 客户端设置一个开关,可以把 JS Bridge 对接到一个远程的 websocket 服务器,而不是对接到本地的 JavaScript 引擎
  2. 本地准备一个网页,其中运行了完整的 JavaScript 引擎的代码,并且也可以链接到一个远程的 websocket 服务器,这样客户端的 native 层和本地网页里的 JavaScript 引擎就串联起来了
  3. 原本通过 JS Bridge 的双向通信内容可以被 websocket 连接记录下来
  4. JavaScript 引擎里的所有代码都可以通过本地浏览器的开发者工具进行 debug 和 console 控制
  5. 开发一个简易的 Chrome Devtools Extension,可以得到 Weex 实例的界面结构,并以目录树的方式呈现出来。

这样,一个客户端开关,一个 websocket 服务器,一个本地的 JavaScript 引擎页面,一个开发者工具扩展,我们就实现了 Weex 的远程调试。

devtools-screenshot

图:开发者工具的审查元素功能扩展

unit test 和 ci 实践

Weex 的 JavaScript 引擎作为一个相对底层的项目,品控需要做到非常严格和极致,否则一个小小的失误在客户端长期运行之后都有可能带来灾难性的后果。

本次 Weex 的开发当中,我们认真实践了基于 mocha、chai 和 sinon 的单元测试,每个源代码的文件夹都放了一个名叫 __test__ 的文件夹,里面放了这个目录下所有同名的 JavaScript 文件,每个文件的内容都是当前文件夹内对应文件的测试用例。

_2015-11-18_19_08_07 _2015-11-18_19_08_17

图:所有的源文件都在同级目录下有一个 __test__ 文件夹放相应的测试代码

在项目中后期,我们还引入了集团内部的一个 ci 系统,每次开发新功能,先开一个分支,然后写测试用例,最后进行代码实现知道跑通所有的 test case,搞定之后发起 merge request,ci 系统会自动在线上运行所有的回归测试,再次验证其正确性和各项指标。

network_graph_-_weapp-plus___js-framework___gitlab

图:项目分支管理

cise

图: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,而且支持服务端的同构。这些都是基于移动端现状的二次改进,目前还在细节构思和研发当中。希望不久的将来可以跟大家见面。

<template>
  <div>
    <img src="http://www.taobao.com/favicon.ico" width="32" height="32" onclick="foo"></img>
    <span style="color: red;">Hello{{name}}!</span>
  </div>
</template>

<style>
  .title {color: red;}
</style>

<script>
  module.exports = {
    data: {
      title: 'World'
    },
    methods: {
      foo: function (e) {
        this.title = 'V'
      }
    }
  }
</script>

基于 v.js 的源文件

vjs-demo

v.js 的展示效果

{
  "ref":"0","type":"root","attr":{},"style":{},
  "children":[
    {"ref":"1","type":"div","attr":{},"style":{},"component":"...",
      "children":[
        {"ref":"2","type":"image","attr":{"height":"32","width":"32","src":"http://www.baidu.com/favicon.ico"},"style":{},"event":["click"]},
        {"ref":"3","type":"span","attr":{"value":"HelloWorld!"},"style":{"color":"red"}}
      ]
    }
  ]}

同构的 JSON 数据,方便 native 快速渲染

<root ref="0">
  <div ref="1">
    <image ref="2" height="32" width="32" src="http://www.baidu.com/favicon.ico" onclick="$fire"></image>
    <span ref="3" value="HelloWorld!" style="color: red"></span>
  </div>
</root>

同构的 HTML 代码,方便浏览器快速渲染

篇幅有限,写了三篇还是觉得不够,期待和大家更多的交流我晚上再针对大家频繁问及或感兴趣的问题做一个整理欢迎继续阅读大家近期针对 Weex 的常见问题汇总整理

阿里无线前端团队更多精彩的内容还在后面排队,我这边对无线电商动态化方案的思考就先写到这里了:)

Jinjiang commented 9 years ago

@dolmens 全部会转换成 JS 或 JSON,客户端只有 JS Engine,不需要做 HTML 和 CSS 的解析

Jinjiang commented 9 years ago

@yutingzhao1991 好问题,我个人的看法是 webkit 的优化侧重点主要还是沿袭了 PC 端的一些典型场景,也许未来会发生一些改变,但是这个节奏比想象中慢得多,而且 iOS 下只有他自家的 webkit 可以使用,我们无法控制这件事情

yutingzhao1991 commented 9 years ago

@Jinjiang 是啊,记得12年的时候html5火得一塌糊涂,最后呢?发现还是要走native。有时候还是要务实啊,当年的html5就是太执着于web技术了,但是最终的决定权还是在用户还是在整个生态环境。目前感觉这个方向是靠谱的,web的开发模式+各个端各自的组件view。也许某一天ios和andriod官方都借鉴并支持目前web前端的这种更有效的开发模式了。 JS >= ObjectC + Java + ...... :D

rocman commented 9 years ago

弱弱地问,weex 应该怎么发音?

whq731 commented 9 years ago

@rocman we 克斯

Hi-Rube commented 9 years ago

问一下 加入 v8 后对客户端的体积有什么影响

如何同一个 URL 或二维码,客户端请求到的是 JS Bundle,而浏览器打开的是一个 HTML5 页面 // 判断来路和根据特定规则进行跳转不行么?

AliThink commented 8 years ago

感谢分享,对于服务器js更新后该如何下发到客户端,并对比出需要更新的部分进行局部刷新这块比较感兴趣,这里weex是如何做的呢?借鉴了virtual dom吗?

qqqzhch commented 8 years ago

我没理解错的这个transformer 1 如果运行在uc这样的浏览器里面 相当于是服务器端渲染 2 如果运行在手机淘宝里面的web view 是app原生渲染 是这样吗?

Jinjiang commented 8 years ago

@qqqzhch transformer 会把 template, style, script 都转换成一段段 json 或 js,这样客户端只运行 js,不必同时解析 html/css 这些语法,你这里提的服务端渲染,出来的应该是个完整的 virtual dom 了,但是这里的 js 还会继续进行数据监听和绑定,然后才是生成最终的 virtual dom 再发送给 native 端渲染

miniflier commented 8 years ago

"一旦我们可以在服务端直接渲染出界面的 JSON 结构,客户端就可以绕过 JavaScript 解析过程,直接根据 JSON 结构把界面渲染出来。与其对应的逻辑控制稍后也会初始化好,并和界面效果最终保持同步,但在整个过程中,首屏加载的时间会进一步缩短。而且,更令人兴奋的是,如果当前界面刚好没有交互逻辑,甚至后期的 JavaScript 也不需要参与了,这是一条更短的链路!"

这个地方请教一下,客户端什么情况下会拿到JSON,什么情况下拿到JS呢? 对于有动态业务逻辑的需求,肯定是需要用JS。但是仅仅是动态UI,是不是只用JSON就能解决问题?

Jinjiang commented 8 years ago

@miniflier 等于先把初始化的virtual dom先生成优先加载并渲染,然后再把动态逻辑“补”上去,具体实现是有一些窍门的,已经不是什么行业秘密了

jellychen commented 8 years ago

我觉得weex的项目的确是一个KPI的项目!

从双十一来看只是非常简单的页面再用,说实在的只是对电商的一些些(很少的页面有作用),其次由于事件跨线程的延迟 会导致事件处理的操作延迟到界面渲染上面去,这增加了 从事件发出到UI响应的时间。是一种巨大的性能的倒退的做法!!! 其实JavaScript的性能不是很高,采用大量的js的代码运行维持Native的界面运行,对内存和CPU都是浪费!

其次week一起来就拉起 排版线程(注意是排版线程)和JSCore的线程,无端端就增加了好几个线程(由于JSCore自己还会起线程),这不得不说是一个性能的倒退和浪费。其次很多事件不支持,手势不支持(由于手势是持续的事件反馈,跨线程走message队列显然做不到及时响应)。在IOS下这种性能还说的过去,在安卓上面这种东西不得不说有点垃圾!!非常典型的不计较开销的做法!

最后总结下 为了实现一些些 很少的界面的动态化+配合web前端的开发胃口(其实为了页面动态化,完全可以利用java+网络+lua实现这个东西,而且性能更好)。简单的上线双十一的很简单的页面是不具备说服力的(当然忽悠上层不懂技术老板是可行的,(一下子跨三端,一次编写导出运行 多么诱人!!))。

所以说我个人认为这是一个KPI项目!

@@@在说说RN,这是一个很坑的项目。native代码风格和规范来说说是facebook发布的 吓一跳(对比Google的Chorme的代码),原来美国佬也有这么随便写程序的!我以为只有我天朝才有!RN的话和Weex同理。从问题的出发点就是错的!Web前端的确很多人在用innerHtml的方式在更新dom,所以需要virtualDom 需要diff 这个无可厚非,思想还是挺牛的不得不承认。但是问题出现在ios和android前端并不存在这个问题~ 你简单的把web前端的问题假设成移动设备native前端的问题,然后给出同样的解决方式是不是太过于坑了!

@@@有很多种可以做到移动端native的及时性上线变动(非app版本替换)的方法。兼容三端确实不简单,但是RN不是一种好的解决方案,如果非要说RN是一种好的解决方案,只能说他对web前端的意义还是有的。

国内现在一阵大风说RN这个好 那个好!是不是真的符合大家的场景,还是一味的跟风做KPI项目!

jellychen commented 8 years ago

鄙人 去年一直在用 淘宝的app 在魅族的魅蓝手机上面! 2G内存! 淘宝的APP 那是卡的感动人。IOS倒是还可以!寄希望于IOS可以早日吃点安卓的市场,说不定就可以从机器的角度规避RN此类框架的性能问题

jellychen commented 8 years ago

5年前就听说 性能不是问题,机器愈来愈好,到今天这个CPU过剩时代,我们还是搞出了性能问题,在厉害的硬件也抵不住操蛋的程序!

jellychen commented 8 years ago

还有多个weex实例公用一个jscore的话,那么一直不释放这个jscore的确性能有加强。但是谁能保证前端代码没有内存泄露,如果泄露了那就呵呵了! 这个也是一个坑!

RubyLouvre commented 8 years ago

其实阿里他们就是炒红了vue.js,来带动炒作他们的weex!

data-farmer commented 8 years ago

只能说真的写的好多

mejustme commented 8 years ago

读读,视野打开更多。

0326 commented 8 years ago

最后在 iOS 下选用了总耗时最短的一次性接受所有 Virtual DOM 的策略,而 Android 选用了按“楼层”分段加载 Virtual DOM 的策略,这样的话 Android 的总加载时间其实变得更长了,但是首屏渲染速度大大提高.

我说怎么安卓的加载时间就是比iOS的长,还以为是苹果机高端些,原来是加载策略的问题~ 另外很好奇这个按“楼层”分段加载Virtual DOM是如何实现的?

jp1017 commented 7 years ago

:smile: :+1:

wendaoji commented 7 years ago

@jellychen 我昨天为了买个东西(在6s上),先用浏览器访问淘宝,然后动不动就提示错误,没招了,只能安装淘宝客户端,看了一眼,190多m,不知道是不是用weex做的,没有感觉到什么好的体验。(我们在用vuejs)

ideal commented 6 years ago

请问最后的例子里,为什么不是:

<span style="color: red;">Hello{{title}}!</span>

?