实现 React 的 Playground(3)

356 阅读5分钟

上一节已经实现了左侧,切换文件list,编辑器里面的内容发生变化了。现在需要做的是将左边的编辑器也右边的预览器相互结合,实现playGround的核心功能了。

虽然@monaco-editor/react内置有jsx,ts的编译器,但是它出来的值永远是个字符串,就是说不管你在编辑器里面写出花来,在context里面保存的都是字符串。如果字符串想要展示到页面上,就得需要将jsx和ts重新解析成浏览器能读懂js代码,然后再插入到iframe里面,才能执行。

编译jsx相关代码

接下来就处理jsx和ts编译的问题,选择@babel/standalone。

npm install --save @babel/standalone
 npm install --save-dev @types/babel__standalone

image.png 所以写个编译器,编译工作主要是@babel-standalone来处理,他会将代码里面的jsx和ts给处理成浏览器能够看得懂的东西。

image.png

然后在Previewer/index.tsx下面的文件里面看看它渲染出来的数据

image.png

处理导入的包

在上面我们看到,代码编译了,但是导入文件没有编译,这肯定不行,首先react的导入

就是在script里面加入这个

image.png

处理导入文件

但是这个import App from './App';就必须拿到App在内存里面的地址,这个原理在实现React的Playground(1)说的很清楚了,就是做一个babel插件来处理

image.png

image.png

代码实现:

image.png

在这里需要注意的是我们不但要注意tsx的执行,还得清楚css的执行

image.png

那如何通过文件路径去files里面获取到具体的文件呢?files的内容长这样的

image.png

就是如果我们的导入语句是:import App from './App.tsx',那么我们就可以截取字符串,进行比对,获取对应的内容。

image.png

最后插件就做好了

function importPlugin(files: Files): PluginObj {
  return {
    visitor: {
      ImportDeclaration(path) {
        const modulePath = path.node.source.value;
        //先要区分是本地文件还是包
        console.log(modulePath.startsWith('.'));
        if (modulePath.startsWith('.')) {
          const file = getModuleFile(files, modulePath);//目的是补齐他的后缀
          console.log(file, 555);
          if (!file)
            return;

          if (file.name.endsWith('.css')) {
            path.node.source.value = css2Js(file);
          } else if (file.name.endsWith('.json')) {
            path.node.source.value = json2Js(file);
          } else {
            path.node.source.value = URL.createObjectURL(
              new Blob([babelTransform(file.name, file.value, files)], {
                type: 'application/javascript',
              })
            );
          }
        }
      }
    }
  };
}

小结:处理包

插件的主要逻辑是:我们根据url找具体的文件,文件类型主要有三种:js和json和css的js导入的时候扩展名会省略,而cssjson不会省略。所以根据扩展名可以先将css文件和json文件处理掉。

1.css文件是:我们拿到文件以后,在body下面创建一个style标签,然后直接把样式代码用createTextNode()转成文本,然后插入style标签即可。

2.json文件:json文件没有导入导出,所以我们要把他转成js,才能用。所以先把他处理成这样子:export default [{},{},{}]的字符串,然后用blob处理后,获取url。你要清楚的是在项目里面的json都是这么处理的。

3.js文件:首先咱们的js文件导入的时候并没有带扩展名,但是在files里面存储的数据却是带扩展名的,要想用file[name]拿到文件值,

第一步就是要拿到带扩展名的文件名。用筛选的办法直接寻找即可,因为在创建文件名的时候本就要求不重复。

第二步就是拿到文件以后,我们先要对文件内容进行编译,比如import App from './App',我们拿到App 文件以后,先要对App文件编译,编译后才能去获取内存地址。不然浏览器无法执行。

4.把开发好的插件放入编译器的plugin里面。

测试结果

最终的处理结果如下

image.png

接入iframe

1.创建一个html文件,作为静态模板

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  <title>Preview</title>
</head>
<body>
<script type="importmap"></script>
<script type="module" id="appSrc"></script>
<div id="root"></div>
</body>
</html>

2.在预览器加入iframe,基础代码如下:

image.png

3.首先第一步在html里面加入包引入

image.png

4.添加导入React

如果编辑器里面的组件没有导入React,页面就会报错,所以需要添加一下

image.png

测试如下:

image.png

文件增删改

我们已经实现左边编写代码,右边实时编译,但是现在比起vue的playGrounde来说,还差一个地方就是文件不能新增,现在我们来实现文件的新增。

image.png 双击文件名的时候变成input,可以修改文件名

image.png

1.可编辑文件名

image.png

2.修改文件名存储files里面

image.png

3.修改input的样式

image.png

测试结果:

image.png

4.新增,添加新增按钮

image.png

实现效果:

image.png

5.删除文件

文件名后面有个X号,点击就可以删除

image.png

删除功能直接调用context的内容。

image.png

##6.基础文件不可以删除

在页面上的main.tsx和import-map.json基础文件是不可以被删除的。所以在file里面需要添加一个标志才行。

image.png

image.png

实现效果 只有app.css又删除按钮,其他都没有。

image.png

测试结果:

image.png

image.png

优化: 1.删除按钮需要加上提示框,不能随便删除

2.页面的组件可以使用antd优化,而不是自己写,为了起到锻炼效果,我都是自己写的。

3.icon图标我们可以使用andtIcon组件,我是用svg做的

总结:

整个playground的功能就已经实现好了,我们主要做了下面这些事情:

1.把编辑器和预览器关联起来,实现左边编辑,右边实时展示效果。

2.预览器采用的是加载iframe实现效果,具体是创建一个html基础文件,里面包含两个script标签,一个是的typeimportmap,用来存放react的而是,esm.sh的地址,两一个的typemodule,用来装编译后的main文件。

3.需要重点理解的是,在iframehtml文件里面只需要加入main文件就好了,其他文件都是以blob的形式存储在内存里面的,只要用到它会根据内存url去获取具体的组件内容。

4.如果我们的组件里面没有导入react,在写babel插件的时候,应该给他加上。以免页面报错,提示找不到React

5.文件的增删改主要是利用context 里面定义的方法实现的。所以说context的设计真的很重要。