ONNX Runtime Web 导出功能端到端测试实践:基于 Next.js 与 Vite 的打包器兼容性验证
2026/9/13 21:44:03 网站建设 项目流程

ONNX Runtime Web 导出功能端到端测试实践:基于 Next.js 与 Vite 的打包器兼容性验证

【免费下载链接】onnxruntimeONNX Runtime: cross-platform, high performance ML inferencing and training accelerator项目地址: https://gitcode.com/GitHub_Trending/on/onnxruntime

导读

onnxruntime-web 以纯 JavaScript/WebAssembly 方式在浏览器中运行 ONNX 模型,但其 ESM 打包产物能否被主流前端打包器正确解析、拆分与加载,直接影响开发者的集成体验。本文基于 ONNX Runtime 仓库中js/web/test/e2e/exports目录下的端到端测试工程,系统讲解如何用 Next.js(App Router 与 Turbopack)和 Vite(Vue + JavaScript)两类真实脚手架项目,配合 Puppeteer 自动化验证 onnxruntime-web 的导出功能。读完本文,你将掌握这两套测试用例的工程结构、多线程与 proxy 两种运行时配置的验证方法,以及如何在自己项目中复现同样的集成测试。

测试背景:为什么需要专门验证"导出功能"

onnxruntime-web 的打包产物包含.mjs格式的 ES Module 文件与.wasm二进制资源。不同打包器对 ESM 的静态分析、依赖预构建(pre-bundling)与资源内联策略各不相同,容易出现两类典型问题:

  • 依赖预构建破坏 WebAssembly 加载:如 Vite 在optimizeDeps阶段对使用 WebAssembly 的 npm 包进行预打包时可能出现加载异常(这是 Vite 5.x 时期的已知问题,测试工程中对此有专门处理,详见后文);
  • SSR/CSR 上下文不匹配:Next.js 默认在服务端渲染组件,而 onnxruntime-web 依赖浏览器环境(windowWebAssemblySharedArrayBuffer等),必须采用 CSR 组件或关闭 SSR 的动态导入方式。

因此,js/web/test/e2e/exports/README.md 专门建立一个独立的测试目录,用两个真实脚手架应用(nextjs-default 与 vite-default)来覆盖最常见的 React 与 Vue 生态打包链路。

测试工程总览

整个导出测试位于仓库的 js/web/test/e2e/exports 目录,结构如下:

js/web/test/e2e/exports/ ├── README.md # 测试说明与用例导航 ├── main.js # 测试入口:安装依赖、调度 dev/prod 测试 ├── test.js # 测试执行:启动服务器 + Puppeteer 浏览器自动化 ├── utils.js # 通用工具:shell 命令执行、依赖安装、进程树清理 └── testcases/ ├── nextjs-default.md # Next.js 用例说明 ├── vite-default.md # Vite 用例说明 ├── nextjs-default/ # Next.js 脚手架应用 └── vite-default/ # Vite 脚手架应用

三个核心脚本各司其职,形成一条清晰的测试流水线:

  1. utils.js 中的installOrtPackages()负责在每个测试用例目录下安装依赖(默认执行npm ci,若传入额外包则执行npm install),runShellCmd()以子进程方式运行命令并通过事件机制感知服务器"就绪";
  2. main.js 作为入口,按 nextjs-default → vite-default 的顺序依次调度各用例的 dev 测试、Turbopack 测试、生产构建测试及产物校验;
  3. test.js 中的launchBrowserAndRunTests()使用puppeteer-core驱动本机 Chrome,在页面上模拟用户点击并校验推理结果。

nextjs-default:Next.js 下的 onnxruntime-web 集成验证

脚手架创建方式

根据 nextjs-default.md 的记录,该用例由npx create-next-app@latest交互式生成,创建参数如下(全部保持默认值,除项目名外均选 No):

√ What is your project named? ... nextjs-default √ Would you like to use TypeScript? ... No √ Would you like to use ESLint? ... No √ Would you like to use Tailwind CSS? ... No √ Would you like your code inside a `src/` directory? ... No √ Would you like to use App Router? (recommended) ... No √ Would you like to use Turbopack for `next dev`? ... No √ Would you like to customize the import alias (`@/*` by default)? ... No

从 package.json 可以看到实际依赖为next@^15.5.24react@^19.0.0react-dom@^19.0.0,并保留了默认的dev/build/start三个脚本,同时通过overrides固定了postcss版本,保证脚手架在后续npm ci时可复现安装。虽然向导中选择不使用 App Router,但当前代码实际上落在app/目录结构下(app/layout.jsapp/page.js),这是测试随仓库演进的结果。

基于模板的针对性改造

在脚手架基础上,测试工程做了两类改造(两份用例文档描述一致):

  • 清理:删除默认的 Logo、图片、CSS 与 SVG,避免无关资源干扰测试;
  • 新增测试组件:添加一个纯客户端渲染(CSR)组件,内含:
    • 一个多线程(Multi-thread)复选框#cb-mt
    • 一个代理(Proxy)复选框#cb-px
    • 一个 "Load Model" 按钮#btn-load
    • 一个 "Run Model" 按钮#btn-run
    • 一个状态显示 DIV#ortstate与一个日志显示 DIV#ortlog
  • 新增 helper 模块:负责创建 ORT Session 并执行推理验证。

改造后的 CSR 组件实现在 onnx-test-bar.js 中,其关键点在于:

'use client'; import { useState } from 'react'; export default function OnnxTestBar() { const [ortState, setOrtState] = useState(0); const [ortLog, setOrtLog] = useState('Ready.'); ... const loadModel = async () => { setOrtState(1); ... const { createTestSession } = await import('./onnx-helper'); await createTestSession(document.getElementById('cb-mt').checked, document.getElementById('cb-px').checked); ... };

组件用useState维护一个从 0 到 6 的状态机,loadModelrunTest均通过动态import()按需加载 onnx-helper 模块——这既模拟了真实业务中按需加载 onnxruntime-web 的场景,也验证了打包器对动态导入的正确切分。状态机的含义为:

状态值含义
0初始就绪(两个按钮可用)
1正在加载模型
2模型加载成功(激活 "Run Test" 按钮)
3模型加载失败(日志区显示错误)
4正在运行推理测试
5推理测试通过
6推理测试失败

在 page.js 中,该组件通过next/dynamic并以ssr: false挂载,这是 Next.js 中规避服务端渲染执行浏览器专属代码的标准做法:

'use client'; import dynamic from 'next/dynamic'; const OnnxTestBarComponent = dynamic(() => import('../components/onnx-test-bar'), { ssr: false }); export default function Home() { return ( <div> <main> <OnnxTestBarComponent /> </main> </div> ); }

同时 next.config.mjs 保持默认空配置,意味着这套测试覆盖的是 Next.js无自定义 webpack 配置时的开箱即用导出行为。

onnx-helper:会话创建与推理验证

onnx-helper.js 是验证逻辑的核心,它演示了 onnxruntime-web 的两个关键运行时开关:

import * as ort from 'onnxruntime-web'; export const createTestSession = async (multiThreaded, proxy) => { const model = base64StringToUint8Array(testModelData); const options = {}; if (multiThreaded) { ort.env.wasm.numThreads = 2; assert(typeof SharedArrayBuffer !== 'undefined', 'SharedArrayBuffer is not supported'); } if (proxy) { ort.env.wasm.proxy = true; } mySession = await ort.InferenceSession.create(model, options); };
  • 多线程(ort.env.wasm.numThreads = 2:启用 WASM 多线程需要页面具备SharedArrayBuffer能力,即必须通过 COOP/COEP 响应头开启跨源隔离(cross-origin isolation)。代码在启用前用assert显式检查SharedArrayBuffer是否存在,这正是浏览器端多线程最容易被忽视的前置条件;
  • 代理(ort.env.wasm.proxy = true:将 onnxruntime-web 的 WASM 计算放入独立 Web Worker 中执行,避免阻塞主线程,这是生产环境提升 UI 流畅度的常见配置。

模型数据采用内联 base64 字符串解码为Uint8Array(测试模型为test_abs/model.onnx,对应 Abs 算子),因此无需网络请求即可加载,测试更稳定。推理验证部分构造 60 个正负交替的浮点数(形状[3, 4, 5]),运行模型后逐元素断言输出等于输入的绝对值:

const inputData = [...Array(60).keys()].map((i) => (i % 2 === 0 ? i : -i)); const expectedOutputData = inputData.map((i) => Math.abs(i)); const fetches = await mySession.run({ x: new ort.Tensor('float32', inputData, [3, 4, 5]) }); const y = fetches.y; assert(y instanceof ort.Tensor, 'unexpected result'); assert(y.dims.length === 3 && y.dims[0] === 3 && y.dims[1] === 4 && y.dims[2] === 5, 'incorrect shape'); for (let i = 0; i < expectedOutputData.length; i++) { assert(y.data[i] === expectedOutputData[i], `output data mismatch at index ${i}`); } return 'PASS';

这里同时校验了输出张量的类型、维度([3, 4, 5])与 60 个数值的逐项正确性,属于端到端级别的结果验证。

测试矩阵

根据文档,nextjs-default 用例在三种服务器模式下分别跑 2×2 全组合:

服务器模式组合说明
npm run dev多线程 OFF/ON × 代理 OFF/ON开发服务器
npm run dev -- --turbopack多线程 OFF/ON × 代理 OFF/ON开发服务器(Turbopack)
npm run build+npm run start多线程 OFF/ON × 代理 OFF/ON生产模式

其中npm run dev -- --turbopack专门覆盖 Next.js 的 Rust 版打包器 Turbopack,验证其在开发模式下对 onnxruntime-web 的处理;生产模式则验证next build产物(含资源指纹与代码分割)在next start下可正常运行。

vite-default:Vite + Vue 下的集成验证

脚手架创建方式

vite-default.md 记录该用例由npm create vite@latest生成,选择了 Vue 框架与 JavaScript 变体:

√ Project name: ... vite-default √ Select a framework: » Vue √ Select a variant: » JavaScript

生成后按提示cd vite-default && npm install && npm run dev即可启动。

模板改造与 Vite 关键配置

改造内容与 nextjs-default 一致:删除默认 Logo/图片/CSS/SVG,新增包含两个复选框、两个按钮、状态 DIV 与日志 DIV 的 CSR 组件,并添加同构的 onnx-helper 模块(onnx-helper.js 与 Next.js 版逻辑完全相同,会话创建、多线程/proxy 开关、Abs 模型推理验证均一致)。页面侧的逻辑落在 Vue 的 HelloWorld.vue 中,同样通过动态import('./onnx-helper')按需加载。

Vite 版最重要的差异化配置在 vite.config.js:

import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; export default defineConfig({ // This is a known issue when using WebAssembly with Vite 5.x // Need to specify `optimizeDeps.exclude` to NPM packages that uses WebAssembly // See: https://github.com/vitejs/vite/issues/8427 optimizeDeps: { exclude: ['onnxruntime-web'], }, plugins: [vue()], });

optimizeDeps.exclude: ['onnxruntime-web']是这套用例验证的重点:Vite 默认会对依赖进行预构建(esbuild 预打包),而使用 WebAssembly 的包在预构建阶段存在已知兼容问题,必须将其排除在预构建之外,让 onnxruntime-web 以原生 ESM 方式被浏览器直接加载。该配置项也因此成为"onnxruntime-web + Vite"集成的必备配置。

测试矩阵与产物校验

vite-default 的测试矩阵略少于 Next.js(无 Turbopack 变体):

模式组合端口
npm run dev多线程 OFF/ON × 代理 OFF/ON5173
npm run build+npm run start多线程 OFF/ON × 代理 OFF/ON4173

除此之外,main.js 在 vite-default 的生产构建完成后还执行了一次产物校验(verifyAssets):

await verifyAssets('vite-default', async (cwd) => { const globby = await import('globby'); return { test: 'File "dist/assets/**/ort.*.mjs" should not exist', success: globby.globbySync('dist/assets/**/ort.*.mjs', { cwd }).length === 0, }; });

该断言检查生产构建产物dist/assets/不应残留独立的ort.*.mjs文件——如果 onnxruntime-web 的 ESM 产物未被正确合并进 chunk,就会在dist/assets中出现独立的ort.*.mjs资源,说明打包器对 onnxruntime-web 的导出处理异常。这是对"导出功能"最直接的产物级验证,也是本测试目录命名的由来。

端到端执行原理:Puppeteer 驱动浏览器模拟

test.js 中的launchBrowserAndRunTests()负责整个浏览器自动化流程,其完整链路为:

  1. puppeteer-core启动本机 Chrome(headless: true),并附加--enable-features=SharedArrayBuffer启动参数(保证多线程用例可用,与 onnx-helper 中的 SharedArrayBuffer 检查呼应);
  2. 打开http://localhost:<port>,等待#ortstate可见;
  3. 根据组合勾选#cb-mt(多线程)与#cb-px(代理)复选框;
  4. 点击#btn-load,用waitForFunction等待ortstate进入'2'(成功)或'3'(失败),若失败则从#ortlog读取错误日志;
  5. 点击#btn-run,等待ortstate进入'5'(通过)或'6'(失败),同样读取#ortlog定位问题;
  6. 汇总四条组合的结果,任一失败即整体判定该模式测试失败。

服务器侧的编排在runTest()中:先用runShellCmd以子进程方式启动npm run devnpm run start,通过监听 stdout 中特定"就绪"字符串来感知服务器可用——Next.js 的就绪标志是✓ Ready in,Vite 的就绪标志是终端带颜色的➜ Local:(代码中以转义序列\x1b[32m➜\x1b[39m匹配);收到就绪事件后立即执行浏览器测试,结束后通过tree-kill递归杀掉整个进程树,防止端口残留。生产模式(runProdTest)则先执行npm run build,再对next start/vite preview启动的服务器跑同一套浏览器用例。

本地复现与扩展指南

在具备 Node.js 与 Chrome 环境的前提下,可按以下步骤复现这套导出测试:

  1. 确认测试入口:整体入口为 main.js 的main(PRESERVE, PACKAGES_TO_INSTALL)函数,PRESERVE为真时跳过依赖安装,PACKAGES_TO_INSTALL用于指定额外安装的包(如指向本地构建的 onnxruntime-web tarball);
  2. 安装依赖:首次运行会依次在testcases/nextjs-defaulttestcases/vite-default下执行npm ci(或按传入参数执行npm install <pkg>);由于 onnxruntime-web 依赖外置 npm 包,建议结合构建脚本将仓库内最新构建的包注入PACKAGES_TO_INSTALL,以验证本地改动;
  3. 运行测试:脚本会依次执行——nextjs-default 的 dev 模式(含 Turbopack)与生产模式、vite-default 的 dev 与生产模式,以及 vite-default 的dist产物校验;每轮四组合全部通过才算成功;
  4. 按需扩展:如需覆盖更多打包器或框架(如 Svelte、Astro、Webpack 5 手动配置),可参照现有两个用例的模式——用官方脚手架生成项目 → 添加相同的 CSR 测试组件与 onnx-helper 模块 → 在 main.js 中注册runDevTest/runProdTest(必要时补充verifyAssets产物断言),即可复用同一套 Puppeteer 自动化框架。

小结

onnxruntime-web 的导出功能测试并不是简单的"能否 import",而是通过两个贴近真实工程的最小脚手架,覆盖了三大类集成风险:WebAssembly 依赖的预构建冲突(ViteoptimizeDeps.exclude)、SSR/CSR 环境隔离(Next.jsdynamic+ssr: false)、多线程与代理等运行时开关(SharedArrayBuffernumThreadsproxy)。配合 Puppeteer 的 2×2 组合矩阵与产物级断言,为 onnxruntime-web 的每个发布版本提供了可靠的打包器兼容性保障,也为使用 React/Vue 生态的开发者提供了可直接借鉴的集成样板。

【免费下载链接】onnxruntimeONNX Runtime: cross-platform, high performance ML inferencing and training accelerator项目地址: https://gitcode.com/GitHub_Trending/on/onnxruntime

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

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

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

立即咨询