- 示例工程
【免费下载链接】chrome-extensions-samples
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 输出
加载方式
- 克隆本仓库(
git clone后进入chrome-extensions-samples目录)。 - 打开 Chrome 的扩展管理页,开启"开发者模式",选择"加载已解压的扩展程序",指向本示例目录
functional-samples/cookbook.wasm-helloworld-print。 - 在扩展列表中定位名为"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 侧的main、print、print_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.ts与helloworld_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.url、export 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目录),直接加载即可运行。若想从源码自行构建,按以下步骤操作:
安装 Rust 工具链(rustup 安装即可)。
安装 wasm-pack:
cargo install wasm-pack进入
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
相关推荐
在 Manifest V3 中加载 WASM:cookbook.wasm-helloworld-print-nomodule 实战指南
在 Manifest V3 中加载 WASM:cookbook.wasm helloworld print nomodule 实战指南 本文以 chrome e
示例工程如何用convformer_s36.sail_in22k_ft_in1k提取图像特征?3种实用方法详解
如何用convformer_s36.sail_in22k_ft_in1k提取图像特征?3种实用方法详解 ConvFormer_s36.sail_in22k_ft
在 webpack 中以 ES Module 方式打包使用 wasm-pack 生成的 Rust WebAssembly 模块
在 webpack 中以 ES Module 方式打包使用 wasm pack 生成的 Rust WebAssembly 模块 导读 本文基于 examples
前端构建开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考