前言
之前魔改了一个开源的流程图组件,让女朋友成功不再受 Processon
免费版的困扰——女朋友不想开Processon会员,我魔改了一个无限制的在线绘图软件。
然后有一天,她跟我说:流程图都是比较正式的,而且没有自由画笔,有时候想画一个产品的原型草图,不太适合用流程图软件来画。这我一听,她不就是想要一个白板吗?
然后我就给她推荐了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
仓库上
这就是本文的全部内容,如果你觉得有意思的话,点点关注点点赞吧~