summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHippo2024-01-26 23:13:00 +0100
committerGitHub2024-01-26 23:13:00 +0100
commitce05dfb4b1e9b90fad057d5d4328e4445f986b3b (patch)
tree30a5ebd2c8b1a2d0d147a74c419e68474caedf70
parent614bfdd3fffeeb71ccec1fb35c8d0765e02f7ebd (diff)
downloadIT.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.md5
-rw-r--r--docs/src/content/docs/guides/components.mdx46
-rw-r--r--package.json2
-rw-r--r--packages/starlight/components.ts1
-rw-r--r--packages/starlight/index.ts2
-rw-r--r--packages/starlight/integrations/expressive-code/exports.ts2
-rw-r--r--packages/starlight/integrations/expressive-code/index.ts120
-rw-r--r--packages/starlight/integrations/expressive-code/theming.ts12
-rw-r--r--packages/starlight/integrations/shared/pathToLocale.ts14
-rw-r--r--packages/starlight/internal.ts1
-rw-r--r--packages/starlight/package.json3
-rw-r--r--pnpm-lock.yaml51
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