@prairielearn/compiled-assets 3.3.0 → 3.3.2

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,20 @@
1
1
  # @prairielearn/compiled-assets
2
2
 
3
+ ## 3.3.2
4
+
5
+ ### Patch Changes
6
+
7
+ - efe7435: Add Total size columns to build stats that sum entry point sizes with their code-split chunks
8
+
9
+ ## 3.3.1
10
+
11
+ ### Patch Changes
12
+
13
+ - 926403c: Upgrade `globby` to latest version
14
+ - 70a8029: Upgrade all JavaScript dependencies
15
+ - Updated dependencies [70a8029]
16
+ - @prairielearn/html@4.0.23
17
+
3
18
  ## 3.3.0
4
19
 
5
20
  ### Minor Changes
package/dist/cli.js CHANGED
@@ -8,6 +8,19 @@ import prettyBytes from 'pretty-bytes';
8
8
  import { build } from './index.js';
9
9
  const gzip = promisify(zlib.gzip);
10
10
  const brotli = promisify(zlib.brotliCompress);
11
+ /**
12
+ * Collects all unique asset paths from the manifest, including entry points and their preloads.
13
+ */
14
+ function getAllAssetPaths(manifest) {
15
+ const allPaths = new Set();
16
+ for (const asset of Object.values(manifest)) {
17
+ allPaths.add(asset.assetPath);
18
+ for (const preload of asset.preloads) {
19
+ allPaths.add(preload);
20
+ }
21
+ }
22
+ return allPaths;
23
+ }
11
24
  /**
12
25
  * Writes gzip and brotli compressed versions of the assets in the specified directory.
13
26
  * It reads each asset file, compresses it using gzip and brotli algorithms, and writes the compressed files
@@ -19,14 +32,15 @@ const brotli = promisify(zlib.brotliCompress);
19
32
  */
20
33
  async function writeCompressedAssets(destination, manifest) {
21
34
  const compressedSizes = {};
22
- await Promise.all(Object.values(manifest).map(async (asset) => {
23
- const destinationFilePath = path.resolve(destination, asset.assetPath);
35
+ const allAssetPaths = getAllAssetPaths(manifest);
36
+ await Promise.all([...allAssetPaths].map(async (assetPath) => {
37
+ const destinationFilePath = path.resolve(destination, assetPath);
24
38
  const contents = await fs.readFile(destinationFilePath);
25
39
  const gzipCompressed = await gzip(contents);
26
40
  const brotliCompressed = await brotli(contents);
27
41
  await fs.writeFile(`${destinationFilePath}.gz`, gzipCompressed);
28
42
  await fs.writeFile(`${destinationFilePath}.br`, brotliCompressed);
29
- compressedSizes[asset.assetPath] = {
43
+ compressedSizes[assetPath] = {
30
44
  raw: contents.length,
31
45
  gzip: gzipCompressed.length,
32
46
  brotli: brotliCompressed.length,
@@ -34,6 +48,26 @@ async function writeCompressedAssets(destination, manifest) {
34
48
  }));
35
49
  return compressedSizes;
36
50
  }
51
+ /**
52
+ * Calculates the total size of an entry point including all its preloaded chunks.
53
+ */
54
+ function calculateTotalSizes(asset, compressedSizes) {
55
+ const entrySizes = compressedSizes[asset.assetPath];
56
+ const total = {
57
+ raw: entrySizes.raw,
58
+ gzip: entrySizes.gzip,
59
+ brotli: entrySizes.brotli,
60
+ };
61
+ for (const preload of asset.preloads) {
62
+ const preloadSizes = compressedSizes[preload];
63
+ if (preloadSizes) {
64
+ total.raw += preloadSizes.raw;
65
+ total.gzip += preloadSizes.gzip;
66
+ total.brotli += preloadSizes.brotli;
67
+ }
68
+ }
69
+ return total;
70
+ }
37
71
  program.command('build <source> <destination>').action(async (source, destination) => {
38
72
  const manifest = await build(source, destination);
39
73
  // Write gzip and brotli versions of the output files. Record size information
@@ -42,12 +76,12 @@ program.command('build <source> <destination>').action(async (source, destinatio
42
76
  // Format the output into an object that we can pass to `console.table`.
43
77
  const results = {};
44
78
  Object.entries(manifest).forEach(([entryPoint, asset]) => {
45
- const sizes = compressedSizes[asset.assetPath];
79
+ const totalSizes = calculateTotalSizes(asset, compressedSizes);
46
80
  results[entryPoint] = {
47
81
  'Output file': asset.assetPath,
48
- Size: prettyBytes(sizes.raw),
49
- 'Size (gzip)': prettyBytes(sizes.gzip),
50
- 'Size (brotli)': prettyBytes(sizes.brotli),
82
+ Size: prettyBytes(totalSizes.raw),
83
+ 'Size (gzip)': prettyBytes(totalSizes.gzip),
84
+ 'Size (brotli)': prettyBytes(totalSizes.brotli),
51
85
  };
52
86
  });
53
87
  console.table(results);
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\ntype CompressedSizes = Record<string, Record<string, number>>;\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 await Promise.all(\n Object.values(manifest).map(async (asset) => {\n const destinationFilePath = path.resolve(destination, asset.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[asset.assetPath] = {\n raw: contents.length,\n gzip: gzipCompressed.length,\n brotli: brotliCompressed.length,\n };\n }),\n );\n return compressedSizes;\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 sizes = compressedSizes[asset.assetPath];\n results[entryPoint] = {\n 'Output file': asset.assetPath,\n Size: prettyBytes(sizes.raw),\n 'Size (gzip)': prettyBytes(sizes.gzip),\n 'Size (brotli)': prettyBytes(sizes.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 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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prairielearn/compiled-assets",
3
- "version": "3.3.0",
3
+ "version": "3.3.2",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -15,24 +15,24 @@
15
15
  "test": "vitest run --coverage"
16
16
  },
17
17
  "dependencies": {
18
- "@prairielearn/html": "^4.0.22",
18
+ "@prairielearn/html": "^4.0.23",
19
19
  "commander": "^14.0.2",
20
- "esbuild": "^0.25.11",
20
+ "esbuild": "^0.27.2",
21
21
  "express-static-gzip": "^2.2.0",
22
- "fs-extra": "^11.3.2",
23
- "globby": "^15.0.0",
22
+ "fs-extra": "^11.3.3",
23
+ "globby": "^16.1.0",
24
24
  "pretty-bytes": "^7.1.0",
25
25
  "tmp-promise": "^3.0.3"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@prairielearn/tsconfig": "^0.0.0",
29
- "@types/node": "^22.19.0",
30
- "@vitest/coverage-v8": "^4.0.7",
31
- "express": "^4.21.2",
29
+ "@types/node": "^22.19.5",
30
+ "@vitest/coverage-v8": "^4.0.17",
31
+ "express": "^4.22.1",
32
32
  "get-port": "^7.1.0",
33
33
  "node-fetch": "^3.3.2",
34
- "tsx": "^4.20.6",
34
+ "tsx": "^4.21.0",
35
35
  "typescript": "^5.9.3",
36
- "vitest": "^4.0.7"
36
+ "vitest": "^4.0.17"
37
37
  }
38
38
  }
package/src/cli.ts CHANGED
@@ -12,7 +12,27 @@ import { type AssetsManifest, build } from './index.js';
12
12
  const gzip = promisify(zlib.gzip);
13
13
  const brotli = promisify(zlib.brotliCompress);
14
14
 
15
- type CompressedSizes = Record<string, Record<string, number>>;
15
+ interface AssetSizes {
16
+ raw: number;
17
+ gzip: number;
18
+ brotli: number;
19
+ }
20
+
21
+ type CompressedSizes = Record<string, AssetSizes>;
22
+
23
+ /**
24
+ * Collects all unique asset paths from the manifest, including entry points and their preloads.
25
+ */
26
+ function getAllAssetPaths(manifest: AssetsManifest): Set<string> {
27
+ const allPaths = new Set<string>();
28
+ for (const asset of Object.values(manifest)) {
29
+ allPaths.add(asset.assetPath);
30
+ for (const preload of asset.preloads) {
31
+ allPaths.add(preload);
32
+ }
33
+ }
34
+ return allPaths;
35
+ }
16
36
 
17
37
  /**
18
38
  * Writes gzip and brotli compressed versions of the assets in the specified directory.
@@ -28,15 +48,17 @@ async function writeCompressedAssets(
28
48
  manifest: AssetsManifest,
29
49
  ): Promise<CompressedSizes> {
30
50
  const compressedSizes: CompressedSizes = {};
51
+ const allAssetPaths = getAllAssetPaths(manifest);
52
+
31
53
  await Promise.all(
32
- Object.values(manifest).map(async (asset) => {
33
- const destinationFilePath = path.resolve(destination, asset.assetPath);
54
+ [...allAssetPaths].map(async (assetPath) => {
55
+ const destinationFilePath = path.resolve(destination, assetPath);
34
56
  const contents = await fs.readFile(destinationFilePath);
35
57
  const gzipCompressed = await gzip(contents);
36
58
  const brotliCompressed = await brotli(contents);
37
59
  await fs.writeFile(`${destinationFilePath}.gz`, gzipCompressed);
38
60
  await fs.writeFile(`${destinationFilePath}.br`, brotliCompressed);
39
- compressedSizes[asset.assetPath] = {
61
+ compressedSizes[assetPath] = {
40
62
  raw: contents.length,
41
63
  gzip: gzipCompressed.length,
42
64
  brotli: brotliCompressed.length,
@@ -46,6 +68,32 @@ async function writeCompressedAssets(
46
68
  return compressedSizes;
47
69
  }
48
70
 
71
+ /**
72
+ * Calculates the total size of an entry point including all its preloaded chunks.
73
+ */
74
+ function calculateTotalSizes(
75
+ asset: AssetsManifest[string],
76
+ compressedSizes: CompressedSizes,
77
+ ): AssetSizes {
78
+ const entrySizes = compressedSizes[asset.assetPath];
79
+ const total: AssetSizes = {
80
+ raw: entrySizes.raw,
81
+ gzip: entrySizes.gzip,
82
+ brotli: entrySizes.brotli,
83
+ };
84
+
85
+ for (const preload of asset.preloads) {
86
+ const preloadSizes = compressedSizes[preload];
87
+ if (preloadSizes) {
88
+ total.raw += preloadSizes.raw;
89
+ total.gzip += preloadSizes.gzip;
90
+ total.brotli += preloadSizes.brotli;
91
+ }
92
+ }
93
+
94
+ return total;
95
+ }
96
+
49
97
  program.command('build <source> <destination>').action(async (source, destination) => {
50
98
  const manifest = await build(source, destination);
51
99
 
@@ -56,12 +104,13 @@ program.command('build <source> <destination>').action(async (source, destinatio
56
104
  // Format the output into an object that we can pass to `console.table`.
57
105
  const results: Record<string, any> = {};
58
106
  Object.entries(manifest).forEach(([entryPoint, asset]) => {
59
- const sizes = compressedSizes[asset.assetPath];
107
+ const totalSizes = calculateTotalSizes(asset, compressedSizes);
108
+
60
109
  results[entryPoint] = {
61
110
  'Output file': asset.assetPath,
62
- Size: prettyBytes(sizes.raw),
63
- 'Size (gzip)': prettyBytes(sizes.gzip),
64
- 'Size (brotli)': prettyBytes(sizes.brotli),
111
+ Size: prettyBytes(totalSizes.raw),
112
+ 'Size (gzip)': prettyBytes(totalSizes.gzip),
113
+ 'Size (brotli)': prettyBytes(totalSizes.brotli),
65
114
  };
66
115
  });
67
116
  console.table(results);