从 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 的链接:

原因二:低代码平台场景需要实时查看并调试当前应用的真实效果

用户在低代码平台开发时,如果应用实时预览的效果是与本地构建出来的效果是一致的,同时可以点击跳转到其他页面,查看整个业务流程的效果,那么整个开发体验都会有大幅度提升。

比如家庭健康码流程,包含 3 个页面:首页入口 → 健康码列表 → 健康码详情(详见开头视频动图)

第一个小目标:在浏览器上直接运行 React 源码文件渲染出 Hello, Sandbox!

源码如下:

import React from 'react';
import ReactDOM from 'react-dom';

ReactDOM.render(
  <div>Hello, Sandbox!</div>,
  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 链接体验

其代码转换效果如下:

  • 利用 @babel/plugin-transform-modules-commonjs 插件,将 ESM 语法转换成 CommonJS 格式规范

    解决浏览器不支持直接 import NPM 模块的问题

  • 利用 @babel/plugin-transform-react-jsx Babel 插件,<div /> 转换成 React.createElement('div') 函数

    解决浏览器无法直接识别 React JSX 语法的问题

有了思路,我们立刻开始执行:

<!DOCTYPE html>
<html>
<head>
  <!-- ① 依赖 -->
  <script src="https://unpkg.com/@babel/standalone@7.13.12/babel.min.js"></script>
</head>
<body>
  <div id="root"></div>

  <script>
    const code = `
import React from 'react';
import ReactDOM from 'react-dom';

ReactDOM.render(
  <React.StrictMode>
    <div>Hello, Sandbox!</div>
  </React.StrictMode>,
  document.getElementById('root')
);`
    // ② 转译
    // 此时代码已转为 CJS 格式,import 变成了 require 函数
    const transpiledCode = Babel.transform(code, {
      plugins: [
        ['transform-modules-commonjs'],
        ['transform-react-jsx'],
      ]
    }).code
    // ③ 执行
    eval(transpiledCode)
  </script>
</body>
</html>

执行 Babel 转换后 CommonJS 规范的代码,发现吃了个闭门羹:

原来是 require 函数没有定义,因为 CommonJs 规范就是利用 require 来加载模块的,既然现在没有定义,那我们就定义一个

问题三:如何实现 require 函数?

因为 require 是要引入 react, react-dom 两个 NPM 依赖库的,所以实现 require 函数之前,先插入已打包为 UMD 规范的文件路径,以获取 React, ReactDom 全局变量。

<!DOCTYPE html>
<html>
<head>
  <!-- ① 依赖 -->
  <script src="https://unpkg.com/@babel/standalone@7.13.12/babel.min.js"></script>
  <script src="https://unpkg.com/react@16.14.0/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@16.14.0/umd/react-dom.development.js"></script>
  <!-- 此时 react, react-dom 库已挂载到 window['React'], window['ReactDOM'] -->
</head>
<body>
  <div id="root"></div>
  <script>
    const externals = {
      react: 'React',
      'react-dom': 'ReactDOM'
    }
    function require(moduleName) {
      return window[externals[moduleName]]
    }
  </script>
</body>
</html>

实现 require 函数也非常简单,需要拿哪个 NPM 依赖库,就直接把已加载到全局的库,返回回去即可。

其中的 externals 是什么?

相信熟悉 webpack 的同学应该比较了解,简单来说就是配置哪些库是在运行时(runtime),再去外部(全局)获取这些扩展依赖。详情请点击

前期准备工作已经做完,我们将以下文件保存为 index.html ,然后本地打开看看效果

<!DOCTYPE html>
<html>
<head>
  <!-- ① 依赖 -->
  <script src="https://unpkg.com/@babel/standalone@7.13.12/babel.min.js"></script>
  <script src="https://unpkg.com/react@16.14.0/umd/react.development.js"></script&
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值