@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.
|
|
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: `
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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 =
|
|
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
|
|
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 }));
|