其中第一个点,在小程序开发规范当中,每一个标签元素其实都是一个内置组件,小程序对这些内置组件做了一定程度的封装,内置组件有自己的功能特性、样式规则、事件绑定等等,我们平时开发组件都是基于这些内置组件来进行开发的。在做 web 开发一样,浏览器提供了 div、input、button 等这些内置组件,只不过封装程度,提供的功能特性没有小程序提供的那么多。那么如果要做跨 web 的输出的话,最终的目标肯定是小程序和 web 渲染都是一致的,也就是在小程序使用一个内置的 button 组件,在 web 侧需要对应的实现其功能。因此在标签语法的处理流程中是通过收集&标记 template 当中使用到了哪些内置基础组件(内置组件都是全局的),渲染阶段其实是将渲染这些内置组件所对应在 vue 侧实现的自定义组件。最终达到的效果就是在小程序渲染的基础组件和 web 侧渲染出来的都是一致的。
举个例子,在小程序当中有 view 标签,但是在 web 侧是没有的,那么在转 web 的过程中会根据源码 view 标签上是否有绑定事件等条件来判断最终输出到 web 是 div 还是 mpx-view(mpx 内置的封装好的组件)。如果没有绑定事件,那么在 template-compiler 处理过程中直接将标签转为 div,最终模板 ast 处理完后对 ast 序列化后的 template 代码也就得到了在 web 侧可以识别运行的代码标签。(为什么对于绑定了事件的标签要转为 mpx 封装好的组件?因为只有利用自定义组件才能响应事件处理函数,否则对于普通的 div 标签是没法处理事件的)
整体上的实现思路是基于小程序的开发规范然后再做跨 web 的兼容。因为 mpx 也是采用 sfc 方式去组织文件内容(包括template/script/style/json),底层框架是基于 vue。
编译环节:
在编译环节需要解决的一个核心问题就是 mpx sfc 如何转化为 vue sfc,在这一层处理完之后,后面的编译构建流程即可直接复用 vue-loader(即 vue 项目的编译构建)的构建流程。
本地编译启动后,mpx 文件首先会经过 mpx loader 的处理,只不过在 mpx loader 内部针对 web 环境下的编译工作做了一些特殊处理:
processTemplate | processStyle | processJson -> processScript
对于项目的入口文件,注入 vue 以及项目的初始化渲染代码
对于非入口文件,例如通过 createPage 或者 createComponent 创建的页面/组件(在 vue 里面是没有 page 概念的,一切都是组件),便需要处理 template/script/style/json 这些 block 的内容,特别是 json 配置只是在小程序里面独有的(主要是申明一些组件的依赖路径或者其他的配置),这也是和 vue sfc 里面差异非常大的点(vue sfc 里面组件依赖的申明是在 components 字段里面申明的),所以 mpx sfc 转为 vue sfc 这个点来说,需要在编译环节处理为满足 vue sfc 文件开发规范的内容。
对于 template 来说,需要解决的3个重要的问题:
其中第一个点,在小程序开发规范当中,每一个标签元素其实都是一个内置组件,小程序对这些内置组件做了一定程度的封装,内置组件有自己的功能特性、样式规则、事件绑定等等,我们平时开发组件都是基于这些内置组件来进行开发的。在做 web 开发一样,浏览器提供了
div
、input
、button
等这些内置组件,只不过封装程度,提供的功能特性没有小程序提供的那么多。那么如果要做跨 web 的输出的话,最终的目标肯定是小程序和 web 渲染都是一致的,也就是在小程序使用一个内置的 button 组件,在 web 侧需要对应的实现其功能。因此在标签语法的处理流程中是通过收集&标记 template 当中使用到了哪些内置基础组件(内置组件都是全局的),渲染阶段其实是将渲染这些内置组件所对应在 vue 侧实现的自定义组件。最终达到的效果就是在小程序渲染的基础组件和 web 侧渲染出来的都是一致的。举个例子,在小程序当中有
view
标签,但是在 web 侧是没有的,那么在转 web 的过程中会根据源码 view 标签上是否有绑定事件等条件来判断最终输出到 web 是div
还是mpx-view
(mpx 内置的封装好的组件)。如果没有绑定事件,那么在 template-compiler 处理过程中直接将标签转为div
,最终模板 ast 处理完后对 ast 序列化后的 template 代码也就得到了在 web 侧可以识别运行的代码标签。(为什么对于绑定了事件的标签要转为 mpx 封装好的组件?因为只有利用自定义组件才能响应事件处理函数,否则对于普通的div
标签是没法处理事件的)对于第二个点,小程序对于模板语法做了一定程度的增强,提供了一些可以提高开发效率&体验的 directive,例如条件渲染(wx:if),列表渲染等(wx:for),而在 vue 当中不仅仅提供了条件渲染、列表渲染还有一些对于 class,style,show 等非常好用的一些指令。所以在这部分的处理过程当中,首先是对于2个平台上已有指令的对齐(wx:if -> v-if),另外一个点就是对于小程序的指令系统做增强,即在小程序的规范生态下通过编译 + runtime 配合来达到实现小程序没有但是 vue 里面有的功能。所以在使用 mpx 进行小程序开发的过程中,可以使用一些原生小程序没有提供的增强型的指令。
对于第三点事件处理,mpx 还是遵照小程序的书写规范原则,只不过在 web 侧做一定的兼容处理。
对于 json 配置来说,需要解决的核心问题是:
在小程序的规范当中,json 配置(组件依赖关系是松散的)是在小程序框架内部去完成依赖关系确认的。那么对于小程序跨 web 的场景来说,比较重要的一点就是解析 json 配置的内容并完成组件的路径收集,这部分的内容最终是需要合并到 vue options 当中。就类似于在开发 vue 项目的时候进行的组件的依赖关系的申明。因此对于 mpx sfc 来说,json 配置的编译处理也是先于 script 的。在小程序规范当中,有严格的 Page/Component 的配置区分,那么进入到
processJSON
处理流程当中需要处理并收集这部分的路径,然后交给 script 的处理流程。对于 script 来说,在 vue sfc 当中 script 需要明确导出 options 配置内容(
export default {}
)。但是在小程序规范当中,在 script 当中只需要调用Page、Component
(mpx 当中是 @mpxjs/core 暴露出来的createPage、createComponent
) 全局方法,因此对于 script 的处理需要解决的几个核心问题就是:对于第一个问题,实际上
createComponent
是对于Component
的一层包装,内部最终会调用Component
,但是在 web 场景下肯定是没有这个全局方法的,所以在 web 侧下调用 createComponent 方法,这个方法内部针对在 web 场景下最终会将 options 挂载至global.currentOpiton
下,这样在运行时阶段相关 options 可以从这上面获取。此外针对 options 的导出注入了运行时的代码:另外在注入的代码当中提供了
processOptions
方法,这个方法实际上会将原始的 options(开发者写的 options 配置)以及在processJSON
阶段收集起来的页面/组件做进一步的合并处理,执行的最终结果是满足 vue sfc 规范处理的文本内容。运行时环节
在编译环节主要是解决了 mpx 小程序和 vue 开发规范之间差异,通过 mpx-loader 当中对于 web 场景下信息的收集、转化以及注入一些辅助函数用以在运行时代码执行阶段。而在运行时环节,mpx 对于每个 vue 组件实例同样也做了一层代理(和对于小程序 this 做代理一样)
在运行时环节需要解决的几个核心的问题:
对于第一点,小程序和 vue 的生命周期直观上是没有拉齐的,首先小程序的 Page 和 Component 的生命周期就是不一致的,而 vue 整体都是基于组件的,所以对于 vue 组件来说生命周期是一致的。那么针对生命周期抹平的这个问题,首先肯定是依托于不同平台自身的生命周期的入口,最终收敛至 mpx 代理内部来做生命周期的统一。
所以最终的效果就是以小程序平台为规范的生命周期最终是在 mpx 代理层做了对应的抹平工作。
对于第二点,mpx 提供了一个单独的 npm package:
@mpxjs/api-proxy
,用以对 api 做平台的抹平相关的工作,使用的方式就是mpx.xxxFn
这种形式,在编译环节这里的 mpx 就会转化到对应平台。这里可以举一个路由的例子:在小程序规范里面,需要在 app.json 里面申明所有的 pages 也就代表了不同的页面路由,小程序框架内部会依据这些配置做路由相关的注册,在 api 使用上提供了wx.navigateTo
,wx.redirectTo
等相关的路由 api,这些 api 在 vue 里面等价于 vueRouter 提供的route.push
,route.replace
等方法。所以在@mpxjs/api-proxy
里面所做的工作就是使用 mpx 暴露出来的这些路由相关的行为在小程序和 vue 表现一致。对于第三点,mpx 对于小程序做了响应式数据的增强,引入了 render 函数,最终使用效果和 vue 的响应式数据类似。那么在 web 场景下,因为 vue 已经提供了响应式的能力,所以对于响应式数据不需要做任何的处理工作,在
src/core/proxy.js
:所以在 web 场景下,对于小程序的一些增强的能力就不需要执行了,统一由 vue 去接管。