tonnage 0.0.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/AGENTS.md +10 -0
- package/CHANGELOG.md +18 -0
- package/LICENSE +373 -21
- package/README.md +33 -2
- package/dist/cli.mjs +95 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/{index.d.ts → index.d.mts} +2 -1
- package/dist/index.d.mts.map +1 -0
- package/dist/{index.js → index.mjs} +2 -2
- package/dist/index.mjs.map +1 -0
- package/dist/{run-DIk7L56w.js → run-P2rhqYM3.mjs} +109 -45
- package/dist/run-P2rhqYM3.mjs.map +1 -0
- package/package.json +50 -52
- package/src/cli.ts +10 -18
- package/src/command-line.ts +52 -0
- package/src/measure.ts +22 -2
- package/src/package-resolution.ts +57 -0
- package/src/report.ts +68 -31
- package/bin/tonnage.bin.js +0 -3
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +0 -62
- package/dist/cli.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/run-DIk7L56w.js.map +0 -1
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,
|
package/bin/tonnage.bin.js
DELETED
package/dist/cli.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {}
|
package/dist/cli.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { t as runTonnage } from "./run-DIk7L56w.js";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import fs from "node:fs/promises";
|
|
5
|
-
import { pathToFileURL } from "node:url";
|
|
6
|
-
//#region src/cli.ts
|
|
7
|
-
const DEFAULT_CONFIG_FILES = [
|
|
8
|
-
`tonnage.config.ts`,
|
|
9
|
-
`tonnage.config.mjs`,
|
|
10
|
-
`tonnage.config.js`
|
|
11
|
-
];
|
|
12
|
-
async function main() {
|
|
13
|
-
const command = process.argv[2];
|
|
14
|
-
const mode = parseMode(command);
|
|
15
|
-
const configPath = await findConfig(process.argv[3]);
|
|
16
|
-
const config = await loadConfig(configPath);
|
|
17
|
-
const result = await runTonnage(config, {
|
|
18
|
-
configDirectory: path.dirname(configPath),
|
|
19
|
-
mode
|
|
20
|
-
});
|
|
21
|
-
if (mode === `check` && result.changed) {
|
|
22
|
-
process.stderr.write(`${path.relative(process.cwd(), result.readmePath)} is out of date. Run tonnage write.\n`);
|
|
23
|
-
process.exitCode = 1;
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
const readme = path.relative(process.cwd(), result.readmePath);
|
|
27
|
-
process.stdout.write(result.changed ? `Updated ${readme}.\n` : `${readme} is already up to date.\n`);
|
|
28
|
-
}
|
|
29
|
-
function parseMode(command) {
|
|
30
|
-
switch (command) {
|
|
31
|
-
case `check`:
|
|
32
|
-
case `test`: return `check`;
|
|
33
|
-
case `make`:
|
|
34
|
-
case `write`: return `write`;
|
|
35
|
-
case void 0:
|
|
36
|
-
default: throw new Error(`Usage: tonnage <write|check> [tonnage.config.ts]`);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
async function findConfig(configArgument) {
|
|
40
|
-
if (configArgument) return path.resolve(configArgument);
|
|
41
|
-
for (const filename of DEFAULT_CONFIG_FILES) {
|
|
42
|
-
const candidate = path.resolve(filename);
|
|
43
|
-
try {
|
|
44
|
-
await fs.access(candidate);
|
|
45
|
-
return candidate;
|
|
46
|
-
} catch {}
|
|
47
|
-
}
|
|
48
|
-
throw new Error(`Could not find a tonnage config (${DEFAULT_CONFIG_FILES.join(`, `)}).`);
|
|
49
|
-
}
|
|
50
|
-
async function loadConfig(configPath) {
|
|
51
|
-
const imported = await import(pathToFileURL(configPath).href);
|
|
52
|
-
if (!imported.default || typeof imported.default !== `object`) throw new Error(`${configPath} must default-export a tonnage config.`);
|
|
53
|
-
return imported.default;
|
|
54
|
-
}
|
|
55
|
-
main().catch((error) => {
|
|
56
|
-
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
57
|
-
process.exitCode = 1;
|
|
58
|
-
});
|
|
59
|
-
//#endregion
|
|
60
|
-
export {};
|
|
61
|
-
|
|
62
|
-
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport fs from \"node:fs/promises\"\nimport path from \"node:path\"\nimport { pathToFileURL } from \"node:url\"\n\nimport { runTonnage } from \"./run.ts\"\nimport type { TonnageConfig, TonnageMode } from \"./types.ts\"\n\nconst DEFAULT_CONFIG_FILES = [\n\t`tonnage.config.ts`,\n\t`tonnage.config.mjs`,\n\t`tonnage.config.js`,\n]\n\nasync function main(): Promise<void> {\n\tconst command = process.argv[2]\n\tconst mode = parseMode(command)\n\tconst configPath = await findConfig(process.argv[3])\n\tconst config = await loadConfig(configPath)\n\tconst result = await runTonnage(config, {\n\t\tconfigDirectory: path.dirname(configPath),\n\t\tmode,\n\t})\n\n\tif (mode === `check` && result.changed) {\n\t\tprocess.stderr.write(\n\t\t\t`${path.relative(process.cwd(), result.readmePath)} is out of date. Run tonnage write.\\n`,\n\t\t)\n\t\tprocess.exitCode = 1\n\t\treturn\n\t}\n\n\tconst readme = path.relative(process.cwd(), result.readmePath)\n\tprocess.stdout.write(\n\t\tresult.changed\n\t\t\t? `Updated ${readme}.\\n`\n\t\t\t: `${readme} is already up to date.\\n`,\n\t)\n}\n\nfunction parseMode(command: string | undefined): TonnageMode {\n\tswitch (command) {\n\t\tcase `check`:\n\t\tcase `test`:\n\t\t\treturn `check`\n\t\tcase `make`:\n\t\tcase `write`:\n\t\t\treturn `write`\n\t\tcase undefined:\n\t\tdefault:\n\t\t\tthrow new Error(`Usage: tonnage <write|check> [tonnage.config.ts]`)\n\t}\n}\n\nasync function findConfig(configArgument: string | undefined): Promise<string> {\n\tif (configArgument) {\n\t\treturn path.resolve(configArgument)\n\t}\n\n\tfor (const filename of DEFAULT_CONFIG_FILES) {\n\t\tconst candidate = path.resolve(filename)\n\t\ttry {\n\t\t\tawait fs.access(candidate)\n\t\t\treturn candidate\n\t\t} catch {\n\t\t\t// Keep looking for the next supported config filename.\n\t\t}\n\t}\n\n\tthrow new Error(\n\t\t`Could not find a tonnage config (${DEFAULT_CONFIG_FILES.join(`, `)}).`,\n\t)\n}\n\nasync function loadConfig(configPath: string): Promise<TonnageConfig> {\n\tconst imported = (await import(pathToFileURL(configPath).href)) as {\n\t\tdefault?: unknown\n\t}\n\tif (!imported.default || typeof imported.default !== `object`) {\n\t\tthrow new Error(`${configPath} must default-export a tonnage config.`)\n\t}\n\treturn imported.default\n}\n\nmain().catch((error: unknown) => {\n\tprocess.stderr.write(\n\t\t`${error instanceof Error ? error.message : String(error)}\\n`,\n\t)\n\tprocess.exitCode = 1\n})\n"],"mappings":";;;;;;AASA,MAAM,uBAAuB;CAC5B;CACA;CACA;AACD;AAEA,eAAe,OAAsB;CACpC,MAAM,UAAU,QAAQ,KAAK;CAC7B,MAAM,OAAO,UAAU,OAAO;CAC9B,MAAM,aAAa,MAAM,WAAW,QAAQ,KAAK,EAAE;CACnD,MAAM,SAAS,MAAM,WAAW,UAAU;CAC1C,MAAM,SAAS,MAAM,WAAW,QAAQ;EACvC,iBAAiB,KAAK,QAAQ,UAAU;EACxC;CACD,CAAC;CAED,IAAI,SAAS,WAAW,OAAO,SAAS;EACvC,QAAQ,OAAO,MACd,GAAG,KAAK,SAAS,QAAQ,IAAI,GAAG,OAAO,UAAU,EAAE,sCACpD;EACA,QAAQ,WAAW;EACnB;CACD;CAEA,MAAM,SAAS,KAAK,SAAS,QAAQ,IAAI,GAAG,OAAO,UAAU;CAC7D,QAAQ,OAAO,MACd,OAAO,UACJ,WAAW,OAAO,OAClB,GAAG,OAAO,0BACd;AACD;AAEA,SAAS,UAAU,SAA0C;CAC5D,QAAQ,SAAR;EACC,KAAK;EACL,KAAK,QACJ,OAAO;EACR,KAAK;EACL,KAAK,SACJ,OAAO;EACR,KAAK,KAAA;EACL,SACC,MAAM,IAAI,MAAM,kDAAkD;CACpE;AACD;AAEA,eAAe,WAAW,gBAAqD;CAC9E,IAAI,gBACH,OAAO,KAAK,QAAQ,cAAc;CAGnC,KAAK,MAAM,YAAY,sBAAsB;EAC5C,MAAM,YAAY,KAAK,QAAQ,QAAQ;EACvC,IAAI;GACH,MAAM,GAAG,OAAO,SAAS;GACzB,OAAO;EACR,QAAQ,CAER;CACD;CAEA,MAAM,IAAI,MACT,oCAAoC,qBAAqB,KAAK,IAAI,EAAE,GACrE;AACD;AAEA,eAAe,WAAW,YAA4C;CACrE,MAAM,WAAY,MAAM,OAAO,cAAc,UAAU,CAAC,CAAC;CAGzD,IAAI,CAAC,SAAS,WAAW,OAAO,SAAS,YAAY,UACpD,MAAM,IAAI,MAAM,GAAG,WAAW,uCAAuC;CAEtE,OAAO,SAAS;AACjB;AAEA,KAAK,CAAC,CAAC,OAAO,UAAmB;CAChC,QAAQ,OAAO,MACd,GAAG,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE,GAC3D;CACA,QAAQ,WAAW;AACpB,CAAC"}
|
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","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;iBAEZ,aACrB,eACA,SAAS,sBACP,QAAQ;iBAOW,eACrB,4BACA,SAAS,wBACP,QAAQ;;;iBC7BK,eAAe;EAC9B;EACA;;iBASe,uBACf,gBACA,kBACA;;;iBCGqB,oBACrB,QAAQ,eACR,0BACE,QAAQ;iBAqDK,sBACf,QAAQ,eACR,SAAQ;;;iBCpEa,WACrB,QAAQ,eACR;EACC;EACA,MAAM;IAEL,QAAQ"}
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/define-config.ts"],"sourcesContent":["import type { TonnageConfig } from \"./types.ts\"\n\nexport function defineConfig(config: TonnageConfig): TonnageConfig {\n\treturn config\n}\n"],"mappings":";;AAEA,SAAgB,aAAa,QAAsC;CAClE,OAAO;AACR"}
|
package/dist/run-DIk7L56w.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"run-DIk7L56w.js","names":[],"sources":["../src/measure.ts","../src/readme.ts","../src/report.ts","../src/run.ts"],"sourcesContent":["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","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 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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"}
|