diff --git a/.gitignore b/.gitignore
index 9f5e845..d39427d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,4 +34,9 @@ crates/js_sdk/pkg
# BMAD Method temporary files
.current-feature-branch
-.bmad-temp/
\ No newline at end of file
+.bmad-temp/
+
+
+node_modules/
+
+pnpm-lock.yaml
\ No newline at end of file
diff --git a/crates/node_sdk/README.md b/crates/node_sdk/README.md
index 5f347ba..464b7e2 100644
--- a/crates/node_sdk/README.md
+++ b/crates/node_sdk/README.md
@@ -1,7 +1,39 @@
+
+
# source_map_parser_node
-**高性能 Source Map 解析 & 错误堆栈映射 (WASM)**
-Rust 实现 + wasm-bindgen 导出,面向 Node.js 生产错误还原、调试定位、上下文截取。
+高性能 Source Map 解析 & 错误堆栈映射(Rust + WASM)
+
+`dist/` 目录提供稳定的库模式入口;`pkg/` 保留底层 wasm-bindgen 原始输出。
+
+
+
+> 自 v0.1.x 起:推荐使用 **库模式封装层 (dist)**。仍可通过 `source_map_parser_node/raw` 访问原始绑定。完全 **ESM only**,不再提供 CJS 入口。
+
+## 🚀 TL;DR
+
+```ts
+import smp, {
+ lookup_token,
+ mapErrorStackWithResolver,
+} from 'source_map_parser_node';
+
+await smp.init(); // 幂等,可省略
+
+const token = JSON.parse(lookup_token(sourceMapContent, 1, 0));
+
+const batch = await smp.mapErrorStackWithResolver({
+ errorStack: someStackString,
+ resolveSourceMap: (p) => cache.get(p),
+});
+```
+
+| 层级 | 入口 | 用途 | 特点 |
+| -------- | ---------------------------- | ---------------- | ---------------------------------------- |
+| 高级封装 | `source_map_parser_node` | 直接业务使用 | 有 `init`、辅助包装函数 |
+| 原始绑定 | `source_map_parser_node/raw` | 自己做包装、调试 | wasm-pack 生成;所有函数返回 JSON 字符串 |
+
+---
## ✨ 特性
@@ -21,12 +53,13 @@ npm install source_map_parser_node
> 如果你是从源码构建,请在仓库根执行 `bash scripts/build-wasm-node.sh`,然后 `require('./crates/node_sdk/pkg')`。
-## ⚡ 快速上手
+## ⚡ 快速上手(库模式)
-```js
-const wasm = require('source_map_parser_node');
+```ts
+import smp, { lookup_token } from 'source_map_parser_node';
+
+// 你也可以:import * as raw from 'source_map_parser_node/raw'
-// 示例最小 sourcemap
const sm = JSON.stringify({
version: 3,
sources: ['a.js'],
@@ -35,23 +68,20 @@ const sm = JSON.stringify({
mappings: 'AAAA',
});
-// 所有导出函数都返回 JSON 字符串,需要再 JSON.parse 一次
-const token = JSON.parse(wasm.lookup_token(sm, 1, 0));
+await smp.init(); // 幂等
+const token = JSON.parse(lookup_token(sm, 1, 0));
console.log(token);
```
-### 一个便捷的包装函数
-
-```js
-const W = require('source_map_parser_node');
-const call = (fn, ...args) => JSON.parse(W[fn](...args));
+### 原始层快速包装
-const tok = call('lookup_token', sm, 1, 0);
+```ts
+import * as raw from 'source_map_parser_node/raw';
+const json = raw.lookup_token(sm, 1, 0);
+const tok = JSON.parse(json);
```
-## 🧪 API 速览
-
-所有函数同步返回 JSON 字符串,请自行 `JSON.parse`。
+## 🧪 API 速览(均返回 JSON 字符串)
| 函数 | 作用 | 关键参数 | 返回结构(概念) |
| ------------------------------------------------------------------------ | ------------------------ | ------------------------------ | -------------------------------------------- |
@@ -65,7 +95,7 @@ const tok = call('lookup_token', sm, 1, 0);
| `generate_token_by_single_stack(line,column,sm,contextOffset?)` | 直接行列生成 | 可选上下文偏移 | `Token \| null` |
| `generate_token_by_stack_raw(stackRaw, formatter?, resolver?, onError?)` | 批量任务模式 | 自定义路径改写/内容解析 | `{ stacks, success, fail }` |
-### generate_token_by_stack_raw 说明
+### `generate_token_by_stack_raw` 说明
```ts
generate_token_by_stack_raw(
@@ -137,3 +167,106 @@ MIT
---
欢迎提 Issue / PR 改进 API;更多开发 / 发布流程参见仓库根 `CONTRIBUTORS.md`。
+
+## 🔀 模块与分层策略
+
+| 目录/入口 | 说明 | 适用场景 |
+| ---------------------------- | ------------------------------------------- | ------------------------------------ |
+| `dist/index.es.js` | 库模式(Vite 构建),顶层已完成 wasm 初始化 | 生产业务、通用集成 |
+| `pkg/*.js/wasm` | wasm-pack 原始输出 | 调试、二次封装、对 wasm 行为精准控制 |
+| `source_map_parser_node/raw` | 指向 `pkg/source_map_parser_node.js` | 需要最原始绑定 |
+
+特性:
+
+- 仅 ESM:无需 CJS 分发路径,减少条件分支
+- wasm 静态导入:让现代打包器可执行拓扑分析与缓存
+- 测试使用 alias 指向 dist,保证真实发布路径被验证
+
+### 常见集成模式
+
+| 场景 | 推荐 | 说明 |
+| -------------- | ------ | --------------------- |
+| Web 服务 / SSR | 库模式 | 直接 import 即可 |
+| CLI / 本地工具 | 库模式 | 体积接受、维护简单 |
+| 极限性能实验 | 原始层 | 自行管理缓存/解析策略 |
+
+### 从旧版本迁移
+
+旧:`import * as wasm from 'source_map_parser_node'` (直接就是原始层)
+新:
+
+```diff
+- import * as wasm from 'source_map_parser_node';
++ import smp, * as wasm from 'source_map_parser_node'; // 保持原有 API 同时获得封装
++ await smp.init();
+```
+
+## 🧠 高级封装:`mapErrorStackWithResolver`
+
+```ts
+import smp from 'source_map_parser_node';
+const result = await smp.mapErrorStackWithResolver({
+ errorStack: rawError.stack,
+ resolveSourceMap: (fp) => lru.get(fp),
+ formatter: (fp) => (fp.endsWith('.map') ? fp : fp + '.map'),
+ onError: (line, msg) => console.warn('[SM_FAIL]', line, msg),
+});
+```
+
+返回即为底层 `generate_token_by_stack_raw` 解析结构。
+
+## 🧩 构建 & 测试
+
+本仓库内部:
+
+```bash
+pnpm run build:lib # 构建 dist
+pnpm test # 预设 pretest 钩子可自动构建
+```
+
+Vite / Vitest 需要:
+
+```ts
+import wasm from 'vite-plugin-wasm';
+import topLevelAwait from 'vite-plugin-top-level-await';
+export default defineConfig({
+ plugins: [wasm(), topLevelAwait()],
+});
+```
+
+## 📦 体积与优化建议
+
+- 若生产体积仍偏大,可使用 `wasm-opt -Oz`(需要安装 binaryen)
+- 频繁重复解析同一 sourcemap:上层缓存其字符串;或追加一个 JS 侧 LRU
+- 批量 stack 解析优先使用 `generate_token_by_stack_raw` 减少往返
+
+## 🧪 返回 JSON 的再封装(可选)
+
+在你的代码中可创建一个轻量包装:
+
+```ts
+import { lookup_token as _lookup } from 'source_map_parser_node';
+export const lookupToken = (sm: string, line: number, col: number) =>
+ JSON.parse(_lookup(sm, line, col));
+```
+
+## 🔒 运行时注意事项
+
+- 行号传入:1-based;列:0-based
+- sourcemap 必须符合 v3 标准;异常返回结构含有 `error`
+- Node 需支持 ESM + WebAssembly(Node 16+ 建议 18+)
+
+## 🧩 Vite / Vitest 使用提示
+
+由于 bundler 目标使用了 **WebAssembly ESM 集成提案** 语法,直接在 Vite 中需要插件支持:
+
+```ts
+// vitest.config.ts / vite.config.ts
+import wasm from 'vite-plugin-wasm';
+import topLevelAwait from 'vite-plugin-top-level-await';
+export default defineConfig({
+ plugins: [wasm(), topLevelAwait()],
+});
+```
+
+若你的构建工具不支持上面语法,可改用 `wasm-pack --target nodejs` 或自己写 `fetch + WebAssembly.instantiate` 包装。
diff --git a/crates/node_sdk/package.json b/crates/node_sdk/package.json
new file mode 100644
index 0000000..accf11b
--- /dev/null
+++ b/crates/node_sdk/package.json
@@ -0,0 +1,44 @@
+{
+ "name": "source_map_parser_node",
+ "collaborators": [
+ "MasonChow "
+ ],
+ "description": "A WebAssembly package for source_map_parser",
+ "version": "0.2.1",
+ "license": "MIT",
+ "type": "module",
+ "files": [
+ "dist/",
+ "README.md",
+ "LICENSE"
+ ],
+ "types": "dist/index.d.ts",
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "import": "./dist/index.es.js"
+ },
+ "./wasm": "./pkg/source_map_parser_node_bg.wasm",
+ "./raw": {
+ "types": "./pkg/source_map_parser_node.d.ts",
+ "import": "./pkg/source_map_parser_node.js"
+ }
+ },
+ "scripts": {
+ "build:lib": "vite build",
+ "build": "bash ../../scripts/build-wasm-node.sh && vite build",
+ "pretest": "pnpm run build:lib",
+ "test": "pnpm pretest && vitest --run",
+ "test:coverage": "vitest --coverage",
+ "deploy": "pnpm run build && npm publish"
+ },
+ "devDependencies": {
+ "@vitest/coverage-v8": "^2.0.5",
+ "@vitest/ui": "^2.0.5",
+ "typescript": "^5.5.4",
+ "vite": "^5.4.0",
+ "vite-plugin-top-level-await": "^1.6.0",
+ "vite-plugin-wasm": "^3.5.0",
+ "vitest": "^2.0.5"
+ }
+}
\ No newline at end of file
diff --git a/crates/node_sdk/src/index.ts b/crates/node_sdk/src/index.ts
new file mode 100644
index 0000000..99fb1de
--- /dev/null
+++ b/crates/node_sdk/src/index.ts
@@ -0,0 +1,43 @@
+// 高级入口:对 pkg 目录下 wasm 绑定做一层稳定封装
+// 目标:库模式构建 (Vite) 输出到 dist,并在使用端自动完成 wasm 初始化。
+// 注意:保持对原始 API 的命名导出,不修改 wasm 生成的函数签名。
+
+// 直接引用已经生成的绑定代码。
+// Vite 在构建时会处理对 .wasm 的静态导入(需保留插件或默认支持)。
+import * as lowLevel from '../pkg/source_map_parser_node.js';
+
+// 再导出所有低层 API,保持向后兼容。
+export * from '../pkg/source_map_parser_node.js';
+
+// 提供一个可显式调用的 init(幂等),方便在某些 SSR/自定义加载场景中手动控制。
+let _inited = false;
+export async function init(): Promise {
+ if (_inited) return;
+ // 这里实际上只要执行过绑定文件的顶层代码就已经初始化,
+ // 但为了语义化,仍然提供一个 Promise 接口,未来可在此扩展(例如自定义 wasm fetch)。
+ _inited = true;
+}
+
+// 提供一个辅助方法,对常见用例进行包装示例(非必须,可选增强)。
+export async function mapErrorStackWithResolver(options: {
+ errorStack: string;
+ resolveSourceMap: (filePath: string) => string | undefined | null;
+ formatter?: (filePath: string) => string;
+ onError?: (rawLine: string, message: string) => void;
+}): Promise {
+ await init();
+ const { errorStack, resolveSourceMap, formatter, onError } = options;
+ return lowLevel.generate_token_by_stack_raw(
+ errorStack,
+ formatter ?? null,
+ (p: string) => resolveSourceMap(p) ?? null,
+ onError ?? null
+ );
+}
+
+// 默认导出整体 API(含原始导出与封装方法)。
+export default {
+ init,
+ mapErrorStackWithResolver,
+ ...lowLevel,
+};
diff --git a/crates/node_sdk/tests/test_basic.test.mjs b/crates/node_sdk/tests/test_basic.test.mjs
new file mode 100644
index 0000000..88e3d9b
--- /dev/null
+++ b/crates/node_sdk/tests/test_basic.test.mjs
@@ -0,0 +1,139 @@
+import { describe, it, expect, beforeAll } from 'vitest';
+import * as wasm from 'source_map_parser_node';
+
+// await wasm.init();
+
+beforeAll(async () => {
+ await wasm.init();
+});
+
+// 简单 source map 生成器
+function simpleSM({ codeLines, src = 'src/a.js' }) {
+ const content = codeLines.join('\n') + '\n';
+ return JSON.stringify({
+ version: 3,
+ file: 'min.js',
+ sources: [src],
+ sourcesContent: [content],
+ names: [],
+ mappings: 'AAAA',
+ });
+}
+
+describe('node sdk basic exports', () => {
+ it('lookup_token returns source location', () => {
+ const sm = simpleSM({ codeLines: ['fn()'] });
+ const raw = wasm.lookup_token(sm, 1, 0); // wasm 返回的是 string (JSON)
+ const tok = JSON.parse(raw);
+ expect(tok.line).toBe(0); // 原始源码行 (0-based in rust output)
+ expect(tok.src).toContain('src/a.js');
+ });
+
+ it('lookup_token_with_context returns context token', () => {
+ const sm = simpleSM({ codeLines: ['l0()', 'l1()', 'l2()'] });
+ const raw = wasm.lookup_token_with_context(sm, 1, 0, 1);
+ const tok = JSON.parse(raw);
+ expect(tok.source_code.length).toBeGreaterThanOrEqual(2);
+ const target = tok.source_code.find((lny) => lny.is_stack_line);
+ expect(target).toBeTruthy();
+ });
+
+ it('lookup_context returns snippet', () => {
+ const sm = simpleSM({ codeLines: ['a()', 'b()', 'c()'] });
+ const raw = wasm.lookup_context(sm, 1, 0, 1);
+ const snippet = JSON.parse(raw);
+ expect(snippet.context.length).toBeGreaterThanOrEqual(2);
+ });
+
+ it('map_stack_line maps a single line', () => {
+ const sm = simpleSM({ codeLines: ['fn()'] });
+ const stackLine = 'at foo (https://example.com/min.js:1:0)';
+ const raw = wasm.map_stack_line(sm, stackLine);
+ const tok = JSON.parse(raw);
+ expect(tok.line).toBe(0);
+ });
+
+ it('map_stack_trace maps multiple lines', () => {
+ const sm = simpleSM({ codeLines: ['l0()', 'l1()'] });
+ const trace = [
+ 'at foo (https://example.com/min.js:1:0)',
+ 'at bar (https://example.com/min.js:1:0)',
+ ].join('\n');
+ const raw = wasm.map_stack_trace(sm, trace);
+ const list = JSON.parse(raw);
+ expect(Array.isArray(list)).toBe(true);
+ expect(list.length).toBe(2);
+ });
+
+ it('map_error_stack simple mapping without context', () => {
+ const sm = simpleSM({ codeLines: ['a()'] });
+ const errorStackRaw = [
+ 'ReferenceError: x is not defined',
+ ' at foo (https://example.com/min.js:1:0)',
+ ].join('\n');
+ const raw = wasm.map_error_stack(sm, errorStackRaw, null);
+ const result = JSON.parse(raw);
+ expect(result.error_message).toMatch(/x is not defined/);
+ expect(result.frames.length).toBe(1);
+ });
+
+ it('map_error_stack with context', () => {
+ const sm = simpleSM({ codeLines: ['l0()', 'l1()', 'l2()'] });
+ const errorStackRaw = [
+ 'TypeError: boom',
+ ' at foo (https://example.com/min.js:1:0)',
+ ].join('\n');
+ const raw = wasm.map_error_stack(sm, errorStackRaw, 1);
+ const result = JSON.parse(raw);
+ expect(result.frames_with_context.length).toBe(1);
+ expect(
+ result.frames_with_context[0].source_code.length
+ ).toBeGreaterThanOrEqual(2);
+ });
+});
+
+describe('batch token generation', () => {
+ it('generate_token_by_single_stack returns token', () => {
+ const sm = simpleSM({ codeLines: ['fn()'] });
+ const raw = wasm.generate_token_by_single_stack(1, 0, sm, null);
+ const tok = JSON.parse(raw);
+ expect(tok.line).toBe(0);
+ });
+
+ it('generate_token_by_stack_raw with resolver + formatter', () => {
+ const sm = simpleSM({ codeLines: ['l0()', 'l1()', 'l2()'] });
+ const stackRaw = [
+ 'Error: test',
+ ' at foo (https://example.com/min.js:1:0)',
+ ' at bar (https://example.com/min.js:1:0)',
+ ].join('\n');
+
+ const formatter = (p) => p; // 不做变换
+ const resolver = (p) => sm; // 始终返回同一个 sourcemap
+ const errors = [];
+ const onError = (line, msg) => errors.push({ line, msg });
+
+ const raw = wasm.generate_token_by_stack_raw(
+ stackRaw,
+ formatter,
+ resolver,
+ onError
+ );
+ const result = JSON.parse(raw);
+ expect(result.success.length).toBe(2);
+ expect(result.fail.length).toBe(0);
+ expect(errors.length).toBe(0);
+ });
+
+ it('generate_token_by_stack_raw when no resolver provided collects fails', () => {
+ const sm = simpleSM({ codeLines: ['l0()'] });
+ const stackRaw = [
+ 'Error: test',
+ ' at foo (https://example.com/min.js:1:0)',
+ ].join('\n');
+ const raw = wasm.generate_token_by_stack_raw(stackRaw, null, null, null);
+ const result = JSON.parse(raw);
+ expect(result.success.length).toBe(0);
+ expect(result.fail.length).toBe(1);
+ });
+});
diff --git a/crates/node_sdk/tests/wasm_extended.rs b/crates/node_sdk/tests/wasm_extended.rs
deleted file mode 100644
index 09a931a..0000000
--- a/crates/node_sdk/tests/wasm_extended.rs
+++ /dev/null
@@ -1,74 +0,0 @@
-use source_map_parser_node::{
- generate_token_by_single_stack, generate_token_by_stack_raw, map_error_stack, map_stack_trace,
-};
-use wasm_bindgen_test::*;
-
-// 仅 Node 环境测试 (wasm-pack test --node),不配置浏览器宏
-
-fn sm_one(content: &str) -> String {
- let esc = content.replace('\n', "\\n");
- format!("{{\"version\":3,\"file\":\"min.js\",\"sources\":[\"a.js\"],\"sourcesContent\":[\"{esc}\"],\"names\":[],\"mappings\":\"AAAA\"}}")
-}
-
-#[wasm_bindgen_test]
-fn single_stack_token_ok() {
- let sm = sm_one("fn()\n");
- let v = generate_token_by_single_stack(1, 0, sm, Some(1));
- let s = v.as_string().unwrap();
- assert!(s.contains("source_code"));
-}
-
-#[wasm_bindgen_test]
-fn single_stack_token_none_when_invalid_line() {
- let sm = sm_one("fn()\n");
- let v = generate_token_by_single_stack(0, 0, sm, None);
- let s = v.as_string().unwrap();
- assert_eq!(s, "null");
-}
-
-#[wasm_bindgen_test]
-fn generate_token_by_stack_raw_with_resolver() {
- use js_sys::Function;
- let stack_raw = "Error: x\n at foo (https://example.com/min.js:1:0)";
- let sm = sm_one("fn()\n");
- // formatter: identity
- let formatter = Function::new_no_args("return arguments[0];");
- // resolver: always return the sm
- let resolver = Function::new_with_args("p", &format!("return `{}`;", sm));
- let js = generate_token_by_stack_raw(
- stack_raw.to_string(),
- Some(formatter.clone()),
- Some(resolver.clone()),
- None,
- );
- let s = js.as_string().unwrap();
- assert!(s.contains("success"));
- assert!(s.contains("\"fail\":[]"));
-}
-
-#[wasm_bindgen_test]
-fn map_error_stack_with_context_some() {
- let sm = sm_one("l0()\nl1()\n");
- let err = "Error: boom\n at foo (https://example.com/min.js:1:0)";
- let js = map_error_stack(&sm, err, Some(1));
- let s = js.as_string().unwrap();
- assert!(s.contains("frames_with_context"));
-}
-
-#[wasm_bindgen_test]
-fn map_error_stack_without_context() {
- let sm = sm_one("l0()\nl1()\n");
- let err = "Error: boom\n at foo (https://example.com/min.js:1:0)";
- let js = map_error_stack(&sm, err, None);
- let s = js.as_string().unwrap();
- assert!(s.contains("frames\""));
-}
-
-#[wasm_bindgen_test]
-fn map_stack_trace_multi() {
- let sm = sm_one("l0()\n");
- let trace = "at foo (https://example.com/min.js:1:0)\n@https://example.com/min.js:1:0";
- let js = map_stack_trace(&sm, trace);
- let s = js.as_string().unwrap();
- assert!(s.starts_with("["));
-}
diff --git a/crates/node_sdk/tests/wasm_smoke.rs b/crates/node_sdk/tests/wasm_smoke.rs
deleted file mode 100644
index 498c8a3..0000000
--- a/crates/node_sdk/tests/wasm_smoke.rs
+++ /dev/null
@@ -1,34 +0,0 @@
-use source_map_parser_node::{lookup_token, lookup_token_with_context, map_stack_line};
-use wasm_bindgen_test::*;
-// Node 环境:无需显式配置宏 (browser 专用)
-
-fn sample_sm() -> String {
- // simple one-line mapping
- let sm = r#"{"version":3,"file":"min.js","sources":["a.js"],"sourcesContent":["fn()\n"],"names":[],"mappings":"AAAA"}"#;
- sm.to_string()
-}
-
-#[wasm_bindgen_test]
-fn lookup_basic() {
- let sm = sample_sm();
- let v = lookup_token(&sm, 1, 0);
- let s = v.as_string().unwrap();
- assert!(s.contains("\"line\":0"));
-}
-
-#[wasm_bindgen_test]
-fn lookup_with_context() {
- let sm = sample_sm();
- let v = lookup_token_with_context(&sm, 1, 0, 1);
- let s = v.as_string().unwrap();
- assert!(s.contains("source_code"));
-}
-
-#[wasm_bindgen_test]
-fn map_stack_line_smoke() {
- let sm = sample_sm();
- let line = "at foo (https://example.com/min.js:1:0)";
- let v = map_stack_line(&sm, line);
- let s = v.as_string().unwrap();
- assert!(s.contains("line"));
-}
diff --git a/crates/node_sdk/vite.config.mjs b/crates/node_sdk/vite.config.mjs
new file mode 100644
index 0000000..745d09b
--- /dev/null
+++ b/crates/node_sdk/vite.config.mjs
@@ -0,0 +1,33 @@
+import { defineConfig } from 'vite';
+import wasm from 'vite-plugin-wasm';
+import topLevelAwait from 'vite-plugin-top-level-await';
+import { resolve } from 'path';
+
+// 构建说明:
+// - 入口使用 src/index.ts 封装
+// - 输出 dist/ 下 ESM + CJS
+// - wasm 文件作为静态资产保留(由 wasm 绑定中的静态 import 触发复制)
+// - 不做压缩,保持可读体积(可按需开启 minify)
+
+export default defineConfig({
+ build: {
+ sourcemap: true,
+ lib: {
+ entry: resolve(__dirname, 'src/index.ts'),
+ name: 'SourceMapParserNode',
+ fileName: () => 'index.es.js',
+ formats: ['es']
+ },
+ rollupOptions: {
+ // 目前无需 external;若未来把 wasm 运行时或其他依赖拆分可在此声明
+ external: [],
+ output: {
+ exports: 'named'
+ }
+ },
+ outDir: 'dist',
+ emptyOutDir: true,
+ target: 'es2022'
+ },
+ plugins: [wasm(), topLevelAwait()],
+});
diff --git a/crates/node_sdk/vitest.config.mjs b/crates/node_sdk/vitest.config.mjs
new file mode 100644
index 0000000..8b1fe03
--- /dev/null
+++ b/crates/node_sdk/vitest.config.mjs
@@ -0,0 +1,40 @@
+import wasm from 'vite-plugin-wasm';
+import topLevelAwait from 'vite-plugin-top-level-await';
+import { fileURLToPath } from 'node:url';
+import { dirname, join } from 'node:path';
+import { defineConfig, mergeConfig } from 'vitest/config';
+import viteConfig from './vite.config.mjs';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+
+export default mergeConfig(
+ viteConfig,
+ defineConfig({
+ test: {
+ environment: 'node',
+ globals: true,
+ // 支持 ts/tsx 及 js/jsx 的 spec 或 test 文件
+ include: ['./tests/*.test.{ts,tsx,js,mjs,jsx}'],
+ // 避免扫描构建产物
+ exclude: ['node_modules', 'pkg', 'target', 'dist'],
+ clearMocks: true,
+ coverage: {
+ provider: 'v8',
+ reporter: ['text', 'json', 'html'],
+ exclude: [
+ 'node_modules/',
+ 'target/',
+ '**/vitest.config.{js,ts}',
+ '**/*.d.ts',
+ ],
+ },
+ },
+ resolve: {
+ alias: {
+ // 指向构建后的库模式入口(dist),确保测试覆盖发布产物
+ source_map_parser_node: join(__dirname, './dist/index.es.js'),
+ },
+ },
+ })
+);
diff --git a/scripts/build-wasm-node.sh b/scripts/build-wasm-node.sh
index 927e293..ac2d610 100644
--- a/scripts/build-wasm-node.sh
+++ b/scripts/build-wasm-node.sh
@@ -3,14 +3,17 @@ set -euo pipefail
ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
CRATE_DIR="$ROOT_DIR/crates/node_sdk"
+OUT_DIR="$CRATE_DIR/pkg"
if ! command -v wasm-pack >/dev/null 2>&1; then
- echo "Error: wasm-pack not found. Install via: cargo install wasm-pack or official installer" >&2
+ echo "Error: wasm-pack not found. Install via: cargo install wasm-pack" >&2
exit 1
fi
+rm -rf "$OUT_DIR"
pushd "$CRATE_DIR" >/dev/null
-wasm-pack build --target nodejs --release "$@"
+echo "[build] wasm-pack (bundler target) -> $OUT_DIR"
+wasm-pack build --target bundler --release --out-dir "$OUT_DIR" "$@"
popd >/dev/null
-printf "\nDone. Output at crates/node_sdk/pkg\n"
+echo "\nDone. Output at crates/node_sdk/pkg (pure ESM bundler target)"