webContainer - 在浏览器运行 node 代码
什么是 WebContainer
WebContainers 是一个基于浏览器的运行时,用于执行 Node.js 应用程序和操作系统命令,它完全运行在您的浏览器页面中。
我的理解,webContainer 就是一个可以运行在浏览器页面中的微型操作系统,提供了文件系统、运行进程的能力,同时内置了 nodejs、npm/yarn/pnpm 等包管理器。
主要特性
- 能够在浏览器中运行 node 及其工具链(如:webpack、vite 等)
- 灵活:在 WebContainers 支持下,编码体验将会大幅提升
- 安全:所有内容都运行在浏览器页面中,非常安全
- 快速:毫秒级启动整个开发环境
- 始终开源免费
对于服务提供方(例如在线 IDE)来说,与在云端虚拟机上运行命令相比,有以下优势:
- 无与伦比的用户体验。没有延迟。比本地主机快。离线工作。
- 成本效益。计算是在本地完成的。不购买云服务器。
- 可以扩大用户规模。以前受限于云服务器的规模,如今直接运行在客户端,使用客户端的算力
- 服务器安全,代码运行在客户侧,不担心服务器运行恶意逻辑,例如挖矿
快速入门
启动 webContainer
执行以下代码即可:
import { WebContainer } from '@webcontainer/api';
// 只能调用一次
const webContainerInstance = await WebContainer.boot();
启动完 webContainer 才能使用。
设置跨域隔离
WebContainers 需要使用 SharedArrayBuffer 这个 API(这个 API 有安全需求)。因此,您需要设置Coop/COEP标头:
// 保护源站免受侵害
Cross-Origin-Embedder-Policy: require-corp
// 保护源站免受攻击
Cross-Origin-Opener-Policy: same-origin
另外,网站必须要使用 https 协议,本地开发时使用 localhost 也可以,否则 SharedArrayBuffer 是不安全的,会被禁用。
挂载文件
使用 mount API 进行挂载文件和目录
const files = {
// 这是一个文件,package.json 是文件名
'package.json': {
file: {
contents: `
{
"name": "vite-starter",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"vite": "^4.0.4"
}
}`,
},
},
// 这是一个目录,src 是目录名
src: {
// 目录下会有 directory 的属性
directory: {
// 这是一个文件,叫 main.js
'main.js': {
// 文件下有 file 的属性
file: {
contents: `
console.log('WebContainers says hello!')
`,
},
},
},
},
};
await webContainerInstance.mount(files);
然后就可以通过 readFile 读取文件:
const file = await webContainerInstance.fs.readFile('/package.json');
目前 webContainer 的 fs 支持以下几种文件操作:
- readFile:读取文件
- readdir:读取目录
- rm:删除文件
- writeFile:写文件
- mkdir:创建目录
运行进程
可以在 webContainer 中运行如 npm install 的命令:
webContainerInstance.spawn('npm', ['install']);
spawn 方法接受三个参数:
- 字符串,为命令名称,例如 npm
- 数组:命令的选项
- 非必须,spawn 的参数(例如指定环境变量,禁止输出等)
安装依赖
以下是一个封装好的安装依赖的函数
async function installDependencies() {
const installProcess = await webContainerInstance.spawn('npm', ['install']);
return installProcess.exit;
}
读取进程的输出
调用 Spawn 函数返回 WebContainerProcess。它的 output 属性是一个可读流 readableStream
const installProcess = await webContainerInstance.spawn('npm', ['install']);
installProcess.output.pipeTo(new WritableStream({
write(data) {
console.log(data);
}
}));
运行 dev Server
有些命令可以启动 dev Server,例如 npm run start 启动一个 vite/express Server。
这时候,可以用过 on 监听到 server-ready 事件
async function startDevServer() {
// 执行 `npm run start` 启动 Express app
await webContainerInstance.spawn('npm', ['run', 'start']);
// 等待 `server-ready` 事件
webContainerInstance.on('server-ready', (port, url) => {
// 通过这个 url,就能访问 server
window.open(url)
});
}
我们平时启动的 dev Server,会通过类似 http://localhost:8080 的方式进行访问,但如果在网页中运行 dev Server就不行了,因为由于安全限制,网页没有足够的权限绑定端口。
因此 webContainer 会提供一个 url,代替 http://localhost:8080 去访问,这也就是 server-ready 事件,会在回调参数中传 url 的原因
url 长这样:https://localhost5173-3ca0-2jv4wvy8--3111.local-corp.webcontainer.io/
!(webContainer)[/static/images/88_1.jpg]
这个请求实际上没有请求到外部,而是在 Service Worker 中直接返回了,通过这样的方式,达到了与平时访问 http://localhost:8080 一样的效果
总结
webContainer 适合交互式编码体验,可以用在生产级IDE,编程教程,下一代文档等应用上。目前虽然看起来功能十分有限,但这其实是一个很有意义的尝试,是一个从 0 到 1 的突破,尝试在浏览器端运行一个微型的操作系统,相信不久的未来,不仅仅是运行 nodejs,其他的语言,例如 python、Java 等等。