Draft.js 改造
缘起
迫于 draft-js 在内部的维护之困难、bug 之多,需要想方案去处理整个富文本组件。 已有功能实现和替换成本,决定文本还是用 draft
需要基于 facebook/draft-js fork 出一份 draft-ts 仓库进行维护,不再像之前一样对编译后的包进行修改,以求降低开发和维护成本
过程
- 首先 draft-js 用了 yarn 去管理,在执行 yarn 之后,发现连安装依赖这个第一步都失败了,所以需要先将一些有问题的依赖做调整,让它能够安装。
- 我们要对它的整个工具链进行改造,首先需要知道整个 draft-js 是怎么被构建的,它用了 gulp,webpack 去做,整个流程都被编写在 gulpfile.js 里。我们要做的就是一步步地去删掉一些流程,观察其还能不能构建,构建产物是什么,最终我们能得到一个依赖最少,流程最精简化的一个状态。这时就可以保持不动,用后面新的工具链的生成产物去对比。
- draft-js 以 flow 编写,而我们的目标是 ts,那么要怎么改造呢?先看看有没有 flow → ts 的现成工具能帮我们做这件事情,一番搜索后有这么篇文章 https://medium.com/@michaelsholty/migrating-500k-lines-of-flow-code-to-typescript-15a8cad43fec 。作者介绍了公司项目 flow → ts 的经验,里面提到他用了一个 babel 插件去做这件事情,尝试了下后,发现可以做到,而且做得还挺不错,就写了个脚本去遍历做这件事情。其中有几个文件里遇到了无法转译的情况,就可以将报错信息输入到一个 log 文件中,人工处理。
- 现在代码里的模块都是用 CommonJS 的 require、module.exports 语法,需要把它们都替换成 ESM import 和 export 。仔细观察其结构会发现我们可以用正则搜索替换的形式去做到,但我不知道怎么写正则。。。。想到之前一直关注着一个叫 AST-Grep https://ast-grep.github.io/ 的项目,以 AST 的形式去做结构化搜索和替换,就开始尝试使用,最终效果非常好,执行
sg -p 'const $P1 = require($P2)' -l ts -r 'import $P1 from $P2' src/ -U和sg -p 'module.exports = $P' -l ts -r 'export default $P'就能达到目的。 - 全部变成 ts 文件了,这时候需要用 tsc 把 ts 代码编译成 js 使用,结果发现这个仓库很恐怖的一点来了。tsc 根本跑不起来,它所有文件都会报无法找到 import 文件的问题。仔细观察他的 import 结构就会发现,它的 import xxx from 'path',path 永远是文件名,跟我们正常使用项目内文件的路径完全不一样。然后查看它的工具链文件发现,它是在编译过程中改写路径名,再将所有文件路径拍平,所有文件失去原有结构,被放在一个父目录下。这个就太坑爹了。后面看 tsc config,它提供了 path 去设置这个相对路径。尝试写了几个文件后,发现真的有用,但是纯手写也不现实,就写脚本,获取文件名及其路径,最后加上去一测,waht,路径问题只解决了一部分。fackbook 以反人类的形式将 node_modules 下的 fbjs (facebook 自己的工具包)也做了 path,我为了不想依赖 fbjs 这么一大坨东西,我就手动将 draft 依赖的文件都搬进了仓库。最终缝缝补补,路径终于解决成功就剩下一堆类型报错。
- 类型报错的解决就是个纯体力活,基本上就是通过 .d.ts 将不知道到底是什么玩意的类型用 any 去表示,再者就是用 /@ts-ignore 将错误忽视,实在不知道什么情况的就把类型删掉用 any,整个流程就是跑一遍 tsc ,修改一遍。
- 解决完类型问题,tsc 终于跑过了,满心欢喜地开始在 playground 里去引用新的 draft-js,结果发现还是报了找不到文件的错误,报错路径奇奇怪怪、左思右想不得解,翻翻 dist 文件发现写在 tsconfig path 的 import 路径没有照我想象中的做了替换,再对 tsconfig path doc 定睛一瞧,不由得破口大骂,原来它早已做了温馨提示,“我们不提供路径改写的功能,我们只是 tsserver 的搬运工”,最终还是需要第三方工具如 babel 的东西来做这些事。于是毅然决然地选择 swc ,我对这个很快很快的 babel 替代品神往已久。这次是我第一次使用它,而当它也第一次编译成功时,我的眼泪也流了下来。好剑,好快的剑。
- 跑起来之后就是对基础功能进行验证,保证没问题后,接下来继续整理其他的几个生态包,react-draft-wysiwyg 、 draftjs-utils 和 playground 的搭建。仓库使用了 pnpm 进行包管理,包之间使用它的 workspace 协议进行声明
回顾(2025)
- 现在 2025 了,希望没有人还被困在 draft-js 上,真是糟心
- 当时做文本系列需求,第一步就是做出上述基础改造。提前打好的基础,为后续自定义渲染效果改造和修复一堆的中文输入相关问题提供了超高开发效率
- pnpm 有个很恶劣的 bug,对 .npmrc 的 authToken 识别有问题,导致无法正常 install https://github.com/pnpm/pnpm/issues/2933 ,而解决方案到现在还是只在 pnpm8.x 有效,所以 pnpm 版本也被锁住了。不过所有仓库中只有这个项目用了 pnpm,倒也没有什么危害
- 我现在才发现我一直没有打对 draft-js 项目名字(Draft.js)。。。只能怪项目做得不好,名字都取不对,代码明明都跟
draft-js有关,对外名字又是一套,见微知著,项目最终死了