在CodeasilyX的增强版的开发中用到了CodeSandbox开源的sandpack插件,以前一直想实现在浏览器实现类似webpack的包编译运行能力,无奈自己能力和精力有限,不敢碰这块大坑,好在CodeSandbox已经实现了😁,接入过程非常轻松,还能满足我特殊定制的功能,着实灵活👍
声明
以下是我对codesandbox/sandpack这个项目的学习笔记,纯个人理解,小弟不敢班门弄斧,如有不妥之处望大佬赐教🙏
Sandpack
先简单介绍一下我对Sandpack的理解吧:
sandpack是由面向web开发者的在线IDE网站codesandbox.io开源的一个封装了包构建运行的组件,也就是CodeSandbox的沙盒系统,只需要传入我们的代码和依赖配置,它就能给我们运行出效果在制定的容器上。
sandpack像sdk一样只是一个接口层,背后主要依靠的codesandbox-client和dependency-packager实现编译运行及包依赖包内容等核心功能。
运行sandpack
我习惯从运行后的效果观察实现方式,先从直观的画面印象再到阅读代码时容易记起相关画面有助于理解。
先用sandpack-client的包来运行一个实例
1 | import { SandpackClient } from "@codesandbox/sandpack-client"; |
运行后能看到空白的界面且控制台有执行打印代码console.log(require('uuid'))
打开调试工具网络面板观察一下有两个第三方服务在请求
这个0-0-6.sandpack.codesandbox.io域名是sandpack-bundler
服务,它是处理包构建代码的主程序,也是运行codesandbox沙盒系统的主程序,等下会讲到。
这个prod-packager-packages.codesandbox.io域名则是codesandbox的packager
服务,负责向npm仓库获取包代码和相关描述配置,每个依赖都会去请求,比如图中请求的babel-runtime@6.26.0这个包的返回内容是这样的:
目前从运行效果来观察能知道sandpack先启动sandpack-bundler
主程序,然后根据传入的代码和依赖列表通过packager
去找对应包的代码内容,在拿到npm包内容和编辑器代码后,就是最关键的编译阶段了,我们一步步来,先讲sandpack-bundler
sandpack-bundler
前面提到sandpack-bundler
是codesandbox沙盒系统的主程序,在运行后的预览框是一个以iframe容器运行,实际就是iframe加载的src
这个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 | new SandpackClient(iframe, sandboxInfo, { |
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)
最终返回内容就是上面提到的那样:
packager的工作做完后就会交给bundler程序中的编译模块(Transpiler)
Transpilation
当前面的packager拿到包内容和依赖关系后,会根据配置决定environment环境, environment目前可配置的值有这些
1 | export type ITemplate = |
每个environment对应了一套类似webpack的loader、plugin的框架配置,可以理解为每个environment对应了一套预置配置preset,在sandpack-client初始化可以指定environment:
1 | new SandpackClient({ |
不同的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)