@doc-kit/core 1.2.0 → 1.3.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 +16 -0
- package/README.md +1 -1
- package/package.json +6 -7
- package/src/generators/ast/generate.mjs +7 -3
- package/src/generators/ast/index.mjs +9 -0
- package/src/generators/index.mjs +1 -0
- package/src/generators/json/README.md +1 -1
- package/src/generators/json/generated/schema.d.ts +1 -1
- package/src/generators/json/index.mjs +9 -0
- package/src/generators/json/utils/markdown.mjs +2 -2
- package/src/generators/json-all/README.md +1 -1
- package/src/generators/json-simple/README.md +1 -1
- package/src/generators/loader.mjs +14 -0
- package/src/generators/metadata/generate.mjs +4 -2
- package/src/generators/metadata/index.mjs +9 -0
- package/src/generators/metadata/maps/mdn.json +3 -0
- package/src/generators/metadata/types.d.ts +1 -1
- package/src/generators/metadata/utils/parse.mjs +2 -4
- package/src/generators/metadata/utils/transformers.mjs +1 -1
- package/src/generators/metadata/utils/visitors.mjs +2 -2
- package/src/generators/types.d.ts +8 -0
- package/src/generators.mjs +4 -0
- package/src/logger/constants.mjs +35 -0
- package/src/logger/transports/github.mjs +48 -15
- package/src/plugins/shiki/highlighter.mjs +373 -0
- package/src/plugins/shiki/rehype.mjs +38 -0
- package/src/{utils/type-annotations/highlighted.mjs → plugins/type-annotations/highlighter.mjs} +14 -19
- package/src/{utils → plugins}/type-annotations/remark.mjs +6 -2
- package/src/threading/chunk-worker.mjs +4 -0
- package/src/threading/constants.mjs +10 -0
- package/src/threading/index.mjs +6 -1
- package/src/utils/configuration/constants.mjs +3 -3
- package/src/utils/configuration/index.mjs +86 -20
- package/src/utils/configuration/types.d.ts +27 -0
- package/src/utils/loaders.mjs +23 -1
- package/src/utils/markdown/constants.mjs +7 -0
- package/src/utils/markdown/plugins.mjs +232 -0
- package/src/utils/markdown/processor.mjs +60 -0
- package/src/utils/highlighter.mjs +0 -198
- package/src/utils/remark-shiki.mjs +0 -29
- package/src/utils/remark.mjs +0 -90
- /package/src/{utils → plugins}/type-annotations/hast.mjs +0 -0
- /package/src/{utils → plugins}/type-annotations/mdast.mjs +0 -0
- /package/src/{utils → plugins}/type-annotations/syntax.mjs +0 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { endianness } from 'node:os';
|
|
5
|
+
|
|
6
|
+
import createSyntaxHighlighter from '@node-core/rehype-shiki/highlighter';
|
|
7
|
+
import { isSpecialLang } from 'shiki/core';
|
|
8
|
+
import { bundledLanguagesInfo } from 'shiki/langs';
|
|
9
|
+
import { bundledThemes } from 'shiki/themes';
|
|
10
|
+
|
|
11
|
+
import { importFromURL } from '#utils/loaders.mjs';
|
|
12
|
+
import { getMarkdownPlugins } from '#utils/markdown/plugins.mjs';
|
|
13
|
+
import { lazy } from '#utils/misc.mjs';
|
|
14
|
+
|
|
15
|
+
const require = createRequire(import.meta.url);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A language a highlighter highlights.
|
|
19
|
+
*
|
|
20
|
+
* @typedef {Pick<import('shiki').LanguageRegistration, 'name' | 'displayName' | 'aliases'>} Language
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A syntax highlighter, creating its Shiki instance on first use.
|
|
25
|
+
*
|
|
26
|
+
* @typedef {Object} SyntaxHighlighter
|
|
27
|
+
* @property {import('shiki').HighlighterCore} shiki - The Shiki instance
|
|
28
|
+
* @property {(languageId?: string) => string} resolveLanguage - Resolves a language, falling back to plain text for unknown ones
|
|
29
|
+
* @property {(code: string, lang: string, meta?: Record<string, unknown>) => string} highlightToHtml - Highlights code, returning the inner HTML of its `<code>` element
|
|
30
|
+
* @property {(code: string, lang: string, meta?: Record<string, unknown>) => ReturnType<import('shiki').HighlighterCore['codeToHast']>} highlightToHast - Highlights code, returning a HAST tree
|
|
31
|
+
* @property {Array<Language>} langs - The languages it highlights
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Creates Shiki's regular expression engine: the wasm (Oniguruma) one where
|
|
36
|
+
* it can, the JavaScript one otherwise.
|
|
37
|
+
*
|
|
38
|
+
* @returns {Promise<import('shiki').RegexEngine>}
|
|
39
|
+
*/
|
|
40
|
+
const createEngine = async () => {
|
|
41
|
+
// riscv64 with sv39 has limited virtual memory space, where creating
|
|
42
|
+
// too many (>20) wasm memory instances fails.
|
|
43
|
+
// https://github.com/nodejs/node/pull/60591
|
|
44
|
+
//
|
|
45
|
+
// The wasm highlighter is currently not compatible with big endian.
|
|
46
|
+
// https://github.com/nodejs/node/pull/62512#issuecomment-4243469950
|
|
47
|
+
if (process.arch !== 'riscv64' && endianness() === 'LE') {
|
|
48
|
+
const { createOnigurumaEngine } = await import('shiki/engine/oniguruma');
|
|
49
|
+
|
|
50
|
+
return createOnigurumaEngine(import('shiki/wasm'));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const { createJavaScriptRegexEngine } =
|
|
54
|
+
await import('shiki/engine/javascript');
|
|
55
|
+
|
|
56
|
+
// Not every bundled grammar compiles under the JavaScript engine,
|
|
57
|
+
// so skip the patterns it cannot handle instead of throwing.
|
|
58
|
+
return createJavaScriptRegexEngine({ forgiving: true });
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// The regular expression engine of this thread, for all its highlighters
|
|
62
|
+
let engine;
|
|
63
|
+
|
|
64
|
+
// The highlighters of the options given, by their JSON
|
|
65
|
+
const highlighters = new Map();
|
|
66
|
+
|
|
67
|
+
// What code in an unknown language is highlighted as
|
|
68
|
+
const FALLBACK_LANGUAGE = 'text';
|
|
69
|
+
|
|
70
|
+
// The languages Shiki bundles. Registering a grammar takes time and memory,
|
|
71
|
+
// and slows down every highlight after it, so each one is only imported and
|
|
72
|
+
// registered once code in it is highlighted (see `resolveLanguage`).
|
|
73
|
+
const BUNDLED_LANGUAGES = bundledLanguagesInfo.map(({ id, name, aliases }) => ({
|
|
74
|
+
name: id,
|
|
75
|
+
displayName: name,
|
|
76
|
+
aliases,
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
// The bundled language each of their names and aliases stands for, as code
|
|
80
|
+
// names its language by either
|
|
81
|
+
const BUNDLED_NAMES = new Map(
|
|
82
|
+
BUNDLED_LANGUAGES.flatMap(({ name, aliases = [] }) =>
|
|
83
|
+
[name, ...aliases].map(alias => [alias, name])
|
|
84
|
+
)
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Imports a bundled language: its grammar, and those of the languages it
|
|
89
|
+
* embeds, lazily too. Shiki only highlights a language embedded lazily (a
|
|
90
|
+
* fenced code block in Markdown) once it is registered, so registering it
|
|
91
|
+
* later would make code highlight differently depending on what was
|
|
92
|
+
* highlighted before it.
|
|
93
|
+
*
|
|
94
|
+
* For the same reason, a grammar injecting into others' (`ts-tags`', into
|
|
95
|
+
* JavaScript's tagged templates) only injects into the language it was
|
|
96
|
+
* imported for: Shiki only applies an injection to the grammars it creates
|
|
97
|
+
* after it. A language given in `langs` keeps its injections.
|
|
98
|
+
*
|
|
99
|
+
* @param {string} name - A name or alias of the language
|
|
100
|
+
* @returns {Array<import('shiki').LanguageRegistration>}
|
|
101
|
+
*/
|
|
102
|
+
const importBundledLanguage = name => {
|
|
103
|
+
const id = BUNDLED_NAMES.get(name);
|
|
104
|
+
const langs = new Map();
|
|
105
|
+
const pending = [id];
|
|
106
|
+
|
|
107
|
+
while (pending.length > 0) {
|
|
108
|
+
const grammars = require(`shiki/langs/${pending.pop()}.mjs`).default;
|
|
109
|
+
|
|
110
|
+
for (const lang of grammars) {
|
|
111
|
+
if (langs.has(lang.name)) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
langs.set(lang.name, lang);
|
|
116
|
+
|
|
117
|
+
const { embeddedLangs = [], embeddedLangsLazy = [] } = lang;
|
|
118
|
+
|
|
119
|
+
for (const embedded of [...embeddedLangs, ...embeddedLangsLazy]) {
|
|
120
|
+
if (BUNDLED_NAMES.has(embedded)) {
|
|
121
|
+
pending.push(BUNDLED_NAMES.get(embedded));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const { scopeName } = langs.get(id);
|
|
128
|
+
|
|
129
|
+
return [...langs.values()].map(lang =>
|
|
130
|
+
lang.injectTo ? { ...lang, injectTo: [scopeName] } : lang
|
|
131
|
+
);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Adds the bundled languages that languages embed, which Shiki registers
|
|
136
|
+
* along with them.
|
|
137
|
+
*
|
|
138
|
+
* @param {Array<import('shiki').LanguageRegistration>} langs - The languages
|
|
139
|
+
* @returns {Array<import('shiki').LanguageRegistration>}
|
|
140
|
+
*/
|
|
141
|
+
const withEmbeddedLanguages = langs => {
|
|
142
|
+
const names = new Set(langs.map(({ name }) => name));
|
|
143
|
+
|
|
144
|
+
const embedded = langs
|
|
145
|
+
.flatMap(({ embeddedLangs = [], embeddedLangsLazy = [] }) => [
|
|
146
|
+
...embeddedLangs,
|
|
147
|
+
...embeddedLangsLazy,
|
|
148
|
+
])
|
|
149
|
+
.filter(name => !names.has(name) && BUNDLED_NAMES.has(name))
|
|
150
|
+
.flatMap(importBundledLanguage);
|
|
151
|
+
|
|
152
|
+
return [...embedded, ...langs];
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Imports a list of options, each given as it is, or as a module (a path
|
|
157
|
+
* relative to the working directory, or a URL) default-exporting it, or a list
|
|
158
|
+
* of them.
|
|
159
|
+
*
|
|
160
|
+
* @template T
|
|
161
|
+
* @param {Array<string | T>} options - The options
|
|
162
|
+
* @returns {Promise<Array<T>>}
|
|
163
|
+
*/
|
|
164
|
+
const importList = async options => {
|
|
165
|
+
const imports = options.map(option =>
|
|
166
|
+
typeof option === 'string' ? importFromURL(option) : option
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const imported = await Promise.all(imports);
|
|
170
|
+
|
|
171
|
+
return imported.flat();
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Imports a theme: the name of one Shiki bundles, a module default-exporting
|
|
176
|
+
* one, or the theme itself. Shiki tells themes apart by their name, so a theme
|
|
177
|
+
* without one is named after its color scheme.
|
|
178
|
+
*
|
|
179
|
+
* @param {string | import('shiki').ThemeRegistration} theme - The theme
|
|
180
|
+
* @param {'light' | 'dark'} scheme - Its color scheme
|
|
181
|
+
* @returns {Promise<import('shiki').ThemeRegistration>}
|
|
182
|
+
*/
|
|
183
|
+
const importTheme = async (theme, scheme) => {
|
|
184
|
+
let imported = theme;
|
|
185
|
+
|
|
186
|
+
if (typeof theme === 'string') {
|
|
187
|
+
if (theme in bundledThemes) {
|
|
188
|
+
const { default: bundledTheme } = await bundledThemes[theme]();
|
|
189
|
+
|
|
190
|
+
imported = bundledTheme;
|
|
191
|
+
} else {
|
|
192
|
+
imported = await importFromURL(theme);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return { name: `custom-${scheme}`, ...imported };
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Imports the options of a highlighter, and creates it.
|
|
201
|
+
*
|
|
202
|
+
* @param {import('./rehype.mjs').ShikiOptions} options - The options
|
|
203
|
+
* @returns {Promise<SyntaxHighlighter>}
|
|
204
|
+
*/
|
|
205
|
+
const importHighlighter = async ({
|
|
206
|
+
langs = [],
|
|
207
|
+
langAlias = {},
|
|
208
|
+
themes,
|
|
209
|
+
transformers = [],
|
|
210
|
+
}) => {
|
|
211
|
+
engine ??= createEngine();
|
|
212
|
+
|
|
213
|
+
const [regexEngine, importedLangs, importedTransformers] = await Promise.all([
|
|
214
|
+
engine,
|
|
215
|
+
importList(langs),
|
|
216
|
+
importList(transformers),
|
|
217
|
+
]);
|
|
218
|
+
|
|
219
|
+
const coreOptions = {
|
|
220
|
+
engine: regexEngine,
|
|
221
|
+
// The bundled languages are registered as code uses them
|
|
222
|
+
langs: withEmbeddedLanguages(importedLangs),
|
|
223
|
+
// A copy, as Shiki adds the aliases of the languages it registers to it
|
|
224
|
+
langAlias: { ...langAlias },
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
// Without themes of its own, the highlighter has a default light and dark one
|
|
228
|
+
if (themes) {
|
|
229
|
+
const [light, dark] = await Promise.all([
|
|
230
|
+
importTheme(themes.light, 'light'),
|
|
231
|
+
importTheme(themes.dark, 'dark'),
|
|
232
|
+
]);
|
|
233
|
+
|
|
234
|
+
coreOptions.themes = [light, dark];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Creates the Shiki instance, on first use, and the options it highlights
|
|
239
|
+
* with.
|
|
240
|
+
*/
|
|
241
|
+
const load = lazy(() => {
|
|
242
|
+
const { shiki } = createSyntaxHighlighter({ coreOptions });
|
|
243
|
+
|
|
244
|
+
// The themes are given by name: Shiki keeps the themes it loaded parsed,
|
|
245
|
+
// but parses a theme object it's given again for every highlight
|
|
246
|
+
const [light, dark] =
|
|
247
|
+
coreOptions.themes?.map(theme => theme.name) ?? shiki.getLoadedThemes();
|
|
248
|
+
|
|
249
|
+
const highlightOptions = {
|
|
250
|
+
themes: { light, dark },
|
|
251
|
+
defaultColor: 'light',
|
|
252
|
+
transformers: importedTransformers,
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
return { shiki, highlightOptions };
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Resolves a language, falling back to plain text for unknown ones. A
|
|
260
|
+
* bundled language is registered the first time it's resolved.
|
|
261
|
+
*
|
|
262
|
+
* @param {string} [languageId]
|
|
263
|
+
* @returns {string}
|
|
264
|
+
*/
|
|
265
|
+
const resolveLanguage = languageId => {
|
|
266
|
+
if (!languageId) {
|
|
267
|
+
return FALLBACK_LANGUAGE;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const instance = load().shiki;
|
|
271
|
+
const name = instance.resolveLangAlias(languageId.toLowerCase());
|
|
272
|
+
|
|
273
|
+
if (isSpecialLang(name) || instance.getLoadedLanguages().includes(name)) {
|
|
274
|
+
return languageId;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (!BUNDLED_NAMES.has(name)) {
|
|
278
|
+
return FALLBACK_LANGUAGE;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
instance.loadLanguageSync(importBundledLanguage(name));
|
|
282
|
+
|
|
283
|
+
return languageId;
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* The options Shiki highlights code with.
|
|
288
|
+
*
|
|
289
|
+
* @param {string} lang - The language of the code
|
|
290
|
+
* @param {Record<string, unknown>} meta - Its metadata
|
|
291
|
+
*/
|
|
292
|
+
const optionsFor = (lang, meta) => ({
|
|
293
|
+
lang: resolveLanguage(lang),
|
|
294
|
+
...load().highlightOptions,
|
|
295
|
+
meta,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
langs: [...BUNDLED_LANGUAGES, ...importedLangs],
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* The Shiki instance.
|
|
303
|
+
*/
|
|
304
|
+
get shiki() {
|
|
305
|
+
return load().shiki;
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
resolveLanguage,
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Highlights code, returning the inner HTML of its `<code>` element.
|
|
312
|
+
*
|
|
313
|
+
* @param {string} code - The code
|
|
314
|
+
* @param {string} lang - Its language
|
|
315
|
+
* @param {Record<string, unknown>} [meta] - Its metadata
|
|
316
|
+
*/
|
|
317
|
+
highlightToHtml: (code, lang, meta = {}) =>
|
|
318
|
+
load()
|
|
319
|
+
.shiki.codeToHtml(code, optionsFor(lang, meta))
|
|
320
|
+
// Shiki wraps the highlighted code in a <pre> and a <code>
|
|
321
|
+
.match(/<code>(.+?)<\/code>/s)[1],
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Highlights code, returning a HAST tree.
|
|
325
|
+
*
|
|
326
|
+
* @param {string} code - The code
|
|
327
|
+
* @param {string} lang - Its language
|
|
328
|
+
* @param {Record<string, unknown>} [meta] - Its metadata
|
|
329
|
+
*/
|
|
330
|
+
highlightToHast: (code, lang, meta = {}) =>
|
|
331
|
+
load().shiki.codeToHast(code, optionsFor(lang, meta)),
|
|
332
|
+
};
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Creates a highlighter of every language Shiki bundles, with the given
|
|
337
|
+
* options (see `./rehype.mjs`). Its Shiki instance is created on first use,
|
|
338
|
+
* each bundled language is registered once code in it is highlighted, and the
|
|
339
|
+
* same options give the same highlighter.
|
|
340
|
+
*
|
|
341
|
+
* @param {import('./rehype.mjs').ShikiOptions} [options] - The options
|
|
342
|
+
* @returns {Promise<SyntaxHighlighter>}
|
|
343
|
+
*/
|
|
344
|
+
export const createHighlighter = (options = {}) => {
|
|
345
|
+
const key = JSON.stringify(options);
|
|
346
|
+
|
|
347
|
+
if (!highlighters.has(key)) {
|
|
348
|
+
highlighters.set(key, importHighlighter(options));
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return highlighters.get(key);
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Gets the highlighter of the Shiki plugin of a generator's Markdown pipeline,
|
|
356
|
+
* as loaded on the current thread, to highlight code as the pipeline does.
|
|
357
|
+
*
|
|
358
|
+
* @param {string} generator - The name of the generator
|
|
359
|
+
* @returns {SyntaxHighlighter}
|
|
360
|
+
*/
|
|
361
|
+
export const getHighlighter = generator => {
|
|
362
|
+
const shiki = getMarkdownPlugins(generator).rehypePlugins.find(
|
|
363
|
+
plugin => Array.isArray(plugin) && plugin[0].highlighter
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
if (!shiki) {
|
|
367
|
+
throw new Error(
|
|
368
|
+
`The Markdown pipeline of "${generator}" does not highlight code`
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return shiki[0].highlighter;
|
|
373
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import rehypeShikiji from '@node-core/rehype-shiki/plugin';
|
|
4
|
+
|
|
5
|
+
import { createHighlighter } from './highlighter.mjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The options of the Shiki plugin. Its modules are paths relative to the
|
|
9
|
+
* working directory, or URLs.
|
|
10
|
+
*
|
|
11
|
+
* @typedef {Object} ShikiOptions
|
|
12
|
+
* @property {Array<string | import('shiki').LanguageRegistration>} [langs] - More languages: grammars, or modules default-exporting grammars
|
|
13
|
+
* @property {Record<string, string>} [langAlias] - Aliases of languages, e.g. `{ conf: 'ini' }`
|
|
14
|
+
* @property {{ light: string | import('shiki').ThemeRegistration, dark: string | import('shiki').ThemeRegistration }} [themes] - The light and dark themes: names of themes Shiki bundles, modules default-exporting themes, or themes
|
|
15
|
+
* @property {Array<string>} [transformers] - Modules default-exporting Shiki transformers, or lists of them
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Loads the rehype plugin highlighting code blocks with Shiki, in every
|
|
20
|
+
* language it bundles. The plugin exposes its highlighter as `highlighter`
|
|
21
|
+
* (see `getHighlighter`).
|
|
22
|
+
*
|
|
23
|
+
* @param {ShikiOptions} [options] - The options
|
|
24
|
+
* @returns {Promise<import('unified').Plugin & { highlighter: import('./highlighter.mjs').SyntaxHighlighter }>}
|
|
25
|
+
*/
|
|
26
|
+
export async function load(options) {
|
|
27
|
+
const highlighter = await createHighlighter(options);
|
|
28
|
+
const transformer = await rehypeShikiji({ highlighter });
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Highlights the code blocks of a tree.
|
|
32
|
+
*/
|
|
33
|
+
const shiki = () => transformer;
|
|
34
|
+
|
|
35
|
+
shiki.highlighter = highlighter;
|
|
36
|
+
|
|
37
|
+
return shiki;
|
|
38
|
+
}
|
package/src/{utils/type-annotations/highlighted.mjs → plugins/type-annotations/highlighter.mjs}
RENAMED
|
@@ -1,38 +1,33 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
import { highlighter } from '#utils/highlighter.mjs';
|
|
4
|
-
|
|
5
3
|
import { typeAnnotationToHast } from './hast.mjs';
|
|
6
4
|
|
|
7
|
-
// Kept apart from `./hast.mjs` on purpose: importing this module loads Shiki
|
|
8
|
-
// (every grammar plus the regex engine), which only the pipelines that
|
|
9
|
-
// highlight should pay for. The `ast` and `metadata` stages never do.
|
|
10
|
-
const [lightTheme, darkTheme] = highlighter.shiki.getLoadedThemes();
|
|
11
|
-
|
|
12
5
|
/**
|
|
13
|
-
*
|
|
14
|
-
* the web (JSX) pipeline. The whole type is highlighted as one
|
|
15
|
-
* fragment, and each resolved identifier's exact character range is
|
|
16
|
-
* in an `<a>` via Shiki decorations. Values that are not TypeScript
|
|
17
|
-
* names such as `HTTP/2 Headers Object`) are highlighted as plain
|
|
18
|
-
* their prose is not coloured as operators and numeric literals.
|
|
6
|
+
* Creates the syntax-highlighted mdast→hast handler for `typeAnnotation`
|
|
7
|
+
* nodes, used by the web (JSX) pipeline. The whole type is highlighted as one
|
|
8
|
+
* inline fragment, and each resolved identifier's exact character range is
|
|
9
|
+
* wrapped in an `<a>` via Shiki decorations. Values that are not TypeScript
|
|
10
|
+
* (display names such as `HTTP/2 Headers Object`) are highlighted as plain
|
|
11
|
+
* text, so their prose is not coloured as operators and numeric literals.
|
|
19
12
|
*
|
|
20
13
|
* Falls back to the minimal handler when the type failed to parse or nothing
|
|
21
14
|
* resolved (no point paying for highlighting then).
|
|
22
15
|
*
|
|
23
|
-
* @param {import('
|
|
24
|
-
* @
|
|
25
|
-
* @returns {import('hast').Element}
|
|
16
|
+
* @param {() => import('#plugins/shiki/highlighter.mjs').SyntaxHighlighter} getHighlighter - Gives the highlighter, once a type is highlighted
|
|
17
|
+
* @returns {(state: import('mdast-util-to-hast').State, node: import('mdast').Node) => import('hast').Element}
|
|
26
18
|
*/
|
|
27
|
-
export const
|
|
19
|
+
export const createTypeAnnotationHandler = getHighlighter => (state, node) => {
|
|
28
20
|
const links = node.data?.links ?? [];
|
|
29
21
|
|
|
30
22
|
if (node.data?.parseError || links.length === 0) {
|
|
31
23
|
return typeAnnotationToHast(state, node);
|
|
32
24
|
}
|
|
33
25
|
|
|
34
|
-
const
|
|
35
|
-
|
|
26
|
+
const { shiki, resolveLanguage } = getHighlighter();
|
|
27
|
+
const [lightTheme, darkTheme] = shiki.getLoadedThemes();
|
|
28
|
+
|
|
29
|
+
const root = shiki.codeToHast(node.value, {
|
|
30
|
+
lang: resolveLanguage(node.data?.typescript ? 'typescript' : 'text'),
|
|
36
31
|
themes: { light: lightTheme, dark: darkTheme },
|
|
37
32
|
decorations: links.map(({ start, end, href }) => ({
|
|
38
33
|
start,
|
|
@@ -10,12 +10,16 @@ import { typeAnnotationSyntax } from './syntax.mjs';
|
|
|
10
10
|
* Remark plugin that teaches the parser to treat any balanced `{...}` span in
|
|
11
11
|
* text as a `typeAnnotation` node whose value is a TypeScript type expression.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* It does nothing in MDX, where `{...}` is a real expression, handled by
|
|
14
|
+
* remark-mdx instead.
|
|
15
15
|
*
|
|
16
16
|
* @this {import('unified').Processor}
|
|
17
17
|
*/
|
|
18
18
|
export default function remarkTypeAnnotations() {
|
|
19
|
+
if (this.data('mdx')) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
19
23
|
const data = this.data();
|
|
20
24
|
|
|
21
25
|
(data.micromarkExtensions ??= []).push(typeAnnotationSyntax());
|
|
@@ -3,6 +3,7 @@ import { workerData } from 'node:worker_threads';
|
|
|
3
3
|
import { loadGenerator } from '#generators/loader.mjs';
|
|
4
4
|
import logger from '#logger/index.mjs';
|
|
5
5
|
import { setConfig } from '#utils/configuration/index.mjs';
|
|
6
|
+
import { loadMarkdownPlugins } from '#utils/markdown/plugins.mjs';
|
|
6
7
|
|
|
7
8
|
if (workerData?.logLevel !== undefined) {
|
|
8
9
|
logger.setLogLevel(workerData.logLevel);
|
|
@@ -26,5 +27,8 @@ export default async ({
|
|
|
26
27
|
|
|
27
28
|
const generator = await loadGenerator(generatorSpecifier);
|
|
28
29
|
|
|
30
|
+
// Plugins can't be sent to workers, which import the pipeline themselves
|
|
31
|
+
await loadMarkdownPlugins(generator, configuration[generator.name]?.markdown);
|
|
32
|
+
|
|
29
33
|
return generator.processChunk(input, itemIndices, extra);
|
|
30
34
|
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The heap size limit of each worker thread (V8's old space), in MB. V8 lets a
|
|
5
|
+
* heap grow to several times its live data before collecting it, the more the
|
|
6
|
+
* higher its limit, so every worker held several times what it was using. The
|
|
7
|
+
* biggest page of the Node.js docs, `all.html`, takes ~300MB. Node.js's
|
|
8
|
+
* `--max-old-space-size` replaces it.
|
|
9
|
+
*/
|
|
10
|
+
export const WORKER_HEAP_SIZE = 512;
|
package/src/threading/index.mjs
CHANGED
|
@@ -2,6 +2,8 @@ import Piscina from 'piscina';
|
|
|
2
2
|
|
|
3
3
|
import logger from '#logger/index.mjs';
|
|
4
4
|
|
|
5
|
+
import { WORKER_HEAP_SIZE } from './constants.mjs';
|
|
6
|
+
|
|
5
7
|
const poolLogger = logger.child('WorkerPool');
|
|
6
8
|
|
|
7
9
|
const workerScript = import.meta.resolve('./chunk-worker.mjs');
|
|
@@ -22,7 +24,10 @@ export default function createWorkerPool(threads) {
|
|
|
22
24
|
filename: workerScript,
|
|
23
25
|
minThreads: 0,
|
|
24
26
|
maxThreads: threads,
|
|
25
|
-
|
|
27
|
+
// A worker idle for 250ms ends, so its heap is gone during a long stretch
|
|
28
|
+
// of work on the main thread, such as bundling the site
|
|
29
|
+
idleTimeout: 250,
|
|
30
|
+
resourceLimits: { maxOldGenerationSizeMb: WORKER_HEAP_SIZE },
|
|
26
31
|
workerData: { logLevel: logger.getLogLevel() },
|
|
27
32
|
});
|
|
28
33
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* The default `threads` ceiling. Each worker
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* The default `threads` ceiling. Each worker holds a heap of its own, with the
|
|
5
|
+
* libraries and the pages it is working on, so past a few threads memory, not
|
|
6
|
+
* CPU, is what runs out. `--threads` raises it.
|
|
7
7
|
*/
|
|
8
8
|
export const DEFAULT_MAX_THREADS = 4;
|
|
9
9
|
|