diff --git a/.changeset/beige-deers-travel.md b/.changeset/beige-deers-travel.md new file mode 100644 index 000000000..cdfaa6a41 --- /dev/null +++ b/.changeset/beige-deers-travel.md @@ -0,0 +1,5 @@ +--- +'astro': minor +--- + +Make Astro.url match the build.format configuration during the build diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 19aa85695..ce0c3e4cb 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -428,7 +428,7 @@ export interface AstroUserConfig { * @name trailingSlash * @type {('always' | 'never' | 'ignore')} * @default `'ignore'` - * @see buildOptions.pageUrlFormat + * @see build.format * @description * * Set the route matching behavior of the dev server. Choose from the following options: @@ -517,6 +517,13 @@ export interface AstroUserConfig { * } * } * ``` + * + * #### Effect on Astro.url + * Setting `build.format` controls what `Astro.url` is set to during the build. When it is: + * - `directory` - The `Astro.url.pathname` will include a trailing slash to mimic folder behavior; ie `/foo/`. + * - `file` - The `Astro.url.pathname` will include `.html`; ie `/foo.html`. + * + * This means that when you create relative URLs using `new URL('./relative', Astro.url)`, you will get consistent behavior between dev and build. */ format?: 'file' | 'directory'; }; diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index 5a32122a7..218f1bab3 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -8,6 +8,7 @@ import type { AstroConfig, ComponentInstance, EndpointHandler, + RouteType, SSRLoadedRenderer, } from '../../@types/astro'; import type { BuildInternals } from '../../core/build/internal.js'; @@ -243,6 +244,27 @@ function addPageName(pathname: string, opts: StaticBuildOptions): void { opts.pageNames.push(pathname.replace(/^\//, '')); } +function getUrlForPath(pathname: string, base: string, origin: string, format: 'directory' | 'file', routeType: RouteType): URL { + /** + * Examples: + * pathname: /, /foo + * base: / + */ + const ending = format === 'directory' ? '/' : '.html'; + let buildPathname: string; + if(pathname === '/' || pathname === '') { + buildPathname = base; + } else if(routeType === 'endpoint') { + const buildPathRelative = removeLeadingForwardSlash(pathname); + buildPathname = base + buildPathRelative; + } else { + const buildPathRelative = removeTrailingForwardSlash(removeLeadingForwardSlash(pathname)) + ending; + buildPathname = base + buildPathRelative; + } + const url = new URL(buildPathname, origin); + return url; +} + async function generatePath( pathname: string, opts: StaticBuildOptions, @@ -290,7 +312,8 @@ async function generatePath( } const ssr = opts.astroConfig.output === 'server'; - const url = new URL(opts.astroConfig.base + removeLeadingForwardSlash(pathname), origin); + const url = getUrlForPath(pathname, opts.astroConfig.base, origin, + opts.astroConfig.build.format, pageData.route.type); const options: RenderOptions = { adapterName: undefined, links, diff --git a/packages/astro/test/astro-get-static-paths.test.js b/packages/astro/test/astro-get-static-paths.test.js index 8a0fcca58..80b81140a 100644 --- a/packages/astro/test/astro-get-static-paths.test.js +++ b/packages/astro/test/astro-get-static-paths.test.js @@ -169,6 +169,6 @@ describe('getStaticPaths - Astro.url', () => { const html = await fixture.readFile('/food/tacos/index.html'); const $ = cheerio.load(html); - expect($('#url').text()).to.equal('/food/tacos'); + expect($('#url').text()).to.equal('/food/tacos/'); }); }); diff --git a/packages/astro/test/fixtures/page-format/package.json b/packages/astro/test/fixtures/page-format/package.json new file mode 100644 index 000000000..6352f5bfd --- /dev/null +++ b/packages/astro/test/fixtures/page-format/package.json @@ -0,0 +1,8 @@ +{ + "name": "@test/page-format", + "version": "0.0.0", + "private": true, + "dependencies": { + "astro": "workspace:*" + } +} diff --git a/packages/astro/test/fixtures/page-format/src/pages/nested/page.astro b/packages/astro/test/fixtures/page-format/src/pages/nested/page.astro new file mode 100644 index 000000000..eb67508a7 --- /dev/null +++ b/packages/astro/test/fixtures/page-format/src/pages/nested/page.astro @@ -0,0 +1,4 @@ +--- +const another = new URL('./another/', Astro.url); +--- + diff --git a/packages/astro/test/page-format.test.js b/packages/astro/test/page-format.test.js new file mode 100644 index 000000000..cc31d49d3 --- /dev/null +++ b/packages/astro/test/page-format.test.js @@ -0,0 +1,52 @@ +import { expect } from 'chai'; +import * as cheerio from 'cheerio'; +import { loadFixture } from './test-utils.js'; + +describe('build.format', () => { + describe('directory', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + before(async () => { + fixture = await loadFixture({ + root: './fixtures/page-format/', + }); + }); + + describe('Build', () => { + before(async () => { + await fixture.build(); + }); + + it('relative urls created point to sibling folders', async () => { + let html = await fixture.readFile('/nested/page/index.html'); + let $ = cheerio.load(html); + expect($('#another').attr('href')).to.equal('/nested/page/another/'); + }); + }); + }); + + describe('file', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + before(async () => { + fixture = await loadFixture({ + root: './fixtures/page-format/', + build: { + format: 'file', + }, + }); + }); + + describe('Build', () => { + before(async () => { + await fixture.build(); + }); + + it('relative urls created point to sibling folders', async () => { + let html = await fixture.readFile('/nested/page.html'); + let $ = cheerio.load(html); + expect($('#another').attr('href')).to.equal('/nested/another/'); + }); + }); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 300361cb5..05e7e4ece 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1697,6 +1697,12 @@ importers: packages/astro/test/fixtures/multiple-renderers/renderers/two: specifiers: {} + packages/astro/test/fixtures/page-format: + specifiers: + astro: workspace:* + dependencies: + astro: link:../../.. + packages/astro/test/fixtures/page-level-styles: specifiers: astro: workspace:*