diff --git a/.changeset/stale-ducks-protect.md b/.changeset/stale-ducks-protect.md new file mode 100644 index 0000000000..a87098fc95 --- /dev/null +++ b/.changeset/stale-ducks-protect.md @@ -0,0 +1,5 @@ +--- +"astro": patch +--- + +Fixes an issue that causes static entrypoints build to fail because of the path in certain conditions. Specifically, it failed if the path had an extension (like `.astro`, `.mdx` etc) and such extension would be also within the path (like `./.astro/index.astro`). diff --git a/packages/astro/src/core/build/pipeline.ts b/packages/astro/src/core/build/pipeline.ts index 6d018fd8a3..68cee31a7c 100644 --- a/packages/astro/src/core/build/pipeline.ts +++ b/packages/astro/src/core/build/pipeline.ts @@ -15,10 +15,8 @@ import { getPageDataByComponent, mergeInlineCss, } from './internal.js'; -import { - ASTRO_PAGE_RESOLVED_MODULE_ID, - getVirtualModulePageNameFromPath, -} from './plugins/plugin-pages.js'; +import { ASTRO_PAGE_MODULE_ID, ASTRO_PAGE_RESOLVED_MODULE_ID } from './plugins/plugin-pages.js'; +import { getVirtualModulePageNameFromPath } from './plugins/util.js'; import { RESOLVED_SPLIT_MODULE_ID } from './plugins/plugin-ssr.js'; import { ASTRO_PAGE_EXTENSION_POST_PATTERN } from './plugins/util.js'; import type { PageBuildData, StaticBuildOptions } from './types.js'; @@ -215,7 +213,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(path); + const moduleSpecifier = getVirtualModulePageNameFromPath(ASTRO_PAGE_MODULE_ID, path); // We retrieve the original JS module const filePath = this.internals.entrySpecifierToBundleMap.get(moduleSpecifier); if (filePath) { diff --git a/packages/astro/src/core/build/plugins/plugin-pages.ts b/packages/astro/src/core/build/plugins/plugin-pages.ts index 6ce5ce58cf..dd488a97d9 100644 --- a/packages/astro/src/core/build/plugins/plugin-pages.ts +++ b/packages/astro/src/core/build/plugins/plugin-pages.ts @@ -1,4 +1,3 @@ -import { extname } from 'node:path'; import type { Plugin as VitePlugin } from 'vite'; import { routeIsRedirect } from '../../redirects/index.js'; import { addRollupInput } from '../add-rollup-input.js'; @@ -6,29 +5,13 @@ import { type BuildInternals, eachPageFromAllPages } from '../internal.js'; import type { AstroBuildPlugin } from '../plugin.js'; import type { StaticBuildOptions } from '../types.js'; import { RENDERERS_MODULE_ID } from './plugin-renderers.js'; -import { ASTRO_PAGE_EXTENSION_POST_PATTERN, getPathFromVirtualModulePageName } from './util.js'; +import { getPathFromVirtualModulePageName, getVirtualModulePageNameFromPath } from './util.js'; export const ASTRO_PAGE_MODULE_ID = '@astro-page:'; export const ASTRO_PAGE_RESOLVED_MODULE_ID = '\0' + ASTRO_PAGE_MODULE_ID; -/** - * 1. We add a fixed prefix, which is used as virtual module naming convention; - * 2. We replace the dot that belongs extension with an arbitrary string. - * - * @param path - */ -export function getVirtualModulePageNameFromPath(path: string) { - // we mask the extension, so this virtual file - // so rollup won't trigger other plugins in the process - const extension = extname(path); - return `${ASTRO_PAGE_MODULE_ID}${path.replace( - extension, - extension.replace('.', ASTRO_PAGE_EXTENSION_POST_PATTERN) - )}`; -} - export function getVirtualModulePageIdFromPath(path: string) { - const name = getVirtualModulePageNameFromPath(path); + const name = getVirtualModulePageNameFromPath(ASTRO_PAGE_MODULE_ID, path); return '\x00' + name; } @@ -43,7 +26,7 @@ function vitePluginPages(opts: StaticBuildOptions, internals: BuildInternals): V if (routeIsRedirect(pageData.route)) { continue; } - inputs.add(getVirtualModulePageNameFromPath(path)); + inputs.add(getVirtualModulePageNameFromPath(ASTRO_PAGE_MODULE_ID, path)); } return addRollupInput(options, Array.from(inputs)); diff --git a/packages/astro/src/core/build/plugins/util.ts b/packages/astro/src/core/build/plugins/util.ts index 84e6657b9e..e205f75ebd 100644 --- a/packages/astro/src/core/build/plugins/util.ts +++ b/packages/astro/src/core/build/plugins/util.ts @@ -44,20 +44,20 @@ export function extendManualChunks(outputOptions: OutputOptions, hooks: ExtendMa export const ASTRO_PAGE_EXTENSION_POST_PATTERN = '@_@'; /** - * 1. We add a fixed prefix, which is used as virtual module naming convention; - * 2. We replace the dot that belongs extension with an arbitrary string. + * 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 */ export function getVirtualModulePageNameFromPath(virtualModulePrefix: string, path: string) { - // we mask the extension, so this virtual file - // so rollup won't trigger other plugins in the process const extension = extname(path); - return `${virtualModulePrefix}${path.replace( - extension, - extension.replace('.', ASTRO_PAGE_EXTENSION_POST_PATTERN) - )}`; + return virtualModulePrefix + + (extension.startsWith('.') + ? path.slice(0, -extension.length) + extension.replace('.', ASTRO_PAGE_EXTENSION_POST_PATTERN) + : path); } /** diff --git a/packages/astro/test/ssr-dynamic.test.js b/packages/astro/test/ssr-dynamic.test.js index 60b61335da..6016f5fbde 100644 --- a/packages/astro/test/ssr-dynamic.test.js +++ b/packages/astro/test/ssr-dynamic.test.js @@ -3,14 +3,18 @@ import { before, describe, it } from 'node:test'; import { load as cheerioLoad } from 'cheerio'; import testAdapter from './test-adapter.js'; import { loadFixture } from './test-utils.js'; +import { dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { mkdirSync, writeFileSync } from 'node:fs'; describe('Dynamic pages in SSR', () => { /** @type {import('./test-utils').Fixture} */ let fixture; before(async () => { + const root = './fixtures/ssr-dynamic/'; fixture = await loadFixture({ - root: './fixtures/ssr-dynamic/', + root, output: 'server', integrations: [ { @@ -21,6 +25,18 @@ describe('Dynamic pages in SSR', () => { pattern: '/path-alias/[id]', entrypoint: './src/pages/api/products/[id].js', }); + + const entrypoint = fileURLToPath( + new URL(`${root}.astro/test.astro`, import.meta.url) + ); + console.log(entrypoint) + mkdirSync(dirname(entrypoint), { recursive: true }); + writeFileSync(entrypoint, '

Index

'); + + injectRoute({ + pattern: '/test', + entrypoint, + }); }, }, }, @@ -80,4 +96,10 @@ describe('Dynamic pages in SSR', () => { const favicon = await matchRoute('/favicon.ico'); assert.equal(favicon, undefined); }); + + it('injectRoute entrypoint should not fail build if containing the extension several times in the path', async () => { + const html = await fetchHTML('/test'); + const $ = cheerioLoad(html); + assert.equal($('h1').text(), 'Index'); + }); });