@meteorjs/rspack 3.0.0-beta.0 → 3.0.0-beta.2
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/lib/ignore.js +13 -5
- package/lib/localDependenciesHelpers.js +8 -8
- package/lib/mergeRulesSplitOverlap.js +1 -1
- package/lib/meteorRspackHelpers.js +2 -7
- package/lib/swc.js +7 -1
- package/lib/test.js +4 -3
- package/lib/testClientNodePolyfills.js +66 -0
- package/package.json +13 -7
- package/plugins/RequireExtenalsPlugin.js +59 -14
- package/rspack.config.js +103 -52
- package/.claude/settings.local.json +0 -10
package/lib/ignore.js
CHANGED
|
@@ -85,15 +85,25 @@ function createIgnoreGlobConfig(entries = []) {
|
|
|
85
85
|
/**
|
|
86
86
|
* Creates a regex pattern to match the specified glob patterns.
|
|
87
87
|
* Converts glob patterns with * and ** into regex equivalents.
|
|
88
|
-
*
|
|
88
|
+
*
|
|
89
89
|
* @param {string[]} globPatterns - Array of glob patterns from createIgnoreGlobConfig
|
|
90
|
+
* @param {string} [rootPath] - Absolute root that matched paths must belong to
|
|
90
91
|
* @returns {RegExp} - Regex pattern to match the specified patterns
|
|
91
92
|
*/
|
|
92
|
-
function createIgnoreRegex(globPatterns) {
|
|
93
|
+
function createIgnoreRegex(globPatterns, rootPath) {
|
|
93
94
|
if (!Array.isArray(globPatterns) || globPatterns.length === 0) {
|
|
94
95
|
throw new Error('globPatterns must be a non-empty array');
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
// Rspack applies context exclusions to absolute module paths. Anchor rooted
|
|
99
|
+
// patterns here so matching starts inside the app, not in a parent folder.
|
|
100
|
+
const pathPrefix = rootPath
|
|
101
|
+
? `^${rootPath
|
|
102
|
+
.replace(/\\/g, '/')
|
|
103
|
+
.replace(/\/+$/, '')
|
|
104
|
+
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?=/|$)`
|
|
105
|
+
: '(?:^|/)';
|
|
106
|
+
|
|
97
107
|
// Process each glob pattern and convert to regex
|
|
98
108
|
const regexPatterns = globPatterns.map(pattern => {
|
|
99
109
|
// Skip negation patterns for the regex
|
|
@@ -115,9 +125,7 @@ function createIgnoreRegex(globPatterns) {
|
|
|
115
125
|
// Convert the ** placeholder to its regex equivalent (any number of characters including /)
|
|
116
126
|
regexPattern = regexPattern.replace(new RegExp(DOUBLE_ASTERISK_PLACEHOLDER, 'g'), '.*');
|
|
117
127
|
|
|
118
|
-
|
|
119
|
-
// but we still want to ensure it matches to the end of the path segment
|
|
120
|
-
regexPattern = '(?:^|/)' + regexPattern;
|
|
128
|
+
regexPattern = pathPrefix + regexPattern;
|
|
121
129
|
|
|
122
130
|
return regexPattern;
|
|
123
131
|
}).filter(pattern => pattern !== null);
|
|
@@ -20,7 +20,7 @@ function extractLocalDependencies(configFilePath) {
|
|
|
20
20
|
|
|
21
21
|
// Parse the file into an AST
|
|
22
22
|
const ast = swc.parseSync(content, {
|
|
23
|
-
syntax: 'ecmascript',
|
|
23
|
+
syntax: /\.[mc]?tsx?$/.test(configFilePath) ? 'typescript' : 'ecmascript',
|
|
24
24
|
dynamicImport: true,
|
|
25
25
|
target: 'es2020',
|
|
26
26
|
});
|
|
@@ -30,10 +30,10 @@ function extractLocalDependencies(configFilePath) {
|
|
|
30
30
|
let modulePath = null;
|
|
31
31
|
|
|
32
32
|
// Handle require() calls: require('./plugin')
|
|
33
|
-
if (node.type === 'CallExpression' &&
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
if (node.type === 'CallExpression' &&
|
|
34
|
+
node.callee.type === 'Identifier' &&
|
|
35
|
+
node.callee.value === 'require' &&
|
|
36
|
+
node.arguments.length > 0) {
|
|
37
37
|
const arg = node.arguments[0];
|
|
38
38
|
if (arg.expression?.type === 'StringLiteral') {
|
|
39
39
|
modulePath = arg.expression.value;
|
|
@@ -42,8 +42,8 @@ function extractLocalDependencies(configFilePath) {
|
|
|
42
42
|
|
|
43
43
|
// Handle dynamic import() calls: import('./plugin')
|
|
44
44
|
if (node.type === 'CallExpression' &&
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
node.callee.type === 'Import' &&
|
|
46
|
+
node.arguments.length > 0) {
|
|
47
47
|
const arg = node.arguments[0];
|
|
48
48
|
if (arg.expression?.type === 'StringLiteral') {
|
|
49
49
|
modulePath = arg.expression.value;
|
|
@@ -163,7 +163,7 @@ function resolveLocalModule(modulePath, configDir, projectDir) {
|
|
|
163
163
|
const resolvedReal = fs.realpathSync(resolvedPath);
|
|
164
164
|
const projectReal = fs.realpathSync(projectDir);
|
|
165
165
|
|
|
166
|
-
const isWithinProject =
|
|
166
|
+
const isWithinProject =
|
|
167
167
|
resolvedReal === projectReal ||
|
|
168
168
|
resolvedReal.startsWith(projectReal + path.sep);
|
|
169
169
|
const hasNodeModulesSegment = resolvedReal.split(path.sep).includes('node_modules');
|
|
@@ -56,17 +56,12 @@ function compileWithRspack(deps, { options = {} } = {}) {
|
|
|
56
56
|
*/
|
|
57
57
|
function setCache(
|
|
58
58
|
enabled,
|
|
59
|
-
cacheConfig = { cache: true
|
|
59
|
+
cacheConfig = { cache: true }
|
|
60
60
|
) {
|
|
61
61
|
return prepareMeteorRspackConfig(
|
|
62
62
|
enabled
|
|
63
63
|
? cacheConfig
|
|
64
|
-
: {
|
|
65
|
-
cache: false, // disable cache
|
|
66
|
-
experiments: {
|
|
67
|
-
cache: false, // disable persistent cache (experimental flag)
|
|
68
|
-
},
|
|
69
|
-
}
|
|
64
|
+
: { cache: false }
|
|
70
65
|
);
|
|
71
66
|
}
|
|
72
67
|
|
package/lib/swc.js
CHANGED
|
@@ -14,7 +14,13 @@ function getMeteorAppSwcrc(file = '.swcrc') {
|
|
|
14
14
|
|
|
15
15
|
if (file.endsWith('.ts')) {
|
|
16
16
|
try {
|
|
17
|
-
|
|
17
|
+
let swc;
|
|
18
|
+
try {
|
|
19
|
+
swc = require(require.resolve('@swc/core', { paths: [process.cwd()] }));
|
|
20
|
+
} catch (e) {
|
|
21
|
+
if (e.code !== 'MODULE_NOT_FOUND') throw e;
|
|
22
|
+
swc = require('@swc/core');
|
|
23
|
+
}
|
|
18
24
|
const result = swc.transformSync(content, {
|
|
19
25
|
jsc: {
|
|
20
26
|
parser: {
|
package/lib/test.js
CHANGED
|
@@ -44,7 +44,8 @@ const generateEagerTestFile = ({
|
|
|
44
44
|
|
|
45
45
|
// Create regex from ignore entries
|
|
46
46
|
const excludeFoldersRegex = createIgnoreRegex(
|
|
47
|
-
createIgnoreGlobConfig(ignoreEntries)
|
|
47
|
+
createIgnoreGlobConfig(ignoreEntries),
|
|
48
|
+
projectDir,
|
|
48
49
|
);
|
|
49
50
|
// Create regex from meteor ignore entries
|
|
50
51
|
const excludeMeteorIgnoreRegex = inMeteorIgnoreEntries.length > 0
|
|
@@ -74,14 +75,14 @@ const generateEagerTestFile = ({
|
|
|
74
75
|
exclude: ${excludeFoldersRegex.toString()},
|
|
75
76
|
mode: 'eager',
|
|
76
77
|
});
|
|
77
|
-
ctx.keys().filter((k) => {
|
|
78
|
+
await Promise.all(ctx.keys().filter((k) => {
|
|
78
79
|
${
|
|
79
80
|
excludeMeteorIgnoreRegex
|
|
80
81
|
? `// Only exclude based on *relative* path segments.
|
|
81
82
|
return !MeteorIgnoreRegex.test(k);`
|
|
82
83
|
: "return true;"
|
|
83
84
|
}
|
|
84
|
-
}).
|
|
85
|
+
}).map(ctx));
|
|
85
86
|
${
|
|
86
87
|
extraEntry
|
|
87
88
|
? `const extra = import.meta.webpackContext('${toPosix(path.dirname(
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const { ProvidePlugin } = require("@rspack/core");
|
|
2
|
+
|
|
3
|
+
let meteorNodeStubs;
|
|
4
|
+
try {
|
|
5
|
+
const stubPath = require.resolve("meteor-node-stubs", { paths: [process.cwd()] });
|
|
6
|
+
meteorNodeStubs = require(stubPath);
|
|
7
|
+
} catch (e) {
|
|
8
|
+
if (e.code !== 'MODULE_NOT_FOUND') throw e;
|
|
9
|
+
try {
|
|
10
|
+
meteorNodeStubs = require("meteor-node-stubs");
|
|
11
|
+
} catch (fallbackError) {
|
|
12
|
+
if (fallbackError.code !== 'MODULE_NOT_FOUND') throw fallbackError;
|
|
13
|
+
// It's optional. If not installed, meteorNodeStubs remains undefined.
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const TEST_CLIENT_NODE_POLYFILLS = [
|
|
18
|
+
"assert",
|
|
19
|
+
"buffer",
|
|
20
|
+
"constants",
|
|
21
|
+
"crypto",
|
|
22
|
+
"events",
|
|
23
|
+
"fs",
|
|
24
|
+
"http",
|
|
25
|
+
"https",
|
|
26
|
+
"os",
|
|
27
|
+
"path",
|
|
28
|
+
"querystring",
|
|
29
|
+
"stream",
|
|
30
|
+
"string_decoder",
|
|
31
|
+
"sys",
|
|
32
|
+
"timers",
|
|
33
|
+
"tty",
|
|
34
|
+
"url",
|
|
35
|
+
"util",
|
|
36
|
+
"vm",
|
|
37
|
+
"zlib",
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
function createTestClientNodePolyfillConfig() {
|
|
41
|
+
const fallback = Object.fromEntries(
|
|
42
|
+
TEST_CLIENT_NODE_POLYFILLS.map((name) => [name, meteorNodeStubs?.[name] ?? false]),
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const plugins = [];
|
|
46
|
+
|
|
47
|
+
if (meteorNodeStubs?.buffer) {
|
|
48
|
+
plugins.push(
|
|
49
|
+
new ProvidePlugin({
|
|
50
|
+
Buffer: [meteorNodeStubs.buffer, "Buffer"],
|
|
51
|
+
})
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
resolve: {
|
|
57
|
+
alias: {
|
|
58
|
+
"timers/promises$": require.resolve("isomorphic-timers-promises"),
|
|
59
|
+
},
|
|
60
|
+
fallback,
|
|
61
|
+
},
|
|
62
|
+
plugins,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = { createTestClientNodePolyfillConfig };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meteorjs/rspack",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.2",
|
|
4
4
|
"description": "Configuration logic for using Rspack in Meteor projects",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -13,16 +13,22 @@
|
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"fast-deep-equal": "^3.1.3",
|
|
15
15
|
"ignore-loader": "^0.1.2",
|
|
16
|
-
"
|
|
17
|
-
"
|
|
16
|
+
"isomorphic-timers-promises": "^1.0.1",
|
|
17
|
+
"rspack-merge": "^1.0.1"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"semver": "^7.7.4"
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|
|
23
|
-
"@rspack/cli": ">=2.
|
|
24
|
-
"@rspack/core": ">=2.
|
|
25
|
-
"@rspack/dev-server": ">=2.
|
|
26
|
-
"@swc/core": ">=1.3.0"
|
|
23
|
+
"@rspack/cli": ">=2.2.0",
|
|
24
|
+
"@rspack/core": ">=2.2.0",
|
|
25
|
+
"@rspack/dev-server": ">=2.2.0",
|
|
26
|
+
"@swc/core": ">=1.3.0",
|
|
27
|
+
"meteor-node-stubs": ">=1.2.0"
|
|
28
|
+
},
|
|
29
|
+
"peerDependenciesMeta": {
|
|
30
|
+
"meteor-node-stubs": {
|
|
31
|
+
"optional": true
|
|
32
|
+
}
|
|
27
33
|
}
|
|
28
34
|
}
|
|
@@ -13,6 +13,18 @@ const path = require('path');
|
|
|
13
13
|
// Normalize a path to always use forward slashes (POSIX style).
|
|
14
14
|
// Module identifiers in bundled JS must use '/' regardless of OS.
|
|
15
15
|
const toPosix = (p) => p.replace(/\\/g, '/');
|
|
16
|
+
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
17
|
+
const buildStandaloneRequireRegex = (pkg) =>
|
|
18
|
+
new RegExp(`^\\s*require\\('${escapeRegExp(pkg)}'\\);?.*(\\r?\\n)?`, 'gm');
|
|
19
|
+
const buildStandaloneImportRegex = (pkg) =>
|
|
20
|
+
new RegExp(
|
|
21
|
+
`^[\\t ]*import[\\t ]+'${escapeRegExp(pkg)}';?[^\\r\\n]*(?:\\r?\\n|$)`,
|
|
22
|
+
'gm'
|
|
23
|
+
);
|
|
24
|
+
const MANAGED_IMPORT_BLOCK_REGEX =
|
|
25
|
+
/^\/\/\s*\(function\s+(?:eagerExternalImports\d+|lastImports(?:\d+)?)\s*\(\)\s*\{\s*\r?\n[\s\S]*?^\/\/\s*\}\)\s*(?:\r?\n|$)/gm;
|
|
26
|
+
const STANDALONE_REQUIRE_REGEX = /^\s*require\('([^']+)'\)/gm;
|
|
27
|
+
const STANDALONE_IMPORT_REGEX = /^\s*import\s+'([^']+)'/gm;
|
|
16
28
|
|
|
17
29
|
class RequireExternalsPlugin {
|
|
18
30
|
constructor({
|
|
@@ -152,6 +164,13 @@ class RequireExternalsPlugin {
|
|
|
152
164
|
}
|
|
153
165
|
|
|
154
166
|
compiler.hooks.done.tap({ name: this.pluginName, stage: -10 }, (stats) => {
|
|
167
|
+
// Preserve the last known-good Meteor runtime entry when compilation
|
|
168
|
+
// fails. Rewriting externals from incomplete stats would make Meteor
|
|
169
|
+
// restart against a broken Rspack bundle.
|
|
170
|
+
if (stats.hasErrors()) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
155
174
|
// 1) Ensure globalThis.module / exports block is present if enabled
|
|
156
175
|
if (this._enableGlobalPolyfill) {
|
|
157
176
|
this._ensureGlobalThisModule();
|
|
@@ -160,8 +179,24 @@ class RequireExternalsPlugin {
|
|
|
160
179
|
// 2) Re-load existing requires from disk on every run
|
|
161
180
|
const existing = this._readExistingRequires();
|
|
162
181
|
|
|
163
|
-
// 2a) Compute the *current* externals in this build
|
|
164
|
-
|
|
182
|
+
// 2a) Compute the *current* externals in this build.
|
|
183
|
+
// Only module names are needed, so start from `all: false` instead of
|
|
184
|
+
// serializing the whole stats object (chunks, assets, reasons, ...)
|
|
185
|
+
// on every compilation — a fixed cost per rebuild that grows with app
|
|
186
|
+
// size. `cachedModules: true` keeps unchanged modules in the list on
|
|
187
|
+
// incremental rebuilds, so their requires are not wrongly removed.
|
|
188
|
+
// `orphanModules: true` is required in production: module
|
|
189
|
+
// concatenation absorbs the externals' importers, marking the
|
|
190
|
+
// external modules themselves as orphans, and without this flag
|
|
191
|
+
// they vanish from the list entirely — which stripped every
|
|
192
|
+
// mirrored require (meteor/*, Blaze .html) from the meteor entry.
|
|
193
|
+
// See meteor/meteor#14568.
|
|
194
|
+
const info = stats.toJson({
|
|
195
|
+
all: false,
|
|
196
|
+
modules: true,
|
|
197
|
+
cachedModules: true,
|
|
198
|
+
orphanModules: true,
|
|
199
|
+
});
|
|
165
200
|
const current = new Set();
|
|
166
201
|
for (const m of info.modules) {
|
|
167
202
|
const matchInfo = this._isExternalModule(m.name);
|
|
@@ -173,15 +208,22 @@ class RequireExternalsPlugin {
|
|
|
173
208
|
}
|
|
174
209
|
}
|
|
175
210
|
|
|
176
|
-
// 2b) Remove any
|
|
211
|
+
// 2b) Remove any dependencies that are no longer in `current`
|
|
177
212
|
const toRemove = [...existing].filter(p => !current.has(p));
|
|
178
213
|
if (toRemove.length) {
|
|
179
214
|
let content = fs.readFileSync(this.filePath, 'utf-8');
|
|
180
215
|
|
|
181
|
-
// Strip stale
|
|
216
|
+
// Strip stale requires and imports generated by this plugin. Other
|
|
217
|
+
// standalone imports link Meteor entry and Rspack output files and
|
|
218
|
+
// must remain intact.
|
|
182
219
|
for (const pkg of toRemove) {
|
|
183
|
-
const
|
|
184
|
-
|
|
220
|
+
const requireRe = buildStandaloneRequireRegex(pkg);
|
|
221
|
+
const importRe = buildStandaloneImportRegex(pkg);
|
|
222
|
+
content = content.replace(requireRe, '');
|
|
223
|
+
content = content.replace(
|
|
224
|
+
MANAGED_IMPORT_BLOCK_REGEX,
|
|
225
|
+
(block) => block.replace(importRe, '')
|
|
226
|
+
);
|
|
185
227
|
}
|
|
186
228
|
|
|
187
229
|
// Strip out any now-empty helper functions:
|
|
@@ -206,11 +248,11 @@ class RequireExternalsPlugin {
|
|
|
206
248
|
// Re-populate `existing` so the add-diff is accurate
|
|
207
249
|
existing.clear();
|
|
208
250
|
// Check for require statements
|
|
209
|
-
for (const match of content.matchAll(
|
|
251
|
+
for (const match of content.matchAll(STANDALONE_REQUIRE_REGEX)) {
|
|
210
252
|
existing.add(match[1]);
|
|
211
253
|
}
|
|
212
254
|
// Also check for import statements (used in the new format)
|
|
213
|
-
for (const match of content.matchAll(
|
|
255
|
+
for (const match of content.matchAll(STANDALONE_IMPORT_REGEX)) {
|
|
214
256
|
existing.add(match[1]);
|
|
215
257
|
}
|
|
216
258
|
}
|
|
@@ -478,19 +520,22 @@ class RequireExternalsPlugin {
|
|
|
478
520
|
|
|
479
521
|
_readExistingRequires() {
|
|
480
522
|
const existing = new Set();
|
|
523
|
+
// Generated Rspack bridge imports are not managed externals. Relative
|
|
524
|
+
// imports such as Blaze HTML files still belong to this plugin.
|
|
525
|
+
const isRspackBridgeImport = (modulePath) =>
|
|
526
|
+
typeof modulePath === 'string' &&
|
|
527
|
+
/(?:^|[/\\])[^/\\]*-rspack\.(?:js|cjs)$/.test(modulePath);
|
|
481
528
|
try {
|
|
482
529
|
const content = fs.readFileSync(this.filePath, 'utf-8');
|
|
483
530
|
// Check for require statements
|
|
484
|
-
const requireRegex = /require\('([^']+)'\)/g;
|
|
485
531
|
let match;
|
|
486
|
-
while ((match =
|
|
487
|
-
existing.add(match[1]);
|
|
532
|
+
while ((match = STANDALONE_REQUIRE_REGEX.exec(content)) !== null) {
|
|
533
|
+
if (!isRspackBridgeImport(match[1])) existing.add(match[1]);
|
|
488
534
|
}
|
|
489
535
|
|
|
490
536
|
// Also check for import statements (used in the new format)
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
existing.add(match[1]);
|
|
537
|
+
while ((match = STANDALONE_IMPORT_REGEX.exec(content)) !== null) {
|
|
538
|
+
if (!isRspackBridgeImport(match[1])) existing.add(match[1]);
|
|
494
539
|
}
|
|
495
540
|
} catch {
|
|
496
541
|
// ignore if file missing or unreadable
|
package/rspack.config.js
CHANGED
|
@@ -2,8 +2,7 @@ const { DefinePlugin, BannerPlugin, NormalModuleReplacementPlugin } = require('@
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const { inspect } = require('node:util');
|
|
4
4
|
const path = require('path');
|
|
5
|
-
const { merge } = require('
|
|
6
|
-
const NodePolyfillPlugin = require('node-polyfill-webpack-plugin');
|
|
5
|
+
const { merge } = require('rspack-merge');
|
|
7
6
|
|
|
8
7
|
const { cleanOmittedPaths, mergeSplitOverlap } = require("./lib/mergeRulesSplitOverlap.js");
|
|
9
8
|
const { getMeteorAppSwcConfig } = require('./lib/swc.js');
|
|
@@ -30,7 +29,7 @@ const {
|
|
|
30
29
|
const { loadUserAndOverrideConfig } = require('./lib/meteorRspackConfigHelpers.js');
|
|
31
30
|
const { prepareMeteorRspackConfig } = require("./lib/meteorRspackConfigFactory");
|
|
32
31
|
const { extractLocalDependencies } = require('./lib/localDependenciesHelpers.js');
|
|
33
|
-
|
|
32
|
+
const { createTestClientNodePolyfillConfig } = require("./lib/testClientNodePolyfills.js");
|
|
34
33
|
|
|
35
34
|
// Safe require that doesn't throw if the module isn't found
|
|
36
35
|
function safeRequire(moduleName) {
|
|
@@ -95,21 +94,22 @@ function createCacheStrategy(
|
|
|
95
94
|
].filter(Boolean);
|
|
96
95
|
|
|
97
96
|
return {
|
|
98
|
-
cache:
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
type: "
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
buildDependencies: buildDependencies,
|
|
111
|
-
})
|
|
97
|
+
cache: {
|
|
98
|
+
version: `cache-${mode}${(side && `-${side}`) || ""}`,
|
|
99
|
+
type: "persistent",
|
|
100
|
+
storage: {
|
|
101
|
+
type: "filesystem",
|
|
102
|
+
// Rspack invalidates a persistent cache on version mismatch. Keep
|
|
103
|
+
// development and production in separate directories so switching
|
|
104
|
+
// between `meteor run` and `meteor build` leaves both caches warm.
|
|
105
|
+
// See meteor/meteor#14568.
|
|
106
|
+
directory: `node_modules/.cache/rspack/${
|
|
107
|
+
[buildContext, side, mode].filter(Boolean).join('-') || 'default'
|
|
108
|
+
}`,
|
|
112
109
|
},
|
|
110
|
+
...(buildDependencies.length > 0 && {
|
|
111
|
+
buildDependencies: buildDependencies,
|
|
112
|
+
})
|
|
113
113
|
},
|
|
114
114
|
};
|
|
115
115
|
}
|
|
@@ -127,15 +127,13 @@ function createSwcConfig({
|
|
|
127
127
|
}) {
|
|
128
128
|
const defaultConfig = {
|
|
129
129
|
jsc: {
|
|
130
|
-
baseUrl: process.cwd(),
|
|
131
|
-
paths: { '/*': ['*', '/*'] },
|
|
132
130
|
parser: {
|
|
133
131
|
syntax: isTypescriptEnabled ? 'typescript' : 'ecmascript',
|
|
134
132
|
...(isTsxEnabled && { tsx: true }),
|
|
135
133
|
...(isJsxEnabled && { jsx: true }),
|
|
136
134
|
...(isAngularEnabled && { decorators: true }),
|
|
137
135
|
},
|
|
138
|
-
target: 'es2015',
|
|
136
|
+
target: isClient ? 'es2015' : 'es2022',
|
|
139
137
|
...(isReactEnabled && {
|
|
140
138
|
transform: {
|
|
141
139
|
react: {
|
|
@@ -404,7 +402,11 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
404
402
|
cacheStrategy = createCacheStrategy(
|
|
405
403
|
mode,
|
|
406
404
|
(Meteor.isClient && "client") || "server",
|
|
407
|
-
|
|
405
|
+
// buildContext must be passed here too: this reassignment is the
|
|
406
|
+
// effective cache strategy, and omitting it made the cache directory
|
|
407
|
+
// collide across build contexts (e.g. custom METEOR_LOCAL_DIR setups).
|
|
408
|
+
// See meteor/meteor#14568.
|
|
409
|
+
{ projectConfigPath, configPath, buildContext }
|
|
408
410
|
);
|
|
409
411
|
|
|
410
412
|
// Determine run point
|
|
@@ -426,7 +428,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
426
428
|
// Additional ignore entries
|
|
427
429
|
const additionalEntries = [
|
|
428
430
|
"**/.meteor/local/**",
|
|
429
|
-
"
|
|
431
|
+
path.join(projectDir, "dist", "**").replace(/\\/g, "/"),
|
|
430
432
|
...(isTest && isTestEager
|
|
431
433
|
? [`**/${buildContext}/**`, "**/.meteor/local/**", "node_modules/**"]
|
|
432
434
|
: []),
|
|
@@ -439,7 +441,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
439
441
|
],
|
|
440
442
|
};
|
|
441
443
|
|
|
442
|
-
if (Meteor.
|
|
444
|
+
if (Meteor.isVerbose) {
|
|
443
445
|
console.log("[i] Rspack mode:", mode);
|
|
444
446
|
console.log("[i] Meteor flags:", Meteor);
|
|
445
447
|
}
|
|
@@ -462,9 +464,6 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
462
464
|
...(isReactEnabled ? [/^react$/, /^react-dom$/] : []),
|
|
463
465
|
...(isServer ? [/^bcrypt$/] : []),
|
|
464
466
|
];
|
|
465
|
-
const alias = {
|
|
466
|
-
"/": path.resolve(process.cwd()),
|
|
467
|
-
};
|
|
468
467
|
const fallback = {
|
|
469
468
|
...(isClient && makeWebNodeBuiltinsAlias()),
|
|
470
469
|
};
|
|
@@ -485,13 +484,15 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
485
484
|
const reactRefreshModule = isReactEnabled
|
|
486
485
|
? safeRequire("@rspack/plugin-react-refresh")
|
|
487
486
|
: null;
|
|
487
|
+
const ReactRefreshRspackPlugin =
|
|
488
|
+
reactRefreshModule?.ReactRefreshRspackPlugin || reactRefreshModule;
|
|
488
489
|
|
|
489
490
|
const requireExternalsPlugin = new RequireExternalsPlugin({
|
|
490
491
|
filePath: path.join(buildContext, runPath),
|
|
491
492
|
...(Meteor.isBlazeEnabled && {
|
|
492
493
|
externals: /\.html$/,
|
|
493
494
|
isEagerImport: (module) => module.endsWith(".html"),
|
|
494
|
-
...(isProd && {
|
|
495
|
+
...((isProd || (isTest && isClient)) && {
|
|
495
496
|
lastImports: [`./${outputFilename}`],
|
|
496
497
|
}),
|
|
497
498
|
}),
|
|
@@ -620,7 +621,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
620
621
|
if (isMainChunk) return `../${buildContext}/${outputPath}`;
|
|
621
622
|
return chunkSuffix;
|
|
622
623
|
},
|
|
623
|
-
|
|
624
|
+
library: { type: "commonjs2" },
|
|
624
625
|
publicPath: "/",
|
|
625
626
|
chunkFilename: `${chunksContext}/[id]${isProd ? ".[chunkhash]" : ""}.js`,
|
|
626
627
|
assetModuleFilename,
|
|
@@ -650,14 +651,24 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
650
651
|
{ test: /\.css$/, type: "css/auto" },
|
|
651
652
|
...extraRules,
|
|
652
653
|
],
|
|
654
|
+
parser: {
|
|
655
|
+
javascript: {
|
|
656
|
+
// Relax Rspack 2.0 strict ESM linking; SWC-stripped TS type re-exports otherwise fail the build.
|
|
657
|
+
exportsPresence: "warn",
|
|
658
|
+
},
|
|
659
|
+
},
|
|
660
|
+
},
|
|
661
|
+
resolve: {
|
|
662
|
+
extensions,
|
|
663
|
+
fallback,
|
|
664
|
+
roots: [path.resolve(projectDir)],
|
|
653
665
|
},
|
|
654
|
-
resolve: { extensions, alias, fallback },
|
|
655
666
|
externals,
|
|
656
667
|
externalsType: "commonjs2",
|
|
657
668
|
plugins: [
|
|
658
669
|
...[
|
|
659
|
-
...(isReactEnabled &&
|
|
660
|
-
? [new
|
|
670
|
+
...(isReactEnabled && ReactRefreshRspackPlugin && isDevEnvironment
|
|
671
|
+
? [new ReactRefreshRspackPlugin()]
|
|
661
672
|
: []),
|
|
662
673
|
requireExternalsPlugin,
|
|
663
674
|
assetExternalsPlugin,
|
|
@@ -740,7 +751,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
740
751
|
output: {
|
|
741
752
|
path: serverOutputDir,
|
|
742
753
|
filename: () => `../${buildContext}/${outputPath}`,
|
|
743
|
-
|
|
754
|
+
library: { type: "commonjs2" },
|
|
744
755
|
chunkFilename: `${chunksContext}/[id]${isProd ? ".[chunkhash]" : ""}.js`,
|
|
745
756
|
assetModuleFilename,
|
|
746
757
|
...(isProd && { clean: { keep: keepOutsideBuild() } }),
|
|
@@ -771,14 +782,16 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
771
782
|
javascript: {
|
|
772
783
|
// Dynamic imports on the server are treated as bundled in the same chunk
|
|
773
784
|
dynamicImportMode: "eager",
|
|
785
|
+
// Relax Rspack 2.0 strict ESM linking; SWC-stripped TS type re-exports otherwise fail the build.
|
|
786
|
+
exportsPresence: "warn",
|
|
774
787
|
},
|
|
775
788
|
},
|
|
776
789
|
},
|
|
777
790
|
resolve: {
|
|
778
791
|
extensions,
|
|
779
|
-
alias,
|
|
780
792
|
modules: ["node_modules", path.resolve(projectDir)],
|
|
781
793
|
conditionNames: ["import", "require", "node", "default"],
|
|
794
|
+
roots: [path.resolve(projectDir)],
|
|
782
795
|
},
|
|
783
796
|
externals,
|
|
784
797
|
externalsType: "commonjs2",
|
|
@@ -814,8 +827,12 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
814
827
|
isDevEnvironment || isNative || isTest
|
|
815
828
|
? "source-map"
|
|
816
829
|
: "hidden-source-map",
|
|
817
|
-
|
|
818
|
-
|
|
830
|
+
// Apply the persistent cache to production builds too (the client
|
|
831
|
+
// config always has it); previously `meteor build` recompiled the
|
|
832
|
+
// entire server bundle cold every time. Eager server test builds are
|
|
833
|
+
// still excluded, since their generated entry changes on every run.
|
|
834
|
+
// See meteor/meteor#14568.
|
|
835
|
+
...(!(isTest && isTestEager) && cacheStrategy),
|
|
819
836
|
...lazyCompilationConfig,
|
|
820
837
|
...loggingConfig,
|
|
821
838
|
};
|
|
@@ -827,7 +844,18 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
827
844
|
devServer: { port: devServerPort },
|
|
828
845
|
stats: { preset: "normal" },
|
|
829
846
|
infrastructureLogging: { level: "info" },
|
|
830
|
-
...(isProd &&
|
|
847
|
+
...(isProd &&
|
|
848
|
+
isClient && {
|
|
849
|
+
output: {
|
|
850
|
+
// Nx Angular config emits ESM chunks in production by default.
|
|
851
|
+
// Meteor serves and minifies classic browser bundles here.
|
|
852
|
+
module: false,
|
|
853
|
+
scriptType: false,
|
|
854
|
+
chunkFormat: "array-push",
|
|
855
|
+
chunkLoading: "jsonp",
|
|
856
|
+
workerChunkLoading: "import-scripts",
|
|
857
|
+
},
|
|
858
|
+
}),
|
|
831
859
|
}
|
|
832
860
|
: {};
|
|
833
861
|
|
|
@@ -847,7 +875,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
847
875
|
optimization: {
|
|
848
876
|
splitChunks: false,
|
|
849
877
|
},
|
|
850
|
-
|
|
878
|
+
...createTestClientNodePolyfillConfig(),
|
|
851
879
|
}
|
|
852
880
|
: {};
|
|
853
881
|
|
|
@@ -904,8 +932,8 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
904
932
|
|
|
905
933
|
delete config["meteor.enablePortableBuild"];
|
|
906
934
|
|
|
907
|
-
if (Meteor.
|
|
908
|
-
|
|
935
|
+
if (Meteor.isVerbose) {
|
|
936
|
+
console.log("Config:", inspect(config, { depth: null, colors: true }));
|
|
909
937
|
}
|
|
910
938
|
|
|
911
939
|
// Check if lazyCompilation is enabled and warn the user
|
|
@@ -922,19 +950,42 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
922
950
|
|
|
923
951
|
// Add MeteorRspackOutputPlugin as the last plugin to output compilation info
|
|
924
952
|
const meteorRspackOutputPlugin = new MeteorRspackOutputPlugin({
|
|
925
|
-
getData: (stats, { isRebuild, compilationCount, compiler }) =>
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
953
|
+
getData: (stats, { isRebuild, compilationCount, compiler }) => {
|
|
954
|
+
const generatedAppPaths = [
|
|
955
|
+
path.join('.meteor', 'local'),
|
|
956
|
+
path.join('.meteor', 'types'),
|
|
957
|
+
buildContext,
|
|
958
|
+
];
|
|
959
|
+
const changedFiles = [
|
|
960
|
+
...(compiler?.modifiedFiles || []),
|
|
961
|
+
...(compiler?.removedFiles || []),
|
|
962
|
+
];
|
|
963
|
+
const hasAppSourceChanges =
|
|
964
|
+
changedFiles.length === 0 ||
|
|
965
|
+
changedFiles.some(file => {
|
|
966
|
+
const relativePath = path.relative(projectDir, file);
|
|
967
|
+
return !generatedAppPaths.some(generatedPath =>
|
|
968
|
+
relativePath === generatedPath ||
|
|
969
|
+
relativePath.startsWith(`${generatedPath}${path.sep}`)
|
|
970
|
+
);
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
return {
|
|
974
|
+
name: config.name,
|
|
975
|
+
mode: config.mode,
|
|
976
|
+
hasErrors: stats.hasErrors(),
|
|
977
|
+
hasWarnings: stats.hasWarnings(),
|
|
978
|
+
hasAppSourceChanges,
|
|
979
|
+
hash: stats.hash,
|
|
980
|
+
timestamp: Date.now(),
|
|
981
|
+
statsOverrided,
|
|
982
|
+
compilationCount,
|
|
983
|
+
isRebuild,
|
|
984
|
+
...(!isRebuild && compiler && {
|
|
985
|
+
delegatedExtensions: extractDelegatedExtensions(stats, compiler),
|
|
986
|
+
}),
|
|
987
|
+
};
|
|
988
|
+
},
|
|
938
989
|
});
|
|
939
990
|
config.plugins = [meteorRspackOutputPlugin, ...(config.plugins || [])];
|
|
940
991
|
|