diff --git a/packages/core/package.json b/packages/core/package.json index 7416680e25..1101dee97a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -35,7 +35,8 @@ "types.d.ts" ], "scripts": { - "build": "rs lib", + "build": "pnpm run build:esm-runner-transform && rs lib", + "build:esm-runner-transform": "node swc-plugins/esm-runner-transform/build.js", "dev": "rs lib -w", "prebundle": "prebundle", "test": "rs test" diff --git a/packages/core/src/server/runner/basic.ts b/packages/core/src/server/runner/basic.ts index dab72839fa..868c597c2f 100644 --- a/packages/core/src/server/runner/basic.ts +++ b/packages/core/src/server/runner/basic.ts @@ -34,6 +34,7 @@ const getSubPath = (p: string) => { export interface IBasicRunnerOptions { name: string; + bundleFiles?: ReadonlyMap; isBundleOutput: (modulePath: string) => boolean; readFileSync: (path: string) => string; dist: string; diff --git a/packages/core/src/server/runner/esmRunnerTransform.ts b/packages/core/src/server/runner/esmRunnerTransform.ts new file mode 100644 index 0000000000..c166190001 --- /dev/null +++ b/packages/core/src/server/runner/esmRunnerTransform.ts @@ -0,0 +1,117 @@ +import path from 'node:path'; +import { STATIC_PATH } from '../../constants'; +import { color } from '../../helpers'; + +export type TransformSourceFile = Readonly<{ + path: string; + source: string; + sourceMap?: string; +}>; + +export type SwcTransformOutput = { + code: string; + map?: string; +}; + +export type SwcTransform = ( + source: string, + options: Record, +) => Promise | SwcTransformOutput; + +export const ESM_RUNNER_TRANSFORM_PLUGIN_PATH: string = path.join( + STATIC_PATH, + 'swc-esm-runner-transform.wasm', +); + +const TRANSFORMED_ESM_PARAMETERS = [ + '__rsbuild_import__', + '__rsbuild_dynamic_import__', + '__rsbuild_exports__', + '__rsbuild_export_all__', + '__rsbuild_export_name__', + '__rsbuild_import_meta__', +]; + +const STRICT_MODE_PREFIX = '"use strict";\n'; + +const ASYNC_FUNCTION_BODY_LINE_OFFSET = (() => { + const marker = '/* module-runner-body */'; + const AsyncFunction = async function () {}.constructor as new ( + ...parameters: string[] + ) => (...args: unknown[]) => Promise; + // rslint-disable-next-line @typescript-eslint/no-implied-eval + const source = new AsyncFunction( + ...TRANSFORMED_ESM_PARAMETERS, + marker, + ).toString(); + return source.slice(0, source.indexOf(marker)).split('\n').length - 1; +})(); + +const offsetSourceMap = (sourceMap: string): string => { + const payload = JSON.parse(sourceMap) as { mappings?: unknown }; + if (typeof payload.mappings !== 'string') { + return sourceMap; + } + payload.mappings = `${';'.repeat(ASYNC_FUNCTION_BODY_LINE_OFFSET + 1)}${payload.mappings}`; + return JSON.stringify(payload); +}; + +const appendSourceMetadata = ( + code: string, + sourceMap: string | undefined, + moduleId: string, +): string => { + const executable = `${STRICT_MODE_PREFIX}${code.trimEnd()}`; + const sourceUrl = `//# sourceURL=${moduleId}`; + if (!sourceMap) { + return `${executable}\n${sourceUrl}`; + } + const encoded = Buffer.from(offsetSourceMap(sourceMap)).toString('base64'); + // Keep the complete directive out of Rsbuild's own bundled source so source + // map scanners do not mistake this template for the bundle's map. + const sourceMappingUrl = `sourceMapping${String.fromCharCode(85, 82, 76)}`; + return `${executable}\n${sourceUrl}\n//# ${sourceMappingUrl}=data:application/json;base64,${encoded}`; +}; + +export const transformForTransformedEsm = async ( + file: TransformSourceFile, + transform: SwcTransform, +): Promise => { + let result: SwcTransformOutput; + try { + result = await transform(file.source, { + configFile: false, + filename: file.path, + inlineSourcesContent: true, + inputSourceMap: file.sourceMap ?? false, + isModule: true, + jsc: { + parser: { dynamicImport: true, syntax: 'ecmascript' }, + target: 'es2022', + experimental: { + // swc_plugin_runner falls back to its process-memory cache when this + // existing file cannot be created as a cache directory. + cacheRoot: ESM_RUNNER_TRANSFORM_PLUGIN_PATH, + plugins: [[ESM_RUNNER_TRANSFORM_PLUGIN_PATH, {}]], + }, + }, + module: { type: 'es6' }, + sourceMaps: true, + swcrc: false, + }); + } catch (error) { + const reason = error instanceof Error ? `: ${error.message}` : ''; + throw new Error( + `${color.dim('[rsbuild:runner]')} Failed to transform ${file.path} for the module runner with ${ESM_RUNNER_TRANSFORM_PLUGIN_PATH}${reason}`, + { cause: error }, + ); + } + + if (!result || typeof result.code !== 'string') { + throw new Error( + `${color.dim('[rsbuild:runner]')} SWC returned no module-runner code for ${file.path}`, + ); + } + + return appendSourceMetadata(result.code, result.map, file.path); +}; diff --git a/packages/core/src/server/runner/index.ts b/packages/core/src/server/runner/index.ts index 52a13e9d09..4d4b5c6c60 100644 --- a/packages/core/src/server/runner/index.ts +++ b/packages/core/src/server/runner/index.ts @@ -1,8 +1,10 @@ /** * The following code is modified based on @rspack/test-tools/runner */ -import { color } from '../../helpers'; +import { color, require } from '../../helpers'; +import { CommonJsRunner } from './cjs'; import { EsmRunner } from './esm'; +import { TransformedEsmRunner } from './transformedEsm'; import type { Runner, RunnerFactory, RunnerFactoryOptions } from './type'; class BasicRunnerFactory implements RunnerFactory { @@ -29,7 +31,18 @@ class BasicRunnerFactory implements RunnerFactory { )} resource in Rsbuild server`, ); } - return new EsmRunner(runnerOptions); + + if (!compilerOptions.output.module) { + return new CommonJsRunner(runnerOptions); + } + + // rslint-disable-next-line @typescript-eslint/no-require-imports + const vm = require('node:vm') as typeof import('node:vm'); + if (vm.SourceTextModule) { + return new EsmRunner(runnerOptions); + } + + return new TransformedEsmRunner(runnerOptions); } } diff --git a/packages/core/src/server/runner/transformedEsm.ts b/packages/core/src/server/runner/transformedEsm.ts new file mode 100644 index 0000000000..ba275855e2 --- /dev/null +++ b/packages/core/src/server/runner/transformedEsm.ts @@ -0,0 +1,473 @@ +import path from 'node:path'; +import { isBuiltin, SourceMap, type SourceMapPayload } from 'node:module'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import { experiments } from '@rspack/core'; +import type { IBasicRunnerOptions } from './basic'; +import { color } from '../../helpers'; +import { + transformForTransformedEsm, + type SwcTransform, +} from './esmRunnerTransform'; +import type { Runner, RunnerRequirer } from './type'; + +type Namespace = Record; + +type TransformedEsmImportMetadata = { + importedNames?: string[]; +}; + +type TransformedEsmImportMeta = { + dirname: string; + filename: string; + glob: () => never; + resolve: (specifier: string, parent?: string) => never; + url: string; +}; + +type ModuleState = 'evaluating' | 'evaluated' | 'failed'; + +type ModuleNode = { + ambiguousExports: Set; + error?: unknown; + evaluationPromise?: Promise; + explicitExports: Set; + exports: Namespace; + id: string; + mapErrorStack: (error: unknown) => void; + state: ModuleState; +}; + +const TRANSFORMED_ESM_PARAMETERS = [ + '__rsbuild_import__', + '__rsbuild_dynamic_import__', + '__rsbuild_exports__', + '__rsbuild_export_all__', + '__rsbuild_export_name__', + '__rsbuild_import_meta__', +]; + +const AsyncFunction = async function () {}.constructor as new ( + ...parameters: string[] +) => (...args: unknown[]) => Promise; + +const INLINE_SOURCE_MAP = + /(?:^|\r?\n)[\t ]*\/\/[#@][\t ]*sourceMappingURL=data:application\/json;base64,([^\s]+)[\t ]*$/; + +const TRAILING_SOURCE_MAP_COMMENT = + /(?:^|\r?\n)[\t ]*(?:\/\/[#@][\t ]*sourceMappingURL=(\S+)|\/\*[#@][\t ]*sourceMappingURL=([^*\s]+)[\t ]*\*\/)[\t ]*(?=\s*$)/; + +const SOURCE_MAP_DATA_URL = + /^data:application\/json(?:;charset=[^;,]+)?(?:(;base64))?,(.*)$/i; + +const throwUnsupportedImportMetaMethod = (method: string): never => { + throw new Error( + `${color.dim('[rsbuild:runner]')} import.meta.${method}() is not supported.`, + ); +}; + +const createImportMeta = (moduleId: string): TransformedEsmImportMeta => ({ + dirname: path.dirname(moduleId), + filename: moduleId, + glob: () => throwUnsupportedImportMetaMethod('glob'), + resolve: () => throwUnsupportedImportMetaMethod('resolve'), + url: pathToFileURL(moduleId).href, +}); + +const escapeRegExp = (value: string): string => + value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + +const createStackTraceMapper = (code: string, moduleId: string) => { + const encodedSourceMap = INLINE_SOURCE_MAP.exec(code)?.[1]; + if (!encodedSourceMap) { + return (_error: unknown): void => {}; + } + + let sourceMap: SourceMap; + try { + const payload = JSON.parse( + Buffer.from(encodedSourceMap, 'base64').toString(), + ) as SourceMapPayload; + sourceMap = new SourceMap(payload); + } catch { + return (_error: unknown): void => {}; + } + + const moduleFrame = new RegExp( + `${escapeRegExp(moduleId)}:(\\d+):(\\d+)`, + 'g', + ); + return (error: unknown): void => { + if ( + Error.prepareStackTrace === undefined || + !(error instanceof Error) || + !error.stack + ) { + return; + } + error.stack = error.stack.replace(moduleFrame, (frame, line, column) => { + const origin = sourceMap.findOrigin(Number(line), Number(column)); + if (!('fileName' in origin)) { + return frame; + } + return `${origin.fileName}:${origin.lineNumber}:${origin.columnNumber}`; + }); + }; +}; + +const createNamespace = (): Namespace => { + const namespace = Object.create(null) as Namespace; + Object.defineProperty(namespace, Symbol.toStringTag, { + configurable: false, + enumerable: false, + value: 'Module', + }); + return namespace; +}; + +const analyzeImportedModDifference = ( + namespace: Namespace, + specifier: string, + metadata?: TransformedEsmImportMetadata, + ambiguousExports?: ReadonlySet, +): void => { + if (!metadata?.importedNames?.length) { + return; + } + const missingBindings = metadata.importedNames.filter( + (name) => !(name in namespace), + ); + if (missingBindings.length > 0) { + const lastBinding = missingBindings[missingBindings.length - 1]; + if (ambiguousExports?.has(lastBinding)) { + throw new SyntaxError( + `${color.dim('[rsbuild:runner]')} The requested module '${specifier}' contains conflicting star exports for name '${lastBinding}'`, + ); + } + throw new SyntaxError( + `${color.dim('[rsbuild:runner]')} The requested module '${specifier}' does not provide an export named '${lastBinding}'`, + ); + } +}; + +class TransformedEsmEvaluator { + readonly #bundleOutputRoot: string; + readonly #isBundleOutput: IBasicRunnerOptions['isBundleOutput']; + readonly #readFileSync: IBasicRunnerOptions['readFileSync']; + readonly #modules = new Map(); + + constructor(options: IBasicRunnerOptions) { + this.#bundleOutputRoot = options.dist; + this.#isBundleOutput = options.isBundleOutput; + this.#readFileSync = options.readFileSync; + } + + async evaluate(moduleId: string): Promise { + const normalizedId = this.#normalizeBundleModuleId(moduleId); + if (!this.#isBundleOutput(normalizedId)) { + throw new Error( + `${color.dim('[rsbuild:runner]')} Unknown bundle module ${normalizedId}`, + ); + } + return this.#evaluateModule(this.#getModule(normalizedId), new Set()); + } + + #getModule(moduleId: string): ModuleNode { + const existing = this.#modules.get(moduleId); + if (existing?.state === 'failed') { + this.#modules.delete(moduleId); + } else if (existing) { + return existing; + } + + const moduleNode: ModuleNode = { + ambiguousExports: new Set(), + explicitExports: new Set(), + exports: createNamespace(), + id: moduleId, + mapErrorStack: () => {}, + state: 'evaluating', + }; + this.#modules.set(moduleId, moduleNode); + return moduleNode; + } + + async #evaluateModule( + moduleNode: ModuleNode, + ancestors: Set, + ): Promise { + if (moduleNode.state === 'evaluated') { + return moduleNode.exports; + } + if (moduleNode.state === 'failed') { + throw moduleNode.error; + } + if (moduleNode.evaluationPromise) { + if (ancestors.has(moduleNode.id)) { + return moduleNode.exports; + } + return moduleNode.evaluationPromise; + } + + const nextAncestors = new Set(ancestors); + nextAncestors.add(moduleNode.id); + const evaluationPromise = this.#executeModule(moduleNode, nextAncestors) + .then(() => { + moduleNode.state = 'evaluated'; + return moduleNode.exports; + }) + .catch((error) => { + moduleNode.mapErrorStack(error); + moduleNode.error = error; + moduleNode.state = 'failed'; + this.#modules.delete(moduleNode.id); + throw error; + }); + moduleNode.evaluationPromise = evaluationPromise; + return evaluationPromise; + } + + async #executeModule( + moduleNode: ModuleNode, + ancestors: Set, + ): Promise { + const code = await this.#transformBundleModule(moduleNode.id); + moduleNode.mapErrorStack = createStackTraceMapper(code, moduleNode.id); + + let execute: (...args: unknown[]) => Promise; + try { + // rslint-disable-next-line @typescript-eslint/no-implied-eval + execute = new AsyncFunction(...TRANSFORMED_ESM_PARAMETERS, code); + } catch (error) { + throw new Error( + `${color.dim('[rsbuild:runner]')} Failed to instantiate module-runner code for ${moduleNode.id}`, + { cause: error }, + ); + } + + const exportName = (name: string, getter: () => unknown): void => { + moduleNode.explicitExports.add(name); + Object.defineProperty(moduleNode.exports, name, { + configurable: true, + enumerable: true, + get: () => { + try { + return getter(); + } catch { + return undefined; + } + }, + }); + }; + const starExportSources = new Map(); + const exportAll = (namespace: Namespace): void => { + for (const name of Object.keys(namespace)) { + if ( + name === 'default' || + name === '__esModule' || + moduleNode.explicitExports.has(name) || + moduleNode.ambiguousExports.has(name) + ) { + continue; + } + const existingSource = starExportSources.get(name); + if (existingSource) { + if (existingSource !== namespace) { + starExportSources.delete(name); + moduleNode.ambiguousExports.add(name); + delete moduleNode.exports[name]; + } + continue; + } + starExportSources.set(name, namespace); + Object.defineProperty(moduleNode.exports, name, { + configurable: true, + enumerable: true, + get: () => namespace[name], + }); + } + }; + const staticImport = ( + specifier: string, + metadata?: TransformedEsmImportMetadata, + ) => this.#import(specifier, moduleNode.id, ancestors, metadata, true); + const dynamicImport = (specifier: string) => + this.#import(specifier, moduleNode.id, ancestors, undefined, false); + + await execute( + staticImport, + dynamicImport, + moduleNode.exports, + exportAll, + exportName, + createImportMeta(moduleNode.id), + ); + } + + async #transformBundleModule(moduleId: string): Promise { + const source = this.#readFileSync(moduleId); + const match = TRAILING_SOURCE_MAP_COMMENT.exec(source); + const sourceMapUrl = (match?.[1] ?? match?.[2])?.trim(); + + let sourceMap: string | undefined; + if (sourceMapUrl) { + const dataUrlMatch = SOURCE_MAP_DATA_URL.exec(sourceMapUrl); + if (dataUrlMatch) { + sourceMap = dataUrlMatch[1] + ? Buffer.from(dataUrlMatch[2], 'base64').toString() + : decodeURIComponent(dataUrlMatch[2]); + } else { + const resolvedUrl = new URL(sourceMapUrl, pathToFileURL(moduleId)); + if (resolvedUrl.protocol === 'file:') { + sourceMap = this.#readFileSync(fileURLToPath(resolvedUrl)); + } + } + } + + return transformForTransformedEsm( + { path: moduleId, source, sourceMap }, + experiments.swc.transform as SwcTransform, + ); + } + + async #import( + specifier: string, + importer: string, + ancestors: Set, + metadata: TransformedEsmImportMetadata | undefined, + validate: boolean, + ): Promise { + if (typeof specifier !== 'string') { + throw new TypeError( + `${color.dim('[rsbuild:runner]')} Module specifier must be a string`, + ); + } + + const bundleModuleId = this.#resolveBundleModuleId(specifier, importer); + if (bundleModuleId) { + const dependency = this.#getModule(bundleModuleId); + const isCycle = + dependency.state !== 'evaluated' && ancestors.has(dependency.id); + const namespace = isCycle + ? dependency.exports + : await this.#evaluateModule(dependency, ancestors); + if (validate) { + analyzeImportedModDifference( + namespace, + specifier, + metadata, + dependency.ambiguousExports, + ); + } + return namespace; + } + + const namespace = await this.#runExternalModule(specifier, importer); + if (validate) { + analyzeImportedModDifference(namespace, specifier, metadata); + } + return namespace; + } + + #resolveBundleModuleId( + specifier: string, + importer: string, + ): string | undefined { + const request = specifier.split(/[?#]/, 1)[0].replaceAll('\\', '/'); + if (request.startsWith('file:')) { + const resolved = fileURLToPath(request); + return this.#isBundleOutput(resolved) ? resolved : undefined; + } + if (request.startsWith('.')) { + const resolved = path.resolve(path.dirname(importer), request); + return this.#isBundleOutput(resolved) ? resolved : undefined; + } + const candidate = path.isAbsolute(request) + ? path.normalize(request) + : path.resolve(this.#bundleOutputRoot, request); + return this.#isBundleOutput(candidate) ? candidate : undefined; + } + + #normalizeBundleModuleId(moduleId: string): string { + if (!path.isAbsolute(moduleId)) { + throw new Error( + `${color.dim('[rsbuild:runner]')} Bundle module ID must be absolute: ${moduleId}`, + ); + } + const normalized = path.normalize(moduleId); + const relative = path.relative(this.#bundleOutputRoot, normalized); + if ( + !relative || + path.isAbsolute(relative) || + relative === '..' || + relative.startsWith(`..${path.sep}`) + ) { + throw new Error( + `${color.dim('[rsbuild:runner]')} Bundle module is outside the output root: ${moduleId}`, + ); + } + return normalized; + } + + #resolveExternalModuleId(specifier: string, importer: string): string { + if (isBuiltin(specifier)) { + return specifier; + } + if (path.isAbsolute(specifier)) { + return pathToFileURL(specifier).href; + } + if (/^[a-zA-Z][a-zA-Z\d+.-]*:/.test(specifier)) { + return specifier; + } + + const result = new experiments.resolver.ResolverFactory({ + conditionNames: ['node', 'import', 'default'], + mainFields: ['module', 'main'], + }).sync(path.dirname(importer), specifier); + if (!result.path) { + throw new Error( + `${color.dim('[rsbuild:runner]')} Cannot resolve external module '${specifier}' imported from ${importer}: ${result.error}`, + ); + } + return pathToFileURL(result.path).href; + } + + #runExternalModule(specifier: string, importer: string): Promise { + return import( + this.#resolveExternalModuleId(specifier, importer) + ) as Promise; + } +} + +export class TransformedEsmRunner implements Runner { + readonly #options: IBasicRunnerOptions; + readonly #evaluator: TransformedEsmEvaluator; + + constructor(options: IBasicRunnerOptions) { + this.#options = options; + this.#evaluator = new TransformedEsmEvaluator(options); + } + + async run(file: string): Promise { + return await this.getRequire()(this.#options.dist, file); + } + + getRequire(): RunnerRequirer { + return (currentDirectory, modulePath) => { + if (Array.isArray(modulePath)) { + throw new Error( + `${color.dim('[rsbuild:runner]')} Array require is not supported by the module runner.`, + ); + } + const request = modulePath.split('?', 1)[0]; + const absolutePath = path.isAbsolute(request) + ? request + : request.startsWith('.') + ? path.resolve(currentDirectory, request) + : path.resolve(this.#options.dist, request); + return this.#evaluator.evaluate(absolutePath).then((namespace) => { + const defaultValue = namespace.default; + return defaultValue instanceof Promise ? defaultValue : namespace; + }); + }; + } +} diff --git a/packages/core/src/server/runner/type.ts b/packages/core/src/server/runner/type.ts index ef32dd3761..bc5c6c0170 100644 --- a/packages/core/src/server/runner/type.ts +++ b/packages/core/src/server/runner/type.ts @@ -43,8 +43,11 @@ export interface Runner { } export type RunnerFactoryOptions = { + bundleFiles?: ReadonlyMap; dist: string; compilerOptions: CompilerOptions; + entryName?: string; + environmentName?: string; readFileSync: (path: string) => string; isBundleOutput: (modulePath: string) => boolean; }; diff --git a/packages/core/static/.gitignore b/packages/core/static/.gitignore new file mode 100644 index 0000000000..bdfef3ebc5 --- /dev/null +++ b/packages/core/static/.gitignore @@ -0,0 +1 @@ +/swc-esm-runner-transform.wasm diff --git a/packages/core/swc-plugins/README.md b/packages/core/swc-plugins/README.md new file mode 100644 index 0000000000..613f5b6f82 --- /dev/null +++ b/packages/core/swc-plugins/README.md @@ -0,0 +1,20 @@ +# SWC plugins + +This directory contains the Rust sources for SWC WASM plugins that are owned +and shipped by `@rsbuild/core`. + +- `esm-runner-transform` lowers emitted ESM bundles to the protocol consumed + by the transformed ESM runner. +- The generated WASM binary is not committed. GitHub CI builds it and copies + it to `packages/core/static` before tests, builds, and releases. + +The `@rsbuild/core` build compiles the plugin automatically. To build only +the plugin locally: + +```bash +pnpm --filter @rsbuild/core run build:esm-runner-transform +``` + +Each standalone crate owns its `Cargo.lock` and ignores only its local +`target` directory. If more plugins begin sharing Rust dependencies or build +configuration, this directory can be converted to a Cargo workspace. diff --git a/packages/core/swc-plugins/esm-runner-transform/.gitignore b/packages/core/swc-plugins/esm-runner-transform/.gitignore new file mode 100644 index 0000000000..ac137159bb --- /dev/null +++ b/packages/core/swc-plugins/esm-runner-transform/.gitignore @@ -0,0 +1,2 @@ +/target/ +*.rs.bk diff --git a/packages/core/swc-plugins/esm-runner-transform/Cargo.lock b/packages/core/swc-plugins/esm-runner-transform/Cargo.lock new file mode 100644 index 0000000000..2d30732850 --- /dev/null +++ b/packages/core/swc-plugins/esm-runner-transform/Cargo.lock @@ -0,0 +1,2039 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "ar_archive_writer" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73cd58deff2140a0a8eae87e417bd01db68a33e148aa93d1e8cd837e55e312b6" +dependencies = [ + "object", +] + +[[package]] +name = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + +[[package]] +name = "ast_node" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edf54a7a1bf98e127c22e9e2b9d19619092c74283fdc14e4d67da69780f90db6" +dependencies = [ + "quote", + "swc_macros_common", + "syn 2.0.119", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + +[[package]] +name = "better_scoped_tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd228125315b132eed175bf47619ac79b945b26e56b848ba203ae4ea8603609" +dependencies = [ + "scoped-tls", +] + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "bitvec" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" +dependencies = [ + "allocator-api2", +] + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "bytes-str" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "577d2bf5650f8554d5a372af5ac93535110a0fc75b3e702bb853369febf227c2" +dependencies = [ + "bytes", + "serde", +] + +[[package]] +name = "camino" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb1307f12aa967b5a58416e87b3653360e0fd614a016b6e970db08fecbb1b80d" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cbor4ii" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faed1a83001dc2c9201451030cc317e35bef36c84d3781d7c5bb9f343c397da8" + +[[package]] +name = "cc" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "compact_str" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "ryu", + "static_assertions", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "data-encoding" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" + +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "serde", + "uuid", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "difference" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "dragonbox_ecma" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd8e701084c37e7ef62d3f9e453b618130cbc0ef3573847785952a3ac3f746bf" + +[[package]] +name = "either" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "find-msvc-tools" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "from_variant" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ff35a391aef949120a0340d690269b3d9f63460a6106e99bd07b961f345ea9" +dependencies = [ + "swc_macros_common", + "syn 2.0.119", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", +] + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hstr" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23efb2e58d2f27a96edfa907c26016deb120b7ac8c66a19d2748f0e9c145f3cb" +dependencies = [ + "hashbrown 0.14.5", + "new_debug_unreachable", + "once_cell", + "rustc-hash", + "serde", + "triomphe", +] + +[[package]] +name = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92a7ed671a6aad807a8651a2e1782a6598fda9ce5185dd8158549e95a91c6428" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "if_chain" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd62e6b5e86ea8eeeb8db1de02880a6abc01a397b2ebb64b5d74ac255318f5cb" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", +] + +[[package]] +name = "is-macro" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57a3e447e24c22647738e4607f1df1e0ec6f72e16182c4cd199f647cdfb0e4" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "miette" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" +dependencies = [ + "cfg-if", + "miette-derive", + "owo-colors", + "textwrap", + "unicode-width 0.1.14", +] + +[[package]] +name = "miette-derive" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.39.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5a6c098c7a3b6547378093f5cc30bc54fd361ce711e05293a5cc589562739b" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + +[[package]] +name = "owo-colors" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" + +[[package]] +name = "par-core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96cbd21255b7fb29a5d51ef38a779b517a91abd59e2756c039583f43ef4c90f" +dependencies = [ + "once_cell", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher 1.0.3", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "psm" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd034599e63b970727f70d79e02d62390a4a84f7c6b827c27c46d5ac3fa622" +dependencies = [ + "ar_archive_writer", + "cc", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + +[[package]] +name = "rsbuild_swc_esm_runner_transform" +version = "0.1.0" +dependencies = [ + "swc_core", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "seq-macro" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stacker" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707f49d46706bacf8a2b00d51dace3f9de527c13eec3778f570c411f89e69967" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "string_enum" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae36a4951ca7bd1cfd991c241584a9824a70f6aff1e7d4f693fb3f2465e4030e" +dependencies = [ + "quote", + "swc_macros_common", + "syn 2.0.119", +] + +[[package]] +name = "swc_allocator" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb41c2f41afa7357a86109f7e058f6b140ab415b8cd196c1a7b54f2703c85417" +dependencies = [ + "allocator-api2", + "bumpalo", + "hashbrown 0.14.5", + "rustc-hash", +] + +[[package]] +name = "swc_atoms" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c70a493080ceb12dabddb96905a3ddac679fbe9c71ddc81a2a769126e8cde7c4" +dependencies = [ + "cbor4ii", + "hstr", + "once_cell", + "serde", +] + +[[package]] +name = "swc_common" +version = "26.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142d2a06cafce623859ca799f5d1935bb8a886f18b66a2585ddeadff210121d0" +dependencies = [ + "anyhow", + "ast_node", + "better_scoped_tls", + "bytes-str", + "cbor4ii", + "either", + "from_variant", + "num-bigint", + "once_cell", + "parking_lot", + "rustc-hash", + "serde", + "siphasher 0.3.11", + "swc_atoms", + "swc_eq_ignore_macros", + "swc_sourcemap", + "swc_visit", + "termcolor", + "tracing", + "unicode-width 0.2.2", + "url", +] + +[[package]] +name = "swc_core" +version = "77.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3550b31eb172f3b27c55a05642a9d13520a55552382a708e44bdf286dfcbbe1" +dependencies = [ + "swc_allocator", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_codegen", + "swc_ecma_parser", + "swc_ecma_transforms_base", + "swc_ecma_transforms_testing", + "swc_ecma_visit", + "swc_plugin", + "swc_plugin_macro", + "swc_plugin_proxy", + "swc_transform_common", +] + +[[package]] +name = "swc_ecma_ast" +version = "29.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6fd43fc7e90b9d5b252af666c147f5e2ee692add2b075f7f0d6fc2f5357bb6" +dependencies = [ + "bitflags", + "cbor4ii", + "is-macro", + "num-bigint", + "once_cell", + "phf", + "rustc-hash", + "string_enum", + "swc_atoms", + "swc_common", + "swc_visit", + "unicode-id-start", +] + +[[package]] +name = "swc_ecma_codegen" +version = "32.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1b19a1d9f059fb77b0bc321688c7af59fe687a975581379456962586959c5d1" +dependencies = [ + "ascii", + "compact_str", + "dragonbox_ecma", + "memchr", + "num-bigint", + "once_cell", + "regex", + "rustc-hash", + "serde", + "swc_allocator", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_codegen_macros", + "swc_ecma_utils", + "tracing", +] + +[[package]] +name = "swc_ecma_codegen_macros" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e276dc62c0a2625a560397827989c82a93fd545fcf6f7faec0935a82cc4ddbb8" +dependencies = [ + "proc-macro2", + "swc_macros_common", + "syn 2.0.119", +] + +[[package]] +name = "swc_ecma_parser" +version = "45.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75a380e3e36a2167f1a2c6fd725c87a3aee60d404505667ea2c7c9e85763a70e" +dependencies = [ + "bitflags", + "compact_str", + "either", + "num-bigint", + "phf", + "rustc-hash", + "seq-macro", + "serde", + "stacker", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "tracing", +] + +[[package]] +name = "swc_ecma_testing" +version = "27.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d9eca985397245f45f146e13bb4f4aec6c9238fe8a05124ab9e626662f0d813" +dependencies = [ + "anyhow", + "hex", + "sha2", + "testing", + "tracing", +] + +[[package]] +name = "swc_ecma_transforms_base" +version = "49.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4efb43c0b7a0478fc084c55849e182ce85e7123070eb61cfeba67811ab253051" +dependencies = [ + "better_scoped_tls", + "indexmap", + "once_cell", + "par-core", + "phf", + "rustc-hash", + "serde", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_parser", + "swc_ecma_utils", + "swc_ecma_visit", + "tracing", +] + +[[package]] +name = "swc_ecma_transforms_testing" +version = "53.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f76aec842cdbe4ae1c8a953f8b126c9e26a42ee388c9b3c9b68a0e4e5e9747c9" +dependencies = [ + "ansi_term", + "anyhow", + "base64", + "hex", + "serde", + "serde_json", + "sha2", + "swc_common", + "swc_ecma_ast", + "swc_ecma_codegen", + "swc_ecma_parser", + "swc_ecma_testing", + "swc_ecma_transforms_base", + "swc_ecma_utils", + "swc_ecma_visit", + "swc_sourcemap", + "tempfile", + "testing", +] + +[[package]] +name = "swc_ecma_utils" +version = "35.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6fb39737b1c2bde685c5d85799aa04107548de69087a77c690a3ca8bbfc5100" +dependencies = [ + "dragonbox_ecma", + "indexmap", + "num_cpus", + "once_cell", + "par-core", + "rustc-hash", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_visit", + "tracing", +] + +[[package]] +name = "swc_ecma_visit" +version = "29.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a830a070fea84a3a8a8c556463f0551e3bfc6cf2f8bdeecd62fa753e22a72289" +dependencies = [ + "new_debug_unreachable", + "num-bigint", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_visit", + "tracing", +] + +[[package]] +name = "swc_eq_ignore_macros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c16ce73424a6316e95e09065ba6a207eba7765496fed113702278b7711d4b632" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "swc_error_reporters" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "997aa1a85b9ebdab127d8819ea5ebfa5b90e5a958d969e25cce4c853c36d26de" +dependencies = [ + "anyhow", + "miette", + "once_cell", + "serde", + "swc_common", +] + +[[package]] +name = "swc_macros_common" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1efbaa74943dc5ad2a2fb16cbd78b77d7e4d63188f3c5b4df2b4dcd2faaae" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "swc_plugin" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92b27449420554de6ad8d49004ad3d36e6ac64ecb51d1b0fe1002afcd7a45d85" +dependencies = [ + "once_cell", +] + +[[package]] +name = "swc_plugin_macro" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "584f7e74ca311c843d67e143331d9fb17bd5468f6216f3feac0b012d7cc268c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "swc_plugin_proxy" +version = "30.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b13ffe2fc1fb9b1936f6ceaf21d8e2e68f23ac9f0022db414e5f30c5fc52a966" +dependencies = [ + "better_scoped_tls", + "cbor4ii", + "rustc-hash", + "swc_common", + "swc_ecma_ast", + "tracing", +] + +[[package]] +name = "swc_sourcemap" +version = "10.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c421e5e39e43a4b1b70c07922d7bffd5c22e8eff1340c0b15d0bfd0328822ee" +dependencies = [ + "base64-simd", + "bitvec", + "bytes-str", + "data-encoding", + "debugid", + "if_chain", + "rustc-hash", + "serde", + "serde_json", + "unicode-id-start", + "url", +] + +[[package]] +name = "swc_transform_common" +version = "20.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693ff873dd31f50c44f5c05182631715bc03f7e1123e7805279b25d1db2bed20" +dependencies = [ + "better_scoped_tls", + "rustc-hash", + "serde", + "swc_common", +] + +[[package]] +name = "swc_visit" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62fb71484b486c185e34d2172f0eabe7f4722742aad700f426a494bb2de232a2" +dependencies = [ + "either", + "new_debug_unreachable", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "testing" +version = "27.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab499434ba981fb7aada35505f2035e1bae4517c18bafebf8954dd2dc7e979af" +dependencies = [ + "cargo_metadata", + "difference", + "once_cell", + "pretty_assertions", + "regex", + "rustc-hash", + "serde", + "serde_json", + "swc_common", + "swc_error_reporters", + "testing_macros", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "testing_macros" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c21fb13b703fb0bf466ec891704055ec18e2a8c8d2d47c31b65d7006044e146" +dependencies = [ + "anyhow", + "glob", + "once_cell", + "proc-macro2", + "quote", + "regex", + "relative-path", + "syn 2.0.119", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "unicode-linebreak", + "unicode-width 0.2.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "triomphe" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b40688ea6389c8171614b25491f71d4a27946e0c7ce2da1c6de27e25abf1a0ae" +dependencies = [ + "serde", + "stable_deref_trait", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-id-start" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81b79ad29b5e19de4260020f8919b443b2ef0277d242ce532ec7b7a2cc8b6007" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cefc03fd367c0c6d4305de1b312cf00248c4114f4a0418ce6a6af769e3b0bd9" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + +[[package]] +name = "wasm-bindgen" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b5c6b5976d66c1d703c4fd17d3f5e43c8cedaacf604961b171adc7130896d8" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f212a141d820099d57ffafb9569be9617a6f27d3dc881fbee8fb56642f917a9" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/packages/core/swc-plugins/esm-runner-transform/Cargo.toml b/packages/core/swc-plugins/esm-runner-transform/Cargo.toml new file mode 100644 index 0000000000..08420f0443 --- /dev/null +++ b/packages/core/swc-plugins/esm-runner-transform/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "rsbuild_swc_esm_runner_transform" +version = "0.1.0" +edition = "2024" +publish = false + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +swc_core = { version = "=77.0.0", features = ["ecma_plugin_transform"] } + +[dev-dependencies] +swc_core = { version = "=77.0.0", features = [ + "common", + "ecma_codegen", + "ecma_parser", + "ecma_transforms", + "ecma_visit", +] } + +[profile.release] +codegen-units = 1 +lto = true +opt-level = "s" +panic = "abort" +strip = "symbols" + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ["cfg(swc_ast_unknown)"] } diff --git a/packages/core/swc-plugins/esm-runner-transform/build.js b/packages/core/swc-plugins/esm-runner-transform/build.js new file mode 100644 index 0000000000..131c517077 --- /dev/null +++ b/packages/core/swc-plugins/esm-runner-transform/build.js @@ -0,0 +1,42 @@ +import { spawnSync } from 'node:child_process'; +import { copyFileSync } from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +// cspell:ignore rustup wasip +const pluginDir = path.dirname(fileURLToPath(import.meta.url)); +const target = 'wasm32-wasip1'; + +const run = (command, args) => { + const result = spawnSync(command, args, { + cwd: pluginDir, + stdio: 'inherit', + }); + + if (result.error) { + if (result.error.code === 'ENOENT') { + throw new Error( + `Cannot build the ESM runner transform because ${command} is not installed. Install Rust with rustup and try again.`, + ); + } + throw result.error; + } + + if (result.status !== 0) { + process.exit(result.status ?? 1); + } +}; + +run('rustup', ['target', 'add', target]); +run('cargo', ['build', '--locked', '--release', '--target', target]); + +copyFileSync( + path.join( + pluginDir, + 'target', + target, + 'release', + 'rsbuild_swc_esm_runner_transform.wasm', + ), + path.resolve(pluginDir, '../../static/swc-esm-runner-transform.wasm'), +); diff --git a/packages/core/swc-plugins/esm-runner-transform/src/lib.rs b/packages/core/swc-plugins/esm-runner-transform/src/lib.rs new file mode 100644 index 0000000000..af16b3c008 --- /dev/null +++ b/packages/core/swc-plugins/esm-runner-transform/src/lib.rs @@ -0,0 +1,769 @@ +#![allow(clippy::not_unsafe_ptr_arg_deref)] + +use std::collections::HashMap; + +use swc_core::{ + atoms::{Atom, Wtf8Atom}, + common::{DUMMY_SP, Mark, SyntaxContext}, + ecma::{ + ast::*, + visit::{VisitMut, VisitMutWith}, + }, + plugin::{plugin_transform, proxies::TransformPluginProgramMetadata}, +}; + +const IMPORT_HELPER: &str = "__rsbuild_import__"; +const DYNAMIC_IMPORT_HELPER: &str = "__rsbuild_dynamic_import__"; +const EXPORT_ALL_HELPER: &str = "__rsbuild_export_all__"; +const EXPORT_NAME_HELPER: &str = "__rsbuild_export_name__"; +const IMPORT_META_HELPER: &str = "__rsbuild_import_meta__"; + +#[derive(Clone)] +struct ImportBinding { + namespace: Ident, + imported: Option, +} + +struct ImportRequest { + imported_names: Vec, + namespace: Ident, + source: Wtf8Atom, + export_all: bool, +} + +struct TransformedEsmTransform { + bindings: HashMap, + helper_ctxt: SyntaxContext, + private_mark: Mark, + request_index: usize, +} + +impl TransformedEsmTransform { + fn new(unresolved_mark: Mark) -> Self { + Self { + bindings: HashMap::new(), + helper_ctxt: SyntaxContext::empty().apply_mark(unresolved_mark), + private_mark: Mark::new(), + request_index: 0, + } + } + + fn helper_ident(&self, name: &str) -> Ident { + Ident::new(name.into(), DUMMY_SP, self.helper_ctxt) + } + + fn private_ident(&mut self, prefix: &str) -> Ident { + let index = self.request_index; + self.request_index += 1; + Ident::new( + format!("{prefix}{index}__").into(), + DUMMY_SP, + SyntaxContext::empty().apply_mark(self.private_mark), + ) + } + + fn transform_module(&mut self, mut module: Module) -> Module { + let mut export_registrations = Vec::new(); + let mut requests = Vec::new(); + let mut body = Vec::new(); + + for item in module.body { + match item { + ModuleItem::ModuleDecl(ModuleDecl::Import(import)) => { + self.assert_supported_import(&import); + let namespace = self.private_ident("__rsbuild_import_"); + let mut imported_names = Vec::new(); + + for specifier in import.specifiers { + match specifier { + ImportSpecifier::Named(named) => { + if named.is_type_only { + continue; + } + let imported = named + .imported + .as_ref() + .map(module_export_name) + .unwrap_or_else(|| named.local.sym.clone()); + imported_names.push(imported.clone()); + self.bindings.insert( + named.local.to_id(), + ImportBinding { + namespace: namespace.clone(), + imported: Some(imported), + }, + ); + } + ImportSpecifier::Default(default) => { + let imported = Atom::from("default"); + imported_names.push(imported.clone()); + self.bindings.insert( + default.local.to_id(), + ImportBinding { + namespace: namespace.clone(), + imported: Some(imported), + }, + ); + } + ImportSpecifier::Namespace(namespace_specifier) => { + self.bindings.insert( + namespace_specifier.local.to_id(), + ImportBinding { + namespace: namespace.clone(), + imported: None, + }, + ); + } + #[cfg(swc_ast_unknown)] + _ => panic!("[rsbuild:runner] Unsupported import specifier"), + } + } + + requests.push(ImportRequest { + imported_names, + namespace, + source: import.src.value, + export_all: false, + }); + } + ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export)) => { + for ident in declaration_bindings(&export.decl) { + export_registrations.push( + self.export_name_statement( + ident.sym.clone(), + Expr::Ident(ident.clone()), + ), + ); + } + body.push(ModuleItem::Stmt(Stmt::Decl(export.decl))); + } + ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(export)) => { + self.assert_supported_named_export(&export); + if let Some(source) = export.src { + let namespace = self.private_ident("__rsbuild_import_"); + let mut imported_names = Vec::new(); + for specifier in export.specifiers { + match specifier { + ExportSpecifier::Named(named) => { + if named.is_type_only { + continue; + } + let imported = module_export_name(&named.orig); + let exported = named + .exported + .as_ref() + .map(module_export_name) + .unwrap_or_else(|| imported.clone()); + imported_names.push(imported.clone()); + export_registrations.push(self.export_name_statement( + exported, + namespace_member(&namespace, imported), + )); + } + ExportSpecifier::Namespace(namespace_export) => { + export_registrations.push(self.export_name_statement( + module_export_name(&namespace_export.name), + Expr::Ident(namespace.clone()), + )); + } + ExportSpecifier::Default(default) => { + let imported = Atom::from("default"); + imported_names.push(imported.clone()); + export_registrations.push(self.export_name_statement( + default.exported.sym, + namespace_member(&namespace, imported), + )); + } + #[cfg(swc_ast_unknown)] + _ => panic!("[rsbuild:runner] Unsupported export specifier"), + } + } + requests.push(ImportRequest { + imported_names, + namespace, + source: source.value, + export_all: false, + }); + } else { + for specifier in export.specifiers { + match specifier { + ExportSpecifier::Named(named) if !named.is_type_only => { + let exported = named + .exported + .as_ref() + .map(module_export_name) + .unwrap_or_else(|| module_export_name(&named.orig)); + let local = match named.orig { + ModuleExportName::Ident(ident) => Expr::Ident(ident), + ModuleExportName::Str(_) => { + panic!( + "[rsbuild:runner] A local export name must be an identifier" + ) + } + #[cfg(swc_ast_unknown)] + _ => { + panic!("[rsbuild:runner] Unsupported local export name") + } + }; + export_registrations + .push(self.export_name_statement(exported, local)); + } + ExportSpecifier::Named(_) => {} + _ => panic!("[rsbuild:runner] Unsupported local export specifier"), + } + } + } + } + ModuleItem::ModuleDecl(ModuleDecl::ExportAll(export)) => { + if export.with.is_some() { + panic!("[rsbuild:runner] Import attributes are not supported"); + } + let namespace = self.private_ident("__rsbuild_import_"); + requests.push(ImportRequest { + imported_names: Vec::new(), + namespace, + source: export.src.value, + export_all: true, + }); + } + ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(export)) => { + match export.decl { + DefaultDecl::Fn(function) => { + let ident = function + .ident + .unwrap_or_else(|| self.private_ident("__rsbuild_default_")); + export_registrations.push(self.export_name_statement( + Atom::from("default"), + Expr::Ident(ident.clone()), + )); + body.push(ModuleItem::Stmt(Stmt::Decl(Decl::Fn(FnDecl { + ident, + declare: false, + function: function.function, + })))); + } + DefaultDecl::Class(class) => { + let ident = class + .ident + .unwrap_or_else(|| self.private_ident("__rsbuild_default_")); + export_registrations.push(self.export_name_statement( + Atom::from("default"), + Expr::Ident(ident.clone()), + )); + body.push(ModuleItem::Stmt(Stmt::Decl(Decl::Class(ClassDecl { + ident, + declare: false, + class: class.class, + })))); + } + DefaultDecl::TsInterfaceDecl(_) => {} + #[cfg(swc_ast_unknown)] + _ => panic!("[rsbuild:runner] Unsupported default export declaration"), + } + } + ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(export)) => { + let ident = self.private_ident("__rsbuild_default_"); + export_registrations.push( + self.export_name_statement( + Atom::from("default"), + Expr::Ident(ident.clone()), + ), + ); + body.push(const_statement(ident, *export.expr)); + } + ModuleItem::ModuleDecl(other) => { + panic!("[rsbuild:runner] Unsupported module declaration: {other:?}") + } + ModuleItem::Stmt(statement) => body.push(ModuleItem::Stmt(statement)), + #[cfg(swc_ast_unknown)] + _ => panic!("[rsbuild:runner] Unsupported module item"), + } + } + + let mut transformed_body = export_registrations; + for request in requests { + transformed_body.push(self.import_statement(&request)); + if request.export_all { + transformed_body.push(self.export_all_statement(request.namespace)); + } + } + transformed_body.extend(body); + + module.body = transformed_body; + module.visit_mut_with(&mut BindingRewriter { + bindings: &self.bindings, + dynamic_import_helper: self.helper_ident(DYNAMIC_IMPORT_HELPER), + import_meta_helper: self.helper_ident(IMPORT_META_HELPER), + }); + module + } + + fn assert_supported_import(&self, import: &ImportDecl) { + if import.with.is_some() { + panic!("[rsbuild:runner] Import attributes are not supported"); + } + if import.phase != ImportPhase::Evaluation { + panic!("[rsbuild:runner] Import source/defer phases are not supported"); + } + } + + fn assert_supported_named_export(&self, export: &NamedExport) { + if export.with.is_some() { + panic!("[rsbuild:runner] Import attributes are not supported"); + } + } + + fn export_name_statement(&self, name: Atom, value: Expr) -> ModuleItem { + let getter = Expr::Arrow(ArrowExpr { + span: DUMMY_SP, + ctxt: SyntaxContext::empty(), + params: Vec::new(), + body: Box::new(ArrowFunctionBody::Expr(Box::new(value))), + is_async: false, + is_generator: false, + type_params: None, + return_type: None, + }); + call_statement( + self.helper_ident(EXPORT_NAME_HELPER), + vec![string_expr(name), getter], + ) + } + + fn import_statement(&self, request: &ImportRequest) -> ModuleItem { + let mut args = vec![string_expr( + request.source.clone().to_atom_lossy().into_owned(), + )]; + if !request.imported_names.is_empty() { + let names = request + .imported_names + .iter() + .cloned() + .map(|name| Some(ExprOrSpread::from(string_expr(name)))) + .collect(); + args.push(Expr::Object(ObjectLit { + span: DUMMY_SP, + props: vec![PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp { + key: PropName::Ident(IdentName::new("importedNames".into(), DUMMY_SP)), + value: Box::new(Expr::Array(ArrayLit { + span: DUMMY_SP, + elems: names, + })), + })))], + })); + } + let call = call_expr(self.helper_ident(IMPORT_HELPER), args); + const_statement( + request.namespace.clone(), + Expr::Await(AwaitExpr { + span: DUMMY_SP, + arg: Box::new(call), + }), + ) + } + + fn export_all_statement(&self, namespace: Ident) -> ModuleItem { + call_statement( + self.helper_ident(EXPORT_ALL_HELPER), + vec![Expr::Ident(namespace)], + ) + } +} + +struct BindingRewriter<'a> { + bindings: &'a HashMap, + dynamic_import_helper: Ident, + import_meta_helper: Ident, +} + +impl BindingRewriter<'_> { + fn replacement(&self, ident: &Ident) -> Option { + let binding = self.bindings.get(&ident.to_id())?; + Some(match &binding.imported { + Some(imported) => namespace_member(&binding.namespace, imported.clone()), + None => Expr::Ident(binding.namespace.clone()), + }) + } + + fn unbound_replacement(&self, ident: &Ident) -> Option { + let replacement = self.replacement(ident)?; + Some(Expr::Seq(SeqExpr { + span: DUMMY_SP, + exprs: vec![ + Box::new(Expr::Lit(Lit::Num(Number { + span: DUMMY_SP, + value: 0.0, + raw: None, + }))), + Box::new(replacement), + ], + })) + } + + fn visit_mut_call_callee(&mut self, callee: &mut Box) { + if let Expr::Ident(ident) = &**callee + && let Some(replacement) = self.unbound_replacement(ident) + { + **callee = replacement; + } else { + callee.visit_mut_with(self); + } + } +} + +impl VisitMut for BindingRewriter<'_> { + fn visit_mut_call_expr(&mut self, call: &mut CallExpr) { + call.args.visit_mut_with(self); + match &mut call.callee { + Callee::Import(import) => { + if import.phase != ImportPhase::Evaluation { + panic!("[rsbuild:runner] Dynamic import source/defer phases are not supported"); + } + call.callee = + Callee::Expr(Box::new(Expr::Ident(self.dynamic_import_helper.clone()))); + } + Callee::Expr(callee) => self.visit_mut_call_callee(callee), + Callee::Super(_) => {} + #[cfg(swc_ast_unknown)] + _ => panic!("[rsbuild:runner] Unsupported call callee"), + } + } + + fn visit_mut_opt_call(&mut self, call: &mut OptCall) { + call.args.visit_mut_with(self); + self.visit_mut_call_callee(&mut call.callee); + } + + fn visit_mut_tagged_tpl(&mut self, template: &mut TaggedTpl) { + template.tpl.visit_mut_with(self); + if let Expr::Ident(ident) = &*template.tag + && let Some(replacement) = self.unbound_replacement(ident) + { + *template.tag = replacement; + } else { + template.tag.visit_mut_with(self); + } + } + + fn visit_mut_prop(&mut self, property: &mut Prop) { + if let Prop::Shorthand(ident) = property + && let Some(replacement) = self.replacement(ident) + { + *property = Prop::KeyValue(KeyValueProp { + key: PropName::Ident(IdentName::new(ident.sym.clone(), ident.span)), + value: Box::new(replacement), + }); + return; + } + property.visit_mut_children_with(self); + } + + fn visit_mut_expr(&mut self, expression: &mut Expr) { + match expression { + Expr::Ident(ident) => { + if let Some(replacement) = self.replacement(ident) { + *expression = replacement; + } + } + Expr::MetaProp(meta) if meta.kind == MetaPropKind::ImportMeta => { + *expression = Expr::Ident(self.import_meta_helper.clone()); + } + _ => expression.visit_mut_children_with(self), + } + } +} + +fn module_export_name(name: &ModuleExportName) -> Atom { + name.atom().into_owned() +} + +fn declaration_bindings(declaration: &Decl) -> Vec { + match declaration { + Decl::Class(class) => vec![class.ident.clone()], + Decl::Fn(function) => vec![function.ident.clone()], + Decl::Var(variable) => variable + .decls + .iter() + .flat_map(|declaration| pattern_bindings(&declaration.name)) + .collect(), + _ => Vec::new(), + } +} + +fn pattern_bindings(pattern: &Pat) -> Vec { + match pattern { + Pat::Ident(binding) => vec![binding.id.clone()], + Pat::Array(array) => array + .elems + .iter() + .flatten() + .flat_map(pattern_bindings) + .collect(), + Pat::Rest(rest) => pattern_bindings(&rest.arg), + Pat::Object(object) => object + .props + .iter() + .flat_map(|property| match property { + ObjectPatProp::KeyValue(property) => pattern_bindings(&property.value), + ObjectPatProp::Assign(property) => vec![property.key.id.clone()], + ObjectPatProp::Rest(property) => pattern_bindings(&property.arg), + #[cfg(swc_ast_unknown)] + _ => Vec::new(), + }) + .collect(), + Pat::Assign(assign) => pattern_bindings(&assign.left), + Pat::Invalid(_) | Pat::Expr(_) => Vec::new(), + #[cfg(swc_ast_unknown)] + _ => Vec::new(), + } +} + +fn namespace_member(namespace: &Ident, property: Atom) -> Expr { + Expr::Member(MemberExpr { + span: DUMMY_SP, + obj: Box::new(Expr::Ident(namespace.clone())), + prop: MemberProp::Computed(ComputedPropName { + span: DUMMY_SP, + expr: Box::new(string_expr(property)), + }), + }) +} + +fn string_expr(value: Atom) -> Expr { + Expr::Lit(Lit::Str(Str { + span: DUMMY_SP, + value: value.into(), + raw: None, + })) +} + +fn call_expr(callee: Ident, args: Vec) -> Expr { + Expr::Call(CallExpr { + span: DUMMY_SP, + ctxt: SyntaxContext::empty(), + callee: Callee::Expr(Box::new(Expr::Ident(callee))), + args: args.into_iter().map(ExprOrSpread::from).collect(), + type_args: None, + }) +} + +fn call_statement(callee: Ident, args: Vec) -> ModuleItem { + ModuleItem::Stmt(Stmt::Expr(ExprStmt { + span: DUMMY_SP, + expr: Box::new(call_expr(callee, args)), + })) +} + +fn const_statement(name: Ident, value: Expr) -> ModuleItem { + ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl { + span: DUMMY_SP, + ctxt: SyntaxContext::empty(), + kind: VarDeclKind::Const, + declare: false, + decls: vec![VarDeclarator { + span: DUMMY_SP, + name: Pat::Ident(BindingIdent { + id: name, + type_ann: None, + }), + init: Some(Box::new(value)), + definite: false, + }], + })))) +} + +fn transform_program(program: Program, unresolved_mark: Mark) -> Program { + match program { + Program::Module(module) => { + Program::Module(TransformedEsmTransform::new(unresolved_mark).transform_module(module)) + } + Program::Script(_) => panic!("[rsbuild:runner] Expected an ECMAScript module"), + #[cfg(swc_ast_unknown)] + _ => panic!("[rsbuild:runner] Unsupported SWC program variant"), + } +} + +#[plugin_transform] +fn swc_plugin(program: Program, metadata: TransformPluginProgramMetadata) -> Program { + transform_program(program, metadata.unresolved_mark) +} + +#[cfg(test)] +mod tests { + use swc_core::{ + common::{FileName, GLOBALS, Globals, Mark, SourceMap, sync::Lrc}, + ecma::{ + ast::Program, + codegen::to_code_default, + parser::{EsSyntax, Parser, StringInput, Syntax, lexer::Lexer}, + transforms::base::{fixer::fixer, hygiene::hygiene, resolver}, + visit::VisitMutWith, + }, + }; + + use super::transform_program; + + fn transform(source: &str) -> String { + GLOBALS.set(&Globals::new(), || { + let source_map: Lrc = Default::default(); + let source_file = source_map.new_source_file( + FileName::Custom("fixture.mjs".into()).into(), + source.to_string(), + ); + let lexer = Lexer::new( + Syntax::Es(EsSyntax { + import_attributes: true, + ..Default::default() + }), + Default::default(), + StringInput::from(&*source_file), + None, + ); + let mut parser = Parser::new_from(lexer); + let mut program = Program::Module(parser.parse_module().expect("fixture should parse")); + assert!(parser.take_errors().is_empty()); + + let unresolved_mark = Mark::new(); + program.visit_mut_with(&mut resolver(unresolved_mark, Mark::new(), false)); + let mut program = transform_program(program, unresolved_mark); + program.visit_mut_with(&mut hygiene()); + program.visit_mut_with(&mut fixer(None)); + to_code_default(source_map, None, &program) + }) + } + + #[test] + fn rewrites_import_reads_without_rewriting_shadowed_bindings() { + let output = transform( + r#" + import fallback, { value as remote } from 'dependency'; + import * as namespace from 'namespace'; + import 'side-effect'; + const __rsbuild_import__ = 'user binding'; + export const direct = remote; + export const defaultValue = fallback; + export const ns = namespace; + export const shadowed = (remote) => ({ remote }); + export { __rsbuild_import__ as helperCollision }; + "#, + ); + + assert_eq!( + output, + r#"__rsbuild_export_name__("direct", ()=>direct); +__rsbuild_export_name__("defaultValue", ()=>defaultValue); +__rsbuild_export_name__("ns", ()=>ns); +__rsbuild_export_name__("shadowed", ()=>shadowed); +__rsbuild_export_name__("helperCollision", ()=>__rsbuild_import__1); +const __rsbuild_import_0__ = await __rsbuild_import__("dependency", { + importedNames: [ + "default", + "value" + ] +}); +const __rsbuild_import_1__ = await __rsbuild_import__("namespace"); +const __rsbuild_import_2__ = await __rsbuild_import__("side-effect"); +const __rsbuild_import__1 = 'user binding'; +const direct = __rsbuild_import_0__["value"]; +const defaultValue = __rsbuild_import_0__["default"]; +const ns = __rsbuild_import_1__; +const shadowed = (remote)=>({ + remote + }); +"# + ); + } + + #[test] + fn preserves_object_shorthand_destructuring_class_and_unbound_calls() { + let output = transform( + r#" + import { Base, call, tag, value } from 'dependency'; + const { local } = { local: 1 }; + export class Child extends Base {} + export const object = { value, local }; + export const result = call(); + export const optionalResult = call?.(); + export const tagged = tag`value`; + "#, + ); + + assert_eq!( + output, + r#"__rsbuild_export_name__("Child", ()=>Child); +__rsbuild_export_name__("object", ()=>object); +__rsbuild_export_name__("result", ()=>result); +__rsbuild_export_name__("optionalResult", ()=>optionalResult); +__rsbuild_export_name__("tagged", ()=>tagged); +const __rsbuild_import_0__ = await __rsbuild_import__("dependency", { + importedNames: [ + "Base", + "call", + "tag", + "value" + ] +}); +const { local } = { + local: 1 +}; +class Child extends __rsbuild_import_0__["Base"] { +} +const object = { + value: __rsbuild_import_0__["value"], + local +}; +const result = (0, __rsbuild_import_0__["call"])(); +const optionalResult = (0, __rsbuild_import_0__["call"])?.(); +const tagged = (0, __rsbuild_import_0__["tag"])`value`; +"# + ); + } + + #[test] + fn rewrites_exports_reexports_dynamic_import_and_import_meta() { + let output = transform( + r#" + export const local = 1; + export { local as "string name" }; + export default function named() {} + export { value as renamed, default as otherDefault } from 'dependency'; + export * from 'star'; + export * as namespace from 'namespace'; + export const dynamic = () => import('./lazy.mjs'); + export const meta = import.meta.url; + "#, + ); + + assert_eq!( + output, + r#"__rsbuild_export_name__("local", ()=>local); +__rsbuild_export_name__("string name", ()=>local); +__rsbuild_export_name__("default", ()=>named); +__rsbuild_export_name__("renamed", ()=>__rsbuild_import_0__["value"]); +__rsbuild_export_name__("otherDefault", ()=>__rsbuild_import_0__["default"]); +__rsbuild_export_name__("namespace", ()=>__rsbuild_import_2__); +__rsbuild_export_name__("dynamic", ()=>dynamic); +__rsbuild_export_name__("meta", ()=>meta); +const __rsbuild_import_0__ = await __rsbuild_import__("dependency", { + importedNames: [ + "value", + "default" + ] +}); +const __rsbuild_import_1__ = await __rsbuild_import__("star"); +__rsbuild_export_all__(__rsbuild_import_1__); +const __rsbuild_import_2__ = await __rsbuild_import__("namespace"); +const local = 1; +function named() {} +const dynamic = ()=>__rsbuild_dynamic_import__('./lazy.mjs'); +const meta = __rsbuild_import_meta__.url; +"# + ); + } + + #[test] + #[should_panic(expected = "Import attributes are not supported")] + fn rejects_import_attributes() { + transform("import value from 'dependency' with { type: 'json' }; void value;"); + } +} diff --git a/packages/core/tests/transformedEsmRunner.test.ts b/packages/core/tests/transformedEsmRunner.test.ts new file mode 100644 index 0000000000..d81e6b5df9 --- /dev/null +++ b/packages/core/tests/transformedEsmRunner.test.ts @@ -0,0 +1,548 @@ +import path from 'node:path'; +import { pathToFileURL } from 'node:url'; +import { color } from '../src/helpers'; +import { run } from '../src/server/runner'; +import { TransformedEsmRunner } from '../src/server/runner/transformedEsm'; +import type { RunnerFactoryOptions } from '../src/server/runner/type'; + +type ModuleFixture = readonly [moduleId: string, code: string]; +type TestNamespace = Record; + +const DEFAULT_DIST = path.resolve('/virtual/module-runner-dist'); + +const createRunner = ( + entries: ReadonlyArray, + dist = DEFAULT_DIST, +) => { + const files = new Map( + entries.map(([moduleId, code]) => [path.resolve(dist, moduleId), code]), + ); + return new TransformedEsmRunner({ + compilerOptions: { + output: { module: true }, + target: 'node', + } as RunnerFactoryOptions['compilerOptions'], + dist, + isBundleOutput: (fileName) => files.has(fileName), + name: 'entry.mjs', + readFileSync: (fileName) => { + const content = files.get(fileName); + if (content === undefined) { + throw new Error(`Unknown output file: ${fileName}`); + } + return content; + }, + }); +}; + +let externalIndex = 0; +const externalModule = (source: string): string => + `data:text/javascript;charset=utf-8,${encodeURIComponent(source)}#module-runner-${externalIndex++}`; + +test('runs ESM bundle output through the runner factory', async () => { + const files = new Map([ + [path.join(DEFAULT_DIST, 'entry.mjs'), 'export const value = 42;'], + ]); + const options: RunnerFactoryOptions = { + compilerOptions: { + output: { module: true }, + target: 'node', + } as RunnerFactoryOptions['compilerOptions'], + dist: DEFAULT_DIST, + isBundleOutput: (fileName) => files.has(fileName), + readFileSync: (fileName) => files.get(fileName)!, + }; + + await expect( + run({ bundlePath: 'entry.mjs', ...options }), + ).resolves.toMatchObject({ value: 42 }); +}); + +test('evaluates basic exports once and shares concurrent evaluation', async () => { + const stateKey = '__rsbuildTransformedEsmEvaluationCount'; + const runner = createRunner([ + [ + 'entry.mjs', + `globalThis[${JSON.stringify(stateKey)}] = (globalThis[${JSON.stringify(stateKey)}] || 0) + 1; +export const value = 42; +export default 'default';`, + ], + ]); + + try { + const [first, second] = (await Promise.all([ + runner.run('entry.mjs'), + runner.run('entry.mjs'), + ])) as TestNamespace[]; + expect(first).toBe(second); + expect(first).toMatchObject({ default: 'default', value: 42 }); + expect((globalThis as TestNamespace)[stateKey]).toBe(1); + } finally { + delete (globalThis as TestNamespace)[stateKey]; + } +}); + +test('evaluates local dependencies in source order and supports late dynamic import', async () => { + const stateKey = '__rsbuildTransformedEsmOrder'; + const runner = createRunner([ + [ + 'entry.mjs', + `import './first.mjs'; +import './second.mjs'; +export const order = globalThis[${JSON.stringify(stateKey)}]; +export const load = () => import('./lazy.mjs');`, + ], + ['first.mjs', `globalThis[${JSON.stringify(stateKey)}] = 'first';`], + [ + 'second.mjs', + `globalThis[${JSON.stringify(stateKey)}] += ':second'; +await Promise.resolve();`, + ], + ['lazy.mjs', `export const value = 'lazy';`], + ]); + + try { + const result = (await runner.run('entry.mjs')) as TestNamespace; + expect(result.order).toBe('first:second'); + await expect(result.load()).resolves.toMatchObject({ value: 'lazy' }); + } finally { + delete (globalThis as TestNamespace)[stateKey]; + } +}); + +test('propagates top-level await completion and rejection', async () => { + const completed = createRunner([ + [ + 'entry.mjs', + `import { value } from './dependency.mjs'; export const result = value;`, + ], + [ + 'dependency.mjs', + `await Promise.resolve(); export const value = 'completed';`, + ], + ]); + await expect(completed.run('entry.mjs')).resolves.toMatchObject({ + result: 'completed', + }); + + const rejected = createRunner([ + [ + 'entry.mjs', + `import './dependency.mjs'; export const unreachable = true;`, + ], + ['dependency.mjs', `await Promise.reject(new Error('TLA rejected'));`], + ]); + await expect(rejected.run('entry.mjs')).rejects.toThrow('TLA rejected'); +}); + +test('shares dependency evaluation across concurrent importers', async () => { + const stateKey = '__rsbuildTransformedEsmDependencyCount'; + const runner = createRunner([ + ['first.mjs', `import { value } from './shared.mjs'; export { value };`], + ['second.mjs', `import { value } from './shared.mjs'; export { value };`], + [ + 'shared.mjs', + `globalThis[${JSON.stringify(stateKey)}] = (globalThis[${JSON.stringify(stateKey)}] || 0) + 1; +await Promise.resolve(); +export const value = globalThis[${JSON.stringify(stateKey)}];`, + ], + ]); + + try { + const [first, second] = (await Promise.all([ + runner.run('first.mjs'), + runner.run('second.mjs'), + ])) as TestNamespace[]; + expect(first.value).toBe(1); + expect(second.value).toBe(1); + expect((globalThis as TestNamespace)[stateKey]).toBe(1); + } finally { + delete (globalThis as TestNamespace)[stateKey]; + } +}); + +test('supports mutually cyclic bundle modules', async () => { + const runner = createRunner([ + [ + 'entry.mjs', + `import { readB } from './b.mjs'; +export const valueA = 'A'; +export const readA = () => valueA; +export const cycle = () => readB();`, + ], + [ + 'b.mjs', + `import { readA } from './entry.mjs'; +export const readB = () => readA() + 'B';`, + ], + ]); + + const result = (await runner.run('entry.mjs')) as TestNamespace; + expect(result.cycle()).toBe('AB'); +}); + +test('reports a missing static export in a cycle before executing its body', async () => { + const stateKey = '__rsbuildTransformedEsmMissingCycleExecuted'; + const runner = createRunner([ + [ + 'entry.mjs', + `import './dependency.mjs'; +export const present = true;`, + ], + [ + 'dependency.mjs', + `import { missing } from './entry.mjs'; +globalThis[${JSON.stringify(stateKey)}] = true; +export const value = missing;`, + ], + ]); + + try { + await expect(runner.run('entry.mjs')).rejects.toThrow( + "does not provide an export named 'missing'", + ); + expect((globalThis as TestNamespace)[stateKey]).toBeUndefined(); + } finally { + delete (globalThis as TestNamespace)[stateKey]; + } +}); + +test('keeps direct external imports live and calls imported functions unbound', async () => { + const externalId = externalModule(` +export let ready = false; +export function mark() { ready = true; } +export function receiver() { return this; } +`); + const runner = createRunner([ + [ + 'entry.mjs', + `import { ready, mark, receiver } from ${JSON.stringify(externalId)}; +export const read = () => ready; +export const update = () => { + const before = ready; + mark(); + return [before, ready, receiver(), receiver?.()]; +};`, + ], + ]); + + const result = (await runner.run('entry.mjs')) as TestNamespace; + expect(result.read()).toBe(false); + expect(result.update()).toEqual([false, true, undefined, undefined]); + expect(result.read()).toBe(true); +}); + +test('propagates live bindings through named, default, star and multilevel reexports', async () => { + const externalId = externalModule(` +export let value = 0; +export function increment() { value += 1; } +`); + const runner = createRunner([ + [ + 'entry.mjs', + `import current, { value, increment } from './bridge.mjs'; +import { value as starValue } from './star.mjs'; +export const update = () => { + const before = [value, current, starValue]; + increment(); + return [before, value, current, starValue]; +};`, + ], + [ + 'bridge.mjs', + `export { value, value as default, increment } from ${JSON.stringify(externalId)};`, + ], + ['star.mjs', `export * from './bridge.mjs';`], + ]); + + const result = (await runner.run('entry.mjs')) as TestNamespace; + expect(result.update()).toEqual([[0, 0, 0], 1, 1, 1]); + expect(result.update()).toEqual([[1, 1, 1], 2, 2, 2]); +}); + +test('omits ambiguous star exports while preserving explicit exports', async () => { + const runner = createRunner([ + [ + 'entry.mjs', + `export * from './first.mjs'; +export * from './first.mjs'; +export * from './second.mjs'; +export { overridden } from './first.mjs';`, + ], + [ + 'first.mjs', + `export const conflict = 'first'; +export const firstOnly = 'first'; +export const overridden = 'first'; +export const repeated = 'first';`, + ], + [ + 'second.mjs', + `export const conflict = 'second'; +export const overridden = 'second'; +export const secondOnly = 'second';`, + ], + [ + 'consumer.mjs', + `import { conflict } from './entry.mjs'; +export const value = conflict;`, + ], + ]); + + const namespace = (await runner.run('entry.mjs')) as TestNamespace; + expect(namespace).toMatchObject({ + firstOnly: 'first', + overridden: 'first', + repeated: 'first', + secondOnly: 'second', + }); + expect(Object.hasOwn(namespace, 'conflict')).toBe(false); + + await expect(runner.run('consumer.mjs')).rejects.toMatchObject({ + message: `${color.dim('[rsbuild:runner]')} The requested module './entry.mjs' contains conflicting star exports for name 'conflict'`, + name: 'SyntaxError', + }); +}); + +test('observes asynchronous external export updates', async () => { + const externalId = externalModule(` +export let ready = false; +export async function markLater() { + await Promise.resolve(); + ready = true; +} +`); + const runner = createRunner([ + [ + 'entry.mjs', + `import { ready, markLater } from ${JSON.stringify(externalId)}; +export const observe = async () => { + const before = ready; + await markLater(); + return [before, ready]; +};`, + ], + ]); + + const result = (await runner.run('entry.mjs')) as TestNamespace; + await expect(result.observe()).resolves.toEqual([false, true]); +}); + +test('shares live external bindings across importers and calls tags unbound', async () => { + const externalId = externalModule(` +export let value = 0; +export function increment() { value += 1; } +export function tag() { return this; } +`); + const runner = createRunner([ + [ + 'entry.mjs', + `import { read } from './reader.mjs'; +import { update, readTagThis } from './writer.mjs'; +export { read, update, readTagThis };`, + ], + [ + 'reader.mjs', + `import { value as current } from ${JSON.stringify(externalId)}; +export const read = () => current;`, + ], + [ + 'writer.mjs', + `import { increment, tag } from ${JSON.stringify(externalId)}; +export const update = () => increment(); +export const readTagThis = () => tag\`value\`;`, + ], + ]); + + const result = (await runner.run('entry.mjs')) as TestNamespace; + expect(result.read()).toBe(0); + result.update(); + expect(result.read()).toBe(1); + expect(result.readTagThis()).toBeUndefined(); +}); + +test('preserves the native external namespace object', async () => { + const externalId = externalModule(`export const value = 1;`); + const runner = createRunner([ + [ + 'entry.mjs', + `import * as namespace from ${JSON.stringify(externalId)}; +export { namespace };`, + ], + ]); + const nativeNamespace = await import(externalId); + + const result = (await runner.run('entry.mjs')) as TestNamespace; + expect(result.namespace).toBe(nativeNamespace); +}); + +test('reports missing static exports without rejecting namespace or dynamic imports', async () => { + const externalId = externalModule(`export const present = 1;`); + const missingRunner = createRunner([ + [ + 'entry.mjs', + `import { missing } from ${JSON.stringify(externalId)}; +export const value = typeof missing;`, + ], + ]); + + await expect(missingRunner.run('entry.mjs')).rejects.toMatchObject({ + message: `${color.dim('[rsbuild:runner]')} The requested module '${externalId}' does not provide an export named 'missing'`, + name: 'SyntaxError', + }); + + const namespaceRunner = createRunner([ + [ + 'entry.mjs', + `import * as namespace from ${JSON.stringify(externalId)}; +export const staticMissing = namespace.missing; +export const dynamicMissing = () => import(${JSON.stringify(externalId)}).then((mod) => mod.missing);`, + ], + ]); + const result = (await namespaceRunner.run('entry.mjs')) as TestNamespace; + expect(result.staticMissing).toBeUndefined(); + await expect(result.dynamicMissing()).resolves.toBeUndefined(); + + const missingDefaultRunner = createRunner([ + [ + 'entry.mjs', + `import missingDefault from ${JSON.stringify(externalId)}; +export const value = missingDefault;`, + ], + ]); + await expect(missingDefaultRunner.run('entry.mjs')).rejects.toThrow( + "does not provide an export named 'default'", + ); + + const nonBindingImports = createRunner([ + [ + 'entry.mjs', + `import ${JSON.stringify(externalId)}; +export * from ${JSON.stringify(externalId)}; +export const loaded = true;`, + ], + ]); + await expect(nonBindingImports.run('entry.mjs')).resolves.toMatchObject({ + loaded: true, + present: 1, + }); +}); + +test('provides Node import.meta metadata and Rsbuild unsupported-method errors', async () => { + const moduleId = path.join(DEFAULT_DIST, 'nested', 'module.mjs'); + const runner = createRunner([ + ['entry.mjs', `export { meta, unsupported } from './nested/module.mjs';`], + [ + 'nested/module.mjs', + `export const meta = { + dirname: import.meta.dirname, + filename: import.meta.filename, + url: import.meta.url, +}; +export const unsupported = () => import.meta.resolve('./dependency.mjs');`, + ], + ]); + + const result = (await runner.run('entry.mjs')) as TestNamespace; + expect(result.meta).toEqual({ + dirname: path.dirname(moduleId), + filename: moduleId, + url: pathToFileURL(moduleId).href, + }); + expect(() => result.unsupported()).toThrow( + `${color.dim('[rsbuild:runner]')} import.meta.resolve() is not supported.`, + ); +}); + +test.each([ + ['glob', `import.meta.glob('./*.js')`], + ['resolve', `import.meta.resolve('./dependency.mjs')`], +])('reports unsupported import.meta.%s()', async (method, expression) => { + const runner = createRunner([ + ['entry.mjs', `${expression};\nexport const value = 1;`], + ]); + + await expect(runner.run('entry.mjs')).rejects.toMatchObject({ + message: `${color.dim('[rsbuild:runner]')} import.meta.${method}() is not supported.`, + name: 'Error', + }); +}); + +test('maps runtime errors to the original source location', async () => { + const entryPath = path.join(DEFAULT_DIST, 'entry.mjs'); + const runner = createRunner([ + [ + 'entry.mjs', + `export const before = 1; +export function fail() { + throw new Error('source map failure'); +} +fail();`, + ], + ]); + let runtimeError: unknown; + + try { + await runner.run('entry.mjs'); + } catch (error) { + runtimeError = error; + } + + expect(runtimeError).toBeInstanceOf(Error); + expect((runtimeError as Error).message).toBe('source map failure'); + expect((runtimeError as Error).stack).toContain(`at fail (${entryPath}:3:9)`); +}); + +test('evaluates bundle dependencies before later external dependencies', async () => { + const stateKey = '__rsbuildTransformedEsmExternalOrder'; + const externalId = externalModule(` +const key = ${JSON.stringify(stateKey)}; +if (globalThis[key] !== 'bundle') { + throw new Error('bundle dependency was not evaluated first'); +} +globalThis[key] += ':external'; +`); + const runner = createRunner([ + [ + 'entry.mjs', + `import './polyfill.mjs'; +import ${JSON.stringify(externalId)}; +export const order = globalThis[${JSON.stringify(stateKey)}];`, + ], + ['polyfill.mjs', `globalThis[${JSON.stringify(stateKey)}] = 'bundle';`], + ]); + + try { + await expect(runner.run('entry.mjs')).resolves.toMatchObject({ + order: 'bundle:external', + }); + } finally { + delete (globalThis as TestNamespace)[stateKey]; + } +}); + +test('retries a failed module and isolates separate runner graphs', async () => { + const entryPath = path.join(DEFAULT_DIST, 'entry.mjs'); + let attempt = 0; + const options = { + compilerOptions: { + output: { module: true }, + target: 'node', + } as RunnerFactoryOptions['compilerOptions'], + dist: DEFAULT_DIST, + isBundleOutput: (fileName: string) => fileName === entryPath, + name: 'entry.mjs', + readFileSync: () => + attempt++ === 0 ? 'export const = invalid;' : 'export const value = 1;', + }; + const runner = new TransformedEsmRunner(options); + + await expect(runner.run('entry.mjs')).rejects.toBeInstanceOf(Error); + await expect(runner.run('entry.mjs')).resolves.toMatchObject({ value: 1 }); + + const other = createRunner([['entry.mjs', 'export const value = 2;']]); + await expect(other.run('entry.mjs')).resolves.toMatchObject({ value: 2 }); + await expect(runner.run('../outside.mjs')).rejects.toThrow( + 'Bundle module is outside the output root', + ); +});