您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center   Code  
会员   
   
 
     
   
 订阅
  捐助
uni-app黑魔法:小程序自定义组件运行到H5平台
 
作者:崔红保
  1457  次浏览      15
 2020-7-21 
 
编辑推荐:
本文分享了uni-app将微信小程序自定义组件发行到 H5 平台的实现思路,希望对大家有所启发。
本文来自于微信前端之巅,由火龙果软件Alice编辑推荐。

2017 年 1 月 9 日,微信发布小程序,历经 3 年发展,在今年主题为”未完成 Always Beta“的微信公开课 PRO 上,微信团队披露,2019 年小程序日活跃用户超过 3 亿,全年累计成交额达 8000 亿,同比增长超 160%。

看到小程序如此惊人的增长力,我们有理由相信,有中国特色的小程序互联网时代已经到来,微信小程序也已成为继 iOS、Android、H5 之后的第四大流量平台。

平台分裂,为不同平台编写相同的业务代码,是件无趣的事情。

有追求的程序员,一直在探索代码复用的方案,Hybrid App 即是代表。

而在如今的小程序时代,对于同样基于 WEB 技术的 H5 和小程序,如何实现代码复用,是很多前端工程师探索的方向。业内也已有不少成熟方案,从场景上来说,大致分为三类:

1、基于跨端框架,从头开发,一套代码,发行多个平台,比如 DCloud 出品的uni-app、京东凹凸实验室的taro。

2、复用 H5 代码,转换 H5 代码在小程序环境中执行;适用于有 H5 平台沉淀,未开发小程序或小程序完善度较低的开发者;

美团的 mpvue 框架是早期探索解决这个问题的代表,但因小程序不支持 dom 操作,故 mpvue 适用于 vue 的无 dom 操作的 H5 代码转换;

最近微信官方推出的 kbone,也是为了解决“把 Web 端的代码挪到小程序环境内执行”;不过,kbone 相比 mpvue 前进了一部,因为:

kbone 实现了一个适配器,在适配层里模拟出了浏览器环境,让 Web 端的代码可以不做什么改动便可运行在小程序里。

3、复用小程序代码,转换小程序代码在 web 环境中运行;适用于有小程序代码沉淀,未开发 H5 或 H5 平台完善度较低的开发者;这个方向业内成熟的方案还比较少。

uni-app近期支持了小程序自定义组件运行到 H5 平台,是对如上第三种场景的一种探索。

需求场景

鉴于小程序的低成本获客特征,很多厂商选择先开发小程序,验证业务模式后,再扩展至 H5、App 等其它平台。

开发者虽可借助转换器将小程序代码转换为uni-app项目(或其它跨端框架项目),快速实现多平台发行;但不少开发者是不敢轻易决策将跨端版本替换之前线上的小程序版本的,毕竟线上版本已稳定运行了一段时间。

常选的方案是:让原生小程序版本和uni-app跨端版本并行一段时间,微信平台继续使用原生版本,其它平台使用uni-app跨端版本;经过一段时间验证uni-app版本稳定后,再使用uni-app版替换掉原生小程序版本。

在这段并行的时间内,开发者需要同时维护微信原生、uni-app两个版本,新增业务需编写两份逻辑相同的代码,重复劳动,成本叠加,如何改善?

借助uni-app 支持将微信小程序组件运行到 H5 平台的特性,我们给出一种思路:

开发者在原生小程序项目中,将新增业务以自定义组件的方式开发,优先上线小程序;

拷贝小程序组件的wxml/wxss/js/json文件到uni-app 项目下,通过uni-app的编译器及运行时,保证小程序自定义组件在 H5 平台的正确运行。

这个方案的好处是:

优先小程序开发,毕竟小程序早已上线,有存量用户;

复用小程序组件,新增业务仅需开发一套代码即可,降低开发成本。

不止自己开发的小程序组件,业内开源的三方小程序组件,均可复制到uni-app项目项目中,运行到 H5 平台。

另外,部分公司的产品经理,会要求不同平台有不同的交互,但核心业务逻辑是相同的,开发者常会通过维护不同项目的方式来满足产品经理需求。此时,采取如上方案,同样可满足多个项目复用相同业务逻辑的诉求。

实际上,uni-app之前已支持将小程序自定义组件运行到 App 平台,对于有小程序组件沉淀或优先小程序的开发者来说,这是个好消息,一套业务组件,快速运行到 iOS、Android、H5、微信小程序这四大流量平台(实际上也可运行到 QQ 小程序平台)。

uni-app 引用小程序组件演示

uni-app项目中使用自定义组件的方法很简单,分为三步:

1、拷贝小程序自定义组件到uni-app项目根目录下的wxcomponents文件夹下

2、在 pages.json 对应页面的 style -> usingComponents引入组件,如:

{
"pages": [
{
"path": "index/index",
"style": {
"usingComponents": {
"custom": "/wxcomponents/custom/index"
}
}
}
]
}

 

3、在页面中使用自定义组件,如:

<!-- 页面模板 (index.vue) -->
<view>
<!-- 在页面中对自定义组件进行引用 -->
<custom name="uni-app"></custom>
</view>

方案实现思路

简单介绍下uni-app的多端发行原理。

uni-app基于Vue.js runtime,页面文件遵循Vue.js 单文件组件 (SFC) 规范,天然对 H5 的支持比较好,发行到 H5 平台时,先通过vue-loader解析.vue文件,导出Vue.js 组件选项对象,然后在运行时补充规范实现:

组件:框架提供内置组件(view/swiper/picker 等)的实现,保证平台 UI 及交互的一致性;

接口:在 H5 平台封装框架接口,比如路由跳转,showToast 等界面交互;

生命周期:Vue.js 的理念是一切皆为组件,没有应用和页面的概念;框架需创造出应用及页面的概念,模拟 onLaunch、onShow 等钩子。

uni-app 发行到小程序平台时,逻辑又有不同,主要工作有 2 块:

编译器:将.vue文件拆分成wxml/wxss/js/json4 个原生页面文件;

运行时:Vue.js和小程序都是逻辑视图层框架,都有数据绑定功能;运行时会实现Vue.js到小程序的数据同步,及小程序到Vue.js的事件代理。

小程序自定义组件类似小程序原生的页面开发,一个自定义组件同样由wxml/wxss/js/json 4 个文件组成,另有单独的组件规范(如Component 构造器、Behaviors特性等)。

所以,小程序自定义组件运行到 H5 平台,可借助uni-app已有平台功能快速实现:

编译阶段:将wxml/wxss/js/json4 个文件合并为.vue文件(类似 uni-app 发行到小程序的逆过程),然后调用uni-app发行 H5 平台的编译过程,通过vue-loader解析.vue文件,导出 Vue.js 组件选项对象;

运行阶段:实现 Component 构造器、Behaviors特性,模拟自定义组件特有的生命周期。

编译:转换文件(mp2vue)

小程序自定义组件发行到 H5 平台,在编译环节主要有 2 项工作:

1、将自定义组件的wxml/wxss/js/json 4 个文件组成,编译转换成.vue文件,即小程序转 vue,可简写为mp2vue

2、通过vue-loader解析.vue文件,导出 Vue.js 组件选项对象

其中,步骤 2 是Vue.js项目的标准编译过程,略过不提;我们重点阐述步骤 1。

mp2vue将 4 个独立wxml/wxss/js/json 的文件合并成一个.vue文件,并组装成template、script、style 这种三段式的结构,流程包括:

wxml文件生成template节点,同时完成指令、事件等模板语法转换;

js/json文件生成script节点,同时完成组件注册过;

wxss文件生成style节点,自动转换部分 css 兼容语法;

合并为.vue文件。

具体实现上,uni-app编译前先扫描wxcomponents目录,若存在则认为有小程序自定义组件,启动文件转换工作 (uni-migration插件来完成):

// 加载转换器
const migrate = require('@dcloudio/uni-migration')
// 扫描 wxcomponents 目录
const wxcomponents = path.resolve(process.env.UNI_INPUT_DIR, 'wxcomponents')
if (fs.existsSync(wxcomponents)) {
migrate(wxcomponents, false, {
silent: true
}) // 转换 mp-weixin 小程序组件
}

 

接着开始对wxml/wxss/js/json文件逐个解析,并合并为一个.vue文件:

module.exports = function transformFile(input, options) {
// 首先转换 json 文件,判断是否为组件
const [jsCode, isComponent] = transformJsonFile(filepath + '.json', deps)
options.isComponent = isComponent
// 转换 wxml 文件
const [templateCode, wxsCode = '', wxsFiles = []] = transformTemplateFile (filepath + templateExtname, options)
// 转换 wxss 文件
const styleCode = transformStyleFile(filepath + styleExtname, options, deps) || ''
// 转换 js 文件
const scriptCode = transformScriptFile(filepath + '.js', jsCode, options, deps)
// 生成合并后的.vue 文件源码
return [
`${commentsCode}<template>
${templateCode}
</template>
${wxsCode}
<script>
${scriptCode}
</script>
<style platform="mp-weixin">
${styleCode}
</style>`,
deps,
wxsFiles
]
}

进一步细节说明,wxml 文件转为 template 节点时,需完成各项指令、事件等模板语法的转换,例如:

将一个最简自定义组件,按照如上流程转换,结果示意如下:

运行时:模拟小程序组件环境

uni-app的编译器并不转换小程序组件的 JS 代码,依然保留Component构造器的写法,甚至其中的 API 依然是wx.开头的方式,这些都依赖uni-app在 H5 平台的运行时来解决,主要有如下几部分内容:

Component构造器:解析小程序组件的各种选项配置,转换为Vue组件定义,包括变通实现其中的差异部分,如小程序组件特有的”组件所在页面的生命周期“;

Behaviors特性:转换为 Vue 的 混入(mixin);

数据响应:在 H5 平台实现setData接口及this.data.xx = yy的数据通讯机制;

API 前缀:可在运行时通过代理机制,自动将wx.xx替换为uni.xx,这个比较简单,不详述。

Component 构造器

uni-app在 H5 平台定义了一个Component函数,执行到小程序的Component构造器函数后,开始循环解析其属性,并转换成 Vue 组件属性,流程示意代码如下:

export function Component (options) {
const componentOptions = parseComponent(options)
componentOptions.mixins.unshift (polyfill)
componentOptions.mpOptions.path = global['__wxRoute']
initRelationsHandler (componentOptions)
global['__wxComponents'] [global['__wxRoute']] = componentOptions
}

export function parseComponent (mpComponentOptions) {
const {
data,
options,
methods,
behaviors,
lifetimes,
observers,
relations,
properties,
pageLifetimes,
externalClasses
} = mpComponentOptions

const vueComponentOptions = {
mixins: [],
props: {},
watch: {},
mpOptions: {
mpObservers: []
}
}

// 开始逐个解析所有属性
parseData(data, vueComponentOptions)
parseOptions(options, vueComponentOptions)
parseMethods(methods, vueComponentOptions)
parseBehaviors(behaviors, vueComponentOptions)
parseLifetimes(lifetimes, vueComponentOptions)
parseObservers(observers, vueComponentOptions)
parseRelations(relations, vueComponentOptions)
parseProperties(properties, vueComponentOptions)
parsePageLifetimes(pageLifetimes, vueComponentOptions)
parseExternalClasses(externalClasses, vueComponentOptions)
parseLifecycle(mpComponentOptions, vueComponentOptions)
parseDefinitionFilter(mpComponentOptions, vueComponentOptions)
// 返回 Vue 组件
return vueComponentOptions
}

在这个过程中,需处理小程序自定义组件和 Vue 组件的属性对应关系及细节差异,如小程序组件的lifetimes:

小程序的pageLifetimes(组件所在页面的生命周期)在 Vue 中是没有的,需要映射为uni-app封装的页面生命周期:

Behaviors特性的实现过程,类似Component构造器,不再赘述。

数据响应

Vue和小程序都有一套数据绑定系统,但机制不同,比如在 Vue 体系下,数据赋值是这样的:

this.a = 1

但在小程序中,数据赋值方式则是这样的:

this.setData({
a:1
}) // 响应式
this.data.a = 2 // 非响应式

另外,小程序和Vue在数据的properties、observer等方面都存在不少差异,经过我们评估,若将小程序的数据响应用法直接映射到Vue体系下,复杂度较高且有性能压力,故uni-app在 H5 平台按照微信的语法规范,单独实现了一套数据响应系统。

// 小程序的 setData 在 H5 平台的实现
function setData (data, callback) {
if (!isPlainObject(data)) {
return
}
Object.keys(data).forEach(key => {
if (setDataByExprPath (key, data[key], this.data)) {
!hasOwn(this, key) && proxy(this, SOURCE_KEY, key);
}
});
this.$forceUpdate();// 数据变化,强制视图更新(响应式)
isFn(callback) && this.$nextTick(callback);
}

将setData挂载到 vm 对象上,可通过this.setData这种小程序的方式调用;同时将数据绑定到 data 属性上,支持this.data.xx的访问方式。

export function initState (vm) {
const instanceData = JSON.parse (JSON.stringify(vm.$options.mpOptions.data || {}))
vm[SOURCE_KEY] = instanceData

//vm 对象上挂载 setData 方法,实现小程序方法
vm.setData = setData

const propertyDefinition = {
get () {
return vm[SOURCE_KEY]
},
set (value) {
vm[SOURCE_KEY] = value
}
}
// 小程序用法,可通过 this.data.xx 访问
Object.defineProperties(vm, {
data: propertyDefinition,
properties: propertyDefinition
})

Object.keys(instanceData).forEach(key => {
proxy(vm, SOURCE_KEY, key)
})
}

虽然数据响应是uni-app自己实现的,但渲染依然使用了 Vue 框架的render函数,此时需小程序规范中的this.data.xx和 Vue 规范中的this.xx保持一致,通过代理的方式实现:

// mp/polyfill/state/proxy.js
const sharedPropertyDefinition = {
enumerable: true,
configurable: true
};

function proxy (target, sourceKey, key) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
};
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val;
};
Object.defineProperty(target, key, sharedPropertyDefinition);
}

这里仅列出了主要的几步,中间涉及细节很多;部分无法通过Vue扩展机制实现的功能,只好修改Vue.js的内核源码,比如updateProperties支持、小程序wxs、externalClasses等功能在 H5 平台的支持,都需要定制部分 Vue.js runtime 源码。

结语

本文分享了uni-app将微信小程序自定义组件发行到 H5 平台的实现思路,希望对大家有所启发。

但这种方案,归根到底是为了解决多套项目并存时的业务重复开发的问题。

如果你是从头开发,我们建议直接选择业内成熟的跨端框架,既可以保持一套代码,更省力的维护,还可以借助框架的成熟生态(如跨端 UI 库及插件市场),基于成熟轮子,快速完成业务的上线开发;

uni-app框架代码,包括小程序组件发行到 H5 平台的代码,全部开源在 github,如果大家对本文逻辑有疑问,欢迎提交 issue 交流。

 

 
   
1457 次浏览       15
相关文章

深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
相关文档

重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
相关课程

基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程
最新课程计划
信息架构建模(基于UML+EA)3-21[北京]
软件架构设计师 3-21[北京]
图数据库与知识图谱 3-25[北京]
业务架构设计 4-11[北京]
SysML和EA系统设计与建模 4-22[北京]
DoDAF规范、模型与实例 5-23[北京]
 
最新文章
如何设计高扩展的在线网页制作平台
electron入门心得
使用 Electron 构建桌面应用
VUE.JS组件化开发实践
深入理解JSCore
最新课程
HTML 5 + CSS3 原理与开发应用
Web前端高级工程师必备技能实战
Vue大型项目开发实战
React原理与实践
Vue.js进阶与案例实践
更多...   
成功案例
中交集团 构建Web自动化测试框架
某著名电信公司 Vue.js进阶与案例
国电通网络技术 HTML5+CSS3 +web前端框
移动通信 移动互联网应用开发原理
某电力行 android开发平台最佳
更多...