summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/great-turtles-greet.md5
-rw-r--r--packages/astro/src/core/build/generate.ts7
-rw-r--r--packages/astro/src/core/build/internal.ts150
-rw-r--r--packages/astro/src/core/build/page-data.ts11
-rw-r--r--packages/astro/src/core/build/pipeline.ts71
-rw-r--r--packages/astro/src/core/build/plugins/plugin-css.ts11
-rw-r--r--packages/astro/src/core/build/plugins/plugin-manifest.ts3
-rw-r--r--packages/astro/src/core/build/plugins/plugin-pages.ts20
-rw-r--r--packages/astro/src/core/build/plugins/plugin-ssr.ts31
-rw-r--r--packages/astro/src/core/build/plugins/util.ts60
-rw-r--r--packages/astro/src/core/build/static-build.ts44
-rw-r--r--packages/astro/src/core/build/types.ts1
-rw-r--r--packages/astro/test/fixtures/reuse-injected-entrypoint/astro.config.mjs34
-rw-r--r--packages/astro/test/fixtures/reuse-injected-entrypoint/package.json8
-rw-r--r--packages/astro/test/fixtures/reuse-injected-entrypoint/src/[id].astro20
-rw-r--r--packages/astro/test/fixtures/reuse-injected-entrypoint/src/pages/index.astro12
-rw-r--r--packages/astro/test/fixtures/reuse-injected-entrypoint/src/to-inject.astro12
-rw-r--r--packages/astro/test/reuse-injected-entrypoint.test.js135
-rw-r--r--packages/astro/test/test-utils.js4
-rw-r--r--pnpm-lock.yaml126
20 files changed, 549 insertions, 216 deletions
diff --git a/.changeset/great-turtles-greet.md b/.changeset/great-turtles-greet.md
new file mode 100644
index 000000000..7043e0e23
--- /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 355d551ea..dbd1e915d 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 a2c74271f..24389353b 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<string, PageBuildData>;
+ pagesByKeys: Map<string, PageBuildData>;
/**
* A map for page-specific output.
@@ -134,7 +129,7 @@ export function createBuildInternals(): BuildInternals {
inlinedScripts: new Map(),
entrySpecifierToBundleMap: new Map<string, string>(),
pageToBundleMap: new Map<string, string>(),
- 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<string, PageBuildData>): Map<string, PageBuildData> {
+ // Create a map to store the pages with the public key, mimicking internal.pagesByKeys
+ const pagesWithPublicKey = new Map<string, PageBuildData>();
+
+ 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, void, unknown> {
+): 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 a151bae2c..6358a6f55 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 daae6940e..cfedc1f2d 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<never>();
- 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<PageBuildData, string> {
const pages = new Map<PageBuildData, string>();
- 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 c50951e0b..c99615602 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 5bb6ddab0..6afd521be 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 dd488a97d..71195a2e2 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<string>();
- 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 07cba1d57..97c7ea1cb 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<string>();
- 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 d1bd266cd..c1552599d 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<VitePlugin['outputOptions'], Function>;
@@ -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 e2acc4ea5..a73835f68 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<string>();
// 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 4b502c353..53c6dcb93 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 000000000..266e31c07
--- /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 000000000..c0ca107cc
--- /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 000000000..151fcfc5e
--- /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;
+---
+<html lang="en">
+ <head>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width" />
+ <title>Routing</title>
+ </head>
+ <body>
+ <h1>[id].astro</h1>
+ <p>{id}</p>
+ </body>
+</html> \ 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 000000000..4c057d514
--- /dev/null
+++ b/packages/astro/test/fixtures/reuse-injected-entrypoint/src/pages/index.astro
@@ -0,0 +1,12 @@
+---
+---
+<html lang="en">
+ <head>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width" />
+ <title>Routing</title>
+ </head>
+ <body>
+ <h1>index.astro</h1>
+ </body>
+</html> \ 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 000000000..13d5bac25
--- /dev/null
+++ b/packages/astro/test/fixtures/reuse-injected-entrypoint/src/to-inject.astro
@@ -0,0 +1,12 @@
+---
+---
+<html lang="en">
+ <head>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width" />
+ <title>Routing</title>
+ </head>
+ <body>
+ <h1>to-inject.astro</h1>
+ </body>
+</html> \ 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 000000000..18723f16c
--- /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 e0eb8c2b7..bd7e1f903 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 98b0868de..d3bb10a16 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