Rust + Neon + Node 开发 VSCode 插件(英雄集结篇)

1,339 阅读5分钟

「这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战

公司梭哈了 Protobuf,为了提效,我们开发了一套完整的工具链,转换工程、PB Mock、各语言筛选工具等等,今天主要来讲讲筛选工具的事情。

转换工程把 .proto 文件转换成各个语言,我们目前支持 javagojsswiftdartpython等语言,转换结果保存在各自的结果仓库。

不同的客户端需要用到的 PB 文件不尽相同,全量引用会增加代码体积,对客户端非常不友好,所以需要一个工具来做筛选工作,客户端只需要引入接口使用到的 PB 文件。工具根据一份开发人员维护的 JSON 列表从结果仓库查找指定文件,输出到指定目录下。

第一版筛选工具是用 Node开发的,使用 pkg 这个包进行可执行文件的打包,功能上满足需求,也在持续优化迭代。最大的问题就是打包后输出的可执行文件实在太大了,足足 45M ,如果上传到 git 显得很笨重,不上传的话每次新拉取项目都要去下载工具,着实有些麻烦。所以再想有没有可以优化的地方。

最近团队组织学习 Rust ,在测试以后发现,编译出来的可执行文件远远小于 Node pkg 的体积,用 Rust 来写新一代工具的想法便浮上心头。

然而就算用 Rust 来写,还是需要下载,还有没有更偷懒一点的方法呢?突然想到 Rust 可以编译成 Node 模块,是不是能整合成一个项目,既可以用在 VSCode 插件项目又能编译出可执行文件,这样使用 VSCode 的小伙伴就不用单独下载了,也能完成更多原来单可执行文件做不到的事情。

Node 模块编译选型

在选择 Rust to Node 模块编译工具的时候,考虑了两个方向:

  1. WebAssembly
  2. Node 原生模块

WebAssembly

在测试中使用的是 wasm-pack 这个工具,支持将 Rust 编译为多种 js 引用方式,比如 nodejswebwebpack等。[详见官网]

生成的 wasm 经测试的确可用,但是因为安全性并不支持系统级别的文件操作,不论 Node 或者浏览器环境都行,所以这个方向放弃。

这边提一下,目前在使用苹果自家芯片(M1、M1X)的 mac 上,用官网的安装方法,或者 npm 直接安装会失败。

唯一成功的方式是使用 cargo 安装,如果用默认的方式也会失败,爬了很多文后才发现,需要将安装目标设置为老版本的Mac OS,这样 cargo 会认为平台是 x86 ,这样编译安装就可以了。

经测试,这样安装的 wasm-pack 编译的结果没有什么问题,也有可能是测试程度较浅,朋友们自行探索。

env MACOSX_DEPLOYMENT_TARGET=10.7 cargo install wasm-pack

Node 原生模块

node_bindgen

安装简单,但是安装后编辑器一直报无法找到该模块,却又能编译出 Node 模块,强迫症不能忍,让了。

Neon

主角来了,它的表现非常好,只需要在 Rust 下加一个连接层,就能实现 NodeRust 都成功调用。直到把它引入 VSCode 插件项目后,噩梦开始了,VSCode 调试的时候一直报 NODE_MODULE_VERSION 要求的是 89,而引入的 Node 模块是 83

经过搜索发现,这个问题的原因是 Electron 没有使用 NodeABI 版本,而是自定义了版本,而VSCode是基于Electron开发的。这样就算把本地的 Node 切换到跟 Electron 一致,也编译不出可用的 Node 模块。

这个问题困扰了我很久,后来在一个Neon官方提供的的 Demo 中,找到了一份环境变量的配置,在执行编译 Node 模块之前,先引入这份环境变量,就可以编译出对应版本的模块了。

export NEON_NODE_ABI=89
export npm_config_target=13.5.1
export npm_config_disturl=https://atom.io/download/electron
# export npm_config_arch=x64   M1/M1X 芯片不需要
# export npm_config_target_arch=x64   M1/M1X 芯片不需要
export npm_config_runtime=electron
export npm_config_build_from_source=true

这样虽然可以使用,但是有个问题,VSCode 如果更新了Electron版本,支持的ABI版本发生变化以后,我们编译出来的模块就不能使用了。目前想到的方法有三个:

  1. 预先构建从 89 - 102(目前最高)的 Node 模块。
  2. 插件加载的时候根据当前 VSCode 内环境构建对应版本的 Node 模块。
  3. VSCode 更新时及时更新插件版本。

第一个方法显然是一个笨方法,插件体积会非常大,非常不可取。

第二个方法需要宿主环境安装有 Rust,否则无法完成编译。

第三个方法目前开来是最靠谱的方法了,只能期待巨硬更新Electron版本慢一点了。

项目结构

技术选型暂定以后,我们项目的结构如下:

bb-pb-tools
├─ .eslintrc.json
├─ .gitignore
├─ .vscode
│  ├─ extensions.json
│  ├─ launch.json
│  ├─ settings.json
│  └─ tasks.json
├─ .vscodeignore
├─ CHANGELOG.md
├─ README.md
├─ env.sh
├─ native  Rust 目录,Node 模块、Rust 可执行文件的编译在这个目录下进行
│  ├─ Cargo.lock
│  ├─ Cargo.toml
│  ├─ artifacts.json
│  ├─ build.rs
│  ├─ index.node 编译后的 Node 模块,后面考虑输出到其他目录
│  └─ src
│     ├─ lib
│     │  └─ index.rs
│     ├─ lib.rs
│     └─ main.rs
├─ package-lock.json
├─ package.json
├─ src  VSCode 插件目录
│  ├─ extension.ts
│  └─ test
│     ├─ runTest.ts
│     └─ suite
│        ├─ extension.test.ts
│        └─ index.ts
├─ tsconfig.json
├─ vsc-extension-quickstart.md
└─ yarn.lock

完整流程

完整流程

好了朋友们,今天的分享就到这里,潮水马上就要涨上来了。