在 Manifest V3 扩展中以 ES Module 方式加载 WASM:cookbook.wasm-helloworld-print 完整实战
2026/9/21 16:13:54 网站建设 项目流程
  • 示例工程

【免费下载链接】chrome-extensions-samples

Chrome Extensions Samples

项目地址:https://gitcode.com/gh_mirrors/ch/chrome-extensions-samples
点击查看免费下载

本篇技术指南基于 chrome-extensions-samples 仓库中的 functional-samples/cookbook.wasm-helloworld-print 示例,讲解如何在 Manifest V3(MV3)扩展的 Service Worker 中通过 ES Module 方式加载并调用 WebAssembly(WASM)模块。读完本文,你将掌握 MV3 下wasm-unsafe-evalCSP 指令的配置方法、type: "module"声明 Service Worker 的要点,以及基于 Rust + wasm-pack 的完整构建与调用链路。

为什么 MV3 中加载 WASM 需要特殊配置

Manifest V3 扩展默认执行严格的内容安全策略(Content Security Policy,CSP),扩展页面与 Service Worker 的默认策略不允许执行不受信任的代码。WASM 模块的实例化在 CSP 语境下被视作一种"不安全求值"(unsafe-eval)行为,因此在 MV3 中加载 WASM 之前,必须先在 manifest 中为extension_pages显式声明wasm-unsafe-eval指令,否则浏览器会直接拒绝实例化 WASM 模块。

本示例的 manifest.json 正是这样配置的:

{ "name": "WASM Load Example - Helloworld", "version": "1.0", "manifest_version": 3, "description": "Shows how to use WebAssembly (WASM) as a module in Manifest V3.", "background": { "service_worker": "background.js", "type": "module" }, "content_security_policy": { "extension_pages": "default-src 'self' 'wasm-unsafe-eval'" } }

两个关键点:

  • content_security_policy.extension_pages设置为"default-src 'self' 'wasm-unsafe-eval'",在默认'self'的基础上放行 WASM 求值;
  • background.service_worker增加"type": "module",将 Service Worker 声明为 ES Module,这是导入 wasm-pack 生成的 ES Module 胶水代码的前提(详见下文)。

运行扩展并验证 WASM 输出

加载方式

  1. 克隆本仓库(git clone后进入chrome-extensions-samples目录)。
  2. 打开 Chrome 的扩展管理页,开启"开发者模式",选择"加载已解压的扩展程序",指向本示例目录functional-samples/cookbook.wasm-helloworld-print
  3. 在扩展列表中定位名为"WASM Load Example - Helloworld"的扩展,打开其 Service Worker 的调试控制台(点击"Service Worker"链接)。

预期输出

扩展安装触发chrome.runtime.onInstalled事件后,Service Worker 控制台会打印出以下三行日志:

[from wasm] Inited. [from wasm] Hello World! [from wasm] Hello John

这三行日志分别来自 Rust 侧的mainprintprint_with_value三个导出函数(详见下文源码分析),它们通过web_sys::console直接写入浏览器控制台,直观证明 WASM 模块已在 MV3 Service Worker 环境中成功实例化并完成函数调用。

源码链路分析:从 Rust 到浏览器控制台

整个示例的调用链可以分为三层,我们可以逐一在仓库中核对源码实现。

第一层:Rust 侧导出函数(wasm/src/lib.rs)

use wasm_bindgen::prelude::*; use web_sys::console; // will be called when the wasm module is loaded // https://rustwasm.github.io/docs/wasm-bindgen/reference/attributes/on-rust-exports/start.html #[wasm_bindgen(start)] pub fn main() { console::log_1(&"[from wasm] Inited.".into()); } #[wasm_bindgen] pub fn print() { console::log_1(&"[from wasm] Hello World!".into()); } #[wasm_bindgen] pub fn print_with_value(value: &str) { // with 2-args log function console::log_2(&"[from wasm] Hello".into(), &value.into()); }

需要留意的三个细节:

  • #[wasm_bindgen(start)]标注的main函数会在 WASM 模块实例化完成后自动执行(对应 wasm-bindgen 的 start 属性),因此Inited.日志不需要显式调用就会出现;
  • #[wasm_bindgen]宏把普通 Rust 函数导出为可被 JS 调用的接口;
  • web_sys::console提供的log_1/log_2分别对应浏览器console.log的单参数与双参数形式,&str通过.into()转换为JsValue

工程配置见 wasm/Cargo.toml:

[package] name = "helloworld-demo" version = "0.1.0" edition = "2021" [lib] crate-type = ["cdylib"] [dependencies] wasm-bindgen = "0.2.84" web-sys = { version = "0.3.61", features = ['console'] }

其中crate-type = ["cdylib"]是编译成 WASM 动态库的标准配置,web-sys显式开启consolefeature 才能使用控制台相关 API。

第二层:wasm-pack 生成的胶水代码(wasm/pkg/)

wasm-pack build --target web会在wasm/pkg目录产出四类文件:

  • helloworld_demo_bg.wasm:真正的二进制 WASM 模块;
  • helloworld_demo.js:ES Module 形式的 JS 胶水代码,负责加载与实例化 WASM、在 JS 与 WASM 内存之间搬运字符串等;
  • helloworld_demo.d.tshelloworld_demo_bg.wasm.d.ts:TypeScript 类型声明。

从生成的 helloworld_demo.d.ts 可以看到对外暴露的接口与 Rust 侧一一对应:

export function main(): void; export function print(): void; export function print_with_value(value: string): void; export default function __wbg_init(module_or_path?: InitInput | Promise<InitInput>): Promise<InitOutput>;

胶水代码内部做了几件关键的事(见 helloworld_demo.js):

  • 通过new URL('helloworld_demo_bg.wasm', import.meta.url)基于模块自身 URL 定位.wasm文件,这正是 ES Module 方式能正确解析资源的机制;
  • 优先使用WebAssembly.instantiateStreaming流式实例化,服务器未以application/wasmMIME 类型提供文件时自动回退到WebAssembly.instantiate
  • passStringToWasm0负责把 JS 字符串编码为 UTF-8 并拷入 WASM 线性内存,getStringFromWasm0负责反向解码,配合__wbindgen_malloc/__wbindgen_realloc完成内存管理。

第三层:Service Worker 中的导入与调用(background.js)

import init, { print, print_with_value } from './wasm/pkg/helloworld_demo.js'; chrome.runtime.onInstalled.addListener(() => { runDemo(); }); async function runDemo() { // Initialize the WASM module await init(); // Call the exported functions from the WASM module print(); print_with_value('John'); }

调用流程非常清晰:先await init()完成 WASM 实例化(此时#[wasm_bindgen(start)]main已自动运行并打印Inited.),随后调用print()打印Hello World!,再调用print_with_value('John')把字符串传入 WASM,由 Rust 侧以console.log双参数形式打印Hello John。扩展安装事件onInstalled作为演示触发器,确保每次安装扩展都会执行一次演示。

关键配置深入:为什么 Service Worker 必须声明为 ES Module

MV3 的 Service Worker 默认按经典脚本处理,无法使用import/export语法。而 wasm-pack 以--target web生成的胶水代码是标准 ES Module(内部使用了import.meta.urlexport default等模块专有特性),因此必须在 manifest 中通过"background": { "service_worker": "background.js", "type": "module" }把 Service Worker 声明为 ES Module,浏览器才能正确解析import init, { print, print_with_value } from './wasm/pkg/helloworld_demo.js'这条语句。原文档以 diff 形式给出了这一改动的要点:

// manifest.json ... "background": { "service_worker": "background.js", + "type": "module" }, ...

本地构建 WASM 的完整流程

仓库已经预构建好了 WASM 产物(wasm/pkg目录),直接加载即可运行。若想从源码自行构建,按以下步骤操作:

  1. 安装 Rust 工具链(rustup 安装即可)。

  2. 安装 wasm-pack:

    cargo install wasm-pack
  3. 进入wasm目录并构建:

    cd wasm wasm-pack build --target web

注意--target web是关键参数:它决定了胶水代码的形态(ES Module,配合type: "module"使用)。构建产物会输出到wasm/pkg/,重新加载扩展(或点击 Service Worker 控制台的刷新按钮)后即可看到效果。

与 no-modules 方案的对比参考

仓库中还提供了配套的对照示例 functional-samples/cookbook.wasm-helloworld-print-nomodule,其 README 几乎一致,唯一区别是构建命令改为:

wasm-pack build --target no-modules

该方案生成的是传统全局脚本形态的胶水代码,Service Worker 无需声明type: "module"。两个示例共用同一份 Rust 源码思路与同样的wasm-unsafe-evalCSP 配置,正好构成一组"模块化导入 vs 全局脚本"的对照实验:前者展示了本文主题(MV3 下以 ES Module 方式使用 WASM),后者展示了不使用 ES Module 时的替代路径。需要说明的是,--target no-modules生成物依赖全局变量与脚本加载顺序,而--target web的模块化方案与现代前端工程习惯更一致,且能利用import.meta.url精确定位.wasm资源。

常见问题与注意事项

  • 控制台无任何 WASM 日志:优先检查 manifest 是否缺少content_security_policy.extension_pages中的'wasm-unsafe-eval',以及background是否带"type": "module";修改 manifest 后需在扩展管理页重新加载扩展。
  • import语句报语法错误:说明 Service Worker 未被声明为 ES Module,确认"type": "module"已就位。
  • .wasm文件 404--target web生成的胶水代码通过new URL('helloworld_demo_bg.wasm', import.meta.url)定位资源,务必保持.js胶水与.wasm文件同目录(即wasm/pkg内的文件要一起随扩展发布)。
  • 流式实例化警告:若看到WebAssembly.instantiateStreaming failed的警告,说明服务器未按application/wasmMIME 返回文件;扩展打包加载通常不触发该问题,胶水代码也会自动回退到WebAssembly.instantiate,功能不受影响。

小结

通过本示例可以完整掌握 MV3 扩展加载 WASM 的三要素:CSP 中声明'wasm-unsafe-eval'、Service Worker 声明"type": "module"、使用 wasm-pack 的--target web产物。结合 background.js、manifest.json、Rust 源码 与 wasm/pkg 生成物 逐层对照阅读,即可在自有扩展中复刻这套调用链路,将 Rust 编写的计算密集逻辑无缝集成进 Manifest V3 的 Service Worker。

  • 示例工程

【免费下载链接】chrome-extensions-samples

Chrome Extensions Samples

项目地址:https://gitcode.com/gh_mirrors/ch/chrome-extensions-samples
点击查看免费下载
上一篇:Grbl系统设置接口:$参数的读取与修改方法
下一篇:Jackett 10分钟跑通种子聚合搜索:装好、配站、自动追更一次搞定

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询