腾讯|从 0 到 1 实现浏览器端沙盒运行环境

腾讯|从 0 到 1 实现浏览器端沙盒运行环境
文章图片

作者:easonruan,腾讯 CSIG 前端开发工程师
本文的浏览器端 Sandbox 沙盒运行环境,大家可以快速理解为类似 CodeSandbox 一样,所有页面代码编译都在前端完成(不依赖后端),并且具备实时热更新功能。
而本文终极目标就是实现这样的浏览器端 Sandbox 沙盒运行环境,可以轻松接入到大部分平台(尤其低代码平台),提升应用的预览速度和开发体验,效果如下:
为什么需要浏览器端 Sandbox 沙盒运行环境?
原因一:Demo 体验流程的转变:繁琐痛苦 → 快速便捷 如果你要体验 Ant Design 组件库里面 Tree 树组件的一个例子,并想修改部分参数查看效果,你需要做以下步骤:
Step1. 安装 Node.js (已安装可忽略)
Step2. 初始化 react 项目 npx create-react-app antd-tree-demo (必须)
Step3. 添加 Ant Design 并安装依赖 npm install (必须)
Step4. 修改项目代码为 Demo 例子代码 (必须)
Step5. 启动项目 npm start (必须)
而当有了浏览器端的前端 Sandbox 沙盒运行环境,只需一个步骤:
Step1. 点击打开一个链接
即可快速体验到 Demo,并且修改代码可实时看到效果。因此 Ant Design 组件库的每个组件例子都附带了 CodeSandbox 的链接:
腾讯|从 0 到 1 实现浏览器端沙盒运行环境
文章图片
原因二:低代码平台场景需要实时查看并调试当前应用的真实效果 用户在低代码平台开发时,如果应用实时预览的效果是与本地构建出来的效果是一致的,同时可以点击跳转到其他页面,查看整个业务流程的效果,那么整个开发体验都会有大幅度提升。
比如家庭健康码流程,包含 3 个页面:首页入口 → 健康码列表 → 健康码详情(详见开头视频动图)
第一个小目标:在浏览器上直接运行 React 源码文件渲染出 Hello, Sandbox!
源码如下:

import React from 'react'; import ReactDOM from 'react-dom'; ReactDOM.render( Hello, Sandbox!, document.getElementById('root') );

问题一:如何让源代码在浏览器上直接执行? 直接在浏览器上面执行可以吗?显然不行
  • 原因 1:浏览器不支持直接 import NPM 模块 (目前支持加载服务端文件 '/xx/xx.jsx')
  • 原因 2:浏览器无法识别 React 的 JSX 语法
虽然最新浏览器 (Chrome 67 版本开始) 已支持 ESM 模块的加载方式,但需要有以下两个前提条件:
  • 条件 1:需要对源代码进行改造,改为相对或绝对路径,比如:import React from 'react' 改成 import React from '/@module/react'
  • 条件 2:需要本地启动服务器端 Server,返回对应代码内容
当 import 其他文件时,比 import App from './App.jsx' ,因为 import 是系统关键词,我们无法直接模拟或者代理 import,此时浏览器会直接发起一个请求,
如果不依赖服务端,就必须另起一个 service worker 进行拦截。
service worker 的注册必须要加载单独的 js 文件(静态服务),无法将 sandbox 整套方案打包成一个 NPM 库来使用,更新迭代较为繁琐,不适用于我目前开发的低代码平台项目。
因此本文介绍的是更容易实现和管理的 CommonJS 格式规范,以 require 模块的形式来模拟执行环境。
问题二:如何将 ESM 格式转换成 CommonJS 格式? 没错,就是 Babel,Babel 有在线转译的 Try it out 版本,大家可以点击 https://babeljs.io/repl 链接体验
其代码转换效果如下:
腾讯|从 0 到 1 实现浏览器端沙盒运行环境
文章图片
  • 利用 @babel/plugin-transform-modules-commonjs 插件,将 ESM 语法转换成 CommonJS 格式规范
    解决浏览器不支持直接 import NPM 模块的问题
  • 利用 @babel/plugin-transform-react-jsx Babel 插件,将 转换成 React.createElement('div') 函数
    解决浏览器无法直接识别 React JSX 语法的问题
有了思路,我们立刻开始执行:

执行 Babel 转换后 CommonJS 规范的代码,发现吃了个闭门羹:
腾讯|从 0 到 1 实现浏览器端沙盒运行环境
文章图片
原来是 require 函数没有定义,因为 CommonJs 规范就是利用 require 来加载模块的,既然现在没有定义,那我们就定义一个
问题三:如何实现 require 函数? 因为 require 是要引入 react, react-dom 两个 NPM 依赖库的,所以实现 require 函数之前,先插入已打包为 UMD 规范的文件路径,以获取 React, ReactDom 全局变量。

实现 require 函数也非常简单,需要拿哪个 NPM 依赖库,就直接把已加载到全局的库,返回回去即可。
其中的 externals 是什么?
相信熟悉 webpack 的同学应该比较了解,简单来说就是配置哪些库是在运行时(runtime),再去外部(全局)获取这些扩展依赖。详情请点击
腾讯|从 0 到 1 实现浏览器端沙盒运行环境
文章图片
前期准备工作已经做完,我们将以下文件保存为 index.html ,然后本地打开看看效果

可以看到,第一个小目标已经完美完成!
腾讯|从 0 到 1 实现浏览器端沙盒运行环境
文章图片
总结:Sandbox 核心方法论 经过上面简单例子的验证,不能发现,最小的例子都要不开以下三步,因此本文总结了浏览器端 Sandbox 沙盒的核心方法论:
  • Step1. 加载依赖
    • 加载 Babel, React, ReactDOM
  • Step2. 转译模块
    • 利用 Babel 将 ESM 转 CommonJS,转 JSX 语法
  • Step3. 执行代码
    • 构造 CommonJS 环境,如 require 加载模块函数
腾讯|从 0 到 1 实现浏览器端沙盒运行环境
文章图片
所以看过本文的同学,其他知识点记不住没关系,将本文的 Sandbox 方法论三部曲记住就行,记住就已经算掌握一半浏览器端沙盒原理了。
重要的事情说三次:
Step1. 加载依赖,Step2. 转译模块,Step3. 执行代码
Step1. 加载依赖,Step2. 转译模块,Step3. 执行代码
Step1. 加载依赖,Step2. 转译模块,Step3. 执行代码
下面我们用 Vue 创建一个业务项目,让 Vue 中用 Sandbox 沙盒(Iframe 形式)来加载另一个 React 应用,同时验证上述 Sandbox 方法论。
第二个小目标:从 0 到 1 实现一个浏览器端的 Sandbox 沙盒运行环境
由于我目前研发的是 WeDa 低代码平台(专有版),因此暂时起名 WeSandbox
WeDa 低代码平台(专有版) 由于内网环境问题暂不放链接,后续合适时期将开放给公司内部体验,目前大家可以先体验 WeDa 公有云版本
第二个小目标最终效果其有以下特点:
  • 可在 Vue 应用 Sandbox 里运行 React 代码
  • React useState 等功能均正常
  • 修改 JSON 数据可热更新 React 组件(不丢失状态)
  • 修改 CSS 数据可热更新样式
腾讯|从 0 到 1 实现浏览器端沙盒运行环境
文章图片
上图运行的是 Vue 应用,里面有个 iframe 承载着 WeSandbox 核心功能,其可以转译并运行 React 的代码。
Vue 应用代码