summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHiDeoo2025-07-29 11:47:07 +0200
committerGitHub2025-07-29 11:47:07 +0200
commit10f6fe22a981247293ee4de106736f1a6ae24b6a (patch)
treea8bcc7f0a50cea76005869eff60d0c6116b76a3d
parent4b5a2fa6656d973bff7091af5fa948a5c18eae12 (diff)
downloadIT.starlight-10f6fe22a981247293ee4de106736f1a6ae24b6a.tar.gz
IT.starlight-10f6fe22a981247293ee4de106736f1a6ae24b6a.tar.bz2
IT.starlight-10f6fe22a981247293ee4de106736f1a6ae24b6a.zip
Prevent Cloudflare build issues due to Node.js builtins usage (#3341)
Co-authored-by: delucis <357379+delucis@users.noreply.github.com> Co-authored-by: Chris Swithinbank <swithinbank@gmail.com>
-rw-r--r--.changeset/olive-weeks-rest.md5
-rw-r--r--packages/starlight/__e2e__/fixtures/no-node-builtins/astro.config.mjs16
-rw-r--r--packages/starlight/__e2e__/fixtures/no-node-builtins/package.json9
-rw-r--r--packages/starlight/__e2e__/fixtures/no-node-builtins/src/content.config.ts7
-rw-r--r--packages/starlight/__e2e__/fixtures/no-node-builtins/src/content/docs/index.md5
-rw-r--r--packages/starlight/__e2e__/fixtures/no-node-builtins/src/noNodeModule.ts36
-rw-r--r--packages/starlight/__e2e__/no-node-builtins.test.ts10
-rw-r--r--packages/starlight/__tests__/remark-rehype/anchor-links.test.ts6
-rw-r--r--packages/starlight/__tests__/remark-rehype/asides.test.ts6
-rw-r--r--packages/starlight/index.ts4
-rw-r--r--packages/starlight/integrations/asides-error.ts9
-rw-r--r--packages/starlight/integrations/asides.ts10
-rw-r--r--packages/starlight/integrations/expressive-code/index.ts132
-rw-r--r--packages/starlight/integrations/expressive-code/preprocessor.ts121
-rw-r--r--packages/starlight/integrations/remark-rehype-utils.ts2
-rw-r--r--packages/starlight/integrations/shared/absolutePathToLang.ts12
-rw-r--r--packages/starlight/integrations/virtual-user-config.ts2
-rw-r--r--packages/starlight/internal.ts2
-rw-r--r--packages/starlight/user-components/Aside.astro2
-rw-r--r--packages/starlight/utils/collection-fs.ts21
-rw-r--r--packages/starlight/utils/collection.ts11
-rw-r--r--packages/starlight/utils/plugins.ts6
-rw-r--r--pnpm-lock.yaml27
23 files changed, 294 insertions, 167 deletions
diff --git a/.changeset/olive-weeks-rest.md b/.changeset/olive-weeks-rest.md
new file mode 100644
index 00000000..1b8dbdc2
--- /dev/null
+++ b/.changeset/olive-weeks-rest.md
@@ -0,0 +1,5 @@
+---
+'@astrojs/starlight': patch
+---
+
+Prevents potential build issues with the Astro Cloudflare adapter due to the dependency on Node.js builtins.
diff --git a/packages/starlight/__e2e__/fixtures/no-node-builtins/astro.config.mjs b/packages/starlight/__e2e__/fixtures/no-node-builtins/astro.config.mjs
new file mode 100644
index 00000000..2d6eeed0
--- /dev/null
+++ b/packages/starlight/__e2e__/fixtures/no-node-builtins/astro.config.mjs
@@ -0,0 +1,16 @@
+// @ts-check
+import starlight from '@astrojs/starlight';
+import { defineConfig } from 'astro/config';
+import { preventNodeBuiltinDependencyPlugin } from './src/noNodeModule';
+
+export default defineConfig({
+ integrations: [
+ starlight({
+ title: 'No Node Builtins',
+ pagefind: false,
+ }),
+ ],
+ vite: {
+ plugins: [preventNodeBuiltinDependencyPlugin()],
+ },
+});
diff --git a/packages/starlight/__e2e__/fixtures/no-node-builtins/package.json b/packages/starlight/__e2e__/fixtures/no-node-builtins/package.json
new file mode 100644
index 00000000..5555ae1f
--- /dev/null
+++ b/packages/starlight/__e2e__/fixtures/no-node-builtins/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "@e2e/no-node-builtins",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "@astrojs/starlight": "workspace:*",
+ "astro": "^5.6.1"
+ }
+}
diff --git a/packages/starlight/__e2e__/fixtures/no-node-builtins/src/content.config.ts b/packages/starlight/__e2e__/fixtures/no-node-builtins/src/content.config.ts
new file mode 100644
index 00000000..d9ee8c9d
--- /dev/null
+++ b/packages/starlight/__e2e__/fixtures/no-node-builtins/src/content.config.ts
@@ -0,0 +1,7 @@
+import { defineCollection } from 'astro:content';
+import { docsLoader } from '@astrojs/starlight/loaders';
+import { docsSchema } from '@astrojs/starlight/schema';
+
+export const collections = {
+ docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
+};
diff --git a/packages/starlight/__e2e__/fixtures/no-node-builtins/src/content/docs/index.md b/packages/starlight/__e2e__/fixtures/no-node-builtins/src/content/docs/index.md
new file mode 100644
index 00000000..fa27b816
--- /dev/null
+++ b/packages/starlight/__e2e__/fixtures/no-node-builtins/src/content/docs/index.md
@@ -0,0 +1,5 @@
+---
+title: Home Page
+---
+
+Home page content
diff --git a/packages/starlight/__e2e__/fixtures/no-node-builtins/src/noNodeModule.ts b/packages/starlight/__e2e__/fixtures/no-node-builtins/src/noNodeModule.ts
new file mode 100644
index 00000000..1089e194
--- /dev/null
+++ b/packages/starlight/__e2e__/fixtures/no-node-builtins/src/noNodeModule.ts
@@ -0,0 +1,36 @@
+import type { ViteUserConfig } from 'astro';
+import { builtinModules } from 'node:module';
+
+const nodeModules = builtinModules.map((name) => [name, `node:${name}`]).flat();
+
+/**
+ * A Vite plugin used to verify that the final bundle does not have a hard dependency on Node.js
+ * builtins due to Starlight.
+ * This is to ensure that Starlight can run on platforms like Cloudflare.
+ *
+ * @see https://github.com/withastro/astro/blob/8491aa56e8685677af8458ff1c5a80d6461413f8/packages/astro/test/test-plugins.js
+ */
+export function preventNodeBuiltinDependencyPlugin(): NonNullable<
+ ViteUserConfig['plugins']
+>[number] {
+ return {
+ name: 'verify-no-node-stuff',
+ generateBundle() {
+ nodeModules.forEach((name) => {
+ const importers = this.getModuleInfo(name)?.importers || [];
+ const starlightPath = new URL('../../../../', import.meta.url).pathname;
+ const nodeStarlightImport = importers.find((importer) =>
+ importer.startsWith(starlightPath)
+ );
+
+ if (nodeStarlightImport) {
+ throw new Error(
+ 'A node builtin dependency imported by Starlight was found in the production bundle:\n\n' +
+ ` - Node builtin: '${name}'\n` +
+ ` - Importer: ${nodeStarlightImport}\n`
+ );
+ }
+ });
+ },
+ };
+}
diff --git a/packages/starlight/__e2e__/no-node-builtins.test.ts b/packages/starlight/__e2e__/no-node-builtins.test.ts
new file mode 100644
index 00000000..00ade3b8
--- /dev/null
+++ b/packages/starlight/__e2e__/no-node-builtins.test.ts
@@ -0,0 +1,10 @@
+import { expect, testFactory } from './test-utils';
+
+const test = testFactory('./fixtures/no-node-builtins/');
+
+test('builds without any dependencies on Node.js builtins', async ({ page, getProdServer }) => {
+ const starlight = await getProdServer();
+ await starlight.goto('/');
+
+ await expect(page.getByText('Home page content')).toBeVisible();
+});
diff --git a/packages/starlight/__tests__/remark-rehype/anchor-links.test.ts b/packages/starlight/__tests__/remark-rehype/anchor-links.test.ts
index af5e92de..0db4e88d 100644
--- a/packages/starlight/__tests__/remark-rehype/anchor-links.test.ts
+++ b/packages/starlight/__tests__/remark-rehype/anchor-links.test.ts
@@ -4,6 +4,7 @@ import { createTranslationSystemFromFs } from '../../utils/translations-fs';
import { StarlightConfigSchema, type StarlightUserConfig } from '../../utils/user-config';
import { absolutePathToLang as getAbsolutePathFromLang } from '../../integrations/shared/absolutePathToLang';
import { starlightAutolinkHeadings } from '../../integrations/heading-links';
+import { getCollectionPosixPath } from '../../utils/collection-fs';
const starlightConfig = StarlightConfigSchema.parse({
title: 'Anchor Links Tests',
@@ -23,7 +24,10 @@ const useTranslations = createTranslationSystemFromFs(
);
function absolutePathToLang(path: string) {
- return getAbsolutePathFromLang(path, { astroConfig, starlightConfig });
+ return getAbsolutePathFromLang(path, {
+ docsPath: getCollectionPosixPath('docs', astroConfig.srcDir),
+ starlightConfig,
+ });
}
const processor = await createMarkdownProcessor({
diff --git a/packages/starlight/__tests__/remark-rehype/asides.test.ts b/packages/starlight/__tests__/remark-rehype/asides.test.ts
index 04ab62cf..257c8a10 100644
--- a/packages/starlight/__tests__/remark-rehype/asides.test.ts
+++ b/packages/starlight/__tests__/remark-rehype/asides.test.ts
@@ -7,6 +7,7 @@ import { createTranslationSystemFromFs } from '../../utils/translations-fs';
import { StarlightConfigSchema, type StarlightUserConfig } from '../../utils/user-config';
import { BuiltInDefaultLocale } from '../../utils/i18n';
import { absolutePathToLang as getAbsolutePathFromLang } from '../../integrations/shared/absolutePathToLang';
+import { getCollectionPosixPath } from '../../utils/collection-fs';
const starlightConfig = StarlightConfigSchema.parse({
title: 'Asides Tests',
@@ -26,7 +27,10 @@ const useTranslations = createTranslationSystemFromFs(
);
function absolutePathToLang(path: string) {
- return getAbsolutePathFromLang(path, { astroConfig, starlightConfig });
+ return getAbsolutePathFromLang(path, {
+ docsPath: getCollectionPosixPath('docs', astroConfig.srcDir),
+ starlightConfig,
+ });
}
const processor = await createMarkdownProcessor({
diff --git a/packages/starlight/index.ts b/packages/starlight/index.ts
index ea6fd8e1..87a5c794 100644
--- a/packages/starlight/index.ts
+++ b/packages/starlight/index.ts
@@ -92,7 +92,9 @@ export default function StarlightIntegration(
// config or by a plugin.
const allIntegrations = [...config.integrations, ...integrations];
if (!allIntegrations.find(({ name }) => name === 'astro-expressive-code')) {
- integrations.push(...starlightExpressiveCode({ starlightConfig, useTranslations }));
+ integrations.push(
+ ...starlightExpressiveCode({ astroConfig: config, starlightConfig, useTranslations })
+ );
}
if (!allIntegrations.find(({ name }) => name === '@astrojs/sitemap')) {
integrations.push(starlightSitemap(starlightConfig));
diff --git a/packages/starlight/integrations/asides-error.ts b/packages/starlight/integrations/asides-error.ts
new file mode 100644
index 00000000..381b576c
--- /dev/null
+++ b/packages/starlight/integrations/asides-error.ts
@@ -0,0 +1,9 @@
+import { AstroError } from 'astro/errors';
+
+export function throwInvalidAsideIconError(icon: string) {
+ throw new AstroError(
+ 'Invalid aside icon',
+ `An aside custom icon must be set to the name of one of Starlight\’s built-in icons, but received \`${icon}\`.\n\n` +
+ 'See https://starlight.astro.build/reference/icons/#all-icons for a list of available icons.'
+ );
+}
diff --git a/packages/starlight/integrations/asides.ts b/packages/starlight/integrations/asides.ts
index 42e6098d..62235e79 100644
--- a/packages/starlight/integrations/asides.ts
+++ b/packages/starlight/integrations/asides.ts
@@ -19,7 +19,7 @@ import { getRemarkRehypeDocsCollectionPath, shouldTransformFile } from './remark
import { Icons } from '../components/Icons';
import { fromHtml } from 'hast-util-from-html';
import type { Element } from 'hast';
-import { AstroError } from 'astro/errors';
+import { throwInvalidAsideIconError } from './asides-error';
interface AsidesOptions {
starlightConfig: Pick<StarlightConfig, 'defaultLocale' | 'locales'>;
@@ -253,14 +253,6 @@ function remarkAsides(options: AsidesOptions): Plugin<[], Root> {
type RemarkPlugins = NonNullable<NonNullable<AstroUserConfig['markdown']>['remarkPlugins']>;
-export function throwInvalidAsideIconError(icon: string) {
- throw new AstroError(
- 'Invalid aside icon',
- `An aside custom icon must be set to the name of one of Starlight\’s built-in icons, but received \`${icon}\`.\n\n` +
- 'See https://starlight.astro.build/reference/icons/#all-icons for a list of available icons.'
- );
-}
-
export function starlightAsides(options: AsidesOptions): RemarkPlugins {
return [remarkDirective, remarkAsides(options)];
}
diff --git a/packages/starlight/integrations/expressive-code/index.ts b/packages/starlight/integrations/expressive-code/index.ts
index 431a86d2..b788d5b7 100644
--- a/packages/starlight/integrations/expressive-code/index.ts
+++ b/packages/starlight/integrations/expressive-code/index.ts
@@ -1,20 +1,9 @@
-import {
- astroExpressiveCode,
- type AstroExpressiveCodeOptions,
- type CustomConfigPreprocessors,
-} from 'astro-expressive-code';
-import { addClassName } from 'astro-expressive-code/hast';
-import type { AstroIntegration } from 'astro';
+import { astroExpressiveCode, type AstroExpressiveCodeOptions } from 'astro-expressive-code';
+import type { AstroConfig, AstroIntegration } from 'astro';
import type { HookParameters, StarlightConfig } from '../../types';
-import { absolutePathToLang } from '../shared/absolutePathToLang';
-import { slugToLocale } from '../shared/slugToLocale';
-import { localeToLang } from '../shared/localeToLang';
-import {
- applyStarlightUiThemeColors,
- preprocessThemes,
- type ThemeObjectOrBundledThemeName,
-} from './theming';
-import { addTranslations } from './translations';
+import { getStarlightEcConfigPreprocessor } from './preprocessor';
+import { type ThemeObjectOrBundledThemeName } from './theming';
+import { getCollectionPosixPath } from '../../utils/collection-fs';
export type StarlightExpressiveCodeOptions = Omit<AstroExpressiveCodeOptions, 'themes'> & {
/**
@@ -63,114 +52,13 @@ export type StarlightExpressiveCodeOptions = Omit<AstroExpressiveCodeOptions, 't
};
type StarlightEcIntegrationOptions = {
+ astroConfig: AstroConfig;
starlightConfig: StarlightConfig;
useTranslations: HookParameters<'config:setup'>['useTranslations'];
};
-/**
- * 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,
-}: StarlightEcIntegrationOptions): CustomConfigPreprocessors['preprocessAstroIntegrationConfig'] {
- return (input): AstroExpressiveCodeOptions => {
- const astroConfig = input.astroConfig;
- const ecConfig = input.ecConfig as StarlightExpressiveCodeOptions;
-
- const {
- themes: themesInput,
- cascadeLayer,
- 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`
- );
- }
-
- // 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 for all defined locales
- addTranslations(starlightConfig, useTranslations);
-
- return {
- themes,
- customizeTheme: (theme) => {
- if (useStarlightUiThemeColors) {
- applyStarlightUiThemeColors(theme);
- }
- if (customizeTheme) {
- theme = customizeTheme(theme) ?? theme;
- }
- return theme;
- },
- defaultLocale: starlightConfig.defaultLocale?.lang ?? starlightConfig.defaultLocale?.locale,
- themeCssSelector: (theme, { styleVariants }) => {
- // If one dark and one light theme are available, and the user has not disabled it,
- // generate theme CSS selectors compatible with Starlight's dark mode switch
- if (useStarlightDarkModeSwitch !== false && styleVariants.length >= 2) {
- const baseTheme = styleVariants[0]?.theme;
- const altTheme = styleVariants.find((v) => v.theme.type !== baseTheme?.type)?.theme;
- if (theme === baseTheme || theme === altTheme) return `[data-theme='${theme.type}']`;
- }
- // Return the default selector
- return `[data-theme='${theme.name}']`;
- },
- cascadeLayer: cascadeLayer ?? 'starlight.components',
- styleOverrides: {
- borderRadius: '0px',
- borderWidth: '1px',
- codePaddingBlock: '0.75rem',
- codePaddingInline: '1rem',
- codeFontFamily: 'var(--__sl-font-mono)',
- codeFontSize: 'var(--sl-text-code)',
- codeLineHeight: 'var(--sl-line-height)',
- uiFontFamily: 'var(--__sl-font)',
- textMarkers: {
- lineDiffIndicatorMarginLeft: '0.25rem',
- defaultChroma: '45',
- backgroundOpacity: '60%',
- ...textMarkersStyleOverrides,
- },
- ...otherStyleOverrides,
- },
- getBlockLocale: ({ file }) => {
- if (file.url) {
- const locale = slugToLocale(file.url.pathname.slice(1), starlightConfig);
- return localeToLang(starlightConfig, locale);
- }
- // Note that EC cannot use the `absolutePathToLang` helper passed down to plugins as this callback
- // is also called in the context of the `<Code>` component.
- return absolutePathToLang(file.path, { starlightConfig, astroConfig });
- },
- plugins,
- ...rest,
- };
- };
-}
-
export const starlightExpressiveCode = ({
+ astroConfig,
starlightConfig,
useTranslations,
}: StarlightEcIntegrationOptions): AstroIntegration[] => {
@@ -213,11 +101,15 @@ export const starlightExpressiveCode = ({
typeof starlightConfig.expressiveCode === 'object'
? (starlightConfig.expressiveCode as AstroExpressiveCodeOptions)
: {};
+
+ let docsPath = getCollectionPosixPath('docs', astroConfig.srcDir);
+
return [
astroExpressiveCode({
...configArgs,
customConfigPreprocessors: {
preprocessAstroIntegrationConfig: getStarlightEcConfigPreprocessor({
+ docsPath,
starlightConfig,
useTranslations,
}),
@@ -225,7 +117,7 @@ export const starlightExpressiveCode = ({
import starlightConfig from 'virtual:starlight/user-config'
import { useTranslations, getStarlightEcConfigPreprocessor } from '@astrojs/starlight/internal'
- export default getStarlightEcConfigPreprocessor({ starlightConfig, useTranslations })
+ export default getStarlightEcConfigPreprocessor({ docsPath: ${JSON.stringify(docsPath)}, starlightConfig, useTranslations })
`,
},
}),
diff --git a/packages/starlight/integrations/expressive-code/preprocessor.ts b/packages/starlight/integrations/expressive-code/preprocessor.ts
new file mode 100644
index 00000000..70c9ba93
--- /dev/null
+++ b/packages/starlight/integrations/expressive-code/preprocessor.ts
@@ -0,0 +1,121 @@
+import {
+ type AstroExpressiveCodeOptions,
+ type CustomConfigPreprocessors,
+} from 'astro-expressive-code';
+import { addClassName } from 'astro-expressive-code/hast';
+import type { StarlightExpressiveCodeOptions } from './index';
+import type { HookParameters, StarlightConfig } from '../../types';
+import { applyStarlightUiThemeColors, preprocessThemes } from './theming';
+import { addTranslations } from './translations';
+import { slugToLocale } from '../shared/slugToLocale';
+import { localeToLang } from '../shared/localeToLang';
+import { absolutePathToLang } from '../shared/absolutePathToLang';
+
+type StarlightEcConfigPreprocessorOptions = {
+ docsPath: string;
+ starlightConfig: StarlightConfig;
+ useTranslations: HookParameters<'config:setup'>['useTranslations'];
+};
+
+/**
+ * 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({
+ docsPath,
+ starlightConfig,
+ useTranslations,
+}: StarlightEcConfigPreprocessorOptions): CustomConfigPreprocessors['preprocessAstroIntegrationConfig'] {
+ return (input): AstroExpressiveCodeOptions => {
+ const ecConfig = input.ecConfig as StarlightExpressiveCodeOptions;
+
+ const {
+ themes: themesInput,
+ cascadeLayer,
+ 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`
+ );
+ }
+
+ // 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 for all defined locales
+ addTranslations(starlightConfig, useTranslations);
+
+ return {
+ themes,
+ customizeTheme: (theme) => {
+ if (useStarlightUiThemeColors) {
+ applyStarlightUiThemeColors(theme);
+ }
+ if (customizeTheme) {
+ theme = customizeTheme(theme) ?? theme;
+ }
+ return theme;
+ },
+ defaultLocale: starlightConfig.defaultLocale?.lang ?? starlightConfig.defaultLocale?.locale,
+ themeCssSelector: (theme, { styleVariants }) => {
+ // If one dark and one light theme are available, and the user has not disabled it,
+ // generate theme CSS selectors compatible with Starlight's dark mode switch
+ if (useStarlightDarkModeSwitch !== false && styleVariants.length >= 2) {
+ const baseTheme = styleVariants[0]?.theme;
+ const altTheme = styleVariants.find((v) => v.theme.type !== baseTheme?.type)?.theme;
+ if (theme === baseTheme || theme === altTheme) return `[data-theme='${theme.type}']`;
+ }
+ // Return the default selector
+ return `[data-theme='${theme.name}']`;
+ },
+ cascadeLayer: cascadeLayer ?? 'starlight.components',
+ styleOverrides: {
+ borderRadius: '0px',
+ borderWidth: '1px',
+ codePaddingBlock: '0.75rem',
+ codePaddingInline: '1rem',
+ codeFontFamily: 'var(--__sl-font-mono)',
+ codeFontSize: 'var(--sl-text-code)',
+ codeLineHeight: 'var(--sl-line-height)',
+ uiFontFamily: 'var(--__sl-font)',
+ textMarkers: {
+ lineDiffIndicatorMarginLeft: '0.25rem',
+ defaultChroma: '45',
+ backgroundOpacity: '60%',
+ ...textMarkersStyleOverrides,
+ },
+ ...otherStyleOverrides,
+ },
+ getBlockLocale: ({ file }) => {
+ if (file.url) {
+ const locale = slugToLocale(file.url.pathname.slice(1), starlightConfig);
+ return localeToLang(starlightConfig, locale);
+ }
+ // Note that EC cannot use the `absolutePathToLang` helper passed down to plugins as this callback
+ // is also called in the context of the `<Code>` component.
+ return absolutePathToLang(file.path, { docsPath, starlightConfig });
+ },
+ plugins,
+ ...rest,
+ };
+ };
+}
diff --git a/packages/starlight/integrations/remark-rehype-utils.ts b/packages/starlight/integrations/remark-rehype-utils.ts
index 97ff09b0..ca63d22b 100644
--- a/packages/starlight/integrations/remark-rehype-utils.ts
+++ b/packages/starlight/integrations/remark-rehype-utils.ts
@@ -1,6 +1,6 @@
import type { AstroConfig } from 'astro';
import type { VFile } from 'vfile';
-import { resolveCollectionPath } from '../utils/collection';
+import { resolveCollectionPath } from '../utils/collection-fs';
/**
* Returns the path to the Starlight docs collection ready to be used in remark/rehype plugins,
diff --git a/packages/starlight/integrations/shared/absolutePathToLang.ts b/packages/starlight/integrations/shared/absolutePathToLang.ts
index eb40c775..74a58da4 100644
--- a/packages/starlight/integrations/shared/absolutePathToLang.ts
+++ b/packages/starlight/integrations/shared/absolutePathToLang.ts
@@ -1,24 +1,20 @@
-import { pathToFileURL } from 'node:url';
-import type { AstroConfig } from 'astro';
import type { StarlightConfig } from '../../types';
import { localeToLang } from './localeToLang';
-import { getCollectionPath } from '../../utils/collection';
import { slugToLocale } from './slugToLocale';
/** Get current language from an absolute file path. */
export function absolutePathToLang(
path: string,
{
+ docsPath,
starlightConfig,
- astroConfig,
}: {
+ docsPath: string;
starlightConfig: Pick<StarlightConfig, 'defaultLocale' | 'locales'>;
- astroConfig: { root: AstroConfig['root']; srcDir: AstroConfig['srcDir'] };
}
): string {
- const docsPath = getCollectionPath('docs', astroConfig.srcDir);
- // Format path to URL-encoded path.
- path = pathToFileURL(path).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('/') && docsPath.startsWith('/')) path = '/' + path;
diff --git a/packages/starlight/integrations/virtual-user-config.ts b/packages/starlight/integrations/virtual-user-config.ts
index 47685cbf..893af513 100644
--- a/packages/starlight/integrations/virtual-user-config.ts
+++ b/packages/starlight/integrations/virtual-user-config.ts
@@ -2,7 +2,7 @@ import type { AstroConfig, HookParameters, ViteUserConfig } from 'astro';
import { existsSync } from 'node:fs';
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
-import { resolveCollectionPath } from '../utils/collection';
+import { resolveCollectionPath } from '../utils/collection-fs';
import type { StarlightConfig } from '../utils/user-config';
import { getAllNewestCommitDate } from '../utils/git';
import type { PluginTranslations } from '../utils/plugins';
diff --git a/packages/starlight/internal.ts b/packages/starlight/internal.ts
index 61583fc8..f7de032d 100644
--- a/packages/starlight/internal.ts
+++ b/packages/starlight/internal.ts
@@ -3,4 +3,4 @@
*/
export { useTranslations } from './utils/translations';
-export { getStarlightEcConfigPreprocessor } from './integrations/expressive-code';
+export { getStarlightEcConfigPreprocessor } from './integrations/expressive-code/preprocessor';
diff --git a/packages/starlight/user-components/Aside.astro b/packages/starlight/user-components/Aside.astro
index 1a1ab00d..1c01bca6 100644
--- a/packages/starlight/user-components/Aside.astro
+++ b/packages/starlight/user-components/Aside.astro
@@ -2,7 +2,7 @@
import { AstroError } from 'astro/errors';
import Icon from './Icon.astro';
import { Icons, type StarlightIcon } from '../components/Icons';
-import { throwInvalidAsideIconError } from '../integrations/asides';
+import { throwInvalidAsideIconError } from '../integrations/asides-error';
const asideVariants = ['note', 'tip', 'caution', 'danger'] as const;
const icons = { note: 'information', tip: 'rocket', caution: 'warning', danger: 'error' } as const;
diff --git a/packages/starlight/utils/collection-fs.ts b/packages/starlight/utils/collection-fs.ts
new file mode 100644
index 00000000..f641039a
--- /dev/null
+++ b/packages/starlight/utils/collection-fs.ts
@@ -0,0 +1,21 @@
+import { resolve } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { getCollectionUrl, type StarlightCollection } from './collection';
+
+/**
+ * @see {@link file://./collection.ts} for more context about this file.
+ *
+ * Below are various functions to easily get paths to collections used in Starlight that rely on
+ * Node.js builtins. They exist in a separate file from {@link file://./collection.ts} to avoid
+ * potentially importing Node.js builtins in the final bundle.
+ */
+
+export function resolveCollectionPath(collection: StarlightCollection, srcDir: URL) {
+ return resolve(fileURLToPath(srcDir), `content/${collection}`);
+}
+
+export function getCollectionPosixPath(collection: StarlightCollection, srcDir: URL) {
+ // TODO: when Astro minimum Node.js version is >= 20.13.0, refactor to use the `fileURLToPath`
+ // second optional argument to enforce POSIX paths by setting `windows: false`.
+ return fileURLToPath(getCollectionUrl(collection, srcDir)).replace(/\\/g, '/');
+}
diff --git a/packages/starlight/utils/collection.ts b/packages/starlight/utils/collection.ts
index 948c8209..086815ae 100644
--- a/packages/starlight/utils/collection.ts
+++ b/packages/starlight/utils/collection.ts
@@ -1,6 +1,3 @@
-import { resolve } from 'node:path';
-import { fileURLToPath } from 'node:url';
-
const collectionNames = ['docs', 'i18n'] as const;
export type StarlightCollection = (typeof collectionNames)[number];
@@ -22,12 +19,8 @@ export type StarlightCollection = (typeof collectionNames)[number];
* these helper functions should be updated to reflect that in one place.
*/
-export function getCollectionPath(collection: StarlightCollection, srcDir: URL) {
- return new URL(`content/${collection}/`, srcDir).pathname;
-}
-
-export function resolveCollectionPath(collection: StarlightCollection, srcDir: URL) {
- return resolve(fileURLToPath(srcDir), `content/${collection}`);
+export function getCollectionUrl(collection: StarlightCollection, srcDir: URL) {
+ return new URL(`content/${collection}/`, srcDir);
}
export function getCollectionPathFromRoot(
diff --git a/packages/starlight/utils/plugins.ts b/packages/starlight/utils/plugins.ts
index a4912cb1..40761f80 100644
--- a/packages/starlight/utils/plugins.ts
+++ b/packages/starlight/utils/plugins.ts
@@ -10,6 +10,7 @@ import {
import type { UserI18nSchema } from './translations';
import { createTranslationSystemFromFs } from './translations-fs';
import { absolutePathToLang as getAbsolutePathFromLang } from '../integrations/shared/absolutePathToLang';
+import { getCollectionPosixPath } from './collection-fs';
/**
* Runs Starlight plugins in the order that they are configured after validating the user-provided
@@ -65,7 +66,10 @@ export async function runPlugins(
);
function absolutePathToLang(path: string) {
- return getAbsolutePathFromLang(path, { astroConfig: context.config, starlightConfig });
+ return getAbsolutePathFromLang(path, {
+ docsPath: getCollectionPosixPath('docs', context.config.srcDir),
+ starlightConfig,
+ });
}
// A list of Astro integrations added by the various plugins.
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f703fe18..90f2b2a8 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -301,6 +301,15 @@ importers:
specifier: ^5.6.1
version: 5.6.2(@types/node@18.16.19)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.36.0)(tsx@4.15.2)(typescript@5.6.3)(yaml@2.6.1)
+ packages/starlight/__e2e__/fixtures/no-node-builtins:
+ dependencies:
+ '@astrojs/starlight':
+ specifier: workspace:*
+ version: link:../../..
+ astro:
+ specifier: ^5.6.1
+ version: 5.6.2(@types/node@18.16.19)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.36.0)(tsx@4.15.2)(typescript@5.6.3)(yaml@2.6.1)
+
packages/starlight/__e2e__/fixtures/ssr:
dependencies:
'@astrojs/node':
@@ -593,9 +602,6 @@ packages:
'@emmetio/stream-reader@2.2.0':
resolution: {integrity: sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw==}
- '@emnapi/runtime@1.1.1':
- resolution: {integrity: sha512-3bfqkzuR1KLx57nZfjr2NLnFOobvyS0aTszaEGCGqmYMVDRaGvgIZbjGSV/MHSSmLgQ/b9JFHQ5xm5WRZYd+XQ==}
-
'@emnapi/runtime@1.4.3':
resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==}
@@ -4431,11 +4437,6 @@ snapshots:
'@emmetio/stream-reader@2.2.0': {}
- '@emnapi/runtime@1.1.1':
- dependencies:
- tslib: 2.5.0
- optional: true
-
'@emnapi/runtime@1.4.3':
dependencies:
tslib: 2.5.0
@@ -4743,7 +4744,7 @@ snapshots:
'@img/sharp-wasm32@0.33.3':
dependencies:
- '@emnapi/runtime': 1.1.1
+ '@emnapi/runtime': 1.4.3
optional: true
'@img/sharp-wasm32@0.34.2':
@@ -5384,7 +5385,7 @@ snapshots:
picomatch: 4.0.2
prompts: 2.4.2
rehype: 13.0.2
- semver: 7.7.1
+ semver: 7.7.2
shiki: 3.2.2
tinyexec: 0.3.2
tinyglobby: 0.2.12
@@ -7483,8 +7484,8 @@ snapshots:
sharp@0.33.3:
dependencies:
color: 4.2.3
- detect-libc: 2.0.3
- semver: 7.7.1
+ detect-libc: 2.0.4
+ semver: 7.7.2
optionalDependencies:
'@img/sharp-darwin-arm64': 0.33.3
'@img/sharp-darwin-x64': 0.33.3
@@ -7758,7 +7759,7 @@ snapshots:
typescript-auto-import-cache@0.3.5:
dependencies:
- semver: 7.7.1
+ semver: 7.7.2
typescript@5.6.3: {}