diff --git a/packages/astro/src/content/vite-plugin-content-assets.ts b/packages/astro/src/content/vite-plugin-content-assets.ts index 38ea959cf7ed..a57fb10562e6 100644 --- a/packages/astro/src/content/vite-plugin-content-assets.ts +++ b/packages/astro/src/content/vite-plugin-content-assets.ts @@ -3,7 +3,7 @@ import { pathToFileURL } from 'node:url'; import type { Plugin, Rollup } from 'vite'; import type { AstroSettings, SSRElement } from '../@types/astro.js'; import { getAssetsPrefix } from '../assets/utils/getAssetsPrefix.js'; -import { moduleIsTopLevelPage, walkParentInfos } from '../core/build/graph.js'; +import { getParentModuleInfos, moduleIsTopLevelPage } from '../core/build/graph.js'; import { type BuildInternals, getPageDataByViteID } from '../core/build/internal.js'; import type { AstroBuildPlugin } from '../core/build/plugin.js'; import type { StaticBuildOptions } from '../core/build/types.js'; @@ -186,7 +186,7 @@ export function astroConfigBuildPlugin( } } else { for (const id of Object.keys(chunk.modules)) { - for (const [pageInfo] of walkParentInfos(id, ssrPluginContext!)) { + for (const pageInfo of getParentModuleInfos(id, ssrPluginContext!)) { if (moduleIsTopLevelPage(pageInfo)) { const pageViteID = pageInfo.id; const pageData = getPageDataByViteID(internals, pageViteID); diff --git a/packages/astro/src/core/build/css-asset-name.ts b/packages/astro/src/core/build/css-asset-name.ts index 64852b366a4b..4172a1cbba19 100644 --- a/packages/astro/src/core/build/css-asset-name.ts +++ b/packages/astro/src/core/build/css-asset-name.ts @@ -4,7 +4,7 @@ import crypto from 'node:crypto'; import npath from 'node:path'; import type { AstroSettings } from '../../@types/astro.js'; import { viteID } from '../util.js'; -import { getTopLevelPages } from './graph.js'; +import { getTopLevelPageModuleInfos } from './graph.js'; // These pages could be used as base names for the chunk hashed name, but they are confusing // and should be avoided it possible @@ -14,10 +14,10 @@ const confusingBaseNames = ['404', '500']; // We could get rid of this and only use the createSlugger implementation, but this creates // slightly prettier names. export function shortHashedName(id: string, ctx: { getModuleInfo: GetModuleInfo }): string { - const parents = Array.from(getTopLevelPages(id, ctx)); + const parents = getTopLevelPageModuleInfos(id, ctx); return createNameHash( getFirstParentId(parents), - parents.map(([page]) => page.id) + parents.map((page) => page.id) ); } @@ -38,9 +38,9 @@ export function createSlugger(settings: AstroSettings) { const map = new Map>(); const sep = '-'; return function (id: string, ctx: { getModuleInfo: GetModuleInfo }): string { - const parents = Array.from(getTopLevelPages(id, ctx)); + const parents = Array.from(getTopLevelPageModuleInfos(id, ctx)); const allParentsKey = parents - .map(([page]) => page.id) + .map((page) => page.id) .sort() .join('-'); const firstParentId = getFirstParentId(parents) || indexPage; @@ -90,9 +90,9 @@ export function createSlugger(settings: AstroSettings) { * Find the first parent id from `parents` where its name is not confusing. * Returns undefined if there's no parents. */ -function getFirstParentId(parents: [ModuleInfo, number, number][]) { +function getFirstParentId(parents: ModuleInfo[]) { for (const parent of parents) { - const id = parent[0].id; + const id = parent.id; const baseName = npath.parse(id).name; if (!confusingBaseNames.includes(baseName)) { return id; @@ -100,7 +100,7 @@ function getFirstParentId(parents: [ModuleInfo, number, number][]) { } // If all parents are confusing, just use the first one. Or if there's no // parents, this will return undefined. - return parents[0]?.[0].id; + return parents[0]?.id; } const charsToReplaceRe = /[.[\]]/g; diff --git a/packages/astro/src/core/build/graph.ts b/packages/astro/src/core/build/graph.ts index d2c924c8d8f2..d4c4089f3312 100644 --- a/packages/astro/src/core/build/graph.ts +++ b/packages/astro/src/core/build/graph.ts @@ -2,17 +2,25 @@ import type { GetModuleInfo, ModuleInfo } from 'rollup'; import { ASTRO_PAGE_RESOLVED_MODULE_ID } from './plugins/plugin-pages.js'; +interface ExtendedModuleInfo { + info: ModuleInfo; + depth: number; + order: number; +} + // This walks up the dependency graph and yields out each ModuleInfo object. -export function* walkParentInfos( +export function getParentExtendedModuleInfos( id: string, ctx: { getModuleInfo: GetModuleInfo }, until?: (importer: string) => boolean, depth = 0, order = 0, + childId = '', seen = new Set(), - childId = '' -): Generator<[ModuleInfo, number, number], void, unknown> { + accumulated: ExtendedModuleInfo[] = [] +): ExtendedModuleInfo[] { seen.add(id); + const info = ctx.getModuleInfo(id); if (info) { if (childId) { @@ -26,17 +34,45 @@ export function* walkParentInfos( order += idx; } } + accumulated.push({ info, depth, order }); + } - yield [info, depth, order]; + if (info && !until?.(id)) { + const importers = info.importers.concat(info.dynamicImporters); + for (const imp of importers) { + if (!seen.has(imp)) { + getParentExtendedModuleInfos(imp, ctx, until, depth + 1, order, id, seen, accumulated); + } + } } - if (until?.(id)) return; - const importers = (info?.importers || []).concat(info?.dynamicImporters || []); - for (const imp of importers) { - if (seen.has(imp)) { - continue; + + return accumulated; +} + +export function getParentModuleInfos( + id: string, + ctx: { getModuleInfo: GetModuleInfo }, + until?: (importer: string) => boolean, + seen = new Set(), + accumulated: ModuleInfo[] = [] +): ModuleInfo[] { + seen.add(id); + + const info = ctx.getModuleInfo(id); + if (info) { + accumulated.push(info); + } + + if (info && !until?.(id)) { + const importers = info.importers.concat(info.dynamicImporters); + for (const imp of importers) { + if (!seen.has(imp)) { + getParentModuleInfos(imp, ctx, until, seen, accumulated); + } } - yield* walkParentInfos(imp, ctx, until, depth + 1, order, seen, id); } + + return accumulated; } // Returns true if a module is a top-level page. We determine this based on whether @@ -50,13 +86,9 @@ export function moduleIsTopLevelPage(info: ModuleInfo): boolean { // This function walks the dependency graph, going up until it finds a page component. // This could be a .astro page, a .markdown or a .md (or really any file extension for markdown files) page. -export function* getTopLevelPages( +export function getTopLevelPageModuleInfos( id: string, ctx: { getModuleInfo: GetModuleInfo } -): Generator<[ModuleInfo, number, number], void, unknown> { - for (const res of walkParentInfos(id, ctx)) { - if (moduleIsTopLevelPage(res[0])) { - yield res; - } - } +): ModuleInfo[] { + return getParentModuleInfos(id, ctx).filter(moduleIsTopLevelPage); } diff --git a/packages/astro/src/core/build/plugins/plugin-analyzer.ts b/packages/astro/src/core/build/plugins/plugin-analyzer.ts index ebf04d776cec..cb1f4078b44f 100644 --- a/packages/astro/src/core/build/plugins/plugin-analyzer.ts +++ b/packages/astro/src/core/build/plugins/plugin-analyzer.ts @@ -6,7 +6,11 @@ import type { AstroBuildPlugin } from '../plugin.js'; import { PROPAGATED_ASSET_FLAG } from '../../../content/consts.js'; import { prependForwardSlash } from '../../../core/path.js'; -import { getTopLevelPages, moduleIsTopLevelPage, walkParentInfos } from '../graph.js'; +import { + getParentModuleInfos, + getTopLevelPageModuleInfos, + moduleIsTopLevelPage, +} from '../graph.js'; import { getPageDataByViteID, trackClientOnlyPageDatas } from '../internal.js'; import type { StaticBuildOptions } from '../types.js'; @@ -45,11 +49,9 @@ export function vitePluginAnalyzer( } if (hoistedScripts.size) { - for (const [parentInfo] of walkParentInfos(from, this, function until(importer) { - return isPropagatedAsset(importer); - })) { + for (const parentInfo of getParentModuleInfos(from, this, isPropagatedAsset)) { if (isPropagatedAsset(parentInfo.id)) { - for (const [nestedParentInfo] of walkParentInfos(from, this)) { + for (const nestedParentInfo of getParentModuleInfos(from, this)) { if (moduleIsTopLevelPage(nestedParentInfo)) { for (const hid of hoistedScripts) { if (!pageScripts.has(nestedParentInfo.id)) { @@ -177,7 +179,7 @@ export function vitePluginAnalyzer( } } - for (const [pageInfo] of getTopLevelPages(id, this)) { + for (const pageInfo of getTopLevelPageModuleInfos(id, this)) { const newPageData = getPageDataByViteID(internals, pageInfo.id); if (!newPageData) continue; diff --git a/packages/astro/src/core/build/plugins/plugin-css.ts b/packages/astro/src/core/build/plugins/plugin-css.ts index a84ce37d8a21..6a4ca393dd50 100644 --- a/packages/astro/src/core/build/plugins/plugin-css.ts +++ b/packages/astro/src/core/build/plugins/plugin-css.ts @@ -8,7 +8,11 @@ import type { PageBuildData, StaticBuildOptions, StylesheetAsset } from '../type import { PROPAGATED_ASSET_FLAG } from '../../../content/consts.js'; import type { AstroPluginCssMetadata } from '../../../vite-plugin-astro/index.js'; import * as assetName from '../css-asset-name.js'; -import { moduleIsTopLevelPage, walkParentInfos } from '../graph.js'; +import { + getParentExtendedModuleInfos, + getParentModuleInfos, + moduleIsTopLevelPage, +} from '../graph.js'; import { eachPageData, getPageDataByViteID, @@ -90,9 +94,8 @@ function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] { return internals.cssModuleToChunkIdMap.get(id)!; } - for (const [pageInfo] of walkParentInfos(id, { - getModuleInfo: meta.getModuleInfo, - })) { + const ctx = { getModuleInfo: meta.getModuleInfo }; + for (const pageInfo of getParentModuleInfos(id, ctx)) { if (new URL(pageInfo.id, 'file://').searchParams.has(PROPAGATED_ASSET_FLAG)) { // Split delayed assets to separate modules // so they can be injected where needed @@ -141,7 +144,7 @@ function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] { // For this CSS chunk, walk parents until you find a page. Add the CSS to that page. for (const id of Object.keys(chunk.modules)) { - for (const [pageInfo, depth, order] of walkParentInfos( + for (const { info: pageInfo, depth, order } of getParentExtendedModuleInfos( id, this, function until(importer) { @@ -149,8 +152,7 @@ function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] { } )) { if (new URL(pageInfo.id, 'file://').searchParams.has(PROPAGATED_ASSET_FLAG)) { - for (const parent of walkParentInfos(id, this)) { - const parentInfo = parent[0]; + for (const parentInfo of getParentModuleInfos(id, this)) { if (moduleIsTopLevelPage(parentInfo) === false) continue; const pageViteID = parentInfo.id; @@ -320,7 +322,7 @@ function* getParentClientOnlys( ctx: { getModuleInfo: GetModuleInfo }, internals: BuildInternals ): Generator { - for (const [info] of walkParentInfos(id, ctx)) { + for (const info of getParentModuleInfos(id, ctx)) { yield* getPageDatasByClientOnlyID(internals, info.id); } } diff --git a/packages/astro/src/vite-plugin-head/index.ts b/packages/astro/src/vite-plugin-head/index.ts index 0350e9d7776c..07169bd962f5 100644 --- a/packages/astro/src/vite-plugin-head/index.ts +++ b/packages/astro/src/vite-plugin-head/index.ts @@ -4,7 +4,7 @@ import type { SSRComponentMetadata, SSRResult } from '../@types/astro.js'; import type { AstroBuildPlugin } from '../core/build/plugin.js'; import type { PluginMetadata } from '../vite-plugin-astro/types.js'; -import { getTopLevelPages, walkParentInfos } from '../core/build/graph.js'; +import { getParentModuleInfos, getTopLevelPageModuleInfos } from '../core/build/graph.js'; import type { BuildInternals } from '../core/build/internal.js'; import { getAstroMetadata } from '../vite-plugin-astro/index.js'; @@ -130,13 +130,13 @@ export function astroHeadBuildPlugin(internals: BuildInternals): AstroBuildPlugi if (modinfo) { const meta = getAstroMetadata(modinfo); if (meta?.containsHead) { - for (const [pageInfo] of getTopLevelPages(id, this)) { + for (const pageInfo of getTopLevelPageModuleInfos(id, this)) { let metadata = getOrCreateMetadata(pageInfo.id); metadata.containsHead = true; } } if (meta?.propagation === 'self') { - for (const [info] of walkParentInfos(id, this)) { + for (const info of getParentModuleInfos(id, this)) { let metadata = getOrCreateMetadata(info.id); if (metadata.propagation !== 'self') { metadata.propagation = 'in-tree'; @@ -147,7 +147,7 @@ export function astroHeadBuildPlugin(internals: BuildInternals): AstroBuildPlugi // Head propagation (aka bubbling) if (mod.code && injectExp.test(mod.code)) { - for (const [info] of walkParentInfos(id, this)) { + for (const info of getParentModuleInfos(id, this)) { getOrCreateMetadata(info.id).propagation = 'in-tree'; } }