aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2024-03-21 20:59:50 +0100
committerLibravatar Kristóf Marussy <kristof@marussy.com>2024-03-21 20:59:50 +0100
commitcad97041b39570e78b38352e60b4dae6870be3c2 (patch)
treea122f5c1155d85c6931ad0ec7e75706d8cf17066
parentAlso compress Docusaurus-generated files (diff)
downloadblog-cad97041b39570e78b38352e60b4dae6870be3c2.tar.gz
blog-cad97041b39570e78b38352e60b4dae6870be3c2.tar.zst
blog-cad97041b39570e78b38352e60b4dae6870be3c2.zip
Simplify compression
Compress Docusaurus-generated files in a separate script instead of a post-build hook to avoid post-build hook ordering and concurrency issues.
-rw-r--r--docusaurus.config.ts1
-rw-r--r--package.json2
-rw-r--r--scripts/compressSite.mjs45
-rw-r--r--scripts/compressionOptions.mjs21
-rw-r--r--src/plugins/compressionPlugin.ts49
5 files changed, 79 insertions, 39 deletions
diff --git a/docusaurus.config.ts b/docusaurus.config.ts
index 9468812..f19513a 100644
--- a/docusaurus.config.ts
+++ b/docusaurus.config.ts
@@ -23,6 +23,7 @@ export default {
23 remarkPlugins: [[smartypants, { dashes: 'oldschool' }]], 23 remarkPlugins: [[smartypants, { dashes: 'oldschool' }]],
24 }, 24 },
25 ], 25 ],
26 '@docusaurus/plugin-sitemap',
26 './src/plugins/compressionPlugin.ts', 27 './src/plugins/compressionPlugin.ts',
27 './src/plugins/responsiveLoaderPlugin.ts', 28 './src/plugins/responsiveLoaderPlugin.ts',
28 './src/plugins/swcMinifyPlugin.ts', 29 './src/plugins/swcMinifyPlugin.ts',
diff --git a/package.json b/package.json
index 57e8f60..fc56b75 100644
--- a/package.json
+++ b/package.json
@@ -14,7 +14,7 @@
14 "private": true, 14 "private": true,
15 "scripts": { 15 "scripts": {
16 "docusaurus": "docusaurus", 16 "docusaurus": "docusaurus",
17 "build": "cross-env WEBPACK_URL_LOADER_LIMIT=0 docusaurus build", 17 "build": "cross-env WEBPACK_URL_LOADER_LIMIT=0 docusaurus build && node scripts/compressSite.mjs",
18 "swizzle": "docusaurus swizzle", 18 "swizzle": "docusaurus swizzle",
19 "clear": "docusaurus clear", 19 "clear": "docusaurus clear",
20 "serve": "docusaurus serve -p 1313 --no-open", 20 "serve": "docusaurus serve -p 1313 --no-open",
diff --git a/scripts/compressSite.mjs b/scripts/compressSite.mjs
new file mode 100644
index 0000000..bd98c55
--- /dev/null
+++ b/scripts/compressSite.mjs
@@ -0,0 +1,45 @@
1/*
2 * Copyright (c) 2024 Kristóf Marussy <kristof@marussy.com>
3 *
4 * SPDX-License-Identifier: MIT
5 */
6
7import { readFile, writeFile } from 'node:fs/promises';
8import path from 'node:path';
9import { promisify } from 'node:util';
10import zlib from 'node:zlib';
11
12import { Globby } from '@docusaurus/utils';
13
14import { brotliOptions, gzipOptions, minRatio } from './compressionOptions.mjs';
15
16const patterns = [
17 '**/*.html',
18 'atom.xml',
19 'feed.json',
20 'robots.txt',
21 'rss.xml',
22 'sitemap.xml',
23];
24const brotliCompress = promisify(zlib.brotliCompress);
25const gzip = promisify(zlib.gzip);
26
27async function compressSite() {
28 const files = await Globby(patterns.map((s) => path.join('build', s)));
29 const promises = files.map(async (file) => {
30 const contents = await readFile(file);
31 const maxLength = contents.length * minRatio;
32 const gzipContents = await gzip(contents, gzipOptions);
33 if (gzipContents.length <= maxLength) {
34 await writeFile(`${file}.gz`, gzipContents);
35 }
36 const brotliContents = await brotliCompress(contents, brotliOptions);
37 if (brotliContents.length <= maxLength) {
38 await writeFile(`${file}.br`, brotliContents);
39 }
40 });
41 await Promise.all(promises);
42}
43
44// @ts-expect-error This file is run directly by nodejs, so top-level await is fine here.
45await compressSite();
diff --git a/scripts/compressionOptions.mjs b/scripts/compressionOptions.mjs
new file mode 100644
index 0000000..3f2d226
--- /dev/null
+++ b/scripts/compressionOptions.mjs
@@ -0,0 +1,21 @@
1/*
2 * Copyright (c) 2024 Kristóf Marussy <kristof@marussy.com>
3 *
4 * SPDX-License-Identifier: MIT
5 */
6
7import zlib from 'node:zlib';
8
9export const minRatio = 0.8;
10
11/** @type {import('node:zlib').ZlibOptions} */
12export const gzipOptions = {
13 level: 9,
14};
15
16/** @type {import('node:zlib').BrotliOptions} */
17export const brotliOptions = {
18 params: {
19 [zlib.constants.BROTLI_PARAM_QUALITY]: 11,
20 },
21};
diff --git a/src/plugins/compressionPlugin.ts b/src/plugins/compressionPlugin.ts
index 8b347cf..7b9feeb 100644
--- a/src/plugins/compressionPlugin.ts
+++ b/src/plugins/compressionPlugin.ts
@@ -4,35 +4,20 @@
4 * SPDX-License-Identifier: MIT 4 * SPDX-License-Identifier: MIT
5 */ 5 */
6 6
7import { readFile, writeFile } from 'node:fs/promises'; 7import type { BrotliOptions } from 'node:zlib';
8import path from 'node:path';
9import { promisify } from 'node:util';
10import zlib, { type BrotliOptions, type ZlibOptions } from 'node:zlib';
11 8
12import pluginSitemap, { type PluginOptions } from '@docusaurus/plugin-sitemap'; 9import type { Plugin } from '@docusaurus/types';
13import type { LoadContext, Plugin } from '@docusaurus/types';
14import { Globby } from '@docusaurus/utils';
15import CompressionPlugin from 'compression-webpack-plugin'; 10import CompressionPlugin from 'compression-webpack-plugin';
16 11
17const gzipOptions = { 12import {
18 level: 9, 13 brotliOptions,
19} satisfies ZlibOptions; 14 gzipOptions,
20const brotliOptions = { 15 minRatio,
21 params: { 16} from '../../scripts/compressionOptions.mjs';
22 [zlib.constants.BROTLI_PARAM_QUALITY]: 11,
23 },
24};
25const test = /\.(js|css|svg|txt)$/;
26const paths = ['**/*.html', 'robots.txt', 'sitemap.xml'];
27 17
28const gzip = promisify(zlib.gzip); 18const test = /\.(js|css|svg|txt)$/;
29const brotliCompress = promisify(zlib.brotliCompress);
30 19
31export default function compressionPlugin( 20export default function compressionPlugin(): Plugin<void> {
32 context: LoadContext,
33 options: PluginOptions,
34): Plugin<void> {
35 const sitemap = pluginSitemap(context, options);
36 return { 21 return {
37 name: 'marussy-compression-plugin', 22 name: 'marussy-compression-plugin',
38 configureWebpack: (_config, isServer) => 23 configureWebpack: (_config, isServer) =>
@@ -44,28 +29,16 @@ export default function compressionPlugin(
44 test, 29 test,
45 filename: '[path][base].gz', 30 filename: '[path][base].gz',
46 compressionOptions: gzipOptions, 31 compressionOptions: gzipOptions,
32 minRatio,
47 }), 33 }),
48 new CompressionPlugin<BrotliOptions>({ 34 new CompressionPlugin<BrotliOptions>({
49 test, 35 test,
50 filename: '[path][base].br', 36 filename: '[path][base].br',
51 algorithm: 'brotliCompress', 37 algorithm: 'brotliCompress',
52 compressionOptions: brotliOptions, 38 compressionOptions: brotliOptions,
39 minRatio,
53 }), 40 }),
54 ], 41 ],
55 }, 42 },
56 async postBuild(props) {
57 await sitemap?.postBuild(props);
58 const files = await Globby(paths.map((s) => path.join(props.outDir, s)));
59 for (const file of files) {
60 console.log(file);
61 const contents = await readFile(file);
62 const gzipContents = await gzip(contents, gzipOptions);
63 await writeFile(`${file}.gz`, gzipContents);
64 const brotliContents = await brotliCompress(contents, brotliOptions);
65 await writeFile(`${file}.br`, brotliContents);
66 }
67 },
68 }; 43 };
69} 44}
70
71export { validateOptions } from '@docusaurus/plugin-sitemap';