@meteorjs/rspack 2.1.0-beta.0 → 2.2.0-beta.0
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/localDependenciesHelpers.js +8 -8
- package/lib/swc.js +7 -1
- package/lib/test.js +2 -2
- package/lib/testClientNodePolyfills.js +66 -0
- package/package.json +9 -3
- package/plugins/RequireExtenalsPlugin.js +52 -14
- package/rspack.config.js +24 -13
|
@@ -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');
|
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
|
@@ -74,14 +74,14 @@ const generateEagerTestFile = ({
|
|
|
74
74
|
exclude: ${excludeFoldersRegex.toString()},
|
|
75
75
|
mode: 'eager',
|
|
76
76
|
});
|
|
77
|
-
ctx.keys().filter((k) => {
|
|
77
|
+
await Promise.all(ctx.keys().filter((k) => {
|
|
78
78
|
${
|
|
79
79
|
excludeMeteorIgnoreRegex
|
|
80
80
|
? `// Only exclude based on *relative* path segments.
|
|
81
81
|
return !MeteorIgnoreRegex.test(k);`
|
|
82
82
|
: "return true;"
|
|
83
83
|
}
|
|
84
|
-
}).
|
|
84
|
+
}).map(ctx));
|
|
85
85
|
${
|
|
86
86
|
extraEntry
|
|
87
87
|
? `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": "2.
|
|
3
|
+
"version": "2.2.0-beta.0",
|
|
4
4
|
"description": "Configuration logic for using Rspack in Meteor projects",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"fast-deep-equal": "^3.1.3",
|
|
15
15
|
"ignore-loader": "^0.1.2",
|
|
16
|
-
"
|
|
16
|
+
"isomorphic-timers-promises": "^1.0.1",
|
|
17
17
|
"webpack-merge": "^6.0.1"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
@@ -22,6 +22,12 @@
|
|
|
22
22
|
"peerDependencies": {
|
|
23
23
|
"@rspack/cli": ">=1.3.0",
|
|
24
24
|
"@rspack/core": ">=1.3.0",
|
|
25
|
-
"@swc/core": ">=1.3.0"
|
|
25
|
+
"@swc/core": ">=1.3.0",
|
|
26
|
+
"meteor-node-stubs": ">=1.2.0"
|
|
27
|
+
},
|
|
28
|
+
"peerDependenciesMeta": {
|
|
29
|
+
"meteor-node-stubs": {
|
|
30
|
+
"optional": true
|
|
31
|
+
}
|
|
26
32
|
}
|
|
27
33
|
}
|
|
@@ -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({
|
|
@@ -160,8 +172,24 @@ class RequireExternalsPlugin {
|
|
|
160
172
|
// 2) Re-load existing requires from disk on every run
|
|
161
173
|
const existing = this._readExistingRequires();
|
|
162
174
|
|
|
163
|
-
// 2a) Compute the *current* externals in this build
|
|
164
|
-
|
|
175
|
+
// 2a) Compute the *current* externals in this build.
|
|
176
|
+
// Only module names are needed, so start from `all: false` instead of
|
|
177
|
+
// serializing the whole stats object (chunks, assets, reasons, ...)
|
|
178
|
+
// on every compilation — a fixed cost per rebuild that grows with app
|
|
179
|
+
// size. `cachedModules: true` keeps unchanged modules in the list on
|
|
180
|
+
// incremental rebuilds, so their requires are not wrongly removed.
|
|
181
|
+
// `orphanModules: true` is required in production: module
|
|
182
|
+
// concatenation absorbs the externals' importers, marking the
|
|
183
|
+
// external modules themselves as orphans, and without this flag
|
|
184
|
+
// they vanish from the list entirely — which stripped every
|
|
185
|
+
// mirrored require (meteor/*, Blaze .html) from the meteor entry.
|
|
186
|
+
// See meteor/meteor#14568.
|
|
187
|
+
const info = stats.toJson({
|
|
188
|
+
all: false,
|
|
189
|
+
modules: true,
|
|
190
|
+
cachedModules: true,
|
|
191
|
+
orphanModules: true,
|
|
192
|
+
});
|
|
165
193
|
const current = new Set();
|
|
166
194
|
for (const m of info.modules) {
|
|
167
195
|
const matchInfo = this._isExternalModule(m.name);
|
|
@@ -173,15 +201,22 @@ class RequireExternalsPlugin {
|
|
|
173
201
|
}
|
|
174
202
|
}
|
|
175
203
|
|
|
176
|
-
// 2b) Remove any
|
|
204
|
+
// 2b) Remove any dependencies that are no longer in `current`
|
|
177
205
|
const toRemove = [...existing].filter(p => !current.has(p));
|
|
178
206
|
if (toRemove.length) {
|
|
179
207
|
let content = fs.readFileSync(this.filePath, 'utf-8');
|
|
180
208
|
|
|
181
|
-
// Strip stale
|
|
209
|
+
// Strip stale requires and imports generated by this plugin. Other
|
|
210
|
+
// standalone imports link Meteor entry and Rspack output files and
|
|
211
|
+
// must remain intact.
|
|
182
212
|
for (const pkg of toRemove) {
|
|
183
|
-
const
|
|
184
|
-
|
|
213
|
+
const requireRe = buildStandaloneRequireRegex(pkg);
|
|
214
|
+
const importRe = buildStandaloneImportRegex(pkg);
|
|
215
|
+
content = content.replace(requireRe, '');
|
|
216
|
+
content = content.replace(
|
|
217
|
+
MANAGED_IMPORT_BLOCK_REGEX,
|
|
218
|
+
(block) => block.replace(importRe, '')
|
|
219
|
+
);
|
|
185
220
|
}
|
|
186
221
|
|
|
187
222
|
// Strip out any now-empty helper functions:
|
|
@@ -206,11 +241,11 @@ class RequireExternalsPlugin {
|
|
|
206
241
|
// Re-populate `existing` so the add-diff is accurate
|
|
207
242
|
existing.clear();
|
|
208
243
|
// Check for require statements
|
|
209
|
-
for (const match of content.matchAll(
|
|
244
|
+
for (const match of content.matchAll(STANDALONE_REQUIRE_REGEX)) {
|
|
210
245
|
existing.add(match[1]);
|
|
211
246
|
}
|
|
212
247
|
// Also check for import statements (used in the new format)
|
|
213
|
-
for (const match of content.matchAll(
|
|
248
|
+
for (const match of content.matchAll(STANDALONE_IMPORT_REGEX)) {
|
|
214
249
|
existing.add(match[1]);
|
|
215
250
|
}
|
|
216
251
|
}
|
|
@@ -478,19 +513,22 @@ class RequireExternalsPlugin {
|
|
|
478
513
|
|
|
479
514
|
_readExistingRequires() {
|
|
480
515
|
const existing = new Set();
|
|
516
|
+
// Generated Rspack bridge imports are not managed externals. Relative
|
|
517
|
+
// imports such as Blaze HTML files still belong to this plugin.
|
|
518
|
+
const isRspackBridgeImport = (modulePath) =>
|
|
519
|
+
typeof modulePath === 'string' &&
|
|
520
|
+
/(?:^|[/\\])[^/\\]*-rspack\.(?:js|cjs)$/.test(modulePath);
|
|
481
521
|
try {
|
|
482
522
|
const content = fs.readFileSync(this.filePath, 'utf-8');
|
|
483
523
|
// Check for require statements
|
|
484
|
-
const requireRegex = /require\('([^']+)'\)/g;
|
|
485
524
|
let match;
|
|
486
|
-
while ((match =
|
|
487
|
-
existing.add(match[1]);
|
|
525
|
+
while ((match = STANDALONE_REQUIRE_REGEX.exec(content)) !== null) {
|
|
526
|
+
if (!isRspackBridgeImport(match[1])) existing.add(match[1]);
|
|
488
527
|
}
|
|
489
528
|
|
|
490
529
|
// Also check for import statements (used in the new format)
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
existing.add(match[1]);
|
|
530
|
+
while ((match = STANDALONE_IMPORT_REGEX.exec(content)) !== null) {
|
|
531
|
+
if (!isRspackBridgeImport(match[1])) existing.add(match[1]);
|
|
494
532
|
}
|
|
495
533
|
} catch {
|
|
496
534
|
// ignore if file missing or unreadable
|
package/rspack.config.js
CHANGED
|
@@ -3,7 +3,6 @@ const fs = require('fs');
|
|
|
3
3
|
const { inspect } = require('node:util');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { merge } = require('webpack-merge');
|
|
6
|
-
const NodePolyfillPlugin = require('node-polyfill-webpack-plugin');
|
|
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) {
|
|
@@ -102,8 +101,13 @@ function createCacheStrategy(
|
|
|
102
101
|
type: "persistent",
|
|
103
102
|
storage: {
|
|
104
103
|
type: "filesystem",
|
|
104
|
+
// Include the mode in the directory (not just the version):
|
|
105
|
+
// rspack invalidates a persistent cache on version mismatch, so
|
|
106
|
+
// sharing one directory between development and production would
|
|
107
|
+
// wipe the cache on every `meteor run` <-> `meteor build` switch
|
|
108
|
+
// instead of keeping both warm. See meteor/meteor#14568.
|
|
105
109
|
directory: `node_modules/.cache/rspack/${
|
|
106
|
-
[buildContext, side].filter(Boolean).join('-') || 'default'
|
|
110
|
+
[buildContext, side, mode].filter(Boolean).join('-') || 'default'
|
|
107
111
|
}`,
|
|
108
112
|
},
|
|
109
113
|
...(buildDependencies.length > 0 && {
|
|
@@ -127,15 +131,13 @@ function createSwcConfig({
|
|
|
127
131
|
}) {
|
|
128
132
|
const defaultConfig = {
|
|
129
133
|
jsc: {
|
|
130
|
-
baseUrl: process.cwd(),
|
|
131
|
-
paths: { '/*': ['*', '/*'] },
|
|
132
134
|
parser: {
|
|
133
135
|
syntax: isTypescriptEnabled ? 'typescript' : 'ecmascript',
|
|
134
136
|
...(isTsxEnabled && { tsx: true }),
|
|
135
137
|
...(isJsxEnabled && { jsx: true }),
|
|
136
138
|
...(isAngularEnabled && { decorators: true }),
|
|
137
139
|
},
|
|
138
|
-
target: 'es2015',
|
|
140
|
+
target: isClient ? 'es2015' : 'es2022',
|
|
139
141
|
...(isReactEnabled && {
|
|
140
142
|
transform: {
|
|
141
143
|
react: {
|
|
@@ -404,7 +406,11 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
404
406
|
cacheStrategy = createCacheStrategy(
|
|
405
407
|
mode,
|
|
406
408
|
(Meteor.isClient && "client") || "server",
|
|
407
|
-
|
|
409
|
+
// buildContext must be passed here too: this reassignment is the
|
|
410
|
+
// effective cache strategy, and omitting it made the cache directory
|
|
411
|
+
// collide across build contexts (e.g. custom METEOR_LOCAL_DIR setups).
|
|
412
|
+
// See meteor/meteor#14568.
|
|
413
|
+
{ projectConfigPath, configPath, buildContext }
|
|
408
414
|
);
|
|
409
415
|
|
|
410
416
|
// Determine run point
|
|
@@ -426,7 +432,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
426
432
|
// Additional ignore entries
|
|
427
433
|
const additionalEntries = [
|
|
428
434
|
"**/.meteor/local/**",
|
|
429
|
-
"
|
|
435
|
+
path.join(projectDir, "dist", "**").replace(/\\/g, "/"),
|
|
430
436
|
...(isTest && isTestEager
|
|
431
437
|
? [`**/${buildContext}/**`, "**/.meteor/local/**", "node_modules/**"]
|
|
432
438
|
: []),
|
|
@@ -491,7 +497,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
491
497
|
...(Meteor.isBlazeEnabled && {
|
|
492
498
|
externals: /\.html$/,
|
|
493
499
|
isEagerImport: (module) => module.endsWith(".html"),
|
|
494
|
-
...(isProd && {
|
|
500
|
+
...((isProd || (isTest && isClient)) && {
|
|
495
501
|
lastImports: [`./${outputFilename}`],
|
|
496
502
|
}),
|
|
497
503
|
}),
|
|
@@ -650,7 +656,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
650
656
|
...extraRules,
|
|
651
657
|
],
|
|
652
658
|
},
|
|
653
|
-
resolve: { extensions, alias, fallback },
|
|
659
|
+
resolve: { extensions, alias, fallback, roots: [path.resolve(projectDir)] },
|
|
654
660
|
externals,
|
|
655
661
|
plugins: [
|
|
656
662
|
...[
|
|
@@ -774,6 +780,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
774
780
|
alias,
|
|
775
781
|
modules: ["node_modules", path.resolve(projectDir)],
|
|
776
782
|
conditionNames: ["import", "require", "node", "default"],
|
|
783
|
+
roots: [path.resolve(projectDir)],
|
|
777
784
|
},
|
|
778
785
|
externals,
|
|
779
786
|
externalsPresets: { node: true },
|
|
@@ -808,8 +815,12 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
808
815
|
isDevEnvironment || isNative || isTest
|
|
809
816
|
? "source-map"
|
|
810
817
|
: "hidden-source-map",
|
|
811
|
-
|
|
812
|
-
|
|
818
|
+
// Apply the persistent cache to production builds too (the client
|
|
819
|
+
// config always has it); previously `meteor build` recompiled the
|
|
820
|
+
// entire server bundle cold every time. Eager server test builds are
|
|
821
|
+
// still excluded, since their generated entry changes on every run.
|
|
822
|
+
// See meteor/meteor#14568.
|
|
823
|
+
...(!(isTest && isTestEager) && cacheStrategy),
|
|
813
824
|
...lazyCompilationConfig,
|
|
814
825
|
...loggingConfig,
|
|
815
826
|
};
|
|
@@ -841,7 +852,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
841
852
|
optimization: {
|
|
842
853
|
splitChunks: false,
|
|
843
854
|
},
|
|
844
|
-
|
|
855
|
+
...createTestClientNodePolyfillConfig(),
|
|
845
856
|
}
|
|
846
857
|
: {};
|
|
847
858
|
|