@meteorjs/rspack 0.0.13 → 0.0.14

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.
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Utilities for merging webpack/rspack configurations with special handling for
3
+ * overlapping file extensions in module rules.
4
+ */
5
+
6
+ import { mergeWithCustomize } from 'webpack-merge';
7
+
8
+ /**
9
+ * File extensions to check when determining rule overlaps.
10
+ */
11
+ export const EXT_CATALOG = [
12
+ '.tsx', '.ts', '.mts', '.cts',
13
+ '.jsx', '.js', '.mjs', '.cjs',
14
+ ];
15
+
16
+ /**
17
+ * Converts rule.test to predicate functions.
18
+ * @param {Object} rule - Rule object
19
+ * @returns {Function[]} Predicate functions
20
+ */
21
+ function testsFrom(rule) {
22
+ const t = rule.test;
23
+ if (!t) return [() => true]; // no test means match all; you can tighten if you want
24
+ const arr = Array.isArray(t) ? t : [t];
25
+ return arr.map(el => {
26
+ if (el instanceof RegExp) return (s) => el.test(s);
27
+ if (typeof el === 'function') return el;
28
+ if (typeof el === 'string') {
29
+ // Webpack allows string match; treat as substring
30
+ return (s) => s.includes(el);
31
+ }
32
+ return () => false;
33
+ });
34
+ }
35
+
36
+ /**
37
+ * Checks if rule matches a file extension.
38
+ * @param {Object} rule - Rule object
39
+ * @param {string} ext - File extension
40
+ * @returns {boolean} True if matches
41
+ */
42
+ function ruleMatchesExt(rule, ext) {
43
+ // simulate a filename to test against
44
+ const filename = `x${ext}`;
45
+ const preds = testsFrom(rule);
46
+ return preds.some(fn => {
47
+ try { return !!fn(filename); } catch { return false; }
48
+ });
49
+ }
50
+
51
+ /**
52
+ * Creates regex for matching file extensions.
53
+ * @param {string[]} exts - File extensions
54
+ * @returns {RegExp} Regex like /\.(js|jsx)$/
55
+ */
56
+ function regexFromExts(exts) {
57
+ const body = exts.map(e => e.replace(/^\./, '')).join('|');
58
+ return new RegExp(`\\.(${body})$`);
59
+ }
60
+
61
+ /**
62
+ * Clones rule with new test property.
63
+ * @param {Object} rule - Rule to clone
64
+ * @param {RegExp|Function|string} newTest - New test value
65
+ * @returns {Object} Cloned rule
66
+ */
67
+ function cloneWithTest(rule, newTest) {
68
+ return { ...rule, test: newTest };
69
+ }
70
+
71
+ /**
72
+ * Merges rules with special handling for overlapping extensions.
73
+ * - Replaces overlapping parts with B rules
74
+ * - Preserves non-overlapping parts from A rules
75
+ *
76
+ * @param {Array} aRules - Base rules
77
+ * @param {Array} bRules - Rules to merge in
78
+ * @returns {Array} Merged rules
79
+ */
80
+ function splitOverlapRulesMerge(aRules, bRules) {
81
+ const result = [...aRules];
82
+
83
+ for (const bRule of bRules) {
84
+ // Try to find an A rule that overlaps B by extensions
85
+ let replaced = false;
86
+
87
+ for (let i = 0; i < result.length; i++) {
88
+ const aRule = result[i];
89
+
90
+ // Determine which extensions each rule matches (within our catalog)
91
+ const aExts = EXT_CATALOG.filter(ext => ruleMatchesExt(aRule, ext));
92
+ const bExts = EXT_CATALOG.filter(ext => ruleMatchesExt(bRule, ext));
93
+
94
+ if (aExts.length === 0 || bExts.length === 0) {
95
+ continue; // nothing meaningful to compare in our catalog
96
+ }
97
+
98
+ const overlap = aExts.filter(e => bExts.includes(e));
99
+ if (overlap.length === 0) continue;
100
+
101
+ // 1) Replace the overlapping A rule with B
102
+ result[i] = bRule;
103
+
104
+ // 2) Add a "residual" A rule for the non-overlapping extensions
105
+ const residual = aExts.filter(e => !overlap.includes(e));
106
+ if (residual.length > 0) {
107
+ const residualRule = cloneWithTest(aRule, regexFromExts(residual));
108
+ result.splice(i, 0, residualRule); // keep residual before B, or after—your choice
109
+ i++; // skip over the newly inserted residual
110
+ }
111
+
112
+ replaced = true;
113
+ break;
114
+ }
115
+
116
+ // If we didn’t overlap with any A rule, just add B
117
+ if (!replaced) {
118
+ result.push(bRule);
119
+ }
120
+ }
121
+
122
+ return result;
123
+ }
124
+
125
+ /**
126
+ * Merges webpack/rspack configs with smart handling of overlapping rules.
127
+ *
128
+ * @param {...Object} configs - Configs to merge
129
+ * @returns {Object} Merged config
130
+ */
131
+ export function mergeSplitOverlap(...configs) {
132
+ return mergeWithCustomize({
133
+ customizeArray(a, b, key) {
134
+ if (key === 'module.rules') {
135
+ const aRules = Array.isArray(a) ? a : [];
136
+ const bRules = Array.isArray(b) ? b : [];
137
+ return splitOverlapRulesMerge(aRules, bRules);
138
+ }
139
+ // fall through to default merging
140
+ return undefined;
141
+ }
142
+ })(...configs);
143
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meteorjs/rspack",
3
- "version": "0.0.13",
3
+ "version": "0.0.14",
4
4
  "description": "Configuration logic for using Rspack in Meteor projects",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/rspack.config.js CHANGED
@@ -7,6 +7,7 @@ import { inspect } from "node:util";
7
7
 
8
8
  import { RequireExternalsPlugin } from './plugins/RequireExtenalsPlugin.js';
9
9
  import { getMeteorAppSwcConfig } from "./lib/swc.js";
10
+ import { mergeSplitOverlap } from './lib/mergeRulesSplitOverlap.js';
10
11
 
11
12
  const require = createRequire(import.meta.url);
12
13
 
@@ -66,7 +67,7 @@ function createSwcConfig({ isTypescriptEnabled, isJsxEnabled, isTsxEnabled, exte
66
67
  const customConfig = getMeteorAppSwcConfig() || {};
67
68
  const swcConfig = merge(defaultConfig, customConfig);
68
69
  return {
69
- test: /\.[jt]sx?$/,
70
+ test: /\.(?:[mc]?js|jsx|[mc]?ts|tsx)$/i,
70
71
  exclude: /node_modules|\.meteor\/local/,
71
72
  loader: 'builtin:swc-loader',
72
73
  options: swcConfig,
@@ -175,6 +176,8 @@ export default function (inMeteor = {}, argv = {}) {
175
176
  ...(isCoffeescriptEnabled ? ['.coffee'] : []),
176
177
  '.ts',
177
178
  '.tsx',
179
+ '.mts',
180
+ '.cts',
178
181
  '.js',
179
182
  '.jsx',
180
183
  '.mjs',
@@ -340,10 +343,10 @@ export default function (inMeteor = {}, argv = {}) {
340
343
  : projectConfig;
341
344
 
342
345
  if (Meteor.isClient) {
343
- clientConfig = merge(clientConfig, userConfig);
346
+ clientConfig = mergeSplitOverlap(clientConfig, userConfig);
344
347
  }
345
348
  if (Meteor.isServer) {
346
- serverConfig = merge(serverConfig, userConfig);
349
+ serverConfig = mergeSplitOverlap(serverConfig, userConfig);
347
350
  }
348
351
  }
349
352