tonnage 0.3.0 → 0.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/dist/index.mjs CHANGED
@@ -1,10 +1,233 @@
1
- import { n as tonnageMarkers, r as updateGeneratedSection, t as runTonnage } from "./run-BJN1MXRf.mjs";
2
- import { a as measureImports, i as measureEntry, n as renderTonnageMarkdown, t as createTonnageReport } from "./report-B3HrJ4UQ.mjs";
1
+ import path from "node:path";
2
+ import fs from "node:fs/promises";
3
+ import { gzipSync } from "node:zlib";
4
+ import { build } from "esbuild";
5
+ import os from "node:os";
6
+ import { jsx } from "galley.md/jsx-runtime";
3
7
  //#region src/define-config.ts
4
8
  function defineConfig(config) {
5
9
  return config;
6
10
  }
7
11
  //#endregion
8
- export { createTonnageReport, defineConfig, measureEntry, measureImports, renderTonnageMarkdown, runTonnage, tonnageMarkers, updateGeneratedSection };
12
+ //#region src/measure.ts
13
+ async function measureEntryWithPlugins(entry, options) {
14
+ return measureBuild({ entryPoints: [path.resolve(options.resolveDirectory, entry)] }, options);
15
+ }
16
+ async function measureImportsWithPlugins(imports, options) {
17
+ if (imports.length === 0) throw new Error(`A tonnage entry must import at least one module.`);
18
+ return measureBuild({ stdin: {
19
+ contents: imports.map((specifier, index) => `export * as entry${index} from ${JSON.stringify(specifier)}`).join(`\n`),
20
+ loader: `js`,
21
+ resolveDir: options.resolveDirectory,
22
+ sourcefile: `tonnage-entry.js`
23
+ } }, options);
24
+ }
25
+ async function measureBuild(entry, options) {
26
+ const platform = options.platform ?? `neutral`;
27
+ const outputFiles = [...(await build({
28
+ bundle: true,
29
+ charset: `utf8`,
30
+ external: [...options.external ?? [], ...platform === `browser` ? [] : [`node:*`]],
31
+ format: `esm`,
32
+ legalComments: `none`,
33
+ logLevel: `silent`,
34
+ ...platform === `neutral` ? { mainFields: [`module`, `main`] } : {},
35
+ minify: true,
36
+ outdir: `out`,
37
+ platform,
38
+ plugins: [...options.plugins ?? []],
39
+ ...entry,
40
+ target: options.target ?? `es2022`,
41
+ treeShaking: true,
42
+ write: false
43
+ })).outputFiles ?? []].filter((output) => output.path.endsWith(`.js`)).sort((left, right) => left.path.localeCompare(right.path));
44
+ if (outputFiles.length === 0) throw new Error(`The tonnage entry did not emit any runtime JavaScript.`);
45
+ const rawBytes = outputFiles.reduce((total, output) => total + output.contents.byteLength, 0);
46
+ return {
47
+ gzipBytes: outputFiles.reduce((total, output) => total + gzipSync(output.contents, { level: 9 }).byteLength, 0),
48
+ rawBytes
49
+ };
50
+ }
51
+ //#endregion
52
+ //#region src/package-resolution.ts
53
+ async function withPackageResolver(packageName, packageDirectory, useResolver) {
54
+ const temporaryDirectory = await fs.mkdtemp(path.join(os.tmpdir(), `tonnage-package-`));
55
+ try {
56
+ const nodeModulesDirectory = path.join(temporaryDirectory, `node_modules`);
57
+ const packageLinkPath = path.resolve(nodeModulesDirectory, packageName);
58
+ if (!packageLinkPath.startsWith(`${nodeModulesDirectory}${path.sep}`)) throw new Error(`Invalid package name: ${packageName}`);
59
+ await fs.mkdir(path.dirname(packageLinkPath), { recursive: true });
60
+ await fs.symlink(packageDirectory, packageLinkPath, process.platform === `win32` ? `junction` : `dir`);
61
+ const filter = new RegExp(`^${escapeRegExp(packageName)}(?:/.*)?$`);
62
+ return await useResolver({
63
+ name: `tonnage-package-resolution`,
64
+ setup(build) {
65
+ build.onResolve({ filter }, async (args) => {
66
+ if (args.resolveDir === temporaryDirectory) return;
67
+ return build.resolve(args.path, {
68
+ importer: args.importer,
69
+ kind: args.kind,
70
+ namespace: args.namespace,
71
+ resolveDir: temporaryDirectory,
72
+ with: args.with
73
+ });
74
+ });
75
+ }
76
+ });
77
+ } finally {
78
+ await fs.rm(temporaryDirectory, {
79
+ force: true,
80
+ recursive: true
81
+ });
82
+ }
83
+ }
84
+ function escapeRegExp(value) {
85
+ return value.replace(/[.*+?^${}()|[\]\\]/g, `\\$&`);
86
+ }
87
+ //#endregion
88
+ //#region src/report.ts
89
+ async function createTonnageReport(config, configDirectory) {
90
+ const packageJsonPath = path.resolve(configDirectory, config.packageJson ?? `package.json`);
91
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, `utf8`));
92
+ if (!packageJson.name) throw new Error(`${packageJsonPath} does not declare a package name.`);
93
+ const resolveDirectory = path.dirname(packageJsonPath);
94
+ const external = unique([...Object.keys(packageJson.peerDependencies ?? {}), ...config.external ?? []]);
95
+ const exportImports = selectExportImports(packageJson.name, packageJson.exports, config.exports);
96
+ const { exportRows, recipeRows } = await withPackageResolver(packageJson.name, resolveDirectory, async (packageResolver) => {
97
+ return {
98
+ exportRows: await Promise.all(exportImports.map(async (specifier) => ({
99
+ ...await measureImportsWithPlugins([specifier], {
100
+ external,
101
+ platform: config.platform,
102
+ plugins: [packageResolver],
103
+ resolveDirectory,
104
+ target: config.target
105
+ }),
106
+ imports: [specifier],
107
+ name: specifier
108
+ }))),
109
+ recipeRows: await Promise.all((config.recipes ?? []).map(async (recipe) => ({
110
+ ...await measureEntryWithPlugins(recipe.entry, {
111
+ external: unique([...external, ...recipe.external ?? []]),
112
+ platform: recipe.platform ?? config.platform,
113
+ plugins: [packageResolver],
114
+ resolveDirectory,
115
+ target: config.target
116
+ }),
117
+ entry: recipe.entry,
118
+ name: recipe.name
119
+ })))
120
+ };
121
+ });
122
+ return {
123
+ exports: exportRows,
124
+ packageName: packageJson.name,
125
+ recipes: recipeRows
126
+ };
127
+ }
128
+ function selectExportImports(packageName, exportsValue, selection) {
129
+ if (selection === false) return [];
130
+ const exportKeys = getExportKeys(exportsValue);
131
+ const options = typeof selection === `object` ? selection : {};
132
+ const included = options.include ? [...options.include] : exportKeys.filter((exportKey) => exportKey !== `./package.json`);
133
+ const excluded = new Set(options.exclude ?? []);
134
+ return included.filter((exportKey) => !excluded.has(exportKey)).map((exportKey) => toPackageImport(packageName, exportKey));
135
+ }
136
+ function getExportKeys(exportsValue) {
137
+ if (typeof exportsValue === `string` || Array.isArray(exportsValue)) return [`.`];
138
+ if (!exportsValue || typeof exportsValue !== `object`) return [`.`];
139
+ const keys = Object.keys(exportsValue);
140
+ return keys.some((key) => key.startsWith(`.`)) ? keys : [`.`];
141
+ }
142
+ function toPackageImport(packageName, exportKey) {
143
+ if (exportKey === `.`) return packageName;
144
+ if (!exportKey.startsWith(`./`)) throw new Error(`Invalid package export key: ${exportKey}`);
145
+ return `${packageName}/${exportKey.slice(2)}`;
146
+ }
147
+ function unique(values) {
148
+ return [...new Set(values)];
149
+ }
150
+ //#endregion
151
+ //#region src/report-content.ts
152
+ /** Build the report's headings, tables, and measurement notes as Galley elements. */
153
+ function reportContent(report, config = {}) {
154
+ const hasExports = report.exports.length > 0;
155
+ const hasRecipes = report.recipes.length > 0;
156
+ const hasMultipleSections = hasExports && hasRecipes;
157
+ const content = [jsx("h2", { children: config.heading ?? "Bundle size" })];
158
+ if (hasExports) content.push(jsx("p", { children: "Package export sizes include complete runtime export surfaces." }));
159
+ if (hasRecipes) content.push(jsx("p", { children: "Usage example sizes bundle their entry files and tree-shake unused exports." }));
160
+ content.push(jsx("p", { children: "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." }));
161
+ if (hasExports) {
162
+ if (hasMultipleSections) content.push(jsx("h3", { children: "Package exports" }));
163
+ content.push(table([
164
+ { title: "Import" },
165
+ {
166
+ title: "Minified JS",
167
+ align: "right"
168
+ },
169
+ {
170
+ title: "Gzip JS",
171
+ align: "right"
172
+ }
173
+ ], report.exports.map((row) => [
174
+ code(row.name),
175
+ formatBytes(row.rawBytes),
176
+ formatBytes(row.gzipBytes)
177
+ ])));
178
+ }
179
+ if (hasRecipes) {
180
+ if (hasMultipleSections) content.push(jsx("h3", { children: "Usage examples" }));
181
+ content.push(table([
182
+ { title: "Recipe" },
183
+ { title: "Entry" },
184
+ {
185
+ title: "Minified JS",
186
+ align: "right"
187
+ },
188
+ {
189
+ title: "Gzip JS",
190
+ align: "right"
191
+ }
192
+ ], report.recipes.map((row) => [
193
+ row.name,
194
+ code(row.entry),
195
+ formatBytes(row.rawBytes),
196
+ formatBytes(row.gzipBytes)
197
+ ])));
198
+ }
199
+ content.push(jsx("p", { children: [
200
+ "Report maintained with ",
201
+ jsx("a", {
202
+ href: "https://github.com/jeremybanka/tonnage",
203
+ children: "tonnage"
204
+ }),
205
+ "."
206
+ ] }));
207
+ return content;
208
+ }
209
+ function code(value) {
210
+ return {
211
+ type: "inlineCode",
212
+ value
213
+ };
214
+ }
215
+ function formatBytes(bytes) {
216
+ return `${new Intl.NumberFormat("en-US").format(bytes)} B`;
217
+ }
218
+ function table(columns, rows) {
219
+ return jsx("table", { children: [jsx("thead", { children: jsx("tr", { children: columns.map(({ title, align }) => jsx("th", {
220
+ children: title,
221
+ align
222
+ })) }) }), jsx("tbody", { children: rows.map((cells) => jsx("tr", { children: cells.map((children) => jsx("td", { children })) })) })] });
223
+ }
224
+ //#endregion
225
+ //#region src/galley.ts
226
+ /** Measure bundles and include the report in a galley.md document. */
227
+ async function Tonnage({ config = {}, directory }, context) {
228
+ return reportContent(await createTonnageReport(config, path.resolve(context.directory, directory ?? ".")), config);
229
+ }
230
+ //#endregion
231
+ export { Tonnage, defineConfig };
9
232
 
10
233
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/define-config.ts","../src/measure.ts","../src/package-resolution.ts","../src/report.ts","../src/report-content.ts","../src/galley.ts"],"sourcesContent":["import type { TonnageConfig } from \"./types.ts\"\n\nexport function defineConfig(config: TonnageConfig): TonnageConfig {\n\treturn config\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\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 unique(values: readonly string[]): string[] {\n\treturn [...new Set(values)]\n}\n","import type { Content, Element } from \"galley.md\"\nimport { jsx } from \"galley.md/jsx-runtime\"\n\nimport type { TonnageConfig, TonnageReport } from \"./types.ts\"\n\n/** Build the report's headings, tables, and measurement notes as Galley elements. */\nexport function reportContent(\n\treport: TonnageReport,\n\tconfig: TonnageConfig = {},\n): Element[] {\n\tconst hasExports = report.exports.length > 0\n\tconst hasRecipes = report.recipes.length > 0\n\tconst hasMultipleSections = hasExports && hasRecipes\n\tconst content = [jsx(\"h2\", { children: config.heading ?? \"Bundle size\" })]\n\n\tif (hasExports)\n\t\tcontent.push(\n\t\t\tjsx(\"p\", {\n\t\t\t\tchildren:\n\t\t\t\t\t\"Package export sizes include complete runtime export surfaces.\",\n\t\t\t}),\n\t\t)\n\tif (hasRecipes)\n\t\tcontent.push(\n\t\t\tjsx(\"p\", {\n\t\t\t\tchildren:\n\t\t\t\t\t\"Usage example sizes bundle their entry files and tree-shake unused exports.\",\n\t\t\t}),\n\t\t)\n\tcontent.push(\n\t\tjsx(\"p\", {\n\t\t\tchildren:\n\t\t\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\t}),\n\t)\n\n\tif (hasExports) {\n\t\tif (hasMultipleSections)\n\t\t\tcontent.push(jsx(\"h3\", { children: \"Package exports\" }))\n\t\tcontent.push(\n\t\t\ttable(\n\t\t\t\t[\n\t\t\t\t\t{ title: \"Import\" },\n\t\t\t\t\t{ title: \"Minified JS\", align: \"right\" },\n\t\t\t\t\t{ title: \"Gzip JS\", align: \"right\" },\n\t\t\t\t],\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),\n\t\t)\n\t}\n\n\tif (hasRecipes) {\n\t\tif (hasMultipleSections)\n\t\t\tcontent.push(jsx(\"h3\", { children: \"Usage examples\" }))\n\t\tcontent.push(\n\t\t\ttable(\n\t\t\t\t[\n\t\t\t\t\t{ title: \"Recipe\" },\n\t\t\t\t\t{ title: \"Entry\" },\n\t\t\t\t\t{ title: \"Minified JS\", align: \"right\" },\n\t\t\t\t\t{ title: \"Gzip JS\", align: \"right\" },\n\t\t\t\t],\n\t\t\t\treport.recipes.map((row) => [\n\t\t\t\t\trow.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),\n\t\t)\n\t}\n\n\tcontent.push(\n\t\tjsx(\"p\", {\n\t\t\tchildren: [\n\t\t\t\t\"Report maintained with \",\n\t\t\t\tjsx(\"a\", {\n\t\t\t\t\thref: \"https://github.com/jeremybanka/tonnage\",\n\t\t\t\t\tchildren: \"tonnage\",\n\t\t\t\t}),\n\t\t\t\t\".\",\n\t\t\t],\n\t\t}),\n\t)\n\treturn content\n}\n\nfunction code(value: string): Content {\n\treturn { type: \"inlineCode\", value }\n}\n\nfunction formatBytes(bytes: number): string {\n\treturn `${new Intl.NumberFormat(\"en-US\").format(bytes)} B`\n}\n\nfunction table(\n\tcolumns: readonly { title: string; align?: \"right\" }[],\n\trows: readonly (readonly Content[])[],\n): Element {\n\treturn jsx(\"table\", {\n\t\tchildren: [\n\t\t\tjsx(\"thead\", {\n\t\t\t\tchildren: jsx(\"tr\", {\n\t\t\t\t\tchildren: columns.map(({ title, align }) =>\n\t\t\t\t\t\tjsx(\"th\", { children: title, align }),\n\t\t\t\t\t),\n\t\t\t\t}),\n\t\t\t}),\n\t\t\tjsx(\"tbody\", {\n\t\t\t\tchildren: rows.map((cells) =>\n\t\t\t\t\tjsx(\"tr\", {\n\t\t\t\t\t\tchildren: cells.map((children) => jsx(\"td\", { children })),\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t}),\n\t\t],\n\t})\n}\n","import path from \"node:path\"\n\nimport type { DocumentContext, Element } from \"galley.md\"\n\nimport { createTonnageReport } from \"./report.ts\"\nimport { reportContent } from \"./report-content.ts\"\nimport type { TonnageConfig } from \"./types.ts\"\n\nexport type TonnageProps = {\n\tconfig?: TonnageConfig\n\t/** Override the document directory when measuring a different package. */\n\tdirectory?: string\n}\n\n/** Measure bundles and include the report in a galley.md document. */\nexport async function Tonnage(\n\t{ config = {}, directory }: TonnageProps,\n\tcontext: DocumentContext,\n): Promise<Element[]> {\n\tconst report = await createTonnageReport(\n\t\tconfig,\n\t\tpath.resolve(context.directory, directory ?? \".\"),\n\t)\n\treturn reportContent(report, config)\n}\n"],"mappings":"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"}
@@ -5,7 +5,6 @@ const config: TonnageConfig = defineConfig({
5
5
  exports: {
6
6
  exclude: [`./package.json`],
7
7
  },
8
- marker: `my-package`,
9
8
  recipes: [
10
9
  {
11
10
  entry: `examples/recipes/react-app.ts`,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tonnage",
3
- "version": "0.3.0",
4
- "description": "Keep deterministic bundle size reports in README files and CI.",
3
+ "version": "0.4.0",
4
+ "description": "Bundle size reports for Galley documents.",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/jeremybanka/tonnage.git"
@@ -10,21 +10,14 @@
10
10
  "author": "Jeremy Banka",
11
11
  "type": "module",
12
12
  "exports": {
13
- "./galley": {
14
- "types": "./dist/galley.d.mts",
15
- "import": "./dist/galley.mjs"
16
- },
17
13
  ".": {
18
- "import": "./dist/index.mjs",
19
- "types": "./dist/index.d.mts"
14
+ "types": "./dist/index.d.mts",
15
+ "import": "./dist/index.mjs"
20
16
  },
21
17
  "./package.json": "./package.json"
22
18
  },
23
19
  "main": "./dist/index.mjs",
24
20
  "types": "./dist/index.d.mts",
25
- "bin": {
26
- "tonnage": "dist/cli.mjs"
27
- },
28
21
  "files": [
29
22
  "AGENTS.md",
30
23
  "CHANGELOG.md",
@@ -35,14 +28,13 @@
35
28
  "LICENSE"
36
29
  ],
37
30
  "dependencies": {
38
- "comline": "0.9.0",
39
31
  "esbuild": "0.28.2"
40
32
  },
41
33
  "devDependencies": {
42
34
  "@types/node": "26.6.5",
43
35
  "breakage": "0.8.5",
44
36
  "galley.md": "0.1.1",
45
- "typescript": "7.1.0-dev.20261020.3"
37
+ "typescript": "7.1.0-dev.20261021.1"
46
38
  },
47
39
  "peerDependencies": {
48
40
  "galley.md": "^0.1.1"
package/src/index.ts CHANGED
@@ -1,18 +1,9 @@
1
1
  export { defineConfig } from "./define-config.ts"
2
- export type { MeasureEntryOptions, MeasureImportsOptions } from "./measure.ts"
3
- export { measureEntry, measureImports } from "./measure.ts"
4
- export { tonnageMarkers, updateGeneratedSection } from "./readme.ts"
5
- export { createTonnageReport, renderTonnageMarkdown } from "./report.ts"
6
- export { runTonnage } from "./run.ts"
2
+ export { Tonnage } from "./galley.ts"
3
+ export type { TonnageProps } from "./galley.ts"
7
4
  export type {
8
- BundleMeasurement,
9
5
  BundlePlatform,
10
6
  TonnageConfig,
11
7
  TonnageExports,
12
- TonnageMode,
13
8
  TonnageRecipe,
14
- TonnageRecipeRow,
15
- TonnageReport,
16
- TonnageRow,
17
- TonnageRunResult,
18
9
  } from "./types.ts"
@@ -3,7 +3,7 @@ import { jsx } from "galley.md/jsx-runtime"
3
3
 
4
4
  import type { TonnageConfig, TonnageReport } from "./types.ts"
5
5
 
6
- /** Use Galley's JSX runtime directly so the source CLI stays executable by Node. */
6
+ /** Build the report's headings, tables, and measurement notes as Galley elements. */
7
7
  export function reportContent(
8
8
  report: TonnageReport,
9
9
  config: TonnageConfig = {},
package/src/report.ts CHANGED
@@ -1,14 +1,11 @@
1
1
  import fs from "node:fs/promises"
2
2
  import path from "node:path"
3
3
 
4
- import { renderMarkdown } from "galley.md"
5
-
6
4
  import {
7
5
  measureEntryWithPlugins,
8
6
  measureImportsWithPlugins,
9
7
  } from "./measure.ts"
10
8
  import { withPackageResolver } from "./package-resolution.ts"
11
- import { reportContent } from "./report-content.ts"
12
9
  import type {
13
10
  TonnageConfig,
14
11
  TonnageExports,
@@ -88,18 +85,6 @@ export async function createTonnageReport(
88
85
  }
89
86
  }
90
87
 
91
- /** Serialize a report with the same Galley elements used by the document component. */
92
- export async function renderTonnageMarkdown(
93
- report: TonnageReport,
94
- config: TonnageConfig = {},
95
- ): Promise<string> {
96
- const directory = process.cwd()
97
- return renderMarkdown(reportContent(report, config), {
98
- directory,
99
- path: path.resolve(directory, config.readme ?? "README.md"),
100
- })
101
- }
102
-
103
88
  function selectExportImports(
104
89
  packageName: string,
105
90
  exportsValue: unknown,
package/src/types.ts CHANGED
@@ -16,10 +16,8 @@ export type TonnageConfig = {
16
16
  exports?: boolean | TonnageExports
17
17
  external?: readonly string[]
18
18
  heading?: string
19
- marker?: string
20
19
  packageJson?: string
21
20
  platform?: BundlePlatform
22
- readme?: string
23
21
  recipes?: readonly TonnageRecipe[]
24
22
  target?: string
25
23
  }
@@ -44,11 +42,3 @@ export type TonnageReport = {
44
42
  packageName: string
45
43
  recipes: readonly TonnageRecipeRow[]
46
44
  }
47
-
48
- export type TonnageMode = `check` | `write`
49
-
50
- export type TonnageRunResult = {
51
- changed: boolean
52
- readmePath: string
53
- report: TonnageReport
54
- }
package/dist/cli.mjs DELETED
@@ -1,113 +0,0 @@
1
- #!/usr/bin/env node
2
- import { t as runTonnage } from "./run-BJN1MXRf.mjs";
3
- import fs from "node:fs/promises";
4
- import path from "node:path";
5
- import { pathToFileURL } from "node:url";
6
- import { cli, completionResponse, help, helpOption, logWarnings, optional } from "comline";
7
- //#region src/command-line.ts
8
- const ROOT_MANUAL = helpOption(`Show help for the tonnage command line.`);
9
- const WRITE_MANUAL = helpOption(`Update the configured README report.`);
10
- const CHECK_MANUAL = helpOption(`Check that the configured README report is up to date.`);
11
- const tonnageCli = cli({
12
- cliName: `tonnage`,
13
- cliDescription: `Keep deterministic bundle size reports in README files and CI.`,
14
- routes: optional({
15
- check: optional({ $configPath: null }),
16
- write: optional({ $configPath: null })
17
- }),
18
- routeOptions: {
19
- "": ROOT_MANUAL,
20
- check: CHECK_MANUAL,
21
- "check/$configPath": CHECK_MANUAL,
22
- write: WRITE_MANUAL,
23
- "write/$configPath": WRITE_MANUAL
24
- },
25
- positionalCompletions: {
26
- "check/$configPath": { fileSystem: `files` },
27
- "write/$configPath": { fileSystem: `files` }
28
- },
29
- discoverConfigPath: () => void 0
30
- });
31
- function completeTonnageCli(args) {
32
- return completionResponse(tonnageCli.definition, args);
33
- }
34
- function parseTonnageCli(args) {
35
- const { inputs, warnings } = tonnageCli(args);
36
- if (inputs.case === `` || inputs.opts.help) return {
37
- kind: `help`,
38
- warnings
39
- };
40
- const mode = inputs.case === `write` || inputs.case === `write/$configPath` ? `write` : `check`;
41
- const configArgument = inputs.path[1];
42
- return configArgument === void 0 ? {
43
- kind: `run`,
44
- mode,
45
- warnings
46
- } : {
47
- configArgument,
48
- kind: `run`,
49
- mode,
50
- warnings
51
- };
52
- }
53
- function renderTonnageCliHelp() {
54
- return help(tonnageCli.definition);
55
- }
56
- //#endregion
57
- //#region src/cli.ts
58
- const DEFAULT_CONFIG_FILES = [
59
- `tonnage.config.ts`,
60
- `tonnage.config.mjs`,
61
- `tonnage.config.js`
62
- ];
63
- async function main() {
64
- const completion = await completeTonnageCli(process.argv);
65
- if (completion !== void 0) {
66
- process.stdout.write(completion);
67
- return;
68
- }
69
- const invocation = parseTonnageCli(process.argv);
70
- logWarnings(invocation.warnings);
71
- if (invocation.kind === `help`) {
72
- process.stdout.write(renderTonnageCliHelp());
73
- return;
74
- }
75
- const { configArgument, mode } = invocation;
76
- const configPath = await findConfig(configArgument);
77
- const config = await loadConfig(configPath);
78
- const result = await runTonnage(config, {
79
- configDirectory: path.dirname(configPath),
80
- mode
81
- });
82
- if (mode === `check` && result.changed) {
83
- process.stderr.write(`${path.relative(process.cwd(), result.readmePath)} is out of date. Run tonnage write.\n`);
84
- process.exitCode = 1;
85
- return;
86
- }
87
- const readme = path.relative(process.cwd(), result.readmePath);
88
- process.stdout.write(result.changed ? `Updated ${readme}.\n` : `${readme} is already up to date.\n`);
89
- }
90
- async function findConfig(configArgument) {
91
- if (configArgument) return path.resolve(configArgument);
92
- for (const filename of DEFAULT_CONFIG_FILES) {
93
- const candidate = path.resolve(filename);
94
- try {
95
- await fs.access(candidate);
96
- return candidate;
97
- } catch {}
98
- }
99
- throw new Error(`Could not find a tonnage config (${DEFAULT_CONFIG_FILES.join(`, `)}).`);
100
- }
101
- async function loadConfig(configPath) {
102
- const imported = await import(pathToFileURL(configPath).href);
103
- if (!imported.default || typeof imported.default !== `object`) throw new Error(`${configPath} must default-export a tonnage config.`);
104
- return imported.default;
105
- }
106
- main().catch((error) => {
107
- process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
108
- process.exitCode = 1;
109
- });
110
- //#endregion
111
- export {};
112
-
113
- //# sourceMappingURL=cli.mjs.map
package/dist/cli.mjs.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"cli.mjs","names":[],"sources":["../src/command-line.ts","../src/cli.ts"],"sourcesContent":["import {\n\ttype CliWarning,\n\tcli,\n\tcompletionResponse,\n\thelp,\n\thelpOption,\n\toptional,\n} from \"comline\"\n\nimport type { TonnageMode } from \"./types.ts\"\n\nconst ROOT_MANUAL = helpOption(`Show help for the tonnage command line.`)\nconst WRITE_MANUAL = helpOption(`Update the configured README report.`)\nconst CHECK_MANUAL = helpOption(\n\t`Check that the configured README report is up to date.`,\n)\n\nconst tonnageCli = cli({\n\tcliName: `tonnage`,\n\tcliDescription: `Keep deterministic bundle size reports in README files and CI.`,\n\troutes: optional({\n\t\tcheck: optional({ $configPath: null }),\n\t\twrite: optional({ $configPath: null }),\n\t}),\n\trouteOptions: {\n\t\t\"\": ROOT_MANUAL,\n\t\tcheck: CHECK_MANUAL,\n\t\t\"check/$configPath\": CHECK_MANUAL,\n\t\twrite: WRITE_MANUAL,\n\t\t\"write/$configPath\": WRITE_MANUAL,\n\t},\n\tpositionalCompletions: {\n\t\t\"check/$configPath\": { fileSystem: `files` },\n\t\t\"write/$configPath\": { fileSystem: `files` },\n\t},\n\tdiscoverConfigPath: () => undefined,\n})\n\nexport type TonnageCliInvocation = { warnings: CliWarning[] } & (\n\t| { kind: `help` }\n\t| { configArgument?: string; kind: `run`; mode: TonnageMode }\n)\n\nexport function completeTonnageCli(\n\targs: string[],\n): Promise<string | undefined> {\n\treturn completionResponse(tonnageCli.definition, args)\n}\n\nexport function parseTonnageCli(args: string[]): TonnageCliInvocation {\n\tconst { inputs, warnings } = tonnageCli(args)\n\n\tif (inputs.case === `` || inputs.opts.help) {\n\t\treturn { kind: `help`, warnings }\n\t}\n\n\tconst mode =\n\t\tinputs.case === `write` || inputs.case === `write/$configPath`\n\t\t\t? `write`\n\t\t\t: `check`\n\tconst configArgument = inputs.path[1]\n\n\treturn configArgument === undefined\n\t\t? { kind: `run`, mode, warnings }\n\t\t: { configArgument, kind: `run`, mode, warnings }\n}\n\nexport function renderTonnageCliHelp(): string {\n\treturn help(tonnageCli.definition)\n}\n","#!/usr/bin/env node\n\nimport fs from \"node:fs/promises\"\nimport path from \"node:path\"\nimport { pathToFileURL } from \"node:url\"\n\nimport { logWarnings } from \"comline\"\n\nimport {\n\tcompleteTonnageCli,\n\tparseTonnageCli,\n\trenderTonnageCliHelp,\n} from \"./command-line.ts\"\nimport { runTonnage } from \"./run.ts\"\nimport type { TonnageConfig } 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 completion = await completeTonnageCli(process.argv)\n\tif (completion !== undefined) {\n\t\tprocess.stdout.write(completion)\n\t\treturn\n\t}\n\n\tconst invocation = parseTonnageCli(process.argv)\n\tlogWarnings(invocation.warnings)\n\tif (invocation.kind === `help`) {\n\t\tprocess.stdout.write(renderTonnageCliHelp())\n\t\treturn\n\t}\n\n\tconst { configArgument, mode } = invocation\n\tconst configPath = await findConfig(configArgument)\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\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":"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"}
package/dist/galley.d.mts DELETED
@@ -1,12 +0,0 @@
1
- import { r as TonnageConfig } from "./types-Ddtp95Z3.mjs";
2
- import { DocumentContext, Element } from "galley.md";
3
- //#region src/galley.d.ts
4
- export type TonnageProps = {
5
- config?: TonnageConfig;
6
- /** Override the document directory when measuring a different package. */
7
- directory?: string;
8
- };
9
- /** Measure bundles and include the report in a galley.md document. */
10
- export declare function Tonnage({ config, directory }: TonnageProps, context: DocumentContext): Promise<Element[]>;
11
- //#endregion
12
- //# sourceMappingURL=galley.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"galley.d.mts","names":[],"sources":["../src/galley.ts"],"mappings":";;;YAQY;EACX,SAAS;;EAET;;;wBAIqB,UACnB,QAAa,aAAa,cAC5B,SAAS,kBACP,QAAQ"}
package/dist/galley.mjs DELETED
@@ -1,12 +0,0 @@
1
- import { r as reportContent, t as createTonnageReport } from "./report-B3HrJ4UQ.mjs";
2
- import path from "node:path";
3
- //#region src/galley.ts
4
- /** Measure bundles and include the report in a galley.md document. */
5
- async function Tonnage({ config = {}, directory }, context) {
6
- const report = await createTonnageReport(config, path.resolve(context.directory, directory ?? "."));
7
- return reportContent(report, config);
8
- }
9
- //#endregion
10
- export { Tonnage };
11
-
12
- //# sourceMappingURL=galley.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"galley.mjs","names":[],"sources":["../src/galley.ts"],"sourcesContent":["import path from \"node:path\"\n\nimport type { DocumentContext, Element } from \"galley.md\"\n\nimport { createTonnageReport } from \"./report.ts\"\nimport { reportContent } from \"./report-content.ts\"\nimport type { TonnageConfig } from \"./types.ts\"\n\nexport type TonnageProps = {\n\tconfig?: TonnageConfig\n\t/** Override the document directory when measuring a different package. */\n\tdirectory?: string\n}\n\n/** Measure bundles and include the report in a galley.md document. */\nexport async function Tonnage(\n\t{ config = {}, directory }: TonnageProps,\n\tcontext: DocumentContext,\n): Promise<Element[]> {\n\tconst report = await createTonnageReport(\n\t\tconfig,\n\t\tpath.resolve(context.directory, directory ?? \".\"),\n\t)\n\treturn reportContent(report, config)\n}\n"],"mappings":";;;;AAeA,eAAsB,QACrB,EAAE,SAAS,CAAC,GAAG,aACf,SACqB;CACrB,MAAM,SAAS,MAAM,oBACpB,QACA,KAAK,QAAQ,QAAQ,WAAW,aAAa,GAAG,CACjD;CACA,OAAO,cAAc,QAAQ,MAAM;AACpC"}