@spatialwalk/avatarkit 1.0.0-beta.10 → 1.0.0-beta.101

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/CHANGELOG.md +771 -4
  2. package/README.md +676 -365
  3. package/dist/StreamingAudioPlayer-BULgPjpe.js +643 -0
  4. package/dist/avatar_core_wasm-CQbUl6zN.js +2696 -0
  5. package/dist/avatar_core_wasm-bd762669.wasm +0 -0
  6. package/dist/core/Avatar.d.ts +5 -7
  7. package/dist/core/AvatarController.d.ts +99 -60
  8. package/dist/core/AvatarManager.d.ts +32 -12
  9. package/dist/core/AvatarSDK.d.ts +58 -0
  10. package/dist/core/AvatarView.d.ts +136 -128
  11. package/dist/index-C0A1HA8M.js +18427 -0
  12. package/dist/index.d.ts +2 -4
  13. package/dist/index.js +17 -17
  14. package/dist/next.d.ts +2 -0
  15. package/dist/performance/FrameRateMonitor.d.ts +85 -0
  16. package/dist/types/character-settings.d.ts +7 -1
  17. package/dist/types/character.d.ts +42 -16
  18. package/dist/types/index.d.ts +165 -45
  19. package/dist/vite.d.ts +19 -0
  20. package/next.d.ts +3 -0
  21. package/next.js +187 -0
  22. package/package.json +38 -8
  23. package/vite.d.ts +20 -0
  24. package/vite.js +126 -0
  25. package/dist/StreamingAudioPlayer-Bq2-bQiT.js +0 -319
  26. package/dist/StreamingAudioPlayer-Bq2-bQiT.js.map +0 -1
  27. package/dist/animation/AnimationWebSocketClient.d.ts +0 -50
  28. package/dist/animation/AnimationWebSocketClient.d.ts.map +0 -1
  29. package/dist/animation/utils/eventEmitter.d.ts +0 -13
  30. package/dist/animation/utils/eventEmitter.d.ts.map +0 -1
  31. package/dist/animation/utils/flameConverter.d.ts +0 -26
  32. package/dist/animation/utils/flameConverter.d.ts.map +0 -1
  33. package/dist/audio/AnimationPlayer.d.ts +0 -57
  34. package/dist/audio/AnimationPlayer.d.ts.map +0 -1
  35. package/dist/audio/StreamingAudioPlayer.d.ts +0 -123
  36. package/dist/audio/StreamingAudioPlayer.d.ts.map +0 -1
  37. package/dist/avatar_core_wasm-D4eEi7Eh.js +0 -1666
  38. package/dist/avatar_core_wasm-D4eEi7Eh.js.map +0 -1
  39. package/dist/avatar_core_wasm.wasm +0 -0
  40. package/dist/config/app-config.d.ts +0 -44
  41. package/dist/config/app-config.d.ts.map +0 -1
  42. package/dist/config/constants.d.ts +0 -29
  43. package/dist/config/constants.d.ts.map +0 -1
  44. package/dist/config/sdk-config-loader.d.ts +0 -12
  45. package/dist/config/sdk-config-loader.d.ts.map +0 -1
  46. package/dist/core/Avatar.d.ts.map +0 -1
  47. package/dist/core/AvatarController.d.ts.map +0 -1
  48. package/dist/core/AvatarDownloader.d.ts +0 -95
  49. package/dist/core/AvatarDownloader.d.ts.map +0 -1
  50. package/dist/core/AvatarKit.d.ts +0 -48
  51. package/dist/core/AvatarKit.d.ts.map +0 -1
  52. package/dist/core/AvatarManager.d.ts.map +0 -1
  53. package/dist/core/AvatarView.d.ts.map +0 -1
  54. package/dist/core/NetworkLayer.d.ts +0 -59
  55. package/dist/core/NetworkLayer.d.ts.map +0 -1
  56. package/dist/generated/driveningress/v1/driveningress.d.ts +0 -80
  57. package/dist/generated/driveningress/v1/driveningress.d.ts.map +0 -1
  58. package/dist/generated/driveningress/v2/driveningress.d.ts +0 -81
  59. package/dist/generated/driveningress/v2/driveningress.d.ts.map +0 -1
  60. package/dist/generated/google/protobuf/struct.d.ts +0 -108
  61. package/dist/generated/google/protobuf/struct.d.ts.map +0 -1
  62. package/dist/generated/google/protobuf/timestamp.d.ts +0 -129
  63. package/dist/generated/google/protobuf/timestamp.d.ts.map +0 -1
  64. package/dist/index-bQnEVIkT.js +0 -5999
  65. package/dist/index-bQnEVIkT.js.map +0 -1
  66. package/dist/index.d.ts.map +0 -1
  67. package/dist/index.js.map +0 -1
  68. package/dist/renderer/RenderSystem.d.ts +0 -79
  69. package/dist/renderer/RenderSystem.d.ts.map +0 -1
  70. package/dist/renderer/covariance.d.ts +0 -13
  71. package/dist/renderer/covariance.d.ts.map +0 -1
  72. package/dist/renderer/renderer.d.ts +0 -8
  73. package/dist/renderer/renderer.d.ts.map +0 -1
  74. package/dist/renderer/sortSplats.d.ts +0 -12
  75. package/dist/renderer/sortSplats.d.ts.map +0 -1
  76. package/dist/renderer/webgl/reorderData.d.ts +0 -14
  77. package/dist/renderer/webgl/reorderData.d.ts.map +0 -1
  78. package/dist/renderer/webgl/webglRenderer.d.ts +0 -66
  79. package/dist/renderer/webgl/webglRenderer.d.ts.map +0 -1
  80. package/dist/renderer/webgpu/webgpuRenderer.d.ts +0 -54
  81. package/dist/renderer/webgpu/webgpuRenderer.d.ts.map +0 -1
  82. package/dist/types/character-settings.d.ts.map +0 -1
  83. package/dist/types/character.d.ts.map +0 -1
  84. package/dist/types/index.d.ts.map +0 -1
  85. package/dist/utils/animation-interpolation.d.ts +0 -17
  86. package/dist/utils/animation-interpolation.d.ts.map +0 -1
  87. package/dist/utils/cls-tracker.d.ts +0 -17
  88. package/dist/utils/cls-tracker.d.ts.map +0 -1
  89. package/dist/utils/error-utils.d.ts +0 -27
  90. package/dist/utils/error-utils.d.ts.map +0 -1
  91. package/dist/utils/logger.d.ts +0 -35
  92. package/dist/utils/logger.d.ts.map +0 -1
  93. package/dist/utils/reqId.d.ts +0 -20
  94. package/dist/utils/reqId.d.ts.map +0 -1
  95. package/dist/wasm/avatarCoreAdapter.d.ts +0 -188
  96. package/dist/wasm/avatarCoreAdapter.d.ts.map +0 -1
  97. package/dist/wasm/avatarCoreMemory.d.ts +0 -141
  98. package/dist/wasm/avatarCoreMemory.d.ts.map +0 -1
package/next.js ADDED
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Next.js plugin for @spatialwalk/avatarkit
3
+ * Handles WASM file configuration for both webpack and Turbopack (Next.js 15+/16+)
4
+ *
5
+ * Usage:
6
+ * import { withAvatarkit } from '@spatialwalk/avatarkit/next'
7
+ * export default withAvatarkit({ ...your next config... })
8
+ */
9
+ import { fileURLToPath } from 'url';
10
+ import { dirname, join } from 'path';
11
+ import { writeFileSync, readFileSync, readdirSync, mkdirSync, copyFileSync, existsSync, } from 'fs';
12
+ // ── Locate SDK directory ──────────────────────────────────────────────
13
+ // Walk up from this file (inside the SDK package) to find the package root.
14
+ // This is reliable regardless of process.cwd(), monorepos, or hoisted deps.
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = dirname(__filename);
17
+ // Since this file IS inside the SDK package (at package root), __dirname is the SDK dir.
18
+ const sdkDir = __dirname;
19
+ const sdkDistDir = join(sdkDir, 'dist');
20
+ // ── Embedded webpack loader ───────────────────────────────────────────
21
+ // Fixes Emscripten's scriptDirectory: bundlers replace import.meta.url with
22
+ // a build-time path that browsers can't use to locate WASM files.
23
+ // This loader replaces it with the correct public path.
24
+ function createLoaderCode(wasmPublicPath) {
25
+ return `module.exports = function(source) {
26
+ // Strategy 1: Replace assignment only (simpler, more robust)
27
+ var pattern1 = /scriptDirectory\\s*=\\s*new\\s+URL\\(\\s*"\\."\\s*,\\s*_scriptName\\s*\\)\\.href\\s*;/;
28
+ var result = source.replace(pattern1, 'scriptDirectory = "${wasmPublicPath}";');
29
+ if (result !== source) return result;
30
+
31
+ // Strategy 2: Full try/catch block (handles different catch syntax)
32
+ var pattern2 = /try\\s*\\{\\s*scriptDirectory\\s*=\\s*new\\s+URL\\(\\s*"\\."\\s*,\\s*_scriptName\\s*\\)\\.href\\s*;?\\s*\\}\\s*catch\\s*(\\([^)]*\\))?\\s*\\{\\s*\\}/;
33
+ result = source.replace(pattern2, 'scriptDirectory = "${wasmPublicPath}";');
34
+ if (result !== source) return result;
35
+
36
+ console.warn('[avatarkit] WARNING: scriptDirectory pattern not matched in', this.resourcePath);
37
+ return source;
38
+ }
39
+ `;
40
+ }
41
+ function ensureLoader(wasmPublicPath) {
42
+ const cacheDir = join(sdkDir, '.cache');
43
+ const loaderPath = join(cacheDir, 'wasm-script-dir-loader.cjs');
44
+ mkdirSync(cacheDir, { recursive: true });
45
+ writeFileSync(loaderPath, createLoaderCode(wasmPublicPath));
46
+ return loaderPath;
47
+ }
48
+ // ── Copy WASM files to public directory ───────────────────────────────
49
+ // Works for both webpack and Turbopack: public/ files are served by Next.js
50
+ function copyWasmToPublic(projectDir) {
51
+ const publicWasmDir = join(projectDir, 'public', '_avatarkit');
52
+ mkdirSync(publicWasmDir, { recursive: true });
53
+ try {
54
+ const files = readdirSync(sdkDistDir);
55
+ for (const file of files) {
56
+ if (file.startsWith('avatar_core_wasm') && file.endsWith('.wasm')) {
57
+ const src = join(sdkDistDir, file);
58
+ const dest = join(publicWasmDir, file);
59
+ copyFileSync(src, dest);
60
+ }
61
+ }
62
+ }
63
+ catch (err) {
64
+ console.warn('[avatarkit] Failed to copy WASM files:', err.message);
65
+ console.warn('[avatarkit] SDK dist dir:', sdkDistDir);
66
+ }
67
+ }
68
+ // ── Webpack plugin: copy WASM to build output ─────────────────────────
69
+ // Emits WASM files into .next/static/chunks/ during webpack compilation.
70
+ // This is needed in addition to public/ for standalone Docker deployments
71
+ // where .next/static/ is copied but public/ may not be.
72
+ class CopyWasmPlugin {
73
+ apply(compiler) {
74
+ compiler.hooks.thisCompilation.tap('AvatarkitCopyWasm', (compilation) => {
75
+ compilation.hooks.processAssets.tap({
76
+ name: 'AvatarkitCopyWasm',
77
+ stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
78
+ }, () => {
79
+ try {
80
+ const files = readdirSync(sdkDistDir);
81
+ for (const file of files) {
82
+ if (file.startsWith('avatar_core_wasm') && file.endsWith('.wasm')) {
83
+ const content = readFileSync(join(sdkDistDir, file));
84
+ compilation.emitAsset(`static/chunks/${file}`, new compiler.webpack.sources.RawSource(content));
85
+ }
86
+ }
87
+ }
88
+ catch (err) {
89
+ console.warn('[avatarkit] CopyWasmPlugin error:', err.message);
90
+ }
91
+ });
92
+ });
93
+ }
94
+ }
95
+ // ── Detect project root from next.config location ─────────────────────
96
+ // next.config.mjs is always at the project root. Since this module is imported
97
+ // from next.config.mjs, we find the project root by walking up from import.meta.url
98
+ // of the calling module. However, we can't access the caller's import.meta.url,
99
+ // so we search for the nearest next.config.* file from the SDK location upwards.
100
+ function findProjectRoot() {
101
+ let dir = process.cwd(); // fallback, but also check for next.config
102
+ const configNames = [
103
+ 'next.config.mjs',
104
+ 'next.config.js',
105
+ 'next.config.ts',
106
+ 'next.config.cjs',
107
+ ];
108
+ // Verify cwd has a next.config file
109
+ for (const name of configNames) {
110
+ if (existsSync(join(dir, name))) {
111
+ return dir;
112
+ }
113
+ }
114
+ // Fallback: search from SDK location upward
115
+ let searchDir = sdkDir;
116
+ while (searchDir !== dirname(searchDir)) {
117
+ for (const name of configNames) {
118
+ if (existsSync(join(searchDir, name))) {
119
+ return searchDir;
120
+ }
121
+ }
122
+ searchDir = dirname(searchDir);
123
+ }
124
+ return process.cwd();
125
+ }
126
+ // ── Main plugin ───────────────────────────────────────────────────────
127
+ export function withAvatarkit(nextConfig = {}) {
128
+ const basePath = nextConfig.basePath || '';
129
+ const wasmPublicPath = `${basePath}/_avatarkit/`;
130
+ const loaderPath = ensureLoader(wasmPublicPath);
131
+ const projectRoot = findProjectRoot();
132
+ // Copy WASM files to public/_avatarkit/ (works for both webpack & Turbopack)
133
+ copyWasmToPublic(projectRoot);
134
+ return {
135
+ ...nextConfig,
136
+ // ── Turbopack configuration (Next.js 15+/16+) ──
137
+ turbopack: {
138
+ ...nextConfig.turbopack,
139
+ rules: {
140
+ ...nextConfig.turbopack?.rules,
141
+ '**/avatar_core_wasm*.js': {
142
+ loaders: [loaderPath],
143
+ as: '*.js',
144
+ },
145
+ },
146
+ },
147
+ // ── Webpack configuration ──
148
+ webpack: (config, context) => {
149
+ // 1. Fix: Next.js sets module.generator.asset.filename which breaks asset/inline
150
+ if (config.module.generator?.asset?.filename) {
151
+ const filename = config.module.generator.asset.filename;
152
+ delete config.module.generator.asset.filename;
153
+ config.module.generator['asset/resource'] = {
154
+ ...config.module.generator['asset/resource'],
155
+ filename,
156
+ };
157
+ }
158
+ // 2. Fix: Emscripten scriptDirectory derived from import.meta.url
159
+ config.module.rules.push({
160
+ test: /avatar_core_wasm.*\.js$/,
161
+ enforce: 'pre',
162
+ use: [{ loader: loaderPath }],
163
+ });
164
+ // 3. Copy WASM files to static/chunks/ (client build only, for standalone)
165
+ if (!context.isServer) {
166
+ config.plugins.push(new CopyWasmPlugin());
167
+ }
168
+ // Chain with user's webpack config
169
+ if (typeof nextConfig.webpack === 'function') {
170
+ return nextConfig.webpack(config, context);
171
+ }
172
+ return config;
173
+ },
174
+ // ── Headers: ensure WASM content-type ──
175
+ async headers() {
176
+ const userHeaders = typeof nextConfig.headers === 'function' ? await nextConfig.headers() : [];
177
+ return [
178
+ ...userHeaders,
179
+ {
180
+ source: '/_avatarkit/:path*.wasm',
181
+ headers: [{ key: 'Content-Type', value: 'application/wasm' }],
182
+ },
183
+ ];
184
+ },
185
+ };
186
+ }
187
+ export default withAvatarkit;
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@spatialwalk/avatarkit",
3
3
  "type": "module",
4
- "version": "1.0.0-beta.10",
4
+ "version": "1.0.0-beta.101",
5
5
  "packageManager": "pnpm@10.18.2",
6
- "description": "SPAvatar SDK - 3D Gaussian Splatting Avatar Rendering SDK",
7
- "author": "SPAvatar Team",
6
+ "description": "AvatarKit SDK - 3D Gaussian Splatting Avatar Rendering SDK",
7
+ "author": "AvatarKit Team",
8
8
  "license": "MIT",
9
9
  "keywords": [
10
10
  "avatar",
@@ -19,6 +19,14 @@
19
19
  "types": "./dist/index.d.ts",
20
20
  "import": "./dist/index.js"
21
21
  },
22
+ "./vite": {
23
+ "types": "./vite.d.ts",
24
+ "import": "./vite.js"
25
+ },
26
+ "./next": {
27
+ "types": "./next.d.ts",
28
+ "import": "./next.js"
29
+ },
22
30
  "./*": {
23
31
  "types": "./dist/*.d.ts",
24
32
  "import": "./dist/*.js"
@@ -30,28 +38,50 @@
30
38
  "files": [
31
39
  "README.md",
32
40
  "CHANGELOG.md",
33
- "dist"
41
+ "dist",
42
+ "vite.js",
43
+ "vite.d.ts",
44
+ "next.js",
45
+ "next.d.ts"
34
46
  ],
35
47
  "scripts": {
36
- "build": "SDK_BUILD=true vite build --mode library",
48
+ "build": "SDK_BUILD=true vite build --mode library && npm run build:vite-plugin && npm run build:next-plugin",
49
+ "build:vite-plugin": "tsc vite.ts --outDir . --module esnext --target es2020 --moduleResolution bundler --esModuleInterop --skipLibCheck --declaration --declarationMap",
50
+ "build:next-plugin": "tsc next.ts --outDir . --module esnext --target es2020 --moduleResolution bundler --esModuleInterop --skipLibCheck --declaration --declarationMap",
37
51
  "dev": "vite build --mode library --watch",
52
+ "demo": "vite --config demo/vite.config.mjs",
53
+ "demo:benchmark": "vite --config benchmark-demo/vite.config.mjs",
54
+ "test:integration": "vite --config tests/integration-runner/vite.config.mjs",
38
55
  "clean": "rm -rf dist",
39
56
  "typecheck": "tsc --noEmit",
40
57
  "test": "cd tests && pnpm test",
41
58
  "test:watch": "cd tests && pnpm run test:watch",
42
- "test:e2e": "cd tests && pnpm run test:e2e"
59
+ "test:e2e": "cd tests && pnpm run test:e2e",
60
+ "test:perf": "cd tests && pnpm run test:perf"
43
61
  },
44
62
  "peerDependencies": {
45
- "@webgpu/types": "*"
63
+ "@webgpu/types": "*",
64
+ "next": ">=13.0.0",
65
+ "vite": "^5.0.0"
66
+ },
67
+ "peerDependenciesMeta": {
68
+ "vite": {
69
+ "optional": true
70
+ },
71
+ "next": {
72
+ "optional": true
73
+ }
46
74
  },
47
75
  "dependencies": {
48
76
  "@bufbuild/protobuf": "^2.10.0",
49
77
  "@guiiai/logg": "^1.2.4",
50
- "nanoid": "^5.1.6"
78
+ "nanoid": "^5.1.6",
79
+ "posthog-js": "^1.310.1"
51
80
  },
52
81
  "devDependencies": {
53
82
  "@types/node": "^20.11.30",
54
83
  "@webgpu/types": "^0.1.65",
84
+ "tsx": "^4.20.6",
55
85
  "typescript": "^5.0.0",
56
86
  "vite": "^5.0.0",
57
87
  "vite-plugin-dts": "^4.5.4"
package/vite.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ import type { Plugin } from 'vite';
2
+ /**
3
+ * Vite plugin for @spatialwalk/avatarkit
4
+ * Automatically handles WASM file configuration for development and production builds
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * import { defineConfig } from 'vite'
9
+ * import { avatarkitVitePlugin } from '@spatialwalk/avatarkit/vite'
10
+ *
11
+ * export default defineConfig({
12
+ * plugins: [
13
+ * avatarkitVitePlugin()
14
+ * ]
15
+ * })
16
+ * ```
17
+ */
18
+ export declare function avatarkitVitePlugin(): Plugin;
19
+ export default avatarkitVitePlugin;
20
+ //# sourceMappingURL=vite.d.ts.map
package/vite.js ADDED
@@ -0,0 +1,126 @@
1
+ import { copyFileSync, existsSync, writeFileSync, readdirSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ /**
4
+ * Vite plugin for @spatialwalk/avatarkit
5
+ * Automatically handles WASM file configuration for development and production builds
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { defineConfig } from 'vite'
10
+ * import { avatarkitVitePlugin } from '@spatialwalk/avatarkit/vite'
11
+ *
12
+ * export default defineConfig({
13
+ * plugins: [
14
+ * avatarkitVitePlugin()
15
+ * ]
16
+ * })
17
+ * ```
18
+ */
19
+ export function avatarkitVitePlugin() {
20
+ let rootDir;
21
+ return {
22
+ name: 'avatarkit-wasm',
23
+ // 保存项目根目录
24
+ configResolved(config) {
25
+ rootDir = config.root;
26
+ },
27
+ // 开发服务器 MIME 类型配置
28
+ configureServer(server) {
29
+ server.middlewares.use((req, res, next) => {
30
+ if (req.url?.endsWith('.wasm')) {
31
+ res.setHeader('Content-Type', 'application/wasm');
32
+ }
33
+ next();
34
+ });
35
+ },
36
+ // 构建时自动复制 WASM 文件和生成 headers
37
+ closeBundle() {
38
+ if (!rootDir)
39
+ return;
40
+ const wasmSourceDir = join(rootDir, 'node_modules/@spatialwalk/avatarkit/dist');
41
+ // 先查找并读取 JS glue 文件,提取它引用的 WASM 文件名
42
+ let wasmFileName = null;
43
+ if (existsSync(wasmSourceDir)) {
44
+ const files = readdirSync(wasmSourceDir);
45
+ const wasmJsFile = files.find((f) => f.startsWith('avatar_core_wasm') && f.endsWith('.js'));
46
+ if (wasmJsFile) {
47
+ const wasmJsSource = join(wasmSourceDir, wasmJsFile);
48
+ try {
49
+ const jsContent = readFileSync(wasmJsSource, 'utf-8');
50
+ // 从 JS 文件中提取 WASM 文件名
51
+ // 匹配 avatar_core_wasm-{hash}.wasm 或 avatar_core_wasm.wasm
52
+ // 使用更精确的正则,匹配带引号或不带引号的情况
53
+ const wasmMatch = jsContent.match(/["'`]?avatar_core_wasm[-\w]*\.wasm["'`]?/g);
54
+ if (wasmMatch && wasmMatch.length > 0) {
55
+ // 取第一个匹配,去掉引号
56
+ wasmFileName = wasmMatch[0].replace(/["'`]/g, '');
57
+ }
58
+ }
59
+ catch (error) {
60
+ console.warn('⚠️ [avatarkit] Could not read JS glue file:', error);
61
+ }
62
+ }
63
+ }
64
+ // 如果从 JS 文件中找到了 WASM 文件名,使用它;否则回退到默认名称
65
+ const targetWasmName = wasmFileName || 'avatar_core_wasm.wasm';
66
+ const wasmSource = join(wasmSourceDir, targetWasmName);
67
+ const wasmDest = join(rootDir, `dist/assets/${targetWasmName}`);
68
+ const wasmJsDest = join(rootDir, 'dist/assets/avatar_core_wasm.js');
69
+ const headersDest = join(rootDir, 'dist/_headers');
70
+ // 复制 WASM 文件
71
+ if (existsSync(wasmSource)) {
72
+ copyFileSync(wasmSource, wasmDest);
73
+ console.log(`✅ [avatarkit] Copied WASM file to dist/assets/${targetWasmName}`);
74
+ }
75
+ else {
76
+ console.warn(`⚠️ [avatarkit] WASM file not found: ${wasmSource}`);
77
+ if (wasmFileName) {
78
+ console.warn(` Expected file: ${targetWasmName} (extracted from JS glue file)`);
79
+ }
80
+ }
81
+ // 复制 WASM JS glue 文件
82
+ if (existsSync(wasmSourceDir)) {
83
+ const files = readdirSync(wasmSourceDir);
84
+ const wasmJsFile = files.find((f) => f.startsWith('avatar_core_wasm') && f.endsWith('.js'));
85
+ if (wasmJsFile) {
86
+ const wasmJsSource = join(wasmSourceDir, wasmJsFile);
87
+ copyFileSync(wasmJsSource, wasmJsDest);
88
+ console.log(`✅ [avatarkit] Copied WASM JS file to dist/assets/avatar_core_wasm.js (from ${wasmJsFile})`);
89
+ }
90
+ else {
91
+ console.log('ℹ️ [avatarkit] WASM JS file not found (may be handled by Vite):', wasmSourceDir);
92
+ }
93
+ }
94
+ // 生成 _headers 文件(用于 Cloudflare Pages 等平台)
95
+ const headersContent = '/*.wasm\n Content-Type: application/wasm\n';
96
+ writeFileSync(headersDest, headersContent);
97
+ console.log('✅ [avatarkit] Created _headers file for Cloudflare Pages');
98
+ },
99
+ // 自动配置 Vite 选项
100
+ config() {
101
+ return {
102
+ optimizeDeps: {
103
+ exclude: ['@spatialwalk/avatarkit']
104
+ },
105
+ assetsInclude: ['**/*.wasm'],
106
+ build: {
107
+ assetsInlineLimit: 0, // 确保 WASM 文件不被内联
108
+ rollupOptions: {
109
+ output: {
110
+ assetFileNames: (assetInfo) => {
111
+ // WASM 文件使用固定名称
112
+ if (assetInfo.name?.endsWith('.wasm')) {
113
+ return 'assets/[name][extname]';
114
+ }
115
+ // 其他资源使用 hash
116
+ return 'assets/[name]-[hash][extname]';
117
+ }
118
+ }
119
+ }
120
+ }
121
+ };
122
+ }
123
+ };
124
+ }
125
+ // 默认导出
126
+ export default avatarkitVitePlugin;