本文作者:0xcc

Tango 低代码引擎沙箱完结解析

Tango 根本介绍

Tango 是一个用于快速构建低代码渠道的低代码规划器结构,并以源代码为中心,履行和烘托前端视图,并为用户供给低代码可视化建立才能,用户的建立操作会转为对源代码的修正。凭借于 Tango 构建的低代码东西或渠道,能够完结 源码进,源码出的效果,无缝与企业内部现有的研制系统进行集成。

开源发展

现在 Tango 规划器引擎部分现已开源,正在活跃推动中,能够通过如下的信息了解到咱们的最新发展:

此外,Tango 的文档现已全面更新,欢迎浏览。

Tango 低代码引擎沙箱完结解析

欢迎大家加入到咱们的社区中来,一起参与到 Tango 低代码引擎的开源建造中。有任何问题都能够通过 Github Issues 反馈给咱们,咱们会及时跟进处理。

往期系列文章


为什么 Tango 需求沙箱

传统的依据 DSL 的低代码方案通常需求完结一套对应的 DSL 语法与烘托器,在烘托器内烘托给定的组件、绑定事情等。与此不同,Tango 是依据 AST 驱动的面向源码的低代码方案。相较于 DSL 方案,Tango 的写法愈加灵敏,但也带来了支撑源代码实时运转的挑战。此外,为了与团队内已有的物料集成,Tango 支撑增加业务组件,因而规划器还需求考虑三方依靠的加载与运转。因而,Tango 需求一个独立的沙箱来运转源码,供给能够媲美本地开发的代码运转时。

在初期,Tango 曾调研了几种方案,如依据 Sea.js 这类 AMD 加载方案。然而,这类方案的问题在于依靠比较固定,需求将依靠预先构建出契合规范的产品(如 UMD 资源),因而不能灵敏地增加依靠。至于 SystemJS 和 ViteSandbox 这类 ESM 方案,因为 Tango 希望支撑直接运用已有的组件物料,而它们的产品首要以 CommonJS 为主,短少 ESM 产品。此外,咱们后续对沙箱的改造优化大幅减少了沙箱初始化的时刻,因而没有选用该方案。

Tango 现在选用的沙箱方案是依据 CodeSandbox 供给的沙箱才能完结的。它的优势在于供给了更完整、接近本地开发的运转时环境,支撑直接拉取 npm 包并运转。它凭借 Babel 将 ESM 和浏览器不支撑的新语法转译为 CommonJS,模拟了 CommonJS 的运转环境,完结了源码在浏览器上直接运转。这样即使依靠没有供给可供浏览器运用的预构建产品,也能在沙箱内实时转译并运转。此外,CodeSandbox 的沙箱运转在一个 iframe 内,能够阻隔代码的运转时环境,防止污染规划器的全局变量。

Tango 沙箱的根本结构

CodeSandbox 是一个在线运转 JavaScript 代码的渠道,它的沙箱凭借 Babel 与 Web Worker 等才能,在浏览器上实时转译与运转代码。你能够把它的沙箱才能幻想成一个在浏览器上运转的 webpack,比如它的转译器 Transpiler 就和 webpack 的 loader 比较接近。。

因为 CodeSandbox 自己完结了各个模板的转译规则,整个转译流程均由自己把控,因而它全体上会比 webpack 轻量些。例如 CodeSandbox 在初始化依靠时能忽略掉绝大多数的 devDependencies,从而大幅减少项目的依靠初始化时刻与转译时刻。

结合 Tango 后的沙箱能够简化为三个部分:

Tango 低代码引擎沙箱完结解析

  • 沙箱前端组件:一个开箱即用的沙箱组件,只需求传入代码和配置就能够完结应用的烘托
  • 在线打包器:供给建立产品的浏览器端构建才能,类似于一个浏览器版本的 webpack,终究形状是一个独立的 iframe
  • 沙箱后端服务:对依靠的资源进行预构建,以及供给资源合并等服务,用来加快沙箱内部的构建打包进程

它的作业流程能够简述如下:

  • 代码准备:渠道引用沙箱组件,通过 postMessage 将代码传递给沙箱
  • 依靠初始化:沙箱处理传入的文件,依据 package.jsondependencies 调用 Packager 打包服务获取依靠
  • 转译代码:解析代码的依靠联系,将依靠的代码通过对应的 Transpiler 转译
  • 履行代码:在沙箱中初始化 html 等,然后从代码的进口文件开端履行转译后的代码
  • 上述履行周期内和履行完结后,沙箱会抛出事情让渠道感知

Tango 沙箱的作业流程

本部分首要参考了 CodeSandbox 怎么作业? 上篇 的部分内容,并在此基础上进行了修正。假如你对 CodeSandbox 底层的更多细节感兴趣,不妨阅读下这篇文章。

依靠的初始化

如前所述,CodeSandbox 在内部完结了中心的转译逻辑(例如 Babel 与 less 转译),整个转译流程都由自己操控,因而在初始化依靠时能够相对轻量一些,只需获取 dependencies 里必要的依靠,忽略掉 devDependencies 以及 @types 开头的只在本地开发时才会用上的依靠。

CodeSandbox 是怎么获取依靠的呢?CodeSandbox 完结了两套方案,一套是默认的远程在线打包方案,另一套是从 unpkg/jsdelivr 等 npm 包资源的 CDN 获取依靠的兜底方案。

CodeSandbox 规划了一个 Serverless 服务 dependency-packager,这个服务担任在线拉取依靠,然后一次性回来包含子依靠在内的所有需求的文件。当服务接纳到接口恳求后,会解析 URL 中的包名与版本号,并在服务端履行 yarn install 装置 npm 包,然后从进口文件开端逐一解析依靠的文件以及各个包之间的依靠联系,终究将被依靠的文件一次性回来。因为该服务仅回来被依靠的文件,在减少网络恳求的资源巨细的同时,沙箱能够防止转译 .d.ts 或测试用例这样运转时不需求的文件。

不过因为 packager 回来的文件是从包的进口文件开端核算的被引进的文件,因而在实际运用中,一些未被引进的文件或许也会被项目运用。当项目引进了被排除的资源时,沙箱会在前端恳求 unpkg/jsdelivr 作为兜底方案,从而顺利完结转译。当然,缺点便是假如缺失的文件比较多,实时获取的方案会多出许多的网络恳求开支。因而 CodeSandbox 还运用了 Service Worker 作资源缓存,减少二次复访的网络恳求。

转译与构建

当 CodeSandbox 开端转译时,会调用 compile() 办法开端转译,整个转译流程大致如下:

Tango 低代码引擎沙箱完结解析

传入沙箱的参数除了代码外,还需求传入 template 参数,该参数用于指定沙箱转译时需求运用的 Preset。Preset 就像 webpack 的配置文件相同,内部界说了怎么预处理依靠、不同的文件该运用哪些 Transpiler、在代码履行前做一些其他的操作等。

Preset 初始化好后,沙箱将初始化一个 Manager 实例,这个 Manager 实例会被 compile() 运用,用于操控整个转译流程的生命周期。然后,Manager 会按照上一节说到的方式初始化项目的依靠。假如传入的依靠发生了变更,沙箱会从头初始化一个新的 Manager 实例,防止运转时被旧的 Manager 依靠影响。

依靠准备好后,传入沙箱的代码会被传入 Manager,Manager 会将代码实例化为 TranspiledModule,解析各模块的依靠联系,核算是否被更新或删除等。然后沙箱将从代码的进口模块开端,依据 Preset 里界说的规则,对每一个模块递归调用指定的 Transpiler 转译。这儿 Transpiler 就像 webpack 的 loader 相同,担任将文件转译为需求的产品。对于复杂的 Transpiler——例如担任转译 JavaScript 的 BabelTranspiler——还会运用 Web Worker 队列来提升转译功率。

当相关的模块都被转译好后,Manager 会进入代码履行阶段。

代码履行

沙箱的运转时模拟了 CommonJS 所需的环境,如 requiremoduleexportsglobal 等办法与变量。当所有需求的模块都被转译好后,Manager 会进入代码履行阶段。代码履行的中心代码如下:

const allGlobals: { [key: string]: any } = {
  require, module, exports, process, global, ...globals,
};
const allGlobalKeys = Object.keys(allGlobals);
const globalsCode = allGlobalKeys.length
  ? allGlobalKeys.join(', ') : '';
const globalsValues = allGlobalKeys.map(k => allGlobals[k]);
const newCode =
  `(function $csb$eval(` + globalsCode + `){` + code + `n})`;
// @ts-ignore
(0, eval)(newCode).apply(allGlobals.global, globalsValues);
return module.exports;

沙箱会从进口模块开端履行,履行时会将代码封装为上述的立即履行函数,然后调用 eval() 履行并传入上述 CommonJS 的办法与变量。若代码引用了其他文件,履行时调用的 require() 办法会按照相同的逻辑递归履行并回来履行后的产品。

通过上述流程后,项目中的代码就会被转译并履行,终究烘托在沙箱里,你就能看到代码的实际效果了。

沙箱的优化改造

在 Tango 上开发的应用是一个完整的项目,并非像 CodeSandbox 网站上那样首要用于承载简略的示例或代码片段。因而用户对沙箱本身的构建功能与加载速度有较高的要求,以满意日常的开发体会。

关于咱们对 CodeSandbox 优化的详细细节,能够参考咱们之前的这篇 云音乐低代码:依据 CodeSandbox 的沙箱功能优化 ,修正后的 CodeSandbox 代码也能够在 GitHub 上找到。

接入 Tango 沙箱

Tango 低代码规划器除了需求让沙箱运转源码、烘托页面以外,还需求完结可视化建立的拖拽才能,因而规划器需求感知到用户在沙箱内的操作。可是,因为沙箱运转在一个独立的 iframe 内,而且布置在独立的域名下,两者之间是跨域的,因而需求做跨域兼容。通过将规划器渠道与沙箱的 document.domain 均设为相同的父域名,并针对 Chrome 的安全策略 在渠道与沙箱增加 Origin-Agent-Cluster: ?0 的 HTTP 呼应头,就能完结渠道与沙箱的跨域通讯。

为了简化沙箱的运用成本,咱们封装了一个 React 组件 @music163/tango-sandbox 供规划器运用,相关代码能够在 Tango 的 GitHub 仓库里找到。它首要分为如下三个部分:

  • IFrameProtocol:担任与沙箱通讯。通过监听 message 事情接纳从沙箱传出的音讯,以获取沙箱自动传出的生命周期。通过在 iframe 内部调用 postMessage() 办法向沙箱传递事情,从而操控沙箱。
  • PreviewManager:担任办理沙箱的根本烘托。其凭借上面的 IFrameProtocol 与沙箱通讯,当代码发生变化时,会向沙箱发送 compile 音讯,从而触发沙箱的构建与烘托。
  • Sandbox:用于烘托沙箱的 React 组件。除了挂载沙箱的 iframe 外,还包含了沙箱配置、注册事情监听函数、音讯传递、路由办理等功能。当组件传入的 props 发生变化时,会相应地更新沙箱代码、更新 iframe 路由等。

Tango 低代码引擎通过向 Sandbox 组件传入 files 来完结代码的烘托,并传入 eventHandler 来监听用户在沙箱内的拖拽操作,终究完结了规划器的组件拖拽建立才能。

不过,沙箱获取依靠的根本才能首要是 CodeSandbox 供给的 packager 与 JSDelivr、unpkg 供给的,假如需求运用团队内部的私有 registry 就需求将相关服务私有化布置了。限于篇幅就不在此做过多赘述,关于 Tango 沙箱的详细接入文档,以及上述第三方服务私有化布置需求做的修正,能够参考咱们供给的 沙箱接入文档

总结

本文简略介绍了 Tango 低代码引擎的沙箱才能,并剖析了 CodeSandbox 的根本结构和作业流程。通过 CodeSandbox 强壮的沙箱才能与优化,Tango 低代码引擎完结了可视化预览与建立才能,为开发者供给了快捷高效的开发体会。

Tango 开源方案

现在咱们现已完结了 Tango 中心完结的根本代码库的开源,包含中心引擎内核、沙箱、设置器、应用结构、物料协议等等,并发布了 RC 版本。在今年,咱们将持续推动云音乐低代码中心才能的开源,包含根本的服务端才能,前端组件库等,并持续优化和完善开源文档。而且,随着其他才能的安稳和时刻的成熟,咱们还将会持续向社区开源更多的内部实践。

参考资料

终究

Tango 低代码引擎沙箱完结解析
更多岗位,可进入网易招聘官网检查 hr.163.com/