评论

CodeSandBox私有化部署实践

CodeSandBox 提供了无需本地环境搭建的全功能开发体验,让我们能够在浏览器中实时预览、调试和共享前端代码,极大地提升了组件开发效率、降低接入成本

前言

快速发展的前端领域,作为公司的前端工程师,经常面临着许多挑战,其中之一就是如何有效地预览和调试前端物料组件。预览组件的重要性不言而喻,它们为前端工程师提供了对用户界面和功能的实时展示,能够更好地理解和评估设计和实现的效果。然而,传统的预览方式往往耗时繁琐,需要在本地环境中进行搭建和调试,给开发流程带来了一定的延迟和复杂性。      

为了解决这一问题,我们选择了基于浏览器的在线开发环境 CodeSandBox方案。CodeSandBox 提供了无需本地环境搭建的全功能开发体验,让我们能够在浏览器中实时预览、调试和共享前端代码,极大地提升了组件开发效率、降低接入成本。

在接下来的文章中,我们将介绍CodeSandBox的相关原理以及我们在落地方案的全过程。

二、CodeSandBox原理

2.1 简述

CodeSandBox 是一个在线的代码编辑器,它在浏览器端进行项目构建而无需依赖服务   器,这是它最大的特点。

通常情况下,要在浏览器中运行一个前端项目,需要配置 Node 环境,通过 npm 安装一系列依赖,然后使用类似 webpack 的工具进行编译,最后再运行一个开发服务器才能在浏览器中预览项目。那么,CodeSandBox 是如何实现这一切的呢?

本质上 CodeSandBox 实现了一个浏览器端的 webpack ,通过其实现的 Packager 安装所需要的依赖,在 SandBox 中通过这个 webpack 将文件编译,再将编译后的文件通过 eval 在浏览器中运行。

2.2 组成部分

CodeSandBox的实现涉及三个主要部分:Editor、Packager和SandBox。

Editor: 用于编辑代码和管理文件的模块。当代码被修改后,通过 postMessage 通知到 SandBox,以便进行相应的处理。

Packager:运行在服务端,它是负责处理项目依赖关系的模块。通过传入包名及其版本信息,Packager 会生成一个包含该依赖项所有内容的 manifest 文件。这个manifest 文件会被缓存在云服务上,以便后续使用。

SandBox:一个运行在独立的 iframe 中的代码执行环境。它负责编译和运行代码。SandBox 利用 postMessage 与外部进行通信,接收来自 Editor 的代码修改通知,并相应地处理。它使用了内部的编译器来将代码进行编译,并通过 eval 函数在浏览器中执行编译后的代码。

2.3 构建过程

如上文的组成关系图所示,在 SandBox 中接收到代码之后,就开始进行整个构建的流程,这个流程就是 CodeSandBox 的核心功能。整个构建流程图如下。(这里只是列举了简易流程,实际场景还有很多复杂的逻辑,如中断构建、缓存、热更新等等)

根据构建流程,下面来看看几个重要的模块,及其实现的思路。

a)  Packager(依赖管理器)  

尽管 npm 是个"黑洞",但作为一个前端项目,还是离不开它,如何处理项目的依赖是在线运行的第一个难题。

在 Pakcager 的实现中,借鉴了 WebpackDllPlugin 的方法,为每个依赖用”包名@版本“作为唯一标识,生成一个 Manifest 文件并进行缓存(后续再有同样的依赖直接从缓存获取),这个文件包含了一个包的所有文件目录索引、依赖、文件内容等信息。

这个文件的生成也是通过yarn在服务端安装依赖,只不过这里为了剔除 npm 模块中多余的文件,服务端还遍历了所有依赖的入口文件 (package.json#main),解析 AST 中的 require 语句,递归解析被 require 的模块,形成一个依赖图,只保留必要的文件,最终输出 Manifest 文件,大致内容如下: 那么在 SandBox 接收到收到一个完整的项目文件,其中包括 package.json,分析其中的 dependencies,剔除一些并不需要的(如@types/*)依赖,接着将每个依赖根据包名@版本生成唯标识,去请求 Packager,拿这个标识去请求服务器,拿到manifest 文件。

获取到所有依赖的 manifest 内容,依赖安装的部分就完成了,可以开始对文件进行编译。

b) Transpilation  

依赖安装完成之后,将会从入口文件开始,对文件进行编译。

上文提到过,CodeSandBox 实现了一个浏览器端的 webpack,并且在 Editor 和SandBox 通信的时候,除了文件内容之外,还有 Template 字段,这个就是指定需要编译的模板。这个模板预设了一些 loader,用来编译指定类型的文件,类似webpack 的配置文件。目前 SandBox 内部预设的一些模板,如 create-react-app、vue-cli 等。

这里可以看一下 create-react-app 预设的部分配置,可以看出和 webpack 的配置很像,设置一个文件后缀名称检测,指定一个 loader 来编译当前类型的文件。 SandBox 根据预设配置,从入口文件进行编译,解析 AST,找出下级依赖,并递归编译。最终会形成一个依赖图。

c) Evaluation  

有了编译完成后的文件和依赖图,这里就可以运行了,从入口文件开始,用 eval 执行,如果执行过程中有 reqiure,则递归 eval 依赖的模块。

至此,整个构建流程就说完了。SandBox 从接收到代码,再安装依赖、编译文件、执行文件,最终在 iframe 中呈现出运行之后的结果。

这其中很多细节逻辑都比较复杂,比如编译的 loader 实现、热更新支持等等,有兴趣可以拜读一下源码研究研究。

三、落地实践

了解完 CodeSandBox 的大致实现原理,我们开始动手实践。虽然 CodeSandBox 官网上功能大部分都是开源的,但是由于一些业务场景和限制,我们最终采用的方案是复用 SandBox 部分功能,并对其代码进行部分改造,以便于实现我们自定义的一些需求。

3.1 实现Editor

CodeSandBox 本身集成了 vscode 的编辑器,功能强大且复杂,然而对于我们的需求来说,有点冗余。于是我们使用 vscode 的简易版(monaco-editor)实现了一个编辑器。

在物料的角度,Editor 只需要简单文件编辑、切换文件等功能。并且我们在 Editor内封装了一些模板,这样我们的物料并不需要关心 create-react-app 之类的模板文件,只要写一个 example 入口文件就可以,并且内部已经处理过 ReactDOM.render之类的方法。会直接渲染入口文件的内容。

在和 SandBox 通信方面,使用 SandBox 开源的 @CodeSandBox/sandpack-client,这个包是专门用来处理和 iframe 通信相关的功能。

3.2 SandBox 私有化部署

CodeSandBox 不支持私有源配置,于是我们需要修改部分源码,并将其私有化部署到我们的服务上,才能正常使用。

a) 修改Packager请求接口  

首先需要将 Packager 服务也私有化部署到我们的服务上,然后修改 SandBox 中请求的路径地址为我们的服务,这样就获取到私有源包的 manifest 文件。

文件路径为packages/sandpack-core/src/npm/preloaded/fetch-dependencies.ts

b) 支持unpkg  

CodeSandBox 中,除了会向打包服务请求获取依赖的内容之外,还有回退方案。就是在找不到依赖的时候,会直接请求某个依赖的 unpk 地址,这里需要添加内部的unpkg 地址逻辑并指向公司的 unpkg 服务上。

添加逻辑,请求内部的地址并拿到文件内容。

这里看到源码会根据优先级找不同的 cdn 文件,我们加上优先级,优先使用内部的unpkg 服务,如果找不到会降级去找 npm 的 unpkg 的,最后会去 jsdeliver。

文件路径是packages/sandpack-core/src/npm/dynamic/fetch-protocols/index.ts

修改完配置之后并 build,将输出的 www 文件夹部署到我们的服务器上,其前端相关的部署就完成了。

3.3 Packager 私有化部署

CodeSandBox 的服务端 Sandpack Packager 也是开源的,下面是它的仓库地址:

https://github.com/CodeSandBox/dependency-packager#sandpack-packager

其内部实现了一个服务端打包的接口,并接入缓存,CodeSandBox 接入的是亚马逊的缓存存储服务。

CodeSandBox 客户端拿到 package.json 之后,将 dependencies 转换为一个由依赖和版本号组成的Combination(标识符, 例如 v1/combinations/babel-runtime@7.3.1&csbbust@1.0.0&react@16.8.4&react-dom@16.8.4&react-router@5.0.1&react-router-dom@5.0.1&react-split-pane@0.1.87.json), 再拿这个 Combination 到服务端请求。服务器会根据 Combination 作为缓存键来缓存打包结果,如果没有命中缓存,则进行打包。服务器会根据 Combination 作为缓存键来缓存打包结果,如果没有命中缓存,则进行打包。

其业务中代码构建逻辑在functions/packager/r/index及installDependiceies中,下面我们就来看看如何改造相关代码进行本地部署。

a)调整不兼容windows的命令(需要在windows执行服务的有此步骤)  

调试代码时需要注意这个项目的很多命令在 Window 下无法使用的,建议用 Mac 或 Linux 运行,如果确实要在 Windows 下面运行,那就需要改造源码。我们是在Mac 与 Linux下运行的,如果在 Windows下测试的话,建议启动后根据命令报错逐个修改替换或增加兼容命令。

b)移除sentry相关的代码  

可在项目中检索 config.secret,然后顺着调用关系删除相关代码。

c)S3缓存逻辑移除  

CodeSandBox 的在线打包服务使用了亚马逊 S3 缓存,我们在实际操作中使用了我们的 CDN 当缓存来用。

在打包操作前,首先从 CDN 查询相关资源是否存在,存在则直接 JSON,不存在则发起打包操作,并将结果上传至 CDN。

d)迁移代码至自有工程  

将打包相关代码迁移至自有工程中,这部分内容较简单,就不做过多赘述了。

四、物料接入

在物料平台首页,我们展示了一个简单的示例,一个CSS动画的物料,我们下面就以这个例子讲讲物料的接入。

4.1 实时预览能力

需要接入物料平台并预览,首先在项目根目录添加 example 文件夹,编写入口文件,这里有一个小细节需要注意,我们这里的 import Bike from '@oort2/bike',这里 @oort2/bike 是当前包名,这是被预览的包给外部使用时的导入方式,预览也是使用了这种形式(我们必须且只能这么做),实际开发调试时使用了 vite/webpack alias 重定向到了 src/index 中。

另外,目前的 SandBox 运行配置中,没有类似 babel-plugin-import 的插件,当物料有依赖样式文件的时候,需要在入口文件引入才能正常加载。 4.2 多框架支持

在 example 文件夹下添加 perview.config.json,指定项目的模板和入口文件,以下是字段含义。

main:index.(tsx|jsx|vue|any): 入口文件,可以随意命名,要和 preview.config.json 中的 main 对应上。

template:模板,目前可以支持 vue-cli、create-react-app、solid、augular、sevlet等常用的前端框架。 4.3 上下文处理

在项目中,尤其是一些业务组件,常常会依赖框架层等提供的配置信息和依赖,并挂载在 Window 对象上。但是在物料预览的沙箱 Window 上,并没有挂载这些对象,这个时候运行起来的组件可能与预期不一致。

于是我们提供了一个上下文文件,约定为 context.ts。在这个文件中,可以手动将一些所需要的对象挂载到 Window 上,以便物料得以正常运行。

再通过 oort2 脚手架上传到物料平台,在物料平台中就可以查看到相关信息,这时候打开物料的预览详情,就可以像 CodeSandBox 一样,展示在右侧。

五、总结展望

通过私有化部署和定制化配置,我们成功将 CodeSandBox 集成到我们的内部环境中。这样,我们可以更好地控制数据安全性、自定义扩展功能,并适应我们的特定需求。我们修改了 Packager 的请求接口和 unpkg 地址,实现了对私有源包的支持。我们还将打包服务端代码迁移到了我们的工程中,以便更好地管理和定制。

此外,我们对物料工程进行了改造和接入,使其能够与物料平台无缝对接,并在平台上进行预览和展示。这为我们的团队提供了更直观、实时的物料展示和交流平台,促进了物料的复用和协作。

在微盟,物料平台为包括装修、融合、微盟云在内的多个业务提供了数千个组件,基于 CodeSandBox 的私有物料组件预览方案为我们的组件开发带来了许多好处,降低了组件的开发、接入成本,提升了开发效率,我们将进一步完善和优化这一方案。通过持续的优化和拓展,这一方案将进一步提升我们的开发效率和协作能力,为我们的前端团队带来更多的价值和创新。


最后一次编辑于  2023-09-11  
点赞 2
收藏
评论

1 个评论

  • 幸遇洛瑶
    幸遇洛瑶
    2024-02-26

    你好,请问下这块私有化部署是不是收费的?

    2024-02-26
    赞同
    回复
登录 后发表内容