背景
2019 年底,微信小程序已经推出了近三个年头,我身边的前端开发者基本都做过至少一次小程序了。很多友商曾打算推动小程序进入 W3C 标准,而微信并不为所动,个人认为,小程序本身在框架设计上称不上「标准」,微信也并没打算做一个「标准的平台」。
小程序更注重产品形态和交互,注重对开发者能力的制约,尽可能减少对用户的干扰;因此,也许小程序从设计之初就没有过多考虑开发层面的「优雅」,而是以方便上手、容易学习为主。最典型的例子就是 App()
、Page()
这一类直接注入到模块内的工厂方法,你不知道、也不需要知道它从何处来,来无影去无踪,是与现在 JS 生态中早已普及的模块化开发有点相悖的。
在架构上,小程序选择了将逻辑层与视图层分离的方式来组织业务代码。小程序的源码提交上传时,JS 会被打包成逻辑层代码(app-service.js
),在运行时与逻辑层基础库 WAService.js
相结合,在逻辑层 Webview(或 JSCore)中执行;WXML/WXSS 将会编译成 JS 并拼接成 page-frame.html
,在运行时与视图层基础库 WAWebview.js
相结合,在视图层堆栈的 Webview 中执行。基础库负责利用客户端提供的通信管道,相互建立联系,对小程序和页面的生命周期、页面上虚拟 DOM 的渲染等进行管理,并在必要时使用客户端提供的原生能力。
熟悉小程序的开发者都知道,这样的架构最主要的目的就是禁止业务代码操作 DOM,迫使开发者使用数据驱动的开发方式,同时在小程序推出初期可以避免良莠不齐的 HTML 项目快速攻占小程序平台,后期则可以缓解小程序平台上的优质产品流失。
kbone 是什么
从 2017 年初小程序推出开始,业界最关心的就是小程序能否转为普通的 Web 开发。最初我们只能简单的用 Babel 进行 JS 的转换;后来小程序推出了 web-view 组件,开发者则开始想办法让 Web 页面使用小程序能力;在知道了 web-view 中的消息不能实时传到小程序逻辑层后,大家则开始选择妥协,改用语法树转换的方式来实现。很多小程序开发框架都是在这一个阶段产生的,如 Wepy、Labrador、mpvue 和 Taro。
语法树转换终究是不可靠的——在 Wepy 和 Taro 的使用中,我们常常会碰到很多语法无法识别的坑,坑的数量与代码量成正比。因此,这些框架更适用于从零开始写,而不适合将一个大型项目移植到小程序。
kbone 是微信团队开源的微信小程序同构框架,与基于语法树转换的 Wepy、Taro 等传统框架不同,kbone 的思路是在逻辑层用类似 SSR 的方式模拟出 DOM 和 BOM 结构,让逻辑层的 HTML5 代码正常运行;而 kbone 会负责将逻辑层中的虚拟 DOM 以 setData 的形式传递给视图层,让视图层利用小程序组件递归渲染的能力,产生出真实的 DOM 结构。
使用 kbone 之后,我们可以将小程序页面理解为一个独立的 html 文档(而不是 SPA 中的一个 router page)。在每个页面的 JS 中初始化 kbone,为逻辑层提供虚拟 DOM 和 BOM 的环境,然后就可以像 H5 一样加载各种主流前端框架和业务代码,kbone 会负责逻辑层和视图层之间的 DOM 和事件同步。
让 kbone 支持 HTML5 inline SVG
在 HTML 中,SVG 的引入有很多种不同的方式,可以像图片一样使用 <img>
标签、background-image
属性,也可以直接在 HTML 中插入 <svg>
标签,另外还有 <object>
、<embed>
等不太常见的方式。
在一些大型 web-view 项目迁移到 kbone 的过程中,常常会遇到 HTML inline SVG(在 HTML 中直接插入 SVG 标签)这种情况;有的页面还会异步加载一个含有很多小图标(<symbol>
)的大 SVG、在页面上用 <use xlink:href="#symbol-id">
的方式,实现 SVG 的 Sprite 化。
本文针对单个页面上出现大量 HTML inline SVG 的实战场景,通过识别并转换成 background-image
,来实现小程序 kbone 对 SVG 的支持。
构造用例
首先我们以 kbone 官方示例 为基础,导入该项目后,在项目根目录新建 kbone-svg.js
,然后进入 /pages/index/index.js
,在 onLoad()
的结尾先写出调用方式和示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
Page({ data: ..., onLoad(query) { ... init(this.window, this.document) this.setData({ pageId: this.pageId }) this.app = this.window.createApp() this.window.$$trigger('load') this.window.$$trigger('wxload', { event: query }) // 添加我们的调用方式和示例 require('../../svg.js') |