ensure distinguish between test module and test for client and server

This commit is contained in:
Nacho Codoñer
2025-07-28 16:19:44 +02:00
parent ac3bcc034e
commit 09fb3fed69
6 changed files with 94 additions and 67 deletions

View File

@@ -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',

View File

@@ -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;

View File

@@ -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}`,

View File

@@ -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<void>} 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);
}
});

View File

@@ -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

View File

@@ -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.