@meteorjs/rspack 0.0.11 → 0.0.12

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meteorjs/rspack",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
4
4
  "description": "Configuration logic for using Rspack in Meteor projects",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -22,12 +22,25 @@ export class RequireExternalsPlugin {
22
22
  // It can be used to customize how external modules are mapped to file paths
23
23
  // If not provided, the default behavior is to map the external module name.
24
24
  externalMap = null,
25
+ // Enable global polyfill for module and exports
26
+ // If true, globalThis.module and globalThis.exports will be defined if they don't exist
27
+ enableGlobalPolyfill = true,
28
+ // Check function to determine if an external import should be eager
29
+ // If provided, it will be called with the package name and should return true for eager imports
30
+ // If not provided or returns false, the import will be lazy (default behavior)
31
+ isEagerImport = null,
32
+ // Array of module paths that should always be imported at the end of the file
33
+ // These will be treated as eager imports but will always be placed after all other imports
34
+ lastImports = null,
25
35
  } = {}) {
26
36
  this.pluginName = 'RequireExternalsPlugin';
27
37
 
28
38
  // Prepare externals
29
39
  this._externals = externals;
30
40
  this._externalMap = externalMap;
41
+ this._enableGlobalPolyfill = enableGlobalPolyfill;
42
+ this._isEagerImport = isEagerImport;
43
+ this._lastImports = lastImports;
31
44
  this._defaultExternalPrefix = 'external ';
32
45
 
33
46
  // Prepare paths
@@ -130,8 +143,10 @@ export class RequireExternalsPlugin {
130
143
  }
131
144
 
132
145
  compiler.hooks.done.tap({ name: this.pluginName, stage: -10 }, (stats) => {
133
- // 1) Ensure globalThis.module / exports block is present
134
- this._ensureGlobalThisModule();
146
+ // 1) Ensure globalThis.module / exports block is present if enabled
147
+ if (this._enableGlobalPolyfill) {
148
+ this._ensureGlobalThisModule();
149
+ }
135
150
 
136
151
  // 2) Re-load existing requires from disk on every run
137
152
  const existing = this._readExistingRequires();
@@ -163,21 +178,39 @@ export class RequireExternalsPlugin {
163
178
  // Strip out any now-empty helper functions:
164
179
  // function lazyExternalImportsX() {
165
180
  // }
166
- const emptyFnRe = /^function\s+lazyExternalImports\d+\s*\(\)\s*{\s*}\s*(\r?\n)?/gm;
167
- content = content.replace(emptyFnRe, '');
181
+ // or new format:
182
+ // // (function eagerExternalImportsX() {
183
+ // // })
184
+ // or lastImports format:
185
+ // // (function lastImports() {
186
+ // // })
187
+ const emptyLazyFnRe = /^function\s+lazyExternalImports\d+\s*\(\)\s*{\s*}\s*(\r?\n)?/gm;
188
+ const emptyEagerFnRe = /^\/\/\s*\(function\s+eagerExternalImports\d+\s*\(\)\s*{\s*\n\/\/\s*\}\)\s*(\r?\n)?/gm;
189
+ const emptyLastFnRe = /^\/\/\s*\(function\s+lastImports(?:\d+)?\s*\(\)\s*{\s*\n\/\/\s*\}\)\s*(\r?\n)?/gm;
190
+ content = content.replace(emptyLazyFnRe, '');
191
+ content = content.replace(emptyEagerFnRe, '');
192
+ content = content.replace(emptyLastFnRe, '');
168
193
 
169
194
  // Write the cleaned file back
170
195
  fs.writeFileSync(this.filePath, content, 'utf-8');
171
196
 
172
197
  // Re-populate `existing` so the add-diff is accurate
173
198
  existing.clear();
199
+ // Check for require statements
174
200
  for (const match of content.matchAll(/require\('([^']+)'\)/g)) {
175
201
  existing.add(match[1]);
176
202
  }
203
+ // Also check for import statements (used in the new format)
204
+ for (const match of content.matchAll(/import\s+'([^']+)'/g)) {
205
+ existing.add(match[1]);
206
+ }
177
207
  }
178
208
 
179
- // 3) Collect any new externals from this build
180
- const newRequires = [];
209
+ // 3) Collect any new externals from this build and separate into eager, lazy, and last
210
+ const newLazyRequires = [];
211
+ const newEagerRequires = [];
212
+ const newLastRequires = [];
213
+
181
214
  for (const module of info.modules) {
182
215
  const name = module.name;
183
216
  const matchInfo = this._isExternalModule(name);
@@ -186,19 +219,180 @@ export class RequireExternalsPlugin {
186
219
  const pkg = this._extractPackageName(name, matchInfo);
187
220
  if (pkg && !existing.has(pkg)) {
188
221
  existing.add(pkg);
189
- newRequires.push(`require('${pkg}')`);
222
+
223
+ // Check if this should be a last import
224
+ if (this._lastImports && Array.isArray(this._lastImports) && this._lastImports.includes(pkg)) {
225
+ newLastRequires.push(`require('${pkg}')`);
226
+ }
227
+ // Check if this should be an eager import
228
+ else if (this._isEagerImport && typeof this._isEagerImport === 'function' && this._isEagerImport(pkg)) {
229
+ newEagerRequires.push(`require('${pkg}')`);
230
+ } else {
231
+ // Default to lazy import
232
+ newLazyRequires.push(`require('${pkg}')`);
233
+ }
190
234
  }
191
235
  }
192
236
 
193
- // 4) Append new imports if any
194
- if (newRequires.length) {
237
+ // 4) Append new lazy imports if any
238
+ if (newLazyRequires.length) {
195
239
  const fnName = `lazyExternalImports${this._funcCount++}`;
196
- const body = newRequires.map(req => ` ${req};`).join('\n');
240
+ const body = newLazyRequires.map(req => ` ${req};`).join('\n');
197
241
  const fnCode = `\nfunction ${fnName}() {\n${body}\n}\n`;
198
242
  try {
199
243
  fs.appendFileSync(this.filePath, fnCode);
200
244
  } catch (err) {
201
- console.error(`Failed to append imports to ${this.filePath}:`, err);
245
+ console.error(`Failed to append lazy imports to ${this.filePath}:`, err);
246
+ }
247
+ }
248
+
249
+ // 5) Append new eager imports if any
250
+ if (newEagerRequires.length) {
251
+ const fnName = `eagerExternalImports${this._funcCount++}`;
252
+ // Convert require statements to import statements
253
+ const body = newEagerRequires
254
+ .map(req => {
255
+ // Extract the module path from require('path')
256
+ const modulePath = req.match(/require\('([^']+)'\)/)[1];
257
+ return `import '${modulePath}';`;
258
+ })
259
+ .join('\n');
260
+ // Use comments instead of actual function
261
+ const fnCode = `\n// (function ${fnName}() {\n${body}\n// })\n`;
262
+ try {
263
+ fs.appendFileSync(this.filePath, fnCode);
264
+ } catch (err) {
265
+ console.error(`Failed to append eager imports to ${this.filePath}:`, err);
266
+ }
267
+ }
268
+
269
+ // 6) Handle lastImports - these should always be at the end of the file
270
+ // First, check if lastImports already exist in the file
271
+ let lastImportsExist = false;
272
+ let lastImportsAtEnd = false;
273
+ let content = '';
274
+
275
+ if (fs.existsSync(this.filePath)) {
276
+ content = fs.readFileSync(this.filePath, 'utf-8');
277
+
278
+ // Check if lastImports exist in the file
279
+ const lastImportsRe = /\/\/\s*\(function\s+lastImports(?:\d+)?\s*\(\)\s*{\s*\n([\s\S]*?)\/\/\s*\}\)/g;
280
+ const match = lastImportsRe.exec(content);
281
+
282
+ if (match) {
283
+ lastImportsExist = true;
284
+
285
+ // Check if lastImports are at the end of the file
286
+ // We'll consider them at the end if there's only whitespace after them
287
+ const afterLastImports = content.substring(match.index + match[0].length);
288
+ if (/^\s*$/.test(afterLastImports)) {
289
+ lastImportsAtEnd = true;
290
+ }
291
+ }
292
+ }
293
+
294
+ // If lastImports exist but are not at the end, move them to the end
295
+ if (lastImportsExist && !lastImportsAtEnd) {
296
+ // Remove the existing lastImports
297
+ const lastImportsRe = /\/\/\s*\(function\s+lastImports(?:\d+)?\s*\(\)\s*{\s*\n[\s\S]*?\/\/\s*\}\)\s*(\r?\n)?/g;
298
+ content = content.replace(lastImportsRe, '');
299
+
300
+ // Extract the imports from the existing lastImports
301
+ const importRe = /import\s+'([^']+)'/g;
302
+ const existingLastImports = [];
303
+ let match;
304
+
305
+ while ((match = importRe.exec(content)) !== null) {
306
+ if (this._lastImports && Array.isArray(this._lastImports) && this._lastImports.includes(match[1])) {
307
+ existingLastImports.push(`import '${match[1]}';`);
308
+ }
309
+ }
310
+
311
+ // Add any new lastImports
312
+ if (this._lastImports && Array.isArray(this._lastImports)) {
313
+ for (const pkg of this._lastImports) {
314
+ if (!existingLastImports.some(imp => imp === `import '${pkg}';`) && existing.has(pkg)) {
315
+ existingLastImports.push(`import '${pkg}';`);
316
+ }
317
+ }
318
+ }
319
+
320
+ // Add the lastImports to the end of the file
321
+ if (existingLastImports.length > 0) {
322
+ const body = existingLastImports.join('\n');
323
+ const fnCode = `\n// (function lastImports() {\n${body}\n// })\n`;
324
+ fs.writeFileSync(this.filePath, content + fnCode);
325
+ } else {
326
+ fs.writeFileSync(this.filePath, content);
327
+ }
328
+ }
329
+ // If lastImports don't exist, add them if needed
330
+ else if (!lastImportsExist) {
331
+ // Collect all lastImports
332
+ const allLastImports = [];
333
+
334
+ // Add any new lastImports from this build
335
+ if (newLastRequires.length) {
336
+ for (const req of newLastRequires) {
337
+ const modulePath = req.match(/require\('([^']+)'\)/)[1];
338
+ allLastImports.push(`import '${modulePath}';`);
339
+ }
340
+ }
341
+
342
+ // Add any existing lastImports from the configuration
343
+ if (this._lastImports && Array.isArray(this._lastImports)) {
344
+ for (const pkg of this._lastImports) {
345
+ if (!allLastImports.some(imp => imp === `import '${pkg}';`) && !existing.has(pkg)) {
346
+ allLastImports.push(`import '${pkg}';`);
347
+ }
348
+ }
349
+ }
350
+
351
+ // Add the lastImports to the end of the file
352
+ if (allLastImports.length > 0) {
353
+ const body = allLastImports.join('\n');
354
+ const fnCode = `\n// (function lastImports() {\n${body}\n// })\n`;
355
+ try {
356
+ fs.appendFileSync(this.filePath, fnCode);
357
+ } catch (err) {
358
+ console.error(`Failed to append last imports to ${this.filePath}:`, err);
359
+ }
360
+ }
361
+ }
362
+ // If lastImports exist and are already at the end, add any new ones
363
+ else if (lastImportsExist && lastImportsAtEnd && newLastRequires.length) {
364
+ // Extract the existing lastImports
365
+ const lastImportsRe = /\/\/\s*\(function\s+lastImports(?:\d+)?\s*\(\)\s*{\s*\n([\s\S]*?)\/\/\s*\}\)/;
366
+ const match = lastImportsRe.exec(content);
367
+
368
+ if (match) {
369
+ const existingBody = match[1];
370
+ const existingImports = new Set();
371
+
372
+ // Extract the imports from the existing lastImports
373
+ const importRe = /import\s+'([^']+)'/g;
374
+ let importMatch;
375
+
376
+ while ((importMatch = importRe.exec(existingBody)) !== null) {
377
+ existingImports.add(importMatch[1]);
378
+ }
379
+
380
+ // Add any new lastImports
381
+ let newBody = existingBody;
382
+ for (const req of newLastRequires) {
383
+ const modulePath = req.match(/require\('([^']+)'\)/)[1];
384
+ if (!existingImports.has(modulePath)) {
385
+ newBody += `import '${modulePath}';\n`;
386
+ }
387
+ }
388
+
389
+ // Replace the existing lastImports with the updated ones
390
+ const updatedContent = content.replace(
391
+ lastImportsRe,
392
+ `// (function lastImports() {\n${newBody}// })`
393
+ );
394
+
395
+ fs.writeFileSync(this.filePath, updatedContent);
202
396
  }
203
397
  }
204
398
  });
@@ -209,12 +403,33 @@ export class RequireExternalsPlugin {
209
403
  if (fs.existsSync(this.filePath)) {
210
404
  try {
211
405
  const content = fs.readFileSync(this.filePath, 'utf-8');
212
- const fnRe = /function\s+lazyExternalImports(\d+)\s*\(\)/g;
406
+ // Check for lazy, eager, and last external imports functions
407
+ const lazyFnRe = /function\s+lazyExternalImports(\d+)\s*\(\)/g;
408
+ // Only match the new commented format
409
+ const eagerFnRe = /\/\/\s*\(function\s+eagerExternalImports(\d+)\s*\(\)/g;
410
+ // Match the lastImports format
411
+ const lastFnRe = /\/\/\s*\(function\s+lastImports(\d+)?\s*\(\)/g;
412
+
213
413
  let match;
214
- while ((match = fnRe.exec(content)) !== null) {
414
+ // Check lazy imports
415
+ while ((match = lazyFnRe.exec(content)) !== null) {
215
416
  const n = parseInt(match[1], 10);
216
417
  if (n > max) max = n;
217
418
  }
419
+
420
+ // Check eager imports
421
+ while ((match = eagerFnRe.exec(content)) !== null) {
422
+ const n = parseInt(match[1], 10);
423
+ if (n > max) max = n;
424
+ }
425
+
426
+ // Check last imports
427
+ while ((match = lastFnRe.exec(content)) !== null) {
428
+ if (match[1]) {
429
+ const n = parseInt(match[1], 10);
430
+ if (n > max) max = n;
431
+ }
432
+ }
218
433
  } catch {
219
434
  // ignore read errors
220
435
  }
@@ -251,11 +466,18 @@ export class RequireExternalsPlugin {
251
466
  const existing = new Set();
252
467
  try {
253
468
  const content = fs.readFileSync(this.filePath, 'utf-8');
469
+ // Check for require statements
254
470
  const requireRegex = /require\('([^']+)'\)/g;
255
471
  let match;
256
472
  while ((match = requireRegex.exec(content)) !== null) {
257
473
  existing.add(match[1]);
258
474
  }
475
+
476
+ // Also check for import statements (used in the new format)
477
+ const importRegex = /import\s+'([^']+)'/g;
478
+ while ((match = importRegex.exec(content)) !== null) {
479
+ existing.add(match[1]);
480
+ }
259
481
  } catch {
260
482
  // ignore if file missing or unreadable
261
483
  }
package/rspack.config.js CHANGED
@@ -43,7 +43,7 @@ function createCacheStrategy(mode) {
43
43
  }
44
44
 
45
45
  // SWC loader rule (JSX/JS)
46
- function createSwcConfig({ isRun, isTypescriptEnabled, isJsxEnabled, isTsxEnabled, externalHelpers }) {
46
+ function createSwcConfig({ isTypescriptEnabled, isJsxEnabled, isTsxEnabled, externalHelpers, isDevEnvironment }) {
47
47
  const defaultConfig = {
48
48
  jsc: {
49
49
  baseUrl: process.cwd(),
@@ -56,8 +56,8 @@ function createSwcConfig({ isRun, isTypescriptEnabled, isJsxEnabled, isTsxEnable
56
56
  target: 'es2015',
57
57
  transform: {
58
58
  react: {
59
- development: isRun,
60
- refresh: isRun,
59
+ development: isDevEnvironment,
60
+ refresh: isDevEnvironment,
61
61
  },
62
62
  },
63
63
  externalHelpers,
@@ -156,12 +156,13 @@ export default function (inMeteor = {}, argv = {}) {
156
156
  console.log('[i] Meteor flags:', Meteor);
157
157
  }
158
158
 
159
+ const isDevEnvironment = isRun && isDev && !isTest;
159
160
  const swcConfigRule = createSwcConfig({
160
- isRun,
161
161
  isTypescriptEnabled,
162
162
  isJsxEnabled,
163
163
  isTsxEnabled,
164
164
  externalHelpers: swcExternalHelpers,
165
+ isDevEnvironment,
165
166
  });
166
167
  const externals = [
167
168
  /^meteor.*/,
@@ -193,16 +194,12 @@ export default function (inMeteor = {}, argv = {}) {
193
194
  filePath: path.join(buildContext, runPath),
194
195
  ...(Meteor.isBlazeEnabled && {
195
196
  externals: /\.html$/,
196
- externalMap: (module) => {
197
- const { request, context } = module;
198
- if (request.endsWith('.html')) {
199
- const relContext = path.relative(process.cwd(), context);
200
- const { name } = path.parse(request);
201
- return `./${relContext}/template.${name}.js`;
202
- }
203
- return request;
197
+ isEagerImport: (module) => module.endsWith('.html'),
198
+ ...isProd && {
199
+ lastImports: [`./${outputFilename}`],
204
200
  },
205
201
  }),
202
+ enableGlobalPolyfill: isDevEnvironment,
206
203
  });
207
204
 
208
205
  const clientNameConfig = `[${isTest && 'test-' || ''}${isTestModule && 'module' || 'client'}-rspack]`;
@@ -210,12 +207,12 @@ export default function (inMeteor = {}, argv = {}) {
210
207
  let clientConfig = {
211
208
  name: clientNameConfig,
212
209
  target: 'web',
213
- mode,
210
+ mode: 'development',
214
211
  entry: path.resolve(process.cwd(), buildContext, entryPath),
215
212
  output: {
216
213
  path: clientOutputDir,
217
214
  filename: () =>
218
- isRun && !isTest ? outputFilename : `../${buildContext}/${outputPath}`,
215
+ isDevEnvironment ? outputFilename : `../${buildContext}/${outputPath}`,
219
216
  libraryTarget: 'commonjs',
220
217
  publicPath: '/',
221
218
  chunkFilename: `${bundlesContext}/[id].[chunkhash].js`,
@@ -243,7 +240,7 @@ export default function (inMeteor = {}, argv = {}) {
243
240
  externals,
244
241
  plugins: [
245
242
  ...[
246
- ...(isReactEnabled && reactRefreshModule
243
+ ...(isReactEnabled && reactRefreshModule && isDevEnvironment
247
244
  ? [new reactRefreshModule()]
248
245
  : []),
249
246
  requireExternalsPlugin,
@@ -261,8 +258,8 @@ export default function (inMeteor = {}, argv = {}) {
261
258
  }),
262
259
  ],
263
260
  watchOptions,
264
- devtool: isDev || isTest ? 'source-map' : 'hidden-source-map',
265
- ...(isRun && !isTest && {
261
+ devtool: isDevEnvironment || isTest ? 'source-map' : 'hidden-source-map',
262
+ ...(isDevEnvironment && {
266
263
  devServer: {
267
264
  static: { directory: clientOutputDir, publicPath: '/__rspack__/' },
268
265
  hot: true,
@@ -322,8 +319,8 @@ export default function (inMeteor = {}, argv = {}) {
322
319
  isTestModule && requireExternalsPlugin,
323
320
  ],
324
321
  watchOptions,
325
- devtool: isRun ? 'source-map' : 'hidden-source-map',
326
- ...((isRun || isTest) &&
322
+ devtool: isDevEnvironment || isTest ? 'source-map' : 'hidden-source-map',
323
+ ...((isDevEnvironment || isTest) &&
327
324
  createCacheStrategy(mode)
328
325
  ),
329
326
  };