package-build-stats 8.0.2 → 8.1.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/build/config/makeRspackConfig.js +7 -1
- package/build/getPackageExportSizes.js +17 -0
- package/build/getPackageStats.js +13 -0
- package/build/utils/build.utils.js +5 -1
- package/build/utils/exports.utils.d.ts +1 -0
- package/build/utils/exports.utils.js +42 -14
- package/package.json +2 -1
- package/src/config/makeRspackConfig.ts +7 -1
- package/src/getPackageExportSizes.ts +30 -0
- package/src/getPackageStats.ts +16 -0
- package/src/utils/build.utils.ts +8 -1
- package/src/utils/exports.utils.ts +56 -16
|
@@ -21,9 +21,15 @@ function makeRspackConfig({ packageName: _packageName, entry, externals, debug:
|
|
|
21
21
|
mode: 'production',
|
|
22
22
|
devtool: _debug ? 'source-map' : false,
|
|
23
23
|
optimization: {
|
|
24
|
-
|
|
24
|
+
// Use 'multiple' to create a separate runtime chunk per entry point
|
|
25
|
+
// 'single' or { name: 'runtime' } shares one runtime across all entries,
|
|
26
|
+
// which breaks tree-shaking when building many exports simultaneously
|
|
27
|
+
runtimeChunk: 'multiple',
|
|
25
28
|
realContentHash: false,
|
|
26
29
|
minimize: minify,
|
|
30
|
+
// Enable tree-shaking optimizations
|
|
31
|
+
usedExports: true, // Mark unused exports for removal
|
|
32
|
+
sideEffects: true, // Respect package.json sideEffects field
|
|
27
33
|
// Rspack automatically uses its built-in default minifiers:
|
|
28
34
|
// - SwcJsMinimizerRspackPlugin for JS (SWC-based, very fast)
|
|
29
35
|
// - LightningCssMinimizerRspackPlugin for CSS (Lightning CSS-based)
|
|
@@ -46,16 +46,31 @@ async function getAllPackageExports(packageString, options = {}) {
|
|
|
46
46
|
}
|
|
47
47
|
async function getPackageExportSizes(packageString, options = {}) {
|
|
48
48
|
const startTime = perf_hooks_1.performance.now();
|
|
49
|
+
const timings = {};
|
|
49
50
|
const { name: packageName, normalPath } = (0, common_utils_1.parsePackageString)(packageString);
|
|
51
|
+
const preparePathStart = perf_hooks_1.performance.now();
|
|
50
52
|
const installPath = await installation_utils_1.default.preparePath(packageName);
|
|
53
|
+
timings.preparePath = perf_hooks_1.performance.now() - preparePathStart;
|
|
54
|
+
console.log(`[PERF] [ExportSizes] preparePath: ${timings.preparePath.toFixed(2)}ms`);
|
|
51
55
|
try {
|
|
56
|
+
const installStart = perf_hooks_1.performance.now();
|
|
52
57
|
await installPackage(packageString, installPath, options);
|
|
58
|
+
timings.install = perf_hooks_1.performance.now() - installStart;
|
|
59
|
+
console.log(`[PERF] [ExportSizes] installPackage: ${timings.install.toFixed(2)}ms`);
|
|
53
60
|
// The package is installed in node_modules subdirectory
|
|
54
61
|
const packagePath = normalPath || path_1.default.join(installPath, 'node_modules', packageName);
|
|
62
|
+
const getAllExportsStart = perf_hooks_1.performance.now();
|
|
55
63
|
const exportMap = await (0, exports_utils_1.getAllExports)(packageString, packagePath, packageName, installPath);
|
|
64
|
+
timings.getAllExports = perf_hooks_1.performance.now() - getAllExportsStart;
|
|
65
|
+
console.log(`[PERF] [ExportSizes] getAllExports: ${timings.getAllExports.toFixed(2)}ms`);
|
|
56
66
|
const exports = Object.keys(exportMap).filter(exp => !(exp === 'default'));
|
|
57
67
|
debug('Got %d exports for %s', exports.length, packageString);
|
|
68
|
+
console.log(`[PERF] [ExportSizes] Found ${exports.length} exports`);
|
|
69
|
+
const externalsStart = perf_hooks_1.performance.now();
|
|
58
70
|
const externals = (0, common_utils_1.getExternals)(packageName, installPath);
|
|
71
|
+
timings.getExternals = perf_hooks_1.performance.now() - externalsStart;
|
|
72
|
+
console.log(`[PERF] [ExportSizes] getExternals: ${timings.getExternals.toFixed(2)}ms`);
|
|
73
|
+
const buildStart = perf_hooks_1.performance.now();
|
|
59
74
|
const builtDetails = await build_utils_1.default.buildPackageIgnoringMissingDeps({
|
|
60
75
|
name: packageName,
|
|
61
76
|
installPath,
|
|
@@ -66,6 +81,8 @@ async function getPackageExportSizes(packageString, options = {}) {
|
|
|
66
81
|
includeDependencySizes: false,
|
|
67
82
|
},
|
|
68
83
|
});
|
|
84
|
+
timings.build = perf_hooks_1.performance.now() - buildStart;
|
|
85
|
+
console.log(`[PERF] [ExportSizes] buildPackage: ${timings.build.toFixed(2)}ms`);
|
|
69
86
|
telemetry_utils_1.default.packageExportsSizes(packageString, startTime, true, options);
|
|
70
87
|
return {
|
|
71
88
|
...builtDetails,
|
package/build/getPackageStats.js
CHANGED
|
@@ -40,9 +40,14 @@ function getPackageJSONDetails(packageName, installPath) {
|
|
|
40
40
|
}
|
|
41
41
|
async function getPackageStats(packageString, options = {}) {
|
|
42
42
|
const startTime = perf_hooks_1.performance.now();
|
|
43
|
+
const timings = {};
|
|
43
44
|
const { name: packageName, isLocal } = (0, common_utils_1.parsePackageString)(packageString);
|
|
45
|
+
const preparePathStart = perf_hooks_1.performance.now();
|
|
44
46
|
const installPath = await installation_utils_1.default.preparePath(packageName, options.client);
|
|
47
|
+
timings.preparePath = perf_hooks_1.performance.now() - preparePathStart;
|
|
48
|
+
console.log(`[PERF] preparePath: ${timings.preparePath.toFixed(2)}ms`);
|
|
45
49
|
try {
|
|
50
|
+
const installStart = perf_hooks_1.performance.now();
|
|
46
51
|
await installation_utils_1.default.installPackage(packageString, installPath, {
|
|
47
52
|
isLocal,
|
|
48
53
|
client: options.client,
|
|
@@ -50,7 +55,13 @@ async function getPackageStats(packageString, options = {}) {
|
|
|
50
55
|
networkConcurrency: options.networkConcurrency,
|
|
51
56
|
installTimeout: options.installTimeout,
|
|
52
57
|
});
|
|
58
|
+
timings.install = perf_hooks_1.performance.now() - installStart;
|
|
59
|
+
console.log(`[PERF] installPackage: ${timings.install.toFixed(2)}ms`);
|
|
60
|
+
const externalsStart = perf_hooks_1.performance.now();
|
|
53
61
|
const externals = (0, common_utils_1.getExternals)(packageName, installPath);
|
|
62
|
+
timings.getExternals = perf_hooks_1.performance.now() - externalsStart;
|
|
63
|
+
console.log(`[PERF] getExternals: ${timings.getExternals.toFixed(2)}ms`);
|
|
64
|
+
const parallelStart = perf_hooks_1.performance.now();
|
|
54
65
|
const [pacakgeJSONDetails, builtDetails] = await Promise.all([
|
|
55
66
|
getPackageJSONDetails(packageName, installPath),
|
|
56
67
|
build_utils_1.default.buildPackageIgnoringMissingDeps({
|
|
@@ -65,6 +76,8 @@ async function getPackageStats(packageString, options = {}) {
|
|
|
65
76
|
},
|
|
66
77
|
}),
|
|
67
78
|
]);
|
|
79
|
+
timings.parallelBuild = perf_hooks_1.performance.now() - parallelStart;
|
|
80
|
+
console.log(`[PERF] parallel (packageJSON + build): ${timings.parallelBuild.toFixed(2)}ms`);
|
|
68
81
|
const hasCSSAsset = builtDetails.assets.some(asset => asset.type === 'css');
|
|
69
82
|
const mainAsset = builtDetails.assets.find(asset => asset.name === 'main' && asset.type === (hasCSSAsset ? 'css' : 'js'));
|
|
70
83
|
if (!mainAsset) {
|
|
@@ -199,7 +199,11 @@ const BuildUtils = {
|
|
|
199
199
|
gzip,
|
|
200
200
|
};
|
|
201
201
|
};
|
|
202
|
-
const assetStatsPromises = ((_a = jsonStats === null || jsonStats === void 0 ? void 0 : jsonStats.assets) === null || _a === void 0 ? void 0 : _a.filter(asset => {
|
|
202
|
+
const assetStatsPromises = ((_a = jsonStats === null || jsonStats === void 0 ? void 0 : jsonStats.assets) === null || _a === void 0 ? void 0 : _a.filter(asset => {
|
|
203
|
+
var _a;
|
|
204
|
+
return !((_a = asset.chunkNames) === null || _a === void 0 ? void 0 : _a.some(name => name === 'runtime' ||
|
|
205
|
+
(typeof name === 'string' && name.startsWith('runtime~'))));
|
|
206
|
+
}).filter(asset => !asset.name.endsWith('LICENSE.txt')).map(getAssetStats)) || [];
|
|
203
207
|
const assetStats = await Promise.all(assetStatsPromises);
|
|
204
208
|
telemetry_utils_1.default.assetsGZIPParseTime(name, perf_hooks_1.performance.now());
|
|
205
209
|
let dependencySizeResults = {};
|
|
@@ -17,6 +17,7 @@ export declare function getAllExports(packageString: string, context: string, lo
|
|
|
17
17
|
* Get exports details from code (compatibility function)
|
|
18
18
|
*
|
|
19
19
|
* This provides the same API as the existing getExportsDetails for backward compatibility
|
|
20
|
+
* Returns simple string arrays for exports (without moduleRequest info)
|
|
20
21
|
*/
|
|
21
22
|
export declare function getExportsDetails(code: string, filename?: string): {
|
|
22
23
|
exports: string[];
|
|
@@ -18,9 +18,13 @@ const promises_1 = __importDefault(require("fs/promises"));
|
|
|
18
18
|
const telemetry_utils_1 = __importDefault(require("./telemetry.utils"));
|
|
19
19
|
const perf_hooks_1 = require("perf_hooks");
|
|
20
20
|
// Initialize resolver with ESM-first configuration
|
|
21
|
-
// Following oxc-linter's resolver configuration:
|
|
22
21
|
// - main_fields: ["module", "main"] - prioritize ESM entry points
|
|
23
|
-
// - condition_names: ["
|
|
22
|
+
// - condition_names: ["import", "default", "require"] - ESM-first export conditions
|
|
23
|
+
// NOTE: We intentionally exclude "node" because Node.js conditional exports resolution
|
|
24
|
+
// uses the PACKAGE's exports field order (not our conditionNames order) to determine
|
|
25
|
+
// priority. Packages like Vue have "node" before "import" in their exports, so including
|
|
26
|
+
// "node" would resolve to CJS files instead of ESM. We keep "require" as a fallback for
|
|
27
|
+
// packages that only export via "require" condition.
|
|
24
28
|
// - extensions: all common JS/TS extensions
|
|
25
29
|
// - symlinks: false - keep paths as-is without resolving symlinks (matches enhanced-resolve behavior)
|
|
26
30
|
const resolver = new oxc_resolver_1.ResolverFactory({
|
|
@@ -35,8 +39,8 @@ const resolver = new oxc_resolver_1.ResolverFactory({
|
|
|
35
39
|
'.cts',
|
|
36
40
|
'.json',
|
|
37
41
|
],
|
|
38
|
-
mainFields: ['module', 'main'], // ESM-first: prioritize
|
|
39
|
-
conditionNames: ['import', '
|
|
42
|
+
mainFields: ['module', 'main'], // ESM-first: prioritize "module" field over "main"
|
|
43
|
+
conditionNames: ['import', 'default', 'require'], // ESM-first: exclude "node" which resolves to CJS
|
|
40
44
|
symlinks: false, // Don't resolve symlinks to match enhanced-resolve behavior
|
|
41
45
|
});
|
|
42
46
|
/**
|
|
@@ -47,6 +51,7 @@ function getExportsFromStaticExports(staticExports) {
|
|
|
47
51
|
const exportAllLocations = [];
|
|
48
52
|
staticExports.forEach(staticExport => {
|
|
49
53
|
staticExport.entries.forEach((entry) => {
|
|
54
|
+
var _a, _b;
|
|
50
55
|
// Skip type-only exports (TypeScript type exports)
|
|
51
56
|
if (entry.isType) {
|
|
52
57
|
return;
|
|
@@ -63,10 +68,16 @@ function getExportsFromStaticExports(staticExports) {
|
|
|
63
68
|
case 'None': // export const foo = 1
|
|
64
69
|
// Get the export name
|
|
65
70
|
if (entry.exportName.kind === 'Name' && entry.exportName.name) {
|
|
66
|
-
exports.push(
|
|
71
|
+
exports.push({
|
|
72
|
+
name: entry.exportName.name,
|
|
73
|
+
moduleRequest: (_a = entry.moduleRequest) === null || _a === void 0 ? void 0 : _a.value, // Track the source module for re-exports
|
|
74
|
+
});
|
|
67
75
|
}
|
|
68
76
|
else if (entry.exportName.kind === 'Default') {
|
|
69
|
-
exports.push(
|
|
77
|
+
exports.push({
|
|
78
|
+
name: 'default',
|
|
79
|
+
moduleRequest: (_b = entry.moduleRequest) === null || _b === void 0 ? void 0 : _b.value,
|
|
80
|
+
});
|
|
70
81
|
}
|
|
71
82
|
break;
|
|
72
83
|
}
|
|
@@ -109,15 +120,26 @@ async function walkExportsRecursive(context, lookupPath, visited, rootContext, i
|
|
|
109
120
|
}
|
|
110
121
|
const { exports, exportAllLocations } = getExportsFromStaticExports(parseResult.module.staticExports);
|
|
111
122
|
const resolvedExports = {};
|
|
112
|
-
// Add direct exports from this module
|
|
113
|
-
|
|
114
|
-
|
|
123
|
+
// Add direct exports from this module, resolving re-exports to their source files
|
|
124
|
+
for (const exp of exports) {
|
|
125
|
+
let sourcePath = resolvedPath;
|
|
126
|
+
// If this is a re-export (export { foo } from './module'), resolve to the source file
|
|
127
|
+
if (exp.moduleRequest) {
|
|
128
|
+
try {
|
|
129
|
+
sourcePath = await resolveModule(path_1.default.dirname(resolvedPath), exp.moduleRequest);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// If resolution fails, fall back to current file
|
|
133
|
+
sourcePath = resolvedPath;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Use path.relative() to calculate the relative path from root to sourcePath
|
|
115
137
|
// This works correctly since symlinks are not resolved (symlinks: false in resolver config)
|
|
116
138
|
const relativePath = root
|
|
117
|
-
? path_1.default.relative(root,
|
|
118
|
-
: path_1.default.basename(
|
|
119
|
-
resolvedExports[exp] = relativePath;
|
|
120
|
-
}
|
|
139
|
+
? path_1.default.relative(root, sourcePath)
|
|
140
|
+
: path_1.default.basename(sourcePath);
|
|
141
|
+
resolvedExports[exp.name] = relativePath;
|
|
142
|
+
}
|
|
121
143
|
// Recursively process export * statements
|
|
122
144
|
const promises = exportAllLocations.map(async (location) => {
|
|
123
145
|
const starExports = await walkExportsRecursive(path_1.default.dirname(resolvedPath), location, visited, root);
|
|
@@ -162,10 +184,16 @@ async function getAllExports(packageString, context, lookupPath, installPath) {
|
|
|
162
184
|
* Get exports details from code (compatibility function)
|
|
163
185
|
*
|
|
164
186
|
* This provides the same API as the existing getExportsDetails for backward compatibility
|
|
187
|
+
* Returns simple string arrays for exports (without moduleRequest info)
|
|
165
188
|
*/
|
|
166
189
|
function getExportsDetails(code, filename = 'module.js') {
|
|
167
190
|
const parseResult = (0, oxc_parser_1.parseSync)(filename, code, {
|
|
168
191
|
sourceType: 'module',
|
|
169
192
|
});
|
|
170
|
-
|
|
193
|
+
const result = getExportsFromStaticExports(parseResult.module.staticExports);
|
|
194
|
+
// Return simple string array for backward compatibility
|
|
195
|
+
return {
|
|
196
|
+
exports: result.exports.map(exp => exp.name),
|
|
197
|
+
exportAllLocations: result.exportAllLocations,
|
|
198
|
+
};
|
|
171
199
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "package-build-stats",
|
|
3
|
-
"version": "8.0
|
|
3
|
+
"version": "8.1.0",
|
|
4
4
|
"author": "Shubham Kanodia <shubham.kanodia10@gmail.com>",
|
|
5
5
|
"repository": "https://github.com/pastelsky/package-build-stats",
|
|
6
6
|
"publishConfig": {
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"compare:test": "tsx scripts/index.ts test",
|
|
39
39
|
"compare:top": "tsx scripts/index.ts top",
|
|
40
40
|
"compare:advanced": "tsx scripts/index.ts compare",
|
|
41
|
+
"perf:test": "tsx scripts/performance-test.ts",
|
|
41
42
|
"prepublish": "rm -rf build && yarn check && tsc && yarn format"
|
|
42
43
|
},
|
|
43
44
|
"prettier": {
|
|
@@ -35,9 +35,15 @@ export default function makeRspackConfig({
|
|
|
35
35
|
mode: 'production',
|
|
36
36
|
devtool: _debug ? 'source-map' : false,
|
|
37
37
|
optimization: {
|
|
38
|
-
|
|
38
|
+
// Use 'multiple' to create a separate runtime chunk per entry point
|
|
39
|
+
// 'single' or { name: 'runtime' } shares one runtime across all entries,
|
|
40
|
+
// which breaks tree-shaking when building many exports simultaneously
|
|
41
|
+
runtimeChunk: 'multiple',
|
|
39
42
|
realContentHash: false,
|
|
40
43
|
minimize: minify,
|
|
44
|
+
// Enable tree-shaking optimizations
|
|
45
|
+
usedExports: true, // Mark unused exports for removal
|
|
46
|
+
sideEffects: true, // Respect package.json sideEffects field
|
|
41
47
|
// Rspack automatically uses its built-in default minifiers:
|
|
42
48
|
// - SwcJsMinimizerRspackPlugin for JS (SWC-based, very fast)
|
|
43
49
|
// - LightningCssMinimizerRspackPlugin for CSS (Lightning CSS-based)
|
|
@@ -62,27 +62,53 @@ export async function getPackageExportSizes(
|
|
|
62
62
|
options: GetPackageStatsOptions = {},
|
|
63
63
|
) {
|
|
64
64
|
const startTime = performance.now()
|
|
65
|
+
const timings: Record<string, number> = {}
|
|
66
|
+
|
|
65
67
|
const { name: packageName, normalPath } = parsePackageString(packageString)
|
|
68
|
+
|
|
69
|
+
const preparePathStart = performance.now()
|
|
66
70
|
const installPath = await InstallationUtils.preparePath(packageName)
|
|
71
|
+
timings.preparePath = performance.now() - preparePathStart
|
|
72
|
+
console.log(
|
|
73
|
+
`[PERF] [ExportSizes] preparePath: ${timings.preparePath.toFixed(2)}ms`,
|
|
74
|
+
)
|
|
67
75
|
|
|
68
76
|
try {
|
|
77
|
+
const installStart = performance.now()
|
|
69
78
|
await installPackage(packageString, installPath, options)
|
|
79
|
+
timings.install = performance.now() - installStart
|
|
80
|
+
console.log(
|
|
81
|
+
`[PERF] [ExportSizes] installPackage: ${timings.install.toFixed(2)}ms`,
|
|
82
|
+
)
|
|
70
83
|
|
|
71
84
|
// The package is installed in node_modules subdirectory
|
|
72
85
|
const packagePath =
|
|
73
86
|
normalPath || path.join(installPath, 'node_modules', packageName)
|
|
87
|
+
|
|
88
|
+
const getAllExportsStart = performance.now()
|
|
74
89
|
const exportMap = await getAllExports(
|
|
75
90
|
packageString,
|
|
76
91
|
packagePath,
|
|
77
92
|
packageName,
|
|
78
93
|
installPath, // Pass installPath as base for relative path calculation
|
|
79
94
|
)
|
|
95
|
+
timings.getAllExports = performance.now() - getAllExportsStart
|
|
96
|
+
console.log(
|
|
97
|
+
`[PERF] [ExportSizes] getAllExports: ${timings.getAllExports.toFixed(2)}ms`,
|
|
98
|
+
)
|
|
80
99
|
|
|
81
100
|
const exports = Object.keys(exportMap).filter(exp => !(exp === 'default'))
|
|
82
101
|
debug('Got %d exports for %s', exports.length, packageString)
|
|
102
|
+
console.log(`[PERF] [ExportSizes] Found ${exports.length} exports`)
|
|
83
103
|
|
|
104
|
+
const externalsStart = performance.now()
|
|
84
105
|
const externals = getExternals(packageName, installPath)
|
|
106
|
+
timings.getExternals = performance.now() - externalsStart
|
|
107
|
+
console.log(
|
|
108
|
+
`[PERF] [ExportSizes] getExternals: ${timings.getExternals.toFixed(2)}ms`,
|
|
109
|
+
)
|
|
85
110
|
|
|
111
|
+
const buildStart = performance.now()
|
|
86
112
|
const builtDetails = await BuildUtils.buildPackageIgnoringMissingDeps({
|
|
87
113
|
name: packageName,
|
|
88
114
|
installPath,
|
|
@@ -93,6 +119,10 @@ export async function getPackageExportSizes(
|
|
|
93
119
|
includeDependencySizes: false,
|
|
94
120
|
},
|
|
95
121
|
})
|
|
122
|
+
timings.build = performance.now() - buildStart
|
|
123
|
+
console.log(
|
|
124
|
+
`[PERF] [ExportSizes] buildPackage: ${timings.build.toFixed(2)}ms`,
|
|
125
|
+
)
|
|
96
126
|
|
|
97
127
|
Telemetry.packageExportsSizes(packageString, startTime, true, options)
|
|
98
128
|
return {
|
package/src/getPackageStats.ts
CHANGED
|
@@ -53,14 +53,20 @@ export default async function getPackageStats(
|
|
|
53
53
|
options: GetPackageStatsOptions = {},
|
|
54
54
|
) {
|
|
55
55
|
const startTime = performance.now()
|
|
56
|
+
const timings: Record<string, number> = {}
|
|
56
57
|
|
|
57
58
|
const { name: packageName, isLocal } = parsePackageString(packageString)
|
|
58
59
|
|
|
60
|
+
const preparePathStart = performance.now()
|
|
59
61
|
const installPath = await InstallationUtils.preparePath(
|
|
60
62
|
packageName,
|
|
61
63
|
options.client,
|
|
62
64
|
)
|
|
65
|
+
timings.preparePath = performance.now() - preparePathStart
|
|
66
|
+
console.log(`[PERF] preparePath: ${timings.preparePath.toFixed(2)}ms`)
|
|
67
|
+
|
|
63
68
|
try {
|
|
69
|
+
const installStart = performance.now()
|
|
64
70
|
await InstallationUtils.installPackage(packageString, installPath, {
|
|
65
71
|
isLocal,
|
|
66
72
|
client: options.client,
|
|
@@ -68,9 +74,15 @@ export default async function getPackageStats(
|
|
|
68
74
|
networkConcurrency: options.networkConcurrency,
|
|
69
75
|
installTimeout: options.installTimeout,
|
|
70
76
|
})
|
|
77
|
+
timings.install = performance.now() - installStart
|
|
78
|
+
console.log(`[PERF] installPackage: ${timings.install.toFixed(2)}ms`)
|
|
71
79
|
|
|
80
|
+
const externalsStart = performance.now()
|
|
72
81
|
const externals = getExternals(packageName, installPath)
|
|
82
|
+
timings.getExternals = performance.now() - externalsStart
|
|
83
|
+
console.log(`[PERF] getExternals: ${timings.getExternals.toFixed(2)}ms`)
|
|
73
84
|
|
|
85
|
+
const parallelStart = performance.now()
|
|
74
86
|
const [pacakgeJSONDetails, builtDetails] = await Promise.all([
|
|
75
87
|
getPackageJSONDetails(packageName, installPath),
|
|
76
88
|
BuildUtils.buildPackageIgnoringMissingDeps({
|
|
@@ -85,6 +97,10 @@ export default async function getPackageStats(
|
|
|
85
97
|
},
|
|
86
98
|
}),
|
|
87
99
|
])
|
|
100
|
+
timings.parallelBuild = performance.now() - parallelStart
|
|
101
|
+
console.log(
|
|
102
|
+
`[PERF] parallel (packageJSON + build): ${timings.parallelBuild.toFixed(2)}ms`,
|
|
103
|
+
)
|
|
88
104
|
|
|
89
105
|
const hasCSSAsset = builtDetails.assets.some(asset => asset.type === 'css')
|
|
90
106
|
const mainAsset = builtDetails.assets.find(
|
package/src/utils/build.utils.ts
CHANGED
|
@@ -306,7 +306,14 @@ const BuildUtils = {
|
|
|
306
306
|
|
|
307
307
|
const assetStatsPromises =
|
|
308
308
|
jsonStats?.assets
|
|
309
|
-
?.filter(
|
|
309
|
+
?.filter(
|
|
310
|
+
asset =>
|
|
311
|
+
!asset.chunkNames?.some(
|
|
312
|
+
name =>
|
|
313
|
+
name === 'runtime' ||
|
|
314
|
+
(typeof name === 'string' && name.startsWith('runtime~')),
|
|
315
|
+
),
|
|
316
|
+
)
|
|
310
317
|
.filter(asset => !asset.name.endsWith('LICENSE.txt'))
|
|
311
318
|
.map(getAssetStats) || []
|
|
312
319
|
const assetStats = await Promise.all(assetStatsPromises)
|
|
@@ -13,9 +13,13 @@ import Telemetry from './telemetry.utils'
|
|
|
13
13
|
import { performance } from 'perf_hooks'
|
|
14
14
|
|
|
15
15
|
// Initialize resolver with ESM-first configuration
|
|
16
|
-
// Following oxc-linter's resolver configuration:
|
|
17
16
|
// - main_fields: ["module", "main"] - prioritize ESM entry points
|
|
18
|
-
// - condition_names: ["
|
|
17
|
+
// - condition_names: ["import", "default", "require"] - ESM-first export conditions
|
|
18
|
+
// NOTE: We intentionally exclude "node" because Node.js conditional exports resolution
|
|
19
|
+
// uses the PACKAGE's exports field order (not our conditionNames order) to determine
|
|
20
|
+
// priority. Packages like Vue have "node" before "import" in their exports, so including
|
|
21
|
+
// "node" would resolve to CJS files instead of ESM. We keep "require" as a fallback for
|
|
22
|
+
// packages that only export via "require" condition.
|
|
19
23
|
// - extensions: all common JS/TS extensions
|
|
20
24
|
// - symlinks: false - keep paths as-is without resolving symlinks (matches enhanced-resolve behavior)
|
|
21
25
|
const resolver = new ResolverFactory({
|
|
@@ -30,19 +34,27 @@ const resolver = new ResolverFactory({
|
|
|
30
34
|
'.cts',
|
|
31
35
|
'.json',
|
|
32
36
|
],
|
|
33
|
-
mainFields: ['module', 'main'], // ESM-first: prioritize
|
|
34
|
-
conditionNames: ['import', '
|
|
37
|
+
mainFields: ['module', 'main'], // ESM-first: prioritize "module" field over "main"
|
|
38
|
+
conditionNames: ['import', 'default', 'require'], // ESM-first: exclude "node" which resolves to CJS
|
|
35
39
|
symlinks: false, // Don't resolve symlinks to match enhanced-resolve behavior
|
|
36
40
|
})
|
|
37
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Represents a named export with its optional source module
|
|
44
|
+
*/
|
|
45
|
+
type NamedExport = {
|
|
46
|
+
name: string
|
|
47
|
+
moduleRequest?: string // The source module for re-exports like `export { foo } from './module'`
|
|
48
|
+
}
|
|
49
|
+
|
|
38
50
|
/**
|
|
39
51
|
* Extract export information from parsed oxc module
|
|
40
52
|
*/
|
|
41
53
|
function getExportsFromStaticExports(staticExports: StaticExport[]): {
|
|
42
|
-
exports:
|
|
54
|
+
exports: NamedExport[]
|
|
43
55
|
exportAllLocations: string[]
|
|
44
56
|
} {
|
|
45
|
-
const exports:
|
|
57
|
+
const exports: NamedExport[] = []
|
|
46
58
|
const exportAllLocations: string[] = []
|
|
47
59
|
|
|
48
60
|
staticExports.forEach(staticExport => {
|
|
@@ -65,9 +77,15 @@ function getExportsFromStaticExports(staticExports: StaticExport[]): {
|
|
|
65
77
|
case 'None': // export const foo = 1
|
|
66
78
|
// Get the export name
|
|
67
79
|
if (entry.exportName.kind === 'Name' && entry.exportName.name) {
|
|
68
|
-
exports.push(
|
|
80
|
+
exports.push({
|
|
81
|
+
name: entry.exportName.name,
|
|
82
|
+
moduleRequest: entry.moduleRequest?.value, // Track the source module for re-exports
|
|
83
|
+
})
|
|
69
84
|
} else if (entry.exportName.kind === 'Default') {
|
|
70
|
-
exports.push(
|
|
85
|
+
exports.push({
|
|
86
|
+
name: 'default',
|
|
87
|
+
moduleRequest: entry.moduleRequest?.value,
|
|
88
|
+
})
|
|
71
89
|
}
|
|
72
90
|
break
|
|
73
91
|
}
|
|
@@ -134,16 +152,31 @@ async function walkExportsRecursive(
|
|
|
134
152
|
|
|
135
153
|
const resolvedExports: ResolvedExports = {}
|
|
136
154
|
|
|
137
|
-
// Add direct exports from this module
|
|
138
|
-
|
|
139
|
-
|
|
155
|
+
// Add direct exports from this module, resolving re-exports to their source files
|
|
156
|
+
for (const exp of exports) {
|
|
157
|
+
let sourcePath = resolvedPath
|
|
158
|
+
|
|
159
|
+
// If this is a re-export (export { foo } from './module'), resolve to the source file
|
|
160
|
+
if (exp.moduleRequest) {
|
|
161
|
+
try {
|
|
162
|
+
sourcePath = await resolveModule(
|
|
163
|
+
path.dirname(resolvedPath),
|
|
164
|
+
exp.moduleRequest,
|
|
165
|
+
)
|
|
166
|
+
} catch {
|
|
167
|
+
// If resolution fails, fall back to current file
|
|
168
|
+
sourcePath = resolvedPath
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Use path.relative() to calculate the relative path from root to sourcePath
|
|
140
173
|
// This works correctly since symlinks are not resolved (symlinks: false in resolver config)
|
|
141
174
|
const relativePath = root
|
|
142
|
-
? path.relative(root,
|
|
143
|
-
: path.basename(
|
|
175
|
+
? path.relative(root, sourcePath)
|
|
176
|
+
: path.basename(sourcePath)
|
|
144
177
|
|
|
145
|
-
resolvedExports[exp] = relativePath
|
|
146
|
-
}
|
|
178
|
+
resolvedExports[exp.name] = relativePath
|
|
179
|
+
}
|
|
147
180
|
|
|
148
181
|
// Recursively process export * statements
|
|
149
182
|
const promises = exportAllLocations.map(async location => {
|
|
@@ -210,11 +243,18 @@ export async function getAllExports(
|
|
|
210
243
|
* Get exports details from code (compatibility function)
|
|
211
244
|
*
|
|
212
245
|
* This provides the same API as the existing getExportsDetails for backward compatibility
|
|
246
|
+
* Returns simple string arrays for exports (without moduleRequest info)
|
|
213
247
|
*/
|
|
214
248
|
export function getExportsDetails(code: string, filename = 'module.js') {
|
|
215
249
|
const parseResult = parseSync(filename, code, {
|
|
216
250
|
sourceType: 'module',
|
|
217
251
|
})
|
|
218
252
|
|
|
219
|
-
|
|
253
|
+
const result = getExportsFromStaticExports(parseResult.module.staticExports)
|
|
254
|
+
|
|
255
|
+
// Return simple string array for backward compatibility
|
|
256
|
+
return {
|
|
257
|
+
exports: result.exports.map(exp => exp.name),
|
|
258
|
+
exportAllLocations: result.exportAllLocations,
|
|
259
|
+
}
|
|
220
260
|
}
|