Skip to content

Commit 72e2f43

Browse files
authored
invalidate snippet cache when module contents change (#315)
* use module contents to determine cache validity * share cache * remove some unused code * empty
1 parent 5d1fb5c commit 72e2f43

File tree

1 file changed

+81
-117
lines changed

1 file changed

+81
-117
lines changed

packages/site-kit/src/lib/markdown/renderer.ts

Lines changed: 81 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import MagicString from 'magic-string';
2-
import { createHash } from 'node:crypto';
2+
import { createHash, Hash } from 'node:crypto';
33
import fs from 'node:fs';
44
import path from 'node:path';
55
import ts from 'typescript';
@@ -34,6 +34,81 @@ const theme = createCssVariablesTheme({
3434
fontStyle: true
3535
});
3636

37+
const hash = createHash('sha256');
38+
hash.update(fs.readFileSync('../../pnpm-lock.yaml', 'utf-8'));
39+
hash_graph(hash, fileURLToPath(import.meta.url));
40+
const digest = hash.digest().toString('base64').replace(/\//g, '-');
41+
42+
/**
43+
* Utility function to work with code snippet caching.
44+
*
45+
* @example
46+
*
47+
* ```js
48+
* const SNIPPETS_CACHE = create_snippet_cache(true);
49+
*
50+
* const { uid, code } = SNIPPETS_CACHE.get(source);
51+
*
52+
* // Later to save the code to the cache
53+
* SNIPPETS_CACHE.save(uid, processed_code);
54+
* ```
55+
*/
56+
async function create_snippet_cache() {
57+
const cache = new Map();
58+
const directory = find_nearest_node_modules(import.meta.url) + '/.snippets';
59+
const current = `${directory}/${digest}`;
60+
61+
if (fs.existsSync(directory)) {
62+
for (const dir of fs.readdirSync(directory)) {
63+
if (dir !== digest) {
64+
fs.rmSync(`${directory}/${dir}`, { force: true, recursive: true });
65+
}
66+
}
67+
} else {
68+
fs.mkdirSync(directory);
69+
}
70+
71+
try {
72+
fs.mkdirSync(`${directory}/${digest}`);
73+
} catch {}
74+
75+
function get_file(source: string) {
76+
const hash = createHash('sha256');
77+
hash.update(source);
78+
const digest = hash.digest().toString('base64').replace(/\//g, '-');
79+
80+
return `${current}/${digest}.html`;
81+
}
82+
83+
return {
84+
get(source: string) {
85+
let snippet = cache.get(source);
86+
87+
if (snippet === undefined) {
88+
const file = get_file(source);
89+
90+
if (fs.existsSync(file)) {
91+
snippet = fs.readFileSync(file, 'utf-8');
92+
cache.set(source, snippet);
93+
}
94+
}
95+
96+
return snippet;
97+
},
98+
save(source: string, html: string) {
99+
cache.set(source, html);
100+
101+
try {
102+
fs.mkdirSync(directory);
103+
} catch {}
104+
105+
fs.writeFileSync(get_file(source), html);
106+
}
107+
};
108+
}
109+
110+
const snippets = await create_snippet_cache();
111+
37112
/**
38113
* A super markdown renderer function. Renders svelte and kit docs specific specific markdown code to html.
39114
*
@@ -119,8 +194,6 @@ export async function render_content_markdown(
119194
body: string,
120195
{ twoslashBanner }: { twoslashBanner?: TwoslashBanner } = {}
121196
) {
122-
const snippets = await create_snippet_cache(true);
123-
124197
const headings: string[] = [];
125198

126199
return await transform(body, {
@@ -453,14 +526,13 @@ function find_nearest_node_modules(file: string): string | null {
453526
}
454527

455528
/**
456-
* Get the `mtime` of the most recently modified file in a dependency graph,
529+
* Get the hash of a dependency graph,
457530
* excluding imports from `node_modules`
458531
*/
459-
function get_mtime(file: string, seen = new Set<string>()) {
460-
if (seen.has(file)) return -1;
532+
function hash_graph(hash: Hash, file: string, seen = new Set<string>()) {
533+
if (seen.has(file)) return;
461534
seen.add(file);
462535

463-
let mtime = fs.statSync(file).mtimeMs;
464536
const content = fs.readFileSync(file, 'utf-8');
465537

466538
for (const [_, source] of content.matchAll(/^import(?:.+?\s+from\s+)?['"](.+)['"];?$/gm)) {
@@ -471,118 +543,10 @@ function get_mtime(file: string, seen = new Set<string>()) {
471543
if (!fs.existsSync(resolved))
472544
throw new Error(`Could not resolve ${source} relative to ${file}`);
473545

474-
mtime = Math.max(mtime, get_mtime(resolved, seen));
475-
}
476-
477-
return mtime;
478-
}
479-
480-
const mtime = Math.max(
481-
get_mtime(fileURLToPath(import.meta.url)),
482-
fs.statSync('node_modules').mtimeMs,
483-
fs.statSync('../../pnpm-lock.yaml').mtimeMs
484-
);
485-
486-
/**
487-
* Utility function to work with code snippet caching.
488-
*
489-
* @example
490-
*
491-
* ```js
492-
* const SNIPPETS_CACHE = create_snippet_cache(true);
493-
*
494-
* const { uid, code } = SNIPPETS_CACHE.get(source);
495-
*
496-
* // Later to save the code to the cache
497-
* SNIPPETS_CACHE.save(uid, processed_code);
498-
* ```
499-
*/
500-
async function create_snippet_cache(should: boolean) {
501-
const cache = new Map();
502-
const directory = find_nearest_node_modules(import.meta.url) + '/.snippets';
503-
504-
if (fs.existsSync(directory)) {
505-
for (const dir of fs.readdirSync(directory)) {
506-
if (!fs.statSync(`${directory}/${dir}`).isDirectory() || +dir < mtime) {
507-
fs.rmSync(`${directory}/${dir}`, { force: true, recursive: true });
508-
}
509-
}
510-
} else {
511-
fs.mkdirSync(directory);
512-
}
513-
514-
try {
515-
fs.mkdirSync(`${directory}/${mtime}`);
516-
} catch {}
517-
518-
function get_file(source: string) {
519-
const hash = createHash('sha256');
520-
hash.update(source);
521-
const digest = hash.digest().toString('base64').replace(/\//g, '-');
522-
523-
return `${directory}/${mtime}/${digest}.html`;
524-
}
525-
526-
return {
527-
get(source: string) {
528-
if (!should) return;
529-
530-
let snippet = cache.get(source);
531-
532-
if (snippet === undefined) {
533-
const file = get_file(source);
534-
535-
if (fs.existsSync(file)) {
536-
snippet = fs.readFileSync(file, 'utf-8');
537-
cache.set(source, snippet);
538-
}
539-
}
540-
541-
return snippet;
542-
},
543-
save(source: string, html: string) {
544-
cache.set(source, html);
545-
546-
try {
547-
fs.mkdirSync(directory);
548-
} catch {}
549-
550-
fs.writeFileSync(get_file(source), html);
551-
}
552-
};
553-
}
554-
555-
function create_type_links(
556-
modules: Modules | undefined,
557-
resolve_link?: (module_name: string, type_name: string) => { slug: string; page: string }
558-
): {
559-
type_regex: RegExp | null;
560-
type_links: Map<string, { slug: string; page: string; relativeURL: string }> | null;
561-
} {
562-
if (!modules || modules.length === 0 || !resolve_link)
563-
return { type_regex: null, type_links: null };
564-
565-
const type_regex = new RegExp(
566-
`(import\\(&apos;(?:svelte|@sveltejs\\/kit)&apos;\\)\\.)?\\b(${modules
567-
.flatMap((module) => module.types)
568-
.map((type) => type?.name)
569-
.join('|')})\\b`,
570-
'g'
571-
);
572-
573-
/** @type {Map<string, { slug: string; page: string; relativeURL: string; }>} */
574-
const type_links = new Map();
575-
576-
for (const module of modules) {
577-
if (!module || !module.name) continue;
578-
579-
for (const type of module.types ?? []) {
580-
const link = resolve_link(module.name, type.name);
581-
type_links.set(type.name, { ...link, relativeURL: link.page + '#' + link.slug });
582-
}
546+
hash_graph(hash, resolved, seen);
583547
}
584548

585-
return { type_regex, type_links };
549+
hash.update(content);
586550
}
587551

588552
function parse_options(source: string, language: string) {

0 commit comments

Comments
 (0)