From ff4703f71fbb0cc0c6c90fe8ec84acd62bc11cfb Mon Sep 17 00:00:00 2001 From: Elecmonkey Date: Wed, 24 Jun 2026 18:48:13 +0800 Subject: [PATCH 01/10] test(wasm): add integration cases for compile and preserve modes Cover compile/preserve modes across bundle and bundleless builds. --- pnpm-lock.yaml | 8 ++ .../wasm/compile-bundle/package.json | 6 ++ .../wasm/compile-bundle/rslib.config.ts | 14 ++++ .../wasm/compile-bundle/src/add.wasm | Bin 0 -> 41 bytes .../wasm/compile-bundle/src/index.js | 1 + .../wasm/compile-bundle/src/utils.js | 3 + .../wasm/compile-bundleless/package.json | 6 ++ .../wasm/compile-bundleless/rslib.config.ts | 14 ++++ .../wasm/compile-bundleless/src/add.wasm | Bin 0 -> 41 bytes .../wasm/compile-bundleless/src/index.js | 1 + .../wasm/compile-bundleless/src/utils.js | 3 + tests/integration/wasm/index.test.ts | 79 ++++++++++++++++++ .../wasm/preserve-bundle/package.json | 6 ++ .../wasm/preserve-bundle/rslib.config.ts | 14 ++++ .../wasm/preserve-bundle/src/add.wasm | Bin 0 -> 41 bytes .../wasm/preserve-bundle/src/index.js | 1 + .../wasm/preserve-bundle/src/utils.js | 3 + .../wasm/preserve-bundleless/package.json | 6 ++ .../wasm/preserve-bundleless/rslib.config.ts | 14 ++++ .../wasm/preserve-bundleless/src/add.wasm | Bin 0 -> 41 bytes .../wasm/preserve-bundleless/src/index.js | 1 + .../wasm/preserve-bundleless/src/utils.js | 3 + 22 files changed, 183 insertions(+) create mode 100644 tests/integration/wasm/compile-bundle/package.json create mode 100644 tests/integration/wasm/compile-bundle/rslib.config.ts create mode 100644 tests/integration/wasm/compile-bundle/src/add.wasm create mode 100644 tests/integration/wasm/compile-bundle/src/index.js create mode 100644 tests/integration/wasm/compile-bundle/src/utils.js create mode 100644 tests/integration/wasm/compile-bundleless/package.json create mode 100644 tests/integration/wasm/compile-bundleless/rslib.config.ts create mode 100644 tests/integration/wasm/compile-bundleless/src/add.wasm create mode 100644 tests/integration/wasm/compile-bundleless/src/index.js create mode 100644 tests/integration/wasm/compile-bundleless/src/utils.js create mode 100644 tests/integration/wasm/index.test.ts create mode 100644 tests/integration/wasm/preserve-bundle/package.json create mode 100644 tests/integration/wasm/preserve-bundle/rslib.config.ts create mode 100644 tests/integration/wasm/preserve-bundle/src/add.wasm create mode 100644 tests/integration/wasm/preserve-bundle/src/index.js create mode 100644 tests/integration/wasm/preserve-bundle/src/utils.js create mode 100644 tests/integration/wasm/preserve-bundleless/package.json create mode 100644 tests/integration/wasm/preserve-bundleless/rslib.config.ts create mode 100644 tests/integration/wasm/preserve-bundleless/src/add.wasm create mode 100644 tests/integration/wasm/preserve-bundleless/src/index.js create mode 100644 tests/integration/wasm/preserve-bundleless/src/utils.js diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 89ea29f85..2adb8b6da 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1254,6 +1254,14 @@ importers: specifier: ^3.5.39 version: 3.5.39(typescript@6.0.3) + tests/integration/wasm/compile-bundle: {} + + tests/integration/wasm/compile-bundleless: {} + + tests/integration/wasm/preserve-bundle: {} + + tests/integration/wasm/preserve-bundleless: {} + tests/integration/worker: {} tests/scripts: {} diff --git a/tests/integration/wasm/compile-bundle/package.json b/tests/integration/wasm/compile-bundle/package.json new file mode 100644 index 000000000..52dd3e3ee --- /dev/null +++ b/tests/integration/wasm/compile-bundle/package.json @@ -0,0 +1,6 @@ +{ + "name": "wasm-compile-bundle-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/wasm/compile-bundle/rslib.config.ts b/tests/integration/wasm/compile-bundle/rslib.config.ts new file mode 100644 index 000000000..8e103a73a --- /dev/null +++ b/tests/integration/wasm/compile-bundle/rslib.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from '@rslib/core'; +import { generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [ + generateBundleEsmConfig({ + bundle: true, + wasm: { mode: 'compile' }, + }), + ], + output: { + target: 'web', + }, +}); diff --git a/tests/integration/wasm/compile-bundle/src/add.wasm b/tests/integration/wasm/compile-bundle/src/add.wasm new file mode 100644 index 0000000000000000000000000000000000000000..357f72da7a0db8add83699082fd51d46bf3352fb GIT binary patch literal 41 wcmZQbEY4+QU|?WmXG~zKuV<`hW@2PuXJ=$iOi5v2;NoOtXHZ~JV9eqM0DJxgJ^%m! literal 0 HcmV?d00001 diff --git a/tests/integration/wasm/compile-bundle/src/index.js b/tests/integration/wasm/compile-bundle/src/index.js new file mode 100644 index 000000000..fcbda0437 --- /dev/null +++ b/tests/integration/wasm/compile-bundle/src/index.js @@ -0,0 +1 @@ +export { useAdd } from './utils.js'; diff --git a/tests/integration/wasm/compile-bundle/src/utils.js b/tests/integration/wasm/compile-bundle/src/utils.js new file mode 100644 index 000000000..7456cf3d0 --- /dev/null +++ b/tests/integration/wasm/compile-bundle/src/utils.js @@ -0,0 +1,3 @@ +import { add } from './add.wasm'; + +export const useAdd = (a, b) => add(a, b); diff --git a/tests/integration/wasm/compile-bundleless/package.json b/tests/integration/wasm/compile-bundleless/package.json new file mode 100644 index 000000000..0fe227c7a --- /dev/null +++ b/tests/integration/wasm/compile-bundleless/package.json @@ -0,0 +1,6 @@ +{ + "name": "wasm-compile-bundleless-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/wasm/compile-bundleless/rslib.config.ts b/tests/integration/wasm/compile-bundleless/rslib.config.ts new file mode 100644 index 000000000..56ecdfab4 --- /dev/null +++ b/tests/integration/wasm/compile-bundleless/rslib.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from '@rslib/core'; +import { generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [ + generateBundleEsmConfig({ + bundle: false, + wasm: { mode: 'compile' }, + }), + ], + output: { + target: 'web', + }, +}); diff --git a/tests/integration/wasm/compile-bundleless/src/add.wasm b/tests/integration/wasm/compile-bundleless/src/add.wasm new file mode 100644 index 0000000000000000000000000000000000000000..357f72da7a0db8add83699082fd51d46bf3352fb GIT binary patch literal 41 wcmZQbEY4+QU|?WmXG~zKuV<`hW@2PuXJ=$iOi5v2;NoOtXHZ~JV9eqM0DJxgJ^%m! literal 0 HcmV?d00001 diff --git a/tests/integration/wasm/compile-bundleless/src/index.js b/tests/integration/wasm/compile-bundleless/src/index.js new file mode 100644 index 000000000..fcbda0437 --- /dev/null +++ b/tests/integration/wasm/compile-bundleless/src/index.js @@ -0,0 +1 @@ +export { useAdd } from './utils.js'; diff --git a/tests/integration/wasm/compile-bundleless/src/utils.js b/tests/integration/wasm/compile-bundleless/src/utils.js new file mode 100644 index 000000000..7456cf3d0 --- /dev/null +++ b/tests/integration/wasm/compile-bundleless/src/utils.js @@ -0,0 +1,3 @@ +import { add } from './add.wasm'; + +export const useAdd = (a, b) => add(a, b); diff --git a/tests/integration/wasm/index.test.ts b/tests/integration/wasm/index.test.ts new file mode 100644 index 000000000..b2ee38ea3 --- /dev/null +++ b/tests/integration/wasm/index.test.ts @@ -0,0 +1,79 @@ +import { existsSync, readdirSync } from 'node:fs'; +import { join } from 'node:path'; +import { expect, test } from '@rstest/core'; +import { buildAndGetResults, queryContent } from 'test-helper'; + +const walk = (dir: string): string[] => + readdirSync(dir, { withFileTypes: true }).flatMap((entry) => { + const p = join(dir, entry.name); + return entry.isDirectory() ? walk(p) : [p]; + }); + +test('wasm: compile + bundle', async () => { + const fixturePath = join(__dirname, 'compile-bundle'); + const { contents } = await buildAndGetResults({ fixturePath }); + + const { content: indexJs } = queryContent(contents.esm!, /index\.js/); + // In compile mode the wasm export is wired through Rspack's runtime, + // not preserved as a top-level ESM import. + expect(indexJs).not.toMatch(/import\s*\{[^}]*\}\s*from\s*["'][^"']+\.wasm["']/); + expect(indexJs).toContain('useAdd'); + + const distDir = join(fixturePath, 'dist/esm'); + // Bundle mode collapses utils.js into the single entry chunk. + const jsFiles = walk(distDir).filter((p) => p.endsWith('.js')); + expect(jsFiles.length).toBe(1); + + const wasmFiles = walk(distDir).filter((p) => p.endsWith('.wasm')); + expect(wasmFiles.length).toBe(1); +}); + +test('wasm: compile + bundleless', async () => { + const fixturePath = join(__dirname, 'compile-bundleless'); + const { contents } = await buildAndGetResults({ fixturePath }); + + const { content: utilsJs } = queryContent(contents.esm!, /utils\.js/); + // In compile mode the wasm export is wired through Rspack's runtime, + // not preserved as a top-level ESM import. + expect(utilsJs).not.toMatch(/import\s*\{[^}]*\}\s*from\s*["'][^"']+\.wasm["']/); + expect(utilsJs).toContain('useAdd'); + + const distDir = join(fixturePath, 'dist/esm'); + const wasmFiles = walk(distDir).filter((p) => p.endsWith('.wasm')); + expect(wasmFiles.length).toBe(1); +}); + +test('wasm: preserve + bundle', async () => { + const fixturePath = join(__dirname, 'preserve-bundle'); + const { contents } = await buildAndGetResults({ fixturePath }); + + const { content: indexJs } = queryContent(contents.esm!, /index\.js/); + // Bundle mode inlines utils into index, so the preserved wasm import is in index.js. + expect(indexJs).toMatch( + /import\s*\{\s*add\s*\}\s*from\s*["']\.\/[^"']+\.wasm["']/, + ); + + const distDir = join(fixturePath, 'dist/esm'); + const emitted = walk(distDir).filter((p) => p.endsWith('.wasm')); + expect(emitted.length).toBe(1); + // bundle + preserve uses a content-hashed filename, not the source filename. + expect(emitted[0]).not.toMatch(/[/\\]add\.wasm$/); +}); + +test('wasm: preserve + bundleless', async () => { + const fixturePath = join(__dirname, 'preserve-bundleless'); + const { contents } = await buildAndGetResults({ fixturePath }); + + const { content: indexJs } = queryContent(contents.esm!, /index\.js/); + const { content: utilsJs } = queryContent(contents.esm!, /utils\.js/); + + // index.js re-exports from ./utils.js, not from the wasm directly. + expect(indexJs).toMatch(/from\s*["']\.\/utils\.js["']/); + expect(indexJs).not.toMatch(/\.wasm/); + + // bundleless + preserve keeps the original wasm filename next to the source layout. + expect(utilsJs).toContain('import { add } from "./add.wasm"'); + + const distDir = join(fixturePath, 'dist/esm'); + expect(existsSync(join(distDir, 'add.wasm'))).toBe(true); +}); diff --git a/tests/integration/wasm/preserve-bundle/package.json b/tests/integration/wasm/preserve-bundle/package.json new file mode 100644 index 000000000..f71c7da0f --- /dev/null +++ b/tests/integration/wasm/preserve-bundle/package.json @@ -0,0 +1,6 @@ +{ + "name": "wasm-preserve-bundle-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/wasm/preserve-bundle/rslib.config.ts b/tests/integration/wasm/preserve-bundle/rslib.config.ts new file mode 100644 index 000000000..9f8f82f46 --- /dev/null +++ b/tests/integration/wasm/preserve-bundle/rslib.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from '@rslib/core'; +import { generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [ + generateBundleEsmConfig({ + bundle: true, + wasm: { mode: 'preserve' }, + }), + ], + output: { + target: 'web', + }, +}); diff --git a/tests/integration/wasm/preserve-bundle/src/add.wasm b/tests/integration/wasm/preserve-bundle/src/add.wasm new file mode 100644 index 0000000000000000000000000000000000000000..357f72da7a0db8add83699082fd51d46bf3352fb GIT binary patch literal 41 wcmZQbEY4+QU|?WmXG~zKuV<`hW@2PuXJ=$iOi5v2;NoOtXHZ~JV9eqM0DJxgJ^%m! literal 0 HcmV?d00001 diff --git a/tests/integration/wasm/preserve-bundle/src/index.js b/tests/integration/wasm/preserve-bundle/src/index.js new file mode 100644 index 000000000..fcbda0437 --- /dev/null +++ b/tests/integration/wasm/preserve-bundle/src/index.js @@ -0,0 +1 @@ +export { useAdd } from './utils.js'; diff --git a/tests/integration/wasm/preserve-bundle/src/utils.js b/tests/integration/wasm/preserve-bundle/src/utils.js new file mode 100644 index 000000000..7456cf3d0 --- /dev/null +++ b/tests/integration/wasm/preserve-bundle/src/utils.js @@ -0,0 +1,3 @@ +import { add } from './add.wasm'; + +export const useAdd = (a, b) => add(a, b); diff --git a/tests/integration/wasm/preserve-bundleless/package.json b/tests/integration/wasm/preserve-bundleless/package.json new file mode 100644 index 000000000..a46172686 --- /dev/null +++ b/tests/integration/wasm/preserve-bundleless/package.json @@ -0,0 +1,6 @@ +{ + "name": "wasm-preserve-bundleless-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/wasm/preserve-bundleless/rslib.config.ts b/tests/integration/wasm/preserve-bundleless/rslib.config.ts new file mode 100644 index 000000000..b6f9c708d --- /dev/null +++ b/tests/integration/wasm/preserve-bundleless/rslib.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from '@rslib/core'; +import { generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [ + generateBundleEsmConfig({ + bundle: false, + wasm: { mode: 'preserve' }, + }), + ], + output: { + target: 'web', + }, +}); diff --git a/tests/integration/wasm/preserve-bundleless/src/add.wasm b/tests/integration/wasm/preserve-bundleless/src/add.wasm new file mode 100644 index 0000000000000000000000000000000000000000..357f72da7a0db8add83699082fd51d46bf3352fb GIT binary patch literal 41 wcmZQbEY4+QU|?WmXG~zKuV<`hW@2PuXJ=$iOi5v2;NoOtXHZ~JV9eqM0DJxgJ^%m! literal 0 HcmV?d00001 diff --git a/tests/integration/wasm/preserve-bundleless/src/index.js b/tests/integration/wasm/preserve-bundleless/src/index.js new file mode 100644 index 000000000..fcbda0437 --- /dev/null +++ b/tests/integration/wasm/preserve-bundleless/src/index.js @@ -0,0 +1 @@ +export { useAdd } from './utils.js'; diff --git a/tests/integration/wasm/preserve-bundleless/src/utils.js b/tests/integration/wasm/preserve-bundleless/src/utils.js new file mode 100644 index 000000000..7456cf3d0 --- /dev/null +++ b/tests/integration/wasm/preserve-bundleless/src/utils.js @@ -0,0 +1,3 @@ +import { add } from './add.wasm'; + +export const useAdd = (a, b) => add(a, b); From 567ed7e6dba70881079622d785b144b11359d601 Mon Sep 17 00:00:00 2001 From: Elecmonkey Date: Sun, 28 Jun 2026 23:46:50 +0800 Subject: [PATCH 02/10] feat(wasm): support wasm.mode to compile or preserve wasm modules in ESM output --- packages/core/src/config.ts | 30 ++++++- packages/core/src/index.ts | 1 + packages/core/src/types/config.ts | 24 +++++ packages/core/src/wasm/collect.ts | 46 ++++++++++ packages/core/src/wasm/compose.ts | 117 ++++++++++++++++++++++++ packages/core/src/wasm/emit.ts | 86 ++++++++++++++++++ packages/core/src/wasm/external.ts | 88 ++++++++++++++++++ packages/core/src/wasm/index.ts | 5 ++ packages/core/src/wasm/path.ts | 138 +++++++++++++++++++++++++++++ packages/core/src/wasm/types.ts | 14 +++ 10 files changed, 548 insertions(+), 1 deletion(-) create mode 100644 packages/core/src/wasm/collect.ts create mode 100644 packages/core/src/wasm/compose.ts create mode 100644 packages/core/src/wasm/emit.ts create mode 100644 packages/core/src/wasm/external.ts create mode 100644 packages/core/src/wasm/index.ts create mode 100644 packages/core/src/wasm/path.ts create mode 100644 packages/core/src/wasm/types.ts diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 30b6db02b..6290c607a 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -27,6 +27,7 @@ import { type CssLoaderOptionsAuto, isCssGlobalFile } from './css/utils'; import { composeExeConfig } from './exe'; import { composeEntryChunkConfig } from './plugins/EntryChunkPlugin'; import { pluginCjsShims, pluginEsmRequireShim } from './plugins/shims'; +import { composeWasmConfig, resolveWasmConfig } from './wasm'; import type { AutoExternal, BannerAndFooter, @@ -1168,6 +1169,7 @@ const composeEntryConfig = async ( bundle: LibConfig['bundle'], root: string, cssModulesAuto: CssLoaderOptionsAuto, + excludeWasmEntry: boolean, userOutBase?: string, ): Promise<{ entryConfig: EnvironmentConfig; outBase: string | null }> => { let entries = rawEntry; @@ -1291,7 +1293,11 @@ const composeEntryConfig = async ( // Filter the glob resolved entry files based on the allowed extensions const resolvedEntryFiles = globEntryFiles.filter((i) => { - return !DTS_EXTENSIONS_PATTERN.test(i); + if (DTS_EXTENSIONS_PATTERN.test(i)) return false; + // In preserve mode, `.wasm` files are handled by the wasm externals + // plugin, not as source entries. + if (excludeWasmEntry && i.endsWith('.wasm')) return false; + return true; }); if (resolvedEntryFiles.length === 0) { @@ -1370,6 +1376,7 @@ const composeBundlelessExternalConfig = ( cssModulesAuto: CssLoaderOptionsAuto, bundle: boolean, outBase: string | null, + preExternals: Rspack.ExternalItem[], ): { config: EnvironmentConfig; resolvedJsRedirect?: DeepRequired; @@ -1411,6 +1418,7 @@ const composeBundlelessExternalConfig = ( config: { output: { externals: [ + ...preExternals, async (data, callback) => { const { request, getResolve, context, contextInfo } = data; if (!request || !getResolve || !context || !contextInfo) { @@ -1828,11 +1836,19 @@ async function composeLibRsbuildConfig( pkgJson, ); + const resolvedWasm = resolveWasmConfig({ + bundle, + format, + wasmConfig: config.wasm, + }); + const excludeWasmEntry = resolvedWasm.mode !== 'compile'; + const { entryConfig, outBase } = await composeEntryConfig( config.source?.entry, config.bundle, root, cssModulesAuto, + excludeWasmEntry, config.outBase, ); const { config: exeConfig } = composeExeConfig({ @@ -1844,12 +1860,22 @@ async function composeLibRsbuildConfig( target, }); + const wasmCompose = composeWasmConfig({ + bundle, + format, + mode: resolvedWasm.mode, + hasUserWasmMode: resolvedWasm.hasUserWasmMode, + outBase, + }); + const wasmConfig = wasmCompose.config; + const { config: bundlelessExternalConfig } = composeBundlelessExternalConfig( jsExtension, redirect, cssModulesAuto, bundle, outBase, + wasmCompose.bundlelessExternal ? [wasmCompose.bundlelessExternal] : [], ); const syntaxConfig = composeSyntaxConfig(target, config?.syntax); const autoExternalConfig = composeAutoExternalConfig({ @@ -1912,6 +1938,7 @@ async function composeLibRsbuildConfig( entryConfig, cssConfig, assetConfig, + wasmConfig, entryChunkConfig, minifyConfig, dtsConfig, @@ -2002,6 +2029,7 @@ export async function composeCreateRsbuildConfig( shims: true, umdName: true, outBase: true, + wasm: true, experiments: true, }), ), diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 8786c3850..f6012e6b9 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -38,6 +38,7 @@ export type { StartMFDevServerOptions, StartDevServerResult, Syntax, + Wasm, } from './types'; export const version: string = RSLIB_VERSION; diff --git a/packages/core/src/types/config.ts b/packages/core/src/types/config.ts index 0c8de827f..4ba032c54 100644 --- a/packages/core/src/types/config.ts +++ b/packages/core/src/types/config.ts @@ -353,6 +353,25 @@ export type Redirect = { dts?: DtsRedirect; }; +export type WasmMode = 'compile' | 'preserve'; + +export type Wasm = { + /** + * How Rslib handles `.wasm` modules in the output. + * + * - `'compile'`: Rspack emits JS glue and the runtime needed to load the wasm. + * - `'preserve'`: Keep `.wasm` as a real ESM import in the output and emit the + * binary. In bundle mode the binary uses a content-hashed filename; in + * bundleless mode it keeps the source-relative path and original filename. + * + * Defaults to `'compile'` when `bundle` is `true`, `'preserve'` when `bundle` + * is `false`. + * + * Only effective for `format: 'esm'` builds. + */ + mode?: WasmMode; +}; + export type LibExperiments = { /** * Whether to enable Rspack advanced ESM output. @@ -473,6 +492,11 @@ export interface LibConfig extends EnvironmentConfig { * @see {@link https://rslib.rs/config/lib/out-base} */ outBase?: string; + /** + * Configure how Rslib handles `.wasm` modules. + * @defaultValue `undefined` + */ + wasm?: Wasm; /** * @inheritdoc */ diff --git a/packages/core/src/wasm/collect.ts b/packages/core/src/wasm/collect.ts new file mode 100644 index 000000000..3d9ee9e8e --- /dev/null +++ b/packages/core/src/wasm/collect.ts @@ -0,0 +1,46 @@ +import { promises as fs } from 'node:fs'; +import { type Rspack, rspack } from '@rsbuild/core'; +import { computeEmitDistRelPath } from './path'; + +/** + * Build the preserved `.wasm` emit list from the module graph. + * + * Walks `compilation.modules` for preserved `ExternalModule` entries whose + * `userRequest` ends with `.wasm`, resolves each request using rspack's + * resolver (so aliases and tsconfig paths work correctly), computes the emit + * path, and returns a map of source path to emit path. + */ +export const collectExternalWasmSources = async ( + compiler: Rspack.Compiler, + compilation: Rspack.Compilation, + outBase: string | null, + preserveToSource: boolean, +): Promise> => { + const found = new Map(); + const { moduleGraph } = compilation; + const resolver = compiler.resolverFactory.get('normal', { dependencyType: 'esm' }); + + for (const module of compilation.modules) { + if (!(module instanceof rspack.ExternalModule)) continue; + const { userRequest } = module; + if (!userRequest || !userRequest.endsWith('.wasm')) continue; + const issuer = moduleGraph.getIssuer(module); + const issuerContext = (issuer as { context?: string } | null)?.context; + if (!issuerContext) continue; + + const resolved = resolver.resolveSync({}, issuerContext, userRequest); + if (!resolved) continue; + + const emitDistRelPath = computeEmitDistRelPath({ + bytes: await fs.readFile(resolved), + compiler, + compilation, + outBase, + preserveToSource, + sourcePath: resolved, + }); + + found.set(resolved, emitDistRelPath); + } + return found; +}; diff --git a/packages/core/src/wasm/compose.ts b/packages/core/src/wasm/compose.ts new file mode 100644 index 000000000..3d6dfe6ad --- /dev/null +++ b/packages/core/src/wasm/compose.ts @@ -0,0 +1,117 @@ +import type { EnvironmentConfig, Rspack } from '@rsbuild/core'; +import type { Format, Wasm, WasmMode } from '../types'; +import { logger } from '../utils/logger'; +import { WasmPreserveEmitPlugin } from './emit'; +import { createWasmPreserveExternal } from './external'; +import { + PLUGIN_NAME, + type ResolvedWasmConfig, + type WasmCompilerContext, +} from './types'; + +export const resolveWasmConfig = ({ + bundle, + format, + wasmConfig, +}: { + bundle: boolean; + format: Format; + wasmConfig?: Wasm; +}): ResolvedWasmConfig => { + const options = wasmConfig ?? {}; + if (format !== 'esm') { + return { + mode: 'compile', + hasUserWasmMode: Boolean(options.mode), + }; + } + + const defaultMode: WasmMode = bundle ? 'compile' : 'preserve'; + return { + mode: options.mode ?? defaultMode, + hasUserWasmMode: Boolean(options.mode), + }; +}; + +export const composeWasmConfig = ({ + bundle, + format, + mode, + hasUserWasmMode, + outBase, +}: { + bundle: boolean; + format: Format; + mode: WasmMode; + hasUserWasmMode: boolean; + outBase: string | null; +}): { + config: EnvironmentConfig; + bundlelessExternal?: Rspack.ExternalItem; +} => { + if (format !== 'esm') { + if (hasUserWasmMode) { + logger.warn( + `[${PLUGIN_NAME}] wasm.mode is only effective for "format: 'esm'", but format is "${format}". The option will be ignored.`, + ); + } + return { config: {} }; + } + + if (mode === 'compile') { + return { config: {} }; + } + + const compilerContext: WasmCompilerContext = {}; + + // bundle + preserve emits content-hashed assets (the `outBase` is irrelevant + // since everything collapses into the entry chunk). bundleless + preserve + // keeps the original source layout and filename next to the emitted JS. + if (bundle) { + return { + config: { + output: { + externals: [ + createWasmPreserveExternal({ + compilerContext, + outBase: null, + preserveToSource: false, + }), + ], + }, + tools: { + rspack: { + plugins: [ + new WasmPreserveEmitPlugin({ + compilerContext, + outBase: null, + preserveToSource: false, + }), + ], + }, + }, + }, + }; + } + + return { + config: { + tools: { + rspack: { + plugins: [ + new WasmPreserveEmitPlugin({ + compilerContext, + outBase, + preserveToSource: true, + }), + ], + }, + }, + }, + bundlelessExternal: createWasmPreserveExternal({ + compilerContext, + outBase, + preserveToSource: true, + }), + }; +}; diff --git a/packages/core/src/wasm/emit.ts b/packages/core/src/wasm/emit.ts new file mode 100644 index 000000000..cc7f423bd --- /dev/null +++ b/packages/core/src/wasm/emit.ts @@ -0,0 +1,86 @@ +import { promises as fs } from 'node:fs'; +import type { Rspack } from '@rsbuild/core'; +import { collectExternalWasmSources } from './collect'; +import { + getAssetEmitPath, + getWebassemblyModuleFilename, +} from './path'; +import { + PLUGIN_NAME, + type WasmCompilerContext, +} from './types'; + +const EMIT_PLUGIN_NAME = 'RslibWasmPreserveEmitPlugin'; + +export class WasmPreserveEmitPlugin { + readonly compilerContext: WasmCompilerContext; + readonly outBase: string | null; + readonly preserveToSource: boolean; + + constructor(options: { + compilerContext: WasmCompilerContext; + outBase: string | null; + preserveToSource: boolean; + }) { + this.compilerContext = options.compilerContext; + this.outBase = options.outBase; + this.preserveToSource = options.preserveToSource; + } + + apply(compiler: Rspack.Compiler): void { + this.compilerContext.compiler = compiler; + const { sources, Compilation } = compiler.webpack; + compiler.hooks.thisCompilation.tap(EMIT_PLUGIN_NAME, (compilation) => { + this.compilerContext.compilation = compilation; + compilation.hooks.processAssets.tapPromise( + { + name: EMIT_PLUGIN_NAME, + stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL, + }, + async () => { + // Derive the emit list from the module graph, which is fully + // available here for both cold and persistent-cache builds. + const toEmit = await collectExternalWasmSources( + compiler, + compilation, + this.outBase, + this.preserveToSource, + ); + + await Promise.all( + Array.from(toEmit, async ([sourcePath, emitPath]) => { + if (compilation.getAsset(emitPath)) return; + try { + const bytes = await fs.readFile(sourcePath); + const assetInfo = + this.outBase && this.preserveToSource + ? undefined + : getAssetEmitPath({ + bytes, + compiler, + compilation, + filenameTemplate: + getWebassemblyModuleFilename(compilation), + sourcePath, + }).info; + compilation.emitAsset( + emitPath, + new sources.RawSource(bytes), + assetInfo, + ); + } catch (err) { + compilation.errors.push( + new compiler.webpack.WebpackError( + `[${PLUGIN_NAME}] Failed to read .wasm source ${sourcePath}: ${ + (err as Error).message + }`, + ), + ); + } + }), + ); + }, + ); + }); + } +} diff --git a/packages/core/src/wasm/external.ts b/packages/core/src/wasm/external.ts new file mode 100644 index 000000000..147d62f98 --- /dev/null +++ b/packages/core/src/wasm/external.ts @@ -0,0 +1,88 @@ +import { promises as fs } from 'node:fs'; +import type { Rspack } from '@rsbuild/core'; +import type { RspackResolver } from '../types'; +import { color } from '../utils/color'; +import { logger } from '../utils/logger'; +import { + computeEmitDistRelPath, + computeRequestFromIssuer, +} from './path'; +import { PLUGIN_NAME, type WasmCompilerContext } from './types'; + +const isPreservableWasmRequest = ( + request: string | undefined, + dependencyType?: string, +): boolean => { + if (!request) return false; + if (!request.endsWith('.wasm')) return false; + // Preserve mode only owns direct WASM ESM-Integration imports. In current + // Rslib builds, `new URL('./x.wasm', import.meta.url)` is filtered earlier by + // `parser.url = false`, so it should not reach this branch. Keep asset-style + // `.wasm` requests out of preserve externalization as they belong to resource + // semantics rather than module semantics. + if (dependencyType === 'asset') return false; + return true; +}; + +export const createWasmPreserveExternal = ({ + compilerContext, + outBase, + preserveToSource, +}: { + compilerContext: WasmCompilerContext; + outBase: string | null; + preserveToSource: boolean; +}): Rspack.ExternalItem => { + let resolver: RspackResolver | undefined; + + const external = async ( + data: Rspack.ExternalItemFunctionData, + callback: ( + err?: Error, + result?: Rspack.ExternalItemValue, + type?: Rspack.ExternalsType, + ) => void, + ): Promise => { + const { request, getResolve, context, contextInfo, dependencyType } = data; + if (!request || !getResolve || !context || !contextInfo) { + callback(); + return; + } + if (!isPreservableWasmRequest(request, dependencyType)) { + callback(); + return; + } + if (!resolver) { + resolver = getResolve() as RspackResolver; + } + try { + const resolved = await resolver(context, request); + const bytes = await fs.readFile(resolved); + const emitDistRelPath = computeEmitDistRelPath({ + bytes, + compiler: compilerContext.compiler!, + compilation: compilerContext.compilation!, + outBase, + preserveToSource, + sourcePath: resolved, + }); + + const issuer = contextInfo.issuer || ''; + const externalRequest = computeRequestFromIssuer( + issuer, + outBase, + emitDistRelPath, + ); + callback(undefined, externalRequest, 'module'); + } catch (err) { + logger.debug( + `[${PLUGIN_NAME}] Failed to externalize ${color.green(`"${request}"`)} from ${color.green(contextInfo.issuer || '')}: ${ + (err as Error).message + }`, + ); + callback(); + } + }; + + return external as Rspack.ExternalItem; +}; diff --git a/packages/core/src/wasm/index.ts b/packages/core/src/wasm/index.ts new file mode 100644 index 000000000..b72207c6b --- /dev/null +++ b/packages/core/src/wasm/index.ts @@ -0,0 +1,5 @@ +export { + composeWasmConfig, + resolveWasmConfig, +} from './compose'; +export type { ResolvedWasmConfig } from './types'; diff --git a/packages/core/src/wasm/path.ts b/packages/core/src/wasm/path.ts new file mode 100644 index 000000000..fa6589736 --- /dev/null +++ b/packages/core/src/wasm/path.ts @@ -0,0 +1,138 @@ +import path from 'node:path'; +import { type Rspack, rspack } from '@rsbuild/core'; + +// Path helpers for wasm preserve mode. They compute where a wasm file is +// emitted and which request should be written back to the output JS. + +const computeWasmContentHash = ( + bytes: Buffer, + compiler: Rspack.Compiler, +): string => { + const hash = rspack.util.createHash( + compiler.options.output.hashFunction!, + ); + if (compiler.options.output.hashSalt) { + hash.update(Buffer.from(compiler.options.output.hashSalt)); + } + hash.update(Buffer.from(bytes)); + return hash.digest(compiler.options.output.hashDigest ?? 'hex'); +}; + +const getSourceFilenamePathData = (sourcePath: string): string => { + const parsed = path.parse(sourcePath); + return `${parsed.name}${parsed.ext}`; +}; + +const getAssetPathData = ({ + bytes, + compiler, + sourcePath, +}: { + bytes: Buffer; + compiler: Rspack.Compiler; + sourcePath: string; +}): { + filename: string; + hash: string; + contentHash: string; +} => { + const contentHash = computeWasmContentHash(bytes, compiler); + return { + filename: getSourceFilenamePathData(sourcePath), + hash: contentHash, + contentHash, + }; +}; + +// Compute the emitted asset path using Rspack's wasm filename template. +export const getAssetEmitPath = ({ + bytes, + compiler, + compilation, + filenameTemplate, + sourcePath, +}: { + bytes: Buffer; + compiler: Rspack.Compiler; + compilation: Rspack.Compilation; + filenameTemplate: string; + sourcePath: string; +}): { + filename: string; + info: Record; +} => { + const { path: filename, info } = compilation.getAssetPathWithInfo( + filenameTemplate, + getAssetPathData({ bytes, compiler, sourcePath }), + ); + return { filename, info: info as Record }; +}; + +// Read the wasm filename template configured on the current compilation. +export const getWebassemblyModuleFilename = ( + compilation: Rspack.Compilation, +): string => { + const filename = compilation.outputOptions.webassemblyModuleFilename; + if (!filename) { + throw new Error( + 'Rspack output.webassemblyModuleFilename is required for wasm preserve asset emit.', + ); + } + return filename; +}; + +// Compute the dist-relative emit path for a preserved wasm file. +export const computeEmitDistRelPath = ({ + bytes, + compiler, + compilation, + outBase, + preserveToSource, + sourcePath, +}: { + bytes: Buffer; + compiler: Rspack.Compiler; + compilation: Rspack.Compilation; + outBase: string | null; + preserveToSource: boolean; + sourcePath: string; +}): string => { + if (outBase && preserveToSource) { + return path.relative(outBase, sourcePath).split(path.sep).join('/'); + } + return getAssetEmitPath({ + bytes, + compiler, + compilation, + filenameTemplate: getWebassemblyModuleFilename(compilation), + sourcePath, + }).filename; +}; + +const ensureDotPrefix = (rel: string): string => { + const normalized = rel.split(path.sep).join('/'); + if (normalized.startsWith('./') || normalized.startsWith('../')) { + return normalized; + } + return `./${normalized}`; +}; + +// Compute the relative ESM import request from an output JS file to wasm. +export const computeRequestFromIssuer = ( + issuer: string, + outBase: string | null, + emitDistRelPath: string, +): string => { + if (outBase && issuer) { + const issuerRelToOutBase = path.relative(outBase, issuer); + const issuerDistDir = path.dirname(issuerRelToOutBase); + const fromDir = issuerDistDir === '.' || issuerDistDir === '' + ? '.' + : issuerDistDir; + return ensureDotPrefix(path.posix.relative( + fromDir.split(path.sep).join('/'), + emitDistRelPath, + )); + } + return ensureDotPrefix(emitDistRelPath); +}; diff --git a/packages/core/src/wasm/types.ts b/packages/core/src/wasm/types.ts new file mode 100644 index 000000000..4de95f4df --- /dev/null +++ b/packages/core/src/wasm/types.ts @@ -0,0 +1,14 @@ +import type { Rspack } from '@rsbuild/core'; +import type { WasmMode } from '../types'; + +export const PLUGIN_NAME = 'rslib:wasm'; + +export type ResolvedWasmConfig = { + mode: WasmMode; + hasUserWasmMode: boolean; +}; + +export type WasmCompilerContext = { + compiler?: Rspack.Compiler; + compilation?: Rspack.Compilation; +}; From 012cf2061b2fa1eb1152f9ebdd432011f1030070 Mon Sep 17 00:00:00 2001 From: Elecmonkey Date: Mon, 29 Jun 2026 00:18:51 +0800 Subject: [PATCH 03/10] docs(wasm): add WebAssembly guide for compile and preserve modes --- website/docs/en/guide/advanced/_meta.json | 1 + .../docs/en/guide/advanced/webassembly.mdx | 308 ++++++++++++++++++ website/docs/zh/guide/advanced/_meta.json | 1 + .../docs/zh/guide/advanced/webassembly.mdx | 308 ++++++++++++++++++ 4 files changed, 618 insertions(+) create mode 100644 website/docs/en/guide/advanced/webassembly.mdx create mode 100644 website/docs/zh/guide/advanced/webassembly.mdx diff --git a/website/docs/en/guide/advanced/_meta.json b/website/docs/en/guide/advanced/_meta.json index ea861b7ec..877e2a205 100644 --- a/website/docs/en/guide/advanced/_meta.json +++ b/website/docs/en/guide/advanced/_meta.json @@ -3,6 +3,7 @@ "output-compatibility", "dts", "css", + "webassembly", "static-assets", "svgr-files", "json-files", diff --git a/website/docs/en/guide/advanced/webassembly.mdx b/website/docs/en/guide/advanced/webassembly.mdx new file mode 100644 index 000000000..2fe3730b3 --- /dev/null +++ b/website/docs/en/guide/advanced/webassembly.mdx @@ -0,0 +1,308 @@ +--- +description: 'Use WebAssembly modules in Rslib libraries with compile and preserve modes.' +--- + +import { Tabs, Tab } from '@theme'; + +# WebAssembly + +Rslib supports building libraries that import WebAssembly (`.wasm`) modules. + +You can use WebAssembly ESM Integration syntax in your source code: + +```ts title="src/index.ts" +import { add } from './add.wasm'; + +export const sum = add(1, 2); +``` + +## API + +Use `wasm` in the `lib` config to configure how Rslib handles direct `.wasm` imports. + +- **Type:** + +```ts +type WasmMode = 'compile' | 'preserve'; + +type Wasm = { + mode?: WasmMode; +}; +``` + +When `mode` is not specified, Rslib selects the default mode based on [bundle](/config/lib/bundle): + +| bundle | default mode | +| ------- | ------------ | +| `true` | `compile` | +| `false` | `preserve` | + +To control the behavior explicitly, set `wasm.mode`: + +```ts title="rslib.config.ts" +export default { + lib: [ + { + format: 'esm', + wasm: { + mode: 'preserve', + }, + }, + ], +}; +``` + +### wasm.mode + +`mode` controls how direct `.wasm` imports are handled. + +- `compile`: Rspack parses the `.wasm` module and generates JavaScript glue code and runtime loading logic. See [Compile mode](#compile-mode). +- `preserve`: Rslib preserves the `.wasm` import in the output and emits the binary for downstream tools or runtimes that support WebAssembly ESM Integration. See [Preserve mode](#preserve-mode). + +`mode` is only effective for ESM output. See [Limitations](#limitations). + +## Compile mode + +In compile mode, Rspack compiles the `.wasm` module and generates JavaScript code that loads and instantiates it at runtime. + +```ts title="rslib.config.ts" +export default { + lib: [ + { + format: 'esm', + bundle: true, + wasm: { + mode: 'compile', + }, + }, + ], +}; +``` + +Compile mode emits the WebAssembly binary as an asset. The output path follows Rsbuild's [output.distPath.wasm](/config/rsbuild/output#outputdistpath) (defaults to `static/wasm`) and the default wasm filename template, for example: + +```text +static/wasm/[contenthash].module.wasm +``` + +You can customize the directory and filename through [output.distPath](/config/rsbuild/output#outputdistpath) and [output.filename](/config/rsbuild/output). + +This mode is suitable when downstream users do not use another build tool, such as importing the library directly in a Node.js application or loading the ESM output directly in a browser. + +In compile mode, Rslib generates runtime code for loading the emitted `.wasm` file. The loading strategy is derived from Rslib's [output.target](/config/rsbuild/output#outputtarget): + +- For web targets, the generated runtime loads `.wasm` with `fetch`. +- For node targets, the generated runtime loads `.wasm` from the file system with async Node.js APIs. + +## Preserve mode + +In preserve mode, Rslib does not generate WebAssembly loading runtime for direct `.wasm` imports. Instead, it keeps a real `.wasm` import in the JavaScript output and emits the binary. + +```ts title="src/index.ts" +import { add } from './add.wasm'; + +export { add }; +``` + +The output layout depends on [bundle](/config/lib/bundle). + +### Bundle mode + +In bundle mode, source files are bundled into JavaScript chunks, so the `.wasm` binary is emitted as an asset with a content-hashed filename: + + + + +```text +dist/ +├── index.js +└── static/ + └── wasm/ + └── [contenthash].module.wasm +``` + + + + +```js +import { add } from './static/wasm/[contenthash].module.wasm'; + +export { add }; +``` + + + + +::: warning + +The content-hashed asset layout breaks WebAssembly ESM Integration semantics for `.wasm` modules that depend on relative module specifiers (for example, some `wasm-bindgen` outputs whose `.wasm` file must stay next to its JavaScript glue files). Moving the binary to the asset directory with a hashed name breaks those relative paths. For such modules, use [bundleless mode](#bundleless-mode) or [compile mode](#compile-mode). + +::: + +### Bundleless mode + +In bundleless mode, Rslib copies the `.wasm` file to the output using the same source-relative path and original filename: + + + + +```text +dist/ +├── index.js +└── add.wasm +``` + + + + +```js +import { add } from './add.wasm'; + +export { add }; +``` + + + + +```ts +import { add } from './add.wasm'; + +export { add }; +``` + + + + +This layout keeps the `.wasm` file next to the JavaScript files, which is recommended for WebAssembly packages that rely on relative module specifiers, such as some `wasm-bindgen` outputs where the `.wasm` file needs to stay next to JavaScript glue files. + +## Supported import forms + +Rslib supports the following WebAssembly ESM Integration import forms in ESM output. + +### Static import and export + +Use static imports and exports to access the instantiated WebAssembly exports through ESM bindings: + +```ts +import { add } from './add.wasm'; +import * as wasm from './add.wasm'; +export { add } from './add.wasm'; +export * from './add.wasm'; +export * as wasm from './add.wasm'; +import './add.wasm'; +``` + +### Dynamic import + +Dynamic import is also supported: + +```ts +const wasm = await import('./add.wasm'); +``` + +In `preserve` mode, Rslib keeps the `.wasm` module import semantics, while the generated JavaScript may still contain bundler runtime code for JavaScript chunks. + +### Source phase import + +`import source` is part of WebAssembly ESM Integration. It imports the compiled `WebAssembly.Module` instead of the instantiated exports. + +This is useful when you need to instantiate the module yourself, for example to pass a custom import object: + +```ts +import source wasmModule from './add.wasm'; + +const { instance } = await WebAssembly.instantiate(wasmModule, { + env: { + now: Date.now, + }, +}); +``` + +Dynamic source phase import is also supported through `import.source()`: + +```ts +const wasmModule = await import.source('./add.wasm'); +``` + +In `compile` mode, Rspack transforms this syntax into JavaScript output, so the final consumer does not need to support source phase imports natively. + +In `preserve` mode, Rslib keeps the source phase import in the output, so the downstream runtime or bundler must support it. + +Runtime support: + +| Runtime | Support | +| --- | --- | +| Node.js | Use Node.js v24.5.0 or later, or Node.js v22.19.0 or later. | +| Deno | Use Deno v2.6 or later. | + +Bundler support: + +| Bundler | Support | +| --- | --- | +| Rspack | Use `@rspack/core` v2.0.8 or later. | +| Rsbuild | Use `@rsbuild/core` v2.0.15 or later. | + +::: warning TypeScript support + +TypeScript does not currently parse `import source` or `import.source()`. Write source phase imports in JavaScript files or use a toolchain that supports the syntax. + +::: + +## Use wasm-bindgen + +[`wasm-bindgen`](https://wasm-bindgen.github.io/wasm-bindgen/) is a Rust and WebAssembly tool that provides an all-in-one WebAssembly development solution. + +When using `wasm-bindgen` with Rslib, generate the output with `--target bundler`: + +```bash +wasm-bindgen ./target/wasm32-unknown-unknown/release/pkg.wasm \ + --target bundler \ + --out-dir ./src/pkg +``` + +The `bundler` target generates JavaScript glue files that import the generated `.wasm` file as an ES module, which matches Rslib's WebAssembly handling model. + +A `wasm-bindgen` output has a relative dependency between the `.wasm` binary and its JavaScript glue files: the glue imports the `.wasm`, and the `.wasm` imports back into the glue. Whether a given Rslib configuration keeps this relationship intact depends on the `mode` and `bundle` combination: + +| bundle | mode | Supported | Notes | +| ------- | ---------- | --------- | --------------------------------------------------------------------- | +| `true` | `compile` | Yes | Rspack resolves the glue dependencies and bundles everything into the JavaScript output. | +| `false` | `compile` | Yes | Rspack resolves the glue dependencies and emits the loading runtime per file. | +| `false` | `preserve` | Yes | The `.wasm` and glue files keep their source-relative layout, so the relative specifiers stay valid. | +| `true` | `preserve` | No | The content-hashed asset layout moves the `.wasm` away from its glue files and breaks the relative specifiers. | + +In `compile` mode, Rspack parses the `.wasm` module and its glue dependencies at build time, so both bundle and bundleless work regardless of layout. + +Use bundleless preserve mode when you want to keep the original `wasm-bindgen` files in the output without generating loading runtime: + +```ts title="rslib.config.ts" +export default { + lib: [ + { + format: 'esm', + bundle: false, + wasm: { + mode: 'preserve', + }, + }, + ], +}; +``` + +::: warning + +Do not use [bundle mode](#bundle-mode) preserve for `wasm-bindgen` outputs. The content-hashed asset layout moves the `.wasm` file away from its glue files and breaks the relative module specifiers in the generated output. Use `compile` mode or bundleless `preserve` mode instead. + +::: + +## TypeScript declarations + +TypeScript does not provide built-in module declarations for `.wasm` files. You can add a declaration file next to the `.wasm` file using the `.d.wasm.ts` extension, which requires [`allowArbitraryExtensions`](https://www.typescriptlang.org/tsconfig/allowArbitraryExtensions.html) in your `tsconfig.json`: + +```ts title="src/add.d.wasm.ts" +export function add(a: number, b: number): number; +``` + +## Limitations + +- Direct WebAssembly ESM Integration imports are only supported for ESM output; `cjs`, `umd`, `iife`, and `mf` formats are not supported for direct `.wasm` import/export. When `mode` is set for a non-ESM format, it is ignored and the format falls back to compile behavior. diff --git a/website/docs/zh/guide/advanced/_meta.json b/website/docs/zh/guide/advanced/_meta.json index ea861b7ec..877e2a205 100644 --- a/website/docs/zh/guide/advanced/_meta.json +++ b/website/docs/zh/guide/advanced/_meta.json @@ -3,6 +3,7 @@ "output-compatibility", "dts", "css", + "webassembly", "static-assets", "svgr-files", "json-files", diff --git a/website/docs/zh/guide/advanced/webassembly.mdx b/website/docs/zh/guide/advanced/webassembly.mdx new file mode 100644 index 000000000..c1fd961e9 --- /dev/null +++ b/website/docs/zh/guide/advanced/webassembly.mdx @@ -0,0 +1,308 @@ +--- +description: '在 Rslib 库中使用 WebAssembly 模块,了解 compile 与 preserve 模式。' +--- + +import { Tabs, Tab } from '@theme'; + +# WebAssembly + +Rslib 支持构建引用 WebAssembly(`.wasm`)模块的库。 + +你可以在源码中使用 WebAssembly ESM Integration 语法: + +```ts title="src/index.ts" +import { add } from './add.wasm'; + +export const sum = add(1, 2); +``` + +## API + +在 `lib` 配置中使用 `wasm` 配置 Rslib 如何处理直接导入的 `.wasm` 模块。 + +- **类型:** + +```ts +type WasmMode = 'compile' | 'preserve'; + +type Wasm = { + mode?: WasmMode; +}; +``` + +当未配置 `mode` 时,Rslib 会根据 [bundle](/config/lib/bundle) 选择默认 mode: + +| bundle | 默认 mode | +| ------- | --------- | +| `true` | `compile` | +| `false` | `preserve` | + +如果需要显式控制行为,可以设置 `wasm.mode`: + +```ts title="rslib.config.ts" +export default { + lib: [ + { + format: 'esm', + wasm: { + mode: 'preserve', + }, + }, + ], +}; +``` + +### wasm.mode + +`mode` 用于控制直接 `.wasm` import 的处理方式。 + +- `compile`:Rspack 解析 `.wasm` 模块,并生成 JavaScript 胶水代码和运行时加载逻辑。详见 [Compile mode](#compile-mode)。 +- `preserve`:Rslib 在产物中保留 `.wasm` import,并输出二进制文件,交给支持 WebAssembly ESM Integration 的下游工具或运行时处理。详见 [Preserve mode](#preserve-mode)。 + +`mode` 仅对 ESM 产物生效,详见 [使用限制](#使用限制)。 + +## Compile mode + +在 compile mode 下,Rspack 会编译 `.wasm` 模块,并生成负责加载和实例化 WebAssembly 的 JavaScript 代码。 + +```ts title="rslib.config.ts" +export default { + lib: [ + { + format: 'esm', + bundle: true, + wasm: { + mode: 'compile', + }, + }, + ], +}; +``` + +Compile mode 会将 WebAssembly 二进制文件作为资源输出。输出路径遵循 Rsbuild 的 [output.distPath.wasm](/config/rsbuild/output#outputdistpath)(默认 `static/wasm`)和默认 wasm 文件名模板,例如: + +```text +static/wasm/[contenthash].module.wasm +``` + +你可以通过 [output.distPath](/config/rsbuild/output#outputdistpath) 和 [output.filename](/config/rsbuild/output) 自定义目录和文件名。 + +该模式适用于下游用户没有再使用构建工具的场景,例如在 Node.js 应用中直接导入该库,或在浏览器中直接加载 ESM 产物。 + +在 compile mode 下,Rslib 会生成用于加载 `.wasm` 文件的运行时代码。具体加载方式会根据 Rslib 的 [output.target](/config/rsbuild/output#outputtarget) 配置决定: + +- Web target 会生成通过 `fetch` 加载 `.wasm` 的运行时代码。 +- Node target 会生成通过 Node.js 异步文件系统 API 加载 `.wasm` 的运行时代码。 + +## Preserve mode + +在 preserve mode 下,Rslib 不会为直接 `.wasm` import 生成 WebAssembly 加载运行时,而是在 JavaScript 产物中保留真实的 `.wasm` import,并输出二进制文件。 + +```ts title="src/index.ts" +import { add } from './add.wasm'; + +export { add }; +``` + +产物布局取决于 [bundle](/config/lib/bundle)。 + +### Bundle 模式 + +在 bundle 模式下,源码文件会被打包进 JavaScript chunk,因此 `.wasm` 二进制会作为资产输出,并使用带 content hash 的文件名: + + + + +```text +dist/ +├── index.js +└── static/ + └── wasm/ + └── [contenthash].module.wasm +``` + + + + +```js +import { add } from './static/wasm/[contenthash].module.wasm'; + +export { add }; +``` + + + + +::: warning + +content hash 的 asset 布局会破坏依赖相对 module specifier 的 `.wasm` 模块的 WebAssembly ESM Integration 语义(例如部分 `wasm-bindgen` 产物,其 `.wasm` 文件必须与 JavaScript glue 文件保持相邻)。将二进制以 hash 文件名移动到资产目录后,这些相对路径会断裂。对于这类模块,请使用 [bundleless 模式](#bundleless-模式) 或 [compile 模式](#compile-mode)。 + +::: + +### Bundleless 模式 + +在 bundleless 模式下,Rslib 会按照源码相对路径和原文件名将 `.wasm` 文件 copy 到产物目录: + + + + +```text +dist/ +├── index.js +└── add.wasm +``` + + + + +```js +import { add } from './add.wasm'; + +export { add }; +``` + + + + +```ts +import { add } from './add.wasm'; + +export { add }; +``` + + + + +该布局会让 `.wasm` 文件与 JavaScript 文件保持相邻,适用于依赖相对 module specifier 的 WebAssembly 包,例如部分 `wasm-bindgen` 产物需要让 `.wasm` 文件与 JavaScript glue 文件保持相邻。 + +## 支持的导入形式 + +Rslib 在 ESM 产物中支持以下 WebAssembly ESM Integration 导入形式。 + +### 静态导入与导出 + +当你希望通过 ESM binding 访问实例化后的 WebAssembly 导出时,可以使用静态导入和导出: + +```ts +import { add } from './add.wasm'; +import * as wasm from './add.wasm'; +export { add } from './add.wasm'; +export * from './add.wasm'; +export * as wasm from './add.wasm'; +import './add.wasm'; +``` + +### 动态导入 + +也支持动态导入: + +```ts +const wasm = await import('./add.wasm'); +``` + +在 `preserve` 模式下,Rslib 会保留 `.wasm` 模块的导入语义,但生成的 JavaScript 中仍可能包含用于加载 JavaScript chunk 的 bundler runtime。 + +### Source phase import + +`import source` 是 WebAssembly ESM Integration 的一部分。它导入的是编译后的 `WebAssembly.Module`,而不是实例化后的导出对象。 + +当你需要自行实例化模块时(例如传入自定义 import object),可以使用这种语法: + +```ts +import source wasmModule from './add.wasm'; + +const { instance } = await WebAssembly.instantiate(wasmModule, { + env: { + now: Date.now, + }, +}); +``` + +也支持通过 `import.source()` 进行动态 source phase import: + +```ts +const wasmModule = await import.source('./add.wasm'); +``` + +在 `compile` 模式下,Rspack 会将这种语法转换为 JavaScript 产物,因此最终消费方不需要原生支持 source phase import。 + +在 `preserve` 模式下,Rslib 会在产物中保留 source phase import,因此下游运行时或打包工具必须支持这种语法。 + +运行时支持: + +| 运行时 | 支持情况 | +| --- | --- | +| Node.js | 请使用 Node.js v24.5.0 及以上版本,或 Node.js v22.19.0 及以上版本。 | +| Deno | 请使用 Deno v2.6 及以上版本。 | + +打包工具支持: + +| 打包工具 | 支持情况 | +| --- | --- | +| Rspack | 请使用 `@rspack/core` v2.0.8 及以上版本。 | +| Rsbuild | 请使用 `@rsbuild/core` v2.0.15 及以上版本。 | + +::: warning TypeScript 支持 + +TypeScript 当前不能解析 `import source` 或 `import.source()`。请在 JavaScript 文件中编写 source phase import,或使用支持该语法的工具链。 + +::: + +## 使用 wasm-bindgen + +[`wasm-bindgen`](https://wasm-bindgen.github.io/wasm-bindgen/) 是 Rust 和 WebAssembly 生态中的工具,它提供了一站式的 WebAssembly 开发方案。 + +在 Rslib 中使用 `wasm-bindgen` 时,建议使用 `--target bundler` 生成产物: + +```bash +wasm-bindgen ./target/wasm32-unknown-unknown/release/pkg.wasm \ + --target bundler \ + --out-dir ./src/pkg +``` + +`bundler` target 会生成以 ES module 形式导入 `.wasm` 文件的 JavaScript glue 产物,这和 Rslib 的 WebAssembly 处理模型匹配。 + +`wasm-bindgen` 产物中,`.wasm` 二进制与 JavaScript glue 文件之间存在相对依赖:glue 会 import `.wasm`,`.wasm` 又会 import 回 glue。某个 Rslib 配置能否保持这种关系,取决于 `mode` 与 `bundle` 的组合: + +| bundle | mode | 是否支持 | 说明 | +| ------- | ---------- | -------- | ---------------------------------------------------------- | +| `true` | `compile` | 支持 | Rspack 解析 glue 依赖,并将所有内容打包进 JavaScript 产物。 | +| `false` | `compile` | 支持 | Rspack 解析 glue 依赖,并按文件生成加载运行时。 | +| `false` | `preserve` | 支持 | `.wasm` 与 glue 文件保持源码相对布局,相对 specifier 仍然有效。 | +| `true` | `preserve` | 不支持 | content hash 的 asset 布局会将 `.wasm` 移离 glue 文件,破坏相对 specifier。 | + +在 `compile` 模式下,Rspack 会在构建时解析 `.wasm` 模块及其 glue 依赖,因此无论 bundle 还是 bundleless 都能正常工作。 + +如果你希望在产物中保留原始的 `wasm-bindgen` 文件、且不生成加载运行时,可以使用 bundleless preserve 模式: + +```ts title="rslib.config.ts" +export default { + lib: [ + { + format: 'esm', + bundle: false, + wasm: { + mode: 'preserve', + }, + }, + ], +}; +``` + +::: warning + +不要对 `wasm-bindgen` 产物使用 [bundle 模式](#bundle-模式) 的 preserve。content hash 的 asset 布局会将 `.wasm` 文件移离 glue 文件,破坏生成产物中的相对 module specifier。请改用 `compile` 模式或 bundleless `preserve` 模式。 + +::: + +## TypeScript 类型声明 + +TypeScript 默认不提供 `.wasm` 文件的模块类型声明。你可以在 `.wasm` 文件旁添加 `.d.wasm.ts` 扩展名的声明文件,这需要在 `tsconfig.json` 中开启 [`allowArbitraryExtensions`](https://www.typescriptlang.org/tsconfig/allowArbitraryExtensions.html): + +```ts title="src/add.d.wasm.ts" +export function add(a: number, b: number): number; +``` + +## 使用限制 + +- 直接 WebAssembly ESM Integration import 仅支持 ESM 产物;`cjs`、`umd`、`iife` 和 `mf` 格式不支持直接 `.wasm` import/export。当为非 ESM 格式设置 `mode` 时,该配置会被忽略,并回退到 compile 行为。 From 04a074894805915a59cbe9d61f10b2a417feec90 Mon Sep 17 00:00:00 2001 From: Elecmonkey Date: Mon, 29 Jun 2026 00:28:47 +0800 Subject: [PATCH 04/10] chore(wasm): add inlines to cspell dictionary --- scripts/dictionary.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/dictionary.txt b/scripts/dictionary.txt index cd80d9e6d..fb2570323 100644 --- a/scripts/dictionary.txt +++ b/scripts/dictionary.txt @@ -50,6 +50,7 @@ idents iife imagex importee +inlines jfif jiti jscpuprofile From 864c63c6e06e42a34a3d9413f0058e1d022c72b0 Mon Sep 17 00:00:00 2001 From: Elecmonkey Date: Mon, 29 Jun 2026 00:54:28 +0800 Subject: [PATCH 05/10] fix(wasm): hold compiler context in a private field to avoid config serialization error --- packages/core/src/wasm/emit.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/src/wasm/emit.ts b/packages/core/src/wasm/emit.ts index cc7f423bd..36ddd5bc1 100644 --- a/packages/core/src/wasm/emit.ts +++ b/packages/core/src/wasm/emit.ts @@ -13,7 +13,7 @@ import { const EMIT_PLUGIN_NAME = 'RslibWasmPreserveEmitPlugin'; export class WasmPreserveEmitPlugin { - readonly compilerContext: WasmCompilerContext; + readonly #compilerContext: WasmCompilerContext; readonly outBase: string | null; readonly preserveToSource: boolean; @@ -22,16 +22,16 @@ export class WasmPreserveEmitPlugin { outBase: string | null; preserveToSource: boolean; }) { - this.compilerContext = options.compilerContext; + this.#compilerContext = options.compilerContext; this.outBase = options.outBase; this.preserveToSource = options.preserveToSource; } apply(compiler: Rspack.Compiler): void { - this.compilerContext.compiler = compiler; + this.#compilerContext.compiler = compiler; const { sources, Compilation } = compiler.webpack; compiler.hooks.thisCompilation.tap(EMIT_PLUGIN_NAME, (compilation) => { - this.compilerContext.compilation = compilation; + this.#compilerContext.compilation = compilation; compilation.hooks.processAssets.tapPromise( { name: EMIT_PLUGIN_NAME, From 10b4c95841ad492b1e6adff808c0f95e98eb8e51 Mon Sep 17 00:00:00 2001 From: Elecmonkey Date: Mon, 29 Jun 2026 11:35:04 +0800 Subject: [PATCH 06/10] fix(wasm): avoid bundleless externalizing compiled wasm --- packages/core/src/config.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 6290c607a..7046ddaf5 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -1377,6 +1377,7 @@ const composeBundlelessExternalConfig = ( bundle: boolean, outBase: string | null, preExternals: Rspack.ExternalItem[], + skipWasmExternal: boolean, ): { config: EnvironmentConfig; resolvedJsRedirect?: DeepRequired; @@ -1425,6 +1426,10 @@ const composeBundlelessExternalConfig = ( callback(); return; } + if (skipWasmExternal && /\.wasm(?:[?#]|$)/.test(request)) { + callback(); + return; + } const { issuer } = contextInfo; const originExtension = extname(request); @@ -1841,7 +1846,7 @@ async function composeLibRsbuildConfig( format, wasmConfig: config.wasm, }); - const excludeWasmEntry = resolvedWasm.mode !== 'compile'; + const excludeWasmEntry = !bundle; const { entryConfig, outBase } = await composeEntryConfig( config.source?.entry, @@ -1876,6 +1881,7 @@ async function composeLibRsbuildConfig( bundle, outBase, wasmCompose.bundlelessExternal ? [wasmCompose.bundlelessExternal] : [], + resolvedWasm.mode === 'compile', ); const syntaxConfig = composeSyntaxConfig(target, config?.syntax); const autoExternalConfig = composeAutoExternalConfig({ From f1e5fb5c78ce3c6658733661985451b20e2565ef Mon Sep 17 00:00:00 2001 From: Elecmonkey Date: Mon, 29 Jun 2026 11:38:49 +0800 Subject: [PATCH 07/10] test(wasm): cover source phase import modes --- pnpm-lock.yaml | 8 ++ tests/integration/wasm/index.test.ts | 84 ++++++++++++++++++ .../wasm/source-compile-bundle/package.json | 6 ++ .../source-compile-bundle/rslib.config.ts | 14 +++ .../wasm/source-compile-bundle/src/add.wasm | Bin 0 -> 41 bytes .../wasm/source-compile-bundle/src/index.js | 1 + .../wasm/source-compile-bundle/src/utils.js | 3 + .../source-compile-bundleless/package.json | 6 ++ .../source-compile-bundleless/rslib.config.ts | 14 +++ .../source-compile-bundleless/src/add.wasm | Bin 0 -> 41 bytes .../source-compile-bundleless/src/index.js | 1 + .../source-compile-bundleless/src/utils.js | 3 + .../wasm/source-preserve-bundle/package.json | 6 ++ .../source-preserve-bundle/rslib.config.ts | 14 +++ .../wasm/source-preserve-bundle/src/add.wasm | Bin 0 -> 41 bytes .../wasm/source-preserve-bundle/src/index.js | 1 + .../wasm/source-preserve-bundle/src/utils.js | 3 + .../source-preserve-bundleless/package.json | 6 ++ .../rslib.config.ts | 14 +++ .../source-preserve-bundleless/src/add.wasm | Bin 0 -> 41 bytes .../source-preserve-bundleless/src/index.js | 1 + .../source-preserve-bundleless/src/utils.js | 3 + 22 files changed, 188 insertions(+) create mode 100644 tests/integration/wasm/source-compile-bundle/package.json create mode 100644 tests/integration/wasm/source-compile-bundle/rslib.config.ts create mode 100644 tests/integration/wasm/source-compile-bundle/src/add.wasm create mode 100644 tests/integration/wasm/source-compile-bundle/src/index.js create mode 100644 tests/integration/wasm/source-compile-bundle/src/utils.js create mode 100644 tests/integration/wasm/source-compile-bundleless/package.json create mode 100644 tests/integration/wasm/source-compile-bundleless/rslib.config.ts create mode 100644 tests/integration/wasm/source-compile-bundleless/src/add.wasm create mode 100644 tests/integration/wasm/source-compile-bundleless/src/index.js create mode 100644 tests/integration/wasm/source-compile-bundleless/src/utils.js create mode 100644 tests/integration/wasm/source-preserve-bundle/package.json create mode 100644 tests/integration/wasm/source-preserve-bundle/rslib.config.ts create mode 100644 tests/integration/wasm/source-preserve-bundle/src/add.wasm create mode 100644 tests/integration/wasm/source-preserve-bundle/src/index.js create mode 100644 tests/integration/wasm/source-preserve-bundle/src/utils.js create mode 100644 tests/integration/wasm/source-preserve-bundleless/package.json create mode 100644 tests/integration/wasm/source-preserve-bundleless/rslib.config.ts create mode 100644 tests/integration/wasm/source-preserve-bundleless/src/add.wasm create mode 100644 tests/integration/wasm/source-preserve-bundleless/src/index.js create mode 100644 tests/integration/wasm/source-preserve-bundleless/src/utils.js diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2adb8b6da..0fcc7442c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1262,6 +1262,14 @@ importers: tests/integration/wasm/preserve-bundleless: {} + tests/integration/wasm/source-compile-bundle: {} + + tests/integration/wasm/source-compile-bundleless: {} + + tests/integration/wasm/source-preserve-bundle: {} + + tests/integration/wasm/source-preserve-bundleless: {} + tests/integration/worker: {} tests/scripts: {} diff --git a/tests/integration/wasm/index.test.ts b/tests/integration/wasm/index.test.ts index b2ee38ea3..ff0404971 100644 --- a/tests/integration/wasm/index.test.ts +++ b/tests/integration/wasm/index.test.ts @@ -39,6 +39,50 @@ test('wasm: compile + bundleless', async () => { expect(utilsJs).toContain('useAdd'); const distDir = join(fixturePath, 'dist/esm'); + expect(existsSync(join(distDir, 'add.js'))).toBe(false); + const wasmFiles = walk(distDir).filter((p) => p.endsWith('.wasm')); + expect(wasmFiles.length).toBe(1); +}); + +test('wasm source phase: compile + bundle', async () => { + const fixturePath = join(__dirname, 'source-compile-bundle'); + const { contents } = await buildAndGetResults({ fixturePath }); + + const { content: indexJs } = queryContent(contents.esm!, /index\.js/); + // Bundle mode collapses utils.js into index.js. Compile mode lowers source + // phase imports into Rspack's wasm runtime, so no native source import leaks. + expect(indexJs).toContain('createAdd'); + expect(indexJs).toContain('WebAssembly.Instance'); + expect(indexJs).not.toContain('import source'); + expect(indexJs).not.toMatch(/from\s*["'][^"']+\.wasm["']/); + + const distDir = join(fixturePath, 'dist/esm'); + const jsFiles = walk(distDir).filter((p) => p.endsWith('.js')); + expect(jsFiles.length).toBe(1); + + const wasmFiles = walk(distDir).filter((p) => p.endsWith('.wasm')); + expect(wasmFiles.length).toBe(1); +}); + +test('wasm source phase: compile + bundleless', async () => { + const fixturePath = join(__dirname, 'source-compile-bundleless'); + const { contents } = await buildAndGetResults({ fixturePath }); + + const { content: indexJs } = queryContent(contents.esm!, /index\.js/); + const { content: utilsJs } = queryContent(contents.esm!, /utils\.js/); + // Bundleless mode keeps the index -> utils boundary. + expect(indexJs).toMatch(/from\s*["']\.\/utils\.js["']/); + expect(indexJs).not.toMatch(/\.wasm/); + + // Compile mode should not preserve native source phase imports in the public + // output; the wasm is lowered into a JS module next to utils.js. + expect(utilsJs).toContain('createAdd'); + expect(utilsJs).toContain('WebAssembly.Instance'); + expect(utilsJs).not.toContain('import source'); + expect(utilsJs).not.toMatch(/from\s*["'][^"']+\.wasm["']/); + + const distDir = join(fixturePath, 'dist/esm'); + expect(existsSync(join(distDir, 'add.js'))).toBe(false); const wasmFiles = walk(distDir).filter((p) => p.endsWith('.wasm')); expect(wasmFiles.length).toBe(1); }); @@ -60,6 +104,26 @@ test('wasm: preserve + bundle', async () => { expect(emitted[0]).not.toMatch(/[/\\]add\.wasm$/); }); +test('wasm source phase: preserve + bundle', async () => { + const fixturePath = join(__dirname, 'source-preserve-bundle'); + const { contents } = await buildAndGetResults({ fixturePath }); + + const { content: indexJs } = queryContent(contents.esm!, /index\.js/); + // Bundle mode collapses utils.js into index.js, and preserve mode keeps the + // native source phase import pointing at the emitted wasm asset. + expect(indexJs).toContain('createAdd'); + expect(indexJs).toMatch( + /import\s+source(?:\s+\w+)?\s+from\s*["']\.\/[^"']+\.wasm["']/, + ); + + const distDir = join(fixturePath, 'dist/esm'); + const jsFiles = walk(distDir).filter((p) => p.endsWith('.js')); + expect(jsFiles.length).toBe(1); + const emitted = walk(distDir).filter((p) => p.endsWith('.wasm')); + expect(emitted.length).toBe(1); + expect(emitted[0]).not.toMatch(/[/\\]add\.wasm$/); +}); + test('wasm: preserve + bundleless', async () => { const fixturePath = join(__dirname, 'preserve-bundleless'); const { contents } = await buildAndGetResults({ fixturePath }); @@ -77,3 +141,23 @@ test('wasm: preserve + bundleless', async () => { const distDir = join(fixturePath, 'dist/esm'); expect(existsSync(join(distDir, 'add.wasm'))).toBe(true); }); + +test('wasm source phase: preserve + bundleless', async () => { + const fixturePath = join(__dirname, 'source-preserve-bundleless'); + const { contents } = await buildAndGetResults({ fixturePath }); + + const { content: indexJs } = queryContent(contents.esm!, /index\.js/); + const { content: utilsJs } = queryContent(contents.esm!, /utils\.js/); + + // Bundleless mode keeps the index -> utils boundary. + expect(indexJs).toMatch(/from\s*["']\.\/utils\.js["']/); + expect(indexJs).not.toMatch(/\.wasm/); + + // Preserve mode keeps the native source phase import and the source-relative + // wasm file layout. + expect(utilsJs).toContain('createAdd'); + expect(utilsJs).toMatch(/import source \w+ from "\.\/add\.wasm"/); + + const distDir = join(fixturePath, 'dist/esm'); + expect(existsSync(join(distDir, 'add.wasm'))).toBe(true); +}); diff --git a/tests/integration/wasm/source-compile-bundle/package.json b/tests/integration/wasm/source-compile-bundle/package.json new file mode 100644 index 000000000..e7b772afa --- /dev/null +++ b/tests/integration/wasm/source-compile-bundle/package.json @@ -0,0 +1,6 @@ +{ + "name": "wasm-source-compile-bundle-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/wasm/source-compile-bundle/rslib.config.ts b/tests/integration/wasm/source-compile-bundle/rslib.config.ts new file mode 100644 index 000000000..8e103a73a --- /dev/null +++ b/tests/integration/wasm/source-compile-bundle/rslib.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from '@rslib/core'; +import { generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [ + generateBundleEsmConfig({ + bundle: true, + wasm: { mode: 'compile' }, + }), + ], + output: { + target: 'web', + }, +}); diff --git a/tests/integration/wasm/source-compile-bundle/src/add.wasm b/tests/integration/wasm/source-compile-bundle/src/add.wasm new file mode 100644 index 0000000000000000000000000000000000000000..357f72da7a0db8add83699082fd51d46bf3352fb GIT binary patch literal 41 wcmZQbEY4+QU|?WmXG~zKuV<`hW@2PuXJ=$iOi5v2;NoOtXHZ~JV9eqM0DJxgJ^%m! literal 0 HcmV?d00001 diff --git a/tests/integration/wasm/source-compile-bundle/src/index.js b/tests/integration/wasm/source-compile-bundle/src/index.js new file mode 100644 index 000000000..4b20ccb0c --- /dev/null +++ b/tests/integration/wasm/source-compile-bundle/src/index.js @@ -0,0 +1 @@ +export { createAdd } from './utils.js'; diff --git a/tests/integration/wasm/source-compile-bundle/src/utils.js b/tests/integration/wasm/source-compile-bundle/src/utils.js new file mode 100644 index 000000000..139f381f6 --- /dev/null +++ b/tests/integration/wasm/source-compile-bundle/src/utils.js @@ -0,0 +1,3 @@ +import source addModule from './add.wasm'; + +export const createAdd = () => new WebAssembly.Instance(addModule).exports.add; diff --git a/tests/integration/wasm/source-compile-bundleless/package.json b/tests/integration/wasm/source-compile-bundleless/package.json new file mode 100644 index 000000000..4f38efd00 --- /dev/null +++ b/tests/integration/wasm/source-compile-bundleless/package.json @@ -0,0 +1,6 @@ +{ + "name": "wasm-source-compile-bundleless-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/wasm/source-compile-bundleless/rslib.config.ts b/tests/integration/wasm/source-compile-bundleless/rslib.config.ts new file mode 100644 index 000000000..56ecdfab4 --- /dev/null +++ b/tests/integration/wasm/source-compile-bundleless/rslib.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from '@rslib/core'; +import { generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [ + generateBundleEsmConfig({ + bundle: false, + wasm: { mode: 'compile' }, + }), + ], + output: { + target: 'web', + }, +}); diff --git a/tests/integration/wasm/source-compile-bundleless/src/add.wasm b/tests/integration/wasm/source-compile-bundleless/src/add.wasm new file mode 100644 index 0000000000000000000000000000000000000000..357f72da7a0db8add83699082fd51d46bf3352fb GIT binary patch literal 41 wcmZQbEY4+QU|?WmXG~zKuV<`hW@2PuXJ=$iOi5v2;NoOtXHZ~JV9eqM0DJxgJ^%m! literal 0 HcmV?d00001 diff --git a/tests/integration/wasm/source-compile-bundleless/src/index.js b/tests/integration/wasm/source-compile-bundleless/src/index.js new file mode 100644 index 000000000..4b20ccb0c --- /dev/null +++ b/tests/integration/wasm/source-compile-bundleless/src/index.js @@ -0,0 +1 @@ +export { createAdd } from './utils.js'; diff --git a/tests/integration/wasm/source-compile-bundleless/src/utils.js b/tests/integration/wasm/source-compile-bundleless/src/utils.js new file mode 100644 index 000000000..139f381f6 --- /dev/null +++ b/tests/integration/wasm/source-compile-bundleless/src/utils.js @@ -0,0 +1,3 @@ +import source addModule from './add.wasm'; + +export const createAdd = () => new WebAssembly.Instance(addModule).exports.add; diff --git a/tests/integration/wasm/source-preserve-bundle/package.json b/tests/integration/wasm/source-preserve-bundle/package.json new file mode 100644 index 000000000..6f9016bd1 --- /dev/null +++ b/tests/integration/wasm/source-preserve-bundle/package.json @@ -0,0 +1,6 @@ +{ + "name": "wasm-source-preserve-bundle-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/wasm/source-preserve-bundle/rslib.config.ts b/tests/integration/wasm/source-preserve-bundle/rslib.config.ts new file mode 100644 index 000000000..9f8f82f46 --- /dev/null +++ b/tests/integration/wasm/source-preserve-bundle/rslib.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from '@rslib/core'; +import { generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [ + generateBundleEsmConfig({ + bundle: true, + wasm: { mode: 'preserve' }, + }), + ], + output: { + target: 'web', + }, +}); diff --git a/tests/integration/wasm/source-preserve-bundle/src/add.wasm b/tests/integration/wasm/source-preserve-bundle/src/add.wasm new file mode 100644 index 0000000000000000000000000000000000000000..357f72da7a0db8add83699082fd51d46bf3352fb GIT binary patch literal 41 wcmZQbEY4+QU|?WmXG~zKuV<`hW@2PuXJ=$iOi5v2;NoOtXHZ~JV9eqM0DJxgJ^%m! literal 0 HcmV?d00001 diff --git a/tests/integration/wasm/source-preserve-bundle/src/index.js b/tests/integration/wasm/source-preserve-bundle/src/index.js new file mode 100644 index 000000000..4b20ccb0c --- /dev/null +++ b/tests/integration/wasm/source-preserve-bundle/src/index.js @@ -0,0 +1 @@ +export { createAdd } from './utils.js'; diff --git a/tests/integration/wasm/source-preserve-bundle/src/utils.js b/tests/integration/wasm/source-preserve-bundle/src/utils.js new file mode 100644 index 000000000..139f381f6 --- /dev/null +++ b/tests/integration/wasm/source-preserve-bundle/src/utils.js @@ -0,0 +1,3 @@ +import source addModule from './add.wasm'; + +export const createAdd = () => new WebAssembly.Instance(addModule).exports.add; diff --git a/tests/integration/wasm/source-preserve-bundleless/package.json b/tests/integration/wasm/source-preserve-bundleless/package.json new file mode 100644 index 000000000..a43c1c389 --- /dev/null +++ b/tests/integration/wasm/source-preserve-bundleless/package.json @@ -0,0 +1,6 @@ +{ + "name": "wasm-source-preserve-bundleless-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/wasm/source-preserve-bundleless/rslib.config.ts b/tests/integration/wasm/source-preserve-bundleless/rslib.config.ts new file mode 100644 index 000000000..b6f9c708d --- /dev/null +++ b/tests/integration/wasm/source-preserve-bundleless/rslib.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from '@rslib/core'; +import { generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [ + generateBundleEsmConfig({ + bundle: false, + wasm: { mode: 'preserve' }, + }), + ], + output: { + target: 'web', + }, +}); diff --git a/tests/integration/wasm/source-preserve-bundleless/src/add.wasm b/tests/integration/wasm/source-preserve-bundleless/src/add.wasm new file mode 100644 index 0000000000000000000000000000000000000000..357f72da7a0db8add83699082fd51d46bf3352fb GIT binary patch literal 41 wcmZQbEY4+QU|?WmXG~zKuV<`hW@2PuXJ=$iOi5v2;NoOtXHZ~JV9eqM0DJxgJ^%m! literal 0 HcmV?d00001 diff --git a/tests/integration/wasm/source-preserve-bundleless/src/index.js b/tests/integration/wasm/source-preserve-bundleless/src/index.js new file mode 100644 index 000000000..4b20ccb0c --- /dev/null +++ b/tests/integration/wasm/source-preserve-bundleless/src/index.js @@ -0,0 +1 @@ +export { createAdd } from './utils.js'; diff --git a/tests/integration/wasm/source-preserve-bundleless/src/utils.js b/tests/integration/wasm/source-preserve-bundleless/src/utils.js new file mode 100644 index 000000000..139f381f6 --- /dev/null +++ b/tests/integration/wasm/source-preserve-bundleless/src/utils.js @@ -0,0 +1,3 @@ +import source addModule from './add.wasm'; + +export const createAdd = () => new WebAssembly.Instance(addModule).exports.add; From 5df4cacf8fcdf3db07e7b2ac2099f587e71f2432 Mon Sep 17 00:00:00 2001 From: Elecmonkey Date: Mon, 29 Jun 2026 11:42:16 +0800 Subject: [PATCH 08/10] test(wasm): rename static import fixtures --- tests/integration/wasm/index.test.ts | 32 +++++++++--------- .../package.json | 0 .../rslib.config.ts | 0 .../src/add.wasm | Bin .../src/index.js | 0 .../src/utils.js | 0 .../package.json | 0 .../rslib.config.ts | 0 .../src/add.wasm | Bin .../src/index.js | 0 .../src/utils.js | 0 .../package.json | 0 .../rslib.config.ts | 0 .../src/add.wasm | Bin .../src/index.js | 0 .../src/utils.js | 0 .../package.json | 0 .../rslib.config.ts | 0 .../src/add.wasm | Bin .../src/index.js | 0 .../src/utils.js | 0 .../package.json | 0 .../rslib.config.ts | 0 .../src/add.wasm | Bin .../src/index.js | 0 .../src/utils.js | 0 .../package.json | 0 .../rslib.config.ts | 0 .../src/add.wasm | Bin .../src/index.js | 0 .../src/utils.js | 0 .../package.json | 0 .../rslib.config.ts | 0 .../src/add.wasm | Bin .../src/index.js | 0 .../src/utils.js | 0 .../package.json | 0 .../rslib.config.ts | 0 .../src/add.wasm | Bin .../src/index.js | 0 .../src/utils.js | 0 41 files changed, 16 insertions(+), 16 deletions(-) rename tests/integration/wasm/{compile-bundle => static-compile-bundle}/package.json (100%) rename tests/integration/wasm/{compile-bundle => static-compile-bundle}/rslib.config.ts (100%) rename tests/integration/wasm/{compile-bundle => static-compile-bundle}/src/add.wasm (100%) rename tests/integration/wasm/{compile-bundle => static-compile-bundle}/src/index.js (100%) rename tests/integration/wasm/{compile-bundle => static-compile-bundle}/src/utils.js (100%) rename tests/integration/wasm/{compile-bundleless => static-compile-bundleless}/package.json (100%) rename tests/integration/wasm/{compile-bundleless => static-compile-bundleless}/rslib.config.ts (100%) rename tests/integration/wasm/{compile-bundleless => static-compile-bundleless}/src/add.wasm (100%) rename tests/integration/wasm/{compile-bundleless => static-compile-bundleless}/src/index.js (100%) rename tests/integration/wasm/{compile-bundleless => static-compile-bundleless}/src/utils.js (100%) rename tests/integration/wasm/{preserve-bundle => static-preserve-bundle}/package.json (100%) rename tests/integration/wasm/{preserve-bundle => static-preserve-bundle}/rslib.config.ts (100%) rename tests/integration/wasm/{preserve-bundle => static-preserve-bundle}/src/add.wasm (100%) rename tests/integration/wasm/{preserve-bundle => static-preserve-bundle}/src/index.js (100%) rename tests/integration/wasm/{preserve-bundle => static-preserve-bundle}/src/utils.js (100%) rename tests/integration/wasm/{preserve-bundleless => static-preserve-bundleless}/package.json (100%) rename tests/integration/wasm/{preserve-bundleless => static-preserve-bundleless}/rslib.config.ts (100%) rename tests/integration/wasm/{preserve-bundleless => static-preserve-bundleless}/src/add.wasm (100%) rename tests/integration/wasm/{preserve-bundleless => static-preserve-bundleless}/src/index.js (100%) rename tests/integration/wasm/{preserve-bundleless => static-preserve-bundleless}/src/utils.js (100%) rename tests/integration/wasm/{source-compile-bundle => static-source-compile-bundle}/package.json (100%) rename tests/integration/wasm/{source-compile-bundle => static-source-compile-bundle}/rslib.config.ts (100%) rename tests/integration/wasm/{source-compile-bundle => static-source-compile-bundle}/src/add.wasm (100%) rename tests/integration/wasm/{source-compile-bundle => static-source-compile-bundle}/src/index.js (100%) rename tests/integration/wasm/{source-compile-bundle => static-source-compile-bundle}/src/utils.js (100%) rename tests/integration/wasm/{source-compile-bundleless => static-source-compile-bundleless}/package.json (100%) rename tests/integration/wasm/{source-compile-bundleless => static-source-compile-bundleless}/rslib.config.ts (100%) rename tests/integration/wasm/{source-compile-bundleless => static-source-compile-bundleless}/src/add.wasm (100%) rename tests/integration/wasm/{source-compile-bundleless => static-source-compile-bundleless}/src/index.js (100%) rename tests/integration/wasm/{source-compile-bundleless => static-source-compile-bundleless}/src/utils.js (100%) rename tests/integration/wasm/{source-preserve-bundle => static-source-preserve-bundle}/package.json (100%) rename tests/integration/wasm/{source-preserve-bundle => static-source-preserve-bundle}/rslib.config.ts (100%) rename tests/integration/wasm/{source-preserve-bundle => static-source-preserve-bundle}/src/add.wasm (100%) rename tests/integration/wasm/{source-preserve-bundle => static-source-preserve-bundle}/src/index.js (100%) rename tests/integration/wasm/{source-preserve-bundle => static-source-preserve-bundle}/src/utils.js (100%) rename tests/integration/wasm/{source-preserve-bundleless => static-source-preserve-bundleless}/package.json (100%) rename tests/integration/wasm/{source-preserve-bundleless => static-source-preserve-bundleless}/rslib.config.ts (100%) rename tests/integration/wasm/{source-preserve-bundleless => static-source-preserve-bundleless}/src/add.wasm (100%) rename tests/integration/wasm/{source-preserve-bundleless => static-source-preserve-bundleless}/src/index.js (100%) rename tests/integration/wasm/{source-preserve-bundleless => static-source-preserve-bundleless}/src/utils.js (100%) diff --git a/tests/integration/wasm/index.test.ts b/tests/integration/wasm/index.test.ts index ff0404971..49d3b088c 100644 --- a/tests/integration/wasm/index.test.ts +++ b/tests/integration/wasm/index.test.ts @@ -9,8 +9,8 @@ const walk = (dir: string): string[] => return entry.isDirectory() ? walk(p) : [p]; }); -test('wasm: compile + bundle', async () => { - const fixturePath = join(__dirname, 'compile-bundle'); +test('wasm static: compile + bundle', async () => { + const fixturePath = join(__dirname, 'static-compile-bundle'); const { contents } = await buildAndGetResults({ fixturePath }); const { content: indexJs } = queryContent(contents.esm!, /index\.js/); @@ -28,8 +28,8 @@ test('wasm: compile + bundle', async () => { expect(wasmFiles.length).toBe(1); }); -test('wasm: compile + bundleless', async () => { - const fixturePath = join(__dirname, 'compile-bundleless'); +test('wasm static: compile + bundleless', async () => { + const fixturePath = join(__dirname, 'static-compile-bundleless'); const { contents } = await buildAndGetResults({ fixturePath }); const { content: utilsJs } = queryContent(contents.esm!, /utils\.js/); @@ -44,8 +44,8 @@ test('wasm: compile + bundleless', async () => { expect(wasmFiles.length).toBe(1); }); -test('wasm source phase: compile + bundle', async () => { - const fixturePath = join(__dirname, 'source-compile-bundle'); +test('wasm static source phase: compile + bundle', async () => { + const fixturePath = join(__dirname, 'static-source-compile-bundle'); const { contents } = await buildAndGetResults({ fixturePath }); const { content: indexJs } = queryContent(contents.esm!, /index\.js/); @@ -64,8 +64,8 @@ test('wasm source phase: compile + bundle', async () => { expect(wasmFiles.length).toBe(1); }); -test('wasm source phase: compile + bundleless', async () => { - const fixturePath = join(__dirname, 'source-compile-bundleless'); +test('wasm static source phase: compile + bundleless', async () => { + const fixturePath = join(__dirname, 'static-source-compile-bundleless'); const { contents } = await buildAndGetResults({ fixturePath }); const { content: indexJs } = queryContent(contents.esm!, /index\.js/); @@ -87,8 +87,8 @@ test('wasm source phase: compile + bundleless', async () => { expect(wasmFiles.length).toBe(1); }); -test('wasm: preserve + bundle', async () => { - const fixturePath = join(__dirname, 'preserve-bundle'); +test('wasm static: preserve + bundle', async () => { + const fixturePath = join(__dirname, 'static-preserve-bundle'); const { contents } = await buildAndGetResults({ fixturePath }); const { content: indexJs } = queryContent(contents.esm!, /index\.js/); @@ -104,8 +104,8 @@ test('wasm: preserve + bundle', async () => { expect(emitted[0]).not.toMatch(/[/\\]add\.wasm$/); }); -test('wasm source phase: preserve + bundle', async () => { - const fixturePath = join(__dirname, 'source-preserve-bundle'); +test('wasm static source phase: preserve + bundle', async () => { + const fixturePath = join(__dirname, 'static-source-preserve-bundle'); const { contents } = await buildAndGetResults({ fixturePath }); const { content: indexJs } = queryContent(contents.esm!, /index\.js/); @@ -124,8 +124,8 @@ test('wasm source phase: preserve + bundle', async () => { expect(emitted[0]).not.toMatch(/[/\\]add\.wasm$/); }); -test('wasm: preserve + bundleless', async () => { - const fixturePath = join(__dirname, 'preserve-bundleless'); +test('wasm static: preserve + bundleless', async () => { + const fixturePath = join(__dirname, 'static-preserve-bundleless'); const { contents } = await buildAndGetResults({ fixturePath }); const { content: indexJs } = queryContent(contents.esm!, /index\.js/); @@ -142,8 +142,8 @@ test('wasm: preserve + bundleless', async () => { expect(existsSync(join(distDir, 'add.wasm'))).toBe(true); }); -test('wasm source phase: preserve + bundleless', async () => { - const fixturePath = join(__dirname, 'source-preserve-bundleless'); +test('wasm static source phase: preserve + bundleless', async () => { + const fixturePath = join(__dirname, 'static-source-preserve-bundleless'); const { contents } = await buildAndGetResults({ fixturePath }); const { content: indexJs } = queryContent(contents.esm!, /index\.js/); diff --git a/tests/integration/wasm/compile-bundle/package.json b/tests/integration/wasm/static-compile-bundle/package.json similarity index 100% rename from tests/integration/wasm/compile-bundle/package.json rename to tests/integration/wasm/static-compile-bundle/package.json diff --git a/tests/integration/wasm/compile-bundle/rslib.config.ts b/tests/integration/wasm/static-compile-bundle/rslib.config.ts similarity index 100% rename from tests/integration/wasm/compile-bundle/rslib.config.ts rename to tests/integration/wasm/static-compile-bundle/rslib.config.ts diff --git a/tests/integration/wasm/compile-bundle/src/add.wasm b/tests/integration/wasm/static-compile-bundle/src/add.wasm similarity index 100% rename from tests/integration/wasm/compile-bundle/src/add.wasm rename to tests/integration/wasm/static-compile-bundle/src/add.wasm diff --git a/tests/integration/wasm/compile-bundle/src/index.js b/tests/integration/wasm/static-compile-bundle/src/index.js similarity index 100% rename from tests/integration/wasm/compile-bundle/src/index.js rename to tests/integration/wasm/static-compile-bundle/src/index.js diff --git a/tests/integration/wasm/compile-bundle/src/utils.js b/tests/integration/wasm/static-compile-bundle/src/utils.js similarity index 100% rename from tests/integration/wasm/compile-bundle/src/utils.js rename to tests/integration/wasm/static-compile-bundle/src/utils.js diff --git a/tests/integration/wasm/compile-bundleless/package.json b/tests/integration/wasm/static-compile-bundleless/package.json similarity index 100% rename from tests/integration/wasm/compile-bundleless/package.json rename to tests/integration/wasm/static-compile-bundleless/package.json diff --git a/tests/integration/wasm/compile-bundleless/rslib.config.ts b/tests/integration/wasm/static-compile-bundleless/rslib.config.ts similarity index 100% rename from tests/integration/wasm/compile-bundleless/rslib.config.ts rename to tests/integration/wasm/static-compile-bundleless/rslib.config.ts diff --git a/tests/integration/wasm/compile-bundleless/src/add.wasm b/tests/integration/wasm/static-compile-bundleless/src/add.wasm similarity index 100% rename from tests/integration/wasm/compile-bundleless/src/add.wasm rename to tests/integration/wasm/static-compile-bundleless/src/add.wasm diff --git a/tests/integration/wasm/compile-bundleless/src/index.js b/tests/integration/wasm/static-compile-bundleless/src/index.js similarity index 100% rename from tests/integration/wasm/compile-bundleless/src/index.js rename to tests/integration/wasm/static-compile-bundleless/src/index.js diff --git a/tests/integration/wasm/compile-bundleless/src/utils.js b/tests/integration/wasm/static-compile-bundleless/src/utils.js similarity index 100% rename from tests/integration/wasm/compile-bundleless/src/utils.js rename to tests/integration/wasm/static-compile-bundleless/src/utils.js diff --git a/tests/integration/wasm/preserve-bundle/package.json b/tests/integration/wasm/static-preserve-bundle/package.json similarity index 100% rename from tests/integration/wasm/preserve-bundle/package.json rename to tests/integration/wasm/static-preserve-bundle/package.json diff --git a/tests/integration/wasm/preserve-bundle/rslib.config.ts b/tests/integration/wasm/static-preserve-bundle/rslib.config.ts similarity index 100% rename from tests/integration/wasm/preserve-bundle/rslib.config.ts rename to tests/integration/wasm/static-preserve-bundle/rslib.config.ts diff --git a/tests/integration/wasm/preserve-bundle/src/add.wasm b/tests/integration/wasm/static-preserve-bundle/src/add.wasm similarity index 100% rename from tests/integration/wasm/preserve-bundle/src/add.wasm rename to tests/integration/wasm/static-preserve-bundle/src/add.wasm diff --git a/tests/integration/wasm/preserve-bundle/src/index.js b/tests/integration/wasm/static-preserve-bundle/src/index.js similarity index 100% rename from tests/integration/wasm/preserve-bundle/src/index.js rename to tests/integration/wasm/static-preserve-bundle/src/index.js diff --git a/tests/integration/wasm/preserve-bundle/src/utils.js b/tests/integration/wasm/static-preserve-bundle/src/utils.js similarity index 100% rename from tests/integration/wasm/preserve-bundle/src/utils.js rename to tests/integration/wasm/static-preserve-bundle/src/utils.js diff --git a/tests/integration/wasm/preserve-bundleless/package.json b/tests/integration/wasm/static-preserve-bundleless/package.json similarity index 100% rename from tests/integration/wasm/preserve-bundleless/package.json rename to tests/integration/wasm/static-preserve-bundleless/package.json diff --git a/tests/integration/wasm/preserve-bundleless/rslib.config.ts b/tests/integration/wasm/static-preserve-bundleless/rslib.config.ts similarity index 100% rename from tests/integration/wasm/preserve-bundleless/rslib.config.ts rename to tests/integration/wasm/static-preserve-bundleless/rslib.config.ts diff --git a/tests/integration/wasm/preserve-bundleless/src/add.wasm b/tests/integration/wasm/static-preserve-bundleless/src/add.wasm similarity index 100% rename from tests/integration/wasm/preserve-bundleless/src/add.wasm rename to tests/integration/wasm/static-preserve-bundleless/src/add.wasm diff --git a/tests/integration/wasm/preserve-bundleless/src/index.js b/tests/integration/wasm/static-preserve-bundleless/src/index.js similarity index 100% rename from tests/integration/wasm/preserve-bundleless/src/index.js rename to tests/integration/wasm/static-preserve-bundleless/src/index.js diff --git a/tests/integration/wasm/preserve-bundleless/src/utils.js b/tests/integration/wasm/static-preserve-bundleless/src/utils.js similarity index 100% rename from tests/integration/wasm/preserve-bundleless/src/utils.js rename to tests/integration/wasm/static-preserve-bundleless/src/utils.js diff --git a/tests/integration/wasm/source-compile-bundle/package.json b/tests/integration/wasm/static-source-compile-bundle/package.json similarity index 100% rename from tests/integration/wasm/source-compile-bundle/package.json rename to tests/integration/wasm/static-source-compile-bundle/package.json diff --git a/tests/integration/wasm/source-compile-bundle/rslib.config.ts b/tests/integration/wasm/static-source-compile-bundle/rslib.config.ts similarity index 100% rename from tests/integration/wasm/source-compile-bundle/rslib.config.ts rename to tests/integration/wasm/static-source-compile-bundle/rslib.config.ts diff --git a/tests/integration/wasm/source-compile-bundle/src/add.wasm b/tests/integration/wasm/static-source-compile-bundle/src/add.wasm similarity index 100% rename from tests/integration/wasm/source-compile-bundle/src/add.wasm rename to tests/integration/wasm/static-source-compile-bundle/src/add.wasm diff --git a/tests/integration/wasm/source-compile-bundle/src/index.js b/tests/integration/wasm/static-source-compile-bundle/src/index.js similarity index 100% rename from tests/integration/wasm/source-compile-bundle/src/index.js rename to tests/integration/wasm/static-source-compile-bundle/src/index.js diff --git a/tests/integration/wasm/source-compile-bundle/src/utils.js b/tests/integration/wasm/static-source-compile-bundle/src/utils.js similarity index 100% rename from tests/integration/wasm/source-compile-bundle/src/utils.js rename to tests/integration/wasm/static-source-compile-bundle/src/utils.js diff --git a/tests/integration/wasm/source-compile-bundleless/package.json b/tests/integration/wasm/static-source-compile-bundleless/package.json similarity index 100% rename from tests/integration/wasm/source-compile-bundleless/package.json rename to tests/integration/wasm/static-source-compile-bundleless/package.json diff --git a/tests/integration/wasm/source-compile-bundleless/rslib.config.ts b/tests/integration/wasm/static-source-compile-bundleless/rslib.config.ts similarity index 100% rename from tests/integration/wasm/source-compile-bundleless/rslib.config.ts rename to tests/integration/wasm/static-source-compile-bundleless/rslib.config.ts diff --git a/tests/integration/wasm/source-compile-bundleless/src/add.wasm b/tests/integration/wasm/static-source-compile-bundleless/src/add.wasm similarity index 100% rename from tests/integration/wasm/source-compile-bundleless/src/add.wasm rename to tests/integration/wasm/static-source-compile-bundleless/src/add.wasm diff --git a/tests/integration/wasm/source-compile-bundleless/src/index.js b/tests/integration/wasm/static-source-compile-bundleless/src/index.js similarity index 100% rename from tests/integration/wasm/source-compile-bundleless/src/index.js rename to tests/integration/wasm/static-source-compile-bundleless/src/index.js diff --git a/tests/integration/wasm/source-compile-bundleless/src/utils.js b/tests/integration/wasm/static-source-compile-bundleless/src/utils.js similarity index 100% rename from tests/integration/wasm/source-compile-bundleless/src/utils.js rename to tests/integration/wasm/static-source-compile-bundleless/src/utils.js diff --git a/tests/integration/wasm/source-preserve-bundle/package.json b/tests/integration/wasm/static-source-preserve-bundle/package.json similarity index 100% rename from tests/integration/wasm/source-preserve-bundle/package.json rename to tests/integration/wasm/static-source-preserve-bundle/package.json diff --git a/tests/integration/wasm/source-preserve-bundle/rslib.config.ts b/tests/integration/wasm/static-source-preserve-bundle/rslib.config.ts similarity index 100% rename from tests/integration/wasm/source-preserve-bundle/rslib.config.ts rename to tests/integration/wasm/static-source-preserve-bundle/rslib.config.ts diff --git a/tests/integration/wasm/source-preserve-bundle/src/add.wasm b/tests/integration/wasm/static-source-preserve-bundle/src/add.wasm similarity index 100% rename from tests/integration/wasm/source-preserve-bundle/src/add.wasm rename to tests/integration/wasm/static-source-preserve-bundle/src/add.wasm diff --git a/tests/integration/wasm/source-preserve-bundle/src/index.js b/tests/integration/wasm/static-source-preserve-bundle/src/index.js similarity index 100% rename from tests/integration/wasm/source-preserve-bundle/src/index.js rename to tests/integration/wasm/static-source-preserve-bundle/src/index.js diff --git a/tests/integration/wasm/source-preserve-bundle/src/utils.js b/tests/integration/wasm/static-source-preserve-bundle/src/utils.js similarity index 100% rename from tests/integration/wasm/source-preserve-bundle/src/utils.js rename to tests/integration/wasm/static-source-preserve-bundle/src/utils.js diff --git a/tests/integration/wasm/source-preserve-bundleless/package.json b/tests/integration/wasm/static-source-preserve-bundleless/package.json similarity index 100% rename from tests/integration/wasm/source-preserve-bundleless/package.json rename to tests/integration/wasm/static-source-preserve-bundleless/package.json diff --git a/tests/integration/wasm/source-preserve-bundleless/rslib.config.ts b/tests/integration/wasm/static-source-preserve-bundleless/rslib.config.ts similarity index 100% rename from tests/integration/wasm/source-preserve-bundleless/rslib.config.ts rename to tests/integration/wasm/static-source-preserve-bundleless/rslib.config.ts diff --git a/tests/integration/wasm/source-preserve-bundleless/src/add.wasm b/tests/integration/wasm/static-source-preserve-bundleless/src/add.wasm similarity index 100% rename from tests/integration/wasm/source-preserve-bundleless/src/add.wasm rename to tests/integration/wasm/static-source-preserve-bundleless/src/add.wasm diff --git a/tests/integration/wasm/source-preserve-bundleless/src/index.js b/tests/integration/wasm/static-source-preserve-bundleless/src/index.js similarity index 100% rename from tests/integration/wasm/source-preserve-bundleless/src/index.js rename to tests/integration/wasm/static-source-preserve-bundleless/src/index.js diff --git a/tests/integration/wasm/source-preserve-bundleless/src/utils.js b/tests/integration/wasm/static-source-preserve-bundleless/src/utils.js similarity index 100% rename from tests/integration/wasm/source-preserve-bundleless/src/utils.js rename to tests/integration/wasm/static-source-preserve-bundleless/src/utils.js From d9982eca963b874bc2a907c422959112fac8e21d Mon Sep 17 00:00:00 2001 From: Elecmonkey Date: Mon, 29 Jun 2026 14:35:59 +0800 Subject: [PATCH 09/10] fix(wasm): stop forcing preserve external type --- packages/core/src/wasm/external.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/wasm/external.ts b/packages/core/src/wasm/external.ts index 147d62f98..b0e529431 100644 --- a/packages/core/src/wasm/external.ts +++ b/packages/core/src/wasm/external.ts @@ -73,7 +73,7 @@ export const createWasmPreserveExternal = ({ outBase, emitDistRelPath, ); - callback(undefined, externalRequest, 'module'); + callback(undefined, externalRequest); } catch (err) { logger.debug( `[${PLUGIN_NAME}] Failed to externalize ${color.green(`"${request}"`)} from ${color.green(contextInfo.issuer || '')}: ${ From e3e100ba91c52c625122325648da664fafe84425 Mon Sep 17 00:00:00 2001 From: Elecmonkey Date: Mon, 29 Jun 2026 15:39:41 +0800 Subject: [PATCH 10/10] test(wasm): cover dynamic import modes --- .../wasm/dynamic-compile-bundle/package.json | 6 + .../dynamic-compile-bundle/rslib.config.ts | 14 ++ .../wasm/dynamic-compile-bundle/src/add.wasm | Bin 0 -> 41 bytes .../wasm/dynamic-compile-bundle/src/index.js | 1 + .../wasm/dynamic-compile-bundle/src/utils.js | 4 + .../dynamic-compile-bundleless/package.json | 6 + .../rslib.config.ts | 14 ++ .../dynamic-compile-bundleless/src/add.wasm | Bin 0 -> 41 bytes .../dynamic-compile-bundleless/src/index.js | 1 + .../dynamic-compile-bundleless/src/utils.js | 4 + .../wasm/dynamic-preserve-bundle/package.json | 6 + .../dynamic-preserve-bundle/rslib.config.ts | 14 ++ .../wasm/dynamic-preserve-bundle/src/add.wasm | Bin 0 -> 41 bytes .../wasm/dynamic-preserve-bundle/src/index.js | 1 + .../wasm/dynamic-preserve-bundle/src/utils.js | 4 + .../dynamic-preserve-bundleless/package.json | 6 + .../rslib.config.ts | 14 ++ .../dynamic-preserve-bundleless/src/add.wasm | Bin 0 -> 41 bytes .../dynamic-preserve-bundleless/src/index.js | 1 + .../dynamic-preserve-bundleless/src/utils.js | 4 + .../package.json | 6 + .../rslib.config.ts | 14 ++ .../src/add.wasm | Bin 0 -> 41 bytes .../src/index.js | 1 + .../src/utils.js | 4 + .../package.json | 6 + .../rslib.config.ts | 14 ++ .../src/add.wasm | Bin 0 -> 41 bytes .../src/index.js | 1 + .../src/utils.js | 4 + .../package.json | 6 + .../rslib.config.ts | 14 ++ .../src/add.wasm | Bin 0 -> 41 bytes .../src/index.js | 1 + .../src/utils.js | 4 + .../package.json | 6 + .../rslib.config.ts | 14 ++ .../src/add.wasm | Bin 0 -> 41 bytes .../src/index.js | 1 + .../src/utils.js | 4 + tests/integration/wasm/index.test.ts | 139 ++++++++++++++++++ 41 files changed, 339 insertions(+) create mode 100644 tests/integration/wasm/dynamic-compile-bundle/package.json create mode 100644 tests/integration/wasm/dynamic-compile-bundle/rslib.config.ts create mode 100644 tests/integration/wasm/dynamic-compile-bundle/src/add.wasm create mode 100644 tests/integration/wasm/dynamic-compile-bundle/src/index.js create mode 100644 tests/integration/wasm/dynamic-compile-bundle/src/utils.js create mode 100644 tests/integration/wasm/dynamic-compile-bundleless/package.json create mode 100644 tests/integration/wasm/dynamic-compile-bundleless/rslib.config.ts create mode 100644 tests/integration/wasm/dynamic-compile-bundleless/src/add.wasm create mode 100644 tests/integration/wasm/dynamic-compile-bundleless/src/index.js create mode 100644 tests/integration/wasm/dynamic-compile-bundleless/src/utils.js create mode 100644 tests/integration/wasm/dynamic-preserve-bundle/package.json create mode 100644 tests/integration/wasm/dynamic-preserve-bundle/rslib.config.ts create mode 100644 tests/integration/wasm/dynamic-preserve-bundle/src/add.wasm create mode 100644 tests/integration/wasm/dynamic-preserve-bundle/src/index.js create mode 100644 tests/integration/wasm/dynamic-preserve-bundle/src/utils.js create mode 100644 tests/integration/wasm/dynamic-preserve-bundleless/package.json create mode 100644 tests/integration/wasm/dynamic-preserve-bundleless/rslib.config.ts create mode 100644 tests/integration/wasm/dynamic-preserve-bundleless/src/add.wasm create mode 100644 tests/integration/wasm/dynamic-preserve-bundleless/src/index.js create mode 100644 tests/integration/wasm/dynamic-preserve-bundleless/src/utils.js create mode 100644 tests/integration/wasm/dynamic-source-compile-bundle/package.json create mode 100644 tests/integration/wasm/dynamic-source-compile-bundle/rslib.config.ts create mode 100644 tests/integration/wasm/dynamic-source-compile-bundle/src/add.wasm create mode 100644 tests/integration/wasm/dynamic-source-compile-bundle/src/index.js create mode 100644 tests/integration/wasm/dynamic-source-compile-bundle/src/utils.js create mode 100644 tests/integration/wasm/dynamic-source-compile-bundleless/package.json create mode 100644 tests/integration/wasm/dynamic-source-compile-bundleless/rslib.config.ts create mode 100644 tests/integration/wasm/dynamic-source-compile-bundleless/src/add.wasm create mode 100644 tests/integration/wasm/dynamic-source-compile-bundleless/src/index.js create mode 100644 tests/integration/wasm/dynamic-source-compile-bundleless/src/utils.js create mode 100644 tests/integration/wasm/dynamic-source-preserve-bundle/package.json create mode 100644 tests/integration/wasm/dynamic-source-preserve-bundle/rslib.config.ts create mode 100644 tests/integration/wasm/dynamic-source-preserve-bundle/src/add.wasm create mode 100644 tests/integration/wasm/dynamic-source-preserve-bundle/src/index.js create mode 100644 tests/integration/wasm/dynamic-source-preserve-bundle/src/utils.js create mode 100644 tests/integration/wasm/dynamic-source-preserve-bundleless/package.json create mode 100644 tests/integration/wasm/dynamic-source-preserve-bundleless/rslib.config.ts create mode 100644 tests/integration/wasm/dynamic-source-preserve-bundleless/src/add.wasm create mode 100644 tests/integration/wasm/dynamic-source-preserve-bundleless/src/index.js create mode 100644 tests/integration/wasm/dynamic-source-preserve-bundleless/src/utils.js diff --git a/tests/integration/wasm/dynamic-compile-bundle/package.json b/tests/integration/wasm/dynamic-compile-bundle/package.json new file mode 100644 index 000000000..d4d414af8 --- /dev/null +++ b/tests/integration/wasm/dynamic-compile-bundle/package.json @@ -0,0 +1,6 @@ +{ + "name": "wasm-dynamic-compile-bundle-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/wasm/dynamic-compile-bundle/rslib.config.ts b/tests/integration/wasm/dynamic-compile-bundle/rslib.config.ts new file mode 100644 index 000000000..8e103a73a --- /dev/null +++ b/tests/integration/wasm/dynamic-compile-bundle/rslib.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from '@rslib/core'; +import { generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [ + generateBundleEsmConfig({ + bundle: true, + wasm: { mode: 'compile' }, + }), + ], + output: { + target: 'web', + }, +}); diff --git a/tests/integration/wasm/dynamic-compile-bundle/src/add.wasm b/tests/integration/wasm/dynamic-compile-bundle/src/add.wasm new file mode 100644 index 0000000000000000000000000000000000000000..357f72da7a0db8add83699082fd51d46bf3352fb GIT binary patch literal 41 wcmZQbEY4+QU|?WmXG~zKuV<`hW@2PuXJ=$iOi5v2;NoOtXHZ~JV9eqM0DJxgJ^%m! literal 0 HcmV?d00001 diff --git a/tests/integration/wasm/dynamic-compile-bundle/src/index.js b/tests/integration/wasm/dynamic-compile-bundle/src/index.js new file mode 100644 index 000000000..4b20ccb0c --- /dev/null +++ b/tests/integration/wasm/dynamic-compile-bundle/src/index.js @@ -0,0 +1 @@ +export { createAdd } from './utils.js'; diff --git a/tests/integration/wasm/dynamic-compile-bundle/src/utils.js b/tests/integration/wasm/dynamic-compile-bundle/src/utils.js new file mode 100644 index 000000000..c6e4bdb34 --- /dev/null +++ b/tests/integration/wasm/dynamic-compile-bundle/src/utils.js @@ -0,0 +1,4 @@ +export const createAdd = async () => { + const wasm = await import('./add.wasm'); + return wasm.add; +}; diff --git a/tests/integration/wasm/dynamic-compile-bundleless/package.json b/tests/integration/wasm/dynamic-compile-bundleless/package.json new file mode 100644 index 000000000..6e60839f3 --- /dev/null +++ b/tests/integration/wasm/dynamic-compile-bundleless/package.json @@ -0,0 +1,6 @@ +{ + "name": "wasm-dynamic-compile-bundleless-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/wasm/dynamic-compile-bundleless/rslib.config.ts b/tests/integration/wasm/dynamic-compile-bundleless/rslib.config.ts new file mode 100644 index 000000000..56ecdfab4 --- /dev/null +++ b/tests/integration/wasm/dynamic-compile-bundleless/rslib.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from '@rslib/core'; +import { generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [ + generateBundleEsmConfig({ + bundle: false, + wasm: { mode: 'compile' }, + }), + ], + output: { + target: 'web', + }, +}); diff --git a/tests/integration/wasm/dynamic-compile-bundleless/src/add.wasm b/tests/integration/wasm/dynamic-compile-bundleless/src/add.wasm new file mode 100644 index 0000000000000000000000000000000000000000..357f72da7a0db8add83699082fd51d46bf3352fb GIT binary patch literal 41 wcmZQbEY4+QU|?WmXG~zKuV<`hW@2PuXJ=$iOi5v2;NoOtXHZ~JV9eqM0DJxgJ^%m! literal 0 HcmV?d00001 diff --git a/tests/integration/wasm/dynamic-compile-bundleless/src/index.js b/tests/integration/wasm/dynamic-compile-bundleless/src/index.js new file mode 100644 index 000000000..4b20ccb0c --- /dev/null +++ b/tests/integration/wasm/dynamic-compile-bundleless/src/index.js @@ -0,0 +1 @@ +export { createAdd } from './utils.js'; diff --git a/tests/integration/wasm/dynamic-compile-bundleless/src/utils.js b/tests/integration/wasm/dynamic-compile-bundleless/src/utils.js new file mode 100644 index 000000000..c6e4bdb34 --- /dev/null +++ b/tests/integration/wasm/dynamic-compile-bundleless/src/utils.js @@ -0,0 +1,4 @@ +export const createAdd = async () => { + const wasm = await import('./add.wasm'); + return wasm.add; +}; diff --git a/tests/integration/wasm/dynamic-preserve-bundle/package.json b/tests/integration/wasm/dynamic-preserve-bundle/package.json new file mode 100644 index 000000000..7b31b4f64 --- /dev/null +++ b/tests/integration/wasm/dynamic-preserve-bundle/package.json @@ -0,0 +1,6 @@ +{ + "name": "wasm-dynamic-preserve-bundle-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/wasm/dynamic-preserve-bundle/rslib.config.ts b/tests/integration/wasm/dynamic-preserve-bundle/rslib.config.ts new file mode 100644 index 000000000..9f8f82f46 --- /dev/null +++ b/tests/integration/wasm/dynamic-preserve-bundle/rslib.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from '@rslib/core'; +import { generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [ + generateBundleEsmConfig({ + bundle: true, + wasm: { mode: 'preserve' }, + }), + ], + output: { + target: 'web', + }, +}); diff --git a/tests/integration/wasm/dynamic-preserve-bundle/src/add.wasm b/tests/integration/wasm/dynamic-preserve-bundle/src/add.wasm new file mode 100644 index 0000000000000000000000000000000000000000..357f72da7a0db8add83699082fd51d46bf3352fb GIT binary patch literal 41 wcmZQbEY4+QU|?WmXG~zKuV<`hW@2PuXJ=$iOi5v2;NoOtXHZ~JV9eqM0DJxgJ^%m! literal 0 HcmV?d00001 diff --git a/tests/integration/wasm/dynamic-preserve-bundle/src/index.js b/tests/integration/wasm/dynamic-preserve-bundle/src/index.js new file mode 100644 index 000000000..4b20ccb0c --- /dev/null +++ b/tests/integration/wasm/dynamic-preserve-bundle/src/index.js @@ -0,0 +1 @@ +export { createAdd } from './utils.js'; diff --git a/tests/integration/wasm/dynamic-preserve-bundle/src/utils.js b/tests/integration/wasm/dynamic-preserve-bundle/src/utils.js new file mode 100644 index 000000000..c6e4bdb34 --- /dev/null +++ b/tests/integration/wasm/dynamic-preserve-bundle/src/utils.js @@ -0,0 +1,4 @@ +export const createAdd = async () => { + const wasm = await import('./add.wasm'); + return wasm.add; +}; diff --git a/tests/integration/wasm/dynamic-preserve-bundleless/package.json b/tests/integration/wasm/dynamic-preserve-bundleless/package.json new file mode 100644 index 000000000..be1174713 --- /dev/null +++ b/tests/integration/wasm/dynamic-preserve-bundleless/package.json @@ -0,0 +1,6 @@ +{ + "name": "wasm-dynamic-preserve-bundleless-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/wasm/dynamic-preserve-bundleless/rslib.config.ts b/tests/integration/wasm/dynamic-preserve-bundleless/rslib.config.ts new file mode 100644 index 000000000..b6f9c708d --- /dev/null +++ b/tests/integration/wasm/dynamic-preserve-bundleless/rslib.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from '@rslib/core'; +import { generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [ + generateBundleEsmConfig({ + bundle: false, + wasm: { mode: 'preserve' }, + }), + ], + output: { + target: 'web', + }, +}); diff --git a/tests/integration/wasm/dynamic-preserve-bundleless/src/add.wasm b/tests/integration/wasm/dynamic-preserve-bundleless/src/add.wasm new file mode 100644 index 0000000000000000000000000000000000000000..357f72da7a0db8add83699082fd51d46bf3352fb GIT binary patch literal 41 wcmZQbEY4+QU|?WmXG~zKuV<`hW@2PuXJ=$iOi5v2;NoOtXHZ~JV9eqM0DJxgJ^%m! literal 0 HcmV?d00001 diff --git a/tests/integration/wasm/dynamic-preserve-bundleless/src/index.js b/tests/integration/wasm/dynamic-preserve-bundleless/src/index.js new file mode 100644 index 000000000..4b20ccb0c --- /dev/null +++ b/tests/integration/wasm/dynamic-preserve-bundleless/src/index.js @@ -0,0 +1 @@ +export { createAdd } from './utils.js'; diff --git a/tests/integration/wasm/dynamic-preserve-bundleless/src/utils.js b/tests/integration/wasm/dynamic-preserve-bundleless/src/utils.js new file mode 100644 index 000000000..c6e4bdb34 --- /dev/null +++ b/tests/integration/wasm/dynamic-preserve-bundleless/src/utils.js @@ -0,0 +1,4 @@ +export const createAdd = async () => { + const wasm = await import('./add.wasm'); + return wasm.add; +}; diff --git a/tests/integration/wasm/dynamic-source-compile-bundle/package.json b/tests/integration/wasm/dynamic-source-compile-bundle/package.json new file mode 100644 index 000000000..fe3464e83 --- /dev/null +++ b/tests/integration/wasm/dynamic-source-compile-bundle/package.json @@ -0,0 +1,6 @@ +{ + "name": "wasm-dynamic-source-compile-bundle-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/wasm/dynamic-source-compile-bundle/rslib.config.ts b/tests/integration/wasm/dynamic-source-compile-bundle/rslib.config.ts new file mode 100644 index 000000000..8e103a73a --- /dev/null +++ b/tests/integration/wasm/dynamic-source-compile-bundle/rslib.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from '@rslib/core'; +import { generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [ + generateBundleEsmConfig({ + bundle: true, + wasm: { mode: 'compile' }, + }), + ], + output: { + target: 'web', + }, +}); diff --git a/tests/integration/wasm/dynamic-source-compile-bundle/src/add.wasm b/tests/integration/wasm/dynamic-source-compile-bundle/src/add.wasm new file mode 100644 index 0000000000000000000000000000000000000000..357f72da7a0db8add83699082fd51d46bf3352fb GIT binary patch literal 41 wcmZQbEY4+QU|?WmXG~zKuV<`hW@2PuXJ=$iOi5v2;NoOtXHZ~JV9eqM0DJxgJ^%m! literal 0 HcmV?d00001 diff --git a/tests/integration/wasm/dynamic-source-compile-bundle/src/index.js b/tests/integration/wasm/dynamic-source-compile-bundle/src/index.js new file mode 100644 index 000000000..4b20ccb0c --- /dev/null +++ b/tests/integration/wasm/dynamic-source-compile-bundle/src/index.js @@ -0,0 +1 @@ +export { createAdd } from './utils.js'; diff --git a/tests/integration/wasm/dynamic-source-compile-bundle/src/utils.js b/tests/integration/wasm/dynamic-source-compile-bundle/src/utils.js new file mode 100644 index 000000000..7397b5ea2 --- /dev/null +++ b/tests/integration/wasm/dynamic-source-compile-bundle/src/utils.js @@ -0,0 +1,4 @@ +export const createAdd = async () => { + const addModule = await import.source('./add.wasm'); + return new WebAssembly.Instance(addModule).exports.add; +}; diff --git a/tests/integration/wasm/dynamic-source-compile-bundleless/package.json b/tests/integration/wasm/dynamic-source-compile-bundleless/package.json new file mode 100644 index 000000000..b5a451eef --- /dev/null +++ b/tests/integration/wasm/dynamic-source-compile-bundleless/package.json @@ -0,0 +1,6 @@ +{ + "name": "wasm-dynamic-source-compile-bundleless-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/wasm/dynamic-source-compile-bundleless/rslib.config.ts b/tests/integration/wasm/dynamic-source-compile-bundleless/rslib.config.ts new file mode 100644 index 000000000..56ecdfab4 --- /dev/null +++ b/tests/integration/wasm/dynamic-source-compile-bundleless/rslib.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from '@rslib/core'; +import { generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [ + generateBundleEsmConfig({ + bundle: false, + wasm: { mode: 'compile' }, + }), + ], + output: { + target: 'web', + }, +}); diff --git a/tests/integration/wasm/dynamic-source-compile-bundleless/src/add.wasm b/tests/integration/wasm/dynamic-source-compile-bundleless/src/add.wasm new file mode 100644 index 0000000000000000000000000000000000000000..357f72da7a0db8add83699082fd51d46bf3352fb GIT binary patch literal 41 wcmZQbEY4+QU|?WmXG~zKuV<`hW@2PuXJ=$iOi5v2;NoOtXHZ~JV9eqM0DJxgJ^%m! literal 0 HcmV?d00001 diff --git a/tests/integration/wasm/dynamic-source-compile-bundleless/src/index.js b/tests/integration/wasm/dynamic-source-compile-bundleless/src/index.js new file mode 100644 index 000000000..4b20ccb0c --- /dev/null +++ b/tests/integration/wasm/dynamic-source-compile-bundleless/src/index.js @@ -0,0 +1 @@ +export { createAdd } from './utils.js'; diff --git a/tests/integration/wasm/dynamic-source-compile-bundleless/src/utils.js b/tests/integration/wasm/dynamic-source-compile-bundleless/src/utils.js new file mode 100644 index 000000000..7397b5ea2 --- /dev/null +++ b/tests/integration/wasm/dynamic-source-compile-bundleless/src/utils.js @@ -0,0 +1,4 @@ +export const createAdd = async () => { + const addModule = await import.source('./add.wasm'); + return new WebAssembly.Instance(addModule).exports.add; +}; diff --git a/tests/integration/wasm/dynamic-source-preserve-bundle/package.json b/tests/integration/wasm/dynamic-source-preserve-bundle/package.json new file mode 100644 index 000000000..3b6c2a99b --- /dev/null +++ b/tests/integration/wasm/dynamic-source-preserve-bundle/package.json @@ -0,0 +1,6 @@ +{ + "name": "wasm-dynamic-source-preserve-bundle-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/wasm/dynamic-source-preserve-bundle/rslib.config.ts b/tests/integration/wasm/dynamic-source-preserve-bundle/rslib.config.ts new file mode 100644 index 000000000..9f8f82f46 --- /dev/null +++ b/tests/integration/wasm/dynamic-source-preserve-bundle/rslib.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from '@rslib/core'; +import { generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [ + generateBundleEsmConfig({ + bundle: true, + wasm: { mode: 'preserve' }, + }), + ], + output: { + target: 'web', + }, +}); diff --git a/tests/integration/wasm/dynamic-source-preserve-bundle/src/add.wasm b/tests/integration/wasm/dynamic-source-preserve-bundle/src/add.wasm new file mode 100644 index 0000000000000000000000000000000000000000..357f72da7a0db8add83699082fd51d46bf3352fb GIT binary patch literal 41 wcmZQbEY4+QU|?WmXG~zKuV<`hW@2PuXJ=$iOi5v2;NoOtXHZ~JV9eqM0DJxgJ^%m! literal 0 HcmV?d00001 diff --git a/tests/integration/wasm/dynamic-source-preserve-bundle/src/index.js b/tests/integration/wasm/dynamic-source-preserve-bundle/src/index.js new file mode 100644 index 000000000..4b20ccb0c --- /dev/null +++ b/tests/integration/wasm/dynamic-source-preserve-bundle/src/index.js @@ -0,0 +1 @@ +export { createAdd } from './utils.js'; diff --git a/tests/integration/wasm/dynamic-source-preserve-bundle/src/utils.js b/tests/integration/wasm/dynamic-source-preserve-bundle/src/utils.js new file mode 100644 index 000000000..7397b5ea2 --- /dev/null +++ b/tests/integration/wasm/dynamic-source-preserve-bundle/src/utils.js @@ -0,0 +1,4 @@ +export const createAdd = async () => { + const addModule = await import.source('./add.wasm'); + return new WebAssembly.Instance(addModule).exports.add; +}; diff --git a/tests/integration/wasm/dynamic-source-preserve-bundleless/package.json b/tests/integration/wasm/dynamic-source-preserve-bundleless/package.json new file mode 100644 index 000000000..a4bab2dc4 --- /dev/null +++ b/tests/integration/wasm/dynamic-source-preserve-bundleless/package.json @@ -0,0 +1,6 @@ +{ + "name": "wasm-dynamic-source-preserve-bundleless-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/wasm/dynamic-source-preserve-bundleless/rslib.config.ts b/tests/integration/wasm/dynamic-source-preserve-bundleless/rslib.config.ts new file mode 100644 index 000000000..b6f9c708d --- /dev/null +++ b/tests/integration/wasm/dynamic-source-preserve-bundleless/rslib.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from '@rslib/core'; +import { generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [ + generateBundleEsmConfig({ + bundle: false, + wasm: { mode: 'preserve' }, + }), + ], + output: { + target: 'web', + }, +}); diff --git a/tests/integration/wasm/dynamic-source-preserve-bundleless/src/add.wasm b/tests/integration/wasm/dynamic-source-preserve-bundleless/src/add.wasm new file mode 100644 index 0000000000000000000000000000000000000000..357f72da7a0db8add83699082fd51d46bf3352fb GIT binary patch literal 41 wcmZQbEY4+QU|?WmXG~zKuV<`hW@2PuXJ=$iOi5v2;NoOtXHZ~JV9eqM0DJxgJ^%m! literal 0 HcmV?d00001 diff --git a/tests/integration/wasm/dynamic-source-preserve-bundleless/src/index.js b/tests/integration/wasm/dynamic-source-preserve-bundleless/src/index.js new file mode 100644 index 000000000..4b20ccb0c --- /dev/null +++ b/tests/integration/wasm/dynamic-source-preserve-bundleless/src/index.js @@ -0,0 +1 @@ +export { createAdd } from './utils.js'; diff --git a/tests/integration/wasm/dynamic-source-preserve-bundleless/src/utils.js b/tests/integration/wasm/dynamic-source-preserve-bundleless/src/utils.js new file mode 100644 index 000000000..7397b5ea2 --- /dev/null +++ b/tests/integration/wasm/dynamic-source-preserve-bundleless/src/utils.js @@ -0,0 +1,4 @@ +export const createAdd = async () => { + const addModule = await import.source('./add.wasm'); + return new WebAssembly.Instance(addModule).exports.add; +}; diff --git a/tests/integration/wasm/index.test.ts b/tests/integration/wasm/index.test.ts index 49d3b088c..71381bd05 100644 --- a/tests/integration/wasm/index.test.ts +++ b/tests/integration/wasm/index.test.ts @@ -87,6 +87,79 @@ test('wasm static source phase: compile + bundleless', async () => { expect(wasmFiles.length).toBe(1); }); +test('wasm dynamic: compile + bundle', async () => { + const fixturePath = join(__dirname, 'dynamic-compile-bundle'); + const { contents } = await buildAndGetResults({ fixturePath }); + + const { content: indexJs } = queryContent(contents.esm!, /index\.js/); + expect(indexJs).toContain('createAdd'); + expect(indexJs).toContain('import("./add.js")'); + expect(indexJs).not.toMatch(/from\s*["'][^"']+\.wasm["']/); + + const distDir = join(fixturePath, 'dist/esm'); + const jsFiles = walk(distDir).filter((p) => p.endsWith('.js')); + expect(jsFiles.length).toBe(3); + expect(existsSync(join(distDir, 'add.js'))).toBe(true); + const wasmFiles = walk(distDir).filter((p) => p.endsWith('.wasm')); + expect(wasmFiles.length).toBe(1); +}); + +test('wasm dynamic: compile + bundleless', async () => { + const fixturePath = join(__dirname, 'dynamic-compile-bundleless'); + const { contents } = await buildAndGetResults({ fixturePath }); + + const { content: indexJs } = queryContent(contents.esm!, /index\.js/); + const { content: utilsJs } = queryContent(contents.esm!, /utils\.js/); + expect(indexJs).toMatch(/from\s*["']\.\/utils\.js["']/); + expect(indexJs).not.toMatch(/\.wasm/); + + expect(utilsJs).toContain('createAdd'); + expect(utilsJs).toContain('import("./add.js")'); + expect(utilsJs).not.toMatch(/from\s*["'][^"']+\.wasm["']/); + + const distDir = join(fixturePath, 'dist/esm'); + expect(existsSync(join(distDir, 'add.js'))).toBe(true); + const wasmFiles = walk(distDir).filter((p) => p.endsWith('.wasm')); + expect(wasmFiles.length).toBe(1); +}); + +test('wasm dynamic source phase: compile + bundle', async () => { + const fixturePath = join(__dirname, 'dynamic-source-compile-bundle'); + const { contents } = await buildAndGetResults({ fixturePath }); + + const { content: indexJs } = queryContent(contents.esm!, /index\.js/); + expect(indexJs).toContain('createAdd'); + expect(indexJs).toContain('import("./add.js")'); + expect(indexJs).toContain('WebAssembly.Instance'); + expect(indexJs).not.toMatch(/from\s*["'][^"']+\.wasm["']/); + + const distDir = join(fixturePath, 'dist/esm'); + const jsFiles = walk(distDir).filter((p) => p.endsWith('.js')); + expect(jsFiles.length).toBe(3); + const wasmFiles = walk(distDir).filter((p) => p.endsWith('.wasm')); + expect(wasmFiles.length).toBe(1); +}); + +test('wasm dynamic source phase: compile + bundleless', async () => { + const fixturePath = join(__dirname, 'dynamic-source-compile-bundleless'); + const { contents } = await buildAndGetResults({ fixturePath }); + + const { content: indexJs } = queryContent(contents.esm!, /index\.js/); + const { content: utilsJs } = queryContent(contents.esm!, /utils\.js/); + expect(indexJs).toMatch(/from\s*["']\.\/utils\.js["']/); + expect(indexJs).not.toMatch(/\.wasm/); + + expect(utilsJs).toContain('createAdd'); + expect(utilsJs).toContain('WebAssembly.Instance'); + expect(utilsJs).toContain('import("./add.js")'); + expect(utilsJs).not.toMatch(/from\s*["'][^"']+\.wasm["']/); + + const distDir = join(fixturePath, 'dist/esm'); + expect(existsSync(join(distDir, 'add.js'))).toBe(true); + const wasmFiles = walk(distDir).filter((p) => p.endsWith('.wasm')); + expect(wasmFiles.length).toBe(1); +}); + test('wasm static: preserve + bundle', async () => { const fixturePath = join(__dirname, 'static-preserve-bundle'); const { contents } = await buildAndGetResults({ fixturePath }); @@ -124,6 +197,40 @@ test('wasm static source phase: preserve + bundle', async () => { expect(emitted[0]).not.toMatch(/[/\\]add\.wasm$/); }); +test('wasm dynamic: preserve + bundle', async () => { + const fixturePath = join(__dirname, 'dynamic-preserve-bundle'); + const { contents } = await buildAndGetResults({ fixturePath }); + + const { content: indexJs } = queryContent(contents.esm!, /index\.js/); + expect(indexJs).toContain('createAdd'); + expect(indexJs).toContain('import("./'); + expect(indexJs).toMatch(/\.wasm["']\)/); + + const distDir = join(fixturePath, 'dist/esm'); + const jsFiles = walk(distDir).filter((p) => p.endsWith('.js')); + expect(jsFiles.length).toBe(1); + const emitted = walk(distDir).filter((p) => p.endsWith('.wasm')); + expect(emitted.length).toBe(1); + expect(emitted[0]).not.toMatch(/[/\\]add\.wasm$/); +}); + +test('wasm dynamic source phase: preserve + bundle', async () => { + const fixturePath = join(__dirname, 'dynamic-source-preserve-bundle'); + const { contents } = await buildAndGetResults({ fixturePath }); + + const { content: indexJs } = queryContent(contents.esm!, /index\.js/); + expect(indexJs).toContain('createAdd'); + expect(indexJs).toContain('import.source("./static/wasm/'); + expect(indexJs).toMatch(/\.wasm["']\)/); + + const distDir = join(fixturePath, 'dist/esm'); + const jsFiles = walk(distDir).filter((p) => p.endsWith('.js')); + expect(jsFiles.length).toBe(1); + const emitted = walk(distDir).filter((p) => p.endsWith('.wasm')); + expect(emitted.length).toBe(1); + expect(emitted[0]).not.toMatch(/[/\\]add\.wasm$/); +}); + test('wasm static: preserve + bundleless', async () => { const fixturePath = join(__dirname, 'static-preserve-bundleless'); const { contents } = await buildAndGetResults({ fixturePath }); @@ -142,6 +249,22 @@ test('wasm static: preserve + bundleless', async () => { expect(existsSync(join(distDir, 'add.wasm'))).toBe(true); }); +test('wasm dynamic: preserve + bundleless', async () => { + const fixturePath = join(__dirname, 'dynamic-preserve-bundleless'); + const { contents } = await buildAndGetResults({ fixturePath }); + + const { content: indexJs } = queryContent(contents.esm!, /index\.js/); + const { content: utilsJs } = queryContent(contents.esm!, /utils\.js/); + expect(indexJs).toMatch(/from\s*["']\.\/utils\.js["']/); + expect(indexJs).not.toMatch(/\.wasm/); + + expect(utilsJs).toContain('createAdd'); + expect(utilsJs).toContain('import("./add.wasm")'); + + const distDir = join(fixturePath, 'dist/esm'); + expect(existsSync(join(distDir, 'add.wasm'))).toBe(true); +}); + test('wasm static source phase: preserve + bundleless', async () => { const fixturePath = join(__dirname, 'static-source-preserve-bundleless'); const { contents } = await buildAndGetResults({ fixturePath }); @@ -161,3 +284,19 @@ test('wasm static source phase: preserve + bundleless', async () => { const distDir = join(fixturePath, 'dist/esm'); expect(existsSync(join(distDir, 'add.wasm'))).toBe(true); }); + +test('wasm dynamic source phase: preserve + bundleless', async () => { + const fixturePath = join(__dirname, 'dynamic-source-preserve-bundleless'); + const { contents } = await buildAndGetResults({ fixturePath }); + + const { content: indexJs } = queryContent(contents.esm!, /index\.js/); + const { content: utilsJs } = queryContent(contents.esm!, /utils\.js/); + expect(indexJs).toMatch(/from\s*["']\.\/utils\.js["']/); + expect(indexJs).not.toMatch(/\.wasm/); + + expect(utilsJs).toContain('createAdd'); + expect(utilsJs).toContain('import.source("./add.wasm")'); + + const distDir = join(fixturePath, 'dist/esm'); + expect(existsSync(join(distDir, 'add.wasm'))).toBe(true); +});