ensure test support using rspack with testModule and distinction to testClient and testServer setups

This commit is contained in:
Nacho Codoñer
2025-07-23 17:47:23 +02:00
parent 483048ba77
commit bbe9521e2a
6 changed files with 119 additions and 34 deletions

View File

@@ -161,7 +161,7 @@ export default function (inMeteor = {}, argv = {}) {
});
const externals = [
/^meteor.*/,
...(isReactEnabled ? [/^react$/, /^react-dom$/] : [])
...(isReactEnabled ? [/^react$/, /^react-dom$/] : []),
];
const alias = {
'/': path.resolve(process.cwd()),
@@ -194,7 +194,7 @@ export default function (inMeteor = {}, argv = {}) {
output: {
path: clientOutputDir,
filename: () =>
isDev ? outputFilename : `../${buildContext}/${outputPath}`,
isRun && !isTest ? outputFilename : `../${buildContext}/${outputPath}`,
libraryTarget: 'commonjs',
publicPath: '/',
chunkFilename: `${bundlesContext}/[id].[chunkhash].js`,
@@ -256,8 +256,8 @@ export default function (inMeteor = {}, argv = {}) {
}),
],
watchOptions,
devtool: isDev ? 'source-map' : 'hidden-source-map',
...(isRun && {
devtool: isDev || isTest ? 'source-map' : 'hidden-source-map',
...(isRun && !isTest && {
devServer: {
static: { directory: clientOutputDir, publicPath: '/__rspack__/' },
hot: true,
@@ -310,7 +310,7 @@ export default function (inMeteor = {}, argv = {}) {
],
watchOptions,
devtool: isRun ? 'source-map' : 'hidden-source-map',
...(isRun &&
...((isRun || isTest) &&
createCacheStrategy(mode)
),
};

View File

@@ -16,7 +16,6 @@ const {
isMeteorAppDevelopment,
isMeteorAppRun,
isMeteorAppBuild,
isMeteorBlazeProject,
} = require('meteor/tools-core/lib/meteor');
const {
@@ -109,11 +108,11 @@ export function ensureModuleFilesExist() {
outputFile: getBuildFilePath({ isMain: true, isServer: true, ...env, role: FILE_ROLE.output, onlyFilename: true }),
};
const testClientFiles = {
entryFile: initialEntrypoints.testClient || '',
entryFile: initialEntrypoints.testClient || initialEntrypoints.testModule || '',
outputFile: getBuildFilePath({ isTest: true, isClient: true, role: FILE_ROLE.output, onlyFilename: true }),
};
const testServerFiles = {
entryFile: initialEntrypoints.testServer || '',
entryFile: initialEntrypoints.testServer || initialEntrypoints.testModule || '',
outputFile: getBuildFilePath({ isTest: true, isServer: true, role: FILE_ROLE.output, onlyFilename: true }),
};

View File

@@ -3,6 +3,7 @@
* @description Functions for configuring Meteor for RSPack
*/
import RSPACK_BUNDLES_CONTEXT from "./constants";
import { getInitialEntrypoints } from "./build-context";
const {
getMeteorAppFilesAndFolders,
@@ -31,6 +32,8 @@ const {
* @returns {void}
*/
export function configureMeteorForRSPack() {
const initialEntrypoints = getInitialEntrypoints();
// Ignore node_modules to prevent Meteor from processing them
const projectFilesAndFolders = getMeteorAppFilesAndFolders({ recursive: false });
const foldersToIgnore = [
@@ -66,16 +69,19 @@ export function configureMeteorForRSPack() {
setMeteorAppEntrypoints({
mainClient: `${RSPACK_BUILD_CONTEXT}/${mainClientModule}`,
mainServer: `${RSPACK_BUILD_CONTEXT}/${mainServerModule}`,
testClient: `${RSPACK_BUILD_CONTEXT}/${testClientModule}`,
testServer: `${RSPACK_BUILD_CONTEXT}/${testServerModule}`,
...(initialEntrypoints.testModule && {
testModule: `${RSPACK_BUILD_CONTEXT}/${testServerModule}`,
} || {
testClient: `${RSPACK_BUILD_CONTEXT}/${testClientModule}`,
testServer: `${RSPACK_BUILD_CONTEXT}/${testServerModule}`,
}),
});
// Ensure module files exist
ensureModuleFilesExist();
// Write content to module files
if (isMeteorAppDevelopment()) {
// writeMainClientEntryForHMR();
if (isMeteorAppRun()) {
setMeteorAppCustomScriptUrl(
`/__rspack__/${getBuildFilePath({ ...env, isMain: true, isClient: true, role: FILE_ROLE.output, onlyFilename: true })}`,
);

View File

@@ -3,6 +3,7 @@
* @description Functions for managing RSPack processes
*/
import { RSPACK_BUILD_CONTEXT } from "./constants";
import { getMeteorAppEntrypoints } from "../../tools-core/lib/meteor";
const {
spawnProcess,
@@ -77,20 +78,12 @@ export function getRSPackEnv({ isClient, isServer }) {
const initialEntrypoints = getMeteorInitialAppEntrypoints();
const entryKey = `${isMeteorAppTest() ? 'test' : 'main'}${isClient ? 'Client' : 'Server'}`;
const inputFilePath = initialEntrypoints[entryKey];
const inputFilePath = initialEntrypoints[entryKey] || `testModule`;
const isTypescriptEnabled = inputFilePath.endsWith('.ts') || inputFilePath.endsWith('.tsx');
const isTsxEnabled = inputFilePath.endsWith('.tsx');
const isJsxEnabled = inputFilePath.endsWith('.jsx');
const pairs = [
['name',
`${RSPACK_BUILD_CONTEXT}/${getBuildFilePath({
...env,
...side,
isMain: true,
role: FILE_ROLE.output,
})}`,
],
['isDevelopment', isMeteorAppDevelopment()],
['isProduction', isMeteorAppProduction()],
['isDebug', isMeteorAppDebug()],
@@ -240,10 +233,11 @@ export function startRSPackServerWatch(options = {}) {
* @param {boolean} options.isClient - Whether this is a client build
* @param {boolean} options.isServer - Whether this is a server build
* @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 } = {}) {
export async function runRSPackBuild({ isClient, isServer, onCompile, watch } = {}) {
const appDir = getMeteorAppDir();
const configFile = getConfigFileName();
@@ -252,7 +246,17 @@ export async function runRSPackBuild({ isClient, isServer, onCompile } = {}) {
logProgress(`Running RSPack build for ${endpoint}...`);
// Use a promise to ensure Meteor waits until RSPack finishes
return new Promise((resolve, reject) => {
const buildProcess = spawnProcess('npx', ['rspack', 'build', '--config', configFile, ...getRSPackEnv({ isClient, isServer })], {
spawnProcess(
'npx',
[
'rspack',
'build',
'--config',
configFile,
...(watch && ['--watch']) || [],
...getRSPackEnv({ isClient, isServer }),
].filter(Boolean),
{
cwd: appDir,
onStdout: (data) => {
logInfo(`[RSPack Build ${endpoint}] ${data}`);

View File

@@ -21,7 +21,9 @@ const {
const {
ensureRSPackInstalled,
checkReactInstalled, ensureRSPackReactInstalled, checkCoffeescriptInstalled,
checkReactInstalled,
ensureRSPackReactInstalled,
checkCoffeescriptInstalled,
} = require('./lib/dependencies');
const {
@@ -52,7 +54,10 @@ const {
const {
isMeteorAppRun,
isMeteorAppBuild,
getMeteorAppEntrypoints
getMeteorInitialAppEntrypoints,
getMeteorAppEntrypoints,
isMeteorAppTest,
isMeteorAppTestWatch,
} = require('meteor/tools-core/lib/meteor');
const {
@@ -110,12 +115,57 @@ try {
// Wait for first compilation to complete
await waitForFirstCompilation(clientFirstCompile, serverFirstCompile, clientFirstCompilePromise, serverFirstCompilePromise);
} else if (isMeteorAppTest()) {
const initialEntrypoints = getMeteorInitialAppEntrypoints();
// Setup compilation tracking and callbacks
const {
clientFirstCompile,
serverFirstCompile,
clientFirstCompilePromise,
serverFirstCompilePromise,
onCompileClient,
onCompileServer,
} = setupCompilationTracking();
if (initialEntrypoints?.testModule && isMeteorAppTestWatch()) {
runRSPackBuild({
isServer: true,
isClient: false,
watch: isMeteorAppTestWatch(),
onCompile: onCompileServer,
});
} else if (initialEntrypoints?.testModule && !isMeteorAppTestWatch()) {
await runRSPackBuild({
isServer: true,
isClient: false,
onCompile: onCompileServer,
});
} else if (initialEntrypoints?.testModule?.client || initialEntrypoints?.testModule?.server) {
runRSPackBuild({
isClient: true,
isServer: false,
watch: isMeteorAppTestWatch(),
onCompile: onCompileClient,
});
runRSPackBuild({
isServer: true,
isClient: false,
watch: isMeteorAppTestWatch(),
onCompile: onCompileServer,
});
// Wait for first compilation to complete
await waitForFirstCompilation(clientFirstCompile, serverFirstCompile, clientFirstCompilePromise, serverFirstCompilePromise);
}
} else if (isMeteorAppBuild()) {
// For 'build' command, run RSPack build without watch mode
// Run client and server builds in parallel and wait for both to complete
await Promise.all([
runRSPackBuild({ isClient: true, isServer: false }),
runRSPackBuild({ isServer: true, isClient: false })
runRSPackBuild({ isServer: true, isClient: false }),
]);
}
} catch (error) {

View File

@@ -61,8 +61,15 @@ export function getMeteorInitialAppEntrypoints() {
return {
mainClient: meteorConfig?.mainModule?.client,
mainServer: meteorConfig?.mainModule?.server,
testClient: meteorConfig?.testModule?.client || meteorConfig?.testModule,
testServer: meteorConfig?.testModule?.server || meteorConfig?.testModule,
...meteorConfig?.testModule?.client && {
testClient: meteorConfig?.testModule?.client,
},
...meteorConfig?.testModule?.server && {
testServer: meteorConfig?.testModule?.server,
},
...!meteorConfig?.testModule?.client && !meteorConfig?.testModule?.server && {
testModule: meteorConfig?.testModule,
},
};
}
@@ -71,21 +78,32 @@ export function getMeteorInitialAppEntrypoints() {
* @param {Object} options - The entry points configuration object.
* @param {string} [options.mainClient] - The client main module path.
* @param {string} [options.mainServer] - The server main module path.
* @param {string} [options.testModule] - The test module path.
* @param {string} [options.testClient] - The client test module path.
* @param {string} [options.testServer] - The server test module path.
*/
export function setMeteorAppEntrypoints({ mainClient, mainServer, testClient, testServer }) {
export function setMeteorAppEntrypoints({
mainClient,
mainServer,
testModule,
testClient,
testServer,
}) {
if (mainClient) {
process.env.METEOR_CONFIG_CLIENT = mainClient;
}
if (mainServer) {
process.env.METEOR_CONFIG_SERVER = mainServer;
}
if (testClient) {
process.env.METEOR_CONFIG_TEST_CLIENT = testClient;
}
if (testServer) {
process.env.METEOR_CONFIG_TEST_SERVER = testServer;
if (testModule) {
process.env.METEOR_CONFIG_TEST = testModule;
} else {
if (testClient) {
process.env.METEOR_CONFIG_TEST_CLIENT = testClient;
}
if (testServer) {
process.env.METEOR_CONFIG_TEST_SERVER = testServer;
}
}
global.ensureMeteorConfigInitialized?.();
}
@@ -124,6 +142,14 @@ export function isMeteorAppTest() {
|| Package?.meteor?.global?.currentCommand?.name === 'test-packages';
}
/**
* Checks if the current Meteor command is 'test' and is running in watch mode.
* @returns {boolean} True if the current command is 'test' and is running in watch mode, false otherwise.
*/
export function isMeteorAppTestWatch() {
return isMeteorAppTest() && !Package?.meteor?.global?.currentCommand?.options?.once;
}
/**
* Checks if the Meteor application is running in development mode.
* @returns {boolean} True if the application is in development mode, false otherwise.