Allow Code component to run in Vercel (#5409)

* Allow Code component to run in Vercel

* Adding a changeset

* Pass theme through to `codeToHtml`

* Use script to generate languages

* another bundling approach

* fix lint warnings
This commit is contained in:
Matthew Phillips 2022-11-16 08:57:53 -05:00 committed by GitHub
parent 6c0f966ef8
commit 9f80a4046f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 1745 additions and 8 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Fix Code component usage in Vercel

View file

@ -56,7 +56,10 @@ const highlighter = await getHighlighter({
// Load custom lang if passed an object, otherwise load the default
langs: typeof lang !== 'string' ? [lang] : undefined,
});
const _html = highlighter.codeToHtml(code, { lang: typeof lang === 'string' ? lang : lang.id });
const _html = highlighter.codeToHtml(code, {
lang: typeof lang === 'string' ? lang : lang.id,
theme
});
const html = repairShikiTheme(_html);
---

View file

@ -1,8 +1,13 @@
import { getHighlighter as getShikiHighlighter } from 'shiki';
import { themes } from './shiki-themes.js';
import { languages } from './shiki-languages.js';
// Caches Promise<Highligher> for reuse when the same theme and langs are provided
const _resolvedHighlighters = new Map();
/** @type {Promise<any>} */
let _allLanguages;
function stringify(opts) {
// Always sort keys before stringifying to make sure objects match regardless of parameter ordering
return JSON.stringify(opts, Object.keys(opts).sort());
@ -12,16 +17,34 @@ function stringify(opts) {
* @param {import('shiki').HighlighterOptions} opts
* @returns {Promise<import('shiki').Highlighter>}
*/
export function getHighlighter(opts) {
const key = stringify(opts);
// Highlighter has already been requested, reuse the same instance
if (_resolvedHighlighters.has(key)) {
return _resolvedHighlighters.get(key);
async function resolveHighlighter(opts) {
const resolvedThemes = [];
if(opts.theme && (opts.theme in themes)) {
resolvedThemes.push(await themes[opts.theme]());
}
let resolvedLanguages;
if(opts.langs) {
resolvedLanguages = opts.langs;
} else {
if(!_allLanguages) {
_allLanguages = (await Promise.all(Object.values(languages).map(fn => fn()))).filter(Boolean);
}
resolvedLanguages = await _allLanguages;
}
/** @type {import('shiki').HighlighterOptions} */
const highlighterOptions = {
...opts,
themes: resolvedThemes,
langs: resolvedLanguages
};
// Do not pass through the theme as that will attempt to load it, even if it's included in themes
delete highlighterOptions['theme'];
// Start the async getHighlighter call and cache the Promise
const highlighter = getShikiHighlighter(opts).then((hl) => {
const highlighter = getShikiHighlighter(highlighterOptions).then((hl) => {
hl.setColorReplacements({
'#000001': 'var(--astro-code-color-text)',
'#000002': 'var(--astro-code-color-background)',
@ -37,6 +60,23 @@ export function getHighlighter(opts) {
});
return hl;
});
return highlighter;
}
/**
* @param {import('shiki').HighlighterOptions} opts
* @returns {Promise<import('shiki').Highlighter>}
*/
export function getHighlighter(opts) {
const key = stringify(opts);
// Highlighter has already been requested, reuse the same instance
if (_resolvedHighlighters.has(key)) {
return _resolvedHighlighters.get(key);
}
const highlighter = resolveHighlighter(opts);
_resolvedHighlighters.set(key, highlighter);
return highlighter;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,31 @@
export const themes = {
'css-variables': () => import('shiki/themes/css-variables.json').then(mod => mod.default),
'dark-plus': () => import('shiki/themes/dark-plus.json').then(mod => mod.default),
'dracula-soft': () => import('shiki/themes/dracula-soft.json').then(mod => mod.default),
'dracula': () => import('shiki/themes/dracula.json').then(mod => mod.default),
'github-dark-dimmed': () => import('shiki/themes/github-dark-dimmed.json').then(mod => mod.default),
'github-dark': () => import('shiki/themes/github-dark.json').then(mod => mod.default),
'github-light': () => import('shiki/themes/github-light.json').then(mod => mod.default),
'hc_light': () => import('shiki/themes/hc_light.json').then(mod => mod.default),
'light-plus': () => import('shiki/themes/light-plus.json').then(mod => mod.default),
'material-darker': () => import('shiki/themes/material-darker.json').then(mod => mod.default),
'material-default': () => import('shiki/themes/material-default.json').then(mod => mod.default),
'material-lighter': () => import('shiki/themes/material-lighter.json').then(mod => mod.default),
'material-ocean': () => import('shiki/themes/material-ocean.json').then(mod => mod.default),
'material-palenight': () => import('shiki/themes/material-palenight.json').then(mod => mod.default),
'min-dark': () => import('shiki/themes/min-dark.json').then(mod => mod.default),
'min-light': () => import('shiki/themes/min-light.json').then(mod => mod.default),
'monokai': () => import('shiki/themes/monokai.json').then(mod => mod.default),
'nord': () => import('shiki/themes/nord.json').then(mod => mod.default),
'one-dark-pro': () => import('shiki/themes/one-dark-pro.json').then(mod => mod.default),
'poimandres': () => import('shiki/themes/poimandres.json').then(mod => mod.default),
'rose-pine-dawn': () => import('shiki/themes/rose-pine-dawn.json').then(mod => mod.default),
'rose-pine-moon': () => import('shiki/themes/rose-pine-moon.json').then(mod => mod.default),
'rose-pine': () => import('shiki/themes/rose-pine.json').then(mod => mod.default),
'slack-dark': () => import('shiki/themes/slack-dark.json').then(mod => mod.default),
'slack-ochin': () => import('shiki/themes/slack-ochin.json').then(mod => mod.default),
'solarized-dark': () => import('shiki/themes/solarized-dark.json').then(mod => mod.default),
'solarized-light': () => import('shiki/themes/solarized-light.json').then(mod => mod.default),
'vitesse-dark': () => import('shiki/themes/vitesse-dark.json').then(mod => mod.default),
'vitesse-light': () => import('shiki/themes/vitesse-light.json').then(mod => mod.default)
};

View file

@ -0,0 +1,41 @@
import fs from 'fs';
const dir = await fs.promises.readdir('packages/astro/node_modules/shiki/languages/');
const langImports = dir.map(f => {
const key = f.slice(0, f.indexOf('.tmLanguage.json'));
return [
key,
`import('shiki/languages/${f}').then(mod => mod.default).then(grammar => {
const lang = BUNDLED_LANGUAGES.find(l => l.id === '${key}');
if(lang) {
return {
...lang,
grammar
};
} else {
return undefined;
}
})`
];
});
let code = `import { BUNDLED_LANGUAGES } from 'shiki';
export const languages = {`;
let i = 0;
for(const [key, imp] of langImports) {
if(i > 0) {
code += ',';
}
code += `\n\t'${key}': () => ${imp}`;
i++;
}
code += '\n};';
// eslint-disable-next-line no-console
console.log(code);
/**
* Run this script and pipe it into the output file, for ex.
* node packages/astro/scripts/shiki-gen-languages.mjs > packages/astro/components/shiki-languages.js
*/

View file

@ -0,0 +1,29 @@
import fs from 'fs';
const dir = await fs.promises.readdir('packages/astro/node_modules/shiki/themes/');
const themeImports = dir.map(f => {
return [
f.slice(0, f.indexOf('.json')),
`import('shiki/themes/${f}').then(mod => mod.default)`
];
});
let code = `export const themes = {`;
let i = 0;
for(const [key, imp] of themeImports) {
if(i > 0) {
code += ',';
}
code += `\n\t'${key}': () => ${imp}`;
i++;
}
code += '\n};';
// eslint-disable-next-line no-console
console.log(code);
/**
* Run this script and pipe it into the output file, for ex.
* node packages/astro/scripts/shiki-gen-themes.mjs > packages/astro/components/shiki-themes.js
*/