codesandbox在线编译部署方式

4,529 阅读10分钟

前言

之前在组内做了一款前端研效工具,需要在线预览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元/月)。

image.png

至于如何使用,官方也给出了具体的文档,主要配置如下: image.png 优点:

  • 官方支持,上手简单,可快速支持私有源
  • 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等

image.png

此处以/functions/api/index.ts修改为例,将原来的亚马逊S3修改为腾讯云COS,其他几处地方同理,并注意调整sdk的调用方式即可,这里以环境变量注入密钥Id与Key(注意:读者在做配置时请谨防密钥泄露!请谨防密钥泄露!请谨防密钥泄露!)

image.png

设置npm源

npm源配置相对简单,主要有三处,两个dockeFile及一处源码(源码处可通过环境变量注入),此处均使用淘宝npm源举例:

image.png

Docker修改均相同:

image.png

源码处也可启动容器时配置环境变量:

image.png

启动及测试

为了使容器能够互相访问端口,先用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服务时一致:

image.png 测试:GET方法请求,可发现其会返回处理后的依赖数据(注:此服务一般不直接对外暴露,api服务调用packager服务后,也是最后返回对象存储的路径,供客户端请求

image.png 在对象存储上也缓存了对应的依赖文件:

image.png

再启动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在对象存储上的路径

image.png

如果我们试着访问对象存储没有的依赖文件(如react-dom),会发现api服务返回了对象存储上的文件路径,同时对象存储也出现了react-dom缓存

image.png

image.png

至此,依赖处理服务已部署完成,再次提醒:谨防对象存储密钥泄露!

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则在自己的云服务上找到存储桶地址即可。

image.png 之后使用构建和启动服务:

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,会再去请求依赖处理服务,待后端完成处理,存入对象存储后,会再次向对象存储进行请求。初次打开较多依赖不存在于对象存储或依赖较大时,稍等一会刷新即可完成“冷启动”。

image.png

至此,已完成对npm源、对象存储的私有化配置及部署~

image.png

总结其特点:

npm源对象存储依赖服务端口iframe服务端口
私有化程度可私有化私有部署私有部署私有部署

总结

本文总结了三种部署在线编译的实践经验,难度从易到难,私有化程度及成本也一起从低到高。当然,这只是demo级别的部署,具体到环境、产品化上还需读者多留意安全性、开源协议等因素。

参考资料

搭建一个属于自己的在线 IDE

CodeSandbox浏览器端的webpack是如何工作的?

CodeSandbox - Online React Playground - Interview with Ives van Hoorne