nanachi h5转译方案原理解析

804 阅读6分钟

方案初探

nanachi

nanachi是Qunar开发的多端小程序转译框架。使用React语法开发,实现一处编写、多端运行,极大地提高了我们的开发效率。目前Qunar已经使用该框架成功上线各大小程序、快应用平台,nanachi成为公司小程序开发标准技术框架。

nanachi官网: rubylouvre.github.io/nanachi/ind…

运行方式

npm install nanachi-cli -g # 全局安装nanachi转译器
nanachi init your_project_name # 初始化模板项目
cd your_project_name && npm install
nanachi watch # 编译项目,默认编译微信,其他平台需要传入对应参数,如watch:ali、watch:bu、watch:h5...

编译分为build和watch模式,watch模式会自动监听代码变化重新构建。

H5编译的watch模式会在本地启动webpack-dev-server,方便实时调试。

H5方案意义

相较传统H5方案优势

用nanachi的方式编写React SPA应用,与传统浏览器开发相比,有以下优势:

  1. 编写一次,多端运行,适配成本极低。
  2. 丰富的组件库,兼容微信的组件实现方式。
  3. 封装了浏览器端的小程序API(如:提示框、打电话、震动、本地存储、网络请求…),功能上与原生体验接近,开发方便。
  4. 不需要关心路由跳转问题,节约开发成本。
  5. 封装好了基础的Webpack打包配置,无特殊需求可实现零配置打包;同时暴露Webpack配置接口,可自由定制。

让nanachi适用于更多场景开发

如果已使用nanachi开发了小程序应用,H5方案适用于以下场景

  1. 可以快速上线一套与小程序业务逻辑一致的touch端应用,抢占浏览器流量入口。
  2. 小程序平台目前层出不穷,可以通过转出的touch端应用套Webview壳快速抢占某个新小程序平台入口,待nanachi官方支持该平台时,也可快速迁移至原生小程序应用。
  3. 小程序开发需要打开IDE,等待原生代码编译。可以先通过H5方案实现基本样式、功能,最后阶段编译成原生代码适配各平台差异功能,提高开发效率。

技术细节、难点解析

编译时方案

H5方案代码编译过程分为三个阶段:

React SPA应用源码编译出Webpack产物的过程大多数前端开发都很熟悉了,这里我们着重介绍从源码到生成中间产物的过程。

小程序中存在很多标签,在浏览器端没有对应的原生实现,我们首先要对这些标签进行转译。

对一些基础标签,会进行直接的标签替换:

对一些复杂的组件,我们实现了一套组件库,schnee-ui,用来抹平浏览器和微信原生标签的差异。

schnee-ui官网: qunarcorp.github.io/schnee-ui/i… 编译时我们会自动导入组件依赖:

开发过小程序的同学知道,小程序分为App、Page、Component三级结构,对于页面(Page)组件,我们对代码进行如下处理:

dynamicPage是我们内部实现的高阶组件,负责每个页面的生命周期钩子调用、标题栏、tab栏的渲染、下拉刷新功能的实现、过场动画等等。

对于App组件,代码处理如下:

在App中挂载PageWrapper组件,这是我们整个应用的容器,主要职责是渲染页面栈。

运行时方案

抹平API、组件(包括原生titleBar、tabBar)

我们在浏览器端实现了一套和微信小程序兼容的API,并且同时支持Callback和Promise的方式接收回调信息。

组件的设计方面,我们也保持和微信小程序写法完全一致。除了一些基础的按钮、选择器、switch开关等,还包括了一些通用的复杂业务组件,比如城市选择器、轮播图、日历等。

实现了浏览器端没有的标题栏和tab栏:

统一生命周期

React方案里缺少一些小程序端的生命周期,我们会在浏览器端模拟实现,在特定的时机触发对应的钩子:

页面堆栈管理

小程序的页面堆栈行为是各平台管理的,到浏览器端我们就需要自己维护一套页面管理方案,同时要和微信端保持一致。 我们会在内部定义一个__currentPages数组,用来存储页面的React实例,并实现自己的路由方法,管理这些实例。

同时模拟了小程序的最大页面数概念,对微信来说目前能保持最多10个页面。我们每次调用navigateTo方法时会检查当前页面栈长度是否达到最大值,达到则先推出数组的第一个元素,再存入新页面实例。

技术难点

路由管理

路由管理的实现过程主要遇到两方面的问题:

  1. 如何更新地址栏

    需要在调用路由方法时同时改变地址栏,对navigateTo我们调用history.pushState,redirectTo调用history.replaceState方法,实现地址栏的更新

  2. 如何保证调用API返回与浏览器原生返回事件行为一致

    我们知道浏览器原生返回操作会触发popstate事件,所以可以监听这个事件然后调用我们内部的API方法,达到行为一致。设计方案如下:

样式作用域

小程序的样式是天然具有局部作用域的,到浏览器端样式会变为全局样式,必然出现样式污染问题,解决方案有以下几种:

  1. 业务自定义命名空间,缺点:工作量大、可靠性低,最先被排除掉。
  2. Css Module方案,传统touch端开发的流行局部样式解决方案,缺点:业务需要修改代码适配,成本高。
  3. Web Component方案,也是微信小程序的局部样式解决方案,优点:无适配问题,业务无感知;缺点:浏览器端有兼容性问题。
  4. 编译时为标签添加特定hash属性(参考VUE scoped样式方案):优点:业务无感知,无兼容性问题。

我们采取的是最后一种方案,计算样式文件和它对应的js文件共同父级目录的MD5值,作为关联两者的唯一hash,分别添加到jsx标签和style文件的标签选择器上,实现局部样式效果。

总结

nanachi作为多端转译框架,已经成为Qunar小程序的开发标准,其跨端特性极大地提高了开发效率。对H5端的支持也让我们可以快速上线与小程序功能一致的touch应用。相较传统React开发,我们维护了与微信高度一致的组件和API,开发者不用再去引入各种第三方组件库来实现一些复杂功能;同时路由行为也由框架层维护,进一步减轻开发负担;最后可以实现零配置打包上线,也支持业务自定义各种Webpack配置,简单灵活。

最后欢迎大家试用nanachi,帮我们提issue、PR,我们会第一时间解决或提供支持。

项目地址: github.com/RubyLouvre/…