@meteorjs/rspack 3.0.0-beta.1 → 3.0.0-beta.3
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/index.d.ts +4 -0
- package/lib/ignore.js +92 -14
- package/lib/ignore.test.js +259 -0
- package/lib/test.js +15 -9
- package/lib/test.test.js +49 -0
- package/package.json +2 -1
- package/plugins/RequireExtenalsPlugin.js +7 -0
- package/rspack.config.js +71 -36
package/index.d.ts
CHANGED
|
@@ -18,6 +18,10 @@ type MeteorEnv = Record<string, any> & {
|
|
|
18
18
|
isProduction: boolean;
|
|
19
19
|
isClient: boolean;
|
|
20
20
|
isServer: boolean;
|
|
21
|
+
/** Explicit client architecture, e.g. web.browser.legacy or web.cordova. */
|
|
22
|
+
arch?: string;
|
|
23
|
+
/** Whether this client compilation uses legacy (ES5) targets. */
|
|
24
|
+
isLegacy: boolean;
|
|
21
25
|
isTest: boolean;
|
|
22
26
|
isDebug: boolean;
|
|
23
27
|
isRun: boolean;
|
package/lib/ignore.js
CHANGED
|
@@ -2,8 +2,26 @@ var fs = require('fs');
|
|
|
2
2
|
var path = require('path');
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Splits a whitespace-delimited list of ignore patterns, as accepted by the
|
|
6
|
+
* METEOR_IGNORE environment variable.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} value - Raw environment variable value
|
|
9
|
+
* @returns {string[]} - Array of ignore patterns
|
|
10
|
+
*/
|
|
11
|
+
function parseIgnoreEnv(value) {
|
|
12
|
+
return (value || '').trim().split(/\s+/).filter(Boolean);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Reads the ignore patterns that apply to the given project directory: the
|
|
17
|
+
* entries of its .meteorignore file followed by the ones in the METEOR_IGNORE
|
|
18
|
+
* environment variable. Empty lines and comment lines (starting with #) are
|
|
19
|
+
* filtered out.
|
|
20
|
+
*
|
|
21
|
+
* The two sources are combined the way meteor-tool combines them (see
|
|
22
|
+
* optimisticReadMeteorIgnore in tools/fs/optimistic.ts), with METEOR_IGNORE
|
|
23
|
+
* last so that it can override the file under gitignore's last-match-wins
|
|
24
|
+
* rule.
|
|
7
25
|
*
|
|
8
26
|
* @param {string} projectDir - The project directory path
|
|
9
27
|
* @returns {string[]} - Array of ignore patterns
|
|
@@ -11,20 +29,22 @@ var path = require('path');
|
|
|
11
29
|
const getMeteorIgnoreEntries = function (projectDir) {
|
|
12
30
|
const meteorIgnorePath = path.join(projectDir, '.meteorignore');
|
|
13
31
|
|
|
32
|
+
let fileEntries = [];
|
|
33
|
+
|
|
14
34
|
// Check if .meteorignore file exists
|
|
15
35
|
try {
|
|
16
36
|
const fileContent = fs.readFileSync(meteorIgnorePath, 'utf8');
|
|
17
37
|
|
|
18
38
|
// Process each line in the file
|
|
19
|
-
|
|
39
|
+
fileEntries = fileContent.split(/\r?\n/)
|
|
20
40
|
.map(line => line.trim())
|
|
21
41
|
.filter(line => line !== '' && !line.startsWith('#'));
|
|
22
|
-
|
|
23
|
-
return entries;
|
|
24
42
|
} catch (e) {
|
|
25
|
-
// If the file doesn't exist or can't be read,
|
|
26
|
-
|
|
43
|
+
// If the file doesn't exist or can't be read, fall back to the
|
|
44
|
+
// environment variable alone.
|
|
27
45
|
}
|
|
46
|
+
|
|
47
|
+
return [...fileEntries, ...parseIgnoreEnv(process.env.METEOR_IGNORE)];
|
|
28
48
|
};
|
|
29
49
|
|
|
30
50
|
/**
|
|
@@ -86,6 +106,11 @@ function createIgnoreGlobConfig(entries = []) {
|
|
|
86
106
|
* Creates a regex pattern to match the specified glob patterns.
|
|
87
107
|
* Converts glob patterns with * and ** into regex equivalents.
|
|
88
108
|
*
|
|
109
|
+
* Negation patterns cannot be expressed in a single regex and are dropped, so
|
|
110
|
+
* this only suits pattern lists known to be positive-only — Rspack's `exclude`
|
|
111
|
+
* option, which accepts nothing but a RegExp. Patterns that come from the app
|
|
112
|
+
* author go through createIgnoreMatcherSource instead.
|
|
113
|
+
*
|
|
89
114
|
* @param {string[]} globPatterns - Array of glob patterns from createIgnoreGlobConfig
|
|
90
115
|
* @param {string} [rootPath] - Absolute root that matched paths must belong to
|
|
91
116
|
* @returns {RegExp} - Regex pattern to match the specified patterns
|
|
@@ -95,14 +120,21 @@ function createIgnoreRegex(globPatterns, rootPath) {
|
|
|
95
120
|
throw new Error('globPatterns must be a non-empty array');
|
|
96
121
|
}
|
|
97
122
|
|
|
98
|
-
// Rspack applies context exclusions to absolute module paths
|
|
99
|
-
//
|
|
123
|
+
// Rspack applies context exclusions to absolute module paths joined with the
|
|
124
|
+
// platform separator, so on Windows they arrive with backslashes. Every
|
|
125
|
+
// separator in the regex accepts both forms (meteor/meteor#14513).
|
|
126
|
+
const SEPARATOR = '[\\\\/]';
|
|
127
|
+
const NOT_SEPARATOR = '[^\\\\/]';
|
|
128
|
+
|
|
129
|
+
// Anchor rooted patterns so matching starts inside the app, not in a parent
|
|
130
|
+
// folder.
|
|
100
131
|
const pathPrefix = rootPath
|
|
101
132
|
? `^${rootPath
|
|
102
133
|
.replace(/\\/g, '/')
|
|
103
134
|
.replace(/\/+$/, '')
|
|
104
|
-
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
105
|
-
|
|
135
|
+
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
136
|
+
.replace(/\//g, SEPARATOR)}(?=${SEPARATOR}|$)`
|
|
137
|
+
: `(?:^|${SEPARATOR})`;
|
|
106
138
|
|
|
107
139
|
// Process each glob pattern and convert to regex
|
|
108
140
|
const regexPatterns = globPatterns.map(pattern => {
|
|
@@ -114,13 +146,16 @@ function createIgnoreRegex(globPatterns, rootPath) {
|
|
|
114
146
|
// Escape special regex characters, but not * and /
|
|
115
147
|
let regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
|
|
116
148
|
|
|
149
|
+
// Accept either separator wherever the pattern has a /
|
|
150
|
+
regexPattern = regexPattern.replace(/\//g, SEPARATOR);
|
|
151
|
+
|
|
117
152
|
// Use a temporary placeholder for ** that won't be affected by the * replacement
|
|
118
153
|
// This is necessary because if we directly replace ** with .* and then replace * with [^/]*
|
|
119
154
|
const DOUBLE_ASTERISK_PLACEHOLDER = '__DOUBLE_ASTERISK__';
|
|
120
155
|
regexPattern = regexPattern.replace(/\*\*/g, DOUBLE_ASTERISK_PLACEHOLDER);
|
|
121
156
|
|
|
122
|
-
// Convert * to regex equivalent (any number of characters except
|
|
123
|
-
regexPattern = regexPattern.replace(/\*/g,
|
|
157
|
+
// Convert * to regex equivalent (any number of characters except a separator)
|
|
158
|
+
regexPattern = regexPattern.replace(/\*/g, `${NOT_SEPARATOR}*`);
|
|
124
159
|
|
|
125
160
|
// Convert the ** placeholder to its regex equivalent (any number of characters including /)
|
|
126
161
|
regexPattern = regexPattern.replace(new RegExp(DOUBLE_ASTERISK_PLACEHOLDER, 'g'), '.*');
|
|
@@ -140,8 +175,51 @@ function createIgnoreRegex(globPatterns, rootPath) {
|
|
|
140
175
|
return new RegExp(combinedPattern);
|
|
141
176
|
}
|
|
142
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Builds the pieces a generated eager-test module needs to decide which test
|
|
180
|
+
* files .meteorignore keeps.
|
|
181
|
+
*
|
|
182
|
+
* .meteorignore uses gitignore syntax, where the last matching pattern wins and
|
|
183
|
+
* a `!pattern` re-includes a path an earlier pattern excluded. Reimplementing
|
|
184
|
+
* that on top of a RegExp lost every negation, which silently reduced a sharded
|
|
185
|
+
* test run to zero test files, so the generated module is handed the same
|
|
186
|
+
* `ignore` package meteor-tool applies to .meteorignore (see
|
|
187
|
+
* tools/fs/optimistic.ts). Both bundlers then agree on the semantics.
|
|
188
|
+
*
|
|
189
|
+
* Returned as source rather than as a predicate because it runs inside the
|
|
190
|
+
* bundle, not in this process: the paths it tests come from
|
|
191
|
+
* import.meta.webpackContext.
|
|
192
|
+
*
|
|
193
|
+
* @param {string[]} entries - .meteorignore-style patterns, in order
|
|
194
|
+
* @returns {{ modulePath: string, source: string }|null} - The `ignore` module
|
|
195
|
+
* to import, and a `(createIgnore) => (contextKey) => boolean` factory
|
|
196
|
+
* expression. Null when there is nothing to filter.
|
|
197
|
+
*/
|
|
198
|
+
function createIgnoreMatcherSource(entries = []) {
|
|
199
|
+
if (!Array.isArray(entries)) {
|
|
200
|
+
throw new Error('Entries must be an array');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (entries.length === 0) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
// Resolved here rather than at module load so that requiring this file
|
|
209
|
+
// stays cheap and does not depend on `ignore` being installed.
|
|
210
|
+
modulePath: require.resolve('ignore'),
|
|
211
|
+
source: `(createIgnore => {
|
|
212
|
+
const ig = createIgnore().add(${JSON.stringify(entries)});
|
|
213
|
+
// import.meta.webpackContext keys look like './imports/a.tests.ts', but
|
|
214
|
+
// \`ignore\` wants a path relative to the project root and rejects the './'.
|
|
215
|
+
return key => ig.ignores(key.replace(/^\\.\\//, ''));
|
|
216
|
+
})`,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
143
220
|
module.exports = {
|
|
221
|
+
createIgnoreGlobConfig,
|
|
222
|
+
createIgnoreMatcherSource,
|
|
144
223
|
createIgnoreRegex,
|
|
145
224
|
getMeteorIgnoreEntries,
|
|
146
|
-
createIgnoreGlobConfig,
|
|
147
225
|
};
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
createIgnoreGlobConfig,
|
|
7
|
+
createIgnoreMatcherSource,
|
|
8
|
+
createIgnoreRegex,
|
|
9
|
+
getMeteorIgnoreEntries,
|
|
10
|
+
} = require('./ignore.js');
|
|
11
|
+
|
|
12
|
+
// Compiles the factory emitted into the generated eager-test module and wires
|
|
13
|
+
// it to the same `ignore` module that module imports, so the assertions below
|
|
14
|
+
// exercise the exact code that runs inside the bundle.
|
|
15
|
+
const compileMatcher = entries => {
|
|
16
|
+
const { modulePath, source } = createIgnoreMatcherSource(entries);
|
|
17
|
+
|
|
18
|
+
// eslint-disable-next-line no-new-func -- the input is this package's own
|
|
19
|
+
// generated source, not user data.
|
|
20
|
+
return new Function('createIgnore', `return (${source})(createIgnore);`)(
|
|
21
|
+
require(modulePath),
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const withMeteorIgnoreEnv = (value, fn) => {
|
|
26
|
+
const previous = process.env.METEOR_IGNORE;
|
|
27
|
+
if (value === undefined) {
|
|
28
|
+
delete process.env.METEOR_IGNORE;
|
|
29
|
+
} else {
|
|
30
|
+
process.env.METEOR_IGNORE = value;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
return fn();
|
|
34
|
+
} finally {
|
|
35
|
+
if (previous === undefined) {
|
|
36
|
+
delete process.env.METEOR_IGNORE;
|
|
37
|
+
} else {
|
|
38
|
+
process.env.METEOR_IGNORE = previous;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
describe('createIgnoreMatcherSource', () => {
|
|
44
|
+
test('re-includes a file a later negation pattern rescues', () => {
|
|
45
|
+
const isIgnored = compileMatcher([
|
|
46
|
+
'*.tests.ts',
|
|
47
|
+
'!/imports/localization/LanguageSections.tests.ts',
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
expect(isIgnored('./imports/localization/LanguageSections.tests.ts')).toBe(
|
|
51
|
+
false,
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('still ignores files no negation pattern rescues', () => {
|
|
56
|
+
const isIgnored = compileMatcher([
|
|
57
|
+
'*.tests.ts',
|
|
58
|
+
'!/imports/localization/LanguageSections.tests.ts',
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
expect(isIgnored('./imports/localization/Other.tests.ts')).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('lets the last matching pattern win when a positive follows a negation', () => {
|
|
65
|
+
const isIgnored = compileMatcher([
|
|
66
|
+
'*.tests.ts',
|
|
67
|
+
'!/imports/a/B.tests.ts',
|
|
68
|
+
'/imports/a/',
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
expect(isIgnored('./imports/a/B.tests.ts')).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('ignores nothing when every pattern is a negation', () => {
|
|
75
|
+
const isIgnored = compileMatcher(['!/imports/a/B.tests.ts']);
|
|
76
|
+
|
|
77
|
+
expect(isIgnored('./imports/a/B.tests.ts')).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('returns null when there are no patterns', () => {
|
|
81
|
+
expect(createIgnoreMatcherSource([])).toBeNull();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('anchors a rooted pattern to the project root', () => {
|
|
85
|
+
const isIgnored = compileMatcher(['/imports/a.tests.ts']);
|
|
86
|
+
|
|
87
|
+
expect(isIgnored('./imports/a.tests.ts')).toBe(true);
|
|
88
|
+
expect(isIgnored('./packages/imports/a.tests.ts')).toBe(false);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('supports character classes', () => {
|
|
92
|
+
const isIgnored = compileMatcher(['shard-[a-c].tests.ts']);
|
|
93
|
+
|
|
94
|
+
expect(isIgnored('./imports/shard-b.tests.ts')).toBe(true);
|
|
95
|
+
expect(isIgnored('./imports/shard-d.tests.ts')).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('supports the single-character wildcard', () => {
|
|
99
|
+
const isIgnored = compileMatcher(['shard-?.tests.ts']);
|
|
100
|
+
|
|
101
|
+
expect(isIgnored('./imports/shard-1.tests.ts')).toBe(true);
|
|
102
|
+
expect(isIgnored('./imports/shard-12.tests.ts')).toBe(false);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('matches a **/ pattern at the project root too', () => {
|
|
106
|
+
const isIgnored = compileMatcher(['**/legacy.tests.ts']);
|
|
107
|
+
|
|
108
|
+
expect(isIgnored('./legacy.tests.ts')).toBe(true);
|
|
109
|
+
expect(isIgnored('./imports/legacy.tests.ts')).toBe(true);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('does not let a negation rescue a file under an excluded directory', () => {
|
|
113
|
+
const isIgnored = compileMatcher(['/imports/', '!/imports/a.tests.ts']);
|
|
114
|
+
|
|
115
|
+
expect(isIgnored('./imports/a.tests.ts')).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('createIgnoreRegex', () => {
|
|
120
|
+
test('matches paths covered by a positive pattern', () => {
|
|
121
|
+
const regex = createIgnoreRegex(createIgnoreGlobConfig(['node_modules/']));
|
|
122
|
+
|
|
123
|
+
expect(regex.test('./node_modules/foo/index.js')).toBe(true);
|
|
124
|
+
expect(regex.test('./imports/a.js')).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('getMeteorIgnoreEntries', () => {
|
|
129
|
+
let projectDir;
|
|
130
|
+
|
|
131
|
+
beforeEach(() => {
|
|
132
|
+
projectDir = fs.mkdtempSync(path.join(os.tmpdir(), 'meteor-ignore-'));
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
afterEach(() => {
|
|
136
|
+
fs.rmSync(projectDir, { recursive: true, force: true });
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('appends METEOR_IGNORE patterns after the .meteorignore ones', () => {
|
|
140
|
+
fs.writeFileSync(
|
|
141
|
+
path.join(projectDir, '.meteorignore'),
|
|
142
|
+
'# comment\n*.tests.ts\n\n',
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const entries = withMeteorIgnoreEnv(
|
|
146
|
+
'!/imports/a/B.tests.ts /imports/c',
|
|
147
|
+
() => getMeteorIgnoreEntries(projectDir),
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
expect(entries).toEqual([
|
|
151
|
+
'*.tests.ts',
|
|
152
|
+
'!/imports/a/B.tests.ts',
|
|
153
|
+
'/imports/c',
|
|
154
|
+
]);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('returns METEOR_IGNORE patterns when there is no .meteorignore file', () => {
|
|
158
|
+
const entries = withMeteorIgnoreEnv('*.tests.ts', () =>
|
|
159
|
+
getMeteorIgnoreEntries(projectDir),
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
expect(entries).toEqual(['*.tests.ts']);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('returns an empty array when neither source has patterns', () => {
|
|
166
|
+
const entries = withMeteorIgnoreEnv(undefined, () =>
|
|
167
|
+
getMeteorIgnoreEntries(projectDir),
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
expect(entries).toEqual([]);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// The folder ignores generateEagerTestFile hands to Rspack as the context
|
|
175
|
+
// `exclude`. Rspack tests that regex against absolute module paths built with
|
|
176
|
+
// the platform separator, so on Windows they arrive with backslashes.
|
|
177
|
+
const EAGER_TEST_IGNORES = [
|
|
178
|
+
'**/node_modules/**',
|
|
179
|
+
'**/.meteor/**',
|
|
180
|
+
'**/public/**',
|
|
181
|
+
'**/private/**',
|
|
182
|
+
'**/_build/**',
|
|
183
|
+
'**/client/**',
|
|
184
|
+
];
|
|
185
|
+
|
|
186
|
+
function excludeFor(rootPath) {
|
|
187
|
+
return createIgnoreRegex(createIgnoreGlobConfig(EAGER_TEST_IGNORES), rootPath);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
describe('createIgnoreRegex', () => {
|
|
191
|
+
describe('POSIX absolute paths', () => {
|
|
192
|
+
const exclude = excludeFor('/Users/dev/app');
|
|
193
|
+
|
|
194
|
+
test('excludes ignored folders below the project root', () => {
|
|
195
|
+
expect(exclude.test('/Users/dev/app/node_modules/pkg/index.test.js')).toBe(true);
|
|
196
|
+
expect(exclude.test('/Users/dev/app/client/thing.test.js')).toBe(true);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('keeps app test files', () => {
|
|
200
|
+
expect(exclude.test('/Users/dev/app/imports/api/thing.test.js')).toBe(false);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test('keeps app test files when a parent folder is named private (#14688)', () => {
|
|
204
|
+
const privateExclude = excludeFor('/private/tmp/app');
|
|
205
|
+
expect(privateExclude.test('/private/tmp/app/imports/thing.test.js')).toBe(false);
|
|
206
|
+
expect(privateExclude.test('/private/tmp/app/private/thing.test.js')).toBe(true);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe('Windows absolute paths', () => {
|
|
211
|
+
const exclude = excludeFor('C:\\Users\\dev\\app');
|
|
212
|
+
|
|
213
|
+
test('excludes ignored folders joined with backslashes (#14513)', () => {
|
|
214
|
+
expect(exclude.test('C:\\Users\\dev\\app\\node_modules\\pkg\\index.test.js')).toBe(true);
|
|
215
|
+
expect(exclude.test('C:\\Users\\dev\\app\\client\\thing.test.js')).toBe(true);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test('keeps app test files', () => {
|
|
219
|
+
expect(exclude.test('C:\\Users\\dev\\app\\imports\\api\\thing.test.js')).toBe(false);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test('accepts a forward-slash root joined with backslashes', () => {
|
|
223
|
+
const mixedExclude = excludeFor('C:/Users/dev/app');
|
|
224
|
+
expect(mixedExclude.test('C:/Users/dev/app\\node_modules\\pkg\\index.test.js')).toBe(true);
|
|
225
|
+
expect(mixedExclude.test('C:/Users/dev/app\\imports\\thing.test.js')).toBe(false);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test('keeps app test files when a parent folder is named private', () => {
|
|
229
|
+
const privateExclude = excludeFor('C:\\Users\\dev\\private\\app');
|
|
230
|
+
expect(privateExclude.test('C:\\Users\\dev\\private\\app\\imports\\thing.test.js')).toBe(false);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
describe('without a root path', () => {
|
|
235
|
+
test('matches relative context keys', () => {
|
|
236
|
+
const exclude = createIgnoreRegex(createIgnoreGlobConfig(['tests/fixtures/**']));
|
|
237
|
+
expect(exclude.test('./tests/fixtures/thing.test.js')).toBe(true);
|
|
238
|
+
expect(exclude.test('./imports/thing.test.js')).toBe(false);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test('keeps a single star within one path segment', () => {
|
|
242
|
+
const exclude = createIgnoreRegex(createIgnoreGlobConfig(['imports/*.test.js']));
|
|
243
|
+
expect(exclude.test('./imports/thing.test.js')).toBe(true);
|
|
244
|
+
expect(exclude.test('./imports/nested/thing.test.js')).toBe(false);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test('never matches when every pattern is a negation', () => {
|
|
248
|
+
const exclude = createIgnoreRegex(createIgnoreGlobConfig(['!imports/**']));
|
|
249
|
+
expect(exclude.test('./imports/thing.test.js')).toBe(false);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test('survives being embedded as a regex literal in generated code', () => {
|
|
254
|
+
const exclude = excludeFor('C:\\Users\\dev\\app');
|
|
255
|
+
const embedded = new Function(`return ${exclude.toString()};`)();
|
|
256
|
+
expect(embedded.test('C:\\Users\\dev\\app\\node_modules\\pkg\\index.test.js')).toBe(true);
|
|
257
|
+
expect(embedded.test('C:\\Users\\dev\\app\\imports\\thing.test.js')).toBe(false);
|
|
258
|
+
});
|
|
259
|
+
});
|
package/lib/test.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const {
|
|
3
|
+
const {
|
|
4
|
+
createIgnoreGlobConfig,
|
|
5
|
+
createIgnoreMatcherSource,
|
|
6
|
+
createIgnoreRegex,
|
|
7
|
+
} = require("./ignore.js");
|
|
4
8
|
|
|
5
9
|
// Normalize a path to always use forward slashes (POSIX style).
|
|
6
10
|
// Module identifiers in bundled JS must use '/' regardless of OS.
|
|
@@ -47,10 +51,11 @@ const generateEagerTestFile = ({
|
|
|
47
51
|
createIgnoreGlobConfig(ignoreEntries),
|
|
48
52
|
projectDir,
|
|
49
53
|
);
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
// Build the .meteorignore filter. It is `ignore`-backed rather than a regex
|
|
55
|
+
// because gitignore's last-match-wins rule cannot be expressed as one, and
|
|
56
|
+
// collapsing it into one used to drop every `!` pattern — silently loading
|
|
57
|
+
// zero test files for a sharded run.
|
|
58
|
+
const meteorIgnoreMatcher = createIgnoreMatcherSource(inMeteorIgnoreEntries);
|
|
54
59
|
|
|
55
60
|
const prefix = (inPrefix && `${inPrefix}-`) || "";
|
|
56
61
|
const filename = isAppTest
|
|
@@ -64,8 +69,9 @@ const generateEagerTestFile = ({
|
|
|
64
69
|
const content = `${
|
|
65
70
|
globalImportPath ? `import '${toPosix(globalImportPath)}';\n\n` : ""
|
|
66
71
|
}${
|
|
67
|
-
|
|
68
|
-
? `
|
|
72
|
+
meteorIgnoreMatcher
|
|
73
|
+
? `import MeteorIgnore from '${toPosix(meteorIgnoreMatcher.modulePath)}';\n\n` +
|
|
74
|
+
`const MeteorIgnoreMatcher = ${meteorIgnoreMatcher.source}(MeteorIgnore);`
|
|
69
75
|
: ""
|
|
70
76
|
}
|
|
71
77
|
{
|
|
@@ -77,9 +83,9 @@ const generateEagerTestFile = ({
|
|
|
77
83
|
});
|
|
78
84
|
await Promise.all(ctx.keys().filter((k) => {
|
|
79
85
|
${
|
|
80
|
-
|
|
86
|
+
meteorIgnoreMatcher
|
|
81
87
|
? `// Only exclude based on *relative* path segments.
|
|
82
|
-
return !
|
|
88
|
+
return !MeteorIgnoreMatcher(k);`
|
|
83
89
|
: "return true;"
|
|
84
90
|
}
|
|
85
91
|
}).map(ctx));
|
package/lib/test.test.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const { createIgnoreMatcherSource } = require('./ignore.js');
|
|
6
|
+
const { generateEagerTestFile } = require('./test.js');
|
|
7
|
+
|
|
8
|
+
describe('generateEagerTestFile', () => {
|
|
9
|
+
let projectDir;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
projectDir = fs.mkdtempSync(path.join(os.tmpdir(), 'meteor-eager-test-'));
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
fs.rmSync(projectDir, { recursive: true, force: true });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const generate = meteorIgnoreEntries =>
|
|
20
|
+
fs.readFileSync(
|
|
21
|
+
generateEagerTestFile({
|
|
22
|
+
isAppTest: false,
|
|
23
|
+
projectDir,
|
|
24
|
+
buildContext: '_build',
|
|
25
|
+
meteorIgnoreEntries,
|
|
26
|
+
}),
|
|
27
|
+
'utf8',
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
test('filters test files through a matcher built from the ignore package', () => {
|
|
31
|
+
const entries = ['*.tests.ts', '!/imports/a/B.tests.ts'];
|
|
32
|
+
const { modulePath, source } = createIgnoreMatcherSource(entries);
|
|
33
|
+
|
|
34
|
+
const content = generate(entries);
|
|
35
|
+
|
|
36
|
+
expect(content).toContain(
|
|
37
|
+
`import MeteorIgnore from '${modulePath.replace(/\\/g, '/')}';`,
|
|
38
|
+
);
|
|
39
|
+
expect(content).toContain(source);
|
|
40
|
+
expect(content).toContain('!MeteorIgnoreMatcher(k)');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('keeps every test file when there are no meteor ignore entries', () => {
|
|
44
|
+
const content = generate([]);
|
|
45
|
+
|
|
46
|
+
expect(content).not.toContain('MeteorIgnore');
|
|
47
|
+
expect(content).toContain('return true;');
|
|
48
|
+
});
|
|
49
|
+
});
|
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.3",
|
|
4
4
|
"description": "Configuration logic for using Rspack in Meteor projects",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"fast-deep-equal": "^3.1.3",
|
|
15
|
+
"ignore": "^7.0.5",
|
|
15
16
|
"ignore-loader": "^0.1.2",
|
|
16
17
|
"isomorphic-timers-promises": "^1.0.1",
|
|
17
18
|
"rspack-merge": "^1.0.1"
|
|
@@ -164,6 +164,13 @@ class RequireExternalsPlugin {
|
|
|
164
164
|
}
|
|
165
165
|
|
|
166
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
|
+
|
|
167
174
|
// 1) Ensure globalThis.module / exports block is present if enabled
|
|
168
175
|
if (this._enableGlobalPolyfill) {
|
|
169
176
|
this._ensureGlobalThisModule();
|
package/rspack.config.js
CHANGED
|
@@ -123,6 +123,7 @@ function createSwcConfig({
|
|
|
123
123
|
externalHelpers,
|
|
124
124
|
isDevEnvironment,
|
|
125
125
|
isClient,
|
|
126
|
+
isLegacy,
|
|
126
127
|
isAngularEnabled,
|
|
127
128
|
}) {
|
|
128
129
|
const defaultConfig = {
|
|
@@ -133,7 +134,7 @@ function createSwcConfig({
|
|
|
133
134
|
...(isJsxEnabled && { jsx: true }),
|
|
134
135
|
...(isAngularEnabled && { decorators: true }),
|
|
135
136
|
},
|
|
136
|
-
target: isClient ? 'es2015' : 'es2022',
|
|
137
|
+
target: isLegacy ? 'es5' : isClient ? 'es2015' : 'es2022',
|
|
137
138
|
...(isReactEnabled && {
|
|
138
139
|
transform: {
|
|
139
140
|
react: {
|
|
@@ -257,6 +258,10 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
257
258
|
Meteor.chunksContext ||
|
|
258
259
|
process.env.RSPACK_CHUNKS_CONTEXT ||
|
|
259
260
|
`build-chunks${(meteorLocalDirName && `-${meteorLocalDirName}`) || ""}`;
|
|
261
|
+
const clientOutputContext = Meteor.clientOutputContext || '';
|
|
262
|
+
const clientOutputDir = path.resolve(projectDir, 'public', clientOutputContext);
|
|
263
|
+
const clientChunkPrefix = clientOutputContext ? '' : `${chunksContext}/`;
|
|
264
|
+
const clientAssetContext = path.posix.relative(clientOutputContext, assetsContext);
|
|
260
265
|
|
|
261
266
|
// Compute build paths before loading user config (needed by Meteor helpers below)
|
|
262
267
|
const outputPath = Meteor.outputPath;
|
|
@@ -267,6 +272,9 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
267
272
|
const isTest = !!Meteor.isTest;
|
|
268
273
|
const isClient = !!Meteor.isClient;
|
|
269
274
|
const isServer = !!Meteor.isServer;
|
|
275
|
+
const isLegacy = !!Meteor.isLegacy;
|
|
276
|
+
Meteor.isLegacy = isLegacy;
|
|
277
|
+
const arch = Meteor.arch;
|
|
270
278
|
const isRun = !!Meteor.isRun;
|
|
271
279
|
const isBuild = !!Meteor.isBuild;
|
|
272
280
|
const isReactEnabled = !!Meteor.isReactEnabled;
|
|
@@ -312,7 +320,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
312
320
|
// reassigned after load once mode is fully resolved.
|
|
313
321
|
let cacheStrategy = createCacheStrategy(
|
|
314
322
|
initialMode,
|
|
315
|
-
(Meteor.isClient && "client") || "server",
|
|
323
|
+
arch || (Meteor.isClient && "client") || "server",
|
|
316
324
|
{ projectConfigPath, configPath, buildContext }
|
|
317
325
|
);
|
|
318
326
|
let swcConfigRule = createSwcConfig({
|
|
@@ -321,8 +329,9 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
321
329
|
isJsxEnabled,
|
|
322
330
|
isTsxEnabled,
|
|
323
331
|
externalHelpers: enableSwcExternalHelpers,
|
|
324
|
-
isDevEnvironment: isRun && initialIsDev && !isTest && !isNative,
|
|
332
|
+
isDevEnvironment: isRun && initialIsDev && !isTest && !isNative && !arch,
|
|
325
333
|
isClient,
|
|
334
|
+
isLegacy,
|
|
326
335
|
isAngularEnabled,
|
|
327
336
|
});
|
|
328
337
|
Meteor.swcConfigOptions = swcConfigRule.options;
|
|
@@ -355,7 +364,8 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
355
364
|
return new HtmlRspackPlugin({
|
|
356
365
|
inject: false,
|
|
357
366
|
cache: true,
|
|
358
|
-
filename:
|
|
367
|
+
filename: path.relative(clientOutputDir,
|
|
368
|
+
path.resolve(projectDir, buildContext, outputDir, 'index.html')).replaceAll(path.sep, '/'),
|
|
359
369
|
templateContent: `
|
|
360
370
|
<head>
|
|
361
371
|
<% for tag in htmlRspackPlugin.tags.headTags { %>
|
|
@@ -401,7 +411,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
401
411
|
|
|
402
412
|
cacheStrategy = createCacheStrategy(
|
|
403
413
|
mode,
|
|
404
|
-
(Meteor.isClient && "client") || "server",
|
|
414
|
+
arch || (Meteor.isClient && "client") || "server",
|
|
405
415
|
// buildContext must be passed here too: this reassignment is the
|
|
406
416
|
// effective cache strategy, and omitting it made the cache directory
|
|
407
417
|
// collide across build contexts (e.g. custom METEOR_LOCAL_DIR setups).
|
|
@@ -418,7 +428,6 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
418
428
|
);
|
|
419
429
|
|
|
420
430
|
// Determine output directories
|
|
421
|
-
const clientOutputDir = path.resolve(projectDir, "public");
|
|
422
431
|
const serverOutputDir = path.resolve(projectDir, "private");
|
|
423
432
|
|
|
424
433
|
|
|
@@ -441,12 +450,12 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
441
450
|
],
|
|
442
451
|
};
|
|
443
452
|
|
|
444
|
-
if (Meteor.
|
|
453
|
+
if (Meteor.isVerbose) {
|
|
445
454
|
console.log("[i] Rspack mode:", mode);
|
|
446
455
|
console.log("[i] Meteor flags:", Meteor);
|
|
447
456
|
}
|
|
448
457
|
|
|
449
|
-
const isDevEnvironment = isRun && isDev && !isTest && !isNative;
|
|
458
|
+
const isDevEnvironment = isRun && isDev && !isTest && !isNative && !arch;
|
|
450
459
|
swcConfigRule = createSwcConfig({
|
|
451
460
|
isTypescriptEnabled,
|
|
452
461
|
isReactEnabled,
|
|
@@ -455,6 +464,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
455
464
|
externalHelpers: enableSwcExternalHelpers,
|
|
456
465
|
isDevEnvironment,
|
|
457
466
|
isClient,
|
|
467
|
+
isLegacy,
|
|
458
468
|
isAngularEnabled,
|
|
459
469
|
});
|
|
460
470
|
Meteor.swcConfigOptions = swcConfigRule.options;
|
|
@@ -492,7 +502,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
492
502
|
...(Meteor.isBlazeEnabled && {
|
|
493
503
|
externals: /\.html$/,
|
|
494
504
|
isEagerImport: (module) => module.endsWith(".html"),
|
|
495
|
-
...((isProd || (isTest && isClient)) && {
|
|
505
|
+
...((isProd || arch || (isTest && isClient)) && {
|
|
496
506
|
lastImports: [`./${outputFilename}`],
|
|
497
507
|
}),
|
|
498
508
|
}),
|
|
@@ -505,7 +515,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
505
515
|
const filename = _fileInfo.filename;
|
|
506
516
|
const isPublic = filename.startsWith("/") || filename.startsWith("public");
|
|
507
517
|
if (isPublic) return `[name][ext][query]`;
|
|
508
|
-
return `${assetsContext}/[hash][ext][query]`;
|
|
518
|
+
return `${isClient ? clientAssetContext : assetsContext}/[hash][ext][query]`;
|
|
509
519
|
};
|
|
510
520
|
|
|
511
521
|
const rsdoctorModule = isBundleVisualizerEnabled
|
|
@@ -537,6 +547,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
537
547
|
: { stats: "errors-warnings", infrastructureLogging: { level: "warn" } };
|
|
538
548
|
|
|
539
549
|
const clientEntry =
|
|
550
|
+
arch && !(isTest && isTestEager) ? path.resolve(projectDir, buildContext, entryPath) :
|
|
540
551
|
isClient && isTest && isTestEager && isTestFullApp
|
|
541
552
|
? generateEagerTestFile({
|
|
542
553
|
isAppTest: true,
|
|
@@ -544,7 +555,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
544
555
|
buildContext,
|
|
545
556
|
ignoreEntries: ["**/server/**"],
|
|
546
557
|
meteorIgnoreEntries,
|
|
547
|
-
prefix: "client",
|
|
558
|
+
prefix: arch || "client",
|
|
548
559
|
extraEntry: path.resolve(process.cwd(), Meteor.mainClientEntry),
|
|
549
560
|
globalImportPath: path.resolve(projectDir, buildContext, entryPath),
|
|
550
561
|
})
|
|
@@ -556,13 +567,13 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
556
567
|
buildContext,
|
|
557
568
|
ignoreEntries: ["**/server/**"],
|
|
558
569
|
meteorIgnoreEntries,
|
|
559
|
-
prefix: "client",
|
|
570
|
+
prefix: arch || "client",
|
|
560
571
|
globalImportPath: path.resolve(projectDir, buildContext, entryPath),
|
|
561
572
|
})
|
|
562
573
|
: isClient && isTest && testEntry
|
|
563
574
|
? path.resolve(process.cwd(), testEntry)
|
|
564
575
|
: path.resolve(process.cwd(), buildContext, entryPath);
|
|
565
|
-
const clientNameConfig = `[${(isTest && "test-") || ""}client-rspack]`;
|
|
576
|
+
const clientNameConfig = `[${(isTest && "test-") || ""}${arch || 'client'}-rspack]`;
|
|
566
577
|
|
|
567
578
|
// Default onListening provided by meteor-rspack. Kept as a named
|
|
568
579
|
// reference so we can detect a user-supplied override after merge
|
|
@@ -603,7 +614,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
603
614
|
// Base client config
|
|
604
615
|
let clientConfig = {
|
|
605
616
|
name: clientNameConfig,
|
|
606
|
-
target: "web",
|
|
617
|
+
target: isLegacy ? ["web", "es5"] : "web",
|
|
607
618
|
mode,
|
|
608
619
|
entry: clientEntry,
|
|
609
620
|
output: {
|
|
@@ -611,27 +622,28 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
611
622
|
filename: (_module) => {
|
|
612
623
|
const chunkName = _module.chunk?.name;
|
|
613
624
|
const isMainChunk = !chunkName || chunkName === "main";
|
|
614
|
-
const chunkSuffix = `${
|
|
625
|
+
const chunkSuffix = `${clientChunkPrefix}[id]${
|
|
615
626
|
isProd ? ".[chunkhash]" : ""
|
|
616
627
|
}.js`;
|
|
617
628
|
if (isDevEnvironment) {
|
|
618
629
|
if (isMainChunk) return outputFilename;
|
|
619
630
|
return chunkSuffix;
|
|
620
631
|
}
|
|
621
|
-
if (isMainChunk) return
|
|
632
|
+
if (isMainChunk) return path.relative(clientOutputDir,
|
|
633
|
+
path.resolve(projectDir, buildContext, outputPath)).replaceAll(path.sep, '/');
|
|
622
634
|
return chunkSuffix;
|
|
623
635
|
},
|
|
624
636
|
library: { type: "commonjs2" },
|
|
625
|
-
publicPath: "/",
|
|
626
|
-
chunkFilename: `${
|
|
637
|
+
publicPath: clientOutputContext ? `/${clientOutputContext}/` : "/",
|
|
638
|
+
chunkFilename: `${clientChunkPrefix}[id]${isProd ? ".[chunkhash]" : ""}.js`,
|
|
627
639
|
assetModuleFilename,
|
|
628
|
-
cssFilename: `${
|
|
640
|
+
cssFilename: `${clientChunkPrefix}[name]${
|
|
629
641
|
isProd ? ".[contenthash]" : ""
|
|
630
642
|
}.css`,
|
|
631
|
-
cssChunkFilename: `${
|
|
643
|
+
cssChunkFilename: `${clientChunkPrefix}[id]${
|
|
632
644
|
isProd ? ".[contenthash]" : ""
|
|
633
645
|
}.css`,
|
|
634
|
-
...(isProd && { clean: { keep: keepOutsideBuild() } }),
|
|
646
|
+
...(isProd && { clean: clientOutputContext ? true : { keep: keepOutsideBuild() } }),
|
|
635
647
|
},
|
|
636
648
|
optimization: {
|
|
637
649
|
usedExports: true,
|
|
@@ -932,8 +944,8 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
932
944
|
|
|
933
945
|
delete config["meteor.enablePortableBuild"];
|
|
934
946
|
|
|
935
|
-
if (Meteor.
|
|
936
|
-
|
|
947
|
+
if (Meteor.isVerbose) {
|
|
948
|
+
console.log("Config:", inspect(config, { depth: null, colors: true }));
|
|
937
949
|
}
|
|
938
950
|
|
|
939
951
|
// Check if lazyCompilation is enabled and warn the user
|
|
@@ -950,19 +962,42 @@ module.exports = async function (inMeteor = {}, argv = {}) {
|
|
|
950
962
|
|
|
951
963
|
// Add MeteorRspackOutputPlugin as the last plugin to output compilation info
|
|
952
964
|
const meteorRspackOutputPlugin = new MeteorRspackOutputPlugin({
|
|
953
|
-
getData: (stats, { isRebuild, compilationCount, compiler }) =>
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
965
|
+
getData: (stats, { isRebuild, compilationCount, compiler }) => {
|
|
966
|
+
const generatedAppPaths = [
|
|
967
|
+
path.join('.meteor', 'local'),
|
|
968
|
+
path.join('.meteor', 'types'),
|
|
969
|
+
buildContext,
|
|
970
|
+
];
|
|
971
|
+
const changedFiles = [
|
|
972
|
+
...(compiler?.modifiedFiles || []),
|
|
973
|
+
...(compiler?.removedFiles || []),
|
|
974
|
+
];
|
|
975
|
+
const hasAppSourceChanges =
|
|
976
|
+
changedFiles.length === 0 ||
|
|
977
|
+
changedFiles.some(file => {
|
|
978
|
+
const relativePath = path.relative(projectDir, file);
|
|
979
|
+
return !generatedAppPaths.some(generatedPath =>
|
|
980
|
+
relativePath === generatedPath ||
|
|
981
|
+
relativePath.startsWith(`${generatedPath}${path.sep}`)
|
|
982
|
+
);
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
return {
|
|
986
|
+
name: config.name,
|
|
987
|
+
mode: config.mode,
|
|
988
|
+
hasErrors: stats.hasErrors(),
|
|
989
|
+
hasWarnings: stats.hasWarnings(),
|
|
990
|
+
hasAppSourceChanges,
|
|
991
|
+
hash: stats.hash,
|
|
992
|
+
timestamp: Date.now(),
|
|
993
|
+
statsOverrided,
|
|
994
|
+
compilationCount,
|
|
995
|
+
isRebuild,
|
|
996
|
+
...(!isRebuild && compiler && {
|
|
997
|
+
delegatedExtensions: extractDelegatedExtensions(stats, compiler),
|
|
998
|
+
}),
|
|
999
|
+
};
|
|
1000
|
+
},
|
|
966
1001
|
});
|
|
967
1002
|
config.plugins = [meteorRspackOutputPlugin, ...(config.plugins || [])];
|
|
968
1003
|
|