From 09fb3fed69d2cd3aee9ce4841b87716587309ff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Mon, 28 Jul 2025 16:19:44 +0200 Subject: [PATCH] ensure distinguish between test module and test for client and server --- npm-packages/meteor-rspack/rspack.config.js | 52 +++++++++++---------- packages/rspack/lib/build-context.js | 50 +++++++++++++------- packages/rspack/lib/config.js | 5 +- packages/rspack/lib/processes.js | 40 +++++++--------- packages/rspack/rspack_plugin.js | 6 ++- packages/tools-core/lib/meteor.js | 8 ++++ 6 files changed, 94 insertions(+), 67 deletions(-) diff --git a/npm-packages/meteor-rspack/rspack.config.js b/npm-packages/meteor-rspack/rspack.config.js index fa86045aef..609a8bacbe 100644 --- a/npm-packages/meteor-rspack/rspack.config.js +++ b/npm-packages/meteor-rspack/rspack.config.js @@ -116,6 +116,7 @@ export default function (inMeteor = {}, argv = {}) { const isClient = Meteor.isClient; const isRun = Meteor.isRun; const isReactEnabled = Meteor.isReactEnabled; + const isTestModule = Meteor.isTestModule; const mode = isProd ? 'production' : 'development'; const isTypescriptEnabled = Meteor.isTypescriptEnabled || false; @@ -185,9 +186,26 @@ export default function (inMeteor = {}, argv = {}) { const reactRefreshModule = isReactEnabled ? safeRequire('@rspack/plugin-react-refresh') : null; + const requireExternalsPlugin = new RequireExternalsPlugin({ + filePath: path.join(buildContext, runPath), + ...(Meteor.isBlazeEnabled && { + externals: /\.html$/, + externalMap: (module) => { + const { request, context } = module; + if (request.endsWith('.html')) { + const relContext = path.relative(process.cwd(), context); + const { name } = path.parse(request); + return `./${relContext}/template.${name}.js`; + } + return request; + }, + }), + }); + + const clientNameConfig = `[${isTest && 'test-' || ''}${isTestModule && 'module' || 'client'}-rspack]`; // Base client config let clientConfig = { - name: '[client-rspack]', + name: clientNameConfig, target: 'web', mode, entry: path.resolve(process.cwd(), buildContext, entryPath), @@ -221,28 +239,12 @@ export default function (inMeteor = {}, argv = {}) { resolve: { extensions, alias }, externals, plugins: [ - ...(isRun - ? [ - ...(isReactEnabled && reactRefreshModule - ? [new reactRefreshModule()] - : []), - new RequireExternalsPlugin({ - filePath: path.join(buildContext, runPath), - ...(Meteor.isBlazeEnabled && { - externals: /\.html$/, - externalMap: (module) => { - const { request, context } = module; - if (request.endsWith('.html')) { - const relContext = path.relative(process.cwd(), context); - const { name } = path.parse(request); - return `./${relContext}/template.${name}.js`; - } - return request; - }, - }), - }), - ].filter(Boolean) - : []), + ...[ + ...(isReactEnabled && reactRefreshModule + ? [new reactRefreshModule()] + : []), + requireExternalsPlugin, + ].filter(Boolean), new DefinePlugin({ 'Meteor.isClient': JSON.stringify(true), 'Meteor.isServer': JSON.stringify(false), @@ -271,9 +273,10 @@ export default function (inMeteor = {}, argv = {}) { }), }; + const serverNameConfig = `[${isTest && 'test-' || ''}${isTestModule && 'module' || 'server'}-rspack]`; // Base server config let serverConfig = { - name: '[server-rspack]', + name: serverNameConfig, target: 'node', mode, entry: path.resolve(process.cwd(), buildContext, entryPath), @@ -313,6 +316,7 @@ export default function (inMeteor = {}, argv = {}) { banner: bannerOutput, entryOnly: true, }), + isTestModule && requireExternalsPlugin, ], watchOptions, devtool: isRun ? 'source-map' : 'hidden-source-map', diff --git a/packages/rspack/lib/build-context.js b/packages/rspack/lib/build-context.js index b708698495..4cc276d20b 100644 --- a/packages/rspack/lib/build-context.js +++ b/packages/rspack/lib/build-context.js @@ -107,12 +107,17 @@ export function ensureModuleFilesExist() { entryFile: initialEntrypoints.mainServer || '', outputFile: getBuildFilePath({ isMain: true, isServer: true, ...env, role: FILE_ROLE.output, onlyFilename: true }), }; - const testClientFiles = { - entryFile: initialEntrypoints.testClient || initialEntrypoints.testModule || '', + const isTestModule = initialEntrypoints.testModule != null; + const testModuleFiles = { + entryFile: initialEntrypoints.testModule || '', + outputFile: getBuildFilePath({ isTest: true, isTestModule: true, role: FILE_ROLE.output, onlyFilename: true }), + }; + const testClientFiles = isTestModule ? null : { + entryFile: initialEntrypoints.testClient || '', outputFile: getBuildFilePath({ isTest: true, isClient: true, role: FILE_ROLE.output, onlyFilename: true }), }; - const testServerFiles = { - entryFile: initialEntrypoints.testServer || initialEntrypoints.testModule || '', + const testServerFiles = isTestModule ? null : { + entryFile: initialEntrypoints.testServer || '', outputFile: getBuildFilePath({ isTest: true, isServer: true, role: FILE_ROLE.output, onlyFilename: true }), }; @@ -131,18 +136,27 @@ export function ensureModuleFilesExist() { [getBuildFilePath({ isMain: true, isServer: true, ...env, role: FILE_ROLE.output })]: getBuildFileContent({ isMain: true, isServer: true, ...env, role: FILE_ROLE.output, ...mainServerFiles }), /* Test module files for client and server */ - [getBuildFilePath({ isTest: true, isClient: true, ...commandRole })]: - getBuildFileContent({ isTest: true, isClient: true, ...commandRole, ...testClientFiles }), - [getBuildFilePath({ isTest: true, isClient: true, role: FILE_ROLE.entry })]: - getBuildFileContent({ isTest: true, isClient: true, role: FILE_ROLE.entry, ...testClientFiles }), - [getBuildFilePath({ isTest: true, isClient: true, role: FILE_ROLE.output })]: - getBuildFileContent({ isTest: true, isClient: true, role: FILE_ROLE.output, ...testClientFiles }), - [getBuildFilePath({ isTest: true, isServer: true, ...commandRole })]: - getBuildFileContent({ isTest: true, isServer: true, ...commandRole, ...testServerFiles }), - [getBuildFilePath({ isTest: true, isServer: true, role: FILE_ROLE.entry })]: - getBuildFileContent({ isTest: true, isServer: true, role: FILE_ROLE.entry, ...testServerFiles }), - [getBuildFilePath({ isTest: true, isServer: true, role: FILE_ROLE.output })]: - getBuildFileContent({ isTest: true, isServer: true, role: FILE_ROLE.output, ...testServerFiles }), + ...(isTestModule && { + [getBuildFilePath({ isTest: true, isTestModule: true, ...commandRole })]: + getBuildFileContent({ isTest: true, isTestModule: true, ...commandRole, ...testModuleFiles }), + [getBuildFilePath({ isTest: true, isTestModule: true, role: FILE_ROLE.entry })]: + getBuildFileContent({ isTest: true, isTestModule: true, role: FILE_ROLE.entry, ...testModuleFiles }), + [getBuildFilePath({ isTest: true, isTestModule: true, role: FILE_ROLE.output })]: + getBuildFileContent({ isTest: true, isTestModule: true, role: FILE_ROLE.output, ...testModuleFiles }), + } || { + [getBuildFilePath({ isTest: true, isClient: true, ...commandRole })]: + getBuildFileContent({ isTest: true, isClient: true, ...commandRole, ...testClientFiles }), + [getBuildFilePath({ isTest: true, isClient: true, role: FILE_ROLE.entry })]: + getBuildFileContent({ isTest: true, isClient: true, role: FILE_ROLE.entry, ...testClientFiles }), + [getBuildFilePath({ isTest: true, isClient: true, role: FILE_ROLE.output })]: + getBuildFileContent({ isTest: true, isClient: true, role: FILE_ROLE.output, ...testClientFiles }), + [getBuildFilePath({ isTest: true, isServer: true, ...commandRole })]: + getBuildFileContent({ isTest: true, isServer: true, ...commandRole, ...testServerFiles }), + [getBuildFilePath({ isTest: true, isServer: true, role: FILE_ROLE.entry })]: + getBuildFileContent({ isTest: true, isServer: true, role: FILE_ROLE.entry, ...testServerFiles }), + [getBuildFilePath({ isTest: true, isServer: true, role: FILE_ROLE.output })]: + getBuildFileContent({ isTest: true, isServer: true, role: FILE_ROLE.output, ...testServerFiles }), + }), }; Object.entries(moduleFiles).forEach(([filename, defaultContent]) => { @@ -190,7 +204,7 @@ export function ensureModuleFilesExist() { export function getBuildFilePath(config) { const module = config?.isTest ? 'test' : config?.isMain ? 'main' : ''; - const side = config?.isServer ? 'server' : config?.isClient ? 'client' : ''; + const side = config?.isTestModule ? 'test' : config?.isServer ? 'server' : config?.isClient ? 'client' : ''; const env = config?.isTest ? '' : config?.isDevelopment @@ -213,7 +227,7 @@ export function getBuildFilePath(config) { export function getBuildFileContent(config) { const module = config?.isTest ? 'test' : config?.isMain ? 'main' : ''; - const side = config?.isServer ? 'server' : config?.isClient ? 'client' : ''; + const side = config?.isTestModule ? 'test' : config?.isServer ? 'server' : config?.isClient ? 'client' : ''; const env = config?.isDevelopment ? 'development' : config?.isProduction ? 'production' : ''; const role = config?.role; diff --git a/packages/rspack/lib/config.js b/packages/rspack/lib/config.js index e9bf568fd5..bfeee2f68e 100644 --- a/packages/rspack/lib/config.js +++ b/packages/rspack/lib/config.js @@ -63,13 +63,14 @@ export function configureMeteorForRSPack() { const mainClientModule = getBuildFilePath({ isMain: true, ...env, ...commandRole, isClient: true }); const mainServerModule = getBuildFilePath({ isMain: true, ...env, ...commandRole, isServer: true }); const testClientModule = getBuildFilePath({ isTest: true, ...env, ...commandRole, isClient: true }); - const testServerModule = getBuildFilePath({ isTest: true, ...env, ...commandRole, isServer: true }); + const isTestModule = initialEntrypoints.testModule != null; + const testServerModule = getBuildFilePath({ isTest: true, ...env, ...commandRole, isTestModule, isServer: true }); // Set entry points in environment variables if they exist setMeteorAppEntrypoints({ mainClient: `${RSPACK_BUILD_CONTEXT}/${mainClientModule}`, mainServer: `${RSPACK_BUILD_CONTEXT}/${mainServerModule}`, - ...(initialEntrypoints.testModule && { + ...(isTestModule && { testModule: `${RSPACK_BUILD_CONTEXT}/${testServerModule}`, } || { testClient: `${RSPACK_BUILD_CONTEXT}/${testClientModule}`, diff --git a/packages/rspack/lib/processes.js b/packages/rspack/lib/processes.js index 2865713465..1dfc4c2d98 100644 --- a/packages/rspack/lib/processes.js +++ b/packages/rspack/lib/processes.js @@ -2,8 +2,6 @@ * @module processes * @description Functions for managing RSPack processes */ -import { RSPACK_BUILD_CONTEXT } from "./constants"; -import { getMeteorAppEntrypoints } from "../../tools-core/lib/meteor"; const { spawnProcess, @@ -28,6 +26,7 @@ const { isMeteorBlazeProject, isMeteorBlazeHotProject, getMeteorInitialAppEntrypoints, + isMeteorAppTestModule, } = require('meteor/tools-core/lib/meteor'); const { @@ -65,20 +64,23 @@ export function getConfigFileName() { export function getRSPackEnv({ isClient, isServer }) { const RSPACK_BUILD_CONTEXT = require('./constants').RSPACK_BUILD_CONTEXT; + const initialEntrypoints = getMeteorInitialAppEntrypoints(); + const isTest = isMeteorAppTest(); + const isTestModule = initialEntrypoints.testModule != null; + const module = isMeteorAppTest() ? { isTest: true } : { isMain: true }; const env = isMeteorAppDevelopment() ? { isDevelopment: true } : { isProduction: true }; - const side = isClient ? { isClient: true } : { isServer: true }; + const side = isTest && isTestModule ? { isTestModule: true } : isClient ? { isClient: true } : { isServer: true }; const commandRole = isMeteorAppRun() ? { role: FILE_ROLE.run } : isMeteorAppBuild() ? { role: FILE_ROLE.build } : { role: FILE_ROLE.run }; - const initialEntrypoints = getMeteorInitialAppEntrypoints(); - const entryKey = `${isMeteorAppTest() ? 'test' : 'main'}${isClient ? 'Client' : 'Server'}`; - const inputFilePath = initialEntrypoints[entryKey] || `testModule`; + const entryKey = `${isTest && isTestModule ? 'test' : 'main'}${isClient ? 'Client' : 'Server'}`; + const inputFilePath = isTest && isTestModule ? initialEntrypoints.testModule : initialEntrypoints[entryKey]; const isTypescriptEnabled = inputFilePath.endsWith('.ts') || inputFilePath.endsWith('.tsx'); const isTsxEnabled = inputFilePath.endsWith('.tsx'); const isJsxEnabled = inputFilePath.endsWith('.jsx'); @@ -88,6 +90,7 @@ export function getRSPackEnv({ isClient, isServer }) { ['isProduction', isMeteorAppProduction()], ['isDebug', isMeteorAppDebug()], ['isTest', isMeteorAppTest()], + ['isTestModule', isTestModule], ['isRun', isMeteorAppRun()], ['isBuild', isMeteorAppBuild()], ['isClient', isClient], @@ -140,9 +143,6 @@ export function startRSPackClientServe(options = {}) { const appDir = getMeteorAppDir(); const configFile = getConfigFileName(); - - logProgress(`[RSPack Client] Starting RSPack serve for client...`); - const newClientProcess = spawnProcess( 'npx', ['rspack', 'serve', '--config', configFile, ...getRSPackEnv({ isClient: true, isServer: false })], { @@ -195,9 +195,6 @@ export function startRSPackServerWatch(options = {}) { const appDir = getMeteorAppDir(); const configFile = getConfigFileName(); - - logProgress(`[RSPack Server] Starting RSPack for server...`); - const newServerProcess = spawnProcess( 'npx', ['rspack', 'build', '--watch', '--config', configFile, ...getRSPackEnv({ isClient: false, isServer: true })], { @@ -232,18 +229,17 @@ export function startRSPackServerWatch(options = {}) { * @param {Object} options - Options for the build * @param {boolean} options.isClient - Whether this is a client build * @param {boolean} options.isServer - Whether this is a server build + * @param {boolean} options.isTestModule - Whether this is a test module * @param {Function} options.onCompile - Callback function to be called when compilation is complete * @param {boolean} options.watch - Whether to run RSPack in watch mode * @returns {Promise} A promise that resolves when the build is complete * @throws {Error} If the build process fails */ -export async function runRSPackBuild({ isClient, isServer, onCompile, watch } = {}) { +export async function runRSPackBuild({ isClient, isServer, isTestModule, onCompile, watch, label = 'Build' } = {}) { const appDir = getMeteorAppDir(); const configFile = getConfigFileName(); - const endpoint = isClient ? 'Client' : 'Server'; - - logProgress(`Running RSPack build for ${endpoint}...`); + const endpoint = isTestModule ? 'Module' : isClient ? 'Client' : 'Server'; // Use a promise to ensure Meteor waits until RSPack finishes return new Promise((resolve, reject) => { spawnProcess( @@ -254,12 +250,12 @@ export async function runRSPackBuild({ isClient, isServer, onCompile, watch } = '--config', configFile, ...(watch && ['--watch']) || [], - ...getRSPackEnv({ isClient, isServer }), + ...getRSPackEnv({ isClient, isServer, isTestModule }), ].filter(Boolean), { cwd: appDir, onStdout: (data) => { - logInfo(`[RSPack Build ${endpoint}] ${data}`); + logInfo(`[RSPack ${label} ${endpoint}] ${data}`); if (onCompile && data.trim().includes("compiled")) { onCompile(data); } @@ -267,22 +263,22 @@ export async function runRSPackBuild({ isClient, isServer, onCompile, watch } = onStderr: (data) => { // Check if this is actually an informational message (like webpack-dev-server messages) if (data.includes('Project is running at:')) { - logInfo(`[RSPack Build ${endpoint}] ${data}`); + logInfo(`[RSPack ${label} ${endpoint}] ${data}`); } else { - logError(`[RSPack Build Error ${endpoint}] ${data}`); + logError(`[RSPack ${label} Error ${endpoint}] ${data}`); } }, onExit: (code) => { if (code === 0) { resolve(); } else { - const error = new Error(`RSPack build failed in ${endpoint} with exit code ${code}`); + const error = new Error(`RSPack ${label} failed in ${endpoint} with exit code ${code}`); logError(error.message); reject(error); } }, onError: (err) => { - logError(`RSPack Build ${endpoint} error: ${err.message}`); + logError(`RSPack ${label} ${endpoint} error: ${err.message}`); reject(err); } }); diff --git a/packages/rspack/rspack_plugin.js b/packages/rspack/rspack_plugin.js index baf5c50f8c..d698c536b8 100644 --- a/packages/rspack/rspack_plugin.js +++ b/packages/rspack/rspack_plugin.js @@ -130,10 +130,12 @@ try { if (initialEntrypoints?.testModule) { runRSPackBuild({ - isServer: true, + isTestModule: true, isClient: false, + isServer: true, watch: isMeteorAppTestWatch(), onCompile: onCompileServer, + label: 'Test', }); await waitForFirstCompilation(clientFirstCompile, serverFirstCompile, clientFirstCompilePromise, serverFirstCompilePromise, { target: 'server' }); } else if (initialEntrypoints?.testModule?.client || initialEntrypoints?.testModule?.server) { @@ -142,6 +144,7 @@ try { isServer: false, watch: isMeteorAppTestWatch(), onCompile: onCompileClient, + label: 'Test', }); runRSPackBuild({ @@ -149,6 +152,7 @@ try { isClient: false, watch: isMeteorAppTestWatch(), onCompile: onCompileServer, + label: 'Test', }); // Wait for first compilation to complete diff --git a/packages/tools-core/lib/meteor.js b/packages/tools-core/lib/meteor.js index 775f89d181..5eb9fd6aab 100644 --- a/packages/tools-core/lib/meteor.js +++ b/packages/tools-core/lib/meteor.js @@ -73,6 +73,14 @@ export function getMeteorInitialAppEntrypoints() { }; } +/** + * Checks if the current Meteor project is configured as test module. + * @returns {boolean} + */ +export function isMeteorAppTestModule() { + return getMeteorInitialAppEntrypoints().testModule != null; +} + /** * Sets the Meteor application entry points in environment variables. * @param {Object} options - The entry points configuration object.