前面三章已经实现了一个react的playGround,主要功能:
1.我们利用@monaco-editor/react做编辑器,
2.利用iframe做预览器,
3.利用@typescript/ata实现编辑器里面ts的检测
4.利用contextApi做文件数据中心
5.利用@babel/standalone来实现浏览器编译react和ts
最终实现的效果如图所示:
这一章节主要对整页面做优化处理:
一. 处理错误
当前如果页面错误,预览区就会出现白屏,在F12里面你会看到具体的报错信息。如图
最好的办法就是把这个错误显示到预览区上去
vuePlayGround的错误处理
我们可以模仿他,也可以将错误直接显示在预览器上,我喜欢将错误直接显示在预览器上,简单粗暴。我们尝试看看:
1.1 拿到编译错误
拿到错误以后,我们希望错误能够安装它原有的格式展示在页面上,而不是被转化成html文本来展示,所以用到pre标签
*pre标签**是HTML**中的一个标签,用于定义预格式化的文本。 * 在pre元素中的文本会保留空白字符(如空格和换行符),并且文本会按照源代码的格式显示,而不会被浏览器解释为HTML元素或样式。
pre标签的好处:
所以我们写的错误组件是这样的:
这个页面很简单,就是一个div上展示错误,在右上角显示一个关闭按钮就好了。面板的样式需要根据错误和警告发生变化,警告是黄色,错误是红色。
1.2把错误显示到预览器上
new Error().stack!.toString()实际上是创建了一个新的Error对象,然后访问它的stack属性,并将其转换为字符串。这个表达式中的!操作符是用来断言stack属性不为null或undefined。等于:
let stack = (new Error().stack || '').toString();
1.3错误哪里来?
在iframe的html上我们需要监听error,然后将监听到的错误用通过 postMessage 传递给父窗口。
在预览器里面我们接收下消息
const [error, setError] = useState('');
const handleMessage = (msg: MessageData) => {
const { type, message } = msg.data;
if (type === 'ERROR') {
setError(message);
}
};
useEffect(() => {
window.addEventListener('message', handleMessage);
return () => {
window.removeEventListener('message', handleMessage);
};
}, []);
测试
二.切换主题
实现效果如下:
1.在context里面设置主题
主题切换需要的数据应该存储在最顶层的context里面,这样所有的组件都能快速的拿到数据
主题切换的原理:声明一些全局的 css 变量,写样式的时候用这些变量。切换主题时切换不同的全局变量值即可。
2.在父组件样式里面定义变量
在index.css下面上声明样式:它申明了一个light的样式,一个是dark的样式。
3.父组件接入样式
然后在index.tsx下面接入
这样只要在父组件下面定义的样式变量,在子组件里面都可以用var获取到。
4.子组件使用样式变量
比如在Header组件里面
其他地方的样式也是类同处理!
5.在Header组件里面添加主题切换按钮
antd的icon已经被提取到了一个专门的包里面:npm i @ant-design/icons -S
测试
6.编辑区添加样式-文件名
7.编辑器:
8.测试效果
三.链接分享
点击分享按钮,链接会复制到剪贴板。然后在新的浏览器窗口打开,可以看到分享的代码。
其实说白了,其实咱们要分享是地址和context数据。因为整个页面,不管是编辑器也好,预览器也好,还是fileList,他们都是围绕context在做事。
那怎么将地址和context数据一起发给对方呢?我们选用的是location.hash,就是把context存放到hash里面去。
1 在provide里面给url加数据
我们要清楚的是:当前的地址里面就应该把files的数据带上,而不是等分享的时候再去处理
测试
2用url在其他浏览器上打开
那把这个 url 分享出去之后,初始化的时候用 hash 中的 files 就好了:
值的注意的是,在files里面的value是字符串,即使在编辑器是js文件,实际上他就是字符串,所有我们直接可以用JSON.parse()处理。
测试
这就算是成功了。
3压缩files文件
如果文件太多,咱们直接放到hash里面很不好,所以我们希望能够把files压缩以后,在传到hash里面。我们选用的工具是:fflate
fflate 是一个快速、轻量级且纯JavaScript实现的压缩库,用于处理gzip、zlib和Deflate格式的数据压缩与解压缩。
它专注于提供高性能的压缩算法实现,特别适合于浏览器环境及Node.js环境中使用,且不依赖任何外部库。fflate的优势是体积小,速度快,所以他是很多前端项目中压缩数据的首选库。
该库主要特点包括:
- 无依赖:fflate是自包含的,不需要额外加载其他库。
- 高性能:相比其他JavaScript压缩库,fflate在压缩和解压缩速度上表现更优。
- 体积小:压缩后的库体积很小,有助于减少项目的总体大小。
- 跨平台:同时支持浏览器和Node.js环境。
- 流式处理:支持流式读写,可以处理大文件而不用担心内存溢出问题。
- 完整支持:实现了Deflate、Zlib和Gzip的压缩与解压缩功能。
- 使用fflate,你可以很容易地对字符串、ArrayBuffer或TypedArray进行压缩和解压缩操作。
例如,以下是一个简单的使用示例:
// 引入fflate
import { gzip, ungzip } from 'fflate';
// 压缩数据
const originalData = 'This is some example data to compress.';
const compressed = gzipSync(originalData);
// 解压缩数据
const decompressed = ungzip(compressed);
console.log(decompressed); // 输出原始数据
安装
npm install --save fflate
写两个工具函数,压缩和解压
这里的 atob、btoa 是二进制的 ASC 码和 base64 的字符串的转换:
在useEffect里面压缩
在getFilesFromUrl里面解压
4 查看效果:
所以对发送url来说,里面的内容压缩是非常有必要的,你只需要用专门的工具压缩,再解压即可。
5 header里面添加按钮
此时的复制了就需要一个剪切板的工具:copy-to-clipboard
npm install --save copy-to-clipboard
四.代码下载
编辑器里面的文件还是下载下来,本地使用,是不是很开心?
首先下载代码使用的工具是file-saver,下载下来需要压缩打包,使用jszip
回忆一下上面我们对url里面hash压缩用的是fflate,现在对文件压缩用的jszip
我们看看他们的对比:
所以此时我们选择了jszip,再看看file-saver他是个纯前端导出工具,如果你想把前端的数据导成一个excel,那就可以是使用file-saver,:参考文章,其实你可以用这个工具把前端的啥都可以导出来。字符串,文件参考
1.安装
npm install --save jszip
npm install --save file-saver
npm install --save-dev @types/file-saver
2.在utils/index.ts里面封装一个下载文件的函数
我们需要用jszip给files里面的每一个文件都打包,形成一个zip以后,用file-saver下载
3.接入按钮
在header的上面提那家一个下载按钮,然后实现点击按钮,直接下载文件。
4.测试
五.性能优化
1. 调试说明
性能优化第一步就是查看他的指标,我们先看看页面指标
是不是下一调,我们看到performance才有38分,就说明代码需要优化了,具体哪里需要优化进入performance看看,就是要看他有没有长任务。
在这篇文章里面就明确的告诉你了,performance怎么使用,有不懂的,可以参考:# 从 EventLoop 到 Proformance ,再到 Web Work
灰色就代表宏任务 task
橙色的是浏览器内部的 JS
蓝色的是 html 的 parse
紫色是样式的 reflow、repaint
绿色的部分就是渲染
其余的颜色都是用户 JS 的执行了
所以在performance的时候,你时刻关注的是除上面颜色以外的东西
需要优化的地方如下:
性能优化的目标就是消除这种 long task。
2.处理compile.ts
长任务的解决办法就是Web Worker
vite里面的使用方式:
import MyWorker from './worker?worker'
const worker = new MyWorker()
简单使用:在compiler里面发送一条消息,主线程接受下
测试如下
其实他们之间的通信就是 postMessage 和监听 message 事件,一边发送消息,另一边监听消息。如果主线程把files的内容发送给conpile.ts编译,那边只要监听这个消息就好了。
在预览器发送消息
conpile.ts里面处理编译工作
优化结果:
再看看performance work线程里面
主线程里面
我们的compile的问题就没有。
其他还需要编译,就请使用相同的方式即可。