作者介绍
邵裕东,2018年3月入职 Qunar,现任平台事业部大前端技术中心前端开发工程师,负责公司移动端框架 Hy、QRN 的开发维护,Nanachi 小程序多端转译框架开发。追求前端工程化,喜欢做一切有意义的事。
方案初探
Nanachi
Nanachi 是 Qunar 开发的多端小程序转译框架。使用 React 语法开发,实现一处编写、多端运行,极大地提高了我们的开发效率。目前 Qunar 已经使用该框架成功上线各大小程序、快应用平台,Nanachi 成为公司小程序开发标准技术框架。
Nanachi 官网:https://rubylouvre.github.io/nanachi/index.html
运行方式
npm install nanachi-cli -g # 全局安装 Nanachi 转译器 nanachi init yourprojectname # 初始化模板项目 cd yourprojectname && npm install nanachi watch # 编译项目,默认编译微信,其他平台需要传入对应参数,如 watch:ali、watch:bu、watch:h5...
编译分为 build 和 watch 模式,watch 模式会自动监听代码变化重新构建。
H5 编译的 watch 模式会在本地启动 webpack-dev-server,方便实时调试。
H5 方案意义
相较传统 H5 方案优势
用 Nanachi 的方式编写 React SPA 应用,与传统浏览器开发相比,有以下优势:
编写一次,多端运行,适配成本极低。
丰富的组件库,兼容微信的组件实现方式。
封装了浏览器端的小程序 API(如:提示框、打电话、震动、本地存储、网络请求…),功能上与原生体验接近,开发方便。
不需要关心路由跳转问题,节约开发成本。
封装好了基础的 Webpack 打包配置,无特殊需求可实现零配置打包;同时暴露 Webpack 配置接口,可自由定制。
让 Nanachi 适用于更多场景开发
如果已使用 Nanachi 开发了小程序应用,H5 方案适用于以下场景:
可以快速上线一套与小程序业务逻辑一致的 touch 端应用,抢占浏览器流量入口。
小程序平台目前层出不穷,可以通过转出的 touch 端应用套 Webview 壳快速抢占某个新小程序平台入口,待 Nanachi 官方支持该平台时,也可快速迁移至原生小程序应用。
小程序开发需要打开 IDE,等待原生代码编译。可以先通过 H5 方案实现基本样式、功能,最后阶段编译成原生代码适配各平台差异功能,提高开发效率。
技术细节、难点解析
编译时方案
H5 方案代码编译过程分为三个阶段:
React SPA 应用源码编译出 Webpack 产物的过程大多数前端开发都很熟悉了,这里我们着重介绍从源码到生成中间产物的过程。
小程序中存在很多标签,在浏览器端没有对应的原生实现,我们首先要对这些标签进行转译。
对一些基础标签,会进行直接的标签替换:
对一些复杂的组件,我们实现了一套组件库,schnee-ui,用来抹平浏览器和微信原生标签的差异。
schnee-ui 官网:https://qunarcorp.github.io/schnee-ui/index.html
编译时我们会自动导入组件依赖:
开发过小程序的同学知道,小程序分为 App、Page、Component 三级结构,对于页面(Page)组件,我们对代码进行如下处理:
dynamicPage 是我们内部实现的高阶组件,负责每个页面的生命周期钩子调用、标题栏、tab 栏的渲染、下拉刷新功能的实现、过场动画等等。
对于 App 组件,代码处理如下:
在 App 中挂载 PageWrapper 组件,这是我们整个应用的容器,主要职责是渲染页面栈。
运行时方案
抹平 API、组件(包括原生 titleBar、tabBar)
我们在浏览器端实现了一套和微信小程序兼容的 API,并且同时支持 Callback 和 Promise 的方式接收回调信息。
组件的设计方面,我们也保持和微信小程序写法完全一致。除了一些基础的按钮、选择器、switch 开关等,还包括了一些通用的复杂业务组件,比如城市选择器、轮播图、日历等。
实现了浏览器端没有的标题栏和 tab 栏:
统一生命周期
React 方案里缺少一些小程序端的生命周期,我们会在浏览器端模拟实现,在特定的时机触发对应的钩子:
页面堆栈管理
小程序的页面堆栈行为是各平台管理的,到浏览器端我们就需要自己维护一套页面管理方案,同时要和微信端保持一致。我们会在内部定义一个__currentPages 数组,用来存储页面的 React 实例,并实现自己的路由方法,管理这些实例。
同时模拟了小程序的最大页面数概念,对微信来说目前能保持最多 10 个页面。我们每次调用 navigateTo 方法时会检查当前页面栈长度是否达到最大值,达到则先推出数组的第一个元素,再存入新页面实例。
技术难点
路由管理
路由管理的实现过程主要遇到两方面的问题:
如何更新地址栏
需要在调用路由方法时同时改变地址栏,对 navigateTo 我们调用 history.pushState,redirectTo 调用 history.replaceState 方法,实现地址栏的更新。
如何保证调用 API 返回与浏览器原生返回事件行为一致
我们知道浏览器原生返回操作会触发 popstate 事件,所以可以监听这个事件然后调用我们内部的 API 方法,达到行为一致。设计方案如下:
样式作用域
小程序的样式是天然具有局部作用域的,到浏览器端样式会变为全局样式,必然出现样式污染问题,解决方案有以下几种:
业务自定义命名空间,缺点:工作量大、可靠性低,最先被排除掉。
Css Module 方案,传统 touch 端开发的流行局部样式解决方案,缺点:业务需要修改代码适配,成本高。
Web Component 方案,也是微信小程序的局部样式解决方案,优点:无适配问题,业务无感知;缺点:浏览器端有兼容性问题。
编译时为标签添加特定 hash 属性(参考 VUE scoped 样式方案):优点:业务无感知,无兼容性问题。
我们采取的是最后一种方案,计算样式文件和它对应的js文件共同父级目录的 MD5 值,作为关联两者的唯一 hash,分别添加到jsx标签和 style 文件的标签选择器上,实现局部样式效果。
总结
Nanachi 作为多端转译框架,已经成为 Qunar 小程序的开发标准,其跨端特性极大地提高了开发效率。对 H5 端的支持也让我们可以快速上线与小程序功能一致的 touch 应用。相较传统 React 开发,我们维护了与微信高度一致的组件和 API,开发者不用再去引入各种第三方组件库来实现一些复杂功能;同时路由行为也由框架层维护,进一步减轻开发负担;最后可以实现零配置打包上线,也支持业务自定义各种 Webpack 配置,简单灵活。
最后欢迎大家试用 Nanachi,帮我们提 issue、PR,我们会第一时间解决或提供支持。
项目地址:https://github.com/RubyLouvre/anu