@expressive-code/plugin-shiki
Advanced tools
Comparing version 0.29.4 to 0.30.0
import { ExpressiveCodeTheme, ExpressiveCodePlugin } from '@expressive-code/core'; | ||
import { BUNDLED_THEMES } from 'shiki'; | ||
import { MaybeGetter, MaybeArray, LanguageRegistration as LanguageRegistration$1, bundledThemes } from 'shikiji'; | ||
type Optional<T, K extends keyof T> = Omit<T, K> & Pick<Partial<T>, K>; | ||
type IRawRepository = Optional<LanguageRegistration$1['repository'], '$self' | '$base'>; | ||
interface LanguageRegistration extends Omit<LanguageRegistration$1, 'repository'> { | ||
repository?: IRawRepository | undefined; | ||
} | ||
type LanguageInput = MaybeGetter<MaybeArray<LanguageRegistration>>; | ||
interface PluginShikiOptions { | ||
/** | ||
* An optional list of additional languages that should be available for syntax highlighting. | ||
* | ||
* Note that you do not need to include languages that are already supported by Shiki. | ||
*/ | ||
langs?: LanguageInput[] | undefined; | ||
} | ||
/** | ||
* A list of all themes bundled with Shiki. | ||
*/ | ||
type BundledShikiTheme = Exclude<(typeof BUNDLED_THEMES)[number], 'css-variables'>; | ||
type BundledShikiTheme = Exclude<keyof typeof bundledThemes, 'css-variables'>; | ||
/** | ||
* Loads a theme bundled with Shiki for use with Expressive Code. | ||
* | ||
* If the given theme name is not a bundled theme, it will be treated as a path to a theme file. | ||
*/ | ||
declare function loadShikiTheme(bundledThemeName: BundledShikiTheme): Promise<ExpressiveCodeTheme>; | ||
declare function pluginShiki(): ExpressiveCodePlugin; | ||
declare function pluginShiki(options?: PluginShikiOptions): ExpressiveCodePlugin; | ||
export { BundledShikiTheme, loadShikiTheme, pluginShiki }; | ||
export { BundledShikiTheme, PluginShikiOptions, loadShikiTheme, pluginShiki }; |
// src/index.ts | ||
import { ExpressiveCodeTheme as ExpressiveCodeTheme2, InlineStyleAnnotation } from "@expressive-code/core"; | ||
// src/cache.ts | ||
import { getHighlighter } from "shiki"; | ||
// src/highlighter.ts | ||
import { getHighlighter, isSpecialLang, bundledLanguages } from "shikiji"; | ||
import { getStableObjectHash } from "@expressive-code/core"; | ||
var highlighterPromise; | ||
var highlighterPromiseByConfig = /* @__PURE__ */ new Map(); | ||
var themeCacheKeys = /* @__PURE__ */ new WeakMap(); | ||
function getThemeCacheKey(theme) { | ||
let key = themeCacheKeys.get(theme); | ||
if (!key) { | ||
key = `${theme.name}-${getStableObjectHash({ bg: theme.bg, fg: theme.fg, settings: theme.settings })}`; | ||
themeCacheKeys.set(theme, key); | ||
async function getCachedHighlighter(config = {}) { | ||
const configCacheKey = getStableObjectHash(config); | ||
let highlighterPromise = highlighterPromiseByConfig.get(configCacheKey); | ||
if (highlighterPromise === void 0) { | ||
highlighterPromise = getHighlighter({ | ||
...config.langs ? { langs: config.langs } : {} | ||
}); | ||
} | ||
return key; | ||
return await highlighterPromise; | ||
} | ||
async function getCachedHighlighter({ theme, cacheKey }) { | ||
const themeUsingCacheKey = { ...theme, name: cacheKey }; | ||
if (highlighterPromise === void 0) { | ||
highlighterPromise = getHighlighter({ theme: themeUsingCacheKey }); | ||
async function ensureThemeIsLoaded(highlighter, theme) { | ||
let cacheKey = themeCacheKeys.get(theme); | ||
if (!cacheKey) { | ||
cacheKey = `${theme.name}-${getStableObjectHash({ bg: theme.bg, fg: theme.fg, settings: theme.settings })}`; | ||
themeCacheKeys.set(theme, cacheKey); | ||
} | ||
const highlighter = await highlighterPromise; | ||
if (!highlighter.getLoadedThemes().includes(cacheKey)) { | ||
const themeUsingCacheKey = { ...theme, name: cacheKey, settings: theme.settings }; | ||
await highlighter.loadTheme(themeUsingCacheKey); | ||
} | ||
return highlighter; | ||
return cacheKey; | ||
} | ||
async function ensureLanguageIsLoaded(highlighter, language) { | ||
const loadedLanguages = new Set(highlighter.getLoadedLanguages()); | ||
if (!loadedLanguages.has(language) && !isSpecialLang(language)) { | ||
if (!Object.keys(bundledLanguages).includes(language)) { | ||
language = "txt"; | ||
} | ||
await highlighter.loadLanguage(language); | ||
} | ||
return language; | ||
} | ||
// src/index.ts | ||
import { BUNDLED_THEMES, loadTheme, FontStyle } from "shiki"; | ||
import { bundledThemes } from "shikiji"; | ||
async function loadShikiTheme(bundledThemeName) { | ||
const shikiTheme = await loadTheme(BUNDLED_THEMES.includes(bundledThemeName) ? `themes/${bundledThemeName}.json` : bundledThemeName); | ||
const shikiThemeWithoutType = { ...shikiTheme }; | ||
delete shikiThemeWithoutType.type; | ||
return new ExpressiveCodeTheme2(shikiThemeWithoutType); | ||
const shikiTheme = (await bundledThemes[bundledThemeName]()).default; | ||
return new ExpressiveCodeTheme2(shikiTheme); | ||
} | ||
function pluginShiki() { | ||
function pluginShiki(options = {}) { | ||
const { langs } = options; | ||
return { | ||
@@ -47,19 +59,25 @@ name: "Shiki", | ||
} | ||
let highlighter; | ||
try { | ||
highlighter = await getCachedHighlighter({ langs }); | ||
} catch (error) { | ||
const msg = error instanceof Error ? error.message : error; | ||
throw new Error(`Failed to load syntax highlighter. Please ensure that the configured langs are supported by Shikiji. Received error message: "${msg}"`, { | ||
cause: error | ||
}); | ||
} | ||
const loadedLanguageName = await ensureLanguageIsLoaded(highlighter, codeBlock.language); | ||
if (loadedLanguageName !== codeBlock.language) { | ||
logger.warn( | ||
`Found unknown code block language "${codeBlock.language}" in ${codeBlock.parentDocument?.sourceFilePath ? `document "${codeBlock.parentDocument?.sourceFilePath}"` : "markdown/MDX document"}. Using "${loadedLanguageName}" instead. You can add custom languages using the "langs" config option.` | ||
); | ||
} | ||
for (let styleVariantIndex = 0; styleVariantIndex < styleVariants.length; styleVariantIndex++) { | ||
const theme = styleVariants[styleVariantIndex].theme; | ||
const cacheKey = getThemeCacheKey(theme); | ||
const highlighter = await getCachedHighlighter({ theme, cacheKey }); | ||
let tokenLines; | ||
if (codeBlock.language === "ansi") { | ||
tokenLines = highlighter.ansiToThemedTokens(code, cacheKey); | ||
} else { | ||
const loadedLanguages = highlighter.getLoadedLanguages().map((lang) => lang.toString()); | ||
const highlighterLanguage = loadedLanguages.includes(codeBlock.language) ? codeBlock.language : "txt"; | ||
if (highlighterLanguage !== codeBlock.language && styleVariantIndex === 0) { | ||
logger.warn( | ||
`Found unknown code block language "${codeBlock.language}" in ${codeBlock.parentDocument?.sourceFilePath ? `document "${codeBlock.parentDocument?.sourceFilePath}"` : "markdown/MDX document"}. Using "${highlighterLanguage}" instead.` | ||
); | ||
} | ||
tokenLines = highlighter.codeToThemedTokens(code, highlighterLanguage, cacheKey, { includeExplanation: false }); | ||
} | ||
const loadedThemeName = await ensureThemeIsLoaded(highlighter, theme); | ||
const tokenLines = highlighter.codeToThemedTokens(code, { | ||
lang: loadedLanguageName, | ||
theme: loadedThemeName, | ||
includeExplanation: false | ||
}); | ||
tokenLines.forEach((line, lineIndex) => { | ||
@@ -72,3 +90,3 @@ if (codeBlock.language === "ansi" && styleVariantIndex === 0) | ||
const tokenEndIndex = charIndex + tokenLength; | ||
const fontStyle = token.fontStyle || FontStyle.None; | ||
const fontStyle = token.fontStyle || 0 /* None */; | ||
codeLines[lineIndex].addAnnotation( | ||
@@ -78,5 +96,5 @@ new InlineStyleAnnotation({ | ||
color: token.color || theme.fg, | ||
italic: (fontStyle & FontStyle.Italic) === FontStyle.Italic, | ||
bold: (fontStyle & FontStyle.Bold) === FontStyle.Bold, | ||
underline: (fontStyle & FontStyle.Underline) === FontStyle.Underline, | ||
italic: (fontStyle & 1 /* Italic */) === 1 /* Italic */, | ||
bold: (fontStyle & 2 /* Bold */) === 2 /* Bold */, | ||
underline: (fontStyle & 4 /* Underline */) === 4 /* Underline */, | ||
inlineRange: { | ||
@@ -83,0 +101,0 @@ columnStart: charIndex, |
{ | ||
"name": "@expressive-code/plugin-shiki", | ||
"version": "0.29.4", | ||
"version": "0.30.0", | ||
"description": "Shiki syntax highlighting plugin for Expressive Code, a text marking & annotation engine for presenting source code on the web.", | ||
@@ -26,8 +26,8 @@ "keywords": [], | ||
"dependencies": { | ||
"@expressive-code/core": "^0.29.4", | ||
"shiki": "^0.14.1" | ||
"@expressive-code/core": "^0.30.0", | ||
"shikiji": "^0.8.0" | ||
}, | ||
"devDependencies": { | ||
"hast-util-to-html": "^8.0.4", | ||
"@internal/test-utils": "^0.2.22" | ||
"@internal/test-utils": "^0.2.23" | ||
}, | ||
@@ -34,0 +34,0 @@ "scripts": { |
@@ -13,2 +13,3 @@ # @expressive-code/plugin-shiki | ||
- [Next.js configuration example using `@next/mdx`](#nextjs-configuration-example-using-nextmdx) | ||
- [Available plugin options](#available-plugin-options) | ||
- [Advanced use cases](#advanced-use-cases) | ||
@@ -52,5 +53,5 @@ - [Manual installation](#manual-installation) | ||
This plugin does not have any configuration options. It automatically uses all themes defined in the Expressive Code configuration under the option `themes`. | ||
When using this plugin through higher-level integration packages, you can configure it by passing options to the higher-level package. It automatically uses all themes defined in the Expressive Code configuration under the option `themes`. | ||
Here are configuration examples on how to select themes in some popular site generators: | ||
Here are configuration examples on how to select themes and add custom language grammars in some popular site generators: | ||
@@ -74,2 +75,9 @@ ### Astro configuration example | ||
themes: ['dracula', 'solarized-light'], | ||
shiki: { | ||
// You can pass additional plugin options here, e.g. to load custom language grammars: | ||
// langs: [ | ||
// import('./some-exported-grammar.mjs'), | ||
// async () => JSON.parse(await fs.readFile('some-json-grammar.json', 'utf-8')) | ||
// ] | ||
}, | ||
} | ||
@@ -97,2 +105,9 @@ | ||
themes: ['dracula', 'solarized-light'], | ||
shiki: { | ||
// You can pass additional plugin options here, e.g. to load custom language grammars: | ||
// langs: [ | ||
// import('./some-exported-grammar.mjs'), | ||
// async () => JSON.parse(await fs.readFile('some-json-grammar.json', 'utf-8')) | ||
// ] | ||
}, | ||
} | ||
@@ -121,2 +136,18 @@ | ||
### Available plugin options | ||
You can pass the following options to the plugin: | ||
- `langs?: LanguageInput[]` | ||
A list of additional languages that should be available for syntax highlighting. | ||
You can pass any of the language input types supported by Shikiji, e.g.: | ||
- `import('./some-exported-grammar.mjs')` | ||
- `async () => JSON.parse(await fs.readFile('some-json-grammar.json', 'utf-8'))` | ||
See the [Shikiji documentation](https://github.com/antfu/shikiji) for more information. | ||
Note that you do not need to include languages that are already supported by Shiki. | ||
## Advanced use cases | ||
@@ -123,0 +154,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
51578
349
184
+ Addedshikiji@^0.8.0
+ Added@expressive-code/core@0.30.2(transitive)
+ Added@types/hast@3.0.4(transitive)
+ Added@types/mdast@4.0.4(transitive)
+ Added@types/unist@3.0.3(transitive)
+ Added@ungap/structured-clone@1.2.1(transitive)
+ Addeddequal@2.0.3(transitive)
+ Addeddevlop@1.1.0(transitive)
+ Addedhast-util-to-html@9.0.4(transitive)
+ Addedhast-util-whitespace@3.0.0(transitive)
+ Addedhtml-void-elements@3.0.0(transitive)
+ Addedmdast-util-to-hast@13.2.0(transitive)
+ Addedmicromark-util-character@2.1.1(transitive)
+ Addedmicromark-util-encode@2.0.1(transitive)
+ Addedmicromark-util-sanitize-uri@2.0.1(transitive)
+ Addedmicromark-util-symbol@2.0.1(transitive)
+ Addedmicromark-util-types@2.0.1(transitive)
+ Addedshikiji@0.8.7(transitive)
+ Addedtrim-lines@3.0.1(transitive)
+ Addedunist-util-is@6.0.0(transitive)
+ Addedunist-util-position@5.0.0(transitive)
+ Addedunist-util-stringify-position@4.0.0(transitive)
+ Addedunist-util-visit@5.0.0(transitive)
+ Addedunist-util-visit-parents@6.0.1(transitive)
+ Addedvfile@6.0.3(transitive)
+ Addedvfile-message@4.0.2(transitive)
- Removedshiki@^0.14.1
- Removed@expressive-code/core@0.29.4(transitive)
- Removedansi-sequence-parser@1.1.1(transitive)
- Removedjsonc-parser@3.3.1(transitive)
- Removedshiki@0.14.7(transitive)
- Removedvscode-oniguruma@1.7.0(transitive)
- Removedvscode-textmate@8.0.0(transitive)