前言
之前在组内做了一款前端研效工具,需要在线预览react代码效果,笔者便私有化部署了codesandbox的在线编译服务。
由于codesandbox的不断迭代完善,其部署方式也在变化,在参考文章时也处理了些难点,本文以此做记录,提供了几种不同私有化程度的部署方式,分享给有需要的朋友,也感谢前辈大佬们的分享,相关文章也会贴在文末。
除了基本的原理介绍,本文更加偏向实践层面,会分析几种不同程度私有化部署的优缺点,以满足不同的场景需要;难易程度由易到难,私有化程度也越来越高,当然,成本也越来越高。
在开始之前,可以先了解下sandpack,它是codesandbox开源的在线编译库,为codesandbox提供了在线编译的能力
基本原理
本文重心在实践层面,故原理会写得比较简略,对细节感兴趣的朋友可学习下以下文章:
架构图
在线编译的基础架构如下,页面通过Window.postMessage将代码传送到iframe中进行编译及执行,iframe中包含了codesandbox自行开发的、可执行于浏览器的简易babel及简易webpack,简易webpack输出依赖图结构(原理同webpack),编译、打包主要是在web worker中进行,以提高性能。最终执行于iframe中,呈现出效果
从架构图可以看出,主要需要以下服务:
- npm源(公有源或私有源等等)
- 对象存储
- 依赖处理服务端口
- iframe服务端口
后文的部署方式也会将以上几点列出来。
流程图
对上文的架构图了清晰后,流程也会明了:
部署
接下来提供几种为自己的应用部署在线编译的方式,难度从易到难,成本从低到高,私有化程度也会逐步提升,同时笔者会配上个人分析的优缺点、适用场景,读者可根据个人场景采取部署方式。
- 直接部署
- 支持私有源部署
- 完全私有化部署
直接部署
直接部署最为简单,直接使用sandpack库即可,可快速为你的应用部署起在线编译服务
import { Sandpack } from "@codesandbox/sandpack-react";
const App = () => {
const files = {}
return (
<Sandpack template="react"/>
)
}
更多配置及基本使用详见sandpack官方文档,笔者这里不再赘述,接下来分析其优点、缺点及适用场景。 优点:
- 上手简单:安装组件库后可直接使用
- 可将代码共享至codesandbox:点击右下角分享按钮,即可将代码同步到codesandbox共享
- 免费
缺点:
- 仅依赖公共npm源:企业内部私有库无法直接使用
- 安全性差:分享误操作可能导致代码共享到codesandbox,带来安全风险
- iframe沙箱环境为codesandbox官方环境:私密性问题,代码执行在外部域上
- 境外依赖及iframe:加载速度可能较慢
适用场景:
- 开源库官网示例:如reactflow官网示例就集成了sandpack提供在线编译服务,并支持跳转到codesandbox
- 个人博客等安全性要求相对较低的站点:如个人博客想支持代码效果预览
| npm源 | 对象存储 | 依赖服务端口 | iframe服务端口 | |
|---|---|---|---|---|
| 私有化程度 | 官方源 | codesandbox提供 | codesandbox提供 | codesandbox提供 |
支持私有源部署
sandpack官方现在支持了用户配置私有源地址(约2022下半年支持),这里的私有源主要包含了github packages、npm私有源、自定义源等等,购买了team pro服务即可使用,当前价格为18美元/月(约124元/月)。
至于如何使用,官方也给出了具体的文档,主要配置如下:
优点:
- 官方支持,上手简单,可快速支持私有源
- team pro服务不止于私有源支持:team pro还有其他支持团队协作的功能【其实就是saas服务】
- 成本低:仅需订阅team pro,其他服务均由codesandbox提供,无需额外购买
缺点:
- 私有源的包代码会缓存到sandpack的对象存储,对代码私密性比较重视需要考量
适用场景:
- 中小型公司、初创团队:有自己的私有库且信任codesandbox服务
| npm源 | 对象存储 | 依赖服务端口 | iframe服务端口 | |
|---|---|---|---|---|
| 私有化程度 | 可私有化 | codesandbox提供 | codesandbox提供 | codesandbox提供 |
完全私有化部署
完全私有化部署则将整套服务都私有化,部署过程相对麻烦,部署更多服务:
- codesandbox / dependency-packager仓库:提供依赖处理服务
- codesandbox / codesandbox-client仓库:提供iframe服务,简易babel及简易webpack的实现也在这个仓
- npm源:私有的npm源
- 对象存储:自行准备,用于缓存处理后的npm依赖
- serverless服务(可选):codesandbox官方通过亚马逊的serverless托管dependecy-packager服务,如果没有serverless,也可通过【服务器 + nodejs服务 + docker】的形式来托管,本文主要是使用此方式,实际生产环境读者可根据自身情况修改(如基于k8s等等);若读者想使用serverless方式部署,可查看仓库中的readme
接下来让我们开始吧!
tips:根据评论区反馈,最好使用node16版本
1.依赖处理服务
依赖处理服务中含有api服务及packager服务:
- api服务:依赖获取服务,若对象存储有缓存依赖,返回依赖文件路径,客户端访问cos依赖文件路径;若对象存储无缓存依赖,调用packager服务去npm源请求依赖并处理,带处理后依赖存入对象存储,获取文件路径。请求POST示例:http://localhost:3004/react@17.0.2
- packager服务:请求npm源,处理依赖数据,并将其存入对象存储,返回访问路径。请求GET示例:http://localhost:3003/react@17.0.2 。(注:此服务会在环境上创建和删除文件,windows系统上可能无法直接运行,建议在docker环境中运行此服务进行测试)>
从代码层面,主要修改:对象存储配置、设置npm源。笔者从官方fork了一个仓库并进行修改(blog分支), 如果读者使用的是腾讯云对象存储服务,直接通过环境变量即可进行配置,若是其他厂商可在标记有TODO:blog处进行修改。
对象存储配置
对象存储配置主要在这两个文件中,codesandbox使用的是亚马逊s3服务,读者可根据自己资源修改,如腾讯云COS、阿里云OSS等
此处以/functions/api/index.ts修改为例,将原来的亚马逊S3修改为腾讯云COS,其他几处地方同理,并注意调整sdk的调用方式即可,这里以环境变量注入密钥Id与Key(注意:读者在做配置时请谨防密钥泄露!请谨防密钥泄露!请谨防密钥泄露!)
设置npm源
npm源配置相对简单,主要有三处,两个dockeFile及一处源码(源码处可通过环境变量注入),此处均使用淘宝npm源举例:
Docker修改均相同:
源码处也可启动容器时配置环境变量:
启动及测试
为了使容器能够互相访问端口,先用docker创建network
docker network create compile-network
先启动packager服务:
docker build --tag compile/dependency:packager -f Dockerfile.packager .
docker run -it -dp 3003:3003 \
--name packager-container \
--network compile-network \
-e SECRET_ID="x" \ # 对象存储ID
-e SECRET_KEY="xx" \ # 对象存储密钥
-e BUCKET_NAME="xxx" \ # 存储桶
-e REGISTRY="https://registry.npm.taobao.org" \ # 私有源配置
compile/dependency:packager
注意容器名称packager-container及端口需与api服务访问packager服务时一致:
测试:GET方法请求,可发现其会返回处理后的依赖数据(注:此服务一般不直接对外暴露,api服务调用packager服务后,也是最后返回对象存储的路径,供客户端请求)
在对象存储上也缓存了对应的依赖文件:
再启动api服务:
docker build --tag compile/dependency:api -f Dockerfile.api .
docker run -it -dp 3004:3004 \
--name api-container \
--network compile-network \
-e SECRET_ID="x" \ # 对象存储ID
-e SECRET_KEY="xx" \ # 对象存储密钥
-e BUCKET_NAME="xxx" \ # 存储桶
-e REGISTRY="https://registry.npm.taobao.org" \ # 私有源配置
compile/dependency:api
测试:POST方法请求,可发现其返回了react17在对象存储上的路径
如果我们试着访问对象存储没有的依赖文件(如react-dom),会发现api服务返回了对象存储上的文件路径,同时对象存储也出现了react-dom缓存
至此,依赖处理服务已部署完成,再次提醒:谨防对象存储密钥泄露!
2.iframe服务
iframe服务配置比较简单,笔者也同样fork了一份仓库(blog分支),在clone仓库前,windows用户可先配置换行符兼容及支持长文件名(此仓库中含有很多node_modules)
git config --global core.autocrlf false
git config --global core.filemode false
git config --global core.safecrlf true
git config --global core.longpaths true
iframe服务配置较少,仅需修改此处,即填上api服务机器地址及存储桶地址。packager填上部署了api服务主机地址,届时浏览器请求依赖时会访问这个地址;bucket则在自己的云服务上找到存储桶地址即可。
之后使用构建和启动服务:
docker build --tag codesandbox-client:compile -f DockerFile.Client .
docker run -it -dp 3002:3002 codesandbox-client:compile
DockerFile.Client调用的build.sh脚本主要参考了文章:搭建一个属于自己的在线 IDE,同时,由于client的不断迭代,现在应该基于node16构建(文章是基于node10),本文也做出了对应的调整。此仓库体积较大,构建时间也较长,需要耐心等待。
3.sandpack使用
sandpack使用时仅需填入iframe服务地址即可:
import React from 'react';
import { Sandpack } from "@codesandbox/sandpack-react";
import './App.css';
function App() {
return (
<Sandpack
template="react"
options={{
// iframe服务地址,此处是本机上测试,故为localhost
bundlerURL: 'http://localhost:3002/',
}}
/>
);
}
export default App;
接下来进行测试,可看见会直接先去对象存储服务请求依赖数据,如果为404,会再去请求依赖处理服务,待后端完成处理,存入对象存储后,会再次向对象存储进行请求。初次打开较多依赖不存在于对象存储或依赖较大时,稍等一会刷新即可完成“冷启动”。
至此,已完成对npm源、对象存储的私有化配置及部署~
总结其特点:
| npm源 | 对象存储 | 依赖服务端口 | iframe服务端口 | |
|---|---|---|---|---|
| 私有化程度 | 可私有化 | 私有部署 | 私有部署 | 私有部署 |
总结
本文总结了三种部署在线编译的实践经验,难度从易到难,私有化程度及成本也一起从低到高。当然,这只是demo级别的部署,具体到环境、产品化上还需读者多留意安全性、开源协议等因素。
参考资料
CodeSandbox浏览器端的webpack是如何工作的?
CodeSandbox - Online React Playground - Interview with Ives van Hoorne