前端测试回顾及我们为什么选择 Karma
In 未分类 on 2015年06月08日 by view: 4,197
0

前端测试,或者 UI 测试一直是业界一大难题。最近 Q.js 使用 Karma 作为测试任务管理工具,本文在回顾前端测试方案的同时,也分析下为什么 Q.js 选用 Karma 而不是其他测试框架。

像素级全站对比

曾今有一批人做过这样的 UI 测试,即最终页面图像是否符合预期,通过图片差异对比来找出可能的问题。

如图所示,所谓像素级站点对比,即利用截屏图像前后对比来找出,站点前后差异,从而发现问题。

React 直出实现与原理
In 未分类 on 2015年05月04日 by view: 2,426
1

前一篇文章我们介绍了虚拟 DOM 的实现与原理,这篇文章我们来讲讲 React 的直出。
比起 MVVM,React 比较容易实现直出,那么 React 的直出是如何实现,有什么值得我们学习的呢?

为什么 MVVM 不能做直出?

对于 MVVM,HTML 片段即为配置,而直出后的 HTML 无法还原配置,所以问题不是 MVVM 能否直出,而是在于直出后的片段能否还原原来的配置。下面是一个简单的例子:

上面这段 HTML 配置和数据在一起,直出后会变成:

这时候当我们失去了 name 的值改变的时候会导致页面渲染这个细节。当然,如果为了实现 MVVM 直出我们可能有另外的方法来解决,例如直出结果变成这样:

这时候我们是可以把丢失的信息找回来的,当然结构可能和我们想象的有些差别。当然还有其他问题,例如直出 HTML 不一定能反向还原数据,由于篇幅问题,这里不展开讨论。

React 如何直出?

2

如图:

  • React 的虚拟 DOM 的生成是可以在任何支持 Javascript 的环境生成的,所以可以在 NodeJS 或 Iojs 环境生成
  • 虚拟 DOM 可以直接转成 String
  • 然后插入到 html 文件中输出给浏览器便可

具体例子可以参考,https://github.com/DavidWells/isomorphic-react-example/,下面是其渲染路由的写法:

OK,我们现在知道如何利用 React 实现直出,以及如何前后端代码复用。

但还有下面几个问题有待解决:

  • 如何渲染文字节点,每个虚拟 DOM 节点是需要对应实际的节点,但无法通过 html 文件生成相邻的 Text Node,例如下面例子应当如何渲染:

  • 如何避免直出的页面被 React 重新渲染一遍?或者直出的页面和前端的数据是不对应的怎么办?

相邻的 Text Node,想多了相邻的 span 而已

1

通过一个简单的例子,我们可以发现,实际上 React 根本没用 Text Node,而是使用 span 来代替 Text Node,这样就可以实现虚拟 DOM 和直出 DOM 的一一映射关系。

重复渲染?没门

刚刚的例子,如果我们通过 React.renderToString 拿到<Test /> 可以发现是:

我们可以发现一个有趣的属性 data-react-checksum,这是啥?实际上这是上面这段 HTML 片段的 adler32 算法值。实际上调用 React.render(<MyComponent />, container); 时候做了下面一些事情:

  • 看看 container 是否为空,不为空则认为有可能是直出了结果。
  • 接下来第一个元素是否有 data-react-checksum 属性,如果有则通过 React.renderToString 拿到前端的,通过 adler32 算法得到的值和 data-react-checksum 对比,如果一致则表示,无需渲染,否则重新渲染,下面是 adler32 算法实现:

  • 如果需要重新渲染,先通过下面简单的差异算法找到差异在哪里,打印出错误:

下面是首屏渲染时的主要逻辑,可以发现 React 对首屏实际上也是通过 innerHTML 来渲染的:

最后

尝试一下下面的代码,想想 React 为啥认为这是错误的?

HLS 视频点播&直播初探
In 未分类 on 2015年04月26日 by view: 3,988
0

前端可选的视频直播协议大致只有两种:

  • RTMP(Real Time Messaging Protocol)
  • HLS(HTTP Live Streaming)
    其中 RTMP 是 Adobe 开发的协议,无法在 iPhone 中兼容,故目前兼容最好的就是 HLS 协议了。

HTTP Live Streaming(HLS)是苹果公司实现的基于 HTTP 的流媒体传输协议,可实现流媒体的直播和点播。原理上是将视频流分片成一系列 HTTP 下载文件。所以,HLS 比 RTMP 有较高的延迟。

前端播放 HLS

  • Native 支持
    1. Android 3.0+
    2. iOS 3.0+
  • flash 支持
    1. Flowplayer(GPL ×
    2. GrindPlayer(MIT)
    3. video-js-swf(Apache License 2.0)
    4. MediaElement.js(MIT)
    5. clappr(BSD IE10+ ×

最后,由于 MediaElement 已经纳入 WordPress 的核心视音频库,以及其良好的兼容性(见下图),所以最后选择使用 MediaElement.js 来实现。

MediaElement.js兼容性

切片准备

可使用 m3u8downloader 下载一个 HLS 源,或者使用 node-m3u 生成 m3u8 索引和 MPEG-TS 切片,下面是我们准备切片:

https://github.com/miniflycn/HLS-demo/tree/master/m3u8

注意看切片索引文件:

其中 #EXT-X-ENDLIST 为切片终止标记,如果没有该标记,浏览器会在文件读取完后再请求索引文件,如果有更新则继续下载新文件,以此达到直播效果。

前端代码

效果

效果

例子源码

https://github.com/miniflycn/HLS-demo

前沿技术解密——VirtualDOM
In 未分类 on 2015年04月07日 by view: 1,862
0

作为 React 的核心技术之一 Virtual DOM,一直披着神秘的面纱。

实际上,Virtual DOM 包含:

  1. Javascript DOM 模型树(VTree),类似文档节点树(DOM)
  2. DOM 模型树转节点树方法(VTree -> DOM)
  3. 两个 DOM 模型树的差异算法(diff(VTree, VTree) -> PatchObject)
  4. 根据差异操作节点方法(patch(DOMNode, PatchObject) -> DOMNode)

接下来我们分别探讨这几个部分:

VTree

VTree 模型非常简单,基本结构如下:

所以我们很容易写一个方法来创建这种树状结构,例如 React 是这么创建的:

VTree -> DOM

这方法也不太难,我们实现一个简单的:

diff(VTree, VTree) -> PatchObject

差异算法是 Virtual DOM 的核心,实际上该差异算法是个取巧算法(当然你不能指望用 O(n^3) 的复杂度来解决两个树的差异问题吧),不过能解决 Web 的大部分问题。

那么 React 是如何取巧的呢?

  1. 分层对比

如图,React 仅仅对同一层的节点尝试匹配,因为实际上,Web 中不太可能把一个 Component 在不同层中移动。

  1. 基于 key 来匹配

还记得之前在 VTree 中的属性有一个叫 key 的东东么?这个是一个 VNode 的唯一识别,用于对两个不同的 VTree 中的 VNode 做匹配的。

这也很好理解,因为我们经常会在 Web 遇到拥有唯一识别的 Component(例如课程卡片、用户卡片等等)的不同排列问题。

  1. 基于自定义元素做优化

React 提供自定义元素,所以匹配更加简单。

patch(DOMNode, PatchObject) -> DOMNode

由于 diff 操作已经找出两个 VTree 不同的地方,只要根据计算出来的结果,我们就可以对 DOM 的进行差异渲染。

扩展阅读

具体可参考下面两份代码实现:

  1. @Matt-Esch 实现的:virtual-dom
  2. 我们自己做的简版实现,用于 Mobile 页面渲染的:qvd
Ques 核心思想——CSSNamespace
In 未分类 on 2015年04月06日 by view: 693
0

Facebook’s challenges are applicable to any very complex websites with many developers. Or any situation where CSS is bundled into multiple files and loaded asynchronously, and often loaded lazily.
——@vjeux
将 Facebook 换成 Tencent 同样适用。

同行们是怎么解决的?

  • Shadow DOM Style

Shadow DOM 的样式是完全隔离的,这就意味着即使你在主文档中有一个针对全部 <h3> 标签的样式选择器,这个样式也不会不经你的允许便影响到 shadow DOM 的元素。

举个例子:

举个栗子

Nacespace

这就很好地为 Web Component 建立了 CSS Namespace 机制。

  • Facebook: CSS in JS

http://blog.vjeux.com/2014/javascript/react-css-in-js-nationjs.html

比较变态的想法,干脆直接不要用 classname,直接用 style,然后利用 js 来写每个元素的 style……

例如,如果要写一个类似 button:hover 的样式,需要写成这样子:

几乎等同于脱离了 css,直接利用 javascript 来实现样式依赖、继承、混入、变量等问题……当然如果我们去看看 React-nativecss-layout,就可以发现,如果想通过 React 打通客户端开发,style 几乎成了必选方案。

我们的方案

我们期望用类似 Web Component 的方式去写 Component 的样式,但在低端浏览器根本就不支持 Shadow DOM,所以,我们基于 BEM 来搭建了一种 CSS Namespace 的方案。

我们的 Component 由下面 3 个文件组成:

  • main.html 结构
  • main.js 逻辑
  • main.css 样式

可参考:https://github.com/miniflycn/Ques/tree/master/src/components/qtree

可以发现我们的 css 是这么写的:

这里面有长得很奇怪的.$__前缀,该前缀是我们的占位符,构建系统会自动将其替换成 Component 名,例如,该 Component 为 qtree,所以生成结果是:

同样道理,在 main.htmlmain.js 中的对应选择器,在构建中也会自动替换成 Component 名。

这有什么好处呢?

  1. 基于路径的 Namespace,路径没有冲突,那么在该项目中 Namespace 也不会冲突
  2. Component 可以任意改名,或复制重构,不会产生任何影响,便于 Component 的重构和扩展
  3. Component 相对隔离,不会对外部产生影响
  4. Component 非绝对隔离,外部可以对其产生一定影响