- 开始日期: 2018-02-14
- RFC PR: (保留为空)
- 跟踪问题: (保留为空)
摘要
使 Rust 包能够透明地依赖 npm 生态系统中的包。这些依赖项将像通过 Cargo 的普通 Rust 依赖项一样,在被其他包使用时无缝工作。
动机
wasm-bindgen
和 wasm-pack
的主要目标是实现 Rust 与 JS 的无缝集成。然而,JS 生态系统中很大一部分,npm,目前在 wasm-bindgen
和 wasm-pack
中几乎没有支持,这使得难以访问 JS 提供的这种丰富资源!
本 RFC 的目标是使这些依赖项能够存在。Rust 包应该能够像 NPM 可以要求编译为 wasm 的 Rust 包一样,要求来自 NPM 的功能。任何当前使用 NPM 包的工作流程(例如使用捆绑器打包 WebAssembly)都应该继续工作,但也允许根据 Rust 依赖项的要求拉取“自定义”NPM 包。
利益相关者
本 RFC 主要影响那些同时使用 wasm-pack
和 wasm-bindgen
以及 Webpack 等捆绑器的用户。然而,这也影响了 Rust 生态系统中核心基础包的开发人员,他们希望能够意识到拉取 NPM 依赖项的能力。
详细说明
将 NPM 依赖项添加到 Rust 项目中将与将 NPM 依赖项添加到普通 JS 项目中非常相似。首先,需要声明依赖项及其版本要求。本 RFC 建议在 crate 的 Cargo.toml
文件旁边的 package.json
文件中执行此操作
{
"dependencies": {
"foo": "^1.0.1"
}
}
package.json
文件最初将是 NPM 的 package.json
的一个子集,只支持一个 dependencies
顶级键,该键在内部包含具有字符串的键值对。但是,除了此验证之外,不会对 dependencies
中的键值对进行任何验证。将来,打算支持 NPM 中更多 package.json
的键,但本 RFC 旨在成为一个 MVP,以便现在能够依赖 NPM。
创建此 package.json
文件后,接下来需要在 Rust crate 中导入该包。与其他 Rust 对 JS 的依赖项一样,这将使用 #[wasm_bindgen]
属性完成
# #![allow(unused_variables)] #fn main() { #[wasm_bindgen(module = "foo")] extern "C" { fn function_in_foo_package(); } #}
注意:在 JS 中,上面的导入将类似于
import { function_in_foo_package } from "foo";
#[wasm_bindgen]
属性中现有的 module
键可用于指示导入来自哪个 ES 模块。这会影响最终输出 wasm 二进制文件中的 module
键,并对应于 package.json
中的包名称。这旨在与捆绑器约定如何将 NPM 包解释为 ES 模块相匹配。
这两个工具到位后,您只需要执行 wasm-pack build
,您就可以开始了!最终的 package.json
将在我们的 package.json
中列出 foo
依赖项,并准备好通过捆绑器使用。
技术实现
在幕后,有一些移动部件使这一切成为可能。让我们首先看一下 wasm-bindgen
中的部分。
本 RFC 的主要目标是实现对 NPM 的透明和传递依赖。#[wasm_bindgen]
宏是 crate 构建中唯一可以访问所有传递依赖项的部分,因此我们将使用它来获取 package.json
。当指定带有 module
键的 #[wasm_bindgen]
时,它将在过程宏的 cwd 中查找 package.json
(请注意,cwd 由 Cargo 设置为包含正在编译的 crate 的 Cargo.toml
的目录,或者编写 #[wasm_bindgen]
的 crate)。如果找到此 package.json
,它将对其进行绝对路径编码到 wasm-bindgen
已经发出的自定义部分中。
稍后,当 wasm-bindgen
CLI 工具执行时,它将解析并解释 wasm-bindgen 自定义部分中的所有项目。将加载、解析和验证(即目前只允许 dependencies
)列出的所有 package.json
文件。如果加载了任何 package.json
,则将在 --out-dir
中的输出 JS 文件旁边发出一个 package.json
文件。
wasm-bindgen
执行后,wasm-pack
将读取输出的 package.json
(如果有),并用元数据和其他已发出的项目对其进行扩充。
如果依赖关系图中的多个 crate 依赖于 NPM 包,那么在本 MVP 建议中,将生成错误。将来,我们可以实现一定程度的合并版本要求,但为了保持简单,wasm-bindgen
将发出错误。
与 --no-modules
的交互
依赖 NPM 包从根本上来说需要 NPM,无论以何种方式。wasm-bindgen
和 wasm-pack
CLI 工具具有输出模式(特别是 wasm-bindgen
的 --no-modules
和 wasm-pack
的 --target no-modules
标志),这些模式旨在不需要 NPM 和其他 JS 工具。在这些情况下,如果在任何 Rust crate 中检测到 package.json
,将发出指示此情况的错误。
请注意,这意味着旨在与 --no-modules
一起工作的核心 crate 将无法添加 NPM 依赖项。相反,他们要么必须从 crates.io 导入 Rust 依赖项,要么使用 本地 JS 代码段 等功能来导入自定义 JS 代码。
缺点
本 RFC 的主要缺点之一是它与 wasm-bindgen
和 wasm-pack
的主要用例,--no-modules
和 --target no-modules
标志,从根本上不兼容。作为短期权宜之计,本 RFC 建议将其设为硬错误,这将阻碍此功能在希望在此模式下可用的 crate 中的采用。
然而,从长远来看,这可能是可行的。例如,许多 NPM 包都可以在 unpkg.com
或其他位置获得。如果这些位置的所有包都遵循众所周知的约定,那么可能可以生成与这些托管 NPM 包的位置兼容的代码。在这种情况下,可能可以在几个位置“只放置一个脚本标签”来使 --no-modules
与 NPM 包一起工作。不过,目前尚不清楚这是否可行。
基本原理和替代方案
在开发本 RFC 时,已经阐明了一些指导其设计的价值观
-
Rust 生成的 WebAssembly 项目的开发应该允许开发人员使用他们最舒适的开发环境。编写 Rust 的开发人员应该可以使用 Rust,而使用 JavaScript 的开发人员应该可以使用基于 JS 的运行时环境(Node.js、Chakra 等)。
-
JavaScript 工具和工作流程应该可以与 Rust 生成的 WebAssembly 项目一起使用。例如,Webpack 和 Parcel 等捆绑器,或
npm audit
和 GreenKeeper 等依赖项管理工具。 -
在可能的情况下,应做出允许解决方案不仅可用于 Rust 开发人员,还可用于 C 和 C++ 开发人员的决定。
-
决定应侧重于创建允许开发人员轻松学习和获得高效开发体验的工作流程。
这些原则导致了上述使用 package.json
声明 NPM 依赖项的建议,然后由 wasm-bindgen
将其分组在一起,以便由 wasm-pack
发布。通过使用 package.json
,我们获得了与 GreenKeeper 和 npm install
等现有工作流程的固有兼容性。此外,package.json
在整个 JS 生态系统中得到了很好的记录和支持,使其非常熟悉。
本 RFC 排除的一些其他替代方案是
-
使用
Cargo.toml
而不是package.json
来声明 NPM 依赖项。例如,我们可以使用[package.metadata.npm.dependencies] foo = "0.1"
但这有一个缺点,即与围绕
package.json
的所有现有工作流程不兼容。此外,它还突出了 NPM 和 Cargo 之间以及如何解释"0.1"
作为版本要求(例如^0.1
或~0.1
)的差异。 -
添加一个单独的清单文件 而不是使用
package.json
也是一种可能性,这可能更容易让wasm-bindgen
读取,然后解析/包含。这可能有一个好处,即它只针对我们的用例,并且不会通过不允许package.json
中其他有效字段来造成误导。但是,这种方法的缺点与Cargo.toml
相同,即它对大多数人来说是一个不熟悉的格式,并且与现有工具不兼容,而没有带来太多好处。 -
内联注释版本依赖项 也可以使用,而不是
package.json
,例如# #![allow(unused_variables)] #fn main() { #[wasm_bindgen(module = "foo", version = "0.1")] extern "C" { // ... } #}
与所有其他替代方案一样,这与现有工具不兼容,但它也不符合 Rust 自己声明依赖项的机制,该机制将版本信息的位置与代码本身分开。
未解决的问题
- MVP 仅使用
dependencies
的限制是否过于严格?是否应该在package.json
中支持更多字段?