• 前端Rust二进制/wasm全平台构建流程简述


    前言

    开门见山,现代前端 Rust 构建基本分三大类,即 构建 .wasm 、构建 .node 二进制 、构建 swc 插件。

    入门详见 《 前端Rust开发WebAssembly与Swc插件快速入门 》 。

    对于单独开发某一类的流程,在上述参考文章中已有介绍,但对于一次开发后全平台构建发布,上述文章并未涉猎,基于此,本文将快速介绍一个最简的 全平台构建 (包括二进制 .node.wasm ) 前端 Rust 包的开发流程是怎样的。

    注:我们默认读者已掌握构建三大类前端 Rust 包的知识。

    正文

    Rust workspace

    以 workspace 组织代码仓库,核心逻辑全部独立为一个子包,参考如下:

     - crates
       - binding_node  # 基于 napi 分发 `.node` 二进制
       - binding_wasm  # 基于 wasm-pack 分发 `.wasm`
       - core          # 核心逻辑
       - ...           # 其他解耦
    
    • 1
    • 2
    • 3
    • 4
    • 5
    binding_node

    其中 binding_node 为 node 构建出口,引用核心逻辑后暴露 API ,简式参考如下:

    // binding_node/src/lib.rs
    
    #[macro_use]
    extern crate napi_derive;
    
    use napi::{bindgen_prelude::AsyncTask, Env, Task};
    use core::{core_process, IInput, IResult};
    
    // ⬇️ 同步部分
    #[napi]
    pub fn method_sync(input: IInput) -> Result<IResult, anyhow::Error> {
        core_process(input)
    }
    
    // ⬇️ 异步部分
    pub struct TaskExecutor {
        input: IInput,
    }
    
    pub struct ProcessTask {
        task: TaskExecutor,
    }
    
    impl Task for ProcessTask {
        type Output = IResult;
        type JsValue = IResult;
    
        fn compute(&mut self) -> napi::Result<Self::Output> {
            self.task.process().map_err(|err| napi::Error::from_reason(&err.to_string()))
        }
    
        fn resolve(&mut self, _env: Env, output: Self::Output) -> napi::Result<Self::JsValue> {
            Ok(output)
        }
    }
    
    impl TaskExecutor {
        pub fn process(&self) -> Result<IResult, anyhow::Error> {
            core_process(self.input.clone())
        }
    }
    
    #[napi(ts_return_type="Promise")]
    pub fn method(input: IInput) -> AsyncTask<ProcessTask> {
        AsyncTask::new(ProcessTask {
            task: TaskExecutor { input },
        })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    注:此处为最简函数式示例,涉及 异步信号、错误处理、错误打印、导出多方法的复杂类 等情况时请自行处理。

    通常情况提供最普通的 同步函数 方法已足够。

    类型处理

    为了尽可能减少工作量,让 napi 自动生成 .d.ts 类型,需给结构对象加上 #[napi] 宏才能生成类型,但所有结构声明在 crates/core 核心逻辑包中,而我们的 napi 出口在 crates/binding_node

    一种解法是条件编译,提供 feature = "node" 的特定模式,参考如下:

    # core/Cargo.toml
    
    [features]
    default = []
    node = ["napi", "napi-derive"]
    
    [dependencies]
    napi = { ..., optional = true }
    napi-derive = { ..., optional = true }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    // 条件编译宏
    
    #[macro_export]
    #[cfg(feature = "node")]
    macro_rules! multi_env {
        ($(
            $items:item
        )*) => {
            use napi_derive::napi;
            $(
                #[napi(object)]
                $items
            )*
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    使用参考:

    multi_env! {
    	pub struct IInput {
    	    pub input: ...,
    	}
    	pub struct IResult {
    	    pub output: ...,
    	}
    	// ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    综上,通过特定 node feature 方式,在引用时条件添加 #[napi] 来做到自动生成类型。

    人工应对复杂类型

    如 想自行管理导出方法类型的暴露情况、涉及 class 等复杂的类型 、无法自动识别生成 等情况,可以人工编写整份 .d.ts 文件,但十分耗费精力。

    对于 异步情况等 引发的动态值转换,而无法自动识别类型的,可尝试 napi 默认自带的类型选项,如 #[napi(ts_return_type="...")] 等选项来辅助修改生成的类型,省时省力。

    构建

    对于多平台,我们通常需要依赖 GitHub Actions 来进行多平台构建,在 CI 中构建、测试后发布到 npm 。

    现代常用构建对象 napi 列表如下:

        "triples": {
          "defaults": false,
          "additional": [
            "x86_64-apple-darwin",
            "aarch64-apple-darwin",
            "x86_64-pc-windows-msvc",
            "aarch64-pc-windows-msvc",
            "x86_64-unknown-linux-gnu",
            "aarch64-unknown-linux-gnu",
            "x86_64-unknown-linux-musl",
            "aarch64-unknown-linux-musl"
          ]
        },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    最常用的即如上 8 个平台,酌情构建 armv7-unknown-linux-gnueabihf ,必要时采用 wasm 兜底即可。

    此部分过于冗长且模板化,可参考 swc 等项目的构建 CI 来取用。

    binding_wasm

    其中 binding_wasm 为 wasm 构建出口,引用核心逻辑后暴露 API ,简式参考如下:

    use wasm_bindgen::prelude::*;
    
    use core::{core_process, IInput, IResult};
    
    #[wasm_bindgen(js_name = "methodSync")]
    pub fn method_sync(input: IInput) -> Result<IResult, JsError> {
        core_process(input).map_err(|err| JsError::new(&err.to_string()))
    }
    
    #[wasm_bindgen(typescript_custom_section)]
    const INTERFACE_DEFINITIONS: &'static str = r#"
    export function method(config: IInput): Promise;
    "#;
    
    #[wasm_bindgen(skip_typescript)]
    pub fn method(input: IInput) -> js_sys::Promise {
        wasm_bindgen_futures::future_to_promise(async {
            core_process(input)
                .map(|r| serde_wasm_bindgen::to_value(&r).unwrap())
                .map_err(|err| JsValue::from_str(&err.to_string()))
        })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    注:此处为最简函数式示例,涉及 异步、错误处理、错误打印 等情况时请自行修订处理。

    通过 serde-wasm-bindgen 来动态转换 JS 值与 Rust 中的结构。

    通常情况提供最普通的 同步函数 方法已足够。

    类型处理

    为了尽可能减少工作量,我们使用 tsify 做自动类型生成,同上文中相同,采用条件编译,提供 feature = "wasm" 模式:

    # core/Cargo.toml
    
    [features]
    default = []
    wasm = ["tsify", "wasm-bindgen"]
    
    [dependencies]
    tsify = { ..., optional = true }
    wasm-bindgen = { ..., optional = true}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    // 条件编译宏
    
    #[macro_export]
    #[cfg(feature = "wasm")]
    macro_rules! multi_env {
        ($(
            $items:item
        )*) => {
            use tsify::Tsify;
            use serde::{Deserialize, Serialize};
            use wasm_bindgen::prelude::*;
            $(
                #[derive(Tsify, Serialize, Deserialize)]
                #[tsify(into_wasm_abi, from_wasm_abi)]
                $items
            )*
        };
    }
    
    // 兜底用
    #[macro_export]
    #[cfg(all(not(feature = "wasm"), not(feature = "node"),))]
    macro_rules! multi_env {
        ($($tokens:tt)*) => {
            $($tokens)*
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    人工应对复杂类型

    如 想自行管理导出方法类型的暴露情况、无法自动识别生成 等情况,可以人工编写整份 .d.ts 文件。

    对于 异步情况等 引发的动态值转换,而无法自动识别类型的,尝试 #[wasm_bindgen(skip_typescript)] 跳过自动生成类型后,使用 #[wasm_bindgen(typescript_custom_section)] 人工插入少量类型声明来解决,节省编写时间。

    构建

    由于 wasm 无需依赖本机环境,根据情况可选在云构建或本地构建均可,主要包含 web 用途与 nodejs 用途的 wasm 产物构建。

    web 用途

    web 用途的 wasm 产物主要用于网页应用,playground 等,构建命令参考:

      # web 用途
      cd crates/binding_wasm && wasm-pack build --verbose --out-dir ./output/wasm_web --out-name index --release
    
    • 1
    • 2

    web 用途的构建产物包含 ESM 格式胶水代码 ,可直接将产物整体用在 webpack 项目导入,对于 webpack 5 可直接开启 async webassembly 特性直接适配项目:

    // webpack.config.js
    
      experiments: {
        asyncWebAssembly: true,
      },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    import * as wasm from '/path/to/wasm-output'
    // 或使用异步 await import() 延时、按需加载
    
    • 1
    • 2

    nodejs 用途

    nodejs 用途主要用于非主流平台兜底,如 边缘函数、serverless 等环境,构建命令参考:

      # nodejs 用途
      cd crates/binding_wasm && wasm-pack build --target nodejs --verbose --out-dir ./output/wasm --out-name index --release
    
    • 1
    • 2

    和 web 用途构建命令区别在于特定了 --target nodejs ,这会得到 CJS 格式产物代码,可直接用于 nodejs 。

    使用与安装时机

    对于两种 wasm 包,通常命名为 @scope/wasm ( nodejs 用途) 、@scope/wasm-web ( web 用途),在对应平台下,可直接安装该包来使用。

    同时,对非主流架构环境,我们一般在主包的 postinstall 时进行脚本检测,并在需要时自动安装 @scope/wasm 包来兜底,具体逻辑较冗长且模板化,可参考 swc 等项目取用即可。

    如何安装指定平台包

    最新版本的 pnpm v8 支持配置 pnpm.supportedArchitectures 来安装想要的平台包,即使你不在某个平台上,这通常用于 wasm 安装校验:

    # .npmrc
    # 关闭 postinstall 缓存
    side-effects-cache=false
    # 打印 postinstall 日志
    reporter=append-only
    
    • 1
    • 2
    • 3
    • 4
    • 5
    // package.json
    
      // ↓ 该配置将匹配不到任何 `.node` 的平台包,于是自动 fallback 到 wasm 兜底包
      "pnpm": {
        "supportedArchitectures": {
          "os": ["unknown"],
          "cpu": ["x64"]
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    wasm 产物优化

    通常 .node 二进制产物由 Rust 生产构建后自动优化,加上 strip 优化体积已足够。

    对于 wasm 一般采用 wasm-opt 优化体积,默认 wasm-pack 生产构建最终阶段会自动下载相关工具并执行优化,如遇网络问题,可关闭自动优化,转为手动下载后执行优化:

      # 提前下载好执行工具,防止网络问题
      cargo install wasm-bindgen-cli
    
    • 1
    • 2
    # binding_wasm/Cargo.toml
    
    # 关闭 wasm-opt 自动优化,之后手动优化
    [package.metadata.wasm-pack.profile.release]
    wasm-opt = false
    
    • 1
    • 2
    • 3
    • 4
    • 5
      # 下载 wasm-opt 工具并解压
      # 最新版本见:https://github.com/WebAssembly/binaryen/releases
      curl -L https://github.com/WebAssembly/binaryen/releases/download/version_116/binaryen-version_116-x86_64-macos.tar.gz -o ./binaryen.tar.gz
      mkdir ./.cache
      tar -xvf ./binaryen.tar.gz -C ./.cache
      # 优化 wasm 产物
      ./.cache/binaryen-version_116/bin/wasm-opt -Oz -o ./output/wasm/index_bg.wasm ./output/wasm/index_bg.wasm
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    总结

    在全平台构建时,编写 十分大量 的人力脚本与文件操作在所难免,如有可能,可将其统一抽象化,方便下次取用。

    由于流程更偏向于固定模板化,在实践时,请自行参考相关项目自取所需即可。

  • 相关阅读:
    黑马程序员Java实战项目--- ATM系统
    解决方案| anyRTC远程检修应用场景
    JS(第二十四课)JS高级Es6语法
    计算机毕业设计django基于Python的学校财务管理系统(源码+系统+mysql数据库+Lw文档)
    [算法周训 3] 字符串训练2
    R实现数据分布特征的视觉化——多笔数据之间的比较
    四、分布式锁之自定义分布式锁
    Python3数据科学包系列(三):数据分析实战
    C++入门04—数组与函数
    【云原生】-Docker安装部署分布式数据库 OceanBase
  • 原文地址:https://blog.csdn.net/qq_21567385/article/details/134338826