From e3a3350cdeeaf2d950db034f1f6cfc32e3d5b63c Mon Sep 17 00:00:00 2001 From: joaner Date: Wed, 3 Jun 2026 21:14:48 +0800 Subject: [PATCH] fix: bump @ioai/wasm-zstd to 1.1.2 and document Next.js integration Based on release/1.3.4 (no main plot/chart changes). - Upgrade @ioai/wasm-zstd to ^1.1.2 (static glue import; fixes Turbopack "Failed to resolve module specifier './wasm-zstd-*.js'" in inline workers) - Regenerate package-lock.json from registry.npmjs.org - Add Next.js (App Router / Turbopack) section and troubleshooting to EMBEDDING.md / EMBEDDING.zh.md Bump to 1.3.5. --- docs/EMBEDDING.md | 107 +++++++++++++++++++++++++++++++++++++++++++ docs/EMBEDDING.zh.md | 107 +++++++++++++++++++++++++++++++++++++++++++ package-lock.json | 12 ++--- package.json | 4 +- 4 files changed, 222 insertions(+), 8 deletions(-) diff --git a/docs/EMBEDDING.md b/docs/EMBEDDING.md index 535a229..41c3dbc 100644 --- a/docs/EMBEDDING.md +++ b/docs/EMBEDDING.md @@ -249,6 +249,109 @@ module.exports = { }; ``` +### Next.js (App Router / Turbopack) + +ROSView is a browser-only component (it relies on Web Workers, WASM, `window`, etc.) and **cannot be server-rendered**. Integrating it into Next.js takes three steps: + +**1. Transpile the package in `next.config.js`** + +```js +// next.config.js +const nextConfig = { + transpilePackages: ['@ioai/rosview'], +}; + +module.exports = nextConfig; +``` + +**2. Wrap it in a Client Component loaded with `ssr: false`** + +`ssr: false` is only allowed inside a **Client Component** — you cannot use it with `next/dynamic` directly in a Server Component (e.g. `page.tsx`). So create a `'use client'` wrapper: + +```tsx +// components/RosViewerClient.tsx +'use client'; + +import '@ioai/rosview/style.css'; +import dynamic from 'next/dynamic'; + +// Client-only load to avoid touching browser APIs during SSR +const RosViewer = dynamic( + () => import('@ioai/rosview').then((m) => ({ default: m.RosViewer })), + { ssr: false }, +); + +export function RosViewerClient({ url }: { url: string }) { + return ( +
+ +
+ ); +} +``` + +```tsx +// app/visualize/page.tsx — a Server Component rendering the client wrapper +import { RosViewerClient } from '@/components/RosViewerClient'; + +export default async function VisualizePage({ + searchParams, +}: { + searchParams: Promise<{ url?: string }>; +}) { + const { url } = await searchParams; + return ; +} +``` + +**3. (Optional) Serve files through a Range-capable route** + +To stream local/private mcap/bag recordings, expose a GET route that supports `Accept-Ranges` and point `url` at it: + +```ts +// app/api/recording/route.ts +import fs from 'node:fs'; +import { NextRequest, NextResponse } from 'next/server'; + +export async function GET(req: NextRequest) { + const filePath = '/data/recording.mcap'; // validated, safe path + const { size } = await fs.promises.stat(filePath); + const range = req.headers.get('range'); + + // No Range: return the whole file but still advertise Range support + if (!range) { + const stream = fs.createReadStream(filePath); + return new NextResponse(stream as unknown as ReadableStream, { + status: 200, + headers: { + 'Content-Type': 'application/octet-stream', + 'Content-Length': String(size), + 'Accept-Ranges': 'bytes', + }, + }); + } + + const [startStr, endStr] = range.replace('bytes=', '').split('-'); + const start = Number(startStr); + const end = endStr ? Number(endStr) : size - 1; + const stream = fs.createReadStream(filePath, { start, end }); + + return new NextResponse(stream as unknown as ReadableStream, { + status: 206, + headers: { + 'Content-Type': 'application/octet-stream', + 'Content-Range': `bytes ${start}-${end}/${size}`, + 'Content-Length': String(end - start + 1), + 'Accept-Ranges': 'bytes', + }, + }); +} +``` + +> **About `db3`:** ROSView does **not** support remote streaming of db3 (SQLite needs random access to the whole database). Download the db3 file in full as a `File` on the client and pass it via the `file` / `files` prop. mcap / bag can be streamed directly through the Range route above. + +> **Version requirement:** Use **`@ioai/rosview` ≥ 1.3.5** (which depends on `@ioai/wasm-zstd` ≥ 1.1.2). 1.3.5 fixes the Turbopack inline-worker regression from 1.3.4 (`Failed to resolve module specifier './wasm-zstd-*.js'`, caused by the inline worker running from a `blob:` URL and unable to resolve the zstd glue's relative dynamic import). `@ioai/wasm-zstd` 1.1.2 statically inlines the glue into the worker, fixing this for good. + --- ## Troubleshooting @@ -281,6 +384,10 @@ Access-Control-Allow-Origin: https://your-app.example.com Firefox has stricter CSP enforcement for workers. If you use a `Content-Security-Policy` header, add `worker-src 'self' blob:`. +### "Failed to resolve module specifier './wasm-zstd-*.js'" + +This occurs with `@ioai/rosview` 1.3.4 (paired with `@ioai/wasm-zstd` 1.1.1) under bundlers that run inline workers from `blob:` URLs, such as Next.js Turbopack. Upgrade to **`@ioai/rosview` ≥ 1.3.5** (`@ioai/wasm-zstd` ≥ 1.1.2) — newer versions statically inline the zstd glue into the worker, removing the runtime relative dynamic import. + --- ## TypeScript support diff --git a/docs/EMBEDDING.zh.md b/docs/EMBEDDING.zh.md index cfe1622..c800a21 100644 --- a/docs/EMBEDDING.zh.md +++ b/docs/EMBEDDING.zh.md @@ -249,6 +249,109 @@ module.exports = { }; ``` +### Next.js(App Router / Turbopack) + +ROSView 是纯浏览器端组件(依赖 Web Worker、WASM、`window` 等),**不能在服务端渲染**。在 Next.js 中集成时遵循以下三点即可: + +**1. 在 `next.config.js` 中转译本包** + +```js +// next.config.js +const nextConfig = { + transpilePackages: ['@ioai/rosview'], +}; + +module.exports = nextConfig; +``` + +**2. 用客户端组件包裹,并以 `ssr: false` 动态加载** + +`ssr: false` 只能在**客户端组件**里使用,不能直接在服务端组件(如 `page.tsx`)里对 `next/dynamic` 使用。因此请新建一个 `'use client'` 包裹组件: + +```tsx +// components/RosViewerClient.tsx +'use client'; + +import '@ioai/rosview/style.css'; +import dynamic from 'next/dynamic'; + +// 仅在客户端加载,避免 SSR 阶段触碰浏览器 API +const RosViewer = dynamic( + () => import('@ioai/rosview').then((m) => ({ default: m.RosViewer })), + { ssr: false }, +); + +export function RosViewerClient({ url }: { url: string }) { + return ( +
+ +
+ ); +} +``` + +```tsx +// app/visualize/page.tsx —— 服务端组件直接渲染上面的客户端组件 +import { RosViewerClient } from '@/components/RosViewerClient'; + +export default async function VisualizePage({ + searchParams, +}: { + searchParams: Promise<{ url?: string }>; +}) { + const { url } = await searchParams; + return ; +} +``` + +**3.(可选)提供支持 HTTP Range 的文件接口** + +要流式可视化本地/私有的 mcap/bag,可在 Next.js 中提供一个支持 `Accept-Ranges` 的 GET 路由,把 `url` 指向它即可: + +```ts +// app/api/recording/route.ts +import fs from 'node:fs'; +import { NextRequest, NextResponse } from 'next/server'; + +export async function GET(req: NextRequest) { + const filePath = '/data/recording.mcap'; // 经过校验的安全路径 + const { size } = await fs.promises.stat(filePath); + const range = req.headers.get('range'); + + // 无 Range:返回整文件,但仍声明支持 Range + if (!range) { + const stream = fs.createReadStream(filePath); + return new NextResponse(stream as unknown as ReadableStream, { + status: 200, + headers: { + 'Content-Type': 'application/octet-stream', + 'Content-Length': String(size), + 'Accept-Ranges': 'bytes', + }, + }); + } + + const [startStr, endStr] = range.replace('bytes=', '').split('-'); + const start = Number(startStr); + const end = endStr ? Number(endStr) : size - 1; + const stream = fs.createReadStream(filePath, { start, end }); + + return new NextResponse(stream as unknown as ReadableStream, { + status: 206, + headers: { + 'Content-Type': 'application/octet-stream', + 'Content-Range': `bytes ${start}-${end}/${size}`, + 'Content-Length': String(end - start + 1), + 'Accept-Ranges': 'bytes', + }, + }); +} +``` + +> **关于 `db3`**:ROSView **不支持远程流式** db3(SQLite 需随机读取整库)。请在前端先把 db3 整文件下载为 `File`,再通过 `file` / `files` prop 传入。mcap / bag 则可直接用上面的 Range 接口流式加载。 + +> **版本要求**:请使用 **`@ioai/rosview` ≥ 1.3.5**(依赖 `@ioai/wasm-zstd` ≥ 1.1.2)。1.3.5 修复了 1.3.4 在 Turbopack 下因 inline worker 以 `blob:` URL 运行、无法解析 zstd glue 相对动态 import 而抛出的 `Failed to resolve module specifier './wasm-zstd-*.js'`;`@ioai/wasm-zstd` 1.1.2 将 glue 静态内联进 worker,彻底解决该问题。 + --- ## 故障排查 @@ -281,6 +384,10 @@ Access-Control-Allow-Origin: https://your-app.example.com Firefox 对 Worker 的 CSP 更严格。若使用 `Content-Security-Policy` 响应头,请添加 `worker-src 'self' blob:`。 +### "Failed to resolve module specifier './wasm-zstd-*.js'" + +该错误出现在 `@ioai/rosview` 1.3.4(搭配 `@ioai/wasm-zstd` 1.1.1)在以 `blob:` URL 运行 inline worker 的打包器(如 Next.js Turbopack)下。升级到 **`@ioai/rosview` ≥ 1.3.5**(`@ioai/wasm-zstd` ≥ 1.1.2)即可修复——新版本会将 zstd glue 静态内联进 worker,不再有运行时相对动态 import。 + --- ## TypeScript 支持 diff --git a/package-lock.json b/package-lock.json index 517c360..c6be19a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ioai/rosview", - "version": "1.3.4", + "version": "1.3.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ioai/rosview", - "version": "1.3.4", + "version": "1.3.5", "license": "MIT", "devDependencies": { "@eslint/js": "^9.39.4", @@ -20,7 +20,7 @@ "@foxglove/rosmsg-serialization": "^2.0.4", "@foxglove/rosmsg2-serialization": "^3.0.3", "@ioai/hdf5": "^1.0.0", - "@ioai/wasm-zstd": "^1.1.1", + "@ioai/wasm-zstd": "^1.1.2", "@mcap/browser": "^1.1.0", "@mcap/core": "^2.0.2", "@playwright/test": "^1.59.1", @@ -1321,9 +1321,9 @@ } }, "node_modules/@ioai/wasm-zstd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@ioai/wasm-zstd/-/wasm-zstd-1.1.1.tgz", - "integrity": "sha512-9uWUW3Rp2PDUYzqZ0p4qU9o2uPXHBZYVuWabVdFpX+W2hmfdOWH+nxgl8f7VkFjnonj8nRebaJPd6BouXjaRPw==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ioai/wasm-zstd/-/wasm-zstd-1.1.2.tgz", + "integrity": "sha512-YT+9O3jas5XNuHjUgyPy116oNZQbAcAATas0m6kFpX5lDlLhLHJms9qWJ+398HmUaw+PTuk6H/a2oGefMHHafg==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index 8ed4725..edc464d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ioai/rosview", - "version": "1.3.4", + "version": "1.3.5", "description": "High-performance robotics data visualization for MCAP, ROS bag, ROS2 db3, HDF5 and BVH — embeddable React component and standalone SPA", "keywords": [ "ros", @@ -97,7 +97,7 @@ "@foxglove/rosmsg-serialization": "^2.0.4", "@foxglove/rosmsg2-serialization": "^3.0.3", "@ioai/hdf5": "^1.0.0", - "@ioai/wasm-zstd": "^1.1.1", + "@ioai/wasm-zstd": "^1.1.2", "@mcap/browser": "^1.1.0", "@mcap/core": "^2.0.2", "@playwright/test": "^1.59.1",