diff options
author | Hippo | 2024-01-26 23:13:00 +0100 |
---|---|---|
committer | GitHub | 2024-01-26 23:13:00 +0100 |
commit | ce05dfb4b1e9b90fad057d5d4328e4445f986b3b (patch) | |
tree | 30a5ebd2c8b1a2d0d147a74c419e68474caedf70 | |
parent | 614bfdd3fffeeb71ccec1fb35c8d0765e02f7ebd (diff) | |
download | IT.starlight-ce05dfb4b1e9b90fad057d5d4328e4445f986b3b.tar.gz IT.starlight-ce05dfb4b1e9b90fad057d5d4328e4445f986b3b.tar.bz2 IT.starlight-ce05dfb4b1e9b90fad057d5d4328e4445f986b3b.zip |
Add Expressive Code `<Code>` component to Starlight (#1395)
Co-authored-by: Chris Swithinbank <swithinbank@gmail.com>
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
-rw-r--r-- | .changeset/shy-stingrays-turn.md | 5 | ||||
-rw-r--r-- | docs/src/content/docs/guides/components.mdx | 46 | ||||
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | packages/starlight/components.ts | 1 | ||||
-rw-r--r-- | packages/starlight/index.ts | 2 | ||||
-rw-r--r-- | packages/starlight/integrations/expressive-code/exports.ts | 2 | ||||
-rw-r--r-- | packages/starlight/integrations/expressive-code/index.ts | 120 | ||||
-rw-r--r-- | packages/starlight/integrations/expressive-code/theming.ts | 12 | ||||
-rw-r--r-- | packages/starlight/integrations/shared/pathToLocale.ts | 14 | ||||
-rw-r--r-- | packages/starlight/internal.ts | 1 | ||||
-rw-r--r-- | packages/starlight/package.json | 3 | ||||
-rw-r--r-- | pnpm-lock.yaml | 51 |
12 files changed, 174 insertions, 85 deletions
diff --git a/.changeset/shy-stingrays-turn.md b/.changeset/shy-stingrays-turn.md new file mode 100644 index 00000000..d53066d5 --- /dev/null +++ b/.changeset/shy-stingrays-turn.md @@ -0,0 +1,5 @@ +--- +"@astrojs/starlight": minor +--- + +Adds a new [`<Code>` component](https://starlight.astro.build/guides/components/#code) to render dynamic code strings with Expressive Code diff --git a/docs/src/content/docs/guides/components.mdx b/docs/src/content/docs/guides/components.mdx index 34658f32..56ffe1f0 100644 --- a/docs/src/content/docs/guides/components.mdx +++ b/docs/src/content/docs/guides/components.mdx @@ -195,3 +195,49 @@ The code above generates the following on the page: A list of all available icons is shown below with their associated names. Click an icon to copy the component code for it. <IconsList /> + +### Code + +Use the `<Code>` component to render syntax highlighted code when using a [Markdown code block](/guides/authoring-content/#code-blocks) is not possible, for example, to render data coming from external sources like files, databases, or APIs. + +See the [Expressive Code “Code Component” docs](https://expressive-code.com/key-features/code-component/) for full details of the props `<Code>` supports. + +```mdx +# src/content/docs/example.mdx + +import { Code } from '@astrojs/starlight/components'; + +export const exampleCode = `console.log('This could come from a file or CMS!');`; +export const fileName = 'example.js'; +export const highlights = ["file", "CMS"]; + +<Code code={exampleCode} lang="js" title={fileName} mark={highlights} /> +``` + +The code above generates the following on the page: + +import { Code } from '@astrojs/starlight/components'; + +export const exampleCode = `console.log('This could come from a file or CMS!');`; +export const fileName = 'example.js'; +export const highlights = ["file", "CMS"]; + +<Code code={exampleCode} lang="js" title={fileName} mark={highlights} /> + +#### Imported Code + +Use [Vite’s `?raw` import suffix](https://vitejs.dev/guide/assets#importing-asset-as-string) to import any code file as a string. +You can then pass this imported string to the `<Code>` component to include it on your page. + +```mdx title="src/content/docs/example.mdx" "?raw" +import { Code } from '@astrojs/starlight/components'; +import importedCode from '/src/env.d.ts?raw'; + +<Code code={importedCode} lang="ts" title="src/env.d.ts" /> +``` + +The code above generates the following on the page: + +import importedCode from '/src/env.d.ts?raw'; + +<Code code={importedCode} lang="ts" title="src/env.d.ts" /> diff --git a/package.json b/package.json index 3a1501af..1d7e1870 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "1.0.0", "description": "", "scripts": { - "build:examples": "pnpm --filter '@example/*' build", + "build:examples": "pnpm --no-bail --workspace-concurrency 1 --filter '@example/*' build", "size": "size-limit", "version": "pnpm changeset version && pnpm i --no-frozen-lockfile", "format": "prettier -w --cache --plugin prettier-plugin-astro ." diff --git a/packages/starlight/components.ts b/packages/starlight/components.ts index ac1c0f7b..15fc9de3 100644 --- a/packages/starlight/components.ts +++ b/packages/starlight/components.ts @@ -4,3 +4,4 @@ export { default as Icon } from './user-components/Icon.astro'; export { default as Tabs } from './user-components/Tabs.astro'; export { default as TabItem } from './user-components/TabItem.astro'; export { default as LinkCard } from './user-components/LinkCard.astro'; +export { Code } from 'astro-expressive-code/components'; diff --git a/packages/starlight/index.ts b/packages/starlight/index.ts index 3b3ba76b..353152e1 100644 --- a/packages/starlight/index.ts +++ b/packages/starlight/index.ts @@ -52,7 +52,7 @@ export default function StarlightIntegration({ const allIntegrations = [...config.integrations, ...integrations]; if (!allIntegrations.find(({ name }) => name === 'astro-expressive-code')) { integrations.push( - ...starlightExpressiveCode({ starlightConfig, astroConfig: config, useTranslations }) + ...starlightExpressiveCode({ starlightConfig, useTranslations }) ); } if (!allIntegrations.find(({ name }) => name === '@astrojs/sitemap')) { diff --git a/packages/starlight/integrations/expressive-code/exports.ts b/packages/starlight/integrations/expressive-code/exports.ts index 9955b8e0..8e9cff47 100644 --- a/packages/starlight/integrations/expressive-code/exports.ts +++ b/packages/starlight/integrations/expressive-code/exports.ts @@ -34,3 +34,5 @@ */ export * from 'astro-expressive-code'; + +export { getStarlightEcConfigPreprocessor } from './index' diff --git a/packages/starlight/integrations/expressive-code/index.ts b/packages/starlight/integrations/expressive-code/index.ts index 13495b45..1fe6fe87 100644 --- a/packages/starlight/integrations/expressive-code/index.ts +++ b/packages/starlight/integrations/expressive-code/index.ts @@ -2,8 +2,9 @@ import { astroExpressiveCode, type AstroExpressiveCodeOptions, addClassName, + type CustomConfigPreprocessors, } from 'astro-expressive-code'; -import type { AstroConfig, AstroIntegration } from 'astro'; +import type { AstroIntegration } from 'astro'; import type { StarlightConfig } from '../../types'; import type { createTranslationSystemFromFs } from '../../utils/translations-fs'; import { pathToLocale } from '../shared/pathToLocale'; @@ -60,56 +61,59 @@ export type StarlightExpressiveCodeOptions = Omit<AstroExpressiveCodeOptions, 't useStarlightUiThemeColors?: boolean | undefined; }; -export const starlightExpressiveCode = ({ - astroConfig, +type StarlightEcIntegrationOptions = { + starlightConfig: StarlightConfig; + useTranslations?: ReturnType<typeof createTranslationSystemFromFs> | undefined; +}; + +/** + * Create an Expressive Code configuration preprocessor based on Starlight config. + * Used internally to set up Expressive Code and by the `<Code>` component. + */ +export function getStarlightEcConfigPreprocessor({ starlightConfig, useTranslations, -}: { - astroConfig: Pick<AstroConfig, 'root' | 'srcDir'>; - starlightConfig: StarlightConfig; - useTranslations: ReturnType<typeof createTranslationSystemFromFs>; -}): AstroIntegration[] => { - const { locales, expressiveCode } = starlightConfig; - if (expressiveCode === false) return []; - const config: StarlightExpressiveCodeOptions = - typeof expressiveCode === 'object' ? expressiveCode : {}; +}: StarlightEcIntegrationOptions): CustomConfigPreprocessors['preprocessAstroIntegrationConfig'] { + return (input): AstroExpressiveCodeOptions => { + const astroConfig = input.astroConfig; + const ecConfig = input.ecConfig as StarlightExpressiveCodeOptions; + const { locales } = starlightConfig; - const { - themes: themesInput, - customizeTheme, - styleOverrides: { textMarkers: textMarkersStyleOverrides, ...otherStyleOverrides } = {}, - useStarlightDarkModeSwitch, - useStarlightUiThemeColors = config.themes === undefined, - plugins = [], - ...rest - } = config; + const { + themes: themesInput, + customizeTheme, + styleOverrides: { textMarkers: textMarkersStyleOverrides, ...otherStyleOverrides } = {}, + useStarlightDarkModeSwitch, + useStarlightUiThemeColors = ecConfig.themes === undefined, + plugins = [], + ...rest + } = ecConfig; - // Handle the `themes` option - const themes = preprocessThemes(themesInput); - if (useStarlightUiThemeColors === true && themes.length < 2) { - console.warn( - `*** Warning: Using the config option "useStarlightUiThemeColors: true" ` + - `with a single theme is not recommended. For better color contrast, ` + - `please provide at least one dark and one light theme.\n` - ); - } + // Handle the `themes` option + const themes = preprocessThemes(themesInput); + if (useStarlightUiThemeColors === true && themes.length < 2) { + console.warn( + `*** Warning: Using the config option "useStarlightUiThemeColors: true" ` + + `with a single theme is not recommended. For better color contrast, ` + + `please provide at least one dark and one light theme.\n` + ); + } - // Add the `not-content` class to all rendered blocks to prevent them from being affected - // by Starlight's default content styles - plugins.push({ - name: 'Starlight Plugin', - hooks: { - postprocessRenderedBlock: ({ renderData }) => { - addClassName(renderData.blockAst, 'not-content'); + // Add the `not-content` class to all rendered blocks to prevent them from being affected + // by Starlight's default content styles + plugins.push({ + name: 'Starlight Plugin', + hooks: { + postprocessRenderedBlock: ({ renderData }) => { + addClassName(renderData.blockAst, 'not-content'); + }, }, - }, - }); + }); - // Add Expressive Code UI translations (if any) for all defined locales - addTranslations(locales, useTranslations); + // Add Expressive Code UI translations (if any) for all defined locales + if (useTranslations) addTranslations(locales, useTranslations); - return [ - astroExpressiveCode({ + return { themes, customizeTheme: (theme) => { if (useStarlightUiThemeColors) { @@ -151,6 +155,36 @@ export const starlightExpressiveCode = ({ getBlockLocale: ({ file }) => pathToLocale(file.path, { starlightConfig, astroConfig }), plugins, ...rest, + }; + }; +} + +export const starlightExpressiveCode = ({ + starlightConfig, + useTranslations, +}: StarlightEcIntegrationOptions): AstroIntegration[] => { + if (starlightConfig.expressiveCode === false) return []; + + const configArgs = + typeof starlightConfig.expressiveCode === 'object' + ? (starlightConfig.expressiveCode as AstroExpressiveCodeOptions) + : {}; + return [ + astroExpressiveCode({ + ...configArgs, + customConfigPreprocessors: { + preprocessAstroIntegrationConfig: getStarlightEcConfigPreprocessor({ + starlightConfig, + useTranslations, + }), + preprocessComponentConfig: ` + import starlightConfig from 'virtual:starlight/user-config' + import { useTranslations } from '@astrojs/starlight/internal' + import { getStarlightEcConfigPreprocessor } from '@astrojs/starlight/expressive-code' + + export default getStarlightEcConfigPreprocessor({ starlightConfig, useTranslations }) + `, + }, }), ]; }; diff --git a/packages/starlight/integrations/expressive-code/theming.ts b/packages/starlight/integrations/expressive-code/theming.ts index e01033b6..39f96b17 100644 --- a/packages/starlight/integrations/expressive-code/theming.ts +++ b/packages/starlight/integrations/expressive-code/theming.ts @@ -1,5 +1,6 @@ -import fs from 'node:fs'; import { ExpressiveCodeTheme, type ThemeObjectOrShikiThemeName } from 'astro-expressive-code'; +import nightOwlDark from './themes/night-owl-dark.jsonc?raw'; +import nightOwlLight from './themes/night-owl-light.jsonc?raw'; export type BundledThemeName = 'starlight-dark' | 'starlight-light'; @@ -20,13 +21,8 @@ export function preprocessThemes( return themes.map((theme) => { // If the current entry is the name of a bundled theme, load it if (theme === 'starlight-dark' || theme === 'starlight-light') { - const bundledThemeFile = - theme === 'starlight-dark' ? 'night-owl-dark.jsonc' : 'night-owl-light.jsonc'; - return customizeBundledTheme( - ExpressiveCodeTheme.fromJSONString( - fs.readFileSync(new URL(`./themes/${bundledThemeFile}`, import.meta.url), 'utf-8') - ) - ); + const bundledTheme = theme === 'starlight-dark' ? nightOwlDark : nightOwlLight; + return customizeBundledTheme(ExpressiveCodeTheme.fromJSONString(bundledTheme)); } // Otherwise, just pass it through return theme; diff --git a/packages/starlight/integrations/shared/pathToLocale.ts b/packages/starlight/integrations/shared/pathToLocale.ts index 34673891..0b2af25b 100644 --- a/packages/starlight/integrations/shared/pathToLocale.ts +++ b/packages/starlight/integrations/shared/pathToLocale.ts @@ -22,11 +22,13 @@ export function pathToLocale( ): string | undefined { const srcDir = new URL(astroConfig.srcDir, astroConfig.root); const docsDir = new URL('content/docs/', srcDir); - const slug = path - // Format path to unix style path. - ?.replace(/\\/g, '/') - // Strip docs path leaving only content collection file ID. - // Example: /Users/houston/repo/src/content/docs/en/guide.md => en/guide.md - .replace(docsDir.pathname, ''); + // Format path to unix style path. + path = path?.replace(/\\/g, '/') + // Ensure that the page path starts with a slash if the docs directory also does, + // which makes stripping the docs path in the next step work on Windows, too. + if (path && !path.startsWith('/') && docsDir.pathname.startsWith('/')) path = '/' + path; + // Strip docs path leaving only content collection file ID. + // Example: /Users/houston/repo/src/content/docs/en/guide.md => en/guide.md + const slug = path?.replace(docsDir.pathname, ''); return slugToLocale(slug, starlightConfig.locales); } diff --git a/packages/starlight/internal.ts b/packages/starlight/internal.ts new file mode 100644 index 00000000..d2d9f83d --- /dev/null +++ b/packages/starlight/internal.ts @@ -0,0 +1 @@ +export { useTranslations } from './utils/translations'; diff --git a/packages/starlight/package.json b/packages/starlight/package.json index 21f74054..938d3d3d 100644 --- a/packages/starlight/package.json +++ b/packages/starlight/package.json @@ -154,6 +154,7 @@ "types": "./components/Search.astro.tsx", "import": "./components/Search.astro" }, + "./internal": "./internal.ts", "./props": "./props.ts", "./schema": "./schema.ts", "./types": "./types.ts", @@ -178,7 +179,7 @@ "@pagefind/default-ui": "^1.0.3", "@types/hast": "^3.0.3", "@types/mdast": "^4.0.3", - "astro-expressive-code": "^0.31.0", + "astro-expressive-code": "^0.32.2", "bcp-47": "^2.1.0", "hast-util-select": "^6.0.2", "hastscript": "^8.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 86d668b7..929795ff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -145,8 +145,8 @@ importers: specifier: ^4.0.3 version: 4.0.3 astro-expressive-code: - specifier: ^0.31.0 - version: 0.31.0(astro@4.2.1) + specifier: ^0.32.2 + version: 0.32.2(astro@4.2.1) bcp-47: specifier: ^2.1.0 version: 2.1.0 @@ -1333,8 +1333,8 @@ packages: requiresBuild: true optional: true - /@expressive-code/core@0.31.0: - resolution: {integrity: sha512-zeCuojWRYeFs0UDOhzpKMzpjI/tJPCQna4jcVp5SJLMn4qNtHXgVmz3AngoMFoFcAlK6meE3wxzy//0d6K4NPw==} + /@expressive-code/core@0.32.2: + resolution: {integrity: sha512-b4/LuslONCqyT48eKlcxsbnIqGw4CSe/aW4Co58UvKrtDMXKtr4erpVx/EE2emszotWt0xtkOjCnS6o171+E4A==} dependencies: '@ctrl/tinycolor': 3.6.1 hast-util-to-html: 8.0.4 @@ -1343,24 +1343,24 @@ packages: postcss-nested: 6.0.1(postcss@8.4.33) dev: false - /@expressive-code/plugin-frames@0.31.0: - resolution: {integrity: sha512-eYWfK3i4w2gSpOGBFNnu05JKSXC90APgUNdam8y5i0Ie2CVAwpxDtEp0NRqugvEKC0aMJe6ZmHN5Hu2WAVJmig==} + /@expressive-code/plugin-frames@0.32.2: + resolution: {integrity: sha512-QKoL5jNCjQnz5GpQMBtZ8Gb1bNXxjarIBkMc8CIugdlvniA442latUKsH1fhacG1UQieSiADctSHjIvVH8Qm9A==} dependencies: - '@expressive-code/core': 0.31.0 + '@expressive-code/core': 0.32.2 hastscript: 7.2.0 dev: false - /@expressive-code/plugin-shiki@0.31.0: - resolution: {integrity: sha512-fU5wPPfV1LGcS+Z1wcEkzI1fzBq9IAdt0DN0ni8sT7E+gpkULda4GA4IFD9iWKCGIhSDsBbG+bjc9hrYoJsDIQ==} + /@expressive-code/plugin-shiki@0.32.2: + resolution: {integrity: sha512-ulNi/NAGMnx8qGBlRTGrH7qHeGV6r15MrkjY/AaTQNImnqory05DF4qOF/dqxe7WywawwsHQ2a4BzsoGYLjicA==} dependencies: - '@expressive-code/core': 0.31.0 + '@expressive-code/core': 0.32.2 shikiji: 0.8.7 dev: false - /@expressive-code/plugin-text-markers@0.31.0: - resolution: {integrity: sha512-32o3pPMBq6bVUfRsAfFyqNpHbD1Z3iftoX9yt95F5zakLMsmHzZL4f0jyNr8XpXe7qcTnl0kIijBkUpvS6Pxfg==} + /@expressive-code/plugin-text-markers@0.32.2: + resolution: {integrity: sha512-1fAkWkQ7qcb6DDqV3ILB1uMi7yvSIu6AHFW+bSzNcgXBl/KCudoUtmZ/YRBnNKbUqH8WSYUA41Yr/SeFwEGmbQ==} dependencies: - '@expressive-code/core': 0.31.0 + '@expressive-code/core': 0.32.2 hastscript: 7.2.0 unist-util-visit-parents: 5.1.3 dev: false @@ -2096,13 +2096,14 @@ packages: hasBin: true dev: false - /astro-expressive-code@0.31.0(astro@4.2.1): - resolution: {integrity: sha512-o6eFrRSYLnlM/2FKkO3MgkbmVxT8N6DJcKvbRf1wbUcRXpz7s1KfugbdsaGw3ABEWUBuQIBsRppcGGw2L816Vg==} + /astro-expressive-code@0.32.2(astro@4.2.1): + resolution: {integrity: sha512-uJbgSCl9F9NGjdfTmBHci5Ws0/zMUNk9dWfOl6rvYaOL6NZha+NNjnmB3Aza7GnxP+NvQt3RV8M2vpcZnaudSw==} peerDependencies: astro: ^3.3.0 || ^4.0.0-beta dependencies: astro: 4.2.1(@types/node@18.16.19) - remark-expressive-code: 0.31.0 + hast-util-to-html: 8.0.4 + remark-expressive-code: 0.32.2 dev: false /astro@4.2.1(@types/node@18.16.19): @@ -3178,13 +3179,13 @@ packages: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} - /expressive-code@0.31.0: - resolution: {integrity: sha512-rxKGYS8iRwNUbRNfyCyoe3XQvBLTtGdXbNKM+ODDWCn4VL2DVT1gD1M2N2Alg8HQHIWZJsZIMsYbziO0MRjPlw==} + /expressive-code@0.32.2: + resolution: {integrity: sha512-fUwnj9O6/5HKSniD/nXLEGKmcwqL+ipWyZAFjxp9weI9AkTiya3bVAo9gVUquM4jXRHSs8pgsRMQgRtKItlriA==} dependencies: - '@expressive-code/core': 0.31.0 - '@expressive-code/plugin-frames': 0.31.0 - '@expressive-code/plugin-shiki': 0.31.0 - '@expressive-code/plugin-text-markers': 0.31.0 + '@expressive-code/core': 0.32.2 + '@expressive-code/plugin-frames': 0.32.2 + '@expressive-code/plugin-shiki': 0.32.2 + '@expressive-code/plugin-text-markers': 0.32.2 dev: false /extend-shallow@2.0.1: @@ -5940,10 +5941,10 @@ packages: - supports-color dev: false - /remark-expressive-code@0.31.0: - resolution: {integrity: sha512-ZnKXo9lB0kBUHZIlw2NdqMMgXriVVajEhtQfJ+MWeibMpyM1kuOa28jefNfNFd3FAoNPrc/A3M0fDRkYvWw9Gw==} + /remark-expressive-code@0.32.2: + resolution: {integrity: sha512-UnCUlu+Q2FO8glmtlEnjIN6V8IKfbGlYLSTDokbd9VCZHkI0+FeHcCc/5WpzGY2CSSPL02AC5rHUfvAZV7tZzQ==} dependencies: - expressive-code: 0.31.0 + expressive-code: 0.32.2 hast-util-to-html: 8.0.4 unist-util-visit: 4.1.2 dev: false |