🚀女朋友说没找到好用的白板工具,我基于开源的魔改了一波🚀
前言
然后有一天,她跟我说:流程图都是比较正式的,而且没有自由画笔,有时候想画一个产品的原型草图,不太适合用流程图软件来画。这我一听,她不就是想要一个白板吗?
然后我就给她推荐了excalidraw,讲真的,这是我用过的最好用的、免费的白板软件。跟她说完之后,她就去用了,然后我就接着打游戏了。

用了一段时间之后她又跟我说,有点使用上的不便问题:
- 数据是存在本地的,没有在云端管理文件/文件夹的能力
- 手写字体是它的一个特别有特色的功能,但是它不支持中文的手写字体

于是我就看了一下, excalidraw 是开源的,那何必不拉下来改一下,改好了让她用用呢?
开始魔改踩坑
下面我们来正式开始魔改,基于这个包——@excalidraw/excalidraw
如果你是使用 vite 开发的,在 excalidraw 里面会用到 process.env 这个变量, vite 中是没有这个变量的,所以需要在配置文件中配一下,不然嘎嘎报错:
define: {
"process.env": process.env,
},
然后安装一下依赖 @excalidraw/excalidraw ,现在我们需要魔改这个库,用到的是 patch-package 这个包。
这里需要注意的是,我们需要改的包的版本号不能带前缀,应该写死版本:

比如说我们魔改一下 excalidraw 的入口文件:

然后执行npx patch-package @excalidraw/excalidraw,可以看到多了一个 patches 目录:

然后我们再新增一个 postinstall 脚本,这样每次重新安装依赖时都会应用 patch 。

我觉得手写体是 excalidraw 的一个很好很好的东西,奈何不支持中文,看仓库上也有不少人给他提过 issue ,然而好像并没有合并。
所以我就只能通过魔改的方式来支持,主要参考的是这个pr,在这里我主要改的也是 /dist/excalidraw.development.js 这个文件。
改完之后 patches 文件大概长这样子

好吧,那也看不出什么东西来,那我们就引入一下,看看改成功了没有:
import { Excalidraw } from "@excalidraw/excalidraw";
const ExDraw = () => {
return (
<div style={{ height: "100%" }}>
<Excalidraw />
</div>
);
};
export default ExDraw;
看起来是成功的了:

对比下面官网的:

然后我们打包发布一下,看看效果如何:

可以看到打包后的包体积比较大,而且还是经过 gzip 之后的。为啥体积那么大呢,因为我们改的是 development 的包,而不是 production 的。为啥不改 production 的呢?是因为不喜欢吗?
因为 production 已经被压缩混淆过,根本无从改起。。。。
可恶,难道只能到这里了吗?

收拾完心情之后,我再找呀找,找到了上述的开源组件对应的仓库地址,既然组件不好改,那我就改它的源码自己再打包一份。
下载下来之后,按照上面说的 PR ,修改了一下,跑起来之后,效果依然是可以的。

然后进到 /src/packages/excalidraw 这个目录下, npm i 安装一下依赖后,执行 npm run build:umd 进行打包构建,构建的产物在该目录的dist目录下。

然后修改一下这个目录的 package.json ,准备发到我们自己的 npm 账户下

执行一下 npm login ,登录你的 npm 账号,然后执行 npm publish ,把我们自己打的包发到 npm 仓库里。
发完包之后,在我们需要用到这个包的仓库安装一下,比如我的包名是 @jayliang/excalidraw ,那么久 npm i @jayliang/excalidraw 。
然后从 @jayliang/excalidraw 中导入 excalidraw 组件,别忘了把 vite.config.ts 的这个配置改成 production :

然而,我这么改了之后,加载的还是 development 的包,无语了,有没有懂 vite 的老哥解释下这个。


索性我就不纠结这个事情了,直接改一下这个 main.js 入口文件,反正我们上面已经用 patch-package 改过一次了,轻车熟路了属于是。

改完之后,执行一下 npx patch-package @jayliang/excalidraw 。
最后部署一下看看,有多大的提升:

一个从 2M 变成了 400K ,一个从 4.7M 变成了 950K ,属于是非常舒服,现在打开的速度十分丝滑。
数据初始化
大概介绍一下excalidraw核心的数据:
elements:节点files:文件,比如上传的图片appState:全局的一些信息
我是把数据存在 mongo 里,所以初始化的时候根据 id 去拿一下数据,然后反序列化一下交给组件就行:
const [initData, setInitData] = useState<any>({});
getFileDetail(id).then((res) => {
try {
let content = res.data.content || "{}";
content = JSON.parse(content);
setInitData(content);
} catch (error) {
console.log("error", error);
setInitData({});
} finally {
setInit(true);
}
});
<Excalidraw
initialData={{
appState: initData.appState || {},
elements: initData.elements || [],
files: initData.files || {},
}}
onChange={handleChange}
langCode="zh-CN"
/>
数据更新
数据更新主要监听组件的 onChange 事件,在这里,组件会把最新的 elements 、 appState 、 files 回调出来,这里需要注意的是,这个 onChange 触发会非常频繁,所以需要加一个防抖
const handleChange = debounce(
(
elements: readonly ExcalidrawElement[],
appState: AppState,
files: BinaryFiles
) => {
const obj = { elements, appState, files };
if (!equal(obj, oldData.current)) {
oldData.current = obj;
}
}
);
由于这个 onChange 触发十分频繁,我就没有在这里做把数据同步到后端的逻辑,而是做了一个保存按钮,需要手动点击或者 ctrl+s 。
以下是保存数据的代码:
const handleSave = async () => {
message.destroy();
const excalidrawAPI = excalidrawAPIRef.current;
const files = excalidrawAPI?.getFiles();
if (files) {
let promise: any = [];
const map: any = {};
Object.keys(files).forEach((id) => {
if (!fileMap.current[id]) {
fileMap.current[id] = true;
const current = files[id];
const file = dataURItoFile(current.dataURL, id);
const form = new FormData();
form.append("file", file);
promise.push(uploadFile(form));
} else {
promise.push(Promise.resolve());
}
map[promise.length - 1] = id;
});
if (promise.length > 0) {
const res = await Promise.all(promise);
for (let i = 0; i < res.length; i++) {
const cur = res[i];
let url = cur?.data;
if (url) {
const id = map[i];
files[id].dataURL = url;
}
}
}
}
const appState = excalidrawAPI?.getAppState();
const elements = oldData.current.elements;
const params = {
files: files || {},
appState: appState || {},
elements: elements || [],
};
await updateFile({
id,
content: JSON.stringify(params),
});
message.success("已保存");
};
这里插入 file 的时候默认是base64格式,所以我们要把 base64 上传换一个 url 之后再把数据落库。
咱们这个 excalidraw 组件也算是成功魔改接入了

最后
以上就是一个魔改第三方 npm 包的一个过程,如果你也有这种需求,不妨参考以下两种方式:
- 使用
patch的方式直接改node_modules的文件 - 从仓库中拉一份代码下来改完、打包发到你自己的
npm仓库上
这就是本文的全部内容,如果你觉得有意思的话,点点关注点点赞吧~
转载自:https://juejin.cn/post/7375083502411595788