@meteorjs/rspack 2.1.0 → 2.2.0-beta.1

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/lib/ignore.js CHANGED
@@ -85,15 +85,25 @@ function createIgnoreGlobConfig(entries = []) {
85
85
  /**
86
86
  * Creates a regex pattern to match the specified glob patterns.
87
87
  * Converts glob patterns with * and ** into regex equivalents.
88
- *
88
+ *
89
89
  * @param {string[]} globPatterns - Array of glob patterns from createIgnoreGlobConfig
90
+ * @param {string} [rootPath] - Absolute root that matched paths must belong to
90
91
  * @returns {RegExp} - Regex pattern to match the specified patterns
91
92
  */
92
- function createIgnoreRegex(globPatterns) {
93
+ function createIgnoreRegex(globPatterns, rootPath) {
93
94
  if (!Array.isArray(globPatterns) || globPatterns.length === 0) {
94
95
  throw new Error('globPatterns must be a non-empty array');
95
96
  }
96
97
 
98
+ // Rspack applies context exclusions to absolute module paths. Anchor rooted
99
+ // patterns here so matching starts inside the app, not in a parent folder.
100
+ const pathPrefix = rootPath
101
+ ? `^${rootPath
102
+ .replace(/\\/g, '/')
103
+ .replace(/\/+$/, '')
104
+ .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?=/|$)`
105
+ : '(?:^|/)';
106
+
97
107
  // Process each glob pattern and convert to regex
98
108
  const regexPatterns = globPatterns.map(pattern => {
99
109
  // Skip negation patterns for the regex
@@ -115,9 +125,7 @@ function createIgnoreRegex(globPatterns) {
115
125
  // Convert the ** placeholder to its regex equivalent (any number of characters including /)
116
126
  regexPattern = regexPattern.replace(new RegExp(DOUBLE_ASTERISK_PLACEHOLDER, 'g'), '.*');
117
127
 
118
- // For absolute paths, we don't want to force the pattern to match from the beginning
119
- // but we still want to ensure it matches to the end of the path segment
120
- regexPattern = '(?:^|/)' + regexPattern;
128
+ regexPattern = pathPrefix + regexPattern;
121
129
 
122
130
  return regexPattern;
123
131
  }).filter(pattern => pattern !== null);
@@ -20,7 +20,7 @@ function extractLocalDependencies(configFilePath) {
20
20
 
21
21
  // Parse the file into an AST
22
22
  const ast = swc.parseSync(content, {
23
- syntax: 'ecmascript',
23
+ syntax: /\.[mc]?tsx?$/.test(configFilePath) ? 'typescript' : 'ecmascript',
24
24
  dynamicImport: true,
25
25
  target: 'es2020',
26
26
  });
@@ -30,10 +30,10 @@ function extractLocalDependencies(configFilePath) {
30
30
  let modulePath = null;
31
31
 
32
32
  // Handle require() calls: require('./plugin')
33
- if (node.type === 'CallExpression' &&
34
- node.callee.type === 'Identifier' &&
35
- node.callee.value === 'require' &&
36
- node.arguments.length > 0) {
33
+ if (node.type === 'CallExpression' &&
34
+ node.callee.type === 'Identifier' &&
35
+ node.callee.value === 'require' &&
36
+ node.arguments.length > 0) {
37
37
  const arg = node.arguments[0];
38
38
  if (arg.expression?.type === 'StringLiteral') {
39
39
  modulePath = arg.expression.value;
@@ -42,8 +42,8 @@ function extractLocalDependencies(configFilePath) {
42
42
 
43
43
  // Handle dynamic import() calls: import('./plugin')
44
44
  if (node.type === 'CallExpression' &&
45
- node.callee.type === 'Import' &&
46
- node.arguments.length > 0) {
45
+ node.callee.type === 'Import' &&
46
+ node.arguments.length > 0) {
47
47
  const arg = node.arguments[0];
48
48
  if (arg.expression?.type === 'StringLiteral') {
49
49
  modulePath = arg.expression.value;
@@ -163,7 +163,7 @@ function resolveLocalModule(modulePath, configDir, projectDir) {
163
163
  const resolvedReal = fs.realpathSync(resolvedPath);
164
164
  const projectReal = fs.realpathSync(projectDir);
165
165
 
166
- const isWithinProject =
166
+ const isWithinProject =
167
167
  resolvedReal === projectReal ||
168
168
  resolvedReal.startsWith(projectReal + path.sep);
169
169
  const hasNodeModulesSegment = resolvedReal.split(path.sep).includes('node_modules');
package/lib/test.js CHANGED
@@ -44,7 +44,8 @@ const generateEagerTestFile = ({
44
44
 
45
45
  // Create regex from ignore entries
46
46
  const excludeFoldersRegex = createIgnoreRegex(
47
- createIgnoreGlobConfig(ignoreEntries)
47
+ createIgnoreGlobConfig(ignoreEntries),
48
+ projectDir,
48
49
  );
49
50
  // Create regex from meteor ignore entries
50
51
  const excludeMeteorIgnoreRegex = inMeteorIgnoreEntries.length > 0
@@ -74,14 +75,14 @@ const generateEagerTestFile = ({
74
75
  exclude: ${excludeFoldersRegex.toString()},
75
76
  mode: 'eager',
76
77
  });
77
- ctx.keys().filter((k) => {
78
+ await Promise.all(ctx.keys().filter((k) => {
78
79
  ${
79
80
  excludeMeteorIgnoreRegex
80
81
  ? `// Only exclude based on *relative* path segments.
81
82
  return !MeteorIgnoreRegex.test(k);`
82
83
  : "return true;"
83
84
  }
84
- }).forEach(ctx);
85
+ }).map(ctx));
85
86
  ${
86
87
  extraEntry
87
88
  ? `const extra = import.meta.webpackContext('${toPosix(path.dirname(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meteorjs/rspack",
3
- "version": "2.1.0",
3
+ "version": "2.2.0-beta.1",
4
4
  "description": "Configuration logic for using Rspack in Meteor projects",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
@@ -172,8 +172,24 @@ class RequireExternalsPlugin {
172
172
  // 2) Re-load existing requires from disk on every run
173
173
  const existing = this._readExistingRequires();
174
174
 
175
- // 2a) Compute the *current* externals in this build
176
- const info = stats.toJson({ modules: true });
175
+ // 2a) Compute the *current* externals in this build.
176
+ // Only module names are needed, so start from `all: false` instead of
177
+ // serializing the whole stats object (chunks, assets, reasons, ...)
178
+ // on every compilation — a fixed cost per rebuild that grows with app
179
+ // size. `cachedModules: true` keeps unchanged modules in the list on
180
+ // incremental rebuilds, so their requires are not wrongly removed.
181
+ // `orphanModules: true` is required in production: module
182
+ // concatenation absorbs the externals' importers, marking the
183
+ // external modules themselves as orphans, and without this flag
184
+ // they vanish from the list entirely — which stripped every
185
+ // mirrored require (meteor/*, Blaze .html) from the meteor entry.
186
+ // See meteor/meteor#14568.
187
+ const info = stats.toJson({
188
+ all: false,
189
+ modules: true,
190
+ cachedModules: true,
191
+ orphanModules: true,
192
+ });
177
193
  const current = new Set();
178
194
  for (const m of info.modules) {
179
195
  const matchInfo = this._isExternalModule(m.name);
@@ -497,17 +513,22 @@ class RequireExternalsPlugin {
497
513
 
498
514
  _readExistingRequires() {
499
515
  const existing = new Set();
516
+ // Generated Rspack bridge imports are not managed externals. Relative
517
+ // imports such as Blaze HTML files still belong to this plugin.
518
+ const isRspackBridgeImport = (modulePath) =>
519
+ typeof modulePath === 'string' &&
520
+ /(?:^|[/\\])[^/\\]*-rspack\.(?:js|cjs)$/.test(modulePath);
500
521
  try {
501
522
  const content = fs.readFileSync(this.filePath, 'utf-8');
502
523
  // Check for require statements
503
524
  let match;
504
525
  while ((match = STANDALONE_REQUIRE_REGEX.exec(content)) !== null) {
505
- existing.add(match[1]);
526
+ if (!isRspackBridgeImport(match[1])) existing.add(match[1]);
506
527
  }
507
528
 
508
529
  // Also check for import statements (used in the new format)
509
530
  while ((match = STANDALONE_IMPORT_REGEX.exec(content)) !== null) {
510
- existing.add(match[1]);
531
+ if (!isRspackBridgeImport(match[1])) existing.add(match[1]);
511
532
  }
512
533
  } catch {
513
534
  // ignore if file missing or unreadable
package/rspack.config.js CHANGED
@@ -101,8 +101,13 @@ function createCacheStrategy(
101
101
  type: "persistent",
102
102
  storage: {
103
103
  type: "filesystem",
104
+ // Include the mode in the directory (not just the version):
105
+ // rspack invalidates a persistent cache on version mismatch, so
106
+ // sharing one directory between development and production would
107
+ // wipe the cache on every `meteor run` <-> `meteor build` switch
108
+ // instead of keeping both warm. See meteor/meteor#14568.
104
109
  directory: `node_modules/.cache/rspack/${
105
- [buildContext, side].filter(Boolean).join('-') || 'default'
110
+ [buildContext, side, mode].filter(Boolean).join('-') || 'default'
106
111
  }`,
107
112
  },
108
113
  ...(buildDependencies.length > 0 && {
@@ -401,7 +406,11 @@ module.exports = async function (inMeteor = {}, argv = {}) {
401
406
  cacheStrategy = createCacheStrategy(
402
407
  mode,
403
408
  (Meteor.isClient && "client") || "server",
404
- { projectConfigPath, configPath }
409
+ // buildContext must be passed here too: this reassignment is the
410
+ // effective cache strategy, and omitting it made the cache directory
411
+ // collide across build contexts (e.g. custom METEOR_LOCAL_DIR setups).
412
+ // See meteor/meteor#14568.
413
+ { projectConfigPath, configPath, buildContext }
405
414
  );
406
415
 
407
416
  // Determine run point
@@ -488,7 +497,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
488
497
  ...(Meteor.isBlazeEnabled && {
489
498
  externals: /\.html$/,
490
499
  isEagerImport: (module) => module.endsWith(".html"),
491
- ...(isProd && {
500
+ ...((isProd || (isTest && isClient)) && {
492
501
  lastImports: [`./${outputFilename}`],
493
502
  }),
494
503
  }),
@@ -806,8 +815,12 @@ module.exports = async function (inMeteor = {}, argv = {}) {
806
815
  isDevEnvironment || isNative || isTest
807
816
  ? "source-map"
808
817
  : "hidden-source-map",
809
- ...((isDevEnvironment || (isTest && !isTestEager) || isNative) &&
810
- cacheStrategy),
818
+ // Apply the persistent cache to production builds too (the client
819
+ // config always has it); previously `meteor build` recompiled the
820
+ // entire server bundle cold every time. Eager server test builds are
821
+ // still excluded, since their generated entry changes on every run.
822
+ // See meteor/meteor#14568.
823
+ ...(!(isTest && isTestEager) && cacheStrategy),
811
824
  ...lazyCompilationConfig,
812
825
  ...loggingConfig,
813
826
  };