@mui/internal-bundle-size-checker 1.0.9-canary.81 → 1.0.9-canary.83
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/LICENSE +1 -1
- package/README.md +3 -1
- package/build/findExportedPaths.d.ts +28 -0
- package/package.json +3 -3
- package/src/configLoader.js +1 -31
- package/src/findExportedPaths.js +238 -0
- package/src/findExportedPaths.test.js +54 -0
- package/src/types.d.ts +1 -1
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -66,7 +66,9 @@ export default defineConfig(async () => {
|
|
|
66
66
|
importedNames: ['Button'],
|
|
67
67
|
// When externals is not specified, peer dependencies will be automatically excluded
|
|
68
68
|
},
|
|
69
|
-
// Expand a package into one entry per export from its package.json
|
|
69
|
+
// Expand a package into one entry per export from its package.json.
|
|
70
|
+
// Wildcard subpath exports (e.g. `"./*"`) are expanded against the files
|
|
71
|
+
// present in the installed package directory.
|
|
70
72
|
{ id: '@mui/material', expand: true },
|
|
71
73
|
// Expand with glob exclusions (matched against the export subpath, e.g. `styles/colors`)
|
|
72
74
|
{
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves a package's subpath exports, expanding wildcard patterns (e.g. `./*`)
|
|
3
|
+
* against the files present in the package directory.
|
|
4
|
+
*/
|
|
5
|
+
export type ExactExport = {
|
|
6
|
+
key: string;
|
|
7
|
+
isNull: boolean;
|
|
8
|
+
};
|
|
9
|
+
export type PatternExport = {
|
|
10
|
+
key: string;
|
|
11
|
+
prefix: string;
|
|
12
|
+
suffix: string;
|
|
13
|
+
base: number;
|
|
14
|
+
isNull: boolean;
|
|
15
|
+
target: string | undefined;
|
|
16
|
+
};
|
|
17
|
+
export type CollectedExports = {
|
|
18
|
+
exact: ExactExport[];
|
|
19
|
+
patterns: PatternExport[];
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Reads a package's `exports` field and returns its concrete subpath export
|
|
23
|
+
* paths, expanding any wildcard subpath patterns (e.g. `./*`) against the
|
|
24
|
+
* files present in the package directory.
|
|
25
|
+
* @param {string} pkgJson - Path to the package's package.json
|
|
26
|
+
* @returns {Promise<string[]>}
|
|
27
|
+
*/
|
|
28
|
+
export declare function findExportedPaths(pkgJson: string): Promise<string[]>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mui/internal-bundle-size-checker",
|
|
3
|
-
"version": "1.0.9-canary.
|
|
3
|
+
"version": "1.0.9-canary.83",
|
|
4
4
|
"author": "MUI Team",
|
|
5
5
|
"description": "Bundle size checker for MUI packages.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"micromatch": "^4.0.8",
|
|
37
37
|
"piscina": "^5.1.4",
|
|
38
38
|
"rollup-plugin-visualizer": "^7.0.1",
|
|
39
|
-
"vite": "^8.0.
|
|
39
|
+
"vite": "^8.0.16",
|
|
40
40
|
"yargs": "^18.0.0",
|
|
41
41
|
"zod": "^4.4.3"
|
|
42
42
|
},
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"@types/micromatch": "4.0.10",
|
|
46
46
|
"@types/yargs": "17.0.35"
|
|
47
47
|
},
|
|
48
|
-
"gitSha": "
|
|
48
|
+
"gitSha": "0f811814fc911cd354856dae0195666fdc46c7fb",
|
|
49
49
|
"scripts": {
|
|
50
50
|
"build": "tsgo -p tsconfig.build.json",
|
|
51
51
|
"test": "pnpm -w test --project @mui/internal-bundle-size-checker",
|
package/src/configLoader.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* Utility to load the bundle-size-checker configuration
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import fs from 'node:fs/promises';
|
|
6
5
|
import path from 'node:path';
|
|
7
6
|
import envCi from 'env-ci';
|
|
8
7
|
import * as module from 'node:module';
|
|
9
8
|
import * as url from 'node:url';
|
|
10
9
|
import micromatch from 'micromatch';
|
|
10
|
+
import { findExportedPaths } from './findExportedPaths.js';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* @typedef {import('./types.js').BundleSizeCheckerConfigObject} BundleSizeCheckerConfigObject
|
|
@@ -104,36 +104,6 @@ export function applyUploadConfigDefaults(uploadConfig, ciInfo) {
|
|
|
104
104
|
return result;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
/**
|
|
108
|
-
* @param {{ [s: string]: any; } | ArrayLike<any>} exportsObj
|
|
109
|
-
* @returns {string[]} Array of export paths
|
|
110
|
-
*/
|
|
111
|
-
function findExports(exportsObj) {
|
|
112
|
-
const paths = [];
|
|
113
|
-
for (const [key, value] of Object.entries(exportsObj)) {
|
|
114
|
-
// ignore null values
|
|
115
|
-
if (!value) {
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
if (key.startsWith('.')) {
|
|
119
|
-
paths.push(key);
|
|
120
|
-
} else {
|
|
121
|
-
paths.push(...findExports(value));
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
return paths;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* @param {import("fs").PathLike | fs.FileHandle} pkgJson
|
|
129
|
-
* @returns {Promise<string[]>}
|
|
130
|
-
*/
|
|
131
|
-
async function findExportedPaths(pkgJson) {
|
|
132
|
-
const pkgContent = await fs.readFile(pkgJson, 'utf8');
|
|
133
|
-
const { exports = {} } = JSON.parse(pkgContent);
|
|
134
|
-
return findExports(exports);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
107
|
/**
|
|
138
108
|
* Checks if the given import source is a top-level package
|
|
139
109
|
* @param {string} importSrc - The import source string
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves a package's subpath exports, expanding wildcard patterns (e.g. `./*`)
|
|
3
|
+
* against the files present in the package directory.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'node:fs/promises';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {{ key: string; isNull: boolean }} ExactExport
|
|
11
|
+
* @typedef {{
|
|
12
|
+
* key: string;
|
|
13
|
+
* prefix: string;
|
|
14
|
+
* suffix: string;
|
|
15
|
+
* base: number;
|
|
16
|
+
* isNull: boolean;
|
|
17
|
+
* target: string | undefined;
|
|
18
|
+
* }} PatternExport
|
|
19
|
+
* @typedef {{ exact: ExactExport[]; patterns: PatternExport[] }} CollectedExports
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Resolves an export value to the first target path string containing a `*`,
|
|
24
|
+
* following condition objects and arrays. Node requires every condition of a
|
|
25
|
+
* subpath pattern to place the `*` identically, so any matching target is
|
|
26
|
+
* enough to enumerate the available stems.
|
|
27
|
+
* @param {unknown} value
|
|
28
|
+
* @returns {string | undefined}
|
|
29
|
+
*/
|
|
30
|
+
function findWildcardTarget(value) {
|
|
31
|
+
if (typeof value === 'string') {
|
|
32
|
+
return value.includes('*') ? value : undefined;
|
|
33
|
+
}
|
|
34
|
+
if (Array.isArray(value)) {
|
|
35
|
+
for (const item of value) {
|
|
36
|
+
const target = findWildcardTarget(item);
|
|
37
|
+
if (target) {
|
|
38
|
+
return target;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
if (value && typeof value === 'object') {
|
|
44
|
+
// Prefer runtime conditions; `types` resolves to .d.ts files, which aren't
|
|
45
|
+
// what gets bundled, so only fall back to it when nothing else matches.
|
|
46
|
+
let typesFallback;
|
|
47
|
+
for (const [condition, conditionValue] of Object.entries(value)) {
|
|
48
|
+
const target = findWildcardTarget(conditionValue);
|
|
49
|
+
if (target) {
|
|
50
|
+
if (condition === 'types') {
|
|
51
|
+
typesFallback = typesFallback ?? target;
|
|
52
|
+
} else {
|
|
53
|
+
return target;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return typesFallback;
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Collects the subpath exports of a package, splitting them into exact keys and
|
|
64
|
+
* wildcard pattern keys. Both kinds keep their `null` (blocking) state so the
|
|
65
|
+
* resolver can reproduce Node's most-specific-match semantics.
|
|
66
|
+
* @param {Record<string, unknown>} exportsObj
|
|
67
|
+
* @returns {CollectedExports}
|
|
68
|
+
*/
|
|
69
|
+
function collectExports(exportsObj) {
|
|
70
|
+
/** @type {CollectedExports} */
|
|
71
|
+
const collected = { exact: [], patterns: [] };
|
|
72
|
+
for (const [key, value] of Object.entries(exportsObj)) {
|
|
73
|
+
if (!key.startsWith('.')) {
|
|
74
|
+
// Top-level condition sugar (e.g. `{ import, require }`); recurse to find
|
|
75
|
+
// any nested subpath keys. Condition objects have none, so this is inert
|
|
76
|
+
// for them, but preserves the previous flattening behavior.
|
|
77
|
+
if (value && typeof value === 'object') {
|
|
78
|
+
const nested = collectExports(/** @type {Record<string, unknown>} */ (value));
|
|
79
|
+
collected.exact.push(...nested.exact);
|
|
80
|
+
collected.patterns.push(...nested.patterns);
|
|
81
|
+
}
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const isNull = value === null;
|
|
86
|
+
if (key.includes('*')) {
|
|
87
|
+
const starIndex = key.indexOf('*');
|
|
88
|
+
collected.patterns.push({
|
|
89
|
+
key,
|
|
90
|
+
prefix: key.slice(0, starIndex),
|
|
91
|
+
suffix: key.slice(starIndex + 1),
|
|
92
|
+
// The length of the static prefix decides specificity: a longer prefix
|
|
93
|
+
// is a more specific match (matches Node's PATTERN_KEY_COMPARE).
|
|
94
|
+
base: starIndex,
|
|
95
|
+
isNull,
|
|
96
|
+
target: isNull ? undefined : findWildcardTarget(value),
|
|
97
|
+
});
|
|
98
|
+
} else {
|
|
99
|
+
collected.exact.push({ key, isNull });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return collected;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Finds the most specific pattern matching an export path, mirroring Node's
|
|
107
|
+
* resolution: the pattern with the longest static prefix wins, then the longest
|
|
108
|
+
* key. The `*` placeholder may span path separators.
|
|
109
|
+
* @param {string} exportPath
|
|
110
|
+
* @param {PatternExport[]} patterns
|
|
111
|
+
* @returns {PatternExport | undefined}
|
|
112
|
+
*/
|
|
113
|
+
function bestPatternMatch(exportPath, patterns) {
|
|
114
|
+
let best;
|
|
115
|
+
for (const pattern of patterns) {
|
|
116
|
+
if (
|
|
117
|
+
exportPath.length >= pattern.prefix.length + pattern.suffix.length &&
|
|
118
|
+
exportPath.startsWith(pattern.prefix) &&
|
|
119
|
+
exportPath.endsWith(pattern.suffix)
|
|
120
|
+
) {
|
|
121
|
+
if (
|
|
122
|
+
!best ||
|
|
123
|
+
pattern.base > best.base ||
|
|
124
|
+
(pattern.base === best.base && pattern.key.length > best.key.length)
|
|
125
|
+
) {
|
|
126
|
+
best = pattern;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return best;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Expands a single wildcard subpath export (e.g. key `./*`, target
|
|
135
|
+
* `./dist/*.js`) into concrete subpath keys by globbing the package directory.
|
|
136
|
+
* Mirrors Node's subpath pattern semantics where `*` is a substring
|
|
137
|
+
* placeholder that may span path separators.
|
|
138
|
+
* @param {string} key
|
|
139
|
+
* @param {string} target
|
|
140
|
+
* @param {string} pkgDir
|
|
141
|
+
* @returns {Promise<string[]>}
|
|
142
|
+
*/
|
|
143
|
+
async function expandWildcardKey(key, target, pkgDir) {
|
|
144
|
+
const keyStarIndex = key.indexOf('*');
|
|
145
|
+
const keyPrefix = key.slice(0, keyStarIndex);
|
|
146
|
+
const keySuffix = key.slice(keyStarIndex + 1);
|
|
147
|
+
|
|
148
|
+
const targetStarIndex = target.indexOf('*');
|
|
149
|
+
const targetPrefix = target.slice(0, targetStarIndex);
|
|
150
|
+
const targetSuffix = target.slice(targetStarIndex + 1);
|
|
151
|
+
|
|
152
|
+
// Limit the filesystem walk to the static directory portion of the target.
|
|
153
|
+
const dirEnd = targetPrefix.lastIndexOf('/');
|
|
154
|
+
const globDir = dirEnd >= 0 ? targetPrefix.slice(0, dirEnd + 1) : '';
|
|
155
|
+
const globPattern = `${globDir.startsWith('./') ? globDir.slice(2) : globDir}**`;
|
|
156
|
+
|
|
157
|
+
const expanded = [];
|
|
158
|
+
for await (const dirent of fs.glob(globPattern, { cwd: pkgDir, withFileTypes: true })) {
|
|
159
|
+
if (!dirent.isFile()) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const absPath = path.join(dirent.parentPath, dirent.name);
|
|
163
|
+
const relPath = `./${path.relative(pkgDir, absPath).split(path.sep).join('/')}`;
|
|
164
|
+
if (relPath.startsWith('./node_modules/')) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (relPath.startsWith(targetPrefix) && relPath.endsWith(targetSuffix)) {
|
|
168
|
+
const stem =
|
|
169
|
+
targetSuffix.length > 0
|
|
170
|
+
? relPath.slice(targetPrefix.length, relPath.length - targetSuffix.length)
|
|
171
|
+
: relPath.slice(targetPrefix.length);
|
|
172
|
+
if (stem.length > 0) {
|
|
173
|
+
expanded.push(`${keyPrefix}${stem}${keySuffix}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return expanded;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Reads a package's `exports` field and returns its concrete subpath export
|
|
182
|
+
* paths, expanding any wildcard subpath patterns (e.g. `./*`) against the
|
|
183
|
+
* files present in the package directory.
|
|
184
|
+
* @param {string} pkgJson - Path to the package's package.json
|
|
185
|
+
* @returns {Promise<string[]>}
|
|
186
|
+
*/
|
|
187
|
+
export async function findExportedPaths(pkgJson) {
|
|
188
|
+
const pkgContent = await fs.readFile(pkgJson, 'utf8');
|
|
189
|
+
const { exports = {} } = JSON.parse(pkgContent);
|
|
190
|
+
const pkgDir = path.dirname(pkgJson);
|
|
191
|
+
|
|
192
|
+
const { exact, patterns } = collectExports(exports);
|
|
193
|
+
const exactIsNullByKey = new Map(exact.map((entry) => [entry.key, entry.isNull]));
|
|
194
|
+
|
|
195
|
+
// Candidate export paths: explicit (non-null) exact exports, plus everything
|
|
196
|
+
// produced by expanding the non-null wildcard patterns against the files on
|
|
197
|
+
// disk. Null patterns are never expanded; they only ever block.
|
|
198
|
+
const candidates = new Set();
|
|
199
|
+
for (const entry of exact) {
|
|
200
|
+
if (!entry.isNull) {
|
|
201
|
+
candidates.add(entry.key);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const expandedGroups = await Promise.all(
|
|
205
|
+
patterns
|
|
206
|
+
.filter((pattern) => !pattern.isNull && pattern.target)
|
|
207
|
+
.map((pattern) =>
|
|
208
|
+
expandWildcardKey(pattern.key, /** @type {string} */ (pattern.target), pkgDir),
|
|
209
|
+
),
|
|
210
|
+
);
|
|
211
|
+
for (const group of expandedGroups) {
|
|
212
|
+
for (const exportPath of group) {
|
|
213
|
+
candidates.add(exportPath);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Keep each candidate only if its most specific matching export key is not
|
|
218
|
+
// blocked by `null`. Exact keys take priority over patterns; among patterns
|
|
219
|
+
// the longest static prefix wins. This reproduces Node's cascade, where a
|
|
220
|
+
// deeper non-null pattern un-blocks paths under a shallower null pattern.
|
|
221
|
+
const result = [];
|
|
222
|
+
for (const exportPath of candidates) {
|
|
223
|
+
const exactIsNull = exactIsNullByKey.get(exportPath);
|
|
224
|
+
let exported;
|
|
225
|
+
if (exactIsNull !== undefined) {
|
|
226
|
+
exported = !exactIsNull;
|
|
227
|
+
} else {
|
|
228
|
+
const best = bestPatternMatch(exportPath, patterns);
|
|
229
|
+
exported = best ? !best.isNull : false;
|
|
230
|
+
}
|
|
231
|
+
if (exported) {
|
|
232
|
+
result.push(exportPath);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
result.sort();
|
|
237
|
+
return result;
|
|
238
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { describe, it, expect } from 'vitest';
|
|
4
|
+
import { findExportedPaths } from './findExportedPaths.js';
|
|
5
|
+
|
|
6
|
+
const fixturesDir = fileURLToPath(new URL('./__fixtures__/exports', import.meta.url));
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {string} name
|
|
10
|
+
* @returns {string}
|
|
11
|
+
*/
|
|
12
|
+
function pkgJson(name) {
|
|
13
|
+
return path.join(fixturesDir, name, 'package.json');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('findExportedPaths', () => {
|
|
17
|
+
it('returns concrete subpaths unchanged when there are no wildcards', async () => {
|
|
18
|
+
expect(await findExportedPaths(pkgJson('static-only'))).toEqual([
|
|
19
|
+
'.',
|
|
20
|
+
'./package.json',
|
|
21
|
+
'./utils',
|
|
22
|
+
]);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('expands a wildcard subpath export against files on disk', async () => {
|
|
26
|
+
expect(await findExportedPaths(pkgJson('wildcard-basic'))).toEqual(['.', './a', './b']);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('expands wildcards across path separators, matching Node subpath semantics', async () => {
|
|
30
|
+
expect(await findExportedPaths(pkgJson('wildcard-nested'))).toEqual(['./a', './sub/c']);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('resolves the wildcard target from runtime conditions, ignoring types', async () => {
|
|
34
|
+
expect(await findExportedPaths(pkgJson('wildcard-conditions'))).toEqual(['./a', './b']);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('drops paths blocked by a null negation pattern', async () => {
|
|
38
|
+
expect(await findExportedPaths(pkgJson('wildcard-negation'))).toEqual(['./a']);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('resolves null patterns by specificity, letting a deeper pattern un-block', async () => {
|
|
42
|
+
// `./foo/bar/*` is null, but `./foo/bar/baz/*` is a more specific match, so
|
|
43
|
+
// `./foo/bar/baz/z` stays exported while everything else under `./foo/bar/`
|
|
44
|
+
// (including deeper paths) is blocked.
|
|
45
|
+
expect(await findExportedPaths(pkgJson('wildcard-cascade'))).toEqual([
|
|
46
|
+
'./foo/a',
|
|
47
|
+
'./foo/bar/baz/z',
|
|
48
|
+
]);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('drops wildcard keys whose value has no expandable target', async () => {
|
|
52
|
+
expect(await findExportedPaths(pkgJson('wildcard-no-target'))).toEqual([]);
|
|
53
|
+
});
|
|
54
|
+
});
|
package/src/types.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export interface ObjectEntry {
|
|
|
25
25
|
importedNames?: string[]; // Optional array of named imports
|
|
26
26
|
externals?: string[]; // Optional array of packages to exclude from the bundle
|
|
27
27
|
track?: boolean; // Whether this bundle should be tracked in PR comments (defaults to false)
|
|
28
|
-
expand?: boolean | { exclude?: string[] }; // Expand the entry to include all exports
|
|
28
|
+
expand?: boolean | { exclude?: string[] }; // Expand the entry to include all exports. Wildcard subpath exports (e.g. `./*`) are expanded against the package's files on disk. Pass `{ exclude }` with glob patterns (matched against export subpaths, e.g. `styles/**`) to skip specific paths
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export type EntryPoint = StringEntry | ObjectEntry;
|