Code前端首页关于Code前端联系我们

小程序架构与技术基本实现的原则与思考

terry 2年前 (2023-09-23) 阅读数 65 #移动小程序

作者:Berwin

致力于小程序的基本实现。在此过程中,他跌跌撞撞,对方向上的架构进行了多次改造。趁着周末写一篇文章记录一下。下面讲一下开发过程中遇到的问题以及一些想法和决定。

本文更多的是描述架构和技术方向层面的思考和决策。对于具体问题如何解决,不会有过多的想象,因为细节太多。

1。单线程

当时我把我们的小程序定位为SPA(单页应用程序),因为我们小程序的托管环境是浏览器。

它看起来只是一个小程序(因为这个窗口没有地址栏什么的),但实际上大多数功能,包括UI渲染和事件交互,都是基于Web技术的,尽管一些native和OS会可以询问功能和 API,但它基本上是一个网站。考虑到目前很多人使用第三方工具用 Vue 或 React 编写小程序,我想:“反正本质上也是一个网站,那为什么不原生嵌入 Vue,让用户可以直接用 Vue 的语法编写小程序呢?” 。

所以我们当时定下了一个基本方向:让开发者使用Vue来开发我们的小程序,并且开发体验与web完全一致。

虽然开发体验与Web一致,但Web技术是如此开放,开发者可以为所欲为。这种情况在小程序中是不允许的。不允许使用不允许直接跳转到其他在线网站,开发者也不允许碰它。 DOM、一些未知的、危险的API不允许使用等视图模板中不允许使用。

如果你想这样做,你需要托管和转换视图渲染层。 ? 第一个解决方案功能有限,一些视图的内部逻辑无法通过polyfill改变。第二种方案的缺陷在于,如果我只想改Vue中的某一段逻辑,如果其他部分有我不改的bug,那么当Vue正式更新版本时我不会同步它们。

两种解决方案都有缺陷和缺点,所以我没有使用这两种解决方案。我使用了另一种解决方案,我认为它应该是迄今为止最好的解决方案。 ?将代码复制到自己的项目目录下进行修改

因为我需要改造渲染层,所以需要重新设置别名web,如下:

const path = require('path')

module.exports = {
  'vue$': path.resolve(__dirname, '../src/web/entry-runtime-with-compiler'),
  compiler: 'vue/src/compiler',
  core: 'vue/src/core',
  shared: 'vue/src/shared',
  web: path.resolve(__dirname, '../src/web'),
  weex: 'vue/src/platforms/weex',
  server: 'vue/src/server',
  sfc: 'vue/src/sfc'
}

总体原则是:如果❀如果你import 不需要修改,它实际上是importnode_modules中的原始视图文件。如果需要改import,其实import就是我的目录,文件也是我改的文件。

如果您对这项技术感兴趣,可以留言详细讨论。由于不是本文的重点,所以不再赘述。

1.2 为标签设置黑名单

view 经过一系列计算后最终产生的结果是一些指令,比如创建 DOM 元素、移除 DOM 元素、插入到某个位置等。

所以当时的做法就是简单地使用tagName来检查创建DOM元素时是否被列入黑名单。如果它在黑名单中,就会触发警告并采取措施。

但事实上这种做法只能是:防主,不防小人。

1.3 遇到的问题

这里做项目时出现的一个问题是,无论如何,都没有办法阻止开发者做一些我们想要禁用的功能。既然是网站,开发者就可以运行JS、操作DOM、操作BOM等等。

所以我们开始考虑将用户代码放在绝对安全的沙箱中执行。

2。双线程

在Web技术下,用户的代码可以放入Web Worker中执行,也可以放入隐藏的iframe中执行,或者宿主环境提供环境。不管怎样,目的都是一样的,就是将用户的代码放在绝对安全的沙箱中执行。

由于开发者是基于Vue.js开发,用户的代码无法单独在沙箱中执行,所以我也将Vue放在沙箱中执行。

此时技术架构和技术方向调整为主从双线程模式。

我将沙盒中的代码称为master。它经过一系列的计算,最终输出一些指令:创建元素、改变元素、设置元素、轨道跳转、事件连接等基本指令。这些指令通过线程间消息传递机制从沙箱传递到网页。这个网站有一个叫做slave的模块,负责监听master的指令,并根据指令执行指定的操作。 小程序架构与技术的底层实现原理及思考

将 Vue 放入 Web Worker 中需要解决很多问题,例如:Vue 原来对 DOM 的直接操作必须转换为向另一个线程发送指令,以及事件连接问题和事件对象问题。事件修饰符(event.preventDefault)问题、路由控制(双向)问题、表单元素双向绑定问题、ref问题等。字符串,很多东西都会变得很烦人。

不过,具体的技术问题还是比较容易解决的。比较困难的问题有两个:“性能”和“原生能力有限”。

2.1 性能

在这种架构下,当页面有较大的UI变化时(比如首次渲染),逻辑线程必须向UI线程发送大量指令,包括:创建DOM、进入DOM、和绑定事件。等等,每条指令都是独立的跨线程消息通信。当消息数量巨大时,性能问题就暴露出来并且非常明显。

如果你去Chrome DevTools的性能面板,你会发现UI线程其实很空闲,但是渲染却很慢。因为传递消息的成本,以及所有编码解码的成本,我自己写DEMO的时候并没有发现任何问题,但是当我把它放到生产环境中制作真正的组件时,我发现性能问题非常明显。

虽然这个架构下存在性能问题,但以我的技术解决这个问题并不是太难。另外一个问题(原生能力有限)是这个架构下永远无法解决的问题。

2.2 Native能力有限

为了安全,用户的代码必须沙箱执行,但既然我们要用户使用Vue进行开发,那么Vue也必须被设置沙箱,这就导致了一个无法解决的问题我们官方提供的原生组件也很有限。

原因很简单。假设用户在模板中使用了官方原生组件,那么这个组件必须提前在view中注册,这意味着我们官方提供的组件也必须是沙箱的。 ,所以我们也限制了自己。

因此,我们想要提供官方的视频组件,但是音频组件却非常困难,因为我们的官方组件无法触及DOM和BOM,更不用说提供其他原生能力的组件了。

当然,还是有办法在不改变架构的情况下解决这个问题的。解决方案如下:

官方组件先在沙箱中注册一个视图组件,然后非特意地实现这个组件。那么UI线程就有一个真实的组件对应这个组件来执行特定的功能,最终渲染到UI上。 小程序架构与技术的底层实现原理及思考

每个组件都要这样做,这对于开发组件的同学来说非常不友好。他们需要了解这个小程序的底层数据架构是如何工作的,也会增加很多工作量。

这不是我想要的。我希望的是,无论我的底层是单线程还是双线程,我都不会意识到顶层的开发。而且由于Vue.js在沙箱中进行各种操作,不确定未来是否存在无法满足的需求,技术风险太高。

此时我慢慢开始感觉到Vue已经成为了一个瓶颈,它限制了我。

3。回到单线程

不仅仅是技术原因,还有一些其他的原因,比如风险太大,时间太短,我们最终决定先把计划切换回单线程,这样至少可以确保这个项目不会被推迟。 。然后对方会慢慢研究并制定出可以解决之前遇到的所有问题的技术方案。

看来我们又回到了起点,因为单线程有一个单线程的问题,那就是Web技术过于开放,我们无法做到对开发者的“安全”和“控制”。

不过,我们还是找到了可以在单线程模式下提高一些安全性的解决方案。解决方案是通过ShadowDOM的close模式来锁定body。这样,开发者自己的代码就无法操作DOM,因为它被我锁定了,但是开发者的JavaScript是免费的。 小程序架构与技术的底层实现原理及思考

在这个架构下,开发者可以操作 DOM、BOM、Vue.js 等一切,但它不能直接操作我锁定的 ShadowDOM。如果想要操作这个ShadowDOM,就必须通过一个合法的Path操作,而这个ShadowDOM就是用来显示小程序的主窗口。

那么BOM上的一些危险API将会被提前禁用。

这个方案看似解决了所有问题,但还是给未来留下了一些隐患。只要开发者的 JavaScript 是免费的,你就永远不会知道他用他的 JavaScript 做了什么。对于一些未知的漏洞来说,可能是非常危险的,这给未来留下了官方和开发者之间的攻防战的风险。

不管怎样,这个方案解决了目前遇到的所有问题,也让我有很多时间去研究如何正确制作小程序。 4。回到双线程

最后我发现双线程是正确的方向。只有把用户的代码放在沙箱中执行,才能真正做到:“安全”和“控制”。

但这一次我决定不再使用Vue.js。我需要开发一个新的框架来支持双线程模式。上次双线程失败的主要原因是UI线程比较轻,Logic线程比较重。用户的代码、Vue.js、官方组件都运行在逻辑线程下,而这个线程只是一个 JS 运行时,所以我们的原生能力是有限的。

这次我决定让UI重一点,逻辑轻一点。只有部分代码和用户框架转移到逻辑线程,大部分操作和原生组件都在UI线程上执行。 小程序架构与技术的底层实现原理及思考

工作线程只是进行一些计算并将数据传递给UI线程。那么大部分工作都是在UI下完成的,官方的组件都是在UI端执行的。

这样可以解决之前遇到的两个问题:性能有限和原生能力有限。

因为线程之间的消息通道只是传输数据,而数据并没有绘制UI指令的数量那么多,可以说根本不是一个大小的。性能问题解决了,不仅性能问题解决了,而且还得到了提升。性能,因为无论用户代码的执行效率有多低,UI线程都不会挂起。

原生能力有限的问题也解决了,因为官方提供的组件根本不在这个线程下运行,安全和控制问题也解决了。

5。一些思考

之前,我一直以为其他小程序都是Native做的,都是基于Web技术来实现的。但是当我随机看到一些信息时,我发现每个人都是基于网络技术的。小程序的实现方法都差不多,遇到的问题也差不多。

也许唯一的区别是我不需要更多的webview,我只需要一个网页,所以我可以把逻辑线程的代码放在webworker中,而其他小程序是多个webview,所以它们可以没有使用网络工作者,但没有显着差异。

5.1 关于跨平台小程序的一些看法

前段时间,各种小程序多端框架满天飞。准确地说,这些框架都是“翻译器”。我个人认为翻译是各种应用的基础。小程序之间的跨端实现只能算是一个临时的技术方案。

我认为真正终极的技术解决方案有两个:

  1. 从渲染引擎层面消除平台之间的差异,例如:Flutter
  2. 每个小程序开发者共同制定一些标准和规范,例如:冲浪

第一种是利用小程序提供的Canvas的一些API来实现一个渲染引擎,然后在渲染引擎上实现一些布局引擎。在此基础上,框架和其他提供的能力都是统一的,在不同的平台上只需要实现不同的渲染引擎即可。不过我不确定小程序提供的canvas是否可以做到这一点,但是网页浏览器提供的canvas可以,SpriteJS也可以。

第二种选择是所有大小程序厂商共同制定一套标准,并按照标准实现自己的API。这种情况比较好,也不是完全不可能。最近,几家大大小小的软件厂商在W3C起草了一份小型软件白皮书。

我会列出白皮书中的重要内容:

  1. 标准化小程序包(即可以在各种大小程序平台上解析的小程序代码,具有统一的.ma后缀文件)
  2. 标准化小程序页面的URI方案(即定义一个协议,然后同一个URI地址可以在不同的小程序平台上打开相同的页面)
  3. 标准化小程序小部件

6。总结

仔细阅读本文的读者应该对我开发小程序的整个流程和一些决定有一个大致的了解。

均采用双线程模型进行小程序底层实现。大家对外宣称是为了:

  1. 方便不同页面之间的数据交换和交互
  2. 更好的编码,让原生开发者体验
  3. 为了性能(防止用户阻塞JS的执行)
  4. 其他好处

但其实真正的原因是:“安全”和“控制”,再加上其他原因。

因为Web技术是完全开放的,JavaScript可以做任何事情。但对于小程序来说,开发者并没有被给予这么高的权限:

  • 不允许开发者跳转到其他在线网站
  • 不允许开发者直接访问 DOM
  • 不允许开发 D '读者可以在Windows上随意使用一些未知的、有潜在危险的API

当然,不一定要用双线程模型来解决这些问题,但双线程模型无疑是最合适的技术方案。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门