@meteorjs/rspack 1.1.0-beta.22 → 1.1.0-beta.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -75,12 +75,6 @@ type MeteorEnv = Record<string, any> & {
75
75
  disablePlugins: (
76
76
  matchers: string | RegExp | ((plugin: any, index: number) => boolean) | Array<string | RegExp | ((plugin: any, index: number) => boolean)>
77
77
  ) => Record<string, any>;
78
- /**
79
- * Omit `Meteor.isDevelopment` and `Meteor.isProduction` from the DefinePlugin so
80
- * the bundle is not tied to a specific Meteor environment (portable / isomorphic builds).
81
- * @returns A config fragment with `meteor.enablePortableBuild: true`
82
- */
83
- enablePortableBuild: () => Record<string, any>;
84
78
  }
85
79
 
86
80
  export type ConfigFactory = (
package/lib/ignore.js CHANGED
@@ -117,7 +117,7 @@ function createIgnoreRegex(globPatterns) {
117
117
 
118
118
  // For absolute paths, we don't want to force the pattern to match from the beginning
119
119
  // but we still want to ensure it matches to the end of the path segment
120
- regexPattern = '(?:^|/)' + regexPattern + '$';
120
+ regexPattern = '(?:^|/)' + regexPattern;
121
121
 
122
122
  return regexPattern;
123
123
  }).filter(pattern => pattern !== null);
@@ -152,19 +152,6 @@ function extendSwcConfig(swcConfig) {
152
152
  });
153
153
  }
154
154
 
155
- /**
156
- * Signal that `Meteor.isDevelopment` and `Meteor.isProduction` should be omitted
157
- * from DefinePlugin, making the bundle portable across Meteor environments.
158
- * Usage: return Meteor.enablePortableBuild() in your rspack.config.js
159
- *
160
- * @returns {Record<string, object>} config fragment with `meteor.enablePortableBuild: true`
161
- */
162
- function enablePortableBuild() {
163
- return prepareMeteorRspackConfig({
164
- "meteor.enablePortableBuild": true,
165
- });
166
- }
167
-
168
155
  /**
169
156
  * Remove plugins from a Rspack config by name, RegExp, predicate, or array of them.
170
157
  * When using a function predicate, it receives both the plugin and its index in the plugins array.
@@ -215,6 +202,12 @@ function disablePlugins(config, matchers) {
215
202
  return config;
216
203
  }
217
204
 
205
+ function outputMeteorRspack(data) {
206
+ const jsonString = JSON.stringify(data);
207
+ const output = `[Meteor-Rspack]${jsonString}[/Meteor-Rspack]`;
208
+ console.log(output);
209
+ }
210
+
218
211
  module.exports = {
219
212
  compileWithMeteor,
220
213
  compileWithRspack,
@@ -223,5 +216,5 @@ module.exports = {
223
216
  extendSwcConfig,
224
217
  makeWebNodeBuiltinsAlias,
225
218
  disablePlugins,
226
- enablePortableBuild,
219
+ outputMeteorRspack,
227
220
  };
package/lib/test.js CHANGED
@@ -9,6 +9,7 @@ const { createIgnoreRegex, createIgnoreGlobConfig } = require("./ignore.js");
9
9
  * @param {string} options.projectDir - The project directory
10
10
  * @param {string} options.buildContext - The build context
11
11
  * @param {string[]} options.ignoreEntries - Array of ignore patterns
12
+ * @param {string[]} options.meteorIgnoreEntries - Array of meteor ignore patterns
12
13
  * @param {string} options.extraEntry - Extra entry to load
13
14
  * @returns {string} The path to the generated file
14
15
  */
@@ -17,6 +18,7 @@ const generateEagerTestFile = ({
17
18
  projectDir,
18
19
  buildContext,
19
20
  ignoreEntries: inIgnoreEntries = [],
21
+ meteorIgnoreEntries: inMeteorIgnoreEntries = [],
20
22
  prefix: inPrefix = '',
21
23
  extraEntry,
22
24
  globalImportPath,
@@ -40,6 +42,11 @@ const generateEagerTestFile = ({
40
42
  const excludeFoldersRegex = createIgnoreRegex(
41
43
  createIgnoreGlobConfig(ignoreEntries)
42
44
  );
45
+ console.log("inMeteorIgnoreEntries", inMeteorIgnoreEntries);
46
+ // Create regex from meteor ignore entries
47
+ const excludeMeteorIgnoreRegex = inMeteorIgnoreEntries.length > 0
48
+ ? createIgnoreRegex(createIgnoreGlobConfig(inMeteorIgnoreEntries))
49
+ : null;
43
50
 
44
51
  const prefix = (inPrefix && `${inPrefix}-`) || "";
45
52
  const filename = isAppTest
@@ -51,15 +58,27 @@ const generateEagerTestFile = ({
51
58
  : "/\\.(?:test|spec)s?\\.[^.]+$/";
52
59
 
53
60
  const content = `${
54
- globalImportPath ? `import '${globalImportPath}';\n\n` : ''
55
- }{
56
- const ctx = import.meta.webpackContext('/', {
61
+ globalImportPath ? `import '${globalImportPath}';\n\n` : ""
62
+ }${
63
+ excludeMeteorIgnoreRegex
64
+ ? `const MeteorIgnoreRegex = ${excludeMeteorIgnoreRegex.toString()};`
65
+ : ""
66
+ }
67
+ {
68
+ const ctx = import.meta.webpackContext('${projectDir}', {
57
69
  recursive: true,
58
70
  regExp: ${regExp},
59
71
  exclude: ${excludeFoldersRegex.toString()},
60
72
  mode: 'eager',
61
73
  });
62
- ctx.keys().forEach(ctx);
74
+ ctx.keys().filter((k) => {
75
+ ${
76
+ excludeMeteorIgnoreRegex
77
+ ? `// Only exclude based on *relative* path segments.
78
+ return !MeteorIgnoreRegex.test(k);`
79
+ : "return true;"
80
+ }
81
+ }).forEach(ctx);
63
82
  ${
64
83
  extraEntry
65
84
  ? `const extra = import.meta.webpackContext('${path.dirname(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meteorjs/rspack",
3
- "version": "1.1.0-beta.22",
3
+ "version": "1.1.0-beta.30",
4
4
  "description": "Configuration logic for using Rspack in Meteor projects",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
@@ -0,0 +1,36 @@
1
+ // MeteorRspackOutputPlugin.js
2
+ //
3
+ // This plugin outputs a JSON stringified with a tag delimiter each time
4
+ // a new Rspack compilation happens. The JSON content is configurable
5
+ // via plugin instantiation options.
6
+
7
+ const { outputMeteorRspack } = require('../lib/meteorRspackHelpers');
8
+
9
+ class MeteorRspackOutputPlugin {
10
+ constructor(options = {}) {
11
+ this.pluginName = 'MeteorRspackOutputPlugin';
12
+ this.options = options;
13
+ this.compilationCount = 0;
14
+ // The data to be output as JSON, can be a static object or a function
15
+ this.getData =
16
+ typeof options.getData === 'function'
17
+ ? options.getData
18
+ : () => options.data || {};
19
+ }
20
+
21
+ apply(compiler) {
22
+ // Hook into the 'done' event which fires after each compilation completes
23
+ compiler.hooks.done.tap(this.pluginName, stats => {
24
+ this.compilationCount++;
25
+ const data = {
26
+ ...(this.getData(stats, {
27
+ compilationCount: this.compilationCount,
28
+ isRebuild: this.compilationCount > 1,
29
+ }) || {}),
30
+ };
31
+ outputMeteorRspack(data);
32
+ });
33
+ }
34
+ }
35
+
36
+ module.exports = { MeteorRspackOutputPlugin };
package/rspack.config.js CHANGED
@@ -10,8 +10,10 @@ const { getMeteorAppSwcConfig } = require('./lib/swc.js');
10
10
  const HtmlRspackPlugin = require('./plugins/HtmlRspackPlugin.js');
11
11
  const { RequireExternalsPlugin } = require('./plugins/RequireExtenalsPlugin.js');
12
12
  const { AssetExternalsPlugin } = require('./plugins/AssetExternalsPlugin.js');
13
+ const { MeteorRspackOutputPlugin } = require('./plugins/MeteorRspackOutputPlugin.js');
13
14
  const { generateEagerTestFile } = require("./lib/test.js");
14
15
  const { getMeteorIgnoreEntries, createIgnoreGlobConfig } = require("./lib/ignore");
16
+ const { mergeMeteorRspackFragments } = require("./lib/meteorRspackConfigFactory.js");
15
17
  const {
16
18
  compileWithMeteor,
17
19
  compileWithRspack,
@@ -20,9 +22,8 @@ const {
20
22
  extendSwcConfig,
21
23
  makeWebNodeBuiltinsAlias,
22
24
  disablePlugins,
23
- enablePortableBuild,
25
+ outputMeteorRspack,
24
26
  } = require('./lib/meteorRspackHelpers.js');
25
- const { loadUserAndOverrideConfig } = require('./lib/meteorRspackConfigHelpers.js');
26
27
  const { prepareMeteorRspackConfig } = require("./lib/meteorRspackConfigFactory");
27
28
 
28
29
  // Safe require that doesn't throw if the module isn't found
@@ -41,7 +42,11 @@ function safeRequire(moduleName) {
41
42
  }
42
43
 
43
44
  // Persistent filesystem cache strategy
44
- function createCacheStrategy(mode, side, { projectConfigPath, configPath } = {}) {
45
+ function createCacheStrategy(
46
+ mode,
47
+ side,
48
+ { projectConfigPath, configPath, buildContext } = {},
49
+ ) {
45
50
  // Check for configuration files
46
51
  const tsconfigPath = path.join(process.cwd(), 'tsconfig.json');
47
52
  const hasTsconfig = fs.existsSync(tsconfigPath);
@@ -82,7 +87,9 @@ function createCacheStrategy(mode, side, { projectConfigPath, configPath } = {})
82
87
  type: "persistent",
83
88
  storage: {
84
89
  type: "filesystem",
85
- directory: `node_modules/.cache/rspack${(side && `/${side}`) || ""}`,
90
+ directory: `node_modules/.cache/rspack${
91
+ (buildContext && `-${buildContext}`) || ''
92
+ }${(side && `/${side}`) || ''}`,
86
93
  },
87
94
  ...(buildDependencies.length > 0 && {
88
95
  buildDependencies: buildDependencies,
@@ -205,29 +212,15 @@ module.exports = async function (inMeteor = {}, argv = {}) {
205
212
  const Meteor = { ...inMeteor };
206
213
  // Convert string boolean values to actual booleans
207
214
  for (const key in Meteor) {
208
- if (Meteor[key] === "true" || Meteor[key] === true) {
215
+ if (Meteor[key] === 'true' || Meteor[key] === true) {
209
216
  Meteor[key] = true;
210
- } else if (Meteor[key] === "false" || Meteor[key] === false) {
217
+ } else if (Meteor[key] === 'false' || Meteor[key] === false) {
211
218
  Meteor[key] = false;
212
219
  }
213
220
  }
214
221
 
215
- const isTestLike = !!Meteor.isTestLike;
216
- const swcExternalHelpers = !!Meteor.swcExternalHelpers;
217
- const isNative = !!Meteor.isNative;
218
- const devServerPort = Meteor.devServerPort || 8080;
219
-
220
- const projectDir = process.cwd();
221
- const projectConfigPath =
222
- Meteor.projectConfigPath || path.resolve(projectDir, "rspack.config.js");
223
-
224
- // Compute build paths before loading user config (needed by Meteor helpers below)
225
- const buildContext = Meteor.buildContext || "_build";
226
- const outputPath = Meteor.outputPath;
227
- const outputDir = path.dirname(Meteor.outputPath || "");
228
- Meteor.buildOutputDir = path.resolve(projectDir, buildContext, outputDir);
229
-
230
- // Meteor flags derived purely from input; independent of loaded user/override configs
222
+ const isProd = !!Meteor.isProduction || argv.mode === 'production';
223
+ const isDev = !!Meteor.isDevelopment || !isProd;
231
224
  const isTest = !!Meteor.isTest;
232
225
  const isClient = !!Meteor.isClient;
233
226
  const isServer = !!Meteor.isServer;
@@ -237,6 +230,17 @@ module.exports = async function (inMeteor = {}, argv = {}) {
237
230
  const isTestModule = !!Meteor.isTestModule;
238
231
  const isTestEager = !!Meteor.isTestEager;
239
232
  const isTestFullApp = !!Meteor.isTestFullApp;
233
+ const isTestLike = !!Meteor.isTestLike;
234
+ const swcExternalHelpers = !!Meteor.swcExternalHelpers;
235
+ const isNative = !!Meteor.isNative;
236
+ const isProfile = !!Meteor.isProfile;
237
+ const isVerbose = !!Meteor.isVerbose;
238
+ const mode = isProd ? 'production' : 'development';
239
+ const projectDir = process.cwd();
240
+ const projectConfigPath = Meteor.projectConfigPath || path.resolve(projectDir, 'rspack.config.js');
241
+ const configPath = Meteor.configPath;
242
+ const testEntry = Meteor.testEntry;
243
+
240
244
  const isTypescriptEnabled = Meteor.isTypescriptEnabled || false;
241
245
  const isJsxEnabled =
242
246
  Meteor.isJsxEnabled || (!isTypescriptEnabled && isReactEnabled) || false;
@@ -244,65 +248,70 @@ module.exports = async function (inMeteor = {}, argv = {}) {
244
248
  Meteor.isTsxEnabled || (isTypescriptEnabled && isReactEnabled) || false;
245
249
  const isBundleVisualizerEnabled = Meteor.isBundleVisualizerEnabled || false;
246
250
  const isAngularEnabled = Meteor.isAngularEnabled || false;
247
- const enableSwcExternalHelpers = !isServer && swcExternalHelpers;
248
251
 
249
- // Defined here so it can be called both before and after the first config load;
250
- // without loaded configs it falls through to argv/Meteor flags.
251
- const getModeFromConfig = (userConfig, overrideConfig) => {
252
- if (overrideConfig?.mode) return overrideConfig.mode;
253
- if (userConfig?.mode) return userConfig.mode;
254
- if (argv.mode) return argv.mode;
255
- if (Meteor.isProduction) return "production";
256
- if (Meteor.isDevelopment) return "development";
257
- return null;
258
- };
252
+ // Determine entry points
253
+ const entryPath = Meteor.entryPath;
254
+
255
+ // Determine output points
256
+ const outputPath = Meteor.outputPath;
257
+ const outputDir = path.dirname(Meteor.outputPath || '');
258
+
259
+ const outputFilename = Meteor.outputFilename;
260
+
261
+ // Determine run point
262
+ const runPath = Meteor.runPath;
263
+
264
+ // Determine banner
265
+ const bannerOutput = JSON.parse(Meteor.bannerOutput || process.env.RSPACK_BANNER || '""');
259
266
 
260
- // Initial mode before user/override configs are loaded
261
- const initialCurrentMode = getModeFromConfig();
262
- const initialIsProd = initialCurrentMode
263
- ? initialCurrentMode === "production"
264
- : !!Meteor.isProduction;
265
- const initialIsDev = initialCurrentMode
266
- ? initialCurrentMode === "development"
267
- : !!Meteor.isDevelopment || !initialIsProd;
268
- const initialMode = initialIsProd ? "production" : "development";
269
-
270
- // Initialized with pre-load values so helpers work during the first config load;
271
- // reassigned after load once mode is fully resolved.
272
- let cacheStrategy = createCacheStrategy(
273
- initialMode,
274
- (isClient && "client") || "server",
275
- { projectConfigPath, configPath: Meteor.configPath }
267
+ // Determine output directories
268
+ const clientOutputDir = path.resolve(projectDir, 'public');
269
+ const serverOutputDir = path.resolve(projectDir, 'private');
270
+
271
+ // Determine context for bundles and assets
272
+ const meteorLocalDirName = process.env.METEOR_LOCAL_DIR
273
+ ? path.basename(process.env.METEOR_LOCAL_DIR.replace(/\\/g, '/'))
274
+ : '';
275
+ const buildContext =
276
+ Meteor.buildContext ||
277
+ process.env.RSPACK_BUILD_CONTEXT ||
278
+ `_build${(meteorLocalDirName && `-${meteorLocalDirName}`) || ''}`;
279
+ const assetsContext =
280
+ Meteor.assetsContext ||
281
+ process.env.RSPACK_ASSETS_CONTEXT ||
282
+ `build-assets${(meteorLocalDirName && `-${meteorLocalDirName}`) || ''}`;
283
+ const chunksContext =
284
+ Meteor.chunksContext ||
285
+ process.env.RSPACK_CHUNKS_CONTEXT ||
286
+ `build-chunks${(meteorLocalDirName && `-${meteorLocalDirName}`) || ''}`;
287
+
288
+ // Determine build output and pass to Meteor
289
+ const buildOutputDir = path.resolve(projectDir, buildContext, outputDir);
290
+ Meteor.buildOutputDir = buildOutputDir;
291
+
292
+ const cacheStrategy = createCacheStrategy(
293
+ mode,
294
+ (Meteor.isClient && 'client') || 'server',
295
+ { projectConfigPath, configPath, buildContext }
276
296
  );
277
- let swcConfigRule = createSwcConfig({
278
- isTypescriptEnabled,
279
- isReactEnabled,
280
- isJsxEnabled,
281
- isTsxEnabled,
282
- externalHelpers: enableSwcExternalHelpers,
283
- isDevEnvironment: isRun && initialIsDev && !isTest && !isNative,
284
- isClient,
285
- isAngularEnabled,
286
- });
287
- Meteor.swcConfigOptions = swcConfigRule.options;
288
297
 
289
298
  // Expose Meteor's helpers to expand Rspack configs
290
- Meteor.compileWithMeteor = (deps) => compileWithMeteor(deps);
299
+ Meteor.compileWithMeteor = deps => compileWithMeteor(deps);
291
300
  Meteor.compileWithRspack = (deps, options = {}) =>
292
301
  compileWithRspack(deps, {
293
302
  options: mergeSplitOverlap(Meteor.swcConfigOptions, options),
294
303
  });
295
- Meteor.setCache = (enabled) =>
296
- setCache(!!enabled, enabled === "memory" ? undefined : cacheStrategy);
304
+ Meteor.setCache = enabled =>
305
+ setCache(
306
+ !!enabled,
307
+ enabled === 'memory' ? undefined : cacheStrategy
308
+ );
297
309
  Meteor.splitVendorChunk = () => splitVendorChunk();
298
- Meteor.extendSwcConfig = (customSwcConfig) =>
299
- extendSwcConfig(customSwcConfig);
310
+ Meteor.extendSwcConfig = (customSwcConfig) => extendSwcConfig(customSwcConfig);
300
311
  Meteor.extendConfig = (...configs) => mergeSplitOverlap(...configs);
301
- Meteor.disablePlugins = (matchers) =>
302
- prepareMeteorRspackConfig({
303
- disablePlugins: matchers,
304
- });
305
- Meteor.enablePortableBuild = () => enablePortableBuild();
312
+ Meteor.disablePlugins = matchers => prepareMeteorRspackConfig({
313
+ disablePlugins: matchers,
314
+ });
306
315
 
307
316
  // Add HtmlRspackPlugin function to Meteor
308
317
  Meteor.HtmlRspackPlugin = (options = {}) => {
@@ -326,57 +335,6 @@ module.exports = async function (inMeteor = {}, argv = {}) {
326
335
  });
327
336
  };
328
337
 
329
- // First pass: resolve user/override configs early so mode overrides (e.g. "production")
330
- // are available before computing isProd/isDev and the rest of the build flags.
331
- // Skipped for Angular since it manages its own mode via the second pass.
332
- let { nextUserConfig, nextOverrideConfig } = isAngularEnabled
333
- ? {}
334
- : await loadUserAndOverrideConfig(projectConfigPath, Meteor, argv);
335
-
336
- // Determine the final mode with loaded configs
337
- const currentMode = getModeFromConfig(nextUserConfig, nextOverrideConfig);
338
- const isProd = currentMode
339
- ? currentMode === "production"
340
- : !!Meteor.isProduction;
341
- const isDev = currentMode
342
- ? currentMode === "development"
343
- : !!Meteor.isDevelopment || !isProd;
344
- const mode = isProd ? "production" : "development";
345
- const isPortableBuild = !!(
346
- nextUserConfig?.["meteor.enablePortableBuild"] ||
347
- nextOverrideConfig?.["meteor.enablePortableBuild"]
348
- );
349
- const configPath = Meteor.configPath;
350
- const testEntry = Meteor.testEntry;
351
-
352
- // Determine entry points
353
- const entryPath = Meteor.entryPath || "";
354
-
355
- // Determine output points
356
- const outputFilename = Meteor.outputFilename;
357
-
358
- // Determine context for bundles and assets
359
- const assetsContext = Meteor.assetsContext || "build-assets";
360
- const chunksContext = Meteor.chunksContext || "build-chunks";
361
-
362
- cacheStrategy = createCacheStrategy(
363
- mode,
364
- (Meteor.isClient && "client") || "server",
365
- { projectConfigPath, configPath }
366
- );
367
-
368
- // Determine run point
369
- const runPath = Meteor.runPath || "";
370
-
371
- // Determine banner
372
- const bannerOutput = JSON.parse(
373
- Meteor.bannerOutput || process.env.RSPACK_BANNER || '""'
374
- );
375
-
376
- // Determine output directories
377
- const clientOutputDir = path.resolve(projectDir, "public");
378
- const serverOutputDir = path.resolve(projectDir, "private");
379
-
380
338
  // Get Meteor ignore entries
381
339
  const meteorIgnoreEntries = getMeteorIgnoreEntries(projectDir);
382
340
 
@@ -392,17 +350,21 @@ module.exports = async function (inMeteor = {}, argv = {}) {
392
350
  // Set default watch options
393
351
  const watchOptions = {
394
352
  ignored: [
395
- ...createIgnoreGlobConfig([...meteorIgnoreEntries, ...additionalEntries]),
353
+ ...createIgnoreGlobConfig([
354
+ ...meteorIgnoreEntries,
355
+ ...additionalEntries,
356
+ ]),
396
357
  ],
397
358
  };
398
359
 
399
360
  if (Meteor.isDebug || Meteor.isVerbose) {
400
- console.log("[i] Rspack mode:", mode);
401
- console.log("[i] Meteor flags:", Meteor);
361
+ console.log('[i] Rspack mode:', mode);
362
+ console.log('[i] Meteor flags:', Meteor);
402
363
  }
403
364
 
365
+ const enableSwcExternalHelpers = !isServer && swcExternalHelpers;
404
366
  const isDevEnvironment = isRun && isDev && !isTest && !isNative;
405
- swcConfigRule = createSwcConfig({
367
+ const swcConfigRule = createSwcConfig({
406
368
  isTypescriptEnabled,
407
369
  isReactEnabled,
408
370
  isJsxEnabled,
@@ -412,6 +374,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
412
374
  isClient,
413
375
  isAngularEnabled,
414
376
  });
377
+ // Expose swc config to use in custom configs
415
378
  Meteor.swcConfigOptions = swcConfigRule.options;
416
379
 
417
380
  const externals = [
@@ -420,34 +383,34 @@ module.exports = async function (inMeteor = {}, argv = {}) {
420
383
  ...(isServer ? [/^bcrypt$/] : []),
421
384
  ];
422
385
  const alias = {
423
- "/": path.resolve(process.cwd()),
386
+ '/': path.resolve(process.cwd()),
424
387
  };
425
388
  const fallback = {
426
389
  ...(isClient && makeWebNodeBuiltinsAlias()),
427
390
  };
428
391
  const extensions = [
429
- ".ts",
430
- ".tsx",
431
- ".mts",
432
- ".cts",
433
- ".js",
434
- ".jsx",
435
- ".mjs",
436
- ".cjs",
437
- ".json",
438
- ".wasm",
392
+ '.ts',
393
+ '.tsx',
394
+ '.mts',
395
+ '.cts',
396
+ '.js',
397
+ '.jsx',
398
+ '.mjs',
399
+ '.cjs',
400
+ '.json',
401
+ '.wasm',
439
402
  ];
440
403
  const extraRules = [];
441
404
 
442
405
  const reactRefreshModule = isReactEnabled
443
- ? safeRequire("@rspack/plugin-react-refresh")
406
+ ? safeRequire('@rspack/plugin-react-refresh')
444
407
  : null;
445
408
 
446
409
  const requireExternalsPlugin = new RequireExternalsPlugin({
447
410
  filePath: path.join(buildContext, runPath),
448
411
  ...(Meteor.isBlazeEnabled && {
449
412
  externals: /\.html$/,
450
- isEagerImport: (module) => module.endsWith(".html"),
413
+ isEagerImport: module => module.endsWith('.html'),
451
414
  ...(isProd && {
452
415
  lastImports: [`./${outputFilename}`],
453
416
  }),
@@ -457,26 +420,25 @@ module.exports = async function (inMeteor = {}, argv = {}) {
457
420
 
458
421
  // Handle assets
459
422
  const assetExternalsPlugin = new AssetExternalsPlugin();
460
- const assetModuleFilename = (_fileInfo) => {
423
+ const assetModuleFilename = _fileInfo => {
461
424
  const filename = _fileInfo.filename;
462
- const isPublic = filename.startsWith("/") || filename.startsWith("public");
425
+ const isPublic = filename.startsWith('/') || filename.startsWith('public');
463
426
  if (isPublic) return `[name][ext][query]`;
464
427
  return `${assetsContext}/[hash][ext][query]`;
465
428
  };
466
429
 
467
430
  const rsdoctorModule = isBundleVisualizerEnabled
468
- ? safeRequire("@rsdoctor/rspack-plugin")
431
+ ? safeRequire('@rsdoctor/rspack-plugin')
469
432
  : null;
470
- const doctorPluginConfig =
471
- isRun && isBundleVisualizerEnabled && rsdoctorModule?.RsdoctorRspackPlugin
472
- ? [
473
- new rsdoctorModule.RsdoctorRspackPlugin({
474
- port: isClient
475
- ? parseInt(Meteor.rsdoctorClientPort || "8888", 10)
476
- : parseInt(Meteor.rsdoctorServerPort || "8889", 10),
477
- }),
478
- ]
479
- : [];
433
+ const doctorPluginConfig = isRun && isBundleVisualizerEnabled && rsdoctorModule?.RsdoctorRspackPlugin
434
+ ? [
435
+ new rsdoctorModule.RsdoctorRspackPlugin({
436
+ port: isClient
437
+ ? (parseInt(Meteor.rsdoctorClientPort || '8888', 10))
438
+ : (parseInt(Meteor.rsdoctorServerPort || '8889', 10)),
439
+ }),
440
+ ]
441
+ : [];
480
442
  const bannerPluginConfig = !isBuild
481
443
  ? [
482
444
  new BannerPlugin({
@@ -487,6 +449,10 @@ module.exports = async function (inMeteor = {}, argv = {}) {
487
449
  : [];
488
450
  // Not supported in Meteor yet (Rspack 1.7+ is enabled by default)
489
451
  const lazyCompilationConfig = { lazyCompilation: false };
452
+ const shouldLogVerbose = isProfile || isVerbose;
453
+ const loggingConfig = shouldLogVerbose
454
+ ? {}
455
+ : { stats: "errors-warnings", infrastructureLogging: { level: "warn" } };
490
456
 
491
457
  const clientEntry =
492
458
  isClient && isTest && isTestEager && isTestFullApp
@@ -494,7 +460,8 @@ module.exports = async function (inMeteor = {}, argv = {}) {
494
460
  isAppTest: true,
495
461
  projectDir,
496
462
  buildContext,
497
- ignoreEntries: [...meteorIgnoreEntries, "**/server/**"],
463
+ ignoreEntries: ["**/server/**"],
464
+ meteorIgnoreEntries,
498
465
  prefix: "client",
499
466
  extraEntry: path.resolve(process.cwd(), Meteor.mainClientEntry),
500
467
  globalImportPath: path.resolve(projectDir, buildContext, entryPath),
@@ -505,18 +472,19 @@ module.exports = async function (inMeteor = {}, argv = {}) {
505
472
  isClient: true,
506
473
  projectDir,
507
474
  buildContext,
508
- ignoreEntries: [...meteorIgnoreEntries, "**/server/**"],
475
+ ignoreEntries: ["**/server/**"],
476
+ meteorIgnoreEntries,
509
477
  prefix: "client",
510
478
  globalImportPath: path.resolve(projectDir, buildContext, entryPath),
511
479
  })
512
480
  : isClient && isTest && testEntry
513
481
  ? path.resolve(process.cwd(), testEntry)
514
482
  : path.resolve(process.cwd(), buildContext, entryPath);
515
- const clientNameConfig = `[${(isTest && "test-") || ""}client-rspack]`;
483
+ const clientNameConfig = `[${(isTest && 'test-') || ''}client-rspack]`;
516
484
  // Base client config
517
485
  let clientConfig = {
518
486
  name: clientNameConfig,
519
- target: "web",
487
+ target: 'web',
520
488
  mode,
521
489
  entry: clientEntry,
522
490
  output: {
@@ -525,7 +493,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
525
493
  const chunkName = _module.chunk?.name;
526
494
  const isMainChunk = !chunkName || chunkName === "main";
527
495
  const chunkSuffix = `${chunksContext}/[id]${
528
- isProd ? ".[chunkhash]" : ""
496
+ isProd ? '.[chunkhash]' : ''
529
497
  }.js`;
530
498
  if (isDevEnvironment) {
531
499
  if (isMainChunk) return outputFilename;
@@ -534,21 +502,21 @@ module.exports = async function (inMeteor = {}, argv = {}) {
534
502
  if (isMainChunk) return `../${buildContext}/${outputPath}`;
535
503
  return chunkSuffix;
536
504
  },
537
- libraryTarget: "commonjs2",
538
- publicPath: "/",
539
- chunkFilename: `${chunksContext}/[id]${isProd ? ".[chunkhash]" : ""}.js`,
505
+ libraryTarget: 'commonjs2',
506
+ publicPath: '/',
507
+ chunkFilename: `${chunksContext}/[id]${isProd ? '.[chunkhash]' : ''}.js`,
540
508
  assetModuleFilename,
541
509
  cssFilename: `${chunksContext}/[name]${
542
- isProd ? ".[contenthash]" : ""
510
+ isProd ? '.[contenthash]' : ''
543
511
  }.css`,
544
512
  cssChunkFilename: `${chunksContext}/[id]${
545
- isProd ? ".[contenthash]" : ""
513
+ isProd ? '.[contenthash]' : ''
546
514
  }.css`,
547
515
  ...(isProd && { clean: { keep: keepOutsideBuild() } }),
548
516
  },
549
517
  optimization: {
550
518
  usedExports: true,
551
- splitChunks: { chunks: "async" },
519
+ splitChunks: { chunks: 'async' },
552
520
  },
553
521
  module: {
554
522
  rules: [
@@ -557,7 +525,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
557
525
  ? [
558
526
  {
559
527
  test: /\.html$/i,
560
- loader: "ignore-loader",
528
+ loader: 'ignore-loader',
561
529
  },
562
530
  ]
563
531
  : []),
@@ -575,43 +543,46 @@ module.exports = async function (inMeteor = {}, argv = {}) {
575
543
  assetExternalsPlugin,
576
544
  ].filter(Boolean),
577
545
  new DefinePlugin({
578
- "Meteor.isClient": JSON.stringify(true),
579
- "Meteor.isServer": JSON.stringify(false),
580
- "Meteor.isTest": JSON.stringify(isTestLike && !isTestFullApp),
581
- "Meteor.isAppTest": JSON.stringify(isTestLike && isTestFullApp),
582
- ...(!isPortableBuild && {
583
- "Meteor.isDevelopment": JSON.stringify(isDev),
584
- "Meteor.isProduction": JSON.stringify(isProd),
585
- }),
546
+ 'Meteor.isClient': JSON.stringify(true),
547
+ 'Meteor.isServer': JSON.stringify(false),
548
+ 'Meteor.isTest': JSON.stringify(isTestLike && !isTestFullApp),
549
+ 'Meteor.isAppTest': JSON.stringify(isTestLike && isTestFullApp),
550
+ 'Meteor.isDevelopment': JSON.stringify(isDev),
551
+ 'Meteor.isProduction': JSON.stringify(isProd),
586
552
  }),
587
553
  ...bannerPluginConfig,
588
554
  Meteor.HtmlRspackPlugin(),
589
555
  ...doctorPluginConfig,
590
556
  new NormalModuleReplacementPlugin(/^node:(.*)$/, (res) => {
591
- res.request = res.request.replace(/^node:/, "");
557
+ res.request = res.request.replace(/^node:/, '');
592
558
  }),
593
559
  ],
594
560
  watchOptions,
595
- devtool:
596
- isDevEnvironment || isNative || isTest
597
- ? "source-map"
598
- : "hidden-source-map",
561
+ devtool: isDevEnvironment || isNative || isTest ? 'source-map' : 'hidden-source-map',
599
562
  ...(isDevEnvironment && {
600
563
  devServer: {
601
564
  ...createRemoteDevServerConfig(),
602
- static: { directory: clientOutputDir, publicPath: "/__rspack__/" },
565
+ static: { directory: clientOutputDir, publicPath: '/__rspack__/' },
603
566
  hot: true,
604
567
  liveReload: true,
605
568
  ...(Meteor.isBlazeEnabled && { hot: false }),
606
- port: devServerPort,
569
+ port: Meteor.devServerPort || 8080,
607
570
  devMiddleware: {
608
- writeToDisk: (filePath) =>
609
- /\.(html)$/.test(filePath) && !filePath.includes(".hot-update."),
571
+ writeToDisk: filePath =>
572
+ /\.(html)$/.test(filePath) && !filePath.includes('.hot-update.'),
573
+ },
574
+ onListening(devServer) {
575
+ if (!devServer) return;
576
+ const { host, port } = devServer.options;
577
+ const protocol = devServer.options.server?.type === "https" ? "https" : "http";
578
+ const devServerUrl = `${protocol}://${host || "localhost"}:${port}`;
579
+ outputMeteorRspack({ devServerUrl });
610
580
  },
611
581
  },
612
582
  }),
613
583
  ...merge(cacheStrategy, { experiments: { css: true } }),
614
584
  ...lazyCompilationConfig,
585
+ ...loggingConfig,
615
586
  };
616
587
 
617
588
  const serverEntry =
@@ -620,7 +591,8 @@ module.exports = async function (inMeteor = {}, argv = {}) {
620
591
  isAppTest: true,
621
592
  projectDir,
622
593
  buildContext,
623
- ignoreEntries: [...meteorIgnoreEntries, "**/client/**"],
594
+ ignoreEntries: ["**/client/**"],
595
+ meteorIgnoreEntries,
624
596
  prefix: "server",
625
597
  globalImportPath: path.resolve(projectDir, buildContext, entryPath),
626
598
  })
@@ -629,25 +601,26 @@ module.exports = async function (inMeteor = {}, argv = {}) {
629
601
  isAppTest: false,
630
602
  projectDir,
631
603
  buildContext,
632
- ignoreEntries: [...meteorIgnoreEntries, "**/client/**"],
604
+ ignoreEntries: ["**/client/**"],
605
+ meteorIgnoreEntries,
633
606
  prefix: "server",
634
607
  globalImportPath: path.resolve(projectDir, buildContext, entryPath),
635
608
  })
636
609
  : isServer && isTest && testEntry
637
610
  ? path.resolve(process.cwd(), testEntry)
638
611
  : path.resolve(projectDir, buildContext, entryPath);
639
- const serverNameConfig = `[${(isTest && "test-") || ""}server-rspack]`;
612
+ const serverNameConfig = `[${(isTest && 'test-') || ''}server-rspack]`;
640
613
  // Base server config
641
614
  let serverConfig = {
642
615
  name: serverNameConfig,
643
- target: "node",
616
+ target: 'node',
644
617
  mode,
645
618
  entry: serverEntry,
646
619
  output: {
647
620
  path: serverOutputDir,
648
621
  filename: () => `../${buildContext}/${outputPath}`,
649
- libraryTarget: "commonjs2",
650
- chunkFilename: `${chunksContext}/[id]${isProd ? ".[chunkhash]" : ""}.js`,
622
+ libraryTarget: 'commonjs2',
623
+ chunkFilename: `${chunksContext}/[id]${isProd ? '.[chunkhash]' : ''}.js`,
651
624
  assetModuleFilename,
652
625
  ...(isProd && { clean: { keep: keepOutsideBuild() } }),
653
626
  },
@@ -661,15 +634,15 @@ module.exports = async function (inMeteor = {}, argv = {}) {
661
634
  parser: {
662
635
  javascript: {
663
636
  // Dynamic imports on the server are treated as bundled in the same chunk
664
- dynamicImportMode: "eager",
637
+ dynamicImportMode: 'eager',
665
638
  },
666
639
  },
667
640
  },
668
641
  resolve: {
669
642
  extensions,
670
643
  alias,
671
- modules: ["node_modules", path.resolve(projectDir)],
672
- conditionNames: ["import", "require", "node", "default"],
644
+ modules: ['node_modules', path.resolve(projectDir)],
645
+ conditionNames: ['import', 'require', 'node', 'default'],
673
646
  },
674
647
  externals,
675
648
  externalsPresets: { node: true },
@@ -677,22 +650,18 @@ module.exports = async function (inMeteor = {}, argv = {}) {
677
650
  new DefinePlugin(
678
651
  isTest && (isTestModule || isTestEager)
679
652
  ? {
680
- "Meteor.isTest": JSON.stringify(isTest && !isTestFullApp),
681
- "Meteor.isAppTest": JSON.stringify(isTest && isTestFullApp),
682
- ...(!isPortableBuild && {
683
- "Meteor.isDevelopment": JSON.stringify(isDev),
684
- }),
653
+ 'Meteor.isTest': JSON.stringify(isTest && !isTestFullApp),
654
+ 'Meteor.isAppTest': JSON.stringify(isTest && isTestFullApp),
655
+ 'Meteor.isDevelopment': JSON.stringify(isDev),
685
656
  }
686
657
  : {
687
- "Meteor.isClient": JSON.stringify(false),
688
- "Meteor.isServer": JSON.stringify(true),
689
- "Meteor.isTest": JSON.stringify(isTestLike && !isTestFullApp),
690
- "Meteor.isAppTest": JSON.stringify(isTestLike && isTestFullApp),
691
- ...(!isPortableBuild && {
692
- "Meteor.isDevelopment": JSON.stringify(isDev),
693
- "Meteor.isProduction": JSON.stringify(isProd),
694
- }),
695
- }
658
+ 'Meteor.isClient': JSON.stringify(false),
659
+ 'Meteor.isServer': JSON.stringify(true),
660
+ 'Meteor.isTest': JSON.stringify(isTestLike && !isTestFullApp),
661
+ 'Meteor.isAppTest': JSON.stringify(isTestLike && isTestFullApp),
662
+ 'Meteor.isDevelopment': JSON.stringify(isDev),
663
+ 'Meteor.isProduction': JSON.stringify(isProd),
664
+ },
696
665
  ),
697
666
  ...bannerPluginConfig,
698
667
  requireExternalsPlugin,
@@ -700,20 +669,111 @@ module.exports = async function (inMeteor = {}, argv = {}) {
700
669
  ...doctorPluginConfig,
701
670
  ],
702
671
  watchOptions,
703
- devtool:
704
- isDevEnvironment || isNative || isTest
705
- ? "source-map"
706
- : "hidden-source-map",
672
+ devtool: isDevEnvironment || isNative || isTest ? 'source-map' : 'hidden-source-map',
707
673
  ...((isDevEnvironment || (isTest && !isTestEager) || isNative) &&
708
674
  cacheStrategy),
709
675
  ...lazyCompilationConfig,
676
+ ...loggingConfig,
710
677
  };
711
678
 
679
+ // Helper function to load and process config files
680
+ async function loadAndProcessConfig(configPath, configType, Meteor, argv, isAngularEnabled) {
681
+ try {
682
+ // Load the config file
683
+ let config;
684
+ if (path.extname(configPath) === '.mjs') {
685
+ // For ESM modules, we need to use dynamic import
686
+ const fileUrl = `file://${configPath}`;
687
+ const module = await import(fileUrl);
688
+ config = module.default || module;
689
+ } else {
690
+ // For CommonJS modules, we can use require
691
+ config = require(configPath)?.default || require(configPath);
692
+ }
693
+
694
+ // Process the config
695
+ const rawConfig = typeof config === 'function' ? config(Meteor, argv) : config;
696
+ const resolvedConfig = await Promise.resolve(rawConfig);
697
+ const userConfig = resolvedConfig && '0' in resolvedConfig ? resolvedConfig[0] : resolvedConfig;
698
+
699
+ // Define omitted paths and warning function
700
+ const omitPaths = [
701
+ "name",
702
+ "target",
703
+ "entry",
704
+ "output.path",
705
+ "output.filename",
706
+ ...(Meteor.isServer ? ["optimization.splitChunks", "optimization.runtimeChunk"] : []),
707
+ ].filter(Boolean);
708
+
709
+ const warningFn = path => {
710
+ if (isAngularEnabled) return;
711
+ console.warn(
712
+ `[${configType}] Ignored custom "${path}" — reserved for Meteor-Rspack integration.`,
713
+ );
714
+ };
715
+
716
+ // Clean omitted paths and merge Meteor Rspack fragments
717
+ let nextConfig = cleanOmittedPaths(userConfig, {
718
+ omitPaths,
719
+ warningFn,
720
+ });
721
+ nextConfig = mergeMeteorRspackFragments(nextConfig);
722
+
723
+ return nextConfig;
724
+ } catch (error) {
725
+ console.error(`Error loading ${configType} from ${configPath}:`, error);
726
+ if (configType === 'rspack.config.js') {
727
+ throw error; // Only rethrow for project config
728
+ }
729
+ return null;
730
+ }
731
+ }
732
+
733
+ // Load and apply project-level overrides for the selected build
734
+ // Check if we're in a Meteor package directory by looking at the path
735
+ const isMeteorPackageConfig = projectDir.includes('/packages/rspack');
736
+ // Track if user config overrides stats and infrastructureLogging
737
+ let statsOverrided = false;
738
+ if (fs.existsSync(projectConfigPath) && !isMeteorPackageConfig) {
739
+ // Check if there's a .mjs or .cjs version of the config file
740
+ const mjsConfigPath = projectConfigPath.replace(/\.js$/, '.mjs');
741
+ const cjsConfigPath = projectConfigPath.replace(/\.js$/, '.cjs');
742
+
743
+ let projectConfigPathToUse = projectConfigPath;
744
+ if (fs.existsSync(mjsConfigPath)) {
745
+ projectConfigPathToUse = mjsConfigPath;
746
+ } else if (fs.existsSync(cjsConfigPath)) {
747
+ projectConfigPathToUse = cjsConfigPath;
748
+ }
749
+
750
+ const nextUserConfig = await loadAndProcessConfig(
751
+ projectConfigPathToUse,
752
+ 'rspack.config.js',
753
+ Meteor,
754
+ argv,
755
+ isAngularEnabled
756
+ );
757
+
758
+ // Track if user config overrides stats
759
+ if (nextUserConfig) {
760
+ if (nextUserConfig.stats != null) {
761
+ statsOverrided = true;
762
+ }
763
+ if (Meteor.isClient) {
764
+ clientConfig = mergeSplitOverlap(clientConfig, nextUserConfig);
765
+ }
766
+ if (Meteor.isServer) {
767
+ serverConfig = mergeSplitOverlap(serverConfig, nextUserConfig);
768
+ }
769
+ }
770
+ }
771
+
712
772
  // Establish Angular overrides to ensure proper integration
713
773
  const angularExpandConfig = isAngularEnabled
714
774
  ? {
715
775
  mode: isProd ? "production" : "development",
716
- devServer: { port: devServerPort },
776
+ devServer: { port: Meteor.devServerPort },
717
777
  stats: { preset: "normal" },
718
778
  infrastructureLogging: { level: "info" },
719
779
  ...(isProd && isClient && { output: { module: false } }),
@@ -740,26 +800,35 @@ module.exports = async function (inMeteor = {}, argv = {}) {
740
800
  }
741
801
  : {};
742
802
 
743
-
744
- // Second pass: re-run only when a mode override was detected, so the user config
745
- // can depend on fully-computed Meteor flags and helpers (swcConfigOptions, buildOutputDir, etc.).
746
- if (nextUserConfig?.mode || nextOverrideConfig?.mode || isAngularEnabled) {
747
- ({ nextUserConfig, nextOverrideConfig } = await loadUserAndOverrideConfig(
748
- projectConfigPath,
749
- Meteor,
750
- argv
751
- ));
752
- }
753
- let config = isClient ? clientConfig : serverConfig;
754
- if (nextUserConfig) {
755
- config = mergeSplitOverlap(config, nextUserConfig);
756
- }
757
-
758
- config = mergeSplitOverlap(config, angularExpandConfig);
803
+ let config = mergeSplitOverlap(
804
+ isClient ? clientConfig : serverConfig,
805
+ angularExpandConfig
806
+ );
759
807
  config = mergeSplitOverlap(config, testClientExpandConfig);
760
808
 
761
- if (nextOverrideConfig) {
762
- config = mergeSplitOverlap(config, nextOverrideConfig);
809
+ // Check for override config file (extra file to override everything)
810
+ if (projectConfigPath) {
811
+ const configDir = path.dirname(projectConfigPath);
812
+ const configFileName = path.basename(projectConfigPath);
813
+ const configExt = path.extname(configFileName);
814
+ const configNameWithoutExt = configFileName.replace(configExt, '');
815
+ const configNameFull = `${configNameWithoutExt}.override${configExt}`;
816
+ const overrideConfigPath = path.join(configDir, configNameFull);
817
+
818
+ if (fs.existsSync(overrideConfigPath)) {
819
+ const nextOverrideConfig = await loadAndProcessConfig(
820
+ overrideConfigPath,
821
+ configNameFull,
822
+ Meteor,
823
+ argv,
824
+ isAngularEnabled
825
+ );
826
+
827
+ if (nextOverrideConfig) {
828
+ // Apply override config as the last step
829
+ config = mergeSplitOverlap(config, nextOverrideConfig);
830
+ }
831
+ }
763
832
  }
764
833
 
765
834
  const shouldDisablePlugins = config?.disablePlugins != null;
@@ -768,23 +837,33 @@ module.exports = async function (inMeteor = {}, argv = {}) {
768
837
  delete config.disablePlugins;
769
838
  }
770
839
 
771
- delete config["meteor.enablePortableBuild"];
772
-
773
- // if (Meteor.isDebug || Meteor.isVerbose) {
774
- console.log("Config:", inspect(config, { depth: null, colors: true }));
775
- // }
840
+ if (Meteor.isDebug || Meteor.isVerbose) {
841
+ console.log('Config:', inspect(config, { depth: null, colors: true }));
842
+ }
776
843
 
777
844
  // Check if lazyCompilation is enabled and warn the user
778
- if (
779
- config.lazyCompilation === true ||
780
- typeof config.lazyCompilation === "object"
781
- ) {
845
+ if (config.lazyCompilation === true || typeof config.lazyCompilation === 'object') {
782
846
  console.warn(
783
- "\n⚠️ Warning: lazyCompilation may not work correctly in the current Meteor-Rspack integration.\n" +
784
- " This feature will be evaluated for support in future Meteor versions.\n" +
785
- " If you encounter any issues, please disable it in your rspack config.\n"
847
+ '\n⚠️ Warning: lazyCompilation may not work correctly in the current Meteor-Rspack integration.\n' +
848
+ ' This feature will be evaluated for support in future Meteor versions.\n' +
849
+ ' If you encounter any issues, please disable it in your rspack config.\n',
786
850
  );
787
851
  }
788
852
 
853
+ // Add MeteorRspackOutputPlugin as the last plugin to output compilation info
854
+ const meteorRspackOutputPlugin = new MeteorRspackOutputPlugin({
855
+ getData: (stats, { isRebuild, compilationCount }) => ({
856
+ name: config.name,
857
+ mode: config.mode,
858
+ hasErrors: stats.hasErrors(),
859
+ hasWarnings: stats.hasWarnings(),
860
+ timestamp: Date.now(),
861
+ statsOverrided,
862
+ compilationCount,
863
+ isRebuild,
864
+ }),
865
+ });
866
+ config.plugins = [meteorRspackOutputPlugin, ...(config.plugins || [])];
867
+
789
868
  return [config];
790
869
  }
@@ -1,121 +0,0 @@
1
- const path = require('path');
2
- const fs = require('fs');
3
- const { cleanOmittedPaths } = require("./mergeRulesSplitOverlap.js");
4
- const { mergeMeteorRspackFragments } = require("./meteorRspackConfigFactory.js");
5
-
6
- // Helper function to load and process config files
7
- async function loadAndProcessConfig(configPath, configType, Meteor, argv, disableWarnings) {
8
- try {
9
- // Load the config file
10
- let config;
11
- if (path.extname(configPath) === '.mjs') {
12
- // For ESM modules, we need to use dynamic import
13
- const fileUrl = `file://${configPath}`;
14
- const module = await import(fileUrl);
15
- config = module.default || module;
16
- } else {
17
- // For CommonJS modules, we can use require
18
- config = require(configPath)?.default || require(configPath);
19
- }
20
-
21
- // Process the config
22
- const rawConfig = typeof config === 'function' ? config(Meteor, argv) : config;
23
- const resolvedConfig = await Promise.resolve(rawConfig);
24
- const userConfig = resolvedConfig && '0' in resolvedConfig ? resolvedConfig[0] : resolvedConfig;
25
-
26
- // Define omitted paths and warning function
27
- const omitPaths = [
28
- "name",
29
- "target",
30
- "entry",
31
- "output.path",
32
- "output.filename",
33
- ...(Meteor.isServer ? ["optimization.splitChunks", "optimization.runtimeChunk"] : []),
34
- ].filter(Boolean);
35
-
36
- const warningFn = path => {
37
- if (disableWarnings) return;
38
- console.warn(
39
- `[${configType}] Ignored custom "${path}" — reserved for Meteor-Rspack integration.`,
40
- );
41
- };
42
-
43
- // Clean omitted paths and merge Meteor Rspack fragments
44
- let nextConfig = cleanOmittedPaths(userConfig, {
45
- omitPaths,
46
- warningFn,
47
- });
48
- nextConfig = mergeMeteorRspackFragments(nextConfig);
49
-
50
- return nextConfig;
51
- } catch (error) {
52
- console.error(`Error loading ${configType} from ${configPath}:`, error);
53
- if (configType === 'rspack.config.js') {
54
- throw error; // Only rethrow for project config
55
- }
56
- return null;
57
- }
58
- }
59
-
60
- /**
61
- * Loads both the user's Rspack configuration and its potential override.
62
- *
63
- * @param {string|undefined} projectConfigPath
64
- * @param {object} Meteor
65
- * @param {object} argv
66
- * @returns {Promise<{ nextUserConfig: object|null, nextOverrideConfig: object|null }>}
67
- */
68
- async function loadUserAndOverrideConfig(projectConfigPath, Meteor, argv) {
69
- let nextUserConfig = null;
70
- let nextOverrideConfig = null;
71
-
72
- const projectDir = process.cwd();
73
- const isMeteorPackageConfig = projectDir.includes("/packages/rspack");
74
-
75
- if (projectConfigPath) {
76
- const configDir = path.dirname(projectConfigPath);
77
- const configFileName = path.basename(projectConfigPath);
78
- const configExt = path.extname(configFileName);
79
- const configNameWithoutExt = configFileName.replace(configExt, '');
80
- const configNameFull = `${configNameWithoutExt}.override${configExt}`;
81
- const overrideConfigPath = path.join(configDir, configNameFull);
82
-
83
- if (fs.existsSync(overrideConfigPath)) {
84
- nextOverrideConfig = await loadAndProcessConfig(
85
- overrideConfigPath,
86
- configNameFull,
87
- Meteor,
88
- argv,
89
- Meteor.isAngularEnabled
90
- );
91
- }
92
-
93
- if (fs.existsSync(projectConfigPath) && !isMeteorPackageConfig) {
94
- // Check if there's a .mjs or .cjs version of the config file
95
- const mjsConfigPath = projectConfigPath.replace(/\.js$/, '.mjs');
96
- const cjsConfigPath = projectConfigPath.replace(/\.js$/, '.cjs');
97
-
98
- let projectConfigPathToUse = projectConfigPath;
99
- if (fs.existsSync(mjsConfigPath)) {
100
- projectConfigPathToUse = mjsConfigPath;
101
- } else if (fs.existsSync(cjsConfigPath)) {
102
- projectConfigPathToUse = cjsConfigPath;
103
- }
104
-
105
- nextUserConfig = await loadAndProcessConfig(
106
- projectConfigPathToUse,
107
- 'rspack.config.js',
108
- Meteor,
109
- argv,
110
- Meteor.isAngularEnabled
111
- );
112
- }
113
- }
114
-
115
- return { nextUserConfig, nextOverrideConfig };
116
- }
117
-
118
- module.exports = {
119
- loadAndProcessConfig,
120
- loadUserAndOverrideConfig,
121
- };