上一节已经实现了左侧,切换文件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
所以写个编译器,编译工作主要是@babel-standalone来处理,他会将代码里面的jsx和ts给处理成浏览器能够看得懂的东西。
然后在Previewer/index.tsx下面的文件里面看看它渲染出来的数据
处理导入的包
在上面我们看到,代码编译了,但是导入文件没有编译,这肯定不行,首先react的导入
就是在script里面加入这个
处理导入文件
但是这个import App from './App';就必须拿到App在内存里面的地址,这个原理在实现React的Playground(1)说的很清楚了,就是做一个babel插件来处理
代码实现:
在这里需要注意的是我们不但要注意tsx的执行,还得清楚css的执行
那如何通过文件路径去files里面获取到具体的文件呢?files的内容长这样的
就是如果我们的导入语句是:import App from './App.tsx',那么我们就可以截取字符串,进行比对,获取对应的内容。
最后插件就做好了
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导入的时候扩展名会省略,而css和json不会省略。所以根据扩展名可以先将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里面。
测试结果
最终的处理结果如下
接入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,基础代码如下:
3.首先第一步在html里面加入包引入
4.添加导入React
如果编辑器里面的组件没有导入React,页面就会报错,所以需要添加一下
测试如下:
文件增删改
我们已经实现左边编写代码,右边实时编译,但是现在比起vue的playGrounde来说,还差一个地方就是文件不能新增,现在我们来实现文件的新增。
双击文件名的时候变成input,可以修改文件名
1.可编辑文件名
2.修改文件名存储files里面
3.修改input的样式
测试结果:
4.新增,添加新增按钮
实现效果:
5.删除文件
文件名后面有个X号,点击就可以删除
删除功能直接调用context的内容。
##6.基础文件不可以删除
在页面上的main.tsx和import-map.json基础文件是不可以被删除的。所以在file里面需要添加一个标志才行。
实现效果 只有app.css又删除按钮,其他都没有。
测试结果:
优化: 1.删除按钮需要加上提示框,不能随便删除
2.页面的组件可以使用antd优化,而不是自己写,为了起到锻炼效果,我都是自己写的。
3.icon图标我们可以使用andt的Icon组件,我是用svg做的
总结:
整个playground的功能就已经实现好了,我们主要做了下面这些事情:
1.把编辑器和预览器关联起来,实现左边编辑,右边实时展示效果。
2.预览器采用的是加载iframe实现效果,具体是创建一个html基础文件,里面包含两个script标签,一个是的type是importmap,用来存放react的而是,esm.sh的地址,两一个的type是module,用来装编译后的main文件。
3.需要重点理解的是,在iframe的html文件里面只需要加入main文件就好了,其他文件都是以blob的形式存储在内存里面的,只要用到它会根据内存url去获取具体的组件内容。
4.如果我们的组件里面没有导入react,在写babel插件的时候,应该给他加上。以免页面报错,提示找不到React。
5.文件的增删改主要是利用context 里面定义的方法实现的。所以说context的设计真的很重要。