在CodeasilyX的增强版的开发中用到了CodeSandbox开源的sandpack插件,以前一直想实现在浏览器实现类似webpack的包编译运行能力,无奈自己能力和精力有限,不敢碰这块大坑,好在CodeSandbox已经实现了😁,接入过程非常轻松,还能满足我特殊定制的功能,着实灵活👍

声明

以下是我对codesandbox/sandpack这个项目的学习笔记,纯个人理解,小弟不敢班门弄斧,如有不妥之处望大佬赐教🙏

Sandpack

先简单介绍一下我对Sandpack的理解吧:

sandpack是由面向web开发者的在线IDE网站codesandbox.io开源的一个封装了包构建运行的组件,也就是CodeSandbox的沙盒系统,只需要传入我们的代码和依赖配置,它就能给我们运行出效果在制定的容器上。

sandpack像sdk一样只是一个接口层,背后主要依靠的codesandbox-clientdependency-packager实现编译运行及包依赖包内容等核心功能。

运行sandpack

我习惯从运行后的效果观察实现方式,先从直观的画面印象再到阅读代码时容易记起相关画面有助于理解。

先用sandpack-client的包来运行一个实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { SandpackClient } from "@codesandbox/sandpack-client";

// There are two ways of initializing a preview, you can give it either an
// iframe element or a selector of an element to create an iframe on.
const client = new SandpackClient(
"#preview", // iframe selector or element itself
{
files: {
"/index.js": {
code: `console.log(require('uuid'))`,
},
},
entry: "/index.js",
dependencies: {
uuid: "latest",
},
} /* We support a third parameter for advanced options, you can find more info below */
);

运行后能看到空白的界面且控制台有执行打印代码console.log(require('uuid'))image-20210820153025825

打开调试工具网络面板观察一下有两个第三方服务在请求

image-20210820153906847
image-20210820153906847

这个0-0-6.sandpack.codesandbox.io域名是sandpack-bundler服务,它是处理包构建代码的主程序,也是运行codesandbox沙盒系统的主程序,等下会讲到。

image-20210820153924499
image-20210820153924499

这个prod-packager-packages.codesandbox.io域名则是codesandbox的packager服务,负责向npm仓库获取包代码和相关描述配置,每个依赖都会去请求,比如图中请求的babel-runtime@6.26.0这个包的返回内容是这样的:

image-20210820155011388
image-20210820155011388

目前从运行效果来观察能知道sandpack先启动sandpack-bundler主程序,然后根据传入的代码和依赖列表通过packager去找对应包的代码内容,在拿到npm包内容和编辑器代码后,就是最关键的编译阶段了,我们一步步来,先讲sandpack-bundler

sandpack-bundler

前面提到sandpack-bundler是codesandbox沙盒系统的主程序,在运行后的预览框是一个以iframe容器运行,实际就是iframe加载的src

image-20210820154427907
image-20210820154427907

这个bundler我们还可以自己构建部署在自己的服务器上,详见sandpack/sandpack-client at main · codesandbox/sandpack (github.com),构建步骤如下:

  • The bundler is part of the codesandbox-client codebase: https://github.com/codesandbox/codesandbox-client
  • Clone the codesandbox-client and install the dependencies in the root folder (yarn install).
  • yarn build:deps to build some of the packages lerna needs for internal links.
  • create your instance of sandpack with yarn build:sandpack.

其实就是构建codesandbox-client项目,会生成一个www的文件夹,然后随便起一个静态服务指定根路径在www文件夹下就可以了作为自己的bundler服务

然后在SandpackClient实例化时进行配置

1
2
3
new SandpackClient(iframe, sandboxInfo, {
bundlerURL: "https://your-hosted-version",
});

packager

packager的代码实现托管在dependency-packager,这个服务部署采用了 Serverless( AWS Lambda) 方式,可以应付packager弹性高并发的场景,也可以部署在自己服务器上,只是需要调整里面的AWS lambda改为自己的服务器执行。

以react为例,会经过以下步骤:

  • 执行yarn下载react以及react的依赖包到服务器上(代码处:install-dependencies
  • 读取包内package.json中的browser、module、main、unpkg字段寻找入口文件(代码处:find-package-infos
  • 从入口文件解析所有的 require 语句,提取被 require 的文件内容和路径生成依赖图 (代码处:find-dependency-dependencies)

最终返回内容就是上面提到的那样:

image-20210820155011388
image-20210820155011388

packager的工作做完后就会交给bundler程序中的编译模块(Transpiler)

Transpilation

当前面的packager拿到包内容和依赖关系后,会根据配置决定environment环境, environment目前可配置的值有这些

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export type ITemplate =
| "vue-cli"
| "preact-cli"
| "svelte"
| "create-react-app-typescript"
| "create-react-app"
| "angular-cli"
| "parcel"
| "@dojo/cli-create-app"
| "cxjs"
| "gatsby"
| "nuxt"
| "next"
| "reason"
| "apollo"
| "sapper"
| "ember"
| "nest"
| "static";

每个environment对应了一套类似webpack的loader、plugin的框架配置,可以理解为每个environment对应了一套预置配置preset,在sandpack-client初始化可以指定environment:

1
2
3
4
new SandpackClient({
files: xxx,
environment: 'create-react-app'
})

不同的preset要传入对应后缀的文件才能被正常解析,比如在environment === 'create-react-app'的时候如果files里有.vue后缀的文件就不能被解析,就像webpack一样要有对应的loader才能解析,如果要对现有的preset支持更多的后缀文件或修改规则只能在CodeSandbox源码中修改再构建一份

对于编译这块我觉得CodeSandbox就不太透明和灵活

Evaluation

执行阶段因Transpiler的不同框架而决定执行的过程,大致就是从entry文件开始递归调用evel执行所有相关的模块,并根据文件适当的插入到文档对应位置(比如样式文件会在编译后合并成<style>标签插入到<head>里)

参考资料

codesandbox-client/v1.ts at master · codesandbox/codesandbox-client (github.com)

codesandbox/sandpack: An ecosystem of components and utilities built around the browser bundler that powers https://codesandbox.io (github.com)

https://github.com/codesandbox/dependency-packager