大型 Web 应用插件化架构探索

简介: 随着 Web 技术的逐渐成熟,越来越多的应用架构趋向于复杂,例如阿里云等巨型控制台项目,每个产品下都有各自的团队来负责维护和迭代。不论是维护还是发布以及管控成本都随着业务体量的增长而逐渐不可控。在这个背景下微前端应用而生,微前端在阿里内部已经有许多成熟的实践,这里不再赘述。本文以微前端为引子(蹭热度),探讨一些另类的 Web 应用所面临的类似问题。

 

前言

随着 Web 技术的逐渐成熟,越来越多的应用架构趋向于复杂,例如阿里云等巨型控制台项目,每个产品下都有各自的团队来负责维护和迭代。不论是维护还是发布以及管控成本都随着业务体量的增长而逐渐不可控。在这个背景下微前端应用而生,微前端在阿里内部已经有许多成熟的实践,这里不再赘述。本文以微前端为引子,探讨一些另类的 Web 应用所面临的类似问题。

现代文本编辑器沉浮

2018年微软 GitHub 后,Atom便经常被拿来调侃,所谓一山不容二虎。在 VS Code 已经成为一众前端工程师编辑器首选的当下,Atom 的地位显得很尴尬,论性能被同为 Electron 的 VS Code 秒杀,论插件,VS Code 去年插件总数就已经突破 1w 大关,而早发布一年多的 Atom 至今还停留在 8k +。再加上微软官方主导的 LSP/DAP 等重量级协议的普及,时至今日 Atom 作为曾经 Web/Electron 技术标杆应用的地位早已被 VS Code 斩落马下。

 

网上关于 Atom 的日渐衰落的讨论,始终离不开性能。Atom 的确太慢了,究其原因很大程度上是被其插件架构所拖累的。尤其是 Atom 在 UI 层面开放过多的权限给插件开发者定制,插件质量良萎不齐以及 UI 完全开放给插件后带来的安全隐患都成为 Atom 的阿喀琉斯之踵。甚至其主界面的 FileTree、Tab 栏、Setting Views 等重要组件都是通过插件实现的。相比之下 VS Code 则封闭很多,VS Code 插件完全运行在 Node.js 端,对于 UI 的定制性只有极个别被封装为纯方法调用的 API。

 

但另一方面,VS Code 这种相对封闭的插件 UI 方案,一些需要更强定制性的功能便无法满足,更多插件开发者开始魔改 VS Code 底层甚至源码来实现定制。例如社区很火的 VS Code Background,这款插件通过强行修改 VS Code 安装文件中的 CSS 来实现编辑器区域的背景图。而另一款 VSC Netease Music 则更激进,因为 VS Code 捆绑包中的 Electron 剔除了 FFmpeg 导致在 Webview 视图下无法播放音视频,使用此插件需要自行替换 FFmpeg 的动态链接库。而这些插件不免会对 VS Code 安装包造成一定程度的破坏,导致用户需要卸载重装。

不止编辑器 - 飞个马

Figma 是一个在线协作式 UI 设计工具, 相比 Sketch 它具有跨平台、实时协作等优点,近年来逐渐受到 UI 设计师们的青睐。而近期 Figma 也正式上线了其插件系统。

 

作为一个 Web 应用,Figma 的插件系统自然也是基于 JavaScript 构建的,这一定程度上降低了开发门槛。自去年6月份 Figma 官方宣布开放插件系统测试以来,已经有越来越多的 Designner/Developer 开发了300+ 插件,其中包括图形资源、文件归档、甚至是导入 3D 模型等。

 

Figma 的插件系统是如何工作的?

这是一个基于 TypeScript + React 技术栈,使用 Webpack 构建的 Figma 插件目录结构

.
├── README.md
├── figma.d.ts
├── manifest.json
├── package-lock.json
├── package.json
├── src
│   ├── code.ts
│   ├── logo.svg
│   ├── ui.css
│   ├── ui.html
│   └── ui.tsx
├── tsconfig.json
└── webpack.config.js

在其 manifest.json 文件中包含了一些简单的信息。

{"name": "React Sample","id": "738168449509241862","api": "1.0.0","main": "dist/code.js","ui": "dist/ui.html"
}

可以看出 Figma 将插件入口分为了 main 与 ui 两部分, main 中包含了插件实际运行时的逻辑,而 ui 则是一个插件的 HTML 片段。即 UI 与逻辑分离。安装一个Color Search 插件后观察页面结构可以发现 main 中的 js 文件被包裹在一个 iframe 里加载到页面上,关于 main 入口的沙箱机制后文中有详细的阐述。而 ui 中的 HTML 最终也被包裹在一个 iframe 里渲染出来,这将有效的避免插件 UI 层 CSS 代码导致全局样式污染。

 

Figma Developers 文档中 有一章节 How Plugins Run 对其插件系统运行机制进行了简单的介绍,简单来说 Figma 为插件中逻辑层的 main 入口创建了一个最小的 JavaScript 执行环境,它运行在浏览器主线程上,在这个执行环境中插件代码无法访问到一些浏览器全局的 API,从而也就无法在代码层面对 Figma 本身运行造成影响。而 UI 层有且仅有一份 HTML 代码片段,在插件被激活后被渲染到一个弹窗中。

 

Figma 官方博客中对其插件的沙箱机制做了详细的阐述。起初他们尝试的方案是 iframe,一个浏览器自带的沙箱环境。将插件代码由 iframe 包裹起来,由于 iframe 天然的限制,这将确保插件代码无法操作 Figma 主界面上下文,同时也可以只开放一份白名单 API 供插件调用。乍一看似乎解决了问题,但由于 iframe 中的插件脚本只能通过 postMessage 与主线程通信,这导致插件中的任何 API 调用都必须被包装为一个异步 async/await 的方法,这无疑对 Figma 的目标用户非专业前端开发者的设计师不够友好。其次对于较大的文档,postMessage 通信序列化的性能成本过高,甚至会导致内存泄漏。

Figma 团队选择回到浏览器主线程,但直接将第三方代码运行在主线程,由此引发的安全问题是不可避免的。最终他们发现了一个尚在 stage2 阶段的草案 Realm API。Realm 旨在创建一个领域对象,用于隔离第三方 JavaScript 作用域的 API。

let g = window; // outer global
let r = new Realm(); // root realmlet f = r.evaluate("(function() { return 17 })");f() === 17 // trueReflect.getPrototypeOf(f) === g.Function.prototype // false
Reflect.getPrototypeOf(f) === r.globalThis.Function.prototype // true

值得注意的是,Realm 同样可以使用 JavaScript 目前已有的特性来实现,即 with 与 Proxy。这也是目前社区比较流行的沙箱方案。

const whitelist = {windiw: undefined,document: undefined,console: window.console,
};const scopeProxy = new Proxy(whitelist, {get(target, prop) {if (prop in target) {return target[prop]}return undefined}
});with (scopeProxy) {eval("console.log(document.write)") // Cannot read property 'write' of undefined!eval("console.log('hello')")        // hello
}

前文中 Figma 插件被 iframe 所包裹的插件 main 入口即包含了一个被 Realm 接管的作用域,你可以认为是类似这段示例代码中的一份 白名单 API,毕竟维护一份白名单比屏蔽黑名单实现起来更简洁。但事实上由于 JavaScript 的原型式继承,插件仍然可以通过 console.log 方法的原型链访问到外部对象,理想的解决方案是将这些白名单 API 在 Realm 上下文中包装一次,从而彻底隔离原型链。

const safeLogFactory = realm.evaluate(`(function safeLogFactory(unsafeLog) { return function safeLog(...args) {unsafeLog(...args);}})
`);const safeLog = safeLogFactory(console.log);const outerIntrinsics = safeLog instanceOf Function;
const innerIntrinsics = realm.evaluate(`log instanceOf Function`, { log: safeLog });
if (outerIntrinsics || !innerIntrinsics) throw new TypeError(); realm.evaluate(`log("Hello outside world!")`, { log: safeLog });

 

显然为每一个白名单中的 API 做这样操作的工作是非常繁杂且容易出错的。那么如何构建一个安全且易于添加 API 的沙箱环境呢?

Duktape 是一个由 C++ 实现的用于嵌入式设备的 JavaScript 解释器,它不支持任何浏览器 API,自然地它可以被编译到 WebAssembly,Figma 团队将 Duktape 嵌入到 Realm 上下文中,插件最终通过 Duktape 解释执行。这样可以安全的实现插件所需 API,且不用担心插件会通过原型链访问到沙箱外部。

 

这是一种被称为 Membrane Pattern 的防御性的编程模式,用于在程序中与子组件(广义上)实现一层中介。简单来说就是代理(Proxy),为一个对象创建一个可控的访问边界,使得它可以保留一部分特性给第三方嵌入脚本,而屏蔽一部分不希望被访问到的特性。关于 Membrane 的详细论述可以查看 Isolating application sub-components with membranes 与 Membranes in JavaScript 这两篇文章。

 

这是最终 Figma 的插件方案,它运行在主线程,不需要担心 postMessage 通信带来的传输损耗。多了一次 Duktape 解释执行的消耗,但得益于 WebAssembly 出色的性能,这部分消耗并不是很大。

另外 Figma 还保留了最初的 iframe ,允许插件可以自行创建 iframe ,并在其中插入任意 JavaScript ,同时它可以与沙箱中的 JavaScript 脚本通过 postMessage 相互通信。

鱼和熊掌如何兼得?

我们把这类插件的需求总结为在 Web 应用中运行第三方代码及其自定义控件,它有与开头提到的微前端架构非常相似的一些问题。

  1. 一定程度上的 JavaScript 代码沙箱隔离机制,应用主体对第三方代码(或子应用)有一定的管控能力
  2. 样式强隔离,第三方代码样式不对应用主体产生 CSS 污染

JavaScript 沙箱

JavaScript 沙箱隔离在社区是个经久不衰的话题,最简单的 iframe 标签 Sandbox 属性就已经能做到 JavaScript 运行时的隔离,社区较为流行的是利用一些语言特性(with、realm、Proxy 等 API )屏蔽(或代理) Window、Document 等全局对象,建立白名单机制,对可能潜在危险操作的 API 重写(如阿里云 Console OS - Browser VM)。另外还有 Figma 这种尝试嵌入平台无关的 JavaScript 解释器,所有第三方代码都通过嵌入的解释器来执行。以及利用 Web Worker 做 DOM Diff 计算,并将计算结果发送回 UI 线程来进行渲染,这个方案早在 2013 年就已经有人进行了实践,这篇论文中作者将 JSDOM 这一 Node.js 平台广泛流行的测试库运行在 Web Worker。而近些年来也有 preact-worker-demo 、react-worker-dom 等项目基于 Web Worker 的 DOM Renderer 尝试将 DOM API 代理到 Worker 线程。而 Google AMP Project 在JSCONF 2018 US 对外公布的 worker-dom 则将 DOM API 在 Web Worker 端实现了 DOM API,虽然实践下来还存在一些问题(例如同步方法无法模拟),但 WorkerDOM 在性能和隔离性上都取得了一定成果。

 

以上这些解决方案被广泛的应用在各种插件化架构的 Web 应用中,但大多都是 Case By Case,每种解决方案都有各自的成本与取舍。

CSS 作用域

CSS 样式隔离方案中,如上文中 Figma 使用 iframe 渲染插件界面,牺牲一部分性能换来了相对完美的样式隔离。而在现代前端工程化体系下,可以通过 CSS Module 在转译时对 class 添加 hash 或 namespace 等方式实现,这类方案较为依赖插件代码编译过程。而更新潮的是利用 Web Component 的 Shadow DOM,将插件元素用 Web Component 包裹起来,Shadow Root 外部样式无法作用于内部,同样 Shadow Root 内部的样式也无法影响到外部。

 

最后

本文列举了目前编辑器、设计工具这类大型 Web 应用插件化架构下所面临的的一些问题,以及社区实践的解决方案。不论是让人又爱又恨的 iframe ,还是 Realm、Web Worker 、 Shadow DOM 等,目前来说每种方案都有各自的优势与不足。但随着 Web 应用的复杂度增长,插件化这一需求也逐渐被各大标准化组织所重视起来。下一篇将着重介绍 KAITIAN IDE 中插件架构的探索与实践,包括 JavaScript 沙箱、CSS 隔离、Web Worker 等。

作者:开发者小助手_LS

原文链接

本文为阿里云原创内容,未经允许不得转载

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/514057.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

高精地图技术专栏 | 基于空间连续性的异常3D点云修复技术

简介: 我们需要通过激光的内部机制和数据处理算法,将这些噪声恢复到它本来的位置。本文会从MTA问题产生的原理、激光应对MTA的内部机制、数据处理算法三方面来介绍高精资料处理是如何解决这个问题的。 1.背景 1.1 高精资料采集 高精采集车是集成了测绘…

pytorch 图像分割的交并比_「通知」《深度学习之图像识别》再版工作启动,欢迎指导和提建议...

不知不觉有三第一本书也上市超过1年半了,第一次写作略显稚嫩,许多细节处理不当,比如没有在印刷前核对公式,被删掉了参考文献影响读者阅读体验等。现在本书正式启动再版工作,注意不是重印,是再版。重印只是修…

mysql实例怎么复制_Mysql实例MySQL数据库复制概论

《Mysql实例MySQL数据库复制概论》要点:本文介绍了Mysql实例MySQL数据库复制概论,希望对您有用。如果有疑问,可以联系我们。导读:每当我们讨论一项(新的)领域技术的时候,最好的方式通常是首先抛出一些问题,这些问题大致分为三类&a…

app inventor离线版_百度要哭了!今日头条出了搜索引擎了,还做了APP

今天小编为大家送上几条新闻了:1 微软给Edge浏览器添加离线小游戏谷歌浏览器在不联网的情况下,会有一个小恐龙跳一跳的游戏,而最近换上chromium内核的edge,也添加了自己的离线小游戏。这是一款冲浪游戏,非常类似于微软…

打车业务下单高并发解决方案

简介: 打车业务下单高并发解决方案前言 在技术领域有一条准则,即不存在银弹技术。在实际工作中,通常无法通过几项简单的技术组合就解决实际业务中各种场景下的复杂问题。虽然追求架构的简单简洁也是架构师的目标之一。但必须认识到架构的简单…

Serverless 时代 DevOps 的最佳打开方式

简介: 传统软件开发过程中,开发和运维是极其分裂的两个环节,运维人员不关心代码是怎样运作的,开发人员也不知道代码是如何运行的。 作者 | 许成铭(竞霄) 来源 | 阿里巴巴云原生公众号 DevOps 简析 传统软…

2020 q5l使用手册电子版_关于2020下半年自考本科毕业生论文预答辩题目查询的通知...

一、对象:2020下半年获论文预答辩资格毕业生(名单见附件1)二、查询方法:请登录以下网址: 输入个人准考证和姓名,查询论文预答辩题目。三、相关要求1. 请将《自学考试本科毕业论文预答辩记录表》(见附件2)中个人相关信息以及预答辩…

现实版“奇异博士”?原来是这款神秘的“数学黑盒”

简介: 现实版“奇异博士”?原来是这款神秘的“数学黑盒”供稿团队:阿里云市场部在漫威电影《复联3》中,奇异博士预测到未来有1400多万种可能,而胜利却只有一种,这唯一的最优解成为他们战斗的希望。只可惜&a…

如何在Spring生态中玩转RocketMQ?

简介: RocketMQ作为业务消息的首选,在消息和流处理领域被广泛应用。而微服务生态Spring框架也是业务开发中最受欢迎的框架,两者的完美契合使得RocketMQ成为Spring Messaging实现中最受欢迎的消息实现。本文展示了5种在Spring生态中文玩转Rock…

enum 有什么好处_林卡尔|先买地板后装修的三大好处

【中华地板网】现在很多装饰公司和设计师, 提倡的装修新模式——“先定地板后装修”。 后装修并不是买完地板后再开工, 而是在开工之前, 先把地板风格、颜色、尺寸、价位定下, 然后让设计师根据地板进行其他设计。 那么这样做究竟…

华为推出业界首个分布式云原生产品:华为云UCS,持续创新,深耕数字化

华为面向ICT产业的全球年度旗舰活动——华为全联接2021拉开序幕。华为轮值董事长徐直军进行了“以持续创新加快数字化发展”的大会主题演讲。 本次大会围绕“深耕数字化”主题,探讨如何深入业务场景,把数字技术与行业知识深度结合,解决核心业…

Flink 必知必会经典课程四:Fault-tolerance in Flink

简介: 本文由 Apache Flink PMC , 阿里巴巴高级技术专家李钰分享,主要从有状态的流计算、全局一致性快照 、Flink的容错机制、Flink的状态管理 四个方面介绍 Flink 的容错机制原理。 作者 | 李钰 分享人:本文由 Apache Flink PMC , 阿里巴巴…

用云原生的思维践行云原生,华为云深耕数字化,一切皆服务

[中国,深圳,2021年9月23日] 华为全联接2021于9月23日开幕。华为轮值董事长徐直军进行了“以持续创新加快数字化发展”的主题演讲,发布业界首个分布式云原生产品——华为云UCS,即 “无处不在的云原生服务”。华为高级副总裁、华为云…

白苹果了怎么办_苹果手机出现白苹果、死机、不断重启怎么办?

小伙伴们的苹果设备是否出现过白苹果、不断重启、更新失败、卡机死机、进入了恢复模式等情况呢?小编自己的iPhoneXR就遇到过,不知道怎么操作的就进入了恢复模式,有时候手机卡机需要强制重启,遇到问题就想去外面的修理店解决一下,…

Knative 多容器支持介绍

简介: 微服务和容器化带来了将应用程序分解成可重复使用的小型单元的诉求,这些单元通常作为单独的进程运行,或者在单独的容器运行。 Kubernetes的Pod模型允许用户创建一个部署单元,该单元可以打包多个容器作为应用程序的单个实例。…

GitHub Action + ACK:云原生 DevOps 落地利

简介: 据信通院《中国 DevOps 现状调查报告(2020年)》显示,63% 的企业已经实践落地 DevOps,采用持续交付流水线打通开发、测试、部署和运维多个环节。但是依然有 20% 的企业反馈实践 DevOps 复杂,自建 Jenk…

win7硬盘安装工具_如何重装电脑安装Win7 系统?最简单,最便捷的办法,值得收藏...

没有U盘、没有光盘怎么安装Win7系统?最简单、最方便的就是用硬盘安装了。在保证能进入系统的前提下,本地硬盘安装Win7系统,能够让你快速体验新的Win7系统。一、安装前准备保证能够正常进入系统;下载Win7系统:64位&…

网络架构优化--云企业网典型场景分析for客户

简介: 网络架构优化--云企业网典型场景分析for客户1. 背景描述 客户从传统的高速通道迁移到云企业网,加入云企业网的VPC,VBR默认全通,但是实际业务场景需要更严格的策略做选择性放通。此外,由于测试账号没有实际专线和…

五个问题,三大策略,手把手教你定制App性能监控方案

作者:友盟U-APM团队 Why? 为什么要做应用性能监控? 首先,我们要知道应用性能监控具体指什么?以及目的: 监控是一套完整的“监视报警”的系统。对于像我们这样的App开发者来说,应用性能监控是衡量App的第…

c++ 打印条码_金蝶盘点机PDA仓库条码管理之——外购入库扫码开单操作

优势点:收到供应商送货后,仓管员手持盘点机PDA现场将需要入库的商品按顺序扫描一遍,即可自动生成电脑软件金蝶里的【外购入库单】,避免仓管员往返电脑费时费事,和人工手工电脑录单效率低容易出错的问题,从而…