diff --git a/.changeset/great-turtles-greet.md b/.changeset/great-turtles-greet.md new file mode 100644 index 000000000000..7043e0e239d4 --- /dev/null +++ b/.changeset/great-turtles-greet.md @@ -0,0 +1,5 @@ +--- +"astro": minor +--- + +Adds the ability for multiple pages to use the same component as an `entrypoint` when building an Astro integration. This change is purely internal, and aligns the build process with the behaviour in the development server. \ No newline at end of file diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index 355d551eaa6c..dbd1e915df96 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -42,7 +42,7 @@ import { createRequest } from '../request.js'; import { matchRoute } from '../routing/match.js'; import { getOutputFilename, isServerLikeOutput } from '../util.js'; import { getOutDirWithinCwd, getOutFile, getOutFolder } from './common.js'; -import { cssOrder, getPageDataByComponent, mergeInlineCss } from './internal.js'; +import { cssOrder, mergeInlineCss } from './internal.js'; import { BuildPipeline } from './pipeline.js'; import type { PageBuildData, @@ -51,6 +51,8 @@ import type { StylesheetAsset, } from './types.js'; import { getTimeStat, shouldAppendForwardSlash } from './util.js'; +import { getVirtualModulePageName } from './plugins/util.js'; +import { ASTRO_PAGE_MODULE_ID } from './plugins/plugin-pages.js'; function createEntryURL(filePath: string, outFolder: URL) { return new URL('./' + filePath + `?time=${Date.now()}`, outFolder); @@ -200,7 +202,6 @@ async function generatePage( // prepare information we need const { config, internals, logger } = pipeline; const pageModulePromise = ssrEntry.page; - const pageInfo = getPageDataByComponent(internals, pageData.route.component); // Calculate information of the page, like scripts, links and styles const styles = pageData.styles @@ -209,7 +210,7 @@ async function generatePage( .reduce(mergeInlineCss, []); // may be used in the future for handling rel=modulepreload, rel=icon, rel=manifest etc. const linkIds: [] = []; - const scripts = pageInfo?.hoistedScript ?? null; + const scripts = pageData.hoistedScript ?? null; if (!pageModulePromise) { throw new Error( `Unable to find the module for ${pageData.component}. This is unexpected and likely a bug in Astro, please report.` diff --git a/packages/astro/src/core/build/internal.ts b/packages/astro/src/core/build/internal.ts index a2c74271f496..24389353ba07 100644 --- a/packages/astro/src/core/build/internal.ts +++ b/packages/astro/src/core/build/internal.ts @@ -3,13 +3,8 @@ import type { RouteData, SSRResult } from '../../@types/astro.js'; import type { PageOptions } from '../../vite-plugin-astro/types.js'; import { prependForwardSlash, removeFileExtension } from '../path.js'; import { viteID } from '../util.js'; -import { - ASTRO_PAGE_RESOLVED_MODULE_ID, - getVirtualModulePageIdFromPath, -} from './plugins/plugin-pages.js'; -import { RESOLVED_SPLIT_MODULE_ID } from './plugins/plugin-ssr.js'; -import { ASTRO_PAGE_EXTENSION_POST_PATTERN } from './plugins/util.js'; -import type { AllPagesData, PageBuildData, StylesheetAsset, ViteID } from './types.js'; +import type { PageBuildData, StylesheetAsset, ViteID } from './types.js'; +import { makePageDataKey } from './plugins/util.js'; export interface BuildInternals { /** @@ -45,7 +40,7 @@ export interface BuildInternals { /** * A map for page-specific information. */ - pagesByComponent: Map; + pagesByKeys: Map; /** * A map for page-specific output. @@ -134,7 +129,7 @@ export function createBuildInternals(): BuildInternals { inlinedScripts: new Map(), entrySpecifierToBundleMap: new Map(), pageToBundleMap: new Map(), - pagesByComponent: new Map(), + pagesByKeys: new Map(), pageOptionsByPage: new Map(), pagesByViteID: new Map(), pagesByClientOnly: new Map(), @@ -161,7 +156,7 @@ export function trackPageData( componentURL: URL ): void { pageData.moduleSpecifier = componentModuleId; - internals.pagesByComponent.set(component, pageData); + internals.pagesByKeys.set(pageData.key, pageData); internals.pagesByViteID.set(viteID(componentURL), pageData); } @@ -229,16 +224,77 @@ export function* getPageDatasByClientOnlyID( } } -export function getPageDataByComponent( +/** + * From its route and component, get the page data from the build internals. + * @param internals Build Internals with all the pages + * @param route The route of the page, used to identify the page + * @param component The component of the page, used to identify the page + */ +export function getPageData( internals: BuildInternals, + route: string, component: string ): PageBuildData | undefined { - if (internals.pagesByComponent.has(component)) { - return internals.pagesByComponent.get(component); - } + let pageData = internals.pagesByKeys.get(makePageDataKey(route, component)); + if (pageData) { return pageData;} return undefined; } +/** + * Get all pages datas from the build internals, using a specific component. + * @param internals Build Internals with all the pages + * @param component path to the component, used to identify related pages + */ +export function getPagesDatasByComponent( + internals: BuildInternals, + component: string +): PageBuildData[] { + const pageDatas: PageBuildData[] = []; + internals.pagesByKeys.forEach((pageData) => { + if (component === pageData.component) pageDatas.push(pageData); + }) + return pageDatas; +} + +// TODO: Should be removed in the future. (Astro 5?) +/** + * Map internals.pagesByKeys to a new map with the public key instead of the internal key. + * This function is only used to avoid breaking changes in the Integrations API, after we changed the way + * we identify pages, from the entrypoint component to an internal key. + * If the page component is unique -> the public key is the component path. (old behavior) + * If the page component is shared -> the public key is the internal key. (new behavior) + * The new behavior on shared entrypoint it's not a breaking change, because it was not supported before. + * @param pagesByKeys A map of all page data by their internal key + */ +export function getPageDatasWithPublicKey(pagesByKeys: Map): Map { + // Create a map to store the pages with the public key, mimicking internal.pagesByKeys + const pagesWithPublicKey = new Map(); + + const pagesByComponentsArray = Array.from(pagesByKeys.values()).map((pageData) => { + return { component: pageData.component, pageData: pageData }; + }); + + // Get pages with unique component, and set the public key to the component. + const pagesWithUniqueComponent = pagesByComponentsArray.filter((page) => { + return pagesByComponentsArray.filter((p) => p.component === page.component).length === 1; + }); + + pagesWithUniqueComponent.forEach((page) => { + pagesWithPublicKey.set(page.component, page.pageData); + }); + + // Get pages with shared component, and set the public key to the internal key. + const pagesWithSharedComponent = pagesByComponentsArray.filter((page) => { + return pagesByComponentsArray.filter((p) => p.component === page.component).length > 1; + }); + + pagesWithSharedComponent.forEach((page) => { + pagesWithPublicKey.set(page.pageData.key, page.pageData); + }); + + return pagesWithPublicKey; +} + export function getPageDataByViteID( internals: BuildInternals, viteid: ViteID @@ -253,44 +309,8 @@ export function hasPageDataByViteID(internals: BuildInternals, viteid: ViteID): return internals.pagesByViteID.has(viteid); } -export function* eachPageData(internals: BuildInternals) { - yield* internals.pagesByComponent.values(); -} - -export function* eachPageFromAllPages(allPages: AllPagesData): Generator<[string, PageBuildData]> { - for (const [path, pageData] of Object.entries(allPages)) { - yield [path, pageData]; - } -} - -export function* eachPageDataFromEntryPoint( - internals: BuildInternals -): Generator<[PageBuildData, string]> { - for (const [entrypoint, filePath] of internals.entrySpecifierToBundleMap) { - // virtual pages can be emitted with different prefixes: - // - the classic way are pages emitted with prefix ASTRO_PAGE_RESOLVED_MODULE_ID -> plugin-pages - // - pages emitted using `build.split`, in this case pages are emitted with prefix RESOLVED_SPLIT_MODULE_ID - if ( - entrypoint.includes(ASTRO_PAGE_RESOLVED_MODULE_ID) || - entrypoint.includes(RESOLVED_SPLIT_MODULE_ID) - ) { - const [, pageName] = entrypoint.split(':'); - const pageData = internals.pagesByComponent.get( - `${pageName.replace(ASTRO_PAGE_EXTENSION_POST_PATTERN, '.')}` - ); - if (!pageData) { - throw new Error( - "Build failed. Astro couldn't find the emitted page from " + pageName + ' pattern' - ); - } - - yield [pageData, filePath]; - } - } -} - export function hasPrerenderedPages(internals: BuildInternals) { - for (const pageData of eachPageData(internals)) { + for (const pageData of internals.pagesByKeys.values()) { if (pageData.route.prerender) { return true; } @@ -350,27 +370,23 @@ export function mergeInlineCss( return acc; } -export function isHoistedScript(internals: BuildInternals, id: string): boolean { - return internals.hoistedScriptIdToPagesMap.has(id); -} - -export function* getPageDatasByHoistedScriptId( +/** + * Get all pages data from the build internals, using a specific hoisted script id. + * @param internals Build Internals with all the pages + * @param id Hoisted script id, used to identify the pages using it + */ +export function getPageDatasByHoistedScriptId( internals: BuildInternals, id: string -): Generator { +): PageBuildData[]{ const set = internals.hoistedScriptIdToPagesMap.get(id); + const pageDatas: PageBuildData[] = []; if (set) { for (const pageId of set) { - const pageData = getPageDataByComponent(internals, pageId.slice(1)); - if (pageData) { - yield pageData; - } + getPagesDatasByComponent(internals, pageId.slice(1)).forEach((pageData) => { + pageDatas.push(pageData); + }); } } -} - -// From a component path such as pages/index.astro find the entrypoint module -export function getEntryFilePathFromComponentPath(internals: BuildInternals, path: string) { - const id = getVirtualModulePageIdFromPath(path); - return internals.entrySpecifierToBundleMap.get(id); + return pageDatas; } diff --git a/packages/astro/src/core/build/page-data.ts b/packages/astro/src/core/build/page-data.ts index a151bae2c6f3..6358a6f55364 100644 --- a/packages/astro/src/core/build/page-data.ts +++ b/packages/astro/src/core/build/page-data.ts @@ -4,6 +4,7 @@ import type { AllPagesData } from './types.js'; import * as colors from 'kleur/colors'; import { debug } from '../logger/core.js'; +import { makePageDataKey } from './plugins/util.js'; export interface CollectPagesDataOptions { settings: AstroSettings; @@ -35,6 +36,8 @@ export async function collectPagesData( // and is then cached across all future SSR builds. In the past, we've had trouble // with parallelized builds without guaranteeing that this is called first. for (const route of manifest.routes) { + // Generate a unique key to identify each page in the build process. + const key = makePageDataKey(route.route, route.component); // static route: if (route.pathname) { const routeCollectionLogTimeout = setInterval(() => { @@ -47,8 +50,8 @@ export async function collectPagesData( clearInterval(routeCollectionLogTimeout); }, 10000); builtPaths.add(route.pathname); - - allPages[route.component] = { + allPages[key] = { + key: key, component: route.component, route, moduleSpecifier: '', @@ -70,8 +73,8 @@ export async function collectPagesData( continue; } // dynamic route: - - allPages[route.component] = { + allPages[key] = { + key: key, component: route.component, route, moduleSpecifier: '', diff --git a/packages/astro/src/core/build/pipeline.ts b/packages/astro/src/core/build/pipeline.ts index daae6940e046..cfedc1f2d916 100644 --- a/packages/astro/src/core/build/pipeline.ts +++ b/packages/astro/src/core/build/pipeline.ts @@ -20,20 +20,11 @@ import { } from '../render/ssr-element.js'; import { isServerLikeOutput } from '../util.js'; import { getOutDirWithinCwd } from './common.js'; -import { - type BuildInternals, - cssOrder, - getEntryFilePathFromComponentPath, - getPageDataByComponent, - mergeInlineCss, -} from './internal.js'; +import { type BuildInternals, cssOrder, getPageData, mergeInlineCss } from './internal.js'; import { ASTRO_PAGE_MODULE_ID, ASTRO_PAGE_RESOLVED_MODULE_ID } from './plugins/plugin-pages.js'; import { RESOLVED_SPLIT_MODULE_ID } from './plugins/plugin-ssr.js'; -import { - ASTRO_PAGE_EXTENSION_POST_PATTERN, - getVirtualModulePageNameFromPath, -} from './plugins/util.js'; import type { PageBuildData, SinglePageBuiltModule, StaticBuildOptions } from './types.js'; +import { getPagesFromVirtualModulePageName, getVirtualModulePageName } from './plugins/util.js'; import { i18nHasFallback } from './util.js'; /** @@ -163,7 +154,7 @@ export class BuildPipeline extends Pipeline { settings, } = this; const links = new Set(); - const pageBuildData = getPageDataByComponent(internals, routeData.component); + const pageBuildData = getPageData(internals, routeData.route, routeData.component); const scripts = createModuleScriptsSet( pageBuildData?.hoistedScript ? [pageBuildData.hoistedScript] : [], base, @@ -203,37 +194,47 @@ export class BuildPipeline extends Pipeline { /** * It collects the routes to generate during the build. - * * It returns a map of page information and their relative entry point as a string. */ retrieveRoutesToGenerate(): Map { const pages = new Map(); - for (const [entrypoint, filePath] of this.internals.entrySpecifierToBundleMap) { + for (const [virtualModulePageName, filePath] of this.internals.entrySpecifierToBundleMap) { // virtual pages can be emitted with different prefixes: // - the classic way are pages emitted with prefix ASTRO_PAGE_RESOLVED_MODULE_ID -> plugin-pages - // - pages emitted using `build.split`, in this case pages are emitted with prefix RESOLVED_SPLIT_MODULE_ID + // - pages emitted using `functionPerRoute`, in this case pages are emitted with prefix RESOLVED_SPLIT_MODULE_ID if ( - entrypoint.includes(ASTRO_PAGE_RESOLVED_MODULE_ID) || - entrypoint.includes(RESOLVED_SPLIT_MODULE_ID) + virtualModulePageName.includes(ASTRO_PAGE_RESOLVED_MODULE_ID) || + virtualModulePageName.includes(RESOLVED_SPLIT_MODULE_ID) ) { - const [, pageName] = entrypoint.split(':'); - const pageData = this.internals.pagesByComponent.get( - `${pageName.replace(ASTRO_PAGE_EXTENSION_POST_PATTERN, '.')}` - ); - if (!pageData) { - throw new Error( - "Build failed. Astro couldn't find the emitted page from " + pageName + ' pattern' + let pageDatas: PageBuildData[] = []; + if (virtualModulePageName.includes(ASTRO_PAGE_RESOLVED_MODULE_ID)) { + pageDatas.push( + ...getPagesFromVirtualModulePageName( + this.internals, + ASTRO_PAGE_RESOLVED_MODULE_ID, + virtualModulePageName + ) ); } - - pages.set(pageData, filePath); + if (virtualModulePageName.includes(RESOLVED_SPLIT_MODULE_ID)) { + pageDatas.push( + ...getPagesFromVirtualModulePageName( + this.internals, + RESOLVED_SPLIT_MODULE_ID, + virtualModulePageName + ) + ); + } + for (const pageData of pageDatas) { + pages.set(pageData, filePath); + } } } - for (const [path, pageData] of this.internals.pagesByComponent.entries()) { + for (const pageData of this.internals.pagesByKeys.values()) { if (routeIsRedirect(pageData.route)) { - pages.set(pageData, path); + pages.set(pageData, pageData.component); } else if ( routeIsFallback(pageData.route) && (i18nHasFallback(this.config) || @@ -245,7 +246,7 @@ export class BuildPipeline extends Pipeline { // The values of the map are the actual `.mjs` files that are generated during the build // Here, we take the component path and transform it in the virtual module name - const moduleSpecifier = getVirtualModulePageNameFromPath(ASTRO_PAGE_MODULE_ID, path); + const moduleSpecifier = getVirtualModulePageName(ASTRO_PAGE_MODULE_ID, pageData.component); // We retrieve the original JS module const filePath = this.internals.entrySpecifierToBundleMap.get(moduleSpecifier); if (filePath) { @@ -330,7 +331,7 @@ export class BuildPipeline extends Pipeline { throw new Error(`Expected a redirect route.`); } if (route.redirectRoute) { - const filePath = getEntryFilePathFromComponentPath(internals, route.redirectRoute.component); + const filePath = getEntryFilePath(this.internals, route.redirectRoute); if (filePath) { const url = createEntryURL(filePath, outFolder); const ssrEntryPage: SinglePageBuiltModule = await import(url.toString()); @@ -350,7 +351,7 @@ export class BuildPipeline extends Pipeline { throw new Error(`Expected a redirect route.`); } if (route.redirectRoute) { - const filePath = getEntryFilePathFromComponentPath(internals, route.redirectRoute.component); + const filePath = getEntryFilePath(this.internals, route.redirectRoute); if (filePath) { const url = createEntryURL(filePath, outFolder); const ssrEntryPage: SinglePageBuiltModule = await import(url.toString()); @@ -365,3 +366,11 @@ export class BuildPipeline extends Pipeline { function createEntryURL(filePath: string, outFolder: URL) { return new URL('./' + filePath + `?time=${Date.now()}`, outFolder); } + +/** + * For a given pageData, returns the entry file path—aka a resolved virtual module in our internals' specifiers. + */ +function getEntryFilePath(internals: BuildInternals, pageData: RouteData) { + const id = '\x00' + getVirtualModulePageName(ASTRO_PAGE_MODULE_ID, pageData.component); + return internals.entrySpecifierToBundleMap.get(id); +} diff --git a/packages/astro/src/core/build/plugins/plugin-css.ts b/packages/astro/src/core/build/plugins/plugin-css.ts index c50951e0b081..c9961560219c 100644 --- a/packages/astro/src/core/build/plugins/plugin-css.ts +++ b/packages/astro/src/core/build/plugins/plugin-css.ts @@ -14,11 +14,9 @@ import { moduleIsTopLevelPage, } from '../graph.js'; import { - eachPageData, getPageDataByViteID, getPageDatasByClientOnlyID, getPageDatasByHoistedScriptId, - isHoistedScript, } from '../internal.js'; import { extendManualChunks, shouldInlineAsset } from './util.js'; @@ -147,7 +145,7 @@ function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] { if (pageData) { appendCSSToPage(pageData, meta, pagesToCss, depth, order); } - } else if (options.target === 'client' && isHoistedScript(internals, pageInfo.id)) { + } else if (options.target === 'client' && internals.hoistedScriptIdToPagesMap.has(pageInfo.id)) { for (const pageData of getPageDatasByHoistedScriptId(internals, pageInfo.id)) { appendCSSToPage(pageData, meta, pagesToCss, -1, order); } @@ -199,7 +197,7 @@ function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] { (chunk) => chunk.type === 'asset' && chunk.name === 'style.css' ); if (cssChunk === undefined) return; - for (const pageData of eachPageData(internals)) { + for (const pageData of internals.pagesByKeys.values()) { const cssToInfoMap = (pagesToCss[pageData.moduleSpecifier] ??= {}); cssToInfoMap[cssChunk.fileName] = { depth: -1, order: -1 }; } @@ -238,14 +236,13 @@ function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] { let sheetAddedToPage = false; - // Apply `pagesToCss` information to the respective `pageData.styles` - for (const pageData of eachPageData(internals)) { + internals.pagesByKeys.forEach((pageData) => { const orderingInfo = pagesToCss[pageData.moduleSpecifier]?.[stylesheet.fileName]; if (orderingInfo !== undefined) { pageData.styles.push({ ...orderingInfo, sheet }); sheetAddedToPage = true; } - } + }) // Apply `moduleIdToPropagatedCss` information to `internals.propagatedStylesMap`. // NOTE: It's pretty much a copy over to `internals.propagatedStylesMap` as it should be diff --git a/packages/astro/src/core/build/plugins/plugin-manifest.ts b/packages/astro/src/core/build/plugins/plugin-manifest.ts index 5bb6ddab038a..6afd521be449 100644 --- a/packages/astro/src/core/build/plugins/plugin-manifest.ts +++ b/packages/astro/src/core/build/plugins/plugin-manifest.ts @@ -19,6 +19,7 @@ import { getOutFile, getOutFolder } from '../common.js'; import { type BuildInternals, cssOrder, mergeInlineCss } from '../internal.js'; import type { AstroBuildPlugin } from '../plugin.js'; import type { StaticBuildOptions } from '../types.js'; +import { makePageDataKey } from './util.js'; const manifestReplace = '@@ASTRO_MANIFEST_REPLACE@@'; const replaceExp = new RegExp(`['"]${manifestReplace}['"]`, 'g'); @@ -189,7 +190,7 @@ function buildManifest( } for (const route of opts.manifest.routes) { - const pageData = internals.pagesByComponent.get(route.component); + const pageData = internals.pagesByKeys.get(makePageDataKey(route.route, route.component)); if (route.prerender || !pageData) continue; const scripts: SerializedRouteInfo['scripts'] = []; if (pageData.hoistedScript) { diff --git a/packages/astro/src/core/build/plugins/plugin-pages.ts b/packages/astro/src/core/build/plugins/plugin-pages.ts index dd488a97d91a..71195a2e2996 100644 --- a/packages/astro/src/core/build/plugins/plugin-pages.ts +++ b/packages/astro/src/core/build/plugins/plugin-pages.ts @@ -1,20 +1,15 @@ import type { Plugin as VitePlugin } from 'vite'; import { routeIsRedirect } from '../../redirects/index.js'; import { addRollupInput } from '../add-rollup-input.js'; -import { type BuildInternals, eachPageFromAllPages } from '../internal.js'; +import { type BuildInternals } from '../internal.js'; import type { AstroBuildPlugin } from '../plugin.js'; import type { StaticBuildOptions } from '../types.js'; import { RENDERERS_MODULE_ID } from './plugin-renderers.js'; -import { getPathFromVirtualModulePageName, getVirtualModulePageNameFromPath } from './util.js'; +import { getPagesFromVirtualModulePageName, getVirtualModulePageName } from './util.js'; export const ASTRO_PAGE_MODULE_ID = '@astro-page:'; export const ASTRO_PAGE_RESOLVED_MODULE_ID = '\0' + ASTRO_PAGE_MODULE_ID; -export function getVirtualModulePageIdFromPath(path: string) { - const name = getVirtualModulePageNameFromPath(ASTRO_PAGE_MODULE_ID, path); - return '\x00' + name; -} - function vitePluginPages(opts: StaticBuildOptions, internals: BuildInternals): VitePlugin { return { name: '@astro/plugin-build-pages', @@ -22,11 +17,13 @@ function vitePluginPages(opts: StaticBuildOptions, internals: BuildInternals): V if (opts.settings.config.output === 'static') { const inputs = new Set(); - for (const [path, pageData] of eachPageFromAllPages(opts.allPages)) { + for (const pageData of Object.values(opts.allPages)) { if (routeIsRedirect(pageData.route)) { continue; } - inputs.add(getVirtualModulePageNameFromPath(ASTRO_PAGE_MODULE_ID, path)); + inputs.add( + getVirtualModulePageName(ASTRO_PAGE_MODULE_ID, pageData.component) + ); } return addRollupInput(options, Array.from(inputs)); @@ -41,9 +38,8 @@ function vitePluginPages(opts: StaticBuildOptions, internals: BuildInternals): V if (id.startsWith(ASTRO_PAGE_RESOLVED_MODULE_ID)) { const imports: string[] = []; const exports: string[] = []; - const pageName = getPathFromVirtualModulePageName(ASTRO_PAGE_RESOLVED_MODULE_ID, id); - const pageData = internals.pagesByComponent.get(pageName); - if (pageData) { + const pageDatas = getPagesFromVirtualModulePageName(internals, ASTRO_PAGE_RESOLVED_MODULE_ID, id); + for (const pageData of pageDatas) { const resolvedPage = await this.resolve(pageData.moduleSpecifier); if (resolvedPage) { imports.push(`const page = () => import(${JSON.stringify(pageData.moduleSpecifier)});`); diff --git a/packages/astro/src/core/build/plugins/plugin-ssr.ts b/packages/astro/src/core/build/plugins/plugin-ssr.ts index 07cba1d57cd2..97c7ea1cbe3b 100644 --- a/packages/astro/src/core/build/plugins/plugin-ssr.ts +++ b/packages/astro/src/core/build/plugins/plugin-ssr.ts @@ -7,14 +7,13 @@ import { routeIsRedirect } from '../../redirects/index.js'; import { isServerLikeOutput } from '../../util.js'; import { addRollupInput } from '../add-rollup-input.js'; import type { BuildInternals } from '../internal.js'; -import { eachPageFromAllPages } from '../internal.js'; import type { AstroBuildPlugin } from '../plugin.js'; import type { StaticBuildOptions } from '../types.js'; import { SSR_MANIFEST_VIRTUAL_MODULE_ID } from './plugin-manifest.js'; import { MIDDLEWARE_MODULE_ID } from './plugin-middleware.js'; import { ASTRO_PAGE_MODULE_ID } from './plugin-pages.js'; import { RENDERERS_MODULE_ID } from './plugin-renderers.js'; -import { getPathFromVirtualModulePageName, getVirtualModulePageNameFromPath } from './util.js'; +import { getComponentFromVirtualModulePageName, getVirtualModulePageName } from './util.js'; export const SSR_VIRTUAL_MODULE_ID = '@astrojs-ssr-virtual-entry'; export const RESOLVED_SSR_VIRTUAL_MODULE_ID = '\0' + SSR_VIRTUAL_MODULE_ID; @@ -44,18 +43,21 @@ function vitePluginSSR( let i = 0; const pageMap: string[] = []; - for (const [path, pageData] of eachPageFromAllPages(allPages)) { + for (const pageData of Object.values(allPages)) { if (routeIsRedirect(pageData.route)) { continue; } - const virtualModuleName = getVirtualModulePageNameFromPath(ASTRO_PAGE_MODULE_ID, path); + const virtualModuleName = getVirtualModulePageName( + ASTRO_PAGE_MODULE_ID, + pageData.component + ); let module = await this.resolve(virtualModuleName); if (module) { const variable = `_page${i}`; // we need to use the non-resolved ID in order to resolve correctly the virtual module imports.push(`const ${variable} = () => import("${virtualModuleName}");`); - const pageData2 = internals.pagesByComponent.get(path); + const pageData2 = internals.pagesByKeys.get(pageData.key); if (pageData2) { pageMap.push(`[${JSON.stringify(pageData2.component)}, ${variable}]`); } @@ -147,11 +149,13 @@ function vitePluginSSRSplit( if (functionPerRouteEnabled) { const inputs = new Set(); - for (const [path, pageData] of eachPageFromAllPages(options.allPages)) { + for (const pageData of Object.values(options.allPages)) { if (routeIsRedirect(pageData.route)) { continue; } - inputs.add(getVirtualModulePageNameFromPath(SPLIT_MODULE_ID, path)); + inputs.add( + getVirtualModulePageName(SPLIT_MODULE_ID, pageData.component) + ); } return addRollupInput(opts, Array.from(inputs)); @@ -167,9 +171,8 @@ function vitePluginSSRSplit( const imports: string[] = []; const contents: string[] = []; const exports: string[] = []; - - const path = getPathFromVirtualModulePageName(RESOLVED_SPLIT_MODULE_ID, id); - const virtualModuleName = getVirtualModulePageNameFromPath(ASTRO_PAGE_MODULE_ID, path); + const componentPath = getComponentFromVirtualModulePageName(RESOLVED_SPLIT_MODULE_ID, id); + const virtualModuleName = getVirtualModulePageName(ASTRO_PAGE_MODULE_ID, componentPath); let module = await this.resolve(virtualModuleName); if (module) { // we need to use the non-resolved ID in order to resolve correctly the virtual module @@ -284,7 +287,7 @@ if (_start in serverEntrypointModule) { * we can't use `writeBundle` hook to get the final file name of the entry point written on disk. * We use this hook instead. * - * We retrieve the {@link RouteData} that belongs the current moduleKey + * We retrieve all the {@link RouteData} that have the same component as the one we are processing. */ function storeEntryPoint( moduleKey: string, @@ -292,9 +295,9 @@ function storeEntryPoint( internals: BuildInternals, fileName: string ) { - const componentPath = getPathFromVirtualModulePageName(RESOLVED_SPLIT_MODULE_ID, moduleKey); - for (const [page, pageData] of eachPageFromAllPages(options.allPages)) { - if (componentPath == page) { + const componentPath = getComponentFromVirtualModulePageName(RESOLVED_SPLIT_MODULE_ID, moduleKey); + for (const pageData of Object.values(options.allPages)) { + if (componentPath == pageData.component) { const publicPath = fileURLToPath(options.settings.config.build.server); internals.entryPoints.set(pageData.route, pathToFileURL(join(publicPath, fileName))); } diff --git a/packages/astro/src/core/build/plugins/util.ts b/packages/astro/src/core/build/plugins/util.ts index d1bd266cd4ab..c1552599d90e 100644 --- a/packages/astro/src/core/build/plugins/util.ts +++ b/packages/astro/src/core/build/plugins/util.ts @@ -1,5 +1,7 @@ import { extname } from 'node:path'; import type { BuildOptions, Rollup, Plugin as VitePlugin } from 'vite'; +import type { BuildInternals } from '../internal.js'; +import type { PageBuildData } from '../types.js'; // eslint-disable-next-line @typescript-eslint/ban-types type OutputOptionsHook = Extract; @@ -40,19 +42,28 @@ export function extendManualChunks(outputOptions: OutputOptions, hooks: ExtendMa }; } -// This is an arbitrary string that we are going to replace the dot of the extension +// This is an arbitrary string that we use to replace the dot of the extension. export const ASTRO_PAGE_EXTENSION_POST_PATTERN = '@_@'; +// This is an arbitrary string that we use to make a pageData key +// Has to be a invalid character for a route, to avoid conflicts. +export const ASTRO_PAGE_KEY_SEPARATOR = '&'; + +/** + * Generate a unique key to identify each page in the build process. + * @param route Usually pageData.route.route + * @param componentPath Usually pageData.component + */ +export function makePageDataKey(route: string, componentPath: string): string { + return route + ASTRO_PAGE_KEY_SEPARATOR + componentPath; +} /** * Prevents Rollup from triggering other plugins in the process by masking the extension (hence the virtual file). - * - * 1. We add a fixed prefix, which is used as virtual module naming convention - * 2. If the path has an extension (at the end of the path), we replace the dot that belongs to the extension with an arbitrary string. - * - * @param virtualModulePrefix - * @param path + * Inverse function of getComponentFromVirtualModulePageName() below. + * @param virtualModulePrefix The prefix used to create the virtual module + * @param path Page component path */ -export function getVirtualModulePageNameFromPath(virtualModulePrefix: string, path: string) { +export function getVirtualModulePageName(virtualModulePrefix: string, path: string): string { const extension = extname(path); return ( virtualModulePrefix + @@ -63,13 +74,34 @@ export function getVirtualModulePageNameFromPath(virtualModulePrefix: string, pa } /** - * - * @param virtualModulePrefix - * @param id + * From the VirtualModulePageName, and the internals, get all pageDatas that use this + * component as their entry point. + * @param virtualModulePrefix The prefix used to create the virtual module + * @param id Virtual module name + */ +export function getPagesFromVirtualModulePageName(internals: BuildInternals, virtualModulePrefix: string, id: string): PageBuildData[] +{ + const path = getComponentFromVirtualModulePageName(virtualModulePrefix, id); + + const pages: PageBuildData[] = []; + internals.pagesByKeys.forEach(pageData => { + if (pageData.component === path) { + pages.push(pageData); + } + }); + + return pages; +} + +/** + * From the VirtualModulePageName, get the component path. + * Remember that the component can be use by multiple routes. + * Inverse function of getVirtualModulePageName() above. + * @param virtualModulePrefix The prefix at the beginning of the virtual module + * @param id Virtual module name */ -export function getPathFromVirtualModulePageName(virtualModulePrefix: string, id: string) { - const pageName = id.slice(virtualModulePrefix.length); - return pageName.replace(ASTRO_PAGE_EXTENSION_POST_PATTERN, '.'); +export function getComponentFromVirtualModulePageName(virtualModulePrefix: string, id: string): string { + return id.slice(virtualModulePrefix.length).replace(ASTRO_PAGE_EXTENSION_POST_PATTERN, '.'); } export function shouldInlineAsset( diff --git a/packages/astro/src/core/build/static-build.ts b/packages/astro/src/core/build/static-build.ts index e2acc4ea56ca..a73835f68186 100644 --- a/packages/astro/src/core/build/static-build.ts +++ b/packages/astro/src/core/build/static-build.ts @@ -9,11 +9,7 @@ import * as vite from 'vite'; import type { RouteData } from '../../@types/astro.js'; import { PROPAGATED_ASSET_FLAG } from '../../content/consts.js'; import { hasAnyContentFlag } from '../../content/utils.js'; -import { - type BuildInternals, - createBuildInternals, - eachPageData, -} from '../../core/build/internal.js'; +import { type BuildInternals, createBuildInternals, getPageDatasWithPublicKey } from '../../core/build/internal.js'; import { emptyDir, removeEmptyDirs } from '../../core/fs/index.js'; import { appendForwardSlash, prependForwardSlash, removeFileExtension } from '../../core/path.js'; import { isModeServerWithNoAdapter, isServerLikeOutput } from '../../core/util.js'; @@ -39,7 +35,6 @@ import { encodeName, getTimeStat, viteBuildReturnToRollupOutputs } from './util. export async function viteBuild(opts: StaticBuildOptions) { const { allPages, settings } = opts; - // Make sure we have an adapter before building if (isModeServerWithNoAdapter(opts.settings)) { throw new AstroError(AstroErrorData.NoAdapterInstalled); @@ -48,17 +43,18 @@ export async function viteBuild(opts: StaticBuildOptions) { settings.timer.start('SSR build'); // The pages to be built for rendering purposes. + // (comment above may be outdated ?) const pageInput = new Set(); // Build internals needed by the CSS plugin const internals = createBuildInternals(); - for (const [component, pageData] of Object.entries(allPages)) { - const astroModuleURL = new URL('./' + component, settings.config.root); - const astroModuleId = prependForwardSlash(component); + for (const pageData of Object.values(allPages)) { + const astroModuleURL = new URL('./' + pageData.component, settings.config.root); + const astroModuleId = prependForwardSlash(pageData.component); // Track the page data in internals - trackPageData(internals, component, pageData, astroModuleId, astroModuleURL); + trackPageData(internals, pageData.component, pageData, astroModuleId, astroModuleURL); if (!routeIsRedirect(pageData.route)) { pageInput.add(astroModuleId); @@ -75,7 +71,6 @@ export async function viteBuild(opts: StaticBuildOptions) { // Register plugins const container = createPluginContainer(opts, internals); registerAllPlugins(container); - // Build your project (SSR application code, assets, client JS, etc.) const ssrTime = performance.now(); opts.logger.info('build', `Building ${settings.config.output} entrypoints...`); @@ -275,7 +270,7 @@ async function ssrBuild( const updatedViteBuildConfig = await runHookBuildSetup({ config: settings.config, - pages: internals.pagesByComponent, + pages: getPageDatasWithPublicKey(internals.pagesByKeys), vite: viteBuildConfig, target: 'server', logger: opts.logger, @@ -336,7 +331,7 @@ async function clientBuild( await runHookBuildSetup({ config: settings.config, - pages: internals.pagesByComponent, + pages: getPageDatasWithPublicKey(internals.pagesByKeys), vite: viteBuildConfig, target: 'client', logger: opts.logger, @@ -370,19 +365,26 @@ async function runPostBuildHooks( /** * For each statically prerendered page, replace their SSR file with a noop. * This allows us to run the SSR build only once, but still remove dependencies for statically rendered routes. + * If a component is shared between a statically rendered route and a SSR route, it will still be included in the SSR build. */ async function cleanStaticOutput( opts: StaticBuildOptions, internals: BuildInternals, ssrOutputChunkNames: string[] ) { - const allStaticFiles = new Set(); - for (const pageData of eachPageData(internals)) { - if (pageData.route.prerender && !pageData.hasSharedModules) { - const { moduleSpecifier } = pageData; - const pageBundleId = internals.pageToBundleMap.get(moduleSpecifier); - const entryBundleId = internals.entrySpecifierToBundleMap.get(moduleSpecifier); - allStaticFiles.add(pageBundleId ?? entryBundleId); + const prerenderedFiles = new Set(); + const onDemandsFiles = new Set(); + for (const pageData of internals.pagesByKeys.values()) { + const { moduleSpecifier } = pageData; + const bundleId = internals.pageToBundleMap.get(moduleSpecifier) ?? internals.entrySpecifierToBundleMap.get(moduleSpecifier); + if (pageData.route.prerender && !pageData.hasSharedModules && !onDemandsFiles.has(bundleId)) { + prerenderedFiles.add(bundleId); + } else { + onDemandsFiles.add(bundleId); + // Check if the component was not previously added to the static build by a statically rendered route + if (prerenderedFiles.has(bundleId)) { + prerenderedFiles.delete(bundleId); + } } } const ssr = isServerLikeOutput(opts.settings.config); @@ -400,7 +402,7 @@ async function cleanStaticOutput( // These chunks should only contain prerendering logic, so they are safe to modify. await Promise.all( files.map(async (filename) => { - if (!allStaticFiles.has(filename)) { + if (!prerenderedFiles.has(filename)) { return; } const url = new URL(filename, out); diff --git a/packages/astro/src/core/build/types.ts b/packages/astro/src/core/build/types.ts index 4b502c353c5a..53c6dcb93350 100644 --- a/packages/astro/src/core/build/types.ts +++ b/packages/astro/src/core/build/types.ts @@ -23,6 +23,7 @@ export type StylesheetAsset = export type HoistedScriptAsset = { type: 'inline' | 'external'; value: string }; export interface PageBuildData { + key: string; component: ComponentPath; route: RouteData; moduleSpecifier: string; diff --git a/packages/astro/test/fixtures/reuse-injected-entrypoint/astro.config.mjs b/packages/astro/test/fixtures/reuse-injected-entrypoint/astro.config.mjs new file mode 100644 index 000000000000..266e31c07f93 --- /dev/null +++ b/packages/astro/test/fixtures/reuse-injected-entrypoint/astro.config.mjs @@ -0,0 +1,34 @@ +import { defineConfig } from 'astro/config'; + +// https://astro.build/config +export default defineConfig({ + integrations: [ + { + name: 'astropi', + hooks: { + 'astro:config:setup': async ({ injectRoute }) => { + injectRoute({ + pattern: `/injected-a`, + entrypoint: './src/to-inject.astro', + prerender: true, + }); + injectRoute({ + pattern: `/injected-b`, + entrypoint: './src/to-inject.astro', + prerender: true, + }); + injectRoute({ + pattern: `/dynamic-a/[id]`, + entrypoint: './src/[id].astro', + prerender: true, + }); + injectRoute({ + pattern: `/dynamic-b/[id]`, + entrypoint: './src/[id].astro', + prerender: true, + }); + }, + }, + }, + ], +}); \ No newline at end of file diff --git a/packages/astro/test/fixtures/reuse-injected-entrypoint/package.json b/packages/astro/test/fixtures/reuse-injected-entrypoint/package.json new file mode 100644 index 000000000000..c0ca107ccc41 --- /dev/null +++ b/packages/astro/test/fixtures/reuse-injected-entrypoint/package.json @@ -0,0 +1,8 @@ +{ + "name": "@test/reuse-injected-entrypoint", + "version": "0.0.0", + "private": true, + "dependencies": { + "astro": "workspace:*" + } +} \ No newline at end of file diff --git a/packages/astro/test/fixtures/reuse-injected-entrypoint/src/[id].astro b/packages/astro/test/fixtures/reuse-injected-entrypoint/src/[id].astro new file mode 100644 index 000000000000..151fcfc5e555 --- /dev/null +++ b/packages/astro/test/fixtures/reuse-injected-entrypoint/src/[id].astro @@ -0,0 +1,20 @@ +--- +export async function getStaticPaths() { + return [ + { params: { id: 'id-1' } }, + { params: { id: 'id-2' } } + ]; +} +const { id } = Astro.params; +--- + + + + + Routing + + +

[id].astro

+

{id}

+ + \ No newline at end of file diff --git a/packages/astro/test/fixtures/reuse-injected-entrypoint/src/pages/index.astro b/packages/astro/test/fixtures/reuse-injected-entrypoint/src/pages/index.astro new file mode 100644 index 000000000000..4c057d51407a --- /dev/null +++ b/packages/astro/test/fixtures/reuse-injected-entrypoint/src/pages/index.astro @@ -0,0 +1,12 @@ +--- +--- + + + + + Routing + + +

index.astro

+ + \ No newline at end of file diff --git a/packages/astro/test/fixtures/reuse-injected-entrypoint/src/to-inject.astro b/packages/astro/test/fixtures/reuse-injected-entrypoint/src/to-inject.astro new file mode 100644 index 000000000000..13d5bac25d98 --- /dev/null +++ b/packages/astro/test/fixtures/reuse-injected-entrypoint/src/to-inject.astro @@ -0,0 +1,12 @@ +--- +--- + + + + + Routing + + +

to-inject.astro

+ + \ No newline at end of file diff --git a/packages/astro/test/reuse-injected-entrypoint.test.js b/packages/astro/test/reuse-injected-entrypoint.test.js new file mode 100644 index 000000000000..18723f16cec8 --- /dev/null +++ b/packages/astro/test/reuse-injected-entrypoint.test.js @@ -0,0 +1,135 @@ +import assert from 'node:assert/strict'; +import { after, before, describe, it } from 'node:test'; +import { load as cheerioLoad } from 'cheerio'; +import { loadFixture } from './test-utils.js'; + +const routes = [ + { + description: 'matches / to index.astro', + url: '/', + h1: 'index.astro', + }, + { + description: 'matches /injected-a to to-inject.astro', + url: '/injected-a', + h1: 'to-inject.astro', + }, + { + description: 'matches /injected-b to to-inject.astro', + url: '/injected-b', + h1: 'to-inject.astro', + }, + { + description: 'matches /dynamic-a/id-1 to [id].astro', + url: '/dynamic-a/id-1', + h1: '[id].astro', + p: 'id-1', + }, + { + description: 'matches /dynamic-a/id-2 to [id].astro', + url: '/dynamic-a/id-2', + h1: '[id].astro', + p: 'id-2', + }, + { + description: 'matches /dynamic-b/id-1 to [id].astro', + url: '/dynamic-b/id-1', + h1: '[id].astro', + p: 'id-1', + }, + { + description: 'matches /dynamic-b/id-2 to [id].astro', + url: '/dynamic-b/id-2', + h1: '[id].astro', + p: 'id-2', + }, +]; + +function appendForwardSlash(path) { + return path.endsWith('/') ? path : path + '/'; +} + +describe('Reuse injected entrypoint', () => { + describe('build', () => { + let fixture; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/reuse-injected-entrypoint/', + }); + await fixture.build(); + }); + + routes.forEach(({ description, url, fourOhFour, h1, p, htmlMatch }) => { + const isEndpoint = htmlMatch && !h1 && !p; + + it(description, async () => { + const htmlFile = isEndpoint ? url : `${appendForwardSlash(url)}index.html`; + + if (fourOhFour) { + assert.equal(fixture.pathExists(htmlFile), false); + return; + } + + const html = await fixture.readFile(htmlFile); + const $ = cheerioLoad(html); + + if (h1) { + assert.equal($('h1').text(), h1); + } + + if (p) { + assert.equal($('p').text(), p); + } + + if (htmlMatch) { + assert.equal(html, htmlMatch); + } + }); + }); + }); + + describe('dev', () => { + let fixture; + let devServer; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/reuse-injected-entrypoint/', + }); + + devServer = await fixture.startDevServer(); + }); + + after(async () => { + await devServer.stop(); + }); + + routes.forEach(({ description, url, fourOhFour, h1, p, htmlMatch }) => { + const isEndpoint = htmlMatch && !h1 && !p; + + // checks URLs as written above + it(description, async () => { + const html = await fixture.fetch(url).then((res) => res.text()); + const $ = cheerioLoad(html); + + if (fourOhFour) { + assert.equal($('title').text(), '404: Not Found'); + return; + } + + if (h1) { + assert.equal($('h1').text(), h1); + } + + if (p) { + assert.equal($('p').text(), p); + } + + if (htmlMatch) { + assert.equal(html, htmlMatch); + } + }); + }); + }); +}); diff --git a/packages/astro/test/test-utils.js b/packages/astro/test/test-utils.js index e0eb8c2b71d2..bd7e1f9035ec 100644 --- a/packages/astro/test/test-utils.js +++ b/packages/astro/test/test-utils.js @@ -8,7 +8,7 @@ import stripAnsi from 'strip-ansi'; import { check } from '../dist/cli/check/index.js'; import build from '../dist/core/build/index.js'; import { RESOLVED_SPLIT_MODULE_ID } from '../dist/core/build/plugins/plugin-ssr.js'; -import { getVirtualModulePageNameFromPath } from '../dist/core/build/plugins/util.js'; +import { getVirtualModulePageName } from '../dist/core/build/plugins/util.js'; import { makeSplitEntryPointFileName } from '../dist/core/build/static-build.js'; import { mergeConfig, resolveConfig } from '../dist/core/config/index.js'; import { dev, preview } from '../dist/core/index.js'; @@ -221,7 +221,7 @@ export async function loadFixture(inlineConfig) { return app; }, loadEntryPoint: async (pagePath, routes, streaming) => { - const virtualModule = getVirtualModulePageNameFromPath(RESOLVED_SPLIT_MODULE_ID, pagePath); + const virtualModule = getVirtualModulePageName(RESOLVED_SPLIT_MODULE_ID, pagePath); const filePath = makeSplitEntryPointFileName(virtualModule, routes); const url = new URL(`./server/${filePath}?id=${fixtureId}`, config.outDir); const { createApp, manifest } = await import(url); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 98b0868de4b4..d3bb10a16be2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3336,6 +3336,12 @@ importers: specifier: workspace:* version: link:../../.. + packages/astro/test/fixtures/reuse-injected-entrypoint: + dependencies: + astro: + specifier: workspace:* + version: link:../../.. + packages/astro/test/fixtures/root-srcdir-css: dependencies: astro: @@ -5623,8 +5629,8 @@ packages: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 - /@antfu/utils@0.7.7: - resolution: {integrity: sha512-gFPqTG7otEJ8uP6wrhDv6mqwGWYZKNvAcCq6u9hOj0c+IKCEsY4L1oC9trPq2SaWIzAfHvqfBDxF591JkMf+kg==} + /@antfu/utils@0.7.8: + resolution: {integrity: sha512-rWQkqXRESdjXtc+7NRfK9lASQjpXJu1ayp7qi1d23zZorY+wBHVLHHoVcMsEnkqEBWTFqbztO7/QdJFzyEcLTg==} dev: false /@asamuzakjp/dom-selector@2.0.2: @@ -5830,7 +5836,7 @@ packages: '@babel/helper-optimise-call-expression': 7.22.5 '@babel/helper-replace-supers': 7.24.1(@babel/core@7.24.5) '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 - '@babel/helper-split-export-declaration': 7.24.5 + '@babel/helper-split-export-declaration': 7.22.6 semver: 6.3.1 dev: false @@ -5940,6 +5946,13 @@ packages: '@babel/types': 7.24.5 dev: false + /@babel/helper-split-export-declaration@7.22.6: + resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.24.5 + dev: false + /@babel/helper-split-export-declaration@7.24.5: resolution: {integrity: sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==} engines: {node: '>=6.9.0'} @@ -7445,8 +7458,8 @@ packages: resolution: {integrity: sha512-n5JEf16Wr4mdkRMZ8wMP/wN9/sHmTjRPbouXjJH371mZ2LEGDl72t8tEsMRNFerQN/QJtivOxqK1frdGa4QK5Q==} engines: {node: '>=10'} - /@jsonjoy.com/base64@1.1.1(tslib@2.6.2): - resolution: {integrity: sha512-LnFjVChaGY8cZVMwAIMjvA1XwQjZ/zIXHyh28IyJkyNkzof4Dkm1+KN9UIm3lHhREH4vs7XwZ0NpkZKnwOtEfg==} + /@jsonjoy.com/base64@1.1.2(tslib@2.6.2): + resolution: {integrity: sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' @@ -7457,8 +7470,8 @@ packages: tslib: 2.6.2 dev: true - /@jsonjoy.com/json-pack@1.0.2(tslib@2.6.2): - resolution: {integrity: sha512-4KMApTgb1Hvjz9Ue7unziJ1xNy3k6d2erp0hz1iXryXsf6LEM3KwN6YrfbqT0vqkUO8Tu+CSnvMia9cWX6YGVw==} + /@jsonjoy.com/json-pack@1.0.4(tslib@2.6.2): + resolution: {integrity: sha512-aOcSN4MeAtFROysrbqG137b7gaDDSmVrl5mpo6sT/w+kcXpWnzhMjmY/Fh/sDx26NBxyIE7MB1seqLeCAzy9Sg==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' @@ -7466,15 +7479,15 @@ packages: tslib: optional: true dependencies: - '@jsonjoy.com/base64': 1.1.1(tslib@2.6.2) - '@jsonjoy.com/util': 1.1.0(tslib@2.6.2) + '@jsonjoy.com/base64': 1.1.2(tslib@2.6.2) + '@jsonjoy.com/util': 1.1.3(tslib@2.6.2) hyperdyperid: 1.2.0 thingies: 1.21.0(tslib@2.6.2) tslib: 2.6.2 dev: true - /@jsonjoy.com/util@1.1.0(tslib@2.6.2): - resolution: {integrity: sha512-Yz+xITJ3Y/w0DBISwPkBETP5/cITHXscjgQNZIkfrVz1V7/ahJY8vw+T+LZy/KtXgKuUWqu4GALAQ3bhGt9J8A==} + /@jsonjoy.com/util@1.1.3(tslib@2.6.2): + resolution: {integrity: sha512-g//kkF4kOwUjemValCtOc/xiYzmwMRmWq3Bn+YnzOzuZLHq2PpMOxxIayN3cKbo7Ko2Np65t6D9H81IvXbXhqg==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' @@ -7482,7 +7495,6 @@ packages: tslib: optional: true dependencies: - hyperdyperid: 1.2.0 tslib: 2.6.2 dev: true @@ -9008,6 +9020,16 @@ packages: estree-walker: 2.0.2 source-map-js: 1.2.0 + /@vue/compiler-core@3.4.27: + resolution: {integrity: sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==} + dependencies: + '@babel/parser': 7.24.5 + '@vue/shared': 3.4.27 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.0 + dev: false + /@vue/compiler-dom@3.4.21: resolution: {integrity: sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==} dependencies: @@ -9028,6 +9050,13 @@ packages: '@vue/compiler-core': 3.4.26 '@vue/shared': 3.4.26 + /@vue/compiler-dom@3.4.27: + resolution: {integrity: sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw==} + dependencies: + '@vue/compiler-core': 3.4.27 + '@vue/shared': 3.4.27 + dev: false + /@vue/compiler-sfc@3.4.21: resolution: {integrity: sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==} dependencies: @@ -9042,32 +9071,32 @@ packages: source-map-js: 1.2.0 dev: false - /@vue/compiler-sfc@3.4.24: - resolution: {integrity: sha512-nRAlJUK02FTWfA2nuvNBAqsDZuERGFgxZ8sGH62XgFSvMxO2URblzulExsmj4gFZ8e+VAyDooU9oAoXfEDNxTA==} + /@vue/compiler-sfc@3.4.26: + resolution: {integrity: sha512-It1dp+FAOCgluYSVYlDn5DtZBxk1NCiJJfu2mlQqa/b+k8GL6NG/3/zRbJnHdhV2VhxFghaDq5L4K+1dakW6cw==} dependencies: '@babel/parser': 7.24.5 - '@vue/compiler-core': 3.4.24 - '@vue/compiler-dom': 3.4.24 - '@vue/compiler-ssr': 3.4.24 - '@vue/shared': 3.4.24 + '@vue/compiler-core': 3.4.26 + '@vue/compiler-dom': 3.4.26 + '@vue/compiler-ssr': 3.4.26 + '@vue/shared': 3.4.26 estree-walker: 2.0.2 magic-string: 0.30.10 postcss: 8.4.38 source-map-js: 1.2.0 - dev: false - /@vue/compiler-sfc@3.4.26: - resolution: {integrity: sha512-It1dp+FAOCgluYSVYlDn5DtZBxk1NCiJJfu2mlQqa/b+k8GL6NG/3/zRbJnHdhV2VhxFghaDq5L4K+1dakW6cw==} + /@vue/compiler-sfc@3.4.27: + resolution: {integrity: sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA==} dependencies: '@babel/parser': 7.24.5 - '@vue/compiler-core': 3.4.26 - '@vue/compiler-dom': 3.4.26 - '@vue/compiler-ssr': 3.4.26 - '@vue/shared': 3.4.26 + '@vue/compiler-core': 3.4.27 + '@vue/compiler-dom': 3.4.27 + '@vue/compiler-ssr': 3.4.27 + '@vue/shared': 3.4.27 estree-walker: 2.0.2 magic-string: 0.30.10 postcss: 8.4.38 source-map-js: 1.2.0 + dev: false /@vue/compiler-ssr@3.4.21: resolution: {integrity: sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==} @@ -9076,19 +9105,19 @@ packages: '@vue/shared': 3.4.21 dev: false - /@vue/compiler-ssr@3.4.24: - resolution: {integrity: sha512-ZsAtr4fhaUFnVcDqwW3bYCSDwq+9Gk69q2r/7dAHDrOMw41kylaMgOP4zRnn6GIEJkQznKgrMOGPMFnLB52RbQ==} - dependencies: - '@vue/compiler-dom': 3.4.24 - '@vue/shared': 3.4.24 - dev: false - /@vue/compiler-ssr@3.4.26: resolution: {integrity: sha512-FNwLfk7LlEPRY/g+nw2VqiDKcnDTVdCfBREekF8X74cPLiWHUX6oldktf/Vx28yh4STNy7t+/yuLoMBBF7YDiQ==} dependencies: '@vue/compiler-dom': 3.4.26 '@vue/shared': 3.4.26 + /@vue/compiler-ssr@3.4.27: + resolution: {integrity: sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==} + dependencies: + '@vue/compiler-dom': 3.4.27 + '@vue/shared': 3.4.27 + dev: false + /@vue/devtools-core@7.1.3(vite@5.2.10)(vue@3.4.26): resolution: {integrity: sha512-pVbWi8pf2Z/fZPioYOIgu+cv9pQG55k4D8bL31ec+Wfe+pQR0ImFDu0OhHfch1Ra8uvLLrAZTF4IKeGAkmzD4A==} dependencies: @@ -9201,6 +9230,10 @@ packages: /@vue/shared@3.4.26: resolution: {integrity: sha512-Fg4zwR0GNnjzodMt3KRy2AWGMKQXByl56+4HjN87soxLNU9P5xcJkstAlIeEF3cU6UYOzmJl1tV0dVPGIljCnQ==} + /@vue/shared@3.4.27: + resolution: {integrity: sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==} + dev: false + /@webcomponents/template-shadowroot@0.2.1: resolution: {integrity: sha512-fXL/vIUakyZL62hyvUh+EMwbVoTc0hksublmRz6ai6et8znHkJa6gtqMUZo1oc7dIz46exHSIImml9QTdknMHg==} dev: false @@ -13014,9 +13047,9 @@ packages: resolution: {integrity: sha512-36cVYFMaa9HNEYyvkyKCwker8DBmOdjWLrfekE/cHEKJ806fCfKNVhOJNvoyV/CrGSZDtfQPbhn0Zid0gbH0Hw==} engines: {node: '>= 4.0.0'} dependencies: - '@jsonjoy.com/json-pack': 1.0.2(tslib@2.6.2) - '@jsonjoy.com/util': 1.1.0(tslib@2.6.2) - sonic-forest: 1.0.0(tslib@2.6.2) + '@jsonjoy.com/json-pack': 1.0.4(tslib@2.6.2) + '@jsonjoy.com/util': 1.1.3(tslib@2.6.2) + sonic-forest: 1.0.3(tslib@2.6.2) tslib: 2.6.2 dev: true @@ -15608,8 +15641,8 @@ packages: solid-js: 1.8.17 dev: false - /sonic-forest@1.0.0(tslib@2.6.2): - resolution: {integrity: sha512-yFO2N4uTUFtgKLw03WWFpN1iEwZySweMsa18XN3Kt0yYrlmVHunC2ZgM+437zDoKISAJHcH3Cg18U7d6tuSgSQ==} + /sonic-forest@1.0.3(tslib@2.6.2): + resolution: {integrity: sha512-dtwajos6IWMEWXdEbW1IkEkyL2gztCAgDplRIX+OT5aRKnEd5e7r7YCxRgXZdhRP1FBdOBf8axeTPhzDv8T4wQ==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' @@ -15617,6 +15650,7 @@ packages: tslib: optional: true dependencies: + tree-dump: 1.0.1(tslib@2.6.2) tslib: 2.6.2 dev: true @@ -16164,6 +16198,18 @@ packages: punycode: 2.3.1 dev: true + /tree-dump@1.0.1(tslib@2.6.2): + resolution: {integrity: sha512-WCkcRBVPSlHHq1dc/px9iOfqklvzCbdRwvlNfxGZsrHqf6aZttfPrd7DJTt6oR10dwUfpFFQeVTkPbBIZxX/YA==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + peerDependenciesMeta: + tslib: + optional: true + dependencies: + tslib: 2.6.2 + dev: true + /trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} @@ -16733,7 +16779,7 @@ packages: vite: optional: true dependencies: - '@antfu/utils': 0.7.7 + '@antfu/utils': 0.7.8 '@rollup/pluginutils': 5.1.0 debug: 4.3.4(supports-color@8.1.1) error-stack-parser-es: 0.1.1 @@ -16809,7 +16855,7 @@ packages: '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.5) '@babel/plugin-transform-typescript': 7.24.4(@babel/core@7.24.5) '@vue/babel-plugin-jsx': 1.2.2(@babel/core@7.24.5) - '@vue/compiler-dom': 3.4.26 + '@vue/compiler-dom': 3.4.24 kolorist: 1.8.0 magic-string: 0.30.10 vite: 5.2.10(@types/node@18.19.31)(sass@1.75.0) @@ -16825,7 +16871,7 @@ packages: vue: optional: true dependencies: - '@vue/compiler-sfc': 3.4.24 + '@vue/compiler-sfc': 3.4.27 svgo: 3.2.0 dev: false