@prairielearn/compiled-assets 4.0.1 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @prairielearn/compiled-assets
2
2
 
3
+ ## 4.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - a6b3d9d: Emit `sizes.json` with per-entry-point bundle sizes during build
8
+
3
9
  ## 4.0.1
4
10
 
5
11
  ### Patch Changes
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"","sourcesContent":["#!/usr/bin/env node\nimport path from 'path';\nimport { promisify } from 'util';\nimport zlib from 'zlib';\n\nimport { program } from 'commander';\nimport fs from 'fs-extra';\nimport prettyBytes from 'pretty-bytes';\n\nimport { type AssetsManifest, build } from './index.js';\n\nconst gzip = promisify(zlib.gzip);\nconst brotli = promisify(zlib.brotliCompress);\n\ninterface AssetSizes {\n raw: number;\n gzip: number;\n brotli: number;\n}\n\ntype CompressedSizes = Record<string, AssetSizes>;\n\n/**\n * Collects all unique asset paths from the manifest, including entry points and their preloads.\n */\nfunction getAllAssetPaths(manifest: AssetsManifest): Set<string> {\n const allPaths = new Set<string>();\n for (const asset of Object.values(manifest)) {\n allPaths.add(asset.assetPath);\n for (const preload of asset.preloads) {\n allPaths.add(preload);\n }\n }\n return allPaths;\n}\n\n/**\n * Writes gzip and brotli compressed versions of the assets in the specified directory.\n * It reads each asset file, compresses it using gzip and brotli algorithms, and writes the compressed files\n * with appropriate extensions (.gz and .br) in the same directory.\n *\n * @param destination Directory where the compressed assets will be written.\n * @param manifest The assets manifest containing information about the assets.\n * @returns A promise that resolves to an object containing the sizes of the original, gzip, and brotli compressed assets.\n */\nasync function writeCompressedAssets(\n destination: string,\n manifest: AssetsManifest,\n): Promise<CompressedSizes> {\n const compressedSizes: CompressedSizes = {};\n const allAssetPaths = getAllAssetPaths(manifest);\n\n await Promise.all(\n [...allAssetPaths].map(async (assetPath) => {\n const destinationFilePath = path.resolve(destination, assetPath);\n const contents = await fs.readFile(destinationFilePath);\n const gzipCompressed = await gzip(contents);\n const brotliCompressed = await brotli(contents);\n await fs.writeFile(`${destinationFilePath}.gz`, gzipCompressed);\n await fs.writeFile(`${destinationFilePath}.br`, brotliCompressed);\n compressedSizes[assetPath] = {\n raw: contents.length,\n gzip: gzipCompressed.length,\n brotli: brotliCompressed.length,\n };\n }),\n );\n return compressedSizes;\n}\n\n/**\n * Calculates the total size of an entry point including all its preloaded chunks.\n */\nfunction calculateTotalSizes(\n asset: AssetsManifest[string],\n compressedSizes: CompressedSizes,\n): AssetSizes {\n const entrySizes = compressedSizes[asset.assetPath];\n const total: AssetSizes = {\n raw: entrySizes.raw,\n gzip: entrySizes.gzip,\n brotli: entrySizes.brotli,\n };\n\n for (const preload of asset.preloads) {\n const preloadSizes = compressedSizes[preload];\n if (preloadSizes) {\n total.raw += preloadSizes.raw;\n total.gzip += preloadSizes.gzip;\n total.brotli += preloadSizes.brotli;\n }\n }\n\n return total;\n}\n\nprogram.command('build <source> <destination>').action(async (source, destination) => {\n const manifest = await build(source, destination);\n\n // Write gzip and brotli versions of the output files. Record size information\n // so we can show it to the user.\n const compressedSizes = await writeCompressedAssets(destination, manifest);\n\n // Format the output into an object that we can pass to `console.table`.\n const results: Record<string, any> = {};\n Object.entries(manifest).forEach(([entryPoint, asset]) => {\n const totalSizes = calculateTotalSizes(asset, compressedSizes);\n\n results[entryPoint] = {\n 'Output file': asset.assetPath,\n Size: prettyBytes(totalSizes.raw),\n 'Size (gzip)': prettyBytes(totalSizes.gzip),\n 'Size (brotli)': prettyBytes(totalSizes.brotli),\n };\n });\n console.table(results);\n});\n\nprogram.parseAsync(process.argv).catch((err) => {\n console.error(err);\n process.exit(1);\n});\n"]}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"","sourcesContent":["#!/usr/bin/env node\nimport path from 'path';\nimport { promisify } from 'util';\nimport zlib from 'zlib';\n\nimport { program } from 'commander';\nimport fs from 'fs-extra';\nimport prettyBytes from 'pretty-bytes';\n\nimport { type AssetsManifest, build } from './index.js';\n\nconst gzip = promisify(zlib.gzip);\nconst brotli = promisify(zlib.brotliCompress);\n\ninterface AssetSizes {\n raw: number;\n gzip: number;\n brotli: number;\n}\n\ntype CompressedSizes = Record<string, AssetSizes>;\n\n/**\n * Collects all unique asset paths from the manifest, including entry points and their preloads.\n */\nfunction getAllAssetPaths(manifest: AssetsManifest): Set<string> {\n const allPaths = new Set<string>();\n for (const asset of Object.values(manifest)) {\n allPaths.add(asset.assetPath);\n for (const preload of asset.preloads) {\n allPaths.add(preload);\n }\n }\n return allPaths;\n}\n\n/**\n * Writes gzip and brotli compressed versions of the assets in the specified directory.\n * It reads each asset file, compresses it using gzip and brotli algorithms, and writes the compressed files\n * with appropriate extensions (.gz and .br) in the same directory.\n *\n * @param destination Directory where the compressed assets will be written.\n * @param manifest The assets manifest containing information about the assets.\n * @returns A promise that resolves to an object containing the sizes of the original, gzip, and brotli compressed assets.\n */\nasync function writeCompressedAssets(\n destination: string,\n manifest: AssetsManifest,\n): Promise<CompressedSizes> {\n const compressedSizes: CompressedSizes = {};\n const allAssetPaths = getAllAssetPaths(manifest);\n\n await Promise.all(\n [...allAssetPaths].map(async (assetPath) => {\n const destinationFilePath = path.resolve(destination, assetPath);\n const contents = await fs.readFile(destinationFilePath);\n const gzipCompressed = await gzip(contents);\n const brotliCompressed = await brotli(contents);\n await fs.writeFile(`${destinationFilePath}.gz`, gzipCompressed);\n await fs.writeFile(`${destinationFilePath}.br`, brotliCompressed);\n compressedSizes[assetPath] = {\n raw: contents.length,\n gzip: gzipCompressed.length,\n brotli: brotliCompressed.length,\n };\n }),\n );\n return compressedSizes;\n}\n\n/**\n * Calculates the total size of an entry point including all its preloaded chunks.\n */\nfunction calculateTotalSizes(\n asset: AssetsManifest[string],\n compressedSizes: CompressedSizes,\n): AssetSizes {\n const entrySizes = compressedSizes[asset.assetPath];\n const total: AssetSizes = {\n raw: entrySizes.raw,\n gzip: entrySizes.gzip,\n brotli: entrySizes.brotli,\n };\n\n for (const preload of asset.preloads) {\n const preloadSizes = compressedSizes[preload];\n if (preloadSizes) {\n total.raw += preloadSizes.raw;\n total.gzip += preloadSizes.gzip;\n total.brotli += preloadSizes.brotli;\n }\n }\n\n return total;\n}\n\nprogram.command('build <source> <destination>').action(async (source, destination) => {\n const manifest = await build(source, destination);\n\n // Write gzip and brotli versions of the output files. Record size information\n // so we can show it to the user.\n const compressedSizes = await writeCompressedAssets(destination, manifest);\n\n // Format the output into an object that we can pass to `console.table`.\n const results: Record<string, any> = {};\n const sizesJson: Record<string, AssetSizes> = {};\n Object.entries(manifest).forEach(([entryPoint, asset]) => {\n const totalSizes = calculateTotalSizes(asset, compressedSizes);\n\n results[entryPoint] = {\n 'Output file': asset.assetPath,\n Size: prettyBytes(totalSizes.raw),\n 'Size (gzip)': prettyBytes(totalSizes.gzip),\n 'Size (brotli)': prettyBytes(totalSizes.brotli),\n };\n\n sizesJson[entryPoint] = totalSizes;\n });\n console.table(results);\n\n // Write the result for processing by other tools (e.g. our bundle size reporting action).\n await fs.writeJSON(path.resolve(destination, 'sizes.json'), sizesJson, { spaces: 2 });\n});\n\nprogram.parseAsync(process.argv).catch((err) => {\n console.error(err);\n process.exit(1);\n});\n"]}
package/dist/cli.js CHANGED
@@ -75,6 +75,7 @@ program.command('build <source> <destination>').action(async (source, destinatio
75
75
  const compressedSizes = await writeCompressedAssets(destination, manifest);
76
76
  // Format the output into an object that we can pass to `console.table`.
77
77
  const results = {};
78
+ const sizesJson = {};
78
79
  Object.entries(manifest).forEach(([entryPoint, asset]) => {
79
80
  const totalSizes = calculateTotalSizes(asset, compressedSizes);
80
81
  results[entryPoint] = {
@@ -83,8 +84,11 @@ program.command('build <source> <destination>').action(async (source, destinatio
83
84
  'Size (gzip)': prettyBytes(totalSizes.gzip),
84
85
  'Size (brotli)': prettyBytes(totalSizes.brotli),
85
86
  };
87
+ sizesJson[entryPoint] = totalSizes;
86
88
  });
87
89
  console.table(results);
90
+ // Write the result for processing by other tools (e.g. our bundle size reporting action).
91
+ await fs.writeJSON(path.resolve(destination, 'sizes.json'), sizesJson, { spaces: 2 });
88
92
  });
89
93
  program.parseAsync(process.argv).catch((err) => {
90
94
  console.error(err);
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\nimport path from 'path';\nimport { promisify } from 'util';\nimport zlib from 'zlib';\n\nimport { program } from 'commander';\nimport fs from 'fs-extra';\nimport prettyBytes from 'pretty-bytes';\n\nimport { type AssetsManifest, build } from './index.js';\n\nconst gzip = promisify(zlib.gzip);\nconst brotli = promisify(zlib.brotliCompress);\n\ninterface AssetSizes {\n raw: number;\n gzip: number;\n brotli: number;\n}\n\ntype CompressedSizes = Record<string, AssetSizes>;\n\n/**\n * Collects all unique asset paths from the manifest, including entry points and their preloads.\n */\nfunction getAllAssetPaths(manifest: AssetsManifest): Set<string> {\n const allPaths = new Set<string>();\n for (const asset of Object.values(manifest)) {\n allPaths.add(asset.assetPath);\n for (const preload of asset.preloads) {\n allPaths.add(preload);\n }\n }\n return allPaths;\n}\n\n/**\n * Writes gzip and brotli compressed versions of the assets in the specified directory.\n * It reads each asset file, compresses it using gzip and brotli algorithms, and writes the compressed files\n * with appropriate extensions (.gz and .br) in the same directory.\n *\n * @param destination Directory where the compressed assets will be written.\n * @param manifest The assets manifest containing information about the assets.\n * @returns A promise that resolves to an object containing the sizes of the original, gzip, and brotli compressed assets.\n */\nasync function writeCompressedAssets(\n destination: string,\n manifest: AssetsManifest,\n): Promise<CompressedSizes> {\n const compressedSizes: CompressedSizes = {};\n const allAssetPaths = getAllAssetPaths(manifest);\n\n await Promise.all(\n [...allAssetPaths].map(async (assetPath) => {\n const destinationFilePath = path.resolve(destination, assetPath);\n const contents = await fs.readFile(destinationFilePath);\n const gzipCompressed = await gzip(contents);\n const brotliCompressed = await brotli(contents);\n await fs.writeFile(`${destinationFilePath}.gz`, gzipCompressed);\n await fs.writeFile(`${destinationFilePath}.br`, brotliCompressed);\n compressedSizes[assetPath] = {\n raw: contents.length,\n gzip: gzipCompressed.length,\n brotli: brotliCompressed.length,\n };\n }),\n );\n return compressedSizes;\n}\n\n/**\n * Calculates the total size of an entry point including all its preloaded chunks.\n */\nfunction calculateTotalSizes(\n asset: AssetsManifest[string],\n compressedSizes: CompressedSizes,\n): AssetSizes {\n const entrySizes = compressedSizes[asset.assetPath];\n const total: AssetSizes = {\n raw: entrySizes.raw,\n gzip: entrySizes.gzip,\n brotli: entrySizes.brotli,\n };\n\n for (const preload of asset.preloads) {\n const preloadSizes = compressedSizes[preload];\n if (preloadSizes) {\n total.raw += preloadSizes.raw;\n total.gzip += preloadSizes.gzip;\n total.brotli += preloadSizes.brotli;\n }\n }\n\n return total;\n}\n\nprogram.command('build <source> <destination>').action(async (source, destination) => {\n const manifest = await build(source, destination);\n\n // Write gzip and brotli versions of the output files. Record size information\n // so we can show it to the user.\n const compressedSizes = await writeCompressedAssets(destination, manifest);\n\n // Format the output into an object that we can pass to `console.table`.\n const results: Record<string, any> = {};\n Object.entries(manifest).forEach(([entryPoint, asset]) => {\n const totalSizes = calculateTotalSizes(asset, compressedSizes);\n\n results[entryPoint] = {\n 'Output file': asset.assetPath,\n Size: prettyBytes(totalSizes.raw),\n 'Size (gzip)': prettyBytes(totalSizes.gzip),\n 'Size (brotli)': prettyBytes(totalSizes.brotli),\n };\n });\n console.table(results);\n});\n\nprogram.parseAsync(process.argv).catch((err) => {\n console.error(err);\n process.exit(1);\n});\n"]}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\nimport path from 'path';\nimport { promisify } from 'util';\nimport zlib from 'zlib';\n\nimport { program } from 'commander';\nimport fs from 'fs-extra';\nimport prettyBytes from 'pretty-bytes';\n\nimport { type AssetsManifest, build } from './index.js';\n\nconst gzip = promisify(zlib.gzip);\nconst brotli = promisify(zlib.brotliCompress);\n\ninterface AssetSizes {\n raw: number;\n gzip: number;\n brotli: number;\n}\n\ntype CompressedSizes = Record<string, AssetSizes>;\n\n/**\n * Collects all unique asset paths from the manifest, including entry points and their preloads.\n */\nfunction getAllAssetPaths(manifest: AssetsManifest): Set<string> {\n const allPaths = new Set<string>();\n for (const asset of Object.values(manifest)) {\n allPaths.add(asset.assetPath);\n for (const preload of asset.preloads) {\n allPaths.add(preload);\n }\n }\n return allPaths;\n}\n\n/**\n * Writes gzip and brotli compressed versions of the assets in the specified directory.\n * It reads each asset file, compresses it using gzip and brotli algorithms, and writes the compressed files\n * with appropriate extensions (.gz and .br) in the same directory.\n *\n * @param destination Directory where the compressed assets will be written.\n * @param manifest The assets manifest containing information about the assets.\n * @returns A promise that resolves to an object containing the sizes of the original, gzip, and brotli compressed assets.\n */\nasync function writeCompressedAssets(\n destination: string,\n manifest: AssetsManifest,\n): Promise<CompressedSizes> {\n const compressedSizes: CompressedSizes = {};\n const allAssetPaths = getAllAssetPaths(manifest);\n\n await Promise.all(\n [...allAssetPaths].map(async (assetPath) => {\n const destinationFilePath = path.resolve(destination, assetPath);\n const contents = await fs.readFile(destinationFilePath);\n const gzipCompressed = await gzip(contents);\n const brotliCompressed = await brotli(contents);\n await fs.writeFile(`${destinationFilePath}.gz`, gzipCompressed);\n await fs.writeFile(`${destinationFilePath}.br`, brotliCompressed);\n compressedSizes[assetPath] = {\n raw: contents.length,\n gzip: gzipCompressed.length,\n brotli: brotliCompressed.length,\n };\n }),\n );\n return compressedSizes;\n}\n\n/**\n * Calculates the total size of an entry point including all its preloaded chunks.\n */\nfunction calculateTotalSizes(\n asset: AssetsManifest[string],\n compressedSizes: CompressedSizes,\n): AssetSizes {\n const entrySizes = compressedSizes[asset.assetPath];\n const total: AssetSizes = {\n raw: entrySizes.raw,\n gzip: entrySizes.gzip,\n brotli: entrySizes.brotli,\n };\n\n for (const preload of asset.preloads) {\n const preloadSizes = compressedSizes[preload];\n if (preloadSizes) {\n total.raw += preloadSizes.raw;\n total.gzip += preloadSizes.gzip;\n total.brotli += preloadSizes.brotli;\n }\n }\n\n return total;\n}\n\nprogram.command('build <source> <destination>').action(async (source, destination) => {\n const manifest = await build(source, destination);\n\n // Write gzip and brotli versions of the output files. Record size information\n // so we can show it to the user.\n const compressedSizes = await writeCompressedAssets(destination, manifest);\n\n // Format the output into an object that we can pass to `console.table`.\n const results: Record<string, any> = {};\n const sizesJson: Record<string, AssetSizes> = {};\n Object.entries(manifest).forEach(([entryPoint, asset]) => {\n const totalSizes = calculateTotalSizes(asset, compressedSizes);\n\n results[entryPoint] = {\n 'Output file': asset.assetPath,\n Size: prettyBytes(totalSizes.raw),\n 'Size (gzip)': prettyBytes(totalSizes.gzip),\n 'Size (brotli)': prettyBytes(totalSizes.brotli),\n };\n\n sizesJson[entryPoint] = totalSizes;\n });\n console.table(results);\n\n // Write the result for processing by other tools (e.g. our bundle size reporting action).\n await fs.writeJSON(path.resolve(destination, 'sizes.json'), sizesJson, { spaces: 2 });\n});\n\nprogram.parseAsync(process.argv).catch((err) => {\n console.error(err);\n process.exit(1);\n});\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prairielearn/compiled-assets",
3
- "version": "4.0.1",
3
+ "version": "4.1.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -30,7 +30,7 @@
30
30
  "devDependencies": {
31
31
  "@prairielearn/tsconfig": "^2.0.0",
32
32
  "@types/node": "^24.10.9",
33
- "@typescript/native-preview": "^7.0.0-dev.20260130.1",
33
+ "@typescript/native-preview": "^7.0.0-dev.20260203.1",
34
34
  "@vitest/coverage-v8": "^4.0.18",
35
35
  "express": "^4.22.1",
36
36
  "get-port": "^7.1.0",
package/src/cli.ts CHANGED
@@ -103,6 +103,7 @@ program.command('build <source> <destination>').action(async (source, destinatio
103
103
 
104
104
  // Format the output into an object that we can pass to `console.table`.
105
105
  const results: Record<string, any> = {};
106
+ const sizesJson: Record<string, AssetSizes> = {};
106
107
  Object.entries(manifest).forEach(([entryPoint, asset]) => {
107
108
  const totalSizes = calculateTotalSizes(asset, compressedSizes);
108
109
 
@@ -112,8 +113,13 @@ program.command('build <source> <destination>').action(async (source, destinatio
112
113
  'Size (gzip)': prettyBytes(totalSizes.gzip),
113
114
  'Size (brotli)': prettyBytes(totalSizes.brotli),
114
115
  };
116
+
117
+ sizesJson[entryPoint] = totalSizes;
115
118
  });
116
119
  console.table(results);
120
+
121
+ // Write the result for processing by other tools (e.g. our bundle size reporting action).
122
+ await fs.writeJSON(path.resolve(destination, 'sizes.json'), sizesJson, { spaces: 2 });
117
123
  });
118
124
 
119
125
  program.parseAsync(process.argv).catch((err) => {