ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Next.js 实战:在服务端渲染的 React 组件中集成 WebAssembly(Rust 编译示例)

Next.js 实战:在服务端渲染的 React 组件中集成 WebAssembly(Rust 编译示例) Next.js 实战在服务端渲染的 React 组件中集成 WebAssemblyRust 编译示例【免费下载链接】next.jsThe React Framework项目地址: https://gitcode.com/GitHub_Trending/next/next.js本指南围绕 Next.js 官方示例 examples/with-webassembly 展开讲解如何在 Next.jsApp Router中导入.wasm模块并将其用在被服务端渲染SSR/预渲染的 React 组件中——即让 WebAssembly 代码同时运行在 Node.js 服务端与浏览器端。读完本文你将掌握 Webpack 5 WebAssembly 实验特性的开启方式、.wasm产物路径配置、Rust 源码编译为 wasm 的命令以及 Server Component 与 Edge Route 中调用 wasm 导出函数的完整写法。示例要解决的问题为什么要在服务端跑 wasmWebAssembly 最常见的用法是纯客户端场景浏览器下载.wasm后由 JS 实例化执行。而 with-webassembly 示例演示的是一个更进阶的诉求import WebAssembly files (.wasm) and use them inside of a React component that is server rendered, so the WebAssembly code is executed on the server too.也就是说希望把“导入 wasm 模块”这件事做成普通的模块导入让它既可以出现在客户端组件中也可以出现在服务端渲染阶段。这样一来涉及 wasm 的计算逻辑本示例中是 Rust 编写的add_one函数既能在服务端预渲染时先行执行、输出静态 HTML又能在交互阶段被复用保证了同一份 wasm 业务逻辑在前、后端的行为一致。示例选用 Rust 编译到 wasm函数体只做一次自增运算目的是用最小可运行的代码把“Rust → wasm → Next.js 模块系统”这条链路打通方便读者迁移到自己的真实逻辑。目录结构与运行方式示例位于 examples/with-webassembly关键文件如下文件职责src/add.rsRust 源码导出add_one(x: i32) - i32add.wasm已编译产物已随示例提交可直接运行add.wasm.d.ts为.wasm模块补充的 TypeScript 类型声明next.config.jsWebpack 5 的 wasm 相关配置components/RustComponent.tsx异步 Server Component导入并调用 wasmapp/page.tsx首页读取 URL 参数并把数字传入组件app/api/edge/route.ts在 Edge Runtime 下使用 wasm 的 API 路由一键引导与仓库中其他示例一致可用create-next-app引导出本地项目对应 README 中的 How to use 章节三种包管理器任选其一npx create-next-app --example with-webassembly with-webassembly-appyarn create next-app --example with-webassembly with-webassembly-apppnpm create next-app --example with-webassembly with-webassembly-app引导完成后进入with-webassembly-app目录执行npm run dev或yarn dev/pnpm dev即可访问。首页是一个接收?number查询参数的页面打开http://localhost:3000时默认取number 30app/page.tsx 是一个async页面组件先把searchParams.number解析为整数再交给RustServerComponent number{number} /渲染页面下方通过 Link 输出/?number${number 1}的“1”入口每次点击都会触发一次新的服务端预渲染请求从而用肉眼观察 wasm 在服务端执行的结果如何写入 HTML。由于RustServerComponent是异步 Server Component整个 wasm 调用发生在服务端构建/预渲染阶段打开浏览器“查看网页源代码”即可看到函数返回值的文本——这正是“服务端也执行 wasm”的直接证据。从 Rust 源码编译出 add.wasmREADME 明确指出示例仓库中已经包含了编译好的 wasm 文件因此直接npm run dev无需 Rust 环境只有当你想修改/重编自己的 Rust 代码时才需要安装 Rust 工具链。源码本体src/add.rs 只有短短 4 行#[no_mangle] pub extern C fn add_one(x: i32) - i32 { x 1 }两点说明#[no_mangle]防止 Rust 对符号做名称改写mangling确保导出的函数名在 wasm 里就叫add_one便于 JS 侧按名称解构取出extern C使用 C ABI导出的是(x: i32) - i32的整数函数规避了 wasm 与 JS 之间传递字符串/复杂结构时的内存所有权问题——这也是示例刻意保持“纯整数运算”的原因。编译命令编译动作被封装在 package.json 的scripts中build-rust: rustc --target wasm32-unknown-unknown -O --crate-typecdylib src/add.rs -o add.wasm对应 README 给出的三种执行方式npm run build-rust # 或 yarn build-rust # 或 pnpm build-rust逐一拆解该rustc命令的参数含义参数作用--target wasm32-unknown-unknown指定 wasm 裸机目标。需要 Rust 已安装该 target首次可执行rustup target add wasm32-unknown-unknown-O开启优化控制产物体积--crate-typecdylib以 C 动态库形式产出等价于导出 wasm 符号表src/add.rs输入源码-o add.wasm输出到项目根目录与src/同级的add.wasm依赖 Rust 工具链的步骤都属于“可选重编译”。若只想体验示例本身跳过 Rust 安装即可仓库已内置产物。在 Server Component 中导入并调用 wasm核心运行逻辑集中在 components/RustComponent.tsxexport async function RustServerComponent({ number }: { number: number }) { const exports await import(../add.wasm); const { add_one: addOne } exports; return {addOne(number)}/; }写法上值得注意的细节使用动态await import()而非静态importwasm 模块需要异步实例化而组件本身是async function的 Server Component天然支持在渲染前await模块就绪顶层exportWebpack 对异步 wasm 的处理会把导出函数直接放在模块的具名导出上因此可以const { add_one: addOne } exports解构出 Rust 函数函数即插即用拿到addOne后可直接在 JSX 中调用并渲染其返回值。由于该组件是 Server Component这段代码不会被打进浏览器 bundle 再执行第二次 wasm 实例化——页面 HTML 在服务端就已确定同时示例的组件层级允许同样的模式被复制到use client客户端组件中从而在交互阶段复用同一份 wasm。TypeScript 对.wasm的识别直接import ../add.wasm在 TS 下会因“未知模块类型”报错示例通过两步解决add.wasm.d.ts 声明模块形状export function add_one(number: Number): Number;tsconfig.json 的include数组中显式加入wasm.d.ts注意示例实际文件名是add.wasm.d.ts与声明的通配语义一致确保类型文件被 TS 编译器收录include: [next-env.d.ts, **/*.ts, **/*.tsx, wasm.d.ts]这样编辑器与next build阶段都能获得add_one的签名提示与类型校验。支撑这一切的 next.config.jsWebpack 5 的 wasm 配置浏览器原生支持 wasm 并不代表模块打包器默认会处理.wasm导入。Webpack 5 默认不启用WebAssembly需要在 next.config.js 中手动开启并解决服务端/客户端的产物路径差异/** type {import(next).NextConfig} */ const nextConfig { webpack(config, { isServer, dev }) { // Use the client static directory in the server bundle and prod mode // Fixes Error occurred prerendering page / config.output.webassemblyModuleFilename isServer !dev ? ../static/wasm/[modulehash].wasm : static/wasm/[modulehash].wasm; // Since Webpack 5 doesnt enable WebAssembly by default, we should do it manually config.experiments { ...config.experiments, asyncWebAssembly: true }; return config; }, }; module.exports nextConfig;两块配置各有其必要原因1.experiments.asyncWebAssembly: trueWebpack 5 以“实验特性”形式提供两类 wasm 支持syncWebAssembly与asyncWebAssembly。由于同步实例化 wasm 与 Webpack 的模块模型存在冲突官方建议使用异步版本。开启asyncWebAssembly: true后.wasm文件就能像普通模块那样被import()并配合顶层export暴露导出函数。注释里 “Webpack 5 doesnt enable WebAssembly by default” 正是该选项存在的原因。2.output.webassemblyModuleFilename解决预渲染报错的关键wasm 需要作为独立资源输出并由运行时异步加载。服务端Node与客户端对资源定位方式不同因此按isServer !dev分支分别设置文件名模板服务端且生产模式../static/wasm/[modulehash].wasm——相对服务端 chunk 输出目录向上回退一层落到静态资源目录以保证构建产物与预渲染prerender阶段能正确取到文件其余场景客户端或 devstatic/wasm/[modulehash].wasm——直接指向站点的静态资源路径。next.config.js 的注释点明了这么做要修复的问题Error occurred prerendering page /。也就是说如果不区分服务端与客户端路径next build在做静态预渲染本示例首页默认无动态参数会被预渲染为静态 HTML时就会因找不到 wasm 资源而报错[modulehash]则保证不同内容的 wasm 各自拥有独立缓存键。扩展在 Edge Runtime 的 Route Handler 中实例化 wasm示例还额外演示了 wasm 在Edge Runtime下的用法app/api/edge/route.tsimport type * as addWasmModule from ../../../add.wasm; // ts-ignore import addWasm from ../../../add.wasm?module; const module$ WebAssembly.instantiate(addWasm); export async function GET() { const instance (await module$) as any; const exports instance.exports as typeof addWasmModule; const { add_one: addOne } exports; const number addOne(10); return new Response(got: ${number}); } export const runtime edge;与 Server Component 相比这段代码展示了另一条更偏底层的路线import addWasm from ../../../add.wasm?module中的?module是Webpack 资源查询参数它让导入得到的addWasm是一个WebAssembly.Module对象而非自动实例化后的导出对象模块顶层用WebAssembly.instantiate(addWasm)手动实例化并把 Promise 提升为模块级单例module$避免每次请求重复编译实例化GET()内await module$后从instance.exports中取出 Rust 导出的add_one调用并返回文本响应got: 11文件末尾的export const runtime edge声明该路由运行在 Edge Runtime验证了 wasm 也能在边缘环境中被WebAssembly.instantiate正常使用类型层面通过import type * as addWasmModule from ../../../add.wasm复用同一份 add.wasm.d.ts 声明为instance.exports提供形状约束示例代码中为此加了// ts-ignore与as any属于对实验性 API 的权宜处理。访问http://localhost:3000/api/edge即可看到该 Route Handler 返回的got: 11它是访问时在 Edge Runtime 中真正执行 Rust 代码得到的结果。小结与改造建议with-webassembly 用不到百行的体量完整演示了 Next.js 中 WebAssembly 的两条落地路径Server Component 路径await import()自动实例化 experiments.asyncWebAssembly把 wasm 当普通模块使用代码最简洁适合计算逻辑在服务端预渲染与客户端复用共存的场景Edge Route 路径?module查询参数取回WebAssembly.Module用WebAssembly.instantiate手动管理实例生命周期适合希望复用单例、精确控制实例化时机的场景。如果要迁移到自己的项目请核对以下四件事开启 next.config.js 中的experiments.asyncWebAssembly并按isServer !dev分支设置webassemblyModuleFilename否则生产构建的预渲染阶段会报错为每个.wasm提供对应的.d.ts声明并确保被 tsconfig.json 的include覆盖编译 Rust 时使用wasm32-unknown-unknowntarget --crate-typecdylib并保持导出函数为简单的数值签名需要更复杂的数据交换时需另行引入内存拷贝与编码逻辑计算密集型函数建议用-O优化编译并评估在服务端/边缘执行与纯客户端执行两种方案的性能与包体取舍。【免费下载链接】next.jsThe React Framework项目地址: https://gitcode.com/GitHub_Trending/next/next.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表