@meteorjs/rspack 0.0.56 → 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.56",
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
  };
@@ -140,6 +181,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
140
181
  const mode = isProd ? 'production' : 'development';
141
182
  const projectDir = process.cwd();
142
183
  const projectConfigPath = Meteor.projectConfigPath || path.resolve(projectDir, 'rspack.config.js');
184
+ const configPath = Meteor.configPath;
143
185
 
144
186
  const isTypescriptEnabled = Meteor.isTypescriptEnabled || false;
145
187
  const isJsxEnabled =
@@ -176,6 +218,13 @@ module.exports = async function (inMeteor = {}, argv = {}) {
176
218
  const buildOutputDir = path.resolve(projectDir, buildContext, outputDir);
177
219
  Meteor.buildOutputDir = buildOutputDir;
178
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
+
179
228
  // Add HtmlRspackPlugin function to Meteor
180
229
  Meteor.HtmlRspackPlugin = (options = {}) => {
181
230
  return new HtmlRspackPlugin({
@@ -225,7 +274,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
225
274
  console.log('[i] Meteor flags:', Meteor);
226
275
  }
227
276
 
228
- const enableSwcExternalHelpers = !!swcExternalHelpers;
277
+ const enableSwcExternalHelpers = !isServer && swcExternalHelpers;
229
278
  const isDevEnvironment = isRun && isDev && !isTest && !isNative;
230
279
  const swcConfigRule = createSwcConfig({
231
280
  isTypescriptEnabled,
@@ -247,6 +296,9 @@ module.exports = async function (inMeteor = {}, argv = {}) {
247
296
  const alias = {
248
297
  '/': path.resolve(process.cwd()),
249
298
  };
299
+ const fallback = {
300
+ ...(isClient && makeWebNodeBuiltinsAlias()),
301
+ };
250
302
  const extensions = [
251
303
  '.ts',
252
304
  '.tsx',
@@ -341,7 +393,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
341
393
  ...extraRules,
342
394
  ],
343
395
  },
344
- resolve: { extensions, alias },
396
+ resolve: { extensions, alias, fallback },
345
397
  externals,
346
398
  plugins: [
347
399
  ...[
@@ -361,6 +413,9 @@ module.exports = async function (inMeteor = {}, argv = {}) {
361
413
  ...bannerPluginConfig,
362
414
  Meteor.HtmlRspackPlugin(),
363
415
  ...doctorPluginConfig,
416
+ new NormalModuleReplacementPlugin(/^node:(.*)$/, (res) => {
417
+ res.request = res.request.replace(/^node:/, '');
418
+ }),
364
419
  ],
365
420
  watchOptions,
366
421
  devtool: isDevEnvironment || isNative || isTest ? 'source-map' : 'hidden-source-map',
@@ -377,7 +432,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
377
432
  },
378
433
  },
379
434
  }),
380
- ...merge(createCacheStrategy(mode, "client"), { experiments: { css: true } })
435
+ ...merge(createCacheStrategy(mode, "client", { projectConfigPath, configPath }), { experiments: { css: true } })
381
436
  };
382
437
 
383
438
 
@@ -431,6 +486,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
431
486
  conditionNames: ['import', 'require', 'node', 'default'],
432
487
  },
433
488
  externals,
489
+ externalsPresets: { node: true },
434
490
  plugins: [
435
491
  new DefinePlugin(
436
492
  isTest && (isTestModule || isTestEager)
@@ -455,7 +511,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
455
511
  watchOptions,
456
512
  devtool: isDevEnvironment || isNative || isTest ? 'source-map' : 'hidden-source-map',
457
513
  ...((isDevEnvironment || (isTest && !isTestEager) || isNative) &&
458
- createCacheStrategy(mode, "server")),
514
+ createCacheStrategy(mode, "server", { projectConfigPath, configPath })),
459
515
  };
460
516
 
461
517
  // Load and apply project-level overrides for the selected build
@@ -523,7 +579,8 @@ module.exports = async function (inMeteor = {}, argv = {}) {
523
579
  }
524
580
  }
525
581
 
526
- const config = isClient ? clientConfig : serverConfig;
582
+ const sideConfig = isClient ? clientConfig : serverConfig;
583
+ const config = mergeMeteorRspackFragments(sideConfig);
527
584
 
528
585
  if (Meteor.isDebug || Meteor.isVerbose) {
529
586
  console.log('Config:', inspect(config, { depth: null, colors: true }));