diff options
author | HiDeoo | 2025-07-29 11:47:07 +0200 |
---|---|---|
committer | GitHub | 2025-07-29 11:47:07 +0200 |
commit | 10f6fe22a981247293ee4de106736f1a6ae24b6a (patch) | |
tree | a8bcc7f0a50cea76005869eff60d0c6116b76a3d | |
parent | 4b5a2fa6656d973bff7091af5fa948a5c18eae12 (diff) | |
download | IT.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>
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: {} |