@meteorjs/rspack 0.0.55 → 0.0.57

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.
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  const { mergeWithCustomize } = require('webpack-merge');
7
+ const isEqual = require('fast-deep-equal');
7
8
 
8
9
  /**
9
10
  * File extensions to check when determining rule overlaps.
@@ -87,6 +88,10 @@ function splitOverlapRulesMerge(aRules, bRules) {
87
88
  for (let i = 0; i < result.length; i++) {
88
89
  const aRule = result[i];
89
90
 
91
+
92
+ const isMergeableRule = isEqual(aRule?.include || [], bRule?.include || []);
93
+ if (!isMergeableRule) continue;
94
+
90
95
  // Determine which extensions each rule matches (within our catalog)
91
96
  const aExts = EXT_CATALOG.filter(ext => ruleMatchesExt(aRule, ext));
92
97
  const bExts = EXT_CATALOG.filter(ext => ruleMatchesExt(bRule, ext));
@@ -0,0 +1,96 @@
1
+ // meteorRspackConfigFactory.js
2
+
3
+ const { mergeSplitOverlap } = require("./mergeRulesSplitOverlap.js");
4
+
5
+ const DEFAULT_PREFIX = "meteorRspackConfig";
6
+ let counter = 0;
7
+
8
+ /**
9
+ * Create a uniquely keyed Rspack config fragment.
10
+ * Example return: { meteorRspackConfig1: { ...customConfig } }
11
+ *
12
+ * @param {object} customConfig
13
+ * @param {{ key?: number|string, prefix?: string }} [opts]
14
+ * @returns {Record<string, object>}
15
+ */
16
+ function prepareMeteorRspackConfig(customConfig, opts = {}) {
17
+ if (!customConfig || typeof customConfig !== "object") {
18
+ throw new TypeError("customConfig must be an object");
19
+ }
20
+ const prefix = opts.prefix || DEFAULT_PREFIX;
21
+
22
+ let name;
23
+ if (opts.key != null) {
24
+ const k = String(opts.key).trim();
25
+ if (/^\d+$/.test(k)) name = `${prefix}${k}`;
26
+ else if (k.startsWith(prefix) && /^\d+$/.test(k.slice(prefix.length)))
27
+ name = k;
28
+ else
29
+ throw new Error(`opts.key must be a positive integer or "${prefix}<n>"`);
30
+
31
+ const n = parseInt(name.slice(prefix.length), 10);
32
+ if (Number.isFinite(n) && n > counter) counter = n;
33
+ } else {
34
+ counter += 1;
35
+ name = `${prefix}${counter}`;
36
+ }
37
+
38
+ return { [name]: customConfig };
39
+ }
40
+
41
+ /**
42
+ * Merge all `{prefix}<n>` fragments into `config` using `mergeSplitOverlap`,
43
+ * then remove those temporary keys. Mutates `config`.
44
+ *
45
+ * Order: fragments are applied in ascending numeric order (1, 2, 3, ...).
46
+ *
47
+ * @param {object} config
48
+ * @param {{ prefix?: string }} [opts]
49
+ * @returns {object} The same (mutated) config instance.
50
+ */
51
+ function mergeMeteorRspackFragments(config, opts = {}) {
52
+ if (!config || typeof config !== "object" || Array.isArray(config)) {
53
+ throw new TypeError("config must be a plain object");
54
+ }
55
+ const prefix = opts.prefix || DEFAULT_PREFIX;
56
+
57
+ // Collect fragment keys like "meteorRspackConfig12"
58
+ const tempKeys = Object.keys(config)
59
+ .filter((k) => k.startsWith(prefix) && /^\d+$/.test(k.slice(prefix.length)))
60
+ .map((k) => [k, parseInt(k.slice(prefix.length), 10)])
61
+ .sort((a, b) => a[1] - b[1])
62
+ .map(([k]) => k);
63
+
64
+ if (tempKeys.length === 0) return config;
65
+
66
+ // Apply each fragment with your merge policy
67
+ for (const k of tempKeys) {
68
+ const fragment = config[k];
69
+ if (!fragment || typeof fragment !== "object" || Array.isArray(fragment)) {
70
+ throw new Error(`Fragment "${k}" must be a plain object`);
71
+ }
72
+ const merged = mergeSplitOverlap(config, fragment);
73
+
74
+ // Keep object identity: replace contents of `config` with `merged`
75
+ replaceObject(config, merged);
76
+ }
77
+
78
+ // Strip the temp keys at the end
79
+ for (const k of tempKeys) delete config[k];
80
+
81
+ return config;
82
+ }
83
+
84
+ function replaceObject(target, source) {
85
+ for (const key of Object.keys(target)) {
86
+ if (!(key in source)) delete target[key];
87
+ }
88
+ for (const key of Object.keys(source)) {
89
+ target[key] = source[key];
90
+ }
91
+ }
92
+
93
+ module.exports = {
94
+ prepareMeteorRspackConfig,
95
+ mergeMeteorRspackFragments,
96
+ };
@@ -0,0 +1,81 @@
1
+ const path = require("path");
2
+ const { prepareMeteorRspackConfig } = require("./meteorRspackConfigFactory");
3
+ const { builtinModules } = require("module");
4
+
5
+ /**
6
+ * Resolve a package directory from node resolution.
7
+ * @param {string} pkg
8
+ * @returns {string} absolute directory of the package
9
+ */
10
+ function pkgDir(pkg) {
11
+ const resolved = require.resolve(`${pkg}/package.json`, {
12
+ paths: [process.cwd()],
13
+ });
14
+ return path.dirname(resolved);
15
+ }
16
+
17
+ /**
18
+ * Wrap externals for Meteor runtime (marks deps as externals).
19
+ * Usage: compileWithMeteor(["sharp", "vimeo", "fs"])
20
+ *
21
+ * @param {string[]} deps - package names or module IDs
22
+ * @returns {Record<string, object>} `{ meteorRspackConfigX: { externals: [...] } }`
23
+ */
24
+ function compileWithMeteor(deps) {
25
+ const flat = deps.flat().filter(Boolean);
26
+ return prepareMeteorRspackConfig({
27
+ externals: flat,
28
+ });
29
+ }
30
+
31
+ /**
32
+ * Add SWC transpilation rules limited to specific deps (monorepo-friendly).
33
+ * Usage: compileWithRspack(["@org/lib-a", "zod"])
34
+ *
35
+ * Requires global `Meteor.swcConfigOptions` (as in your setup).
36
+ *
37
+ * @param {string[]} deps - package names to include in SWC loader
38
+ * @returns {Record<string, object>} `{ meteorRspackConfigX: { module: { rules: [...] } } }`
39
+ */
40
+ function compileWithRspack(deps, { options = {} } = {}) {
41
+ const includeDirs = deps.flat().filter(Boolean).map(pkgDir);
42
+
43
+ return prepareMeteorRspackConfig({
44
+ module: {
45
+ rules: [
46
+ {
47
+ test: /\.(?:[mc]?js|jsx|[mc]?ts|tsx)$/i,
48
+ include: includeDirs,
49
+ loader: "builtin:swc-loader",
50
+ options,
51
+ },
52
+ ],
53
+ },
54
+ });
55
+ }
56
+
57
+ /**
58
+ * Build an alias map that disables ALL Node core modules in a web build.
59
+ * - Includes both 'fs' and 'node:fs' keys
60
+ * - Optional extras let you block non-core modules too
61
+ */
62
+ function makeWebNodeBuiltinsAlias(extras = []) {
63
+ // Strip potential 'node:' prefixes then add both forms
64
+ const core = new Set(builtinModules.map((m) => m.replace(/^node:/, "")));
65
+
66
+ const names = new Set();
67
+ for (const m of core) {
68
+ names.add(m); // e.g. 'fs'
69
+ names.add(`node:${m}`); // e.g. 'node:fs'
70
+ }
71
+ for (const x of extras) names.add(x);
72
+
73
+ // Map every name to false (causes hard error if imported)
74
+ return Object.fromEntries([...names].map((m) => [m, false]));
75
+ }
76
+
77
+ module.exports = {
78
+ compileWithMeteor,
79
+ compileWithRspack,
80
+ makeWebNodeBuiltinsAlias,
81
+ };
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@meteorjs/rspack",
3
- "version": "0.0.55",
3
+ "version": "0.0.57",
4
4
  "description": "Configuration logic for using Rspack in Meteor projects",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
7
7
  "author": "",
8
8
  "license": "ISC",
9
9
  "dependencies": {
10
+ "fast-deep-equal": "^3.1.3",
10
11
  "ignore-loader": "^0.1.2",
11
12
  "webpack-merge": "^6.0.1"
12
13
  },
package/rspack.config.js CHANGED
@@ -1,4 +1,4 @@
1
- const { DefinePlugin, BannerPlugin } = require('@rspack/core');
1
+ const { DefinePlugin, BannerPlugin, NormalModuleReplacementPlugin } = require('@rspack/core');
2
2
  const fs = require('fs');
3
3
  const { inspect } = require('node:util');
4
4
  const path = require('path');
@@ -10,6 +10,12 @@ const HtmlRspackPlugin = require('./plugins/HtmlRspackPlugin.js');
10
10
  const { RequireExternalsPlugin } = require('./plugins/RequireExtenalsPlugin.js');
11
11
  const { generateEagerTestFile } = require("./lib/test.js");
12
12
  const { getMeteorIgnoreEntries, createIgnoreGlobConfig } = require("./lib/ignore");
13
+ const { mergeMeteorRspackFragments } = require("./lib/meteorRspackConfigFactory.js");
14
+ const {
15
+ compileWithMeteor,
16
+ compileWithRspack,
17
+ makeWebNodeBuiltinsAlias,
18
+ } = require('./lib/meteorRspackHelpers.js');
13
19
 
14
20
  // Safe require that doesn't throw if the module isn't found
15
21
  function safeRequire(moduleName) {
@@ -27,17 +33,52 @@ function safeRequire(moduleName) {
27
33
  }
28
34
 
29
35
  // Persistent filesystem cache strategy
30
- function createCacheStrategy(mode, side) {
36
+ function createCacheStrategy(mode, side, { projectConfigPath, configPath } = {}) {
37
+ // Check for configuration files
38
+ const tsconfigPath = path.join(process.cwd(), 'tsconfig.json');
39
+ const hasTsconfig = fs.existsSync(tsconfigPath);
40
+ const babelRcConfig = path.join(process.cwd(), '.babelrc');
41
+ const hasBabelRcConfig = fs.existsSync(babelRcConfig);
42
+ const babelJsConfig = path.join(process.cwd(), 'babel.config.js');
43
+ const hasBabelJsConfig = fs.existsSync(babelJsConfig);
44
+ const swcrcPath = path.join(process.cwd(), '.swcrc');
45
+ const hasSwcrcConfig = fs.existsSync(swcrcPath);
46
+ const swcJsPath = path.join(process.cwd(), 'swc.config.js');
47
+ const hasSwcJsConfig = fs.existsSync(swcJsPath);
48
+ const postcssConfigPath = path.join(process.cwd(), 'postcss.config.js');
49
+ const hasPostcssConfig = fs.existsSync(postcssConfigPath);
50
+ const packageLockPath = path.join(process.cwd(), 'package-lock.json');
51
+ const hasPackageLock = fs.existsSync(packageLockPath);
52
+ const yarnLockPath = path.join(process.cwd(), 'yarn.lock');
53
+ const hasYarnLock = fs.existsSync(yarnLockPath);
54
+
55
+ // Build dependencies array
56
+ const buildDependencies = [
57
+ ...(projectConfigPath ? [projectConfigPath] : []),
58
+ ...(configPath ? [configPath] : []),
59
+ ...(hasTsconfig ? [tsconfigPath] : []),
60
+ ...(hasBabelRcConfig ? [babelRcConfig] : []),
61
+ ...(hasBabelJsConfig ? [babelJsConfig] : []),
62
+ ...(hasSwcrcConfig ? [swcrcPath] : []),
63
+ ...(hasSwcJsConfig ? [swcJsPath] : []),
64
+ ...(hasPostcssConfig ? [postcssConfigPath] : []),
65
+ ...(hasPackageLock ? [packageLockPath] : []),
66
+ ...(hasYarnLock ? [yarnLockPath] : []),
67
+ ].filter(Boolean);
68
+
31
69
  return {
32
70
  cache: true,
33
71
  experiments: {
34
72
  cache: {
35
- version: `swc-${mode}${(side && `-${side}`) || ""}`,
73
+ version: `cache-${mode}${(side && `-${side}`) || ""}`,
36
74
  type: "persistent",
37
75
  storage: {
38
76
  type: "filesystem",
39
77
  directory: `node_modules/.cache/rspack${(side && `/${side}`) || ""}`,
40
78
  },
79
+ ...(buildDependencies.length > 0 && {
80
+ buildDependencies: buildDependencies,
81
+ })
41
82
  },
42
83
  },
43
84
  };
@@ -46,6 +87,7 @@ function createCacheStrategy(mode, side) {
46
87
  // SWC loader rule (JSX/JS)
47
88
  function createSwcConfig({
48
89
  isTypescriptEnabled,
90
+ isReactEnabled,
49
91
  isJsxEnabled,
50
92
  isTsxEnabled,
51
93
  externalHelpers,
@@ -62,12 +104,14 @@ function createSwcConfig({
62
104
  ...(isJsxEnabled && { jsx: true }),
63
105
  },
64
106
  target: 'es2015',
65
- transform: {
66
- react: {
67
- development: isDevEnvironment,
68
- ...(isClient && { refresh: isDevEnvironment }),
107
+ ...(isReactEnabled && {
108
+ transform: {
109
+ react: {
110
+ development: isDevEnvironment,
111
+ ...(isClient && { refresh: isDevEnvironment }),
112
+ },
69
113
  },
70
- },
114
+ }),
71
115
  externalHelpers,
72
116
  },
73
117
  };
@@ -137,6 +181,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
137
181
  const mode = isProd ? 'production' : 'development';
138
182
  const projectDir = process.cwd();
139
183
  const projectConfigPath = Meteor.projectConfigPath || path.resolve(projectDir, 'rspack.config.js');
184
+ const configPath = Meteor.configPath;
140
185
 
141
186
  const isTypescriptEnabled = Meteor.isTypescriptEnabled || false;
142
187
  const isJsxEnabled =
@@ -173,6 +218,13 @@ module.exports = async function (inMeteor = {}, argv = {}) {
173
218
  const buildOutputDir = path.resolve(projectDir, buildContext, outputDir);
174
219
  Meteor.buildOutputDir = buildOutputDir;
175
220
 
221
+ // Expose Meteor's helpers to expand Rspack configs
222
+ Meteor.compileWithMeteor = deps => compileWithMeteor(deps);
223
+ Meteor.compileWithRspack = deps =>
224
+ compileWithRspack(deps, {
225
+ options: Meteor.swcConfigOptions,
226
+ });
227
+
176
228
  // Add HtmlRspackPlugin function to Meteor
177
229
  Meteor.HtmlRspackPlugin = (options = {}) => {
178
230
  return new HtmlRspackPlugin({
@@ -222,10 +274,11 @@ module.exports = async function (inMeteor = {}, argv = {}) {
222
274
  console.log('[i] Meteor flags:', Meteor);
223
275
  }
224
276
 
225
- const enableSwcExternalHelpers = !!swcExternalHelpers;
277
+ const enableSwcExternalHelpers = !isServer && swcExternalHelpers;
226
278
  const isDevEnvironment = isRun && isDev && !isTest && !isNative;
227
279
  const swcConfigRule = createSwcConfig({
228
280
  isTypescriptEnabled,
281
+ isReactEnabled,
229
282
  isJsxEnabled,
230
283
  isTsxEnabled,
231
284
  externalHelpers: enableSwcExternalHelpers,
@@ -243,6 +296,9 @@ module.exports = async function (inMeteor = {}, argv = {}) {
243
296
  const alias = {
244
297
  '/': path.resolve(process.cwd()),
245
298
  };
299
+ const fallback = {
300
+ ...(isClient && makeWebNodeBuiltinsAlias()),
301
+ };
246
302
  const extensions = [
247
303
  '.ts',
248
304
  '.tsx',
@@ -337,7 +393,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
337
393
  ...extraRules,
338
394
  ],
339
395
  },
340
- resolve: { extensions, alias },
396
+ resolve: { extensions, alias, fallback },
341
397
  externals,
342
398
  plugins: [
343
399
  ...[
@@ -357,6 +413,9 @@ module.exports = async function (inMeteor = {}, argv = {}) {
357
413
  ...bannerPluginConfig,
358
414
  Meteor.HtmlRspackPlugin(),
359
415
  ...doctorPluginConfig,
416
+ new NormalModuleReplacementPlugin(/^node:(.*)$/, (res) => {
417
+ res.request = res.request.replace(/^node:/, '');
418
+ }),
360
419
  ],
361
420
  watchOptions,
362
421
  devtool: isDevEnvironment || isNative || isTest ? 'source-map' : 'hidden-source-map',
@@ -373,7 +432,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
373
432
  },
374
433
  },
375
434
  }),
376
- ...merge(createCacheStrategy(mode, "client"), { experiments: { css: true } })
435
+ ...merge(createCacheStrategy(mode, "client", { projectConfigPath, configPath }), { experiments: { css: true } })
377
436
  };
378
437
 
379
438
 
@@ -427,6 +486,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
427
486
  conditionNames: ['import', 'require', 'node', 'default'],
428
487
  },
429
488
  externals,
489
+ externalsPresets: { node: true },
430
490
  plugins: [
431
491
  new DefinePlugin(
432
492
  isTest && (isTestModule || isTestEager)
@@ -451,7 +511,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
451
511
  watchOptions,
452
512
  devtool: isDevEnvironment || isNative || isTest ? 'source-map' : 'hidden-source-map',
453
513
  ...((isDevEnvironment || (isTest && !isTestEager) || isNative) &&
454
- createCacheStrategy(mode, "server")),
514
+ createCacheStrategy(mode, "server", { projectConfigPath, configPath })),
455
515
  };
456
516
 
457
517
  // Load and apply project-level overrides for the selected build
@@ -519,7 +579,8 @@ module.exports = async function (inMeteor = {}, argv = {}) {
519
579
  }
520
580
  }
521
581
 
522
- const config = isClient ? clientConfig : serverConfig;
582
+ const sideConfig = isClient ? clientConfig : serverConfig;
583
+ const config = mergeMeteorRspackFragments(sideConfig);
523
584
 
524
585
  if (Meteor.isDebug || Meteor.isVerbose) {
525
586
  console.log('Config:', inspect(config, { depth: null, colors: true }));