Skip to content

Commit 8f1b039

Browse files
committed
ci: Bring back MDX caching with public folder cache
1 parent c10d7f9 commit 8f1b039

File tree

2 files changed

+68
-5
lines changed

2 files changed

+68
-5
lines changed

.gitignore

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@ npm-debug.log*
44
yarn-debug.log*
55
yarn-error.log*
66

7-
# Ignore generated export markdown files
8-
/public/md-exports/
9-
107
# Runtime data
118
pids
129
*.pid
@@ -96,6 +93,8 @@ public/page-data
9693
# tsbuildinfo file generated by CI
9794
tsconfig.tsbuildinfo
9895

96+
# Ignore generated files
97+
/public/md-exports/
9998
public/mdx-images/*
10099

101100
# yalc

src/mdx.ts

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,19 @@ import matter from 'gray-matter';
33
import {s} from 'hastscript';
44
import yaml from 'js-yaml';
55
import {bundleMDX} from 'mdx-bundler';
6-
import {access, opendir, readFile} from 'node:fs/promises';
6+
import {BinaryLike, createHash} from 'node:crypto';
7+
import {createReadStream, createWriteStream, mkdirSync} from 'node:fs';
8+
import {access, cp, opendir, readFile} from 'node:fs/promises';
79
import path from 'node:path';
10+
// @ts-expect-error ts(2305) -- For some reason "compose" is not recognized in the types
11+
import {compose, Readable} from 'node:stream';
12+
import {json} from 'node:stream/consumers';
13+
import {pipeline} from 'node:stream/promises';
14+
import {
15+
constants as zlibConstants,
16+
createBrotliCompress,
17+
createBrotliDecompress,
18+
} from 'node:zlib';
819
import {limitFunction} from 'p-limit';
920
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
1021
import rehypePresetMinify from 'rehype-preset-minify';
@@ -48,6 +59,33 @@ const root = process.cwd();
4859
// Functions which looks like AWS Lambda and we get `EMFILE` errors when trying to open
4960
// so many files at once.
5061
const FILE_CONCURRENCY_LIMIT = 200;
62+
const CACHE_COMPRESS_LEVEL = 4;
63+
const CACHE_DIR = path.join(root, '.next', 'cache', 'mdx-bundler');
64+
mkdirSync(CACHE_DIR, {recursive: true});
65+
66+
const md5 = (data: BinaryLike) => createHash('md5').update(data).digest('hex');
67+
68+
async function readCacheFile<T>(file: string): Promise<T> {
69+
const reader = createReadStream(file);
70+
const decompressor = createBrotliDecompress();
71+
72+
return (await json(compose(reader, decompressor))) as T;
73+
}
74+
75+
async function writeCacheFile(file: string, data: string) {
76+
await pipeline(
77+
Readable.from(data),
78+
createBrotliCompress({
79+
chunkSize: 32 * 1024,
80+
params: {
81+
[zlibConstants.BROTLI_PARAM_MODE]: zlibConstants.BROTLI_MODE_TEXT,
82+
[zlibConstants.BROTLI_PARAM_QUALITY]: CACHE_COMPRESS_LEVEL,
83+
[zlibConstants.BROTLI_PARAM_SIZE_HINT]: data.length,
84+
},
85+
}),
86+
createWriteStream(file)
87+
);
88+
}
5189

5290
function formatSlug(slug: string) {
5391
return slug.replace(/\.(mdx|md)/, '');
@@ -484,6 +522,20 @@ export async function getFileBySlug(slug: string): Promise<SlugFile> {
484522
);
485523
}
486524

525+
const cacheKey = md5(source);
526+
const cacheFile = path.join(CACHE_DIR, `${cacheKey}.br`);
527+
528+
try {
529+
const cached = await readCacheFile<SlugFile>(cacheFile);
530+
return cached;
531+
} catch (err) {
532+
if (err.code !== 'ENOENT' && err.code !== 'ABORT_ERR' && err.code !== 'Z_BUF_ERROR') {
533+
// If cache is corrupted, ignore and proceed
534+
// eslint-disable-next-line no-console
535+
console.warn(`Failed to read MDX cache: ${cacheFile}`, err);
536+
}
537+
}
538+
487539
process.env.ESBUILD_BINARY_PATH = path.join(
488540
root,
489541
'node_modules',
@@ -496,6 +548,8 @@ export async function getFileBySlug(slug: string): Promise<SlugFile> {
496548

497549
// cwd is how mdx-bundler knows how to resolve relative paths
498550
const cwd = path.dirname(sourcePath);
551+
const assetsCacheDir = path.join(CACHE_DIR, cacheKey);
552+
const outdir = path.join(root, 'public', 'mdx-images');
499553

500554
const result = await bundleMDX<Platform>({
501555
source,
@@ -577,9 +631,10 @@ export async function getFileBySlug(slug: string): Promise<SlugFile> {
577631
// inline svgs
578632
'.svg': 'dataurl',
579633
};
634+
options.metafile = true;
580635
// Set the `outdir` to a public location for this bundle.
581636
// this where this images will be copied
582-
options.outdir = path.join(root, 'public', 'mdx-images');
637+
options.outdir = assetsCacheDir;
583638

584639
// Set write to true so that esbuild will output the files.
585640
options.write = true;
@@ -609,6 +664,15 @@ export async function getFileBySlug(slug: string): Promise<SlugFile> {
609664
},
610665
};
611666

667+
await cp(assetsCacheDir, outdir, {
668+
recursive: true,
669+
force: true,
670+
});
671+
writeCacheFile(cacheFile, JSON.stringify(resultObj)).catch(e => {
672+
// eslint-disable-next-line no-console
673+
console.warn(`Failed to write MDX cache: ${cacheFile}`, e);
674+
});
675+
612676
return resultObj;
613677
}
614678

0 commit comments

Comments
 (0)