From 6f2978945d2bd9e4d1435a01ea3495c95b6bca60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Mon, 15 Sep 2025 17:25:58 +0200 Subject: [PATCH 01/10] Statically analyzable build --- .../blueprints-v1/blueprints-v1-handler.ts | 25 +++---- .../blueprints-v2/blueprints-v2-handler.ts | 21 +----- packages/playground/cli/src/run-cli.ts | 68 ++++++++++++++++--- packages/playground/cli/vite.config.ts | 46 +++++++++++++ .../commonjs-and-jest/tests/wp.spec.ts | 8 ++- .../es-modules-and-vitest/tests/wp.spec.ts | 33 ++++++++- 6 files changed, 156 insertions(+), 45 deletions(-) diff --git a/packages/playground/cli/src/blueprints-v1/blueprints-v1-handler.ts b/packages/playground/cli/src/blueprints-v1/blueprints-v1-handler.ts index fc0cb976b9..ae1dc739ba 100644 --- a/packages/playground/cli/src/blueprints-v1/blueprints-v1-handler.ts +++ b/packages/playground/cli/src/blueprints-v1/blueprints-v1-handler.ts @@ -18,10 +18,13 @@ import { readAsFile, } from './download'; import type { PlaygroundCliBlueprintV1Worker } from './worker-thread-v1'; -// @ts-ignore -import importedWorkerV1UrlString from './worker-thread-v1?worker&url'; import type { MessagePort as NodeMessagePort } from 'worker_threads'; -import { LogVerbosity, type RunCLIArgs, type SpawnedWorker } from '../run-cli'; +import { + LogVerbosity, + type RunCLIArgs, + type SpawnedWorker, + type WorkerType, +} from '../run-cli'; /** * Boots Playground CLI workers using Blueprint version 1. @@ -49,20 +52,8 @@ export class BlueprintsV1Handler { this.processIdSpaceLength = options.processIdSpaceLength; } - getWorkerUrl() { - if ( - process.env['VITEST'] && - importedWorkerV1UrlString.startsWith('/src/') - ) { - // Work around issue where Vitest cannot find the worker script. - return path.join( - import.meta.dirname, - '..', - '..', - importedWorkerV1UrlString - ); - } - return importedWorkerV1UrlString; + getWorkerType(): WorkerType { + return 'v1'; } async bootPrimaryWorker( diff --git a/packages/playground/cli/src/blueprints-v2/blueprints-v2-handler.ts b/packages/playground/cli/src/blueprints-v2/blueprints-v2-handler.ts index 332855bf6b..71623445d5 100644 --- a/packages/playground/cli/src/blueprints-v2/blueprints-v2-handler.ts +++ b/packages/playground/cli/src/blueprints-v2/blueprints-v2-handler.ts @@ -4,11 +4,8 @@ import type { PlaygroundCliBlueprintV2Worker, WorkerBootArgs, } from './worker-thread-v2'; -// @ts-ignore -import importedWorkerV2UrlString from './worker-thread-v2?worker&url'; import type { MessagePort as NodeMessagePort } from 'worker_threads'; -import type { RunCLIArgs, SpawnedWorker } from '../run-cli'; -import path from 'path'; +import type { RunCLIArgs, SpawnedWorker, WorkerType } from '../run-cli'; /** * Boots Playground CLI workers using Blueprint version 2. @@ -37,20 +34,8 @@ export class BlueprintsV2Handler { this.phpVersion = args.php as SupportedPHPVersion; } - getWorkerUrl() { - if ( - process.env['VITEST'] && - importedWorkerV2UrlString.startsWith('/src/') - ) { - // Work around issue where Vitest cannot find the worker script. - return path.join( - import.meta.dirname, - '..', - '..', - importedWorkerV2UrlString - ); - } - return importedWorkerV2UrlString; + getWorkerType(): WorkerType { + return 'v2'; } async bootPrimaryWorker( diff --git a/packages/playground/cli/src/run-cli.ts b/packages/playground/cli/src/run-cli.ts index 9b2819a7d9..8b968d991a 100644 --- a/packages/playground/cli/src/run-cli.ts +++ b/packages/playground/cli/src/run-cli.ts @@ -48,6 +48,12 @@ import { resolveBlueprint } from './resolve-blueprint'; import { BlueprintsV2Handler } from './blueprints-v2/blueprints-v2-handler'; import { BlueprintsV1Handler } from './blueprints-v1/blueprints-v1-handler'; import { startBridge } from '@php-wasm/xdebug-bridge'; +import path from 'path'; + +// Inlined worker URLs for static analysis by downstream bundlers +// These are replaced at build time by the Vite plugin in vite.config.ts +declare const __WORKER_V1_URL__: string; +declare const __WORKER_V2_URL__: string; export const LogVerbosity = { Quiet: { name: 'quiet', severity: LogSeverity.Fatal }, @@ -57,6 +63,8 @@ export const LogVerbosity = { type LogVerbosity = (typeof LogVerbosity)[keyof typeof LogVerbosity]['name']; +export type WorkerType = 'v1' | 'v2'; + export async function parseOptionsAndRunCLI() { try { /** @@ -554,8 +562,8 @@ export async function runCLI(args: RunCLIArgs): Promise { // Kick off worker threads now to save time later. // There is no need to wait for other async processes to complete. const promisedWorkers = spawnWorkerThreads( - handler.getWorkerUrl(), totalWorkerCount, + handler.getWorkerType(), ({ exitCode, isMain, workerIndex }) => { if (exitCode === 0) { return; @@ -756,20 +764,18 @@ export type SpawnedWorker = { worker: Worker; phpPort: NodeMessagePort; }; -function spawnWorkerThreads( - workerUrlString: string, +async function spawnWorkerThreads( count: number, + workerType: WorkerType, onWorkerExit: (options: { exitCode: number; isMain: boolean; workerIndex: number; }) => void ): Promise { - const moduleWorkerUrl = new URL(workerUrlString, import.meta.url); - const promises = []; for (let i = 0; i < count; i++) { - const worker = new Worker(moduleWorkerUrl); + const worker = await spawnWorkerThread(workerType); const onExit: (code: number) => void = (code: number) => { onWorkerExit({ exitCode: code, @@ -791,11 +797,10 @@ function spawnWorkerThreads( worker.once('error', function (e: Error) { console.error(e); const error = new Error( - `Worker failed to load at ${moduleWorkerUrl}. ${ + `Worker failed to load worker. ${ e.message ? `Original error: ${e.message}` : '' }` ); - (error as any).filename = moduleWorkerUrl; reject(error); }); worker.once('exit', onExit); @@ -806,6 +811,53 @@ function spawnWorkerThreads( return Promise.all(promises); } +/** + * A statically analyzable function that spawns a worker thread of a given type. + * + * **Important:** This function builds to code that has the worker URL hardcoded + * inline, e.g. `new Worker(new URL('./worker-thread-v1.js', import.meta.url))`. + * This allows the downstream consumers to statically analyze the code, recognize + * it uses workers, create new entrypoints, and rewrite the new Worker() calls. + * + * @param workerType + * @returns + */ +async function spawnWorkerThread(workerType: 'v1' | 'v2') { + if (workerType === 'v1') { + if (process.env['VITEST'] && __WORKER_V1_URL__.startsWith('/src/')) { + // Work around issue where Vitest cannot find the worker script. + return new Worker( + new URL( + path.join( + import.meta.dirname, + '..', + '..', + __WORKER_V1_URL__ + ), + import.meta.url + ) + ); + } + return new Worker(new URL(__WORKER_V1_URL__, import.meta.url)); + } else { + if (process.env['VITEST'] && __WORKER_V2_URL__.startsWith('/src/')) { + // Work around issue where Vitest cannot find the worker script. + return new Worker( + new URL( + path.join( + import.meta.dirname, + '..', + '..', + __WORKER_V2_URL__ + ), + import.meta.url + ) + ); + } + return new Worker(new URL(__WORKER_V2_URL__, import.meta.url)); + } +} + /** * Expose the file lock manager API on a MessagePort and return it. * diff --git a/packages/playground/cli/vite.config.ts b/packages/playground/cli/vite.config.ts index 241f4f51b2..a54e5e30a9 100644 --- a/packages/playground/cli/vite.config.ts +++ b/packages/playground/cli/vite.config.ts @@ -51,6 +51,52 @@ const plugins = [ viteTsConfigPaths({ root: '../../../', }), + /** + * Inline worker URLs as string literals so downstream bundlers (e.g., webpack) + * can statically analyze `new Worker(new URL('...'))`. + * + * We emit different extensions per output format: + * - ES modules: .js + * - CommonJS: .cjs + */ + { + name: 'inline-worker-url-literals', + renderChunk(code, _chunk, outputOptions) { + const format = (outputOptions as any).format as string | undefined; + const isCjs = format === 'cjs'; + const v1 = isCjs + ? './worker-thread-v1.cjs' + : './worker-thread-v1.js'; + const v2 = isCjs + ? './worker-thread-v2.cjs' + : './worker-thread-v2.js'; + let transformed = code; + // Replace macro tokens if used + transformed = transformed + .split('__WORKER_V1_URL__') + .join(JSON.stringify(v1)); + transformed = transformed + .split('__WORKER_V2_URL__') + .join(JSON.stringify(v2)); + // Replace usages of imported worker URL strings inside new URL(...) + const patternV1 = + /new\s+URL\(\s*importedWorkerV1UrlString\s*,\s*import\.meta\.url\s*\)/g; + const patternV2 = + /new\s+URL\(\s*importedWorkerV2UrlString\s*,\s*import\.meta\.url\s*\)/g; + transformed = transformed.replace( + patternV1, + `new URL(${JSON.stringify(v1)}, import.meta.url)` + ); + transformed = transformed.replace( + patternV2, + `new URL(${JSON.stringify(v2)}, import.meta.url)` + ); + if (transformed !== code) { + return { code: transformed, map: null }; + } + return null; + }, + }, /** * In library mode, Vite bundles all `?url` imports as JS modules with a single, * base64 export. blueprints.phar is too large for that. We need to preserve it diff --git a/packages/playground/test-built-npm-packages/commonjs-and-jest/tests/wp.spec.ts b/packages/playground/test-built-npm-packages/commonjs-and-jest/tests/wp.spec.ts index d221a98da4..eb3267dee9 100644 --- a/packages/playground/test-built-npm-packages/commonjs-and-jest/tests/wp.spec.ts +++ b/packages/playground/test-built-npm-packages/commonjs-and-jest/tests/wp.spec.ts @@ -29,8 +29,14 @@ SupportedPHPVersions.filter( }, 30000); }); + /** + * Very the built Playground packages ship worker files that have stable names. + * This is important for downstream consumers that may need to statically declare + * a separate entrypoint for each worker file. Including a hash in the filename, + * e.g. `worker-thread-v1-af872f.cjs`, would break their build config on every + * @wp-playground/cli release. + */ it('Should include required worker thread files in CLI package', () => { - // Verify that the Playground CLI package ships with the required worker thread files const requiredFiles = ['worker-thread-v1.cjs', 'worker-thread-v2.cjs']; for (const file of requiredFiles) { diff --git a/packages/playground/test-built-npm-packages/es-modules-and-vitest/tests/wp.spec.ts b/packages/playground/test-built-npm-packages/es-modules-and-vitest/tests/wp.spec.ts index d7a9a3e4a5..104084c5bb 100644 --- a/packages/playground/test-built-npm-packages/es-modules-and-vitest/tests/wp.spec.ts +++ b/packages/playground/test-built-npm-packages/es-modules-and-vitest/tests/wp.spec.ts @@ -39,8 +39,14 @@ describe(`PHP ${phpVersion}`, () => { } }); + /** + * Very the built Playground packages ship worker files that have stable names. + * This is important for downstream consumers that may need to statically declare + * a separate entrypoint for each worker file. Including a hash in the filename, + * e.g. `worker-thread-v1-af872f.cjs`, would break their build config on every + * @wp-playground/cli release. + */ it('Should include required worker thread files in CLI package', async () => { - // Verify that the Playground CLI package ships with the required worker thread files const requiredFiles = ['worker-thread-v1.js', 'worker-thread-v2.js']; for (const file of requiredFiles) { @@ -58,4 +64,29 @@ describe(`PHP ${phpVersion}`, () => { } } }); + + it('Should have a new URL("./worker-thread-v1.js", import.meta.url) string', async () => { + const staticStrings = { + 'worker-thread-v1.js': + 'new URL("./worker-thread-v1.js", import.meta.url)', + 'worker-thread-v2.js': + 'new URL("./worker-thread-v2.js", import.meta.url)', + }; + for (const file of Object.keys(staticStrings)) { + try { + // Resolve the file from the CLI package without importing it + const baseUrl = import.meta.resolve(`@wp-playground/cli`); + const url = new URL(file, baseUrl); + const path = fileURLToPath(url); + assert.ok( + staticStrings[file].includes(path), + `Static string for ${file} is not correct` + ); + } catch (error) { + assert.fail( + `Static string for ${file} is not correct: ${error.message}` + ); + } + } + }); }); From 504b55054ef955b6a3855b06d02adf7e36005a9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Mon, 15 Sep 2025 17:32:49 +0200 Subject: [PATCH 02/10] Fix running CLI from source --- packages/playground/cli/src/run-cli.ts | 13 +++++++++++++ .../es-modules-and-vitest/tests/wp.spec.ts | 15 +++++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/packages/playground/cli/src/run-cli.ts b/packages/playground/cli/src/run-cli.ts index 8b968d991a..6456f36421 100644 --- a/packages/playground/cli/src/run-cli.ts +++ b/packages/playground/cli/src/run-cli.ts @@ -823,6 +823,19 @@ async function spawnWorkerThreads( * @returns */ async function spawnWorkerThread(workerType: 'v1' | 'v2') { + /** + * When running the CLI from source via `node cli.ts`, the Vite-provided + * __WORKER_V1_URL__ and __WORKER_V2_URL__ are undefined. Let's set them to + * the correct paths. + */ + if (typeof __WORKER_V1_URL__ === 'undefined') { + // @ts-expect-error + globalThis['__WORKER_V1_URL__'] = './blueprints-v1/worker-thread-v1.ts'; + } + if (typeof __WORKER_V2_URL__ === 'undefined') { + // @ts-expect-error + globalThis['__WORKER_V2_URL__'] = './blueprints-v2/worker-thread-v2.ts'; + } if (workerType === 'v1') { if (process.env['VITEST'] && __WORKER_V1_URL__.startsWith('/src/')) { // Work around issue where Vitest cannot find the worker script. diff --git a/packages/playground/test-built-npm-packages/es-modules-and-vitest/tests/wp.spec.ts b/packages/playground/test-built-npm-packages/es-modules-and-vitest/tests/wp.spec.ts index 104084c5bb..c7396f9113 100644 --- a/packages/playground/test-built-npm-packages/es-modules-and-vitest/tests/wp.spec.ts +++ b/packages/playground/test-built-npm-packages/es-modules-and-vitest/tests/wp.spec.ts @@ -40,7 +40,7 @@ describe(`PHP ${phpVersion}`, () => { }); /** - * Very the built Playground packages ship worker files that have stable names. + * Verify the built Playground packages ship worker files that have stable names. * This is important for downstream consumers that may need to statically declare * a separate entrypoint for each worker file. Including a hash in the filename, * e.g. `worker-thread-v1-af872f.cjs`, would break their build config on every @@ -65,7 +65,14 @@ describe(`PHP ${phpVersion}`, () => { } }); - it('Should have a new URL("./worker-thread-v1.js", import.meta.url) string', async () => { + /** + * Verify the workers are loaded in a way that can be statically analyzed by + * downstream bundlers. Without this, bundling an app relying on Playground CLI + * is challenging as the consumer must handle detecting and chunking workers and + * also rewrite their target URL. + */ + it('Should load workers using a new URL("./worker-thread-v1.js", import.meta.url) string', async () => { + // @TODO: Also verify this is wrapped in a new Worker() call. const staticStrings = { 'worker-thread-v1.js': 'new URL("./worker-thread-v1.js", import.meta.url)', @@ -80,11 +87,11 @@ describe(`PHP ${phpVersion}`, () => { const path = fileURLToPath(url); assert.ok( staticStrings[file].includes(path), - `Static string for ${file} is not correct` + `Workers are not loaded in a statically analyzable way for ${file}` ); } catch (error) { assert.fail( - `Static string for ${file} is not correct: ${error.message}` + `Workers are not loaded in a statically analyzable way for ${file}: ${error.message}` ); } } From 30ad4e0a3edf11ef06c1526e3e8e2cc23e9fe87b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Mon, 15 Sep 2025 17:36:04 +0200 Subject: [PATCH 03/10] Fix running CLI from source --- packages/playground/cli/src/run-cli.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/playground/cli/src/run-cli.ts b/packages/playground/cli/src/run-cli.ts index 6456f36421..b9d64db88a 100644 --- a/packages/playground/cli/src/run-cli.ts +++ b/packages/playground/cli/src/run-cli.ts @@ -829,12 +829,18 @@ async function spawnWorkerThread(workerType: 'v1' | 'v2') { * the correct paths. */ if (typeof __WORKER_V1_URL__ === 'undefined') { + // Need to split the __WORKER_V1_URL__ string in two parts to avoid Vite replacing + // it with a string literal. // @ts-expect-error - globalThis['__WORKER_V1_URL__'] = './blueprints-v1/worker-thread-v1.ts'; + globalThis['__WORKER_' + 'V1_URL__'] = + './blueprints-v1/worker-thread-v1.ts'; } if (typeof __WORKER_V2_URL__ === 'undefined') { + // Need to split the __WORKER_V2_URL__ string in two parts to avoid Vite replacing + // it with a string literal. // @ts-expect-error - globalThis['__WORKER_V2_URL__'] = './blueprints-v2/worker-thread-v2.ts'; + globalThis['__WORKER_' + 'V2_URL__'] = + './blueprints-v2/worker-thread-v2.ts'; } if (workerType === 'v1') { if (process.env['VITEST'] && __WORKER_V1_URL__.startsWith('/src/')) { From 05416d93944ee19930bb82e5f0ef0162a3415714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Mon, 15 Sep 2025 17:43:40 +0200 Subject: [PATCH 04/10] Fix running CLI from source --- packages/playground/cli/src/run-cli.ts | 6 ++---- packages/playground/cli/vite.config.ts | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/playground/cli/src/run-cli.ts b/packages/playground/cli/src/run-cli.ts index b9d64db88a..dd699b4244 100644 --- a/packages/playground/cli/src/run-cli.ts +++ b/packages/playground/cli/src/run-cli.ts @@ -832,15 +832,13 @@ async function spawnWorkerThread(workerType: 'v1' | 'v2') { // Need to split the __WORKER_V1_URL__ string in two parts to avoid Vite replacing // it with a string literal. // @ts-expect-error - globalThis['__WORKER_' + 'V1_URL__'] = - './blueprints-v1/worker-thread-v1.ts'; + globalThis['__WORKER_V1_URL__'] = './blueprints-v1/worker-thread-v1.ts'; } if (typeof __WORKER_V2_URL__ === 'undefined') { // Need to split the __WORKER_V2_URL__ string in two parts to avoid Vite replacing // it with a string literal. // @ts-expect-error - globalThis['__WORKER_' + 'V2_URL__'] = - './blueprints-v2/worker-thread-v2.ts'; + globalThis['__WORKER_V2_URL__'] = './blueprints-v2/worker-thread-v2.ts'; } if (workerType === 'v1') { if (process.env['VITEST'] && __WORKER_V1_URL__.startsWith('/src/')) { diff --git a/packages/playground/cli/vite.config.ts b/packages/playground/cli/vite.config.ts index a54e5e30a9..a9ce89ef2e 100644 --- a/packages/playground/cli/vite.config.ts +++ b/packages/playground/cli/vite.config.ts @@ -73,10 +73,10 @@ const plugins = [ let transformed = code; // Replace macro tokens if used transformed = transformed - .split('__WORKER_V1_URL__') + .split(/(? Date: Mon, 15 Sep 2025 16:35:02 -0400 Subject: [PATCH 05/10] Fix test --- .../es-modules-and-vitest/tests/wp.spec.ts | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/playground/test-built-npm-packages/es-modules-and-vitest/tests/wp.spec.ts b/packages/playground/test-built-npm-packages/es-modules-and-vitest/tests/wp.spec.ts index c7396f9113..bfef78b696 100644 --- a/packages/playground/test-built-npm-packages/es-modules-and-vitest/tests/wp.spec.ts +++ b/packages/playground/test-built-npm-packages/es-modules-and-vitest/tests/wp.spec.ts @@ -1,7 +1,8 @@ import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; -import { access } from 'node:fs/promises'; +import { access, readdir, readFile } from 'node:fs/promises'; import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; import { runCLI } from '@wp-playground/cli'; import type { SupportedPHPVersion } from '@php-wasm/universal'; import { SupportedPHPVersions } from '@php-wasm/universal'; @@ -75,18 +76,28 @@ describe(`PHP ${phpVersion}`, () => { // @TODO: Also verify this is wrapped in a new Worker() call. const staticStrings = { 'worker-thread-v1.js': - 'new URL("./worker-thread-v1.js", import.meta.url)', + 'new URL("worker-thread-v1.js", import.meta.url)', 'worker-thread-v2.js': - 'new URL("./worker-thread-v2.js", import.meta.url)', + 'new URL("worker-thread-v2.js", import.meta.url)', }; for (const file of Object.keys(staticStrings)) { try { // Resolve the file from the CLI package without importing it const baseUrl = import.meta.resolve(`@wp-playground/cli`); const url = new URL(file, baseUrl); - const path = fileURLToPath(url); + const moduleDir = dirname(fileURLToPath(url)); + const runCliModuleNames = (await readdir(moduleDir)).filter( + (name) => /^run-cli-[^.]+\.js$/.test(name) + ); + assert.equal( + runCliModuleNames.length, + 1, + `Only one run-cli .js file should be found in ${moduleDir}` + ); + const runCliPath = join(moduleDir, runCliModuleNames[0]); + const runCliModuleText = await readFile(runCliPath, 'utf8'); assert.ok( - staticStrings[file].includes(path), + runCliModuleText.includes(staticStrings[file]), `Workers are not loaded in a statically analyzable way for ${file}` ); } catch (error) { From b0c54f6ffb49c38ddf1051b1a66e1813c1f65bba Mon Sep 17 00:00:00 2001 From: Brandon Payton Date: Mon, 15 Sep 2025 16:36:51 -0400 Subject: [PATCH 06/10] Try to simplify and use static URLs without Vite replacements --- packages/playground/cli/src/run-cli.ts | 60 ++++---------------------- packages/playground/cli/vite.config.ts | 47 +------------------- 2 files changed, 9 insertions(+), 98 deletions(-) diff --git a/packages/playground/cli/src/run-cli.ts b/packages/playground/cli/src/run-cli.ts index dd699b4244..685fefd0d8 100644 --- a/packages/playground/cli/src/run-cli.ts +++ b/packages/playground/cli/src/run-cli.ts @@ -50,11 +50,6 @@ import { BlueprintsV1Handler } from './blueprints-v1/blueprints-v1-handler'; import { startBridge } from '@php-wasm/xdebug-bridge'; import path from 'path'; -// Inlined worker URLs for static analysis by downstream bundlers -// These are replaced at build time by the Vite plugin in vite.config.ts -declare const __WORKER_V1_URL__: string; -declare const __WORKER_V2_URL__: string; - export const LogVerbosity = { Quiet: { name: 'quiet', severity: LogSeverity.Fatal }, Normal: { name: 'normal', severity: LogSeverity.Info }, @@ -823,55 +818,16 @@ async function spawnWorkerThreads( * @returns */ async function spawnWorkerThread(workerType: 'v1' | 'v2') { - /** - * When running the CLI from source via `node cli.ts`, the Vite-provided - * __WORKER_V1_URL__ and __WORKER_V2_URL__ are undefined. Let's set them to - * the correct paths. - */ - if (typeof __WORKER_V1_URL__ === 'undefined') { - // Need to split the __WORKER_V1_URL__ string in two parts to avoid Vite replacing - // it with a string literal. - // @ts-expect-error - globalThis['__WORKER_V1_URL__'] = './blueprints-v1/worker-thread-v1.ts'; - } - if (typeof __WORKER_V2_URL__ === 'undefined') { - // Need to split the __WORKER_V2_URL__ string in two parts to avoid Vite replacing - // it with a string literal. - // @ts-expect-error - globalThis['__WORKER_V2_URL__'] = './blueprints-v2/worker-thread-v2.ts'; - } if (workerType === 'v1') { - if (process.env['VITEST'] && __WORKER_V1_URL__.startsWith('/src/')) { - // Work around issue where Vitest cannot find the worker script. - return new Worker( - new URL( - path.join( - import.meta.dirname, - '..', - '..', - __WORKER_V1_URL__ - ), - import.meta.url - ) - ); - } - return new Worker(new URL(__WORKER_V1_URL__, import.meta.url)); + return new Worker( + new URL('./blueprints-v1/worker-thread-v1.ts', import.meta.url) + ); + } else if (workerType === 'v2') { + return new Worker( + new URL('./blueprints-v2/worker-thread-v2.ts', import.meta.url) + ); } else { - if (process.env['VITEST'] && __WORKER_V2_URL__.startsWith('/src/')) { - // Work around issue where Vitest cannot find the worker script. - return new Worker( - new URL( - path.join( - import.meta.dirname, - '..', - '..', - __WORKER_V2_URL__ - ), - import.meta.url - ) - ); - } - return new Worker(new URL(__WORKER_V2_URL__, import.meta.url)); + throw new Error(`Invalid worker type: ${workerType}`); } } diff --git a/packages/playground/cli/vite.config.ts b/packages/playground/cli/vite.config.ts index a9ce89ef2e..66b3416fa4 100644 --- a/packages/playground/cli/vite.config.ts +++ b/packages/playground/cli/vite.config.ts @@ -51,52 +51,7 @@ const plugins = [ viteTsConfigPaths({ root: '../../../', }), - /** - * Inline worker URLs as string literals so downstream bundlers (e.g., webpack) - * can statically analyze `new Worker(new URL('...'))`. - * - * We emit different extensions per output format: - * - ES modules: .js - * - CommonJS: .cjs - */ - { - name: 'inline-worker-url-literals', - renderChunk(code, _chunk, outputOptions) { - const format = (outputOptions as any).format as string | undefined; - const isCjs = format === 'cjs'; - const v1 = isCjs - ? './worker-thread-v1.cjs' - : './worker-thread-v1.js'; - const v2 = isCjs - ? './worker-thread-v2.cjs' - : './worker-thread-v2.js'; - let transformed = code; - // Replace macro tokens if used - transformed = transformed - .split(/(? Date: Mon, 15 Sep 2025 19:53:14 -0400 Subject: [PATCH 07/10] Fix linting error --- packages/playground/cli/src/run-cli.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/playground/cli/src/run-cli.ts b/packages/playground/cli/src/run-cli.ts index 685fefd0d8..d451fe767e 100644 --- a/packages/playground/cli/src/run-cli.ts +++ b/packages/playground/cli/src/run-cli.ts @@ -48,7 +48,6 @@ import { resolveBlueprint } from './resolve-blueprint'; import { BlueprintsV2Handler } from './blueprints-v2/blueprints-v2-handler'; import { BlueprintsV1Handler } from './blueprints-v1/blueprints-v1-handler'; import { startBridge } from '@php-wasm/xdebug-bridge'; -import path from 'path'; export const LogVerbosity = { Quiet: { name: 'quiet', severity: LogSeverity.Fatal }, From d066d2a71a7469b403e24183945dbe5468c3a38b Mon Sep 17 00:00:00 2001 From: Brandon Payton Date: Mon, 15 Sep 2025 22:58:29 -0400 Subject: [PATCH 08/10] Try a simplification that does not break webpack expectations --- packages/playground/cli/src/run-cli.ts | 32 +++++++++---- packages/playground/cli/vite.config.ts | 47 ++++++++++++++++++- .../es-modules-and-vitest/tests/wp.spec.ts | 4 +- 3 files changed, 72 insertions(+), 11 deletions(-) diff --git a/packages/playground/cli/src/run-cli.ts b/packages/playground/cli/src/run-cli.ts index d451fe767e..315dedcbbc 100644 --- a/packages/playground/cli/src/run-cli.ts +++ b/packages/playground/cli/src/run-cli.ts @@ -49,6 +49,11 @@ import { BlueprintsV2Handler } from './blueprints-v2/blueprints-v2-handler'; import { BlueprintsV1Handler } from './blueprints-v1/blueprints-v1-handler'; import { startBridge } from '@php-wasm/xdebug-bridge'; +// Inlined worker URLs for static analysis by downstream bundlers +// These are replaced at build time by the Vite plugin in vite.config.ts +declare const __WORKER_V1_URL__: string; +declare const __WORKER_V2_URL__: string; + export const LogVerbosity = { Quiet: { name: 'quiet', severity: LogSeverity.Fatal }, Normal: { name: 'normal', severity: LogSeverity.Info }, @@ -817,16 +822,27 @@ async function spawnWorkerThreads( * @returns */ async function spawnWorkerThread(workerType: 'v1' | 'v2') { + /** + * When running the CLI from source via `node cli.ts`, the Vite-provided + * __WORKER_V1_URL__ and __WORKER_V2_URL__ are undefined. Let's set them to + * the correct paths. + */ + if (typeof __WORKER_V1_URL__ === 'undefined') { + // Need to split the __WORKER_V1_URL__ string in two parts to avoid Vite replacing + // it with a string literal. + // @ts-expect-error + globalThis['__WORKER_V1_URL__'] = './blueprints-v1/worker-thread-v1.ts'; + } + if (typeof __WORKER_V2_URL__ === 'undefined') { + // Need to split the __WORKER_V2_URL__ string in two parts to avoid Vite replacing + // it with a string literal. + // @ts-expect-error + globalThis['__WORKER_V2_URL__'] = './blueprints-v2/worker-thread-v2.ts'; + } if (workerType === 'v1') { - return new Worker( - new URL('./blueprints-v1/worker-thread-v1.ts', import.meta.url) - ); - } else if (workerType === 'v2') { - return new Worker( - new URL('./blueprints-v2/worker-thread-v2.ts', import.meta.url) - ); + return new Worker(new URL(__WORKER_V1_URL__, import.meta.url)); } else { - throw new Error(`Invalid worker type: ${workerType}`); + return new Worker(new URL(__WORKER_V2_URL__, import.meta.url)); } } diff --git a/packages/playground/cli/vite.config.ts b/packages/playground/cli/vite.config.ts index 66b3416fa4..a9ce89ef2e 100644 --- a/packages/playground/cli/vite.config.ts +++ b/packages/playground/cli/vite.config.ts @@ -51,7 +51,52 @@ const plugins = [ viteTsConfigPaths({ root: '../../../', }), - + /** + * Inline worker URLs as string literals so downstream bundlers (e.g., webpack) + * can statically analyze `new Worker(new URL('...'))`. + * + * We emit different extensions per output format: + * - ES modules: .js + * - CommonJS: .cjs + */ + { + name: 'inline-worker-url-literals', + renderChunk(code, _chunk, outputOptions) { + const format = (outputOptions as any).format as string | undefined; + const isCjs = format === 'cjs'; + const v1 = isCjs + ? './worker-thread-v1.cjs' + : './worker-thread-v1.js'; + const v2 = isCjs + ? './worker-thread-v2.cjs' + : './worker-thread-v2.js'; + let transformed = code; + // Replace macro tokens if used + transformed = transformed + .split(/(? { // @TODO: Also verify this is wrapped in a new Worker() call. const staticStrings = { 'worker-thread-v1.js': - 'new URL("worker-thread-v1.js", import.meta.url)', + 'new URL("./worker-thread-v1.js", import.meta.url)', 'worker-thread-v2.js': - 'new URL("worker-thread-v2.js", import.meta.url)', + 'new URL("./worker-thread-v2.js", import.meta.url)', }; for (const file of Object.keys(staticStrings)) { try { From 16aab3e3a796abed683facd785792a67108fe0a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 16 Sep 2025 10:46:39 +0200 Subject: [PATCH 09/10] Clean up comments for worker URL assignments Removed comments about splitting URL strings to avoid Vite replacement. --- packages/playground/cli/src/run-cli.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/playground/cli/src/run-cli.ts b/packages/playground/cli/src/run-cli.ts index 315dedcbbc..a2637ffe21 100644 --- a/packages/playground/cli/src/run-cli.ts +++ b/packages/playground/cli/src/run-cli.ts @@ -828,14 +828,10 @@ async function spawnWorkerThread(workerType: 'v1' | 'v2') { * the correct paths. */ if (typeof __WORKER_V1_URL__ === 'undefined') { - // Need to split the __WORKER_V1_URL__ string in two parts to avoid Vite replacing - // it with a string literal. // @ts-expect-error globalThis['__WORKER_V1_URL__'] = './blueprints-v1/worker-thread-v1.ts'; } if (typeof __WORKER_V2_URL__ === 'undefined') { - // Need to split the __WORKER_V2_URL__ string in two parts to avoid Vite replacing - // it with a string literal. // @ts-expect-error globalThis['__WORKER_V2_URL__'] = './blueprints-v2/worker-thread-v2.ts'; } From f305af60ced5147362abe2f0994726737da69535 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 16 Sep 2025 13:48:09 +0200 Subject: [PATCH 10/10] Migrate wp-playground/cli to rsbuild --- package-lock.json | 346 +++++++++++++++++++++- package.json | 1 + packages/playground/cli/project.json | 41 ++- packages/playground/cli/rsbuild.config.ts | 133 +++++++++ packages/playground/cli/src/run-cli.ts | 21 +- 5 files changed, 506 insertions(+), 36 deletions(-) create mode 100644 packages/playground/cli/rsbuild.config.ts diff --git a/package-lock.json b/package-lock.json index 2b327d0b7e..691166c1a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,6 +57,7 @@ "@nx/vite": "19.8.4", "@nx/workspace": "19.8.4", "@playwright/test": "1.47.1", + "@rsbuild/core": "^1.1.9", "@types/ajv": "1.0.0", "@types/file-saver": "^2.0.5", "@types/fs-ext": "2.0.3", @@ -4936,20 +4937,20 @@ } }, "node_modules/@emnapi/core": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", - "integrity": "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.5.0.tgz", + "integrity": "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==", "dev": true, "license": "MIT", "dependencies": { - "@emnapi/wasi-threads": "1.0.2", + "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", - "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", + "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4957,9 +4958,9 @@ } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz", - "integrity": "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8677,6 +8678,31 @@ "@module-federation/sdk": "0.6.16" } }, + "node_modules/@module-federation/runtime-core": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-core/-/runtime-core-0.18.0.tgz", + "integrity": "sha512-ZyYhrDyVAhUzriOsVfgL6vwd+5ebYm595Y13KeMf6TKDRoUHBMTLGQ8WM4TDj8JNsy7LigncK8C03fn97of0QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/error-codes": "0.18.0", + "@module-federation/sdk": "0.18.0" + } + }, + "node_modules/@module-federation/runtime-core/node_modules/@module-federation/error-codes": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-0.18.0.tgz", + "integrity": "sha512-Woonm8ehyVIUPXChmbu80Zj6uJkC0dD9SJUZ/wOPtO8iiz/m+dkrOugAuKgoiR6qH4F+yorWila954tBz4uKsQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@module-federation/runtime-core/node_modules/@module-federation/sdk": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-0.18.0.tgz", + "integrity": "sha512-Lo/Feq73tO2unjmpRfyyoUkTVoejhItXOk/h5C+4cistnHbTV8XHrW/13fD5e1Iu60heVdAhhelJd6F898Ve9A==", + "dev": true, + "license": "MIT" + }, "node_modules/@module-federation/runtime-tools": { "version": "0.6.16", "resolved": "https://registry.npmjs.org/@module-federation/runtime-tools/-/runtime-tools-0.6.16.tgz", @@ -13599,6 +13625,300 @@ "win32" ] }, + "node_modules/@rsbuild/core": { + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-1.5.7.tgz", + "integrity": "sha512-1/yyJJfZo4hqMsL3WQQmMDYFp0L/znHqjHrYE6NKsiKhkBEwEwSVMk1M5QoRu2EcRL1acW5AJf7WJyKFfPZ//Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rspack/core": "1.5.4", + "@rspack/lite-tapable": "~1.0.1", + "@swc/helpers": "^0.5.17", + "core-js": "~3.45.1", + "jiti": "^2.5.1" + }, + "bin": { + "rsbuild": "bin/rsbuild.js" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/@rsbuild/core/node_modules/jiti": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", + "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/@rspack/binding": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-1.5.4.tgz", + "integrity": "sha512-HtLF5uxbf77hDarB/Wl26XgaTyWkhMogDPUOC1mLU+YPke1vYem8p8yr+McUkRtbhYoqtFMcVcT3S8jKJPP3+g==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "@rspack/binding-darwin-arm64": "1.5.4", + "@rspack/binding-darwin-x64": "1.5.4", + "@rspack/binding-linux-arm64-gnu": "1.5.4", + "@rspack/binding-linux-arm64-musl": "1.5.4", + "@rspack/binding-linux-x64-gnu": "1.5.4", + "@rspack/binding-linux-x64-musl": "1.5.4", + "@rspack/binding-wasm32-wasi": "1.5.4", + "@rspack/binding-win32-arm64-msvc": "1.5.4", + "@rspack/binding-win32-ia32-msvc": "1.5.4", + "@rspack/binding-win32-x64-msvc": "1.5.4" + } + }, + "node_modules/@rspack/binding-darwin-arm64": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.5.4.tgz", + "integrity": "sha512-qD+n4D8KOOSoWdngK87iXl6lqbx1J63f6/xZFLPVIstzxIUbNyo9V9tpJYsoT3gYpnLkPVqA+KwQI0ozgYEXvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rspack/binding-darwin-x64": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.5.4.tgz", + "integrity": "sha512-g75qkrLLa28kVp7pkWAjUADwr+0GumEF134VWHuL+TAm7VCw4IXRKnZhquE8K5kcqRpLcLX4guRqZzK9OEu/hg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rspack/binding-linux-arm64-gnu": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.5.4.tgz", + "integrity": "sha512-O3zSTz/dy1EJHd7YS8zzmAG2zxewEZJi7QlYiU+YhFuqjP2ab6ZFWLHkglvrSy4aHyC8fx9OkSjioYtHUcCSdQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rspack/binding-linux-arm64-musl": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.5.4.tgz", + "integrity": "sha512-ki84vbRY1gbf1T3BHiKAdi3m0hQFmqiAIYvFuLGA9Vop1R+W2C3Mzh8Q5YL6TnWOP0eiwizuigztz4/07fPf6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rspack/binding-linux-x64-gnu": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.5.4.tgz", + "integrity": "sha512-SJVQSgR1JqDEnURI79SRcn/gcdG+yFb2mLUYV/TSPUTxMIlu44p5+fnOY6+6qMtjQhO6J4C2+UyV00U/yjlikA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rspack/binding-linux-x64-musl": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.5.4.tgz", + "integrity": "sha512-UL1xw3yLsFH6UD/ubXXbRaDRNl+qI22QgugKYuqmpDGfOcVlv4fGpf3faPwYJasqPjhDWvcoyd8OqI+ftWKWEA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rspack/binding-wasm32-wasi": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-1.5.4.tgz", + "integrity": "sha512-VPGhik1M87SZQzmX2sRvXrO6KgycSbmJ/bLqVuXHYGjsLkYqw4auKCJrkZcKa1GVsSvpVNC3FlTUk2QxjpmNSA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.0.1" + } + }, + "node_modules/@rspack/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.5.tgz", + "integrity": "sha512-TBr9Cf9onSAS2LQ2+QHx6XcC6h9+RIzJgbqG3++9TUZSH204AwEy5jg3BTQ0VATsyoGj4ee49tN/y6rvaOOtcg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.5.0", + "@emnapi/runtime": "^1.5.0", + "@tybys/wasm-util": "^0.10.1" + } + }, + "node_modules/@rspack/binding-wasm32-wasi/node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@rspack/binding-win32-arm64-msvc": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.5.4.tgz", + "integrity": "sha512-YxhK8dTv/6ff//C5Djm87TkiePuvGRoxLgsHgwR7C0rnA8lS5gLNwrNY9FjAY1x6WamnGGirFK97rigaeTDn+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rspack/binding-win32-ia32-msvc": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.5.4.tgz", + "integrity": "sha512-SU4EyAo1BI1zV/sSDF2cqoN+Qq6iIHLwtq0RJI5WQ4Yjn/mhhRFxNoerPCJUpPiiCxvG/IrpGzGi90MwFnMtNQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rspack/binding-win32-x64-msvc": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.5.4.tgz", + "integrity": "sha512-xEgOCnD2FCUcxRgg3X5etq81vvf8rWwvPASfrG234diSduvU6zRiuiyYFMLTMDwQNEzZEFGHp7wIZNCKHudbng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rspack/core": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@rspack/core/-/core-1.5.4.tgz", + "integrity": "sha512-s/bVG+KRZjIpPP2f4TOQkJ/D+rql7HAV0MFEWoqoyeNnln/p6I28RYbw5zYF+Qg4J0swR8Qk2pbn7qlIdGusLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/runtime-tools": "0.18.0", + "@rspack/binding": "1.5.4", + "@rspack/lite-tapable": "1.0.1" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.1" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@rspack/core/node_modules/@module-federation/error-codes": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-0.18.0.tgz", + "integrity": "sha512-Woonm8ehyVIUPXChmbu80Zj6uJkC0dD9SJUZ/wOPtO8iiz/m+dkrOugAuKgoiR6qH4F+yorWila954tBz4uKsQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rspack/core/node_modules/@module-federation/runtime": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-0.18.0.tgz", + "integrity": "sha512-+C4YtoSztM7nHwNyZl6dQKGUVJdsPrUdaf3HIKReg/GQbrt9uvOlUWo2NXMZ8vDAnf/QRrpSYAwXHmWDn9Obaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/error-codes": "0.18.0", + "@module-federation/runtime-core": "0.18.0", + "@module-federation/sdk": "0.18.0" + } + }, + "node_modules/@rspack/core/node_modules/@module-federation/runtime-tools": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-tools/-/runtime-tools-0.18.0.tgz", + "integrity": "sha512-fSga9o4t1UfXNV/Kh6qFvRyZpPp3EHSPRISNeyT8ZoTpzDNiYzhtw0BPUSSD8m6C6XQh2s/11rI4g80UY+d+hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/runtime": "0.18.0", + "@module-federation/webpack-bundler-runtime": "0.18.0" + } + }, + "node_modules/@rspack/core/node_modules/@module-federation/sdk": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-0.18.0.tgz", + "integrity": "sha512-Lo/Feq73tO2unjmpRfyyoUkTVoejhItXOk/h5C+4cistnHbTV8XHrW/13fD5e1Iu60heVdAhhelJd6F898Ve9A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rspack/core/node_modules/@module-federation/webpack-bundler-runtime": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.18.0.tgz", + "integrity": "sha512-TEvErbF+YQ+6IFimhUYKK3a5wapD90d90sLsNpcu2kB3QGT7t4nIluE25duXuZDVUKLz86tEPrza/oaaCWTpvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/runtime": "0.18.0", + "@module-federation/sdk": "0.18.0" + } + }, + "node_modules/@rspack/lite-tapable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rspack/lite-tapable/-/lite-tapable-1.0.1.tgz", + "integrity": "sha512-VynGOEsVw2s8TAlLf/uESfrgfrq2+rcXB1muPJYBWbsm1Oa6r5qVQhjA5ggM6z/coYPrsVMgovl3Ff7Q7OCp1w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -22961,9 +23281,9 @@ } }, "node_modules/core-js": { - "version": "3.41.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.41.0.tgz", - "integrity": "sha512-SJ4/EHwS36QMJd6h/Rg+GyR4A5xE0FSI3eZ+iBVpfqf1x0eTSg1smWLHrA+2jQThZSh97fmSgFSU8B61nxosxA==", + "version": "3.45.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.45.1.tgz", + "integrity": "sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==", "dev": true, "hasInstallScript": true, "license": "MIT", diff --git a/package.json b/package.json index 8f4f7a14c4..c145bac1c1 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,7 @@ "@nx/vite": "19.8.4", "@nx/workspace": "19.8.4", "@playwright/test": "1.47.1", + "@rsbuild/core": "^1.1.9", "@types/ajv": "1.0.0", "@types/file-saver": "^2.0.5", "@types/fs-ext": "2.0.3", diff --git a/packages/playground/cli/project.json b/packages/playground/cli/project.json index 3e066764ff..6812049876 100644 --- a/packages/playground/cli/project.json +++ b/packages/playground/cli/project.json @@ -26,20 +26,45 @@ } }, "build:bundle": { - "executor": "@nx/vite:build", - "outputs": ["{options.outputPath}"], + "executor": "nx:run-commands", + "outputs": ["dist/packages/playground/cli"], "options": { - "main": "dist/packages/playground/cli/src/cli.js", - "outputPath": "dist/packages/playground/cli" + "commands": [ + { + "command": "npx rsbuild build --config packages/playground/cli/rsbuild.config.ts --mode=production --environment=esm", + "forwardAllArgs": false + }, + { + "command": "tsc -p packages/playground/cli/tsconfig.lib.json --declaration --emitDeclarationOnly --outDir dist/packages/playground/cli", + "forwardAllArgs": false + }, + { + "command": "npx rsbuild build --config packages/playground/cli/rsbuild.config.ts --mode=production --environment=cjs", + "forwardAllArgs": false + } + ], + "parallel": false }, "defaultConfiguration": "production", "configurations": { "development": { - "minify": false + "commands": [ + { + "command": "npx rsbuild build --config packages/playground/cli/rsbuild.config.ts --mode=development --environment=esm", + "forwardAllArgs": false + }, + { + "command": "tsc -p packages/playground/cli/tsconfig.lib.json --declaration --emitDeclarationOnly --outDir dist/packages/playground/cli", + "forwardAllArgs": false + }, + { + "command": "npx rsbuild build --config packages/playground/cli/rsbuild.config.ts --mode=development --environment=cjs", + "forwardAllArgs": false + } + ], + "parallel": false }, - "production": { - "minify": true - } + "production": {} } }, "dev": { diff --git a/packages/playground/cli/rsbuild.config.ts b/packages/playground/cli/rsbuild.config.ts new file mode 100644 index 0000000000..41657f12fc --- /dev/null +++ b/packages/playground/cli/rsbuild.config.ts @@ -0,0 +1,133 @@ +import { readFileSync } from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { + type EnvironmentConfig, + defineConfig, + type RsbuildPlugin, +} from '@rsbuild/core'; + +// eslint-disable-next-line @nx/enforce-module-boundaries +import { getExternalModules } from '../../vite-extensions/vite-external-modules'; + +const filePath = fileURLToPath(import.meta.url); +const cliDir = path.dirname(filePath); +const repoRoot = path.resolve(cliDir, '../../..'); +const distRoot = path.join(repoRoot, 'dist/packages/playground/cli'); + +const tsconfigPath = path.join(repoRoot, 'tsconfig.base.json'); +const tsconfig = JSON.parse(readFileSync(tsconfigPath, 'utf-8')) as { + compilerOptions?: { paths?: Record }; +}; + +const aliasEntries = Object.entries(tsconfig.compilerOptions?.paths ?? {}) + .filter(([, values]) => Array.isArray(values) && values.length > 0) + .map(([key, values]) => [key, path.join(repoRoot, values[0])]); + +const alias = Object.fromEntries(aliasEntries); + +const sharedEntries = { + index: new URL('./src/index.ts', import.meta.url).toString(), + cli: new URL('./src/cli.ts', import.meta.url).toString(), + 'worker-thread-v1': new URL( + './src/blueprints-v1/worker-thread-v1.ts', + import.meta.url + ).toString(), + 'worker-thread-v2': new URL( + './src/blueprints-v2/worker-thread-v2.ts', + import.meta.url + ).toString(), +}; + +type BuildFormat = 'esm' | 'cjs'; + +const createLoadersPlugin = (format: BuildFormat): RsbuildPlugin => ({ + name: `playground-cli-loaders-${format}`, + setup(api) { + api.modifyRspackConfig((config) => { + const isCjs = format === 'cjs'; + + config.output = { + ...(config.output ?? {}), + filename: `[name]${isCjs ? '.cjs' : '.js'}`, + chunkFilename: `[name]-[contenthash]${isCjs ? '.cjs' : '.js'}`, + clean: !isCjs, + }; + + if (!isCjs) { + config.experiments = { + ...(config.experiments ?? {}), + outputModule: true, + }; + } + + config.target = 'node'; + + config.resolve = { + ...(config.resolve ?? {}), + extensions: [ + '.ts', + '.tsx', + '.js', + '.jsx', + '.mjs', + '.cjs', + '.json', + ], + alias: { + ...(config.resolve?.alias ?? {}), + ...alias, + }, + }; + + config.externalsType = isCjs ? 'commonjs' : 'module'; + config.externalsPresets = { + ...(config.externalsPresets ?? {}), + node: true, + }; + config.externals = getExternalModules(); + + const customRules = [ + { + test: /\.(ini|dat)$/i, + type: 'asset/resource', + }, + ]; + + const existingRules = config.module?.rules ?? []; + config.module = { + ...(config.module ?? {}), + rules: [...existingRules, ...customRules], + }; + }); + }, +}); + +const createEnvironmentConfig = (format: BuildFormat): EnvironmentConfig => { + return { + plugins: [createLoadersPlugin(format)], + source: { + entry: sharedEntries, + alias, + }, + output: { + distPath: { + root: distRoot, + }, + target: 'node', + sourceMap: true, + minify: false, + filenameHash: false, + cleanDistPath: format === 'esm', + dataUriLimit: 0, + }, + }; +}; + +export default defineConfig({ + environments: { + esm: createEnvironmentConfig('esm'), + cjs: createEnvironmentConfig('cjs'), + }, +}); diff --git a/packages/playground/cli/src/run-cli.ts b/packages/playground/cli/src/run-cli.ts index a2637ffe21..5826bb8b6a 100644 --- a/packages/playground/cli/src/run-cli.ts +++ b/packages/playground/cli/src/run-cli.ts @@ -822,23 +822,14 @@ async function spawnWorkerThreads( * @returns */ async function spawnWorkerThread(workerType: 'v1' | 'v2') { - /** - * When running the CLI from source via `node cli.ts`, the Vite-provided - * __WORKER_V1_URL__ and __WORKER_V2_URL__ are undefined. Let's set them to - * the correct paths. - */ - if (typeof __WORKER_V1_URL__ === 'undefined') { - // @ts-expect-error - globalThis['__WORKER_V1_URL__'] = './blueprints-v1/worker-thread-v1.ts'; - } - if (typeof __WORKER_V2_URL__ === 'undefined') { - // @ts-expect-error - globalThis['__WORKER_V2_URL__'] = './blueprints-v2/worker-thread-v2.ts'; - } if (workerType === 'v1') { - return new Worker(new URL(__WORKER_V1_URL__, import.meta.url)); + return new Worker( + new URL('./blueprints-v1/worker-thread-v1.ts', import.meta.url) + ); } else { - return new Worker(new URL(__WORKER_V2_URL__, import.meta.url)); + return new Worker( + new URL('./blueprints-v2/worker-thread-v2.ts', import.meta.url) + ); } }