@node-cli/bundlecheck 1.2.0 → 1.4.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/README.md +287 -11
- package/dist/bundlecheck.js +82 -32
- package/dist/bundlecheck.js.map +1 -1
- package/dist/bundler.d.ts +17 -4
- package/dist/bundler.js +113 -91
- package/dist/bundler.js.map +1 -1
- package/dist/cache.d.ts +62 -0
- package/dist/cache.js +196 -0
- package/dist/cache.js.map +1 -0
- package/dist/defaults.d.ts +13 -0
- package/dist/defaults.js +65 -1
- package/dist/defaults.js.map +1 -1
- package/dist/index.d.ts +1 -6
- package/dist/index.js +229 -0
- package/dist/index.js.map +1 -0
- package/dist/parse.d.ts +2 -0
- package/dist/parse.js +26 -1
- package/dist/parse.js.map +1 -1
- package/dist/trend.d.ts +17 -6
- package/dist/trend.js +95 -45
- package/dist/trend.js.map +1 -1
- package/dist/versions.d.ts +1 -1
- package/dist/versions.js +12 -10
- package/dist/versions.js.map +1 -1
- package/package.json +6 -3
package/dist/bundler.js
CHANGED
|
@@ -8,12 +8,12 @@ import * as esbuild from "esbuild";
|
|
|
8
8
|
import { DEFAULT_EXTERNALS } from "./defaults.js";
|
|
9
9
|
const gzipAsync = promisify(zlib.gzip);
|
|
10
10
|
/**
|
|
11
|
-
* Escape special regex characters in a string
|
|
11
|
+
* Escape special regex characters in a string.
|
|
12
12
|
*/ function escapeRegExp(str) {
|
|
13
13
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
* Parse a package specifier to extract name, version, and subpath
|
|
16
|
+
* Parse a package specifier to extract name, version, and subpath.
|
|
17
17
|
* Handles:
|
|
18
18
|
* - @scope/package@1.0.0
|
|
19
19
|
* - @scope/package/subpath@1.0.0
|
|
@@ -24,17 +24,18 @@ const gzipAsync = promisify(zlib.gzip);
|
|
|
24
24
|
let version = "latest";
|
|
25
25
|
// Handle scoped packages (@scope/name...)
|
|
26
26
|
if (workingSpec.startsWith("@")) {
|
|
27
|
-
// Find the second @ which would separate version
|
|
27
|
+
// Find the second @ which would separate version.
|
|
28
28
|
const secondAtIndex = workingSpec.indexOf("@", 1);
|
|
29
29
|
if (secondAtIndex !== -1) {
|
|
30
30
|
version = workingSpec.substring(secondAtIndex + 1);
|
|
31
31
|
workingSpec = workingSpec.substring(0, secondAtIndex);
|
|
32
32
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Now workingSpec is like @scope/name or @scope/name/subpath Split by / and
|
|
35
|
+
* check if there are more than 2 parts.
|
|
36
|
+
*/ const parts = workingSpec.split("/");
|
|
36
37
|
if (parts.length > 2) {
|
|
37
|
-
// Has subpath: @scope/name/subpath/more
|
|
38
|
+
// Has subpath: @scope/name/subpath/more.
|
|
38
39
|
const name = `${parts[0]}/${parts[1]}`;
|
|
39
40
|
const subpath = parts.slice(2).join("/");
|
|
40
41
|
return {
|
|
@@ -43,19 +44,19 @@ const gzipAsync = promisify(zlib.gzip);
|
|
|
43
44
|
subpath
|
|
44
45
|
};
|
|
45
46
|
}
|
|
46
|
-
// No subpath: @scope/name
|
|
47
|
+
// No subpath: @scope/name.
|
|
47
48
|
return {
|
|
48
49
|
name: workingSpec,
|
|
49
50
|
version
|
|
50
51
|
};
|
|
51
52
|
}
|
|
52
|
-
// Handle non-scoped packages (name@version or name/subpath@version)
|
|
53
|
+
// Handle non-scoped packages (name@version or name/subpath@version).
|
|
53
54
|
const atIndex = workingSpec.indexOf("@");
|
|
54
55
|
if (atIndex !== -1) {
|
|
55
56
|
version = workingSpec.substring(atIndex + 1);
|
|
56
57
|
workingSpec = workingSpec.substring(0, atIndex);
|
|
57
58
|
}
|
|
58
|
-
// Check for subpath in non-scoped packages
|
|
59
|
+
// Check for subpath in non-scoped packages.
|
|
59
60
|
const slashIndex = workingSpec.indexOf("/");
|
|
60
61
|
if (slashIndex !== -1) {
|
|
61
62
|
const name = workingSpec.substring(0, slashIndex);
|
|
@@ -72,7 +73,7 @@ const gzipAsync = promisify(zlib.gzip);
|
|
|
72
73
|
};
|
|
73
74
|
}
|
|
74
75
|
/**
|
|
75
|
-
* Format bytes to human-readable string
|
|
76
|
+
* Format bytes to human-readable string.
|
|
76
77
|
*/ export function formatBytes(bytes) {
|
|
77
78
|
if (bytes === 0) {
|
|
78
79
|
return "0 B";
|
|
@@ -88,7 +89,7 @@ const gzipAsync = promisify(zlib.gzip);
|
|
|
88
89
|
return `${Number.parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`;
|
|
89
90
|
}
|
|
90
91
|
/**
|
|
91
|
-
* Create a temporary directory for bundling
|
|
92
|
+
* Create a temporary directory for bundling.
|
|
92
93
|
*/ function createTempDir() {
|
|
93
94
|
const tmpDir = path.join(os.tmpdir(), `bundlecheck-${Date.now()}`);
|
|
94
95
|
fs.mkdirSync(tmpDir, {
|
|
@@ -97,7 +98,7 @@ const gzipAsync = promisify(zlib.gzip);
|
|
|
97
98
|
return tmpDir;
|
|
98
99
|
}
|
|
99
100
|
/**
|
|
100
|
-
* Clean up temporary directory
|
|
101
|
+
* Clean up temporary directory.
|
|
101
102
|
*/ function cleanupTempDir(tmpDir) {
|
|
102
103
|
try {
|
|
103
104
|
fs.rmSync(tmpDir, {
|
|
@@ -105,11 +106,11 @@ const gzipAsync = promisify(zlib.gzip);
|
|
|
105
106
|
force: true
|
|
106
107
|
});
|
|
107
108
|
} catch {
|
|
108
|
-
// Ignore cleanup errors
|
|
109
|
+
// Ignore cleanup errors.
|
|
109
110
|
}
|
|
110
111
|
}
|
|
111
112
|
/**
|
|
112
|
-
* Check if pnpm is available
|
|
113
|
+
* Check if pnpm is available.
|
|
113
114
|
*/ function isPnpmAvailable() {
|
|
114
115
|
try {
|
|
115
116
|
execSync("pnpm --version", {
|
|
@@ -120,30 +121,30 @@ const gzipAsync = promisify(zlib.gzip);
|
|
|
120
121
|
return false;
|
|
121
122
|
}
|
|
122
123
|
}
|
|
123
|
-
// Cache the result of pnpm availability check
|
|
124
|
+
// Cache the result of pnpm availability check.
|
|
124
125
|
let usePnpm = null;
|
|
125
126
|
/**
|
|
126
|
-
* Validate and sanitize a registry URL to prevent command injection
|
|
127
|
+
* Validate and sanitize a registry URL to prevent command injection.
|
|
127
128
|
* @param registry - The registry URL to validate
|
|
128
129
|
* @returns The sanitized URL or undefined if invalid
|
|
129
130
|
* @throws Error if the URL is invalid or contains potentially malicious characters
|
|
130
131
|
*/ function validateRegistryUrl(registry) {
|
|
131
|
-
// Parse as URL to validate format
|
|
132
|
+
// Parse as URL to validate format.
|
|
132
133
|
let url;
|
|
133
134
|
try {
|
|
134
135
|
url = new URL(registry);
|
|
135
136
|
} catch {
|
|
136
137
|
throw new Error(`Invalid registry URL: ${registry}. Must be a valid URL (e.g., https://registry.example.com)`);
|
|
137
138
|
}
|
|
138
|
-
// Only allow http and https protocols
|
|
139
|
+
// Only allow http and https protocols.
|
|
139
140
|
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
140
141
|
throw new Error(`Invalid registry URL protocol: ${url.protocol}. Only http: and https: are allowed`);
|
|
141
142
|
}
|
|
142
|
-
// Return the sanitized URL (URL constructor normalizes it)
|
|
143
|
+
// Return the sanitized URL (URL constructor normalizes it).
|
|
143
144
|
return url.toString();
|
|
144
145
|
}
|
|
145
146
|
/**
|
|
146
|
-
* Get the install command (pnpm preferred, npm fallback)
|
|
147
|
+
* Get the install command (pnpm preferred, npm fallback).
|
|
147
148
|
* @param registry - Optional custom npm registry URL
|
|
148
149
|
*/ function getInstallCommand(registry) {
|
|
149
150
|
if (usePnpm === null) {
|
|
@@ -151,9 +152,9 @@ let usePnpm = null;
|
|
|
151
152
|
}
|
|
152
153
|
let registryArg = "";
|
|
153
154
|
if (registry) {
|
|
154
|
-
// Validate and sanitize the registry URL to prevent command injection
|
|
155
|
+
// Validate and sanitize the registry URL to prevent command injection.
|
|
155
156
|
const sanitizedRegistry = validateRegistryUrl(registry);
|
|
156
|
-
// Quote the URL to handle any special characters safely
|
|
157
|
+
// Quote the URL to handle any special characters safely.
|
|
157
158
|
registryArg = ` --registry "${sanitizedRegistry}"`;
|
|
158
159
|
}
|
|
159
160
|
if (usePnpm) {
|
|
@@ -162,19 +163,19 @@ let usePnpm = null;
|
|
|
162
163
|
return `npm install --legacy-peer-deps --ignore-scripts${registryArg}`;
|
|
163
164
|
}
|
|
164
165
|
/**
|
|
165
|
-
* Generate the entry file content based on package, subpath, and exports
|
|
166
|
+
* Generate the entry file content based on package, subpath, and exports.
|
|
166
167
|
*/ function generateEntryContent(options) {
|
|
167
168
|
const { packageName, subpath, exports, allSubpaths, exportToSubpath } = options;
|
|
168
|
-
// If we have exports mapped to different subpaths
|
|
169
|
+
// If we have exports mapped to different subpaths.
|
|
169
170
|
if (exportToSubpath && exportToSubpath.size > 0) {
|
|
170
|
-
// Group exports by subpath
|
|
171
|
+
// Group exports by subpath.
|
|
171
172
|
const subpathToExports = new Map();
|
|
172
173
|
for (const [exportName, sp] of exportToSubpath){
|
|
173
174
|
const existing = subpathToExports.get(sp) || [];
|
|
174
175
|
existing.push(exportName);
|
|
175
176
|
subpathToExports.set(sp, existing);
|
|
176
177
|
}
|
|
177
|
-
// Generate imports for each subpath
|
|
178
|
+
// Generate imports for each subpath.
|
|
178
179
|
const lines = [];
|
|
179
180
|
const allExportNames = [];
|
|
180
181
|
for (const [sp, exportNames] of subpathToExports){
|
|
@@ -186,43 +187,43 @@ let usePnpm = null;
|
|
|
186
187
|
lines.push(`export { ${allExportNames.join(", ")} };`);
|
|
187
188
|
return lines.join("\n") + "\n";
|
|
188
189
|
}
|
|
189
|
-
// If we have specific exports to import
|
|
190
|
+
// If we have specific exports to import.
|
|
190
191
|
if (exports && exports.length > 0) {
|
|
191
|
-
// Determine the import path
|
|
192
|
+
// Determine the import path.
|
|
192
193
|
const importPath = subpath ? `${packageName}/${subpath}` : packageName;
|
|
193
194
|
const importNames = exports.join(", ");
|
|
194
195
|
return `import { ${importNames} } from "${importPath}";\nexport { ${importNames} };\n`;
|
|
195
196
|
}
|
|
196
|
-
// If we have a specific subpath (but no specific exports)
|
|
197
|
+
// If we have a specific subpath (but no specific exports).
|
|
197
198
|
if (subpath) {
|
|
198
199
|
const importPath = `${packageName}/${subpath}`;
|
|
199
200
|
return `import * as pkg from "${importPath}";\nexport default pkg;\n`;
|
|
200
201
|
}
|
|
201
|
-
// If package has subpath exports only (no main entry), import all subpaths
|
|
202
|
+
// If package has subpath exports only (no main entry), import all subpaths.
|
|
202
203
|
if (allSubpaths && allSubpaths.length > 0) {
|
|
203
204
|
const imports = allSubpaths.map((sp, i)=>`import * as sub${i} from "${packageName}/${sp}";\nexport { sub${i} };`).join("\n");
|
|
204
205
|
return imports + "\n";
|
|
205
206
|
}
|
|
206
|
-
// Default: import everything from main entry
|
|
207
|
+
// Default: import everything from main entry.
|
|
207
208
|
return `import * as pkg from "${packageName}";\nexport default pkg;\n`;
|
|
208
209
|
}
|
|
209
210
|
/**
|
|
210
|
-
* Get externals list based on options
|
|
211
|
-
*/ function getExternals(packageName, externals, noExternal) {
|
|
211
|
+
* Get externals list based on options.
|
|
212
|
+
*/ export function getExternals(packageName, externals, noExternal) {
|
|
212
213
|
if (noExternal) {
|
|
213
214
|
return [];
|
|
214
215
|
}
|
|
215
|
-
// Start with default externals (react, react-dom)
|
|
216
|
+
// Start with default externals (react, react-dom).
|
|
216
217
|
let result = [
|
|
217
218
|
...DEFAULT_EXTERNALS
|
|
218
219
|
];
|
|
219
|
-
// If checking react or react-dom themselves, don't mark them as external
|
|
220
|
+
// If checking react or react-dom themselves, don't mark them as external.
|
|
220
221
|
if (packageName === "react") {
|
|
221
222
|
result = result.filter((e)=>e !== "react");
|
|
222
223
|
} else if (packageName === "react-dom") {
|
|
223
224
|
result = result.filter((e)=>e !== "react-dom");
|
|
224
225
|
}
|
|
225
|
-
// Add any additional externals
|
|
226
|
+
// Add any additional externals.
|
|
226
227
|
if (externals && externals.length > 0) {
|
|
227
228
|
result = [
|
|
228
229
|
...new Set([
|
|
@@ -234,48 +235,51 @@ let usePnpm = null;
|
|
|
234
235
|
return result;
|
|
235
236
|
}
|
|
236
237
|
/**
|
|
237
|
-
* Get version, dependencies, peer dependencies, and exports from an installed
|
|
238
|
+
* Get version, dependencies, peer dependencies, and exports from an installed
|
|
239
|
+
* package.
|
|
238
240
|
*/ function getPackageInfo(tmpDir, packageName) {
|
|
239
241
|
try {
|
|
240
|
-
// Handle scoped packages - the package name in node_modules
|
|
242
|
+
// Handle scoped packages - the package name in node_modules.
|
|
241
243
|
const pkgJsonPath = path.join(tmpDir, "node_modules", packageName, "package.json");
|
|
242
244
|
if (fs.existsSync(pkgJsonPath)) {
|
|
243
245
|
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
|
|
244
|
-
// Check if package has a main entry point
|
|
246
|
+
// Check if package has a main entry point.
|
|
245
247
|
const hasMainEntry = Boolean(pkgJson.main || pkgJson.module || pkgJson.exports?.["."] || pkgJson.exports?.["./index"] || !pkgJson.exports && !pkgJson.main && !pkgJson.module);
|
|
246
248
|
return {
|
|
247
249
|
version: pkgJson.version || "unknown",
|
|
248
250
|
dependencies: pkgJson.dependencies || {},
|
|
249
251
|
peerDependencies: pkgJson.peerDependencies || {},
|
|
250
252
|
exports: pkgJson.exports || null,
|
|
251
|
-
hasMainEntry
|
|
253
|
+
hasMainEntry,
|
|
254
|
+
engines: pkgJson.engines || null
|
|
252
255
|
};
|
|
253
256
|
}
|
|
254
257
|
} catch {
|
|
255
|
-
// Ignore errors reading package info
|
|
258
|
+
// Ignore errors reading package info.
|
|
256
259
|
}
|
|
257
260
|
return {
|
|
258
261
|
version: "unknown",
|
|
259
262
|
dependencies: {},
|
|
260
263
|
peerDependencies: {},
|
|
261
264
|
exports: null,
|
|
262
|
-
hasMainEntry: true
|
|
265
|
+
hasMainEntry: true,
|
|
266
|
+
engines: null
|
|
263
267
|
};
|
|
264
268
|
}
|
|
265
269
|
/**
|
|
266
|
-
* Extract subpath export names from package exports field
|
|
267
|
-
*
|
|
270
|
+
* Extract subpath export names from package exports field Returns array of
|
|
271
|
+
* subpaths like ["header", "body", "datagrid"].
|
|
268
272
|
*/ function getSubpathExports(exports) {
|
|
269
273
|
if (!exports) {
|
|
270
274
|
return [];
|
|
271
275
|
}
|
|
272
276
|
const subpaths = [];
|
|
273
277
|
for (const key of Object.keys(exports)){
|
|
274
|
-
// Skip the main entry point and package.json
|
|
278
|
+
// Skip the main entry point and package.json.
|
|
275
279
|
if (key === "." || key === "./package.json") {
|
|
276
280
|
continue;
|
|
277
281
|
}
|
|
278
|
-
// Remove leading "./" to get subpath name
|
|
282
|
+
// Remove leading "./" to get subpath name.
|
|
279
283
|
if (key.startsWith("./")) {
|
|
280
284
|
subpaths.push(key.substring(2));
|
|
281
285
|
}
|
|
@@ -283,20 +287,20 @@ let usePnpm = null;
|
|
|
283
287
|
return subpaths;
|
|
284
288
|
}
|
|
285
289
|
/**
|
|
286
|
-
* Find which subpath(s) export the given component names
|
|
287
|
-
*
|
|
290
|
+
* Find which subpath(s) export the given component names Reads type definition
|
|
291
|
+
* files or JS files to find the exports.
|
|
288
292
|
*/ function findSubpathsForExports(tmpDir, packageName, exports, componentNames) {
|
|
289
293
|
const packageDir = path.join(tmpDir, "node_modules", packageName);
|
|
290
294
|
const exportToSubpath = new Map();
|
|
291
295
|
for (const [subpathKey, subpathValue] of Object.entries(exports)){
|
|
292
|
-
// Skip main entry and package.json
|
|
296
|
+
// Skip main entry and package.json.
|
|
293
297
|
if (subpathKey === "." || subpathKey === "./package.json") {
|
|
294
298
|
continue;
|
|
295
299
|
}
|
|
296
|
-
// Get the types or import path
|
|
300
|
+
// Get the types or import path.
|
|
297
301
|
let filePath;
|
|
298
302
|
if (typeof subpathValue === "object" && subpathValue !== null) {
|
|
299
|
-
// Prefer types file for more accurate export detection
|
|
303
|
+
// Prefer types file for more accurate export detection.
|
|
300
304
|
filePath = subpathValue.types || subpathValue.import;
|
|
301
305
|
} else if (typeof subpathValue === "string") {
|
|
302
306
|
filePath = subpathValue;
|
|
@@ -304,21 +308,21 @@ let usePnpm = null;
|
|
|
304
308
|
if (!filePath) {
|
|
305
309
|
continue;
|
|
306
310
|
}
|
|
307
|
-
// Resolve the file path
|
|
311
|
+
// Resolve the file path.
|
|
308
312
|
const fullPath = path.join(packageDir, filePath);
|
|
309
313
|
try {
|
|
310
314
|
if (fs.existsSync(fullPath)) {
|
|
311
315
|
const content = fs.readFileSync(fullPath, "utf-8");
|
|
312
316
|
const subpath = subpathKey.startsWith("./") ? subpathKey.substring(2) : subpathKey;
|
|
313
|
-
// Check each component name
|
|
317
|
+
// Check each component name.
|
|
314
318
|
for (const name of componentNames){
|
|
315
|
-
// Skip if already found
|
|
319
|
+
// Skip if already found.
|
|
316
320
|
if (exportToSubpath.has(name)) {
|
|
317
321
|
continue;
|
|
318
322
|
}
|
|
319
|
-
// Escape regex special characters in the name to prevent injection
|
|
323
|
+
// Escape regex special characters in the name to prevent injection.
|
|
320
324
|
const escapedName = escapeRegExp(name);
|
|
321
|
-
// Look for various export patterns
|
|
325
|
+
// Look for various export patterns.
|
|
322
326
|
const patterns = [
|
|
323
327
|
new RegExp(`export\\s*\\{[^}]*\\b${escapedName}\\b[^}]*\\}`, "m"),
|
|
324
328
|
new RegExp(`export\\s+declare\\s+(?:const|function|class)\\s+${escapedName}\\b`, "m"),
|
|
@@ -330,14 +334,14 @@ let usePnpm = null;
|
|
|
330
334
|
}
|
|
331
335
|
}
|
|
332
336
|
} catch {
|
|
333
|
-
// Ignore read errors, continue to next subpath
|
|
337
|
+
// Ignore read errors, continue to next subpath.
|
|
334
338
|
}
|
|
335
339
|
}
|
|
336
|
-
// Check if all exports were found
|
|
340
|
+
// Check if all exports were found.
|
|
337
341
|
if (exportToSubpath.size !== componentNames.length) {
|
|
338
342
|
return {}; // Not all exports found
|
|
339
343
|
}
|
|
340
|
-
// Check if all exports are from the same subpath
|
|
344
|
+
// Check if all exports are from the same subpath.
|
|
341
345
|
const subpaths = new Set(exportToSubpath.values());
|
|
342
346
|
if (subpaths.size === 1) {
|
|
343
347
|
return {
|
|
@@ -346,20 +350,20 @@ let usePnpm = null;
|
|
|
346
350
|
][0]
|
|
347
351
|
};
|
|
348
352
|
}
|
|
349
|
-
// Multiple subpaths needed
|
|
353
|
+
// Multiple subpaths needed.
|
|
350
354
|
return {
|
|
351
355
|
exportToSubpath
|
|
352
356
|
};
|
|
353
357
|
}
|
|
354
358
|
/**
|
|
355
|
-
* Check the bundle size of an npm package
|
|
359
|
+
* Check the bundle size of an npm package.
|
|
356
360
|
*/ export async function checkBundleSize(options) {
|
|
357
|
-
const { packageName: packageSpecifier, exports, additionalExternals, noExternal, gzipLevel = 5, registry } = options;
|
|
358
|
-
// Parse the package specifier to extract name, version, and subpath
|
|
361
|
+
const { packageName: packageSpecifier, exports, additionalExternals, noExternal, gzipLevel = 5, registry, platform: explicitPlatform } = options;
|
|
362
|
+
// Parse the package specifier to extract name, version, and subpath.
|
|
359
363
|
const { name: packageName, version: requestedVersion, subpath } = parsePackageSpecifier(packageSpecifier);
|
|
360
364
|
const tmpDir = createTempDir();
|
|
361
365
|
try {
|
|
362
|
-
// Create initial package.json
|
|
366
|
+
// Create initial package.json.
|
|
363
367
|
const packageJson = {
|
|
364
368
|
name: "bundlecheck-temp",
|
|
365
369
|
version: "1.0.0",
|
|
@@ -369,16 +373,28 @@ let usePnpm = null;
|
|
|
369
373
|
}
|
|
370
374
|
};
|
|
371
375
|
fs.writeFileSync(path.join(tmpDir, "package.json"), JSON.stringify(packageJson, null, 2));
|
|
372
|
-
// Install the main package (try pnpm first, fallback to npm)
|
|
376
|
+
// Install the main package (try pnpm first, fallback to npm).
|
|
373
377
|
const installCmd = getInstallCommand(registry);
|
|
374
378
|
execSync(installCmd, {
|
|
375
379
|
cwd: tmpDir,
|
|
376
380
|
stdio: "pipe"
|
|
377
381
|
});
|
|
378
|
-
|
|
379
|
-
|
|
382
|
+
/**
|
|
383
|
+
* Get package info (version, dependencies, peer dependencies, exports,
|
|
384
|
+
* engines).
|
|
385
|
+
*/ const pkgInfo = getPackageInfo(tmpDir, packageName);
|
|
380
386
|
const peerDepKeys = Object.keys(pkgInfo.peerDependencies);
|
|
381
|
-
|
|
387
|
+
/**
|
|
388
|
+
* Determine platform: use explicit value if provided, otherwise auto-detect
|
|
389
|
+
* from engines.
|
|
390
|
+
*/ let platform = "browser";
|
|
391
|
+
if (explicitPlatform) {
|
|
392
|
+
platform = explicitPlatform;
|
|
393
|
+
} else if (pkgInfo.engines?.node && !pkgInfo.engines?.browser) {
|
|
394
|
+
// Package specifies node engine without browser - likely a Node.js package.
|
|
395
|
+
platform = "node";
|
|
396
|
+
}
|
|
397
|
+
// Collect all dependency names (prod + peer).
|
|
382
398
|
const allDependencies = [
|
|
383
399
|
...new Set([
|
|
384
400
|
...Object.keys(pkgInfo.dependencies),
|
|
@@ -386,40 +402,42 @@ let usePnpm = null;
|
|
|
386
402
|
])
|
|
387
403
|
].sort();
|
|
388
404
|
if (peerDepKeys.length > 0) {
|
|
389
|
-
// Add peer dependencies to package.json
|
|
405
|
+
// Add peer dependencies to package.json.
|
|
390
406
|
for (const dep of peerDepKeys){
|
|
391
|
-
// Use the version range from peer dependencies
|
|
407
|
+
// Use the version range from peer dependencies.
|
|
392
408
|
packageJson.dependencies[dep] = pkgInfo.peerDependencies[dep];
|
|
393
409
|
}
|
|
394
|
-
// Update package.json and reinstall
|
|
410
|
+
// Update package.json and reinstall.
|
|
395
411
|
fs.writeFileSync(path.join(tmpDir, "package.json"), JSON.stringify(packageJson, null, 2));
|
|
396
412
|
execSync(installCmd, {
|
|
397
413
|
cwd: tmpDir,
|
|
398
414
|
stdio: "pipe"
|
|
399
415
|
});
|
|
400
416
|
}
|
|
401
|
-
|
|
402
|
-
|
|
417
|
+
/**
|
|
418
|
+
* Determine if we need to use all subpath exports or find the right
|
|
419
|
+
* subpath(s).
|
|
420
|
+
*/ let allSubpaths;
|
|
403
421
|
let resolvedSubpath = subpath;
|
|
404
422
|
let exportToSubpath;
|
|
405
423
|
if (!subpath && !pkgInfo.hasMainEntry && pkgInfo.exports) {
|
|
406
424
|
if (exports && exports.length > 0) {
|
|
407
|
-
// User specified exports but no subpath - try to find the right subpath(s)
|
|
425
|
+
// User specified exports but no subpath - try to find the right subpath(s).
|
|
408
426
|
const mapping = findSubpathsForExports(tmpDir, packageName, pkgInfo.exports, exports);
|
|
409
427
|
if (mapping.singleSubpath) {
|
|
410
|
-
// All exports from the same subpath
|
|
428
|
+
// All exports from the same subpath.
|
|
411
429
|
resolvedSubpath = mapping.singleSubpath;
|
|
412
430
|
} else if (mapping.exportToSubpath) {
|
|
413
|
-
// Exports from multiple subpaths
|
|
431
|
+
// Exports from multiple subpaths.
|
|
414
432
|
exportToSubpath = mapping.exportToSubpath;
|
|
415
433
|
}
|
|
416
434
|
}
|
|
417
|
-
// If still no subpath resolved and no mapping, bundle all subpaths
|
|
435
|
+
// If still no subpath resolved and no mapping, bundle all subpaths.
|
|
418
436
|
if (!resolvedSubpath && !exportToSubpath) {
|
|
419
437
|
allSubpaths = getSubpathExports(pkgInfo.exports);
|
|
420
438
|
}
|
|
421
439
|
}
|
|
422
|
-
// Create entry file with appropriate content
|
|
440
|
+
// Create entry file with appropriate content.
|
|
423
441
|
const entryContent = generateEntryContent({
|
|
424
442
|
packageName,
|
|
425
443
|
subpath: resolvedSubpath,
|
|
@@ -429,9 +447,9 @@ let usePnpm = null;
|
|
|
429
447
|
});
|
|
430
448
|
const entryFile = path.join(tmpDir, "entry.js");
|
|
431
449
|
fs.writeFileSync(entryFile, entryContent);
|
|
432
|
-
// Get externals
|
|
450
|
+
// Get externals.
|
|
433
451
|
const externals = getExternals(packageName, additionalExternals, noExternal);
|
|
434
|
-
// Bundle with esbuild
|
|
452
|
+
// Bundle with esbuild.
|
|
435
453
|
const result = await esbuild.build({
|
|
436
454
|
entryPoints: [
|
|
437
455
|
entryFile
|
|
@@ -439,27 +457,30 @@ let usePnpm = null;
|
|
|
439
457
|
bundle: true,
|
|
440
458
|
write: false,
|
|
441
459
|
format: "esm",
|
|
442
|
-
platform
|
|
460
|
+
platform,
|
|
443
461
|
target: "es2020",
|
|
444
462
|
minify: true,
|
|
445
463
|
treeShaking: true,
|
|
446
464
|
external: externals,
|
|
447
465
|
metafile: true
|
|
448
466
|
});
|
|
449
|
-
// Get raw size
|
|
467
|
+
// Get raw size.
|
|
450
468
|
const bundleContent = result.outputFiles[0].contents;
|
|
451
469
|
const rawSize = bundleContent.length;
|
|
452
|
-
// Gzip the bundle
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
470
|
+
// Gzip the bundle (only for browser platform - not relevant for Node.js).
|
|
471
|
+
let gzipSize = null;
|
|
472
|
+
if (platform === "browser") {
|
|
473
|
+
const gzipped = await gzipAsync(Buffer.from(bundleContent), {
|
|
474
|
+
level: gzipLevel
|
|
475
|
+
});
|
|
476
|
+
gzipSize = gzipped.length;
|
|
477
|
+
}
|
|
478
|
+
// Determine the display name.
|
|
458
479
|
let displayName = packageName;
|
|
459
480
|
if (resolvedSubpath) {
|
|
460
481
|
displayName = `${packageName}/${resolvedSubpath}`;
|
|
461
482
|
} else if (exportToSubpath && exportToSubpath.size > 0) {
|
|
462
|
-
// Multiple subpaths - show them all
|
|
483
|
+
// Multiple subpaths - show them all.
|
|
463
484
|
const uniqueSubpaths = [
|
|
464
485
|
...new Set(exportToSubpath.values())
|
|
465
486
|
].sort();
|
|
@@ -473,7 +494,8 @@ let usePnpm = null;
|
|
|
473
494
|
gzipSize,
|
|
474
495
|
gzipLevel,
|
|
475
496
|
externals,
|
|
476
|
-
dependencies: allDependencies
|
|
497
|
+
dependencies: allDependencies,
|
|
498
|
+
platform
|
|
477
499
|
};
|
|
478
500
|
} finally{
|
|
479
501
|
cleanupTempDir(tmpDir);
|