tonnage 0.1.0 → 0.1.1
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/CHANGELOG.md +7 -0
- package/README.md +31 -0
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +1 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{run-DrhG2LzX.mjs → run-P2rhqYM3.mjs} +88 -24
- package/dist/run-P2rhqYM3.mjs.map +1 -0
- package/package.json +1 -1
- package/src/measure.ts +22 -2
- package/src/package-resolution.ts +57 -0
- package/src/report.ts +68 -31
- package/dist/run-DrhG2LzX.mjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# tonnage
|
|
2
2
|
|
|
3
|
+
## 0.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- d7015e2: Adapt generated README reports to the measurements they contain, omitting irrelevant methodology and redundant single-table subsection headings.
|
|
8
|
+
- c1457b3: Resolve measured packages without self-dependencies and support main-only packages on the default neutral platform.
|
|
9
|
+
|
|
3
10
|
## 0.1.0
|
|
4
11
|
|
|
5
12
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -36,3 +36,34 @@ public export except `./package.json`. Paths are resolved relative to the config
|
|
|
36
36
|
file. Use `exports.include` or `exports.exclude` to select subpaths, recipe entry
|
|
37
37
|
files to describe realistic runtime boundaries, and `external` for non-peer
|
|
38
38
|
imports that the package intentionally leaves to its consumers.
|
|
39
|
+
|
|
40
|
+
## Report layout
|
|
41
|
+
|
|
42
|
+
Generated reports adapt to the measurements they contain. An exports-only or
|
|
43
|
+
recipes-only report puts its table directly below the configured report heading
|
|
44
|
+
and describes only that type of measurement. A report with both types labels the
|
|
45
|
+
tables `Package exports` and `Usage examples` so readers can distinguish complete
|
|
46
|
+
runtime export surfaces from tree-shaken recipe bundles.
|
|
47
|
+
|
|
48
|
+
Every layout states the exact measurement semantics: minified and level-9 gzip
|
|
49
|
+
JavaScript bytes, with declarations, source maps, CSS, and other assets excluded.
|
|
50
|
+
Peer dependencies remain external, and shared modules are counted once in each
|
|
51
|
+
bundle.
|
|
52
|
+
|
|
53
|
+
## Package resolution and platforms
|
|
54
|
+
|
|
55
|
+
Tonnage resolves the package's own public import name from the manifest selected
|
|
56
|
+
by `packageJson`; a workspace package does not need to install or declare itself
|
|
57
|
+
as a dependency. Root and subpath imports still go through esbuild's package
|
|
58
|
+
resolver, so the manifest's `exports` restrictions and conditions apply. Runtime
|
|
59
|
+
dependencies resolve from the measured package, while its peer dependencies stay
|
|
60
|
+
external.
|
|
61
|
+
|
|
62
|
+
The default `platform` is `neutral`. For packages without `exports`, Tonnage
|
|
63
|
+
configures neutral resolution to try the `module` field and then `main`, so
|
|
64
|
+
main-only packages and dependencies are actionable by default. With conditional
|
|
65
|
+
`exports`, neutral does not enable the `browser` or `node` condition, so exports
|
|
66
|
+
commonly fall through to their `default` target. Set `platform` to `browser` or
|
|
67
|
+
`node` when that runtime is what you want to measure; those choices retain
|
|
68
|
+
esbuild's native export conditions and main-field behavior. A recipe's
|
|
69
|
+
`platform` overrides the report-wide setting for that recipe.
|
package/dist/cli.mjs
CHANGED
package/dist/index.d.mts
CHANGED
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/define-config.ts","../src/measure.ts","../src/readme.ts","../src/report.ts","../src/run.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/define-config.ts","../src/measure.ts","../src/readme.ts","../src/report.ts","../src/run.ts"],"mappings":";;KAAY;KAEA;EACX;EACA;;KAGW;EACX;EACA;EACA;EACA,WAAW;;KAGA;EACX,oBAAoB;EACpB;EACA;EACA;EACA;EACA,WAAW;EACX;EACA,mBAAmB;EACnB;;KAGW;EACX;EACA;;KAGW,aAAa;EACxB;EACA;;KAGW,mBAAmB;EAC9B;EACA;;KAGW;EACX,kBAAkB;EAClB;EACA,kBAAkB;;KAGP;KAEA;EACX;EACA;EACA,QAAQ;;;;iBClDO,aAAa,QAAQ,gBAAgB;;;KCKzC;EACX;EACA,WAAW;EACX;EACA;;KAGW,sBAAsB;iBAMZ,aACrB,eACA,SAAS,sBACP,QAAQ;iBAcW,eACrB,4BACA,SAAS,wBACP,QAAQ;;;iBCxCK,eAAe;EAC9B;EACA;;iBASe,uBACf,gBACA,kBACA;;;iBCOqB,oBACrB,QAAQ,eACR,0BACE,QAAQ;iBA+DK,sBACf,QAAQ,eACR,SAAQ;;;iBClFa,WACrB,QAAQ,eACR;EACC;EACA,MAAM;IAEL,QAAQ"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as measureImports, i as measureEntry, n as createTonnageReport, o as tonnageMarkers, r as renderTonnageMarkdown, s as updateGeneratedSection, t as runTonnage } from "./run-
|
|
1
|
+
import { a as measureImports, i as measureEntry, n as createTonnageReport, o as tonnageMarkers, r as renderTonnageMarkdown, s as updateGeneratedSection, t as runTonnage } from "./run-P2rhqYM3.mjs";
|
|
2
2
|
//#region src/define-config.ts
|
|
3
3
|
function defineConfig(config) {
|
|
4
4
|
return config;
|
|
@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { gzipSync } from "node:zlib";
|
|
4
4
|
import { build } from "esbuild";
|
|
5
|
+
import os from "node:os";
|
|
5
6
|
//#region src/readme.ts
|
|
6
7
|
function tonnageMarkers(marker = `default`) {
|
|
7
8
|
validateMarker(marker);
|
|
@@ -26,9 +27,15 @@ function validateMarker(marker) {
|
|
|
26
27
|
//#endregion
|
|
27
28
|
//#region src/measure.ts
|
|
28
29
|
async function measureEntry(entry, options) {
|
|
30
|
+
return measureEntryWithPlugins(entry, options);
|
|
31
|
+
}
|
|
32
|
+
async function measureEntryWithPlugins(entry, options) {
|
|
29
33
|
return measureBuild({ entryPoints: [path.resolve(options.resolveDirectory, entry)] }, options);
|
|
30
34
|
}
|
|
31
35
|
async function measureImports(imports, options) {
|
|
36
|
+
return measureImportsWithPlugins(imports, options);
|
|
37
|
+
}
|
|
38
|
+
async function measureImportsWithPlugins(imports, options) {
|
|
32
39
|
if (imports.length === 0) throw new Error(`A tonnage entry must import at least one module.`);
|
|
33
40
|
return measureBuild({ stdin: {
|
|
34
41
|
contents: imports.map((specifier, index) => `export * as entry${index} from ${JSON.stringify(specifier)}`).join(`\n`),
|
|
@@ -46,9 +53,11 @@ async function measureBuild(entry, options) {
|
|
|
46
53
|
format: `esm`,
|
|
47
54
|
legalComments: `none`,
|
|
48
55
|
logLevel: `silent`,
|
|
56
|
+
...platform === `neutral` ? { mainFields: [`module`, `main`] } : {},
|
|
49
57
|
minify: true,
|
|
50
58
|
outdir: `out`,
|
|
51
59
|
platform,
|
|
60
|
+
plugins: [...options.plugins ?? []],
|
|
52
61
|
...entry,
|
|
53
62
|
target: options.target ?? `es2022`,
|
|
54
63
|
treeShaking: true,
|
|
@@ -62,6 +71,42 @@ async function measureBuild(entry, options) {
|
|
|
62
71
|
};
|
|
63
72
|
}
|
|
64
73
|
//#endregion
|
|
74
|
+
//#region src/package-resolution.ts
|
|
75
|
+
async function withPackageResolver(packageName, packageDirectory, useResolver) {
|
|
76
|
+
const temporaryDirectory = await fs.mkdtemp(path.join(os.tmpdir(), `tonnage-package-`));
|
|
77
|
+
try {
|
|
78
|
+
const nodeModulesDirectory = path.join(temporaryDirectory, `node_modules`);
|
|
79
|
+
const packageLinkPath = path.resolve(nodeModulesDirectory, packageName);
|
|
80
|
+
if (!packageLinkPath.startsWith(`${nodeModulesDirectory}${path.sep}`)) throw new Error(`Invalid package name: ${packageName}`);
|
|
81
|
+
await fs.mkdir(path.dirname(packageLinkPath), { recursive: true });
|
|
82
|
+
await fs.symlink(packageDirectory, packageLinkPath, process.platform === `win32` ? `junction` : `dir`);
|
|
83
|
+
const filter = new RegExp(`^${escapeRegExp(packageName)}(?:/.*)?$`);
|
|
84
|
+
return await useResolver({
|
|
85
|
+
name: `tonnage-package-resolution`,
|
|
86
|
+
setup(build) {
|
|
87
|
+
build.onResolve({ filter }, async (args) => {
|
|
88
|
+
if (args.resolveDir === temporaryDirectory) return;
|
|
89
|
+
return build.resolve(args.path, {
|
|
90
|
+
importer: args.importer,
|
|
91
|
+
kind: args.kind,
|
|
92
|
+
namespace: args.namespace,
|
|
93
|
+
resolveDir: temporaryDirectory,
|
|
94
|
+
with: args.with
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
} finally {
|
|
100
|
+
await fs.rm(temporaryDirectory, {
|
|
101
|
+
force: true,
|
|
102
|
+
recursive: true
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function escapeRegExp(value) {
|
|
107
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, `\\$&`);
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
65
110
|
//#region src/report.ts
|
|
66
111
|
async function createTonnageReport(config, configDirectory) {
|
|
67
112
|
const packageJsonPath = path.resolve(configDirectory, config.packageJson ?? `package.json`);
|
|
@@ -70,26 +115,32 @@ async function createTonnageReport(config, configDirectory) {
|
|
|
70
115
|
const resolveDirectory = path.dirname(packageJsonPath);
|
|
71
116
|
const external = unique([...Object.keys(packageJson.peerDependencies ?? {}), ...config.external ?? []]);
|
|
72
117
|
const exportImports = selectExportImports(packageJson.name, packageJson.exports, config.exports);
|
|
73
|
-
const exportRows = await
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
118
|
+
const { exportRows, recipeRows } = await withPackageResolver(packageJson.name, resolveDirectory, async (packageResolver) => {
|
|
119
|
+
return {
|
|
120
|
+
exportRows: await Promise.all(exportImports.map(async (specifier) => ({
|
|
121
|
+
...await measureImportsWithPlugins([specifier], {
|
|
122
|
+
external,
|
|
123
|
+
platform: config.platform,
|
|
124
|
+
plugins: [packageResolver],
|
|
125
|
+
resolveDirectory,
|
|
126
|
+
target: config.target
|
|
127
|
+
}),
|
|
128
|
+
imports: [specifier],
|
|
129
|
+
name: specifier
|
|
130
|
+
}))),
|
|
131
|
+
recipeRows: await Promise.all((config.recipes ?? []).map(async (recipe) => ({
|
|
132
|
+
...await measureEntryWithPlugins(recipe.entry, {
|
|
133
|
+
external: unique([...external, ...recipe.external ?? []]),
|
|
134
|
+
platform: recipe.platform ?? config.platform,
|
|
135
|
+
plugins: [packageResolver],
|
|
136
|
+
resolveDirectory,
|
|
137
|
+
target: config.target
|
|
138
|
+
}),
|
|
139
|
+
entry: recipe.entry,
|
|
140
|
+
name: recipe.name
|
|
141
|
+
})))
|
|
142
|
+
};
|
|
143
|
+
});
|
|
93
144
|
return {
|
|
94
145
|
exports: exportRows,
|
|
95
146
|
packageName: packageJson.name,
|
|
@@ -97,12 +148,18 @@ async function createTonnageReport(config, configDirectory) {
|
|
|
97
148
|
};
|
|
98
149
|
}
|
|
99
150
|
function renderTonnageMarkdown(report, config = {}) {
|
|
151
|
+
const hasExports = report.exports.length > 0;
|
|
152
|
+
const hasRecipes = report.recipes.length > 0;
|
|
153
|
+
const hasMultipleSections = hasExports && hasRecipes;
|
|
100
154
|
const lines = [
|
|
101
155
|
`## ${config.heading ?? `Bundle size`}`,
|
|
102
156
|
``,
|
|
103
|
-
|
|
157
|
+
...methodologyLines({
|
|
158
|
+
hasExports,
|
|
159
|
+
hasRecipes
|
|
160
|
+
})
|
|
104
161
|
];
|
|
105
|
-
if (
|
|
162
|
+
if (hasExports) lines.push(``, ...hasMultipleSections ? [`### Package exports`, ``] : [], ...markdownTable([
|
|
106
163
|
`Import`,
|
|
107
164
|
`Minified JS`,
|
|
108
165
|
`Gzip JS`
|
|
@@ -115,7 +172,7 @@ function renderTonnageMarkdown(report, config = {}) {
|
|
|
115
172
|
`right`,
|
|
116
173
|
`right`
|
|
117
174
|
]));
|
|
118
|
-
if (
|
|
175
|
+
if (hasRecipes) lines.push(``, ...hasMultipleSections ? [`### Usage examples`, ``] : [], ...markdownTable([
|
|
119
176
|
`Recipe`,
|
|
120
177
|
`Entry`,
|
|
121
178
|
`Minified JS`,
|
|
@@ -133,6 +190,13 @@ function renderTonnageMarkdown(report, config = {}) {
|
|
|
133
190
|
]));
|
|
134
191
|
return lines.join(`\n`);
|
|
135
192
|
}
|
|
193
|
+
function methodologyLines({ hasExports, hasRecipes }) {
|
|
194
|
+
const lines = [];
|
|
195
|
+
if (hasExports) lines.push(`Package export sizes include complete runtime export surfaces.`);
|
|
196
|
+
if (hasRecipes) lines.push(`Usage example sizes bundle their entry files and tree-shake unused exports.`);
|
|
197
|
+
lines.push(`Sizes are exact minified and level-9 gzip JavaScript byte counts. Declarations, source maps, CSS, and other assets are excluded. Peer dependencies stay external, and shared modules are counted once per bundle.`);
|
|
198
|
+
return lines;
|
|
199
|
+
}
|
|
136
200
|
function selectExportImports(packageName, exportsValue, selection) {
|
|
137
201
|
if (selection === false) return [];
|
|
138
202
|
const exportKeys = getExportKeys(exportsValue);
|
|
@@ -196,4 +260,4 @@ async function runTonnage(config, options) {
|
|
|
196
260
|
//#endregion
|
|
197
261
|
export { measureImports as a, measureEntry as i, createTonnageReport as n, tonnageMarkers as o, renderTonnageMarkdown as r, updateGeneratedSection as s, runTonnage as t };
|
|
198
262
|
|
|
199
|
-
//# sourceMappingURL=run-
|
|
263
|
+
//# sourceMappingURL=run-P2rhqYM3.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-P2rhqYM3.mjs","names":[],"sources":["../src/readme.ts","../src/measure.ts","../src/package-resolution.ts","../src/report.ts","../src/run.ts"],"sourcesContent":["export function tonnageMarkers(marker = `default`): {\n\tend: string\n\tstart: string\n} {\n\tvalidateMarker(marker)\n\treturn {\n\t\tend: `<!-- tonnage:${marker}:end -->`,\n\t\tstart: `<!-- tonnage:${marker}:start -->`,\n\t}\n}\n\nexport function updateGeneratedSection(\n\treadme: string,\n\tmarkdown: string,\n\tmarker?: string,\n): string {\n\tconst { end, start } = tonnageMarkers(marker)\n\tconst startIndex = readme.indexOf(start)\n\tconst endIndex = readme.indexOf(end)\n\n\tif (startIndex === -1 || endIndex === -1) {\n\t\tthrow new Error(\n\t\t\t`README must contain exactly one ${start} and one ${end} marker.`,\n\t\t)\n\t}\n\tif (\n\t\treadme.slice(startIndex + start.length).includes(start) ||\n\t\treadme.slice(endIndex + end.length).includes(end)\n\t) {\n\t\tthrow new Error(\n\t\t\t`README must contain exactly one ${start} and one ${end} marker.`,\n\t\t)\n\t}\n\tif (endIndex < startIndex) {\n\t\tthrow new Error(`${end} must appear after ${start}.`)\n\t}\n\n\tconst contentStart = startIndex + start.length\n\treturn `${readme.slice(0, contentStart)}\\n\\n${markdown.trim()}\\n\\n${readme.slice(endIndex)}`\n}\n\nfunction validateMarker(marker: string): void {\n\tif (!/^[a-z0-9][a-z0-9._-]*$/i.test(marker)) {\n\t\tthrow new Error(\n\t\t\t`Tonnage marker names may contain letters, numbers, dots, underscores, and hyphens.`,\n\t\t)\n\t}\n}\n","import path from \"node:path\"\nimport { gzipSync } from \"node:zlib\"\n\nimport { build, type BuildOptions, type Plugin } from \"esbuild\"\n\nimport type { BundleMeasurement, BundlePlatform } from \"./types.ts\"\n\nexport type MeasureImportsOptions = {\n\texternal?: readonly string[] | undefined\n\tplatform?: BundlePlatform | undefined\n\tresolveDirectory: string\n\ttarget?: string | undefined\n}\n\nexport type MeasureEntryOptions = MeasureImportsOptions\n\ntype InternalMeasureOptions = MeasureImportsOptions & {\n\tplugins?: readonly Plugin[]\n}\n\nexport async function measureEntry(\n\tentry: string,\n\toptions: MeasureEntryOptions,\n): Promise<BundleMeasurement> {\n\treturn measureEntryWithPlugins(entry, options)\n}\n\nexport async function measureEntryWithPlugins(\n\tentry: string,\n\toptions: InternalMeasureOptions,\n): Promise<BundleMeasurement> {\n\treturn measureBuild(\n\t\t{ entryPoints: [path.resolve(options.resolveDirectory, entry)] },\n\t\toptions,\n\t)\n}\n\nexport async function measureImports(\n\timports: readonly string[],\n\toptions: MeasureImportsOptions,\n): Promise<BundleMeasurement> {\n\treturn measureImportsWithPlugins(imports, options)\n}\n\nexport async function measureImportsWithPlugins(\n\timports: readonly string[],\n\toptions: InternalMeasureOptions,\n): Promise<BundleMeasurement> {\n\tif (imports.length === 0) {\n\t\tthrow new Error(`A tonnage entry must import at least one module.`)\n\t}\n\n\tconst entry = imports\n\t\t.map(\n\t\t\t(specifier, index) =>\n\t\t\t\t`export * as entry${index} from ${JSON.stringify(specifier)}`,\n\t\t)\n\t\t.join(`\\n`)\n\treturn measureBuild(\n\t\t{\n\t\t\tstdin: {\n\t\t\t\tcontents: entry,\n\t\t\t\tloader: `js`,\n\t\t\t\tresolveDir: options.resolveDirectory,\n\t\t\t\tsourcefile: `tonnage-entry.js`,\n\t\t\t},\n\t\t},\n\t\toptions,\n\t)\n}\n\nasync function measureBuild(\n\tentry: Pick<BuildOptions, `entryPoints` | `stdin`>,\n\toptions: InternalMeasureOptions,\n): Promise<BundleMeasurement> {\n\tconst platform = options.platform ?? `neutral`\n\n\tconst result = await build({\n\t\tbundle: true,\n\t\tcharset: `utf8`,\n\t\texternal: [\n\t\t\t...(options.external ?? []),\n\t\t\t...(platform === `browser` ? [] : [`node:*`]),\n\t\t],\n\t\tformat: `esm`,\n\t\tlegalComments: `none`,\n\t\tlogLevel: `silent`,\n\t\t...(platform === `neutral` ? { mainFields: [`module`, `main`] } : {}),\n\t\tminify: true,\n\t\toutdir: `out`,\n\t\tplatform,\n\t\tplugins: [...(options.plugins ?? [])],\n\t\t...entry,\n\t\ttarget: options.target ?? `es2022`,\n\t\ttreeShaking: true,\n\t\twrite: false,\n\t})\n\n\tconst outputFiles = [...(result.outputFiles ?? [])]\n\t\t.filter((output) => output.path.endsWith(`.js`))\n\t\t.sort((left, right) => left.path.localeCompare(right.path))\n\tif (outputFiles.length === 0) {\n\t\tthrow new Error(`The tonnage entry did not emit any runtime JavaScript.`)\n\t}\n\tconst rawBytes = outputFiles.reduce(\n\t\t(total, output) => total + output.contents.byteLength,\n\t\t0,\n\t)\n\tconst gzipBytes = outputFiles.reduce(\n\t\t(total, output) =>\n\t\t\ttotal + gzipSync(output.contents, { level: 9 }).byteLength,\n\t\t0,\n\t)\n\n\treturn { gzipBytes, rawBytes }\n}\n","import fs from \"node:fs/promises\"\nimport os from \"node:os\"\nimport path from \"node:path\"\n\nimport type { Plugin } from \"esbuild\"\n\nexport async function withPackageResolver<Result>(\n\tpackageName: string,\n\tpackageDirectory: string,\n\tuseResolver: (plugin: Plugin) => Promise<Result>,\n): Promise<Result> {\n\tconst temporaryDirectory = await fs.mkdtemp(\n\t\tpath.join(os.tmpdir(), `tonnage-package-`),\n\t)\n\ttry {\n\t\tconst nodeModulesDirectory = path.join(temporaryDirectory, `node_modules`)\n\t\tconst packageLinkPath = path.resolve(nodeModulesDirectory, packageName)\n\t\tif (!packageLinkPath.startsWith(`${nodeModulesDirectory}${path.sep}`)) {\n\t\t\tthrow new Error(`Invalid package name: ${packageName}`)\n\t\t}\n\n\t\tawait fs.mkdir(path.dirname(packageLinkPath), { recursive: true })\n\t\tawait fs.symlink(\n\t\t\tpackageDirectory,\n\t\t\tpackageLinkPath,\n\t\t\tprocess.platform === `win32` ? `junction` : `dir`,\n\t\t)\n\n\t\tconst filter = new RegExp(`^${escapeRegExp(packageName)}(?:/.*)?$`)\n\t\tconst plugin: Plugin = {\n\t\t\tname: `tonnage-package-resolution`,\n\t\t\tsetup(build) {\n\t\t\t\tbuild.onResolve({ filter }, async (args) => {\n\t\t\t\t\tif (args.resolveDir === temporaryDirectory) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\treturn build.resolve(args.path, {\n\t\t\t\t\t\timporter: args.importer,\n\t\t\t\t\t\tkind: args.kind,\n\t\t\t\t\t\tnamespace: args.namespace,\n\t\t\t\t\t\tresolveDir: temporaryDirectory,\n\t\t\t\t\t\twith: args.with,\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t},\n\t\t}\n\n\t\treturn await useResolver(plugin)\n\t} finally {\n\t\tawait fs.rm(temporaryDirectory, { force: true, recursive: true })\n\t}\n}\n\nfunction escapeRegExp(value: string): string {\n\treturn value.replace(/[.*+?^${}()|[\\]\\\\]/g, `\\\\$&`)\n}\n","import fs from \"node:fs/promises\"\nimport path from \"node:path\"\n\nimport {\n\tmeasureEntryWithPlugins,\n\tmeasureImportsWithPlugins,\n} from \"./measure.ts\"\nimport { withPackageResolver } from \"./package-resolution.ts\"\nimport type {\n\tTonnageConfig,\n\tTonnageExports,\n\tTonnageReport,\n\tTonnageRow,\n} from \"./types.ts\"\n\ntype PackageJson = {\n\texports?: unknown\n\tname?: string\n\tpeerDependencies?: Record<string, string>\n}\n\nexport async function createTonnageReport(\n\tconfig: TonnageConfig,\n\tconfigDirectory: string,\n): Promise<TonnageReport> {\n\tconst packageJsonPath = path.resolve(\n\t\tconfigDirectory,\n\t\tconfig.packageJson ?? `package.json`,\n\t)\n\tconst packageJson = JSON.parse(\n\t\tawait fs.readFile(packageJsonPath, `utf8`),\n\t) as PackageJson\n\tif (!packageJson.name) {\n\t\tthrow new Error(`${packageJsonPath} does not declare a package name.`)\n\t}\n\n\tconst resolveDirectory = path.dirname(packageJsonPath)\n\tconst peerDependencies = Object.keys(packageJson.peerDependencies ?? {})\n\tconst external = unique([...peerDependencies, ...(config.external ?? [])])\n\tconst exportImports = selectExportImports(\n\t\tpackageJson.name,\n\t\tpackageJson.exports,\n\t\tconfig.exports,\n\t)\n\n\tconst { exportRows, recipeRows } = await withPackageResolver(\n\t\tpackageJson.name,\n\t\tresolveDirectory,\n\t\tasync (packageResolver) => {\n\t\t\tconst exportRows = await Promise.all(\n\t\t\t\texportImports.map(async (specifier): Promise<TonnageRow> => ({\n\t\t\t\t\t...(await measureImportsWithPlugins([specifier], {\n\t\t\t\t\t\texternal,\n\t\t\t\t\t\tplatform: config.platform,\n\t\t\t\t\t\tplugins: [packageResolver],\n\t\t\t\t\t\tresolveDirectory,\n\t\t\t\t\t\ttarget: config.target,\n\t\t\t\t\t})),\n\t\t\t\t\timports: [specifier],\n\t\t\t\t\tname: specifier,\n\t\t\t\t})),\n\t\t\t)\n\t\t\tconst recipeRows = await Promise.all(\n\t\t\t\t(config.recipes ?? []).map(async (recipe) => ({\n\t\t\t\t\t...(await measureEntryWithPlugins(recipe.entry, {\n\t\t\t\t\t\texternal: unique([...external, ...(recipe.external ?? [])]),\n\t\t\t\t\t\tplatform: recipe.platform ?? config.platform,\n\t\t\t\t\t\tplugins: [packageResolver],\n\t\t\t\t\t\tresolveDirectory,\n\t\t\t\t\t\ttarget: config.target,\n\t\t\t\t\t})),\n\t\t\t\t\tentry: recipe.entry,\n\t\t\t\t\tname: recipe.name,\n\t\t\t\t})),\n\t\t\t)\n\n\t\t\treturn { exportRows, recipeRows }\n\t\t},\n\t)\n\n\treturn {\n\t\texports: exportRows,\n\t\tpackageName: packageJson.name,\n\t\trecipes: recipeRows,\n\t}\n}\n\nexport function renderTonnageMarkdown(\n\treport: TonnageReport,\n\tconfig: TonnageConfig = {},\n): string {\n\tconst hasExports = report.exports.length > 0\n\tconst hasRecipes = report.recipes.length > 0\n\tconst hasMultipleSections = hasExports && hasRecipes\n\tconst lines = [\n\t\t`## ${config.heading ?? `Bundle size`}`,\n\t\t``,\n\t\t...methodologyLines({ hasExports, hasRecipes }),\n\t]\n\n\tif (hasExports) {\n\t\tlines.push(\n\t\t\t``,\n\t\t\t...(hasMultipleSections ? [`### Package exports`, ``] : []),\n\t\t\t...markdownTable(\n\t\t\t\t[`Import`, `Minified JS`, `Gzip JS`],\n\t\t\t\treport.exports.map((row) => [\n\t\t\t\t\tcode(row.name),\n\t\t\t\t\tformatBytes(row.rawBytes),\n\t\t\t\t\tformatBytes(row.gzipBytes),\n\t\t\t\t]),\n\t\t\t\t[`left`, `right`, `right`],\n\t\t\t),\n\t\t)\n\t}\n\n\tif (hasRecipes) {\n\t\tlines.push(\n\t\t\t``,\n\t\t\t...(hasMultipleSections ? [`### Usage examples`, ``] : []),\n\t\t\t...markdownTable(\n\t\t\t\t[`Recipe`, `Entry`, `Minified JS`, `Gzip JS`],\n\t\t\t\treport.recipes.map((row) => [\n\t\t\t\t\tescapeCell(row.name),\n\t\t\t\t\tcode(row.entry),\n\t\t\t\t\tformatBytes(row.rawBytes),\n\t\t\t\t\tformatBytes(row.gzipBytes),\n\t\t\t\t]),\n\t\t\t\t[`left`, `left`, `right`, `right`],\n\t\t\t),\n\t\t)\n\t}\n\n\treturn lines.join(`\\n`)\n}\n\nfunction methodologyLines({\n\thasExports,\n\thasRecipes,\n}: {\n\thasExports: boolean\n\thasRecipes: boolean\n}): string[] {\n\tconst lines: string[] = []\n\tif (hasExports) {\n\t\tlines.push(`Package export sizes include complete runtime export surfaces.`)\n\t}\n\tif (hasRecipes) {\n\t\tlines.push(\n\t\t\t`Usage example sizes bundle their entry files and tree-shake unused exports.`,\n\t\t)\n\t}\n\tlines.push(\n\t\t`Sizes are exact minified and level-9 gzip JavaScript byte counts. Declarations, source maps, CSS, and other assets are excluded. Peer dependencies stay external, and shared modules are counted once per bundle.`,\n\t)\n\treturn lines\n}\n\nfunction selectExportImports(\n\tpackageName: string,\n\texportsValue: unknown,\n\tselection: TonnageConfig[`exports`],\n): string[] {\n\tif (selection === false) {\n\t\treturn []\n\t}\n\n\tconst exportKeys = getExportKeys(exportsValue)\n\tconst options: TonnageExports = typeof selection === `object` ? selection : {}\n\tconst included = options.include\n\t\t? [...options.include]\n\t\t: exportKeys.filter((exportKey) => exportKey !== `./package.json`)\n\tconst excluded = new Set(options.exclude ?? [])\n\n\treturn included\n\t\t.filter((exportKey) => !excluded.has(exportKey))\n\t\t.map((exportKey) => toPackageImport(packageName, exportKey))\n}\n\nfunction getExportKeys(exportsValue: unknown): string[] {\n\tif (typeof exportsValue === `string` || Array.isArray(exportsValue)) {\n\t\treturn [`.`]\n\t}\n\tif (!exportsValue || typeof exportsValue !== `object`) {\n\t\treturn [`.`]\n\t}\n\n\tconst keys = Object.keys(exportsValue)\n\treturn keys.some((key) => key.startsWith(`.`)) ? keys : [`.`]\n}\n\nfunction toPackageImport(packageName: string, exportKey: string): string {\n\tif (exportKey === `.`) {\n\t\treturn packageName\n\t}\n\tif (!exportKey.startsWith(`./`)) {\n\t\tthrow new Error(`Invalid package export key: ${exportKey}`)\n\t}\n\treturn `${packageName}/${exportKey.slice(2)}`\n}\n\nfunction formatBytes(bytes: number): string {\n\treturn `${new Intl.NumberFormat(`en-US`).format(bytes)} B`\n}\n\nfunction code(value: string): string {\n\treturn `<code>${escapeCell(value)}</code>`\n}\n\nfunction escapeCell(value: string): string {\n\treturn value.replaceAll(`&`, `&`).replaceAll(`|`, `|`)\n}\n\nfunction unique(values: readonly string[]): string[] {\n\treturn [...new Set(values)]\n}\n\nfunction markdownTable(\n\theadings: readonly string[],\n\trows: readonly (readonly string[])[],\n\talignments: readonly (`left` | `right`)[],\n): string[] {\n\tconst widths = headings.map((heading, column) =>\n\t\tMath.max(heading.length, ...rows.map((row) => row[column]?.length ?? 0), 3),\n\t)\n\tconst renderRow = (cells: readonly string[]): string =>\n\t\t`| ${cells.map((cell, column) => padCell(cell, widths[column] ?? 3, alignments[column] ?? `left`)).join(` | `)} |`\n\tconst separator = widths.map((width, column) =>\n\t\talignments[column] === `right`\n\t\t\t? `${`-`.repeat(width - 1)}:`\n\t\t\t: `-`.repeat(width),\n\t)\n\n\treturn [renderRow(headings), renderRow(separator), ...rows.map(renderRow)]\n}\n\nfunction padCell(\n\tcell: string,\n\twidth: number,\n\talignment: `left` | `right`,\n): string {\n\treturn alignment === `right` ? cell.padStart(width) : cell.padEnd(width)\n}\n","import fs from \"node:fs/promises\"\nimport path from \"node:path\"\n\nimport { updateGeneratedSection } from \"./readme.ts\"\nimport { createTonnageReport, renderTonnageMarkdown } from \"./report.ts\"\nimport type { TonnageConfig, TonnageMode, TonnageRunResult } from \"./types.ts\"\n\nexport async function runTonnage(\n\tconfig: TonnageConfig,\n\toptions: {\n\t\tconfigDirectory?: string\n\t\tmode: TonnageMode\n\t},\n): Promise<TonnageRunResult> {\n\tconst configDirectory = path.resolve(options.configDirectory ?? process.cwd())\n\tconst readmePath = path.resolve(configDirectory, config.readme ?? `README.md`)\n\tconst oldReadme = await fs.readFile(readmePath, `utf8`)\n\tconst report = await createTonnageReport(config, configDirectory)\n\tconst markdown = renderTonnageMarkdown(report, config)\n\tconst newReadme = updateGeneratedSection(oldReadme, markdown, config.marker)\n\tconst changed = oldReadme !== newReadme\n\n\tif (options.mode === `write` && changed) {\n\t\tawait fs.writeFile(readmePath, newReadme)\n\t}\n\n\treturn { changed, readmePath, report }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
package/src/measure.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "node:path"
|
|
2
2
|
import { gzipSync } from "node:zlib"
|
|
3
3
|
|
|
4
|
-
import { build, type BuildOptions } from "esbuild"
|
|
4
|
+
import { build, type BuildOptions, type Plugin } from "esbuild"
|
|
5
5
|
|
|
6
6
|
import type { BundleMeasurement, BundlePlatform } from "./types.ts"
|
|
7
7
|
|
|
@@ -14,9 +14,20 @@ export type MeasureImportsOptions = {
|
|
|
14
14
|
|
|
15
15
|
export type MeasureEntryOptions = MeasureImportsOptions
|
|
16
16
|
|
|
17
|
+
type InternalMeasureOptions = MeasureImportsOptions & {
|
|
18
|
+
plugins?: readonly Plugin[]
|
|
19
|
+
}
|
|
20
|
+
|
|
17
21
|
export async function measureEntry(
|
|
18
22
|
entry: string,
|
|
19
23
|
options: MeasureEntryOptions,
|
|
24
|
+
): Promise<BundleMeasurement> {
|
|
25
|
+
return measureEntryWithPlugins(entry, options)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function measureEntryWithPlugins(
|
|
29
|
+
entry: string,
|
|
30
|
+
options: InternalMeasureOptions,
|
|
20
31
|
): Promise<BundleMeasurement> {
|
|
21
32
|
return measureBuild(
|
|
22
33
|
{ entryPoints: [path.resolve(options.resolveDirectory, entry)] },
|
|
@@ -27,6 +38,13 @@ export async function measureEntry(
|
|
|
27
38
|
export async function measureImports(
|
|
28
39
|
imports: readonly string[],
|
|
29
40
|
options: MeasureImportsOptions,
|
|
41
|
+
): Promise<BundleMeasurement> {
|
|
42
|
+
return measureImportsWithPlugins(imports, options)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function measureImportsWithPlugins(
|
|
46
|
+
imports: readonly string[],
|
|
47
|
+
options: InternalMeasureOptions,
|
|
30
48
|
): Promise<BundleMeasurement> {
|
|
31
49
|
if (imports.length === 0) {
|
|
32
50
|
throw new Error(`A tonnage entry must import at least one module.`)
|
|
@@ -53,7 +71,7 @@ export async function measureImports(
|
|
|
53
71
|
|
|
54
72
|
async function measureBuild(
|
|
55
73
|
entry: Pick<BuildOptions, `entryPoints` | `stdin`>,
|
|
56
|
-
options:
|
|
74
|
+
options: InternalMeasureOptions,
|
|
57
75
|
): Promise<BundleMeasurement> {
|
|
58
76
|
const platform = options.platform ?? `neutral`
|
|
59
77
|
|
|
@@ -67,9 +85,11 @@ async function measureBuild(
|
|
|
67
85
|
format: `esm`,
|
|
68
86
|
legalComments: `none`,
|
|
69
87
|
logLevel: `silent`,
|
|
88
|
+
...(platform === `neutral` ? { mainFields: [`module`, `main`] } : {}),
|
|
70
89
|
minify: true,
|
|
71
90
|
outdir: `out`,
|
|
72
91
|
platform,
|
|
92
|
+
plugins: [...(options.plugins ?? [])],
|
|
73
93
|
...entry,
|
|
74
94
|
target: options.target ?? `es2022`,
|
|
75
95
|
treeShaking: true,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import fs from "node:fs/promises"
|
|
2
|
+
import os from "node:os"
|
|
3
|
+
import path from "node:path"
|
|
4
|
+
|
|
5
|
+
import type { Plugin } from "esbuild"
|
|
6
|
+
|
|
7
|
+
export async function withPackageResolver<Result>(
|
|
8
|
+
packageName: string,
|
|
9
|
+
packageDirectory: string,
|
|
10
|
+
useResolver: (plugin: Plugin) => Promise<Result>,
|
|
11
|
+
): Promise<Result> {
|
|
12
|
+
const temporaryDirectory = await fs.mkdtemp(
|
|
13
|
+
path.join(os.tmpdir(), `tonnage-package-`),
|
|
14
|
+
)
|
|
15
|
+
try {
|
|
16
|
+
const nodeModulesDirectory = path.join(temporaryDirectory, `node_modules`)
|
|
17
|
+
const packageLinkPath = path.resolve(nodeModulesDirectory, packageName)
|
|
18
|
+
if (!packageLinkPath.startsWith(`${nodeModulesDirectory}${path.sep}`)) {
|
|
19
|
+
throw new Error(`Invalid package name: ${packageName}`)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
await fs.mkdir(path.dirname(packageLinkPath), { recursive: true })
|
|
23
|
+
await fs.symlink(
|
|
24
|
+
packageDirectory,
|
|
25
|
+
packageLinkPath,
|
|
26
|
+
process.platform === `win32` ? `junction` : `dir`,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
const filter = new RegExp(`^${escapeRegExp(packageName)}(?:/.*)?$`)
|
|
30
|
+
const plugin: Plugin = {
|
|
31
|
+
name: `tonnage-package-resolution`,
|
|
32
|
+
setup(build) {
|
|
33
|
+
build.onResolve({ filter }, async (args) => {
|
|
34
|
+
if (args.resolveDir === temporaryDirectory) {
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return build.resolve(args.path, {
|
|
39
|
+
importer: args.importer,
|
|
40
|
+
kind: args.kind,
|
|
41
|
+
namespace: args.namespace,
|
|
42
|
+
resolveDir: temporaryDirectory,
|
|
43
|
+
with: args.with,
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return await useResolver(plugin)
|
|
50
|
+
} finally {
|
|
51
|
+
await fs.rm(temporaryDirectory, { force: true, recursive: true })
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function escapeRegExp(value: string): string {
|
|
56
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, `\\$&`)
|
|
57
|
+
}
|
package/src/report.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import fs from "node:fs/promises"
|
|
2
2
|
import path from "node:path"
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
measureEntryWithPlugins,
|
|
6
|
+
measureImportsWithPlugins,
|
|
7
|
+
} from "./measure.ts"
|
|
8
|
+
import { withPackageResolver } from "./package-resolution.ts"
|
|
5
9
|
import type {
|
|
6
10
|
TonnageConfig,
|
|
7
11
|
TonnageExports,
|
|
@@ -39,29 +43,39 @@ export async function createTonnageReport(
|
|
|
39
43
|
config.exports,
|
|
40
44
|
)
|
|
41
45
|
|
|
42
|
-
const exportRows = await
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
46
|
+
const { exportRows, recipeRows } = await withPackageResolver(
|
|
47
|
+
packageJson.name,
|
|
48
|
+
resolveDirectory,
|
|
49
|
+
async (packageResolver) => {
|
|
50
|
+
const exportRows = await Promise.all(
|
|
51
|
+
exportImports.map(async (specifier): Promise<TonnageRow> => ({
|
|
52
|
+
...(await measureImportsWithPlugins([specifier], {
|
|
53
|
+
external,
|
|
54
|
+
platform: config.platform,
|
|
55
|
+
plugins: [packageResolver],
|
|
56
|
+
resolveDirectory,
|
|
57
|
+
target: config.target,
|
|
58
|
+
})),
|
|
59
|
+
imports: [specifier],
|
|
60
|
+
name: specifier,
|
|
61
|
+
})),
|
|
62
|
+
)
|
|
63
|
+
const recipeRows = await Promise.all(
|
|
64
|
+
(config.recipes ?? []).map(async (recipe) => ({
|
|
65
|
+
...(await measureEntryWithPlugins(recipe.entry, {
|
|
66
|
+
external: unique([...external, ...(recipe.external ?? [])]),
|
|
67
|
+
platform: recipe.platform ?? config.platform,
|
|
68
|
+
plugins: [packageResolver],
|
|
69
|
+
resolveDirectory,
|
|
70
|
+
target: config.target,
|
|
71
|
+
})),
|
|
72
|
+
entry: recipe.entry,
|
|
73
|
+
name: recipe.name,
|
|
74
|
+
})),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
return { exportRows, recipeRows }
|
|
78
|
+
},
|
|
65
79
|
)
|
|
66
80
|
|
|
67
81
|
return {
|
|
@@ -75,17 +89,19 @@ export function renderTonnageMarkdown(
|
|
|
75
89
|
report: TonnageReport,
|
|
76
90
|
config: TonnageConfig = {},
|
|
77
91
|
): string {
|
|
92
|
+
const hasExports = report.exports.length > 0
|
|
93
|
+
const hasRecipes = report.recipes.length > 0
|
|
94
|
+
const hasMultipleSections = hasExports && hasRecipes
|
|
78
95
|
const lines = [
|
|
79
96
|
`## ${config.heading ?? `Bundle size`}`,
|
|
80
97
|
``,
|
|
81
|
-
|
|
98
|
+
...methodologyLines({ hasExports, hasRecipes }),
|
|
82
99
|
]
|
|
83
100
|
|
|
84
|
-
if (
|
|
101
|
+
if (hasExports) {
|
|
85
102
|
lines.push(
|
|
86
103
|
``,
|
|
87
|
-
`###
|
|
88
|
-
``,
|
|
104
|
+
...(hasMultipleSections ? [`### Package exports`, ``] : []),
|
|
89
105
|
...markdownTable(
|
|
90
106
|
[`Import`, `Minified JS`, `Gzip JS`],
|
|
91
107
|
report.exports.map((row) => [
|
|
@@ -98,11 +114,10 @@ export function renderTonnageMarkdown(
|
|
|
98
114
|
)
|
|
99
115
|
}
|
|
100
116
|
|
|
101
|
-
if (
|
|
117
|
+
if (hasRecipes) {
|
|
102
118
|
lines.push(
|
|
103
119
|
``,
|
|
104
|
-
`###
|
|
105
|
-
``,
|
|
120
|
+
...(hasMultipleSections ? [`### Usage examples`, ``] : []),
|
|
106
121
|
...markdownTable(
|
|
107
122
|
[`Recipe`, `Entry`, `Minified JS`, `Gzip JS`],
|
|
108
123
|
report.recipes.map((row) => [
|
|
@@ -119,6 +134,28 @@ export function renderTonnageMarkdown(
|
|
|
119
134
|
return lines.join(`\n`)
|
|
120
135
|
}
|
|
121
136
|
|
|
137
|
+
function methodologyLines({
|
|
138
|
+
hasExports,
|
|
139
|
+
hasRecipes,
|
|
140
|
+
}: {
|
|
141
|
+
hasExports: boolean
|
|
142
|
+
hasRecipes: boolean
|
|
143
|
+
}): string[] {
|
|
144
|
+
const lines: string[] = []
|
|
145
|
+
if (hasExports) {
|
|
146
|
+
lines.push(`Package export sizes include complete runtime export surfaces.`)
|
|
147
|
+
}
|
|
148
|
+
if (hasRecipes) {
|
|
149
|
+
lines.push(
|
|
150
|
+
`Usage example sizes bundle their entry files and tree-shake unused exports.`,
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
lines.push(
|
|
154
|
+
`Sizes are exact minified and level-9 gzip JavaScript byte counts. Declarations, source maps, CSS, and other assets are excluded. Peer dependencies stay external, and shared modules are counted once per bundle.`,
|
|
155
|
+
)
|
|
156
|
+
return lines
|
|
157
|
+
}
|
|
158
|
+
|
|
122
159
|
function selectExportImports(
|
|
123
160
|
packageName: string,
|
|
124
161
|
exportsValue: unknown,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"run-DrhG2LzX.mjs","names":[],"sources":["../src/readme.ts","../src/measure.ts","../src/report.ts","../src/run.ts"],"sourcesContent":["export function tonnageMarkers(marker = `default`): {\n\tend: string\n\tstart: string\n} {\n\tvalidateMarker(marker)\n\treturn {\n\t\tend: `<!-- tonnage:${marker}:end -->`,\n\t\tstart: `<!-- tonnage:${marker}:start -->`,\n\t}\n}\n\nexport function updateGeneratedSection(\n\treadme: string,\n\tmarkdown: string,\n\tmarker?: string,\n): string {\n\tconst { end, start } = tonnageMarkers(marker)\n\tconst startIndex = readme.indexOf(start)\n\tconst endIndex = readme.indexOf(end)\n\n\tif (startIndex === -1 || endIndex === -1) {\n\t\tthrow new Error(\n\t\t\t`README must contain exactly one ${start} and one ${end} marker.`,\n\t\t)\n\t}\n\tif (\n\t\treadme.slice(startIndex + start.length).includes(start) ||\n\t\treadme.slice(endIndex + end.length).includes(end)\n\t) {\n\t\tthrow new Error(\n\t\t\t`README must contain exactly one ${start} and one ${end} marker.`,\n\t\t)\n\t}\n\tif (endIndex < startIndex) {\n\t\tthrow new Error(`${end} must appear after ${start}.`)\n\t}\n\n\tconst contentStart = startIndex + start.length\n\treturn `${readme.slice(0, contentStart)}\\n\\n${markdown.trim()}\\n\\n${readme.slice(endIndex)}`\n}\n\nfunction validateMarker(marker: string): void {\n\tif (!/^[a-z0-9][a-z0-9._-]*$/i.test(marker)) {\n\t\tthrow new Error(\n\t\t\t`Tonnage marker names may contain letters, numbers, dots, underscores, and hyphens.`,\n\t\t)\n\t}\n}\n","import path from \"node:path\"\nimport { gzipSync } from \"node:zlib\"\n\nimport { build, type BuildOptions } from \"esbuild\"\n\nimport type { BundleMeasurement, BundlePlatform } from \"./types.ts\"\n\nexport type MeasureImportsOptions = {\n\texternal?: readonly string[] | undefined\n\tplatform?: BundlePlatform | undefined\n\tresolveDirectory: string\n\ttarget?: string | undefined\n}\n\nexport type MeasureEntryOptions = MeasureImportsOptions\n\nexport async function measureEntry(\n\tentry: string,\n\toptions: MeasureEntryOptions,\n): Promise<BundleMeasurement> {\n\treturn measureBuild(\n\t\t{ entryPoints: [path.resolve(options.resolveDirectory, entry)] },\n\t\toptions,\n\t)\n}\n\nexport async function measureImports(\n\timports: readonly string[],\n\toptions: MeasureImportsOptions,\n): Promise<BundleMeasurement> {\n\tif (imports.length === 0) {\n\t\tthrow new Error(`A tonnage entry must import at least one module.`)\n\t}\n\n\tconst entry = imports\n\t\t.map(\n\t\t\t(specifier, index) =>\n\t\t\t\t`export * as entry${index} from ${JSON.stringify(specifier)}`,\n\t\t)\n\t\t.join(`\\n`)\n\treturn measureBuild(\n\t\t{\n\t\t\tstdin: {\n\t\t\t\tcontents: entry,\n\t\t\t\tloader: `js`,\n\t\t\t\tresolveDir: options.resolveDirectory,\n\t\t\t\tsourcefile: `tonnage-entry.js`,\n\t\t\t},\n\t\t},\n\t\toptions,\n\t)\n}\n\nasync function measureBuild(\n\tentry: Pick<BuildOptions, `entryPoints` | `stdin`>,\n\toptions: MeasureEntryOptions,\n): Promise<BundleMeasurement> {\n\tconst platform = options.platform ?? `neutral`\n\n\tconst result = await build({\n\t\tbundle: true,\n\t\tcharset: `utf8`,\n\t\texternal: [\n\t\t\t...(options.external ?? []),\n\t\t\t...(platform === `browser` ? [] : [`node:*`]),\n\t\t],\n\t\tformat: `esm`,\n\t\tlegalComments: `none`,\n\t\tlogLevel: `silent`,\n\t\tminify: true,\n\t\toutdir: `out`,\n\t\tplatform,\n\t\t...entry,\n\t\ttarget: options.target ?? `es2022`,\n\t\ttreeShaking: true,\n\t\twrite: false,\n\t})\n\n\tconst outputFiles = [...(result.outputFiles ?? [])]\n\t\t.filter((output) => output.path.endsWith(`.js`))\n\t\t.sort((left, right) => left.path.localeCompare(right.path))\n\tif (outputFiles.length === 0) {\n\t\tthrow new Error(`The tonnage entry did not emit any runtime JavaScript.`)\n\t}\n\tconst rawBytes = outputFiles.reduce(\n\t\t(total, output) => total + output.contents.byteLength,\n\t\t0,\n\t)\n\tconst gzipBytes = outputFiles.reduce(\n\t\t(total, output) =>\n\t\t\ttotal + gzipSync(output.contents, { level: 9 }).byteLength,\n\t\t0,\n\t)\n\n\treturn { gzipBytes, rawBytes }\n}\n","import fs from \"node:fs/promises\"\nimport path from \"node:path\"\n\nimport { measureEntry, measureImports } from \"./measure.ts\"\nimport type {\n\tTonnageConfig,\n\tTonnageExports,\n\tTonnageReport,\n\tTonnageRow,\n} from \"./types.ts\"\n\ntype PackageJson = {\n\texports?: unknown\n\tname?: string\n\tpeerDependencies?: Record<string, string>\n}\n\nexport async function createTonnageReport(\n\tconfig: TonnageConfig,\n\tconfigDirectory: string,\n): Promise<TonnageReport> {\n\tconst packageJsonPath = path.resolve(\n\t\tconfigDirectory,\n\t\tconfig.packageJson ?? `package.json`,\n\t)\n\tconst packageJson = JSON.parse(\n\t\tawait fs.readFile(packageJsonPath, `utf8`),\n\t) as PackageJson\n\tif (!packageJson.name) {\n\t\tthrow new Error(`${packageJsonPath} does not declare a package name.`)\n\t}\n\n\tconst resolveDirectory = path.dirname(packageJsonPath)\n\tconst peerDependencies = Object.keys(packageJson.peerDependencies ?? {})\n\tconst external = unique([...peerDependencies, ...(config.external ?? [])])\n\tconst exportImports = selectExportImports(\n\t\tpackageJson.name,\n\t\tpackageJson.exports,\n\t\tconfig.exports,\n\t)\n\n\tconst exportRows = await Promise.all(\n\t\texportImports.map(async (specifier): Promise<TonnageRow> => ({\n\t\t\t...(await measureImports([specifier], {\n\t\t\t\texternal,\n\t\t\t\tplatform: config.platform,\n\t\t\t\tresolveDirectory,\n\t\t\t\ttarget: config.target,\n\t\t\t})),\n\t\t\timports: [specifier],\n\t\t\tname: specifier,\n\t\t})),\n\t)\n\tconst recipeRows = await Promise.all(\n\t\t(config.recipes ?? []).map(async (recipe) => ({\n\t\t\t...(await measureEntry(recipe.entry, {\n\t\t\t\texternal: unique([...external, ...(recipe.external ?? [])]),\n\t\t\t\tplatform: recipe.platform ?? config.platform,\n\t\t\t\tresolveDirectory,\n\t\t\t\ttarget: config.target,\n\t\t\t})),\n\t\t\tentry: recipe.entry,\n\t\t\tname: recipe.name,\n\t\t})),\n\t)\n\n\treturn {\n\t\texports: exportRows,\n\t\tpackageName: packageJson.name,\n\t\trecipes: recipeRows,\n\t}\n}\n\nexport function renderTonnageMarkdown(\n\treport: TonnageReport,\n\tconfig: TonnageConfig = {},\n): string {\n\tconst lines = [\n\t\t`## ${config.heading ?? `Bundle size`}`,\n\t\t``,\n\t\t`Public-module rows retain complete runtime export surfaces. Recipe rows bundle their reviewable entry files and tree-shake unused exports. Both report exact minified and level-9 gzip JavaScript byte counts; declarations, source maps, CSS, and other assets are excluded. Peer dependencies stay external, and shared modules are counted once per bundle.`,\n\t]\n\n\tif (report.exports.length > 0) {\n\t\tlines.push(\n\t\t\t``,\n\t\t\t`### Public modules (whole export surface)`,\n\t\t\t``,\n\t\t\t...markdownTable(\n\t\t\t\t[`Import`, `Minified JS`, `Gzip JS`],\n\t\t\t\treport.exports.map((row) => [\n\t\t\t\t\tcode(row.name),\n\t\t\t\t\tformatBytes(row.rawBytes),\n\t\t\t\t\tformatBytes(row.gzipBytes),\n\t\t\t\t]),\n\t\t\t\t[`left`, `right`, `right`],\n\t\t\t),\n\t\t)\n\t}\n\n\tif (report.recipes.length > 0) {\n\t\tlines.push(\n\t\t\t``,\n\t\t\t`### Representative runtimes (tree-shaken)`,\n\t\t\t``,\n\t\t\t...markdownTable(\n\t\t\t\t[`Recipe`, `Entry`, `Minified JS`, `Gzip JS`],\n\t\t\t\treport.recipes.map((row) => [\n\t\t\t\t\tescapeCell(row.name),\n\t\t\t\t\tcode(row.entry),\n\t\t\t\t\tformatBytes(row.rawBytes),\n\t\t\t\t\tformatBytes(row.gzipBytes),\n\t\t\t\t]),\n\t\t\t\t[`left`, `left`, `right`, `right`],\n\t\t\t),\n\t\t)\n\t}\n\n\treturn lines.join(`\\n`)\n}\n\nfunction selectExportImports(\n\tpackageName: string,\n\texportsValue: unknown,\n\tselection: TonnageConfig[`exports`],\n): string[] {\n\tif (selection === false) {\n\t\treturn []\n\t}\n\n\tconst exportKeys = getExportKeys(exportsValue)\n\tconst options: TonnageExports = typeof selection === `object` ? selection : {}\n\tconst included = options.include\n\t\t? [...options.include]\n\t\t: exportKeys.filter((exportKey) => exportKey !== `./package.json`)\n\tconst excluded = new Set(options.exclude ?? [])\n\n\treturn included\n\t\t.filter((exportKey) => !excluded.has(exportKey))\n\t\t.map((exportKey) => toPackageImport(packageName, exportKey))\n}\n\nfunction getExportKeys(exportsValue: unknown): string[] {\n\tif (typeof exportsValue === `string` || Array.isArray(exportsValue)) {\n\t\treturn [`.`]\n\t}\n\tif (!exportsValue || typeof exportsValue !== `object`) {\n\t\treturn [`.`]\n\t}\n\n\tconst keys = Object.keys(exportsValue)\n\treturn keys.some((key) => key.startsWith(`.`)) ? keys : [`.`]\n}\n\nfunction toPackageImport(packageName: string, exportKey: string): string {\n\tif (exportKey === `.`) {\n\t\treturn packageName\n\t}\n\tif (!exportKey.startsWith(`./`)) {\n\t\tthrow new Error(`Invalid package export key: ${exportKey}`)\n\t}\n\treturn `${packageName}/${exportKey.slice(2)}`\n}\n\nfunction formatBytes(bytes: number): string {\n\treturn `${new Intl.NumberFormat(`en-US`).format(bytes)} B`\n}\n\nfunction code(value: string): string {\n\treturn `<code>${escapeCell(value)}</code>`\n}\n\nfunction escapeCell(value: string): string {\n\treturn value.replaceAll(`&`, `&`).replaceAll(`|`, `|`)\n}\n\nfunction unique(values: readonly string[]): string[] {\n\treturn [...new Set(values)]\n}\n\nfunction markdownTable(\n\theadings: readonly string[],\n\trows: readonly (readonly string[])[],\n\talignments: readonly (`left` | `right`)[],\n): string[] {\n\tconst widths = headings.map((heading, column) =>\n\t\tMath.max(heading.length, ...rows.map((row) => row[column]?.length ?? 0), 3),\n\t)\n\tconst renderRow = (cells: readonly string[]): string =>\n\t\t`| ${cells.map((cell, column) => padCell(cell, widths[column] ?? 3, alignments[column] ?? `left`)).join(` | `)} |`\n\tconst separator = widths.map((width, column) =>\n\t\talignments[column] === `right`\n\t\t\t? `${`-`.repeat(width - 1)}:`\n\t\t\t: `-`.repeat(width),\n\t)\n\n\treturn [renderRow(headings), renderRow(separator), ...rows.map(renderRow)]\n}\n\nfunction padCell(\n\tcell: string,\n\twidth: number,\n\talignment: `left` | `right`,\n): string {\n\treturn alignment === `right` ? cell.padStart(width) : cell.padEnd(width)\n}\n","import fs from \"node:fs/promises\"\nimport path from \"node:path\"\n\nimport { updateGeneratedSection } from \"./readme.ts\"\nimport { createTonnageReport, renderTonnageMarkdown } from \"./report.ts\"\nimport type { TonnageConfig, TonnageMode, TonnageRunResult } from \"./types.ts\"\n\nexport async function runTonnage(\n\tconfig: TonnageConfig,\n\toptions: {\n\t\tconfigDirectory?: string\n\t\tmode: TonnageMode\n\t},\n): Promise<TonnageRunResult> {\n\tconst configDirectory = path.resolve(options.configDirectory ?? process.cwd())\n\tconst readmePath = path.resolve(configDirectory, config.readme ?? `README.md`)\n\tconst oldReadme = await fs.readFile(readmePath, `utf8`)\n\tconst report = await createTonnageReport(config, configDirectory)\n\tconst markdown = renderTonnageMarkdown(report, config)\n\tconst newReadme = updateGeneratedSection(oldReadme, markdown, config.marker)\n\tconst changed = oldReadme !== newReadme\n\n\tif (options.mode === `write` && changed) {\n\t\tawait fs.writeFile(readmePath, newReadme)\n\t}\n\n\treturn { changed, readmePath, report }\n}\n"],"mappings":"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"}
|