From 9f44a513a29cd95b29707d36966bf6466866f860 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Thu, 4 Jan 2024 08:37:32 +0000 Subject: [PATCH 01/23] feat(i18n): disable redirect --- packages/astro/src/core/app/types.ts | 3 +- packages/astro/src/core/config/schema.ts | 14 ++++- packages/astro/src/core/endpoint/index.ts | 3 +- packages/astro/src/core/render/context.ts | 7 ++- packages/astro/src/core/render/result.ts | 3 +- .../astro/src/core/routing/manifest/create.ts | 4 +- packages/astro/src/i18n/index.ts | 12 ++-- packages/astro/src/i18n/middleware.ts | 58 ++++++++++++------- 8 files changed, 67 insertions(+), 37 deletions(-) diff --git a/packages/astro/src/core/app/types.ts b/packages/astro/src/core/app/types.ts index ab4a4fc2cdf7..c0dbb54e75de 100644 --- a/packages/astro/src/core/app/types.ts +++ b/packages/astro/src/core/app/types.ts @@ -7,6 +7,7 @@ import type { SSRResult, } from '../../@types/astro.js'; import type { SinglePageBuiltModule } from '../build/types.js'; +import type { RoutingStrategies } from '../config/schema.js'; export type ComponentPath = string; @@ -56,7 +57,7 @@ export type SSRManifest = { export type SSRManifestI18n = { fallback?: Record; - routing?: 'prefix-always' | 'prefix-other-locales'; + routing?: RoutingStrategies; locales: Locales; defaultLocale: string; }; diff --git a/packages/astro/src/core/config/schema.ts b/packages/astro/src/core/config/schema.ts index 5d30d1b5305c..90b7432df8c4 100644 --- a/packages/astro/src/core/config/schema.ts +++ b/packages/astro/src/core/config/schema.ts @@ -61,7 +61,10 @@ const ASTRO_CONFIG_DEFAULTS = { }, } satisfies AstroUserConfig & { server: { open: boolean } }; -type RoutingStrategies = 'prefix-always' | 'prefix-other-locales'; +export type RoutingStrategies = + | 'pathname-prefix-always' + | 'pathname-prefix-other-locales' + | 'pathname-prefix-always-no-redirect'; export const AstroConfigSchema = z.object({ root: z @@ -322,6 +325,7 @@ export const AstroConfigSchema = z.object({ routing: z .object({ prefixDefaultLocale: z.boolean().default(false), + redirectToDefaultLocale: z.boolean().default(true), strategy: z.enum(['pathname']).default('pathname'), }) .default({}) @@ -330,9 +334,13 @@ export const AstroConfigSchema = z.object({ switch (routing.strategy) { case 'pathname': { if (routing.prefixDefaultLocale === true) { - strategy = 'prefix-always'; + if (routing.redirectToDefaultLocale) { + strategy = 'pathname-prefix-always'; + } else { + strategy = 'pathname-prefix-always-no-redirect'; + } } else { - strategy = 'prefix-other-locales'; + strategy = 'pathname-prefix-other-locales'; } } } diff --git a/packages/astro/src/core/endpoint/index.ts b/packages/astro/src/core/endpoint/index.ts index c04c9b2b5021..e8264e881a9d 100644 --- a/packages/astro/src/core/endpoint/index.ts +++ b/packages/astro/src/core/endpoint/index.ts @@ -16,6 +16,7 @@ import { computePreferredLocaleList, } from '../render/context.js'; import { type Environment, type RenderContext } from '../render/index.js'; +import type { RoutingStrategies } from '../config/schema.js'; const clientAddressSymbol = Symbol.for('astro.clientAddress'); const clientLocalsSymbol = Symbol.for('astro.locals'); @@ -27,7 +28,7 @@ type CreateAPIContext = { props: Record; adapterName?: string; locales: Locales | undefined; - routingStrategy: 'prefix-always' | 'prefix-other-locales' | undefined; + routingStrategy: RoutingStrategies | undefined; defaultLocale: string | undefined; }; diff --git a/packages/astro/src/core/render/context.ts b/packages/astro/src/core/render/context.ts index 459b2b8b48cc..9b94f4ad2f8f 100644 --- a/packages/astro/src/core/render/context.ts +++ b/packages/astro/src/core/render/context.ts @@ -11,6 +11,7 @@ import { normalizeTheLocale, toCodes } from '../../i18n/index.js'; import { AstroError, AstroErrorData } from '../errors/index.js'; import type { Environment } from './environment.js'; import { getParamsAndProps } from './params-and-props.js'; +import type { RoutingStrategies } from '../config/schema.js'; const clientLocalsSymbol = Symbol.for('astro.locals'); @@ -31,7 +32,7 @@ export interface RenderContext { locals?: object; locales: Locales | undefined; defaultLocale: string | undefined; - routing: 'prefix-always' | 'prefix-other-locales' | undefined; + routing: RoutingStrategies | undefined; } export type CreateRenderContextArgs = Partial< @@ -239,7 +240,7 @@ export function computePreferredLocaleList(request: Request, locales: Locales): export function computeCurrentLocale( request: Request, locales: Locales, - routingStrategy: 'prefix-always' | 'prefix-other-locales' | undefined, + routingStrategy: RoutingStrategies | undefined, defaultLocale: string | undefined ): undefined | string { const requestUrl = new URL(request.url); @@ -256,7 +257,7 @@ export function computeCurrentLocale( } } } - if (routingStrategy === 'prefix-other-locales') { + if (routingStrategy === 'pathname-prefix-other-locales') { return defaultLocale; } return undefined; diff --git a/packages/astro/src/core/render/result.ts b/packages/astro/src/core/render/result.ts index 29b54ae85393..5faa6442c3e0 100644 --- a/packages/astro/src/core/render/result.ts +++ b/packages/astro/src/core/render/result.ts @@ -18,6 +18,7 @@ import { computePreferredLocale, computePreferredLocaleList, } from './context.js'; +import type { RoutingStrategies } from '../config/schema.js'; const clientAddressSymbol = Symbol.for('astro.clientAddress'); const responseSentSymbol = Symbol.for('astro.responseSent'); @@ -53,7 +54,7 @@ export interface CreateResultArgs { cookies?: AstroCookies; locales: Locales | undefined; defaultLocale: string | undefined; - routingStrategy: 'prefix-always' | 'prefix-other-locales' | undefined; + routingStrategy: RoutingStrategies | undefined; } function getFunctionExpression(slot: any) { diff --git a/packages/astro/src/core/routing/manifest/create.ts b/packages/astro/src/core/routing/manifest/create.ts index fcb31c1cc9b8..22f399f9fc26 100644 --- a/packages/astro/src/core/routing/manifest/create.ts +++ b/packages/astro/src/core/routing/manifest/create.ts @@ -566,7 +566,7 @@ export function createRouteManifest( // Work done, now we start creating "fallback" routes based on the configuration - if (i18n.routing === 'prefix-always') { + if (i18n.routing === 'pathname-prefix-always') { // we attempt to retrieve the index page of the default locale const defaultLocaleRoutes = routesByLocale.get(i18n.defaultLocale); if (defaultLocaleRoutes) { @@ -639,7 +639,7 @@ export function createRouteManifest( let route: string; if ( fallbackToLocale === i18n.defaultLocale && - i18n.routing === 'prefix-other-locales' + i18n.routing === 'pathname-prefix-other-locales' ) { if (fallbackToRoute.pathname) { pathname = `/${fallbackFromLocale}${fallbackToRoute.pathname}`; diff --git a/packages/astro/src/i18n/index.ts b/packages/astro/src/i18n/index.ts index a0dcfdff0e9a..3ac62147a1d0 100644 --- a/packages/astro/src/i18n/index.ts +++ b/packages/astro/src/i18n/index.ts @@ -10,7 +10,7 @@ type GetLocaleRelativeUrl = GetLocaleOptions & { locales: Locales; trailingSlash: AstroConfig['trailingSlash']; format: AstroConfig['build']['format']; - routing?: 'prefix-always' | 'prefix-other-locales'; + routing?: 'pathname-prefix-always' | 'pathname-prefix-other-locales'; defaultLocale: string; }; @@ -45,7 +45,7 @@ export function getLocaleRelativeUrl({ path, prependWith, normalizeLocale = true, - routing = 'prefix-other-locales', + routing = 'pathname-prefix-other-locales', defaultLocale, }: GetLocaleRelativeUrl) { const codeToUse = peekCodePathToUse(_locales, locale); @@ -57,7 +57,7 @@ export function getLocaleRelativeUrl({ } const pathsToJoin = [base, prependWith]; const normalizedLocale = normalizeLocale ? normalizeTheLocale(codeToUse) : codeToUse; - if (routing === 'prefix-always') { + if (routing === 'pathname-prefix-always') { pathsToJoin.push(normalizedLocale); } else if (locale !== defaultLocale) { pathsToJoin.push(normalizedLocale); @@ -88,7 +88,7 @@ type GetLocalesBaseUrl = GetLocaleOptions & { locales: Locales; trailingSlash: AstroConfig['trailingSlash']; format: AstroConfig['build']['format']; - routing?: 'prefix-always' | 'prefix-other-locales'; + routing?: 'pathname-prefix-always' | 'pathname-prefix-other-locales'; defaultLocale: string; }; @@ -100,7 +100,7 @@ export function getLocaleRelativeUrlList({ path, prependWith, normalizeLocale = false, - routing = 'prefix-other-locales', + routing = 'pathname-prefix-other-locales', defaultLocale, }: GetLocalesBaseUrl) { const locales = toPaths(_locales); @@ -108,7 +108,7 @@ export function getLocaleRelativeUrlList({ const pathsToJoin = [base, prependWith]; const normalizedLocale = normalizeLocale ? normalizeTheLocale(locale) : locale; - if (routing === 'prefix-always') { + if (routing === 'pathname-prefix-always') { pathsToJoin.push(normalizedLocale); } else if (locale !== defaultLocale) { pathsToJoin.push(normalizedLocale); diff --git a/packages/astro/src/i18n/middleware.ts b/packages/astro/src/i18n/middleware.ts index 12732d880eb0..7b0f820bb13a 100644 --- a/packages/astro/src/i18n/middleware.ts +++ b/packages/astro/src/i18n/middleware.ts @@ -54,30 +54,48 @@ export function createI18nMiddleware( if (response instanceof Response) { const pathnameContainsDefaultLocale = url.pathname.includes(`/${defaultLocale}`); - if (i18n.routing === 'prefix-other-locales' && pathnameContainsDefaultLocale) { - const newLocation = url.pathname.replace(`/${defaultLocale}`, ''); - response.headers.set('Location', newLocation); - return new Response(null, { - status: 404, - headers: response.headers, - }); - } else if (i18n.routing === 'prefix-always') { - if (url.pathname === base + '/' || url.pathname === base) { - if (trailingSlash === 'always') { - return context.redirect(`${appendForwardSlash(joinPaths(base, i18n.defaultLocale))}`); - } else { - return context.redirect(`${joinPaths(base, i18n.defaultLocale)}`); + switch (i18n.routing) { + case 'pathname-prefix-other-locales': { + if (pathnameContainsDefaultLocale) { + const newLocation = url.pathname.replace(`/${defaultLocale}`, ''); + response.headers.set('Location', newLocation); + return new Response(null, { + status: 404, + headers: response.headers, + }); + } + break; + } + + case 'pathname-prefix-always-no-redirect': { + if (!pathnameHasLocale(url.pathname, i18n.locales)) { + return new Response(null, { + status: 404, + headers: response.headers, + }); } + break; } - // Astro can't know where the default locale is supposed to be, so it returns a 404 with no content. - else if (!pathnameHasLocale(url.pathname, i18n.locales)) { - return new Response(null, { - status: 404, - headers: response.headers, - }); + case 'pathname-prefix-always': { + if (url.pathname === base + '/' || url.pathname === base) { + if (trailingSlash === 'always') { + return context.redirect(`${appendForwardSlash(joinPaths(base, i18n.defaultLocale))}`); + } else { + return context.redirect(`${joinPaths(base, i18n.defaultLocale)}`); + } + } + + // Astro can't know where the default locale is supposed to be, so it returns a 404 with no content. + else if (!pathnameHasLocale(url.pathname, i18n.locales)) { + return new Response(null, { + status: 404, + headers: response.headers, + }); + } } } + if (response.status >= 300 && fallback) { const fallbackKeys = i18n.fallback ? Object.keys(i18n.fallback) : []; @@ -103,7 +121,7 @@ export function createI18nMiddleware( let newPathname: string; // If a locale falls back to the default locale, we want to **remove** the locale because // the default locale doesn't have a prefix - if (pathFallbackLocale === defaultLocale && routing === 'prefix-other-locales') { + if (pathFallbackLocale === defaultLocale && routing === 'pathname-prefix-other-locales') { newPathname = url.pathname.replace(`/${urlLocale}`, ``); } else { newPathname = url.pathname.replace(`/${urlLocale}`, `/${pathFallbackLocale}`); From 6aff28501c7cb2072f20d7cd866418c2b125f58b Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Mon, 8 Jan 2024 08:47:32 +0000 Subject: [PATCH 02/23] feat(i18n): add option to disable redirect to default language --- packages/astro/src/@types/astro.ts | 11 +++ packages/astro/src/i18n/index.ts | 5 +- packages/astro/src/i18n/middleware.ts | 7 +- .../src/pages/index.astro | 8 ++ packages/astro/test/i18n-routing.test.js | 94 +++++++++++++++++-- .../astro/test/units/i18n/astro_i18n.test.js | 16 ++-- 6 files changed, 124 insertions(+), 17 deletions(-) create mode 100644 packages/astro/test/fixtures/i18n-routing-prefix-always/src/pages/index.astro diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 97429212c698..ad4960c26739 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -1493,6 +1493,17 @@ export interface AstroUserConfig { */ prefixDefaultLocale: boolean; + /** + * @docs + * @name i18n.routing.redirectToDefaultLocale + * @kind h4 + * @type {boolean} + * @default `true` + * @version 4.2.0 + * @description + * */ + redirectToDefaultLocale: boolean; + /** * @name i18n.routing.strategy * @type {"pathname"} diff --git a/packages/astro/src/i18n/index.ts b/packages/astro/src/i18n/index.ts index 3ac62147a1d0..a575dfa97316 100644 --- a/packages/astro/src/i18n/index.ts +++ b/packages/astro/src/i18n/index.ts @@ -3,6 +3,7 @@ import type { AstroConfig, Locales } from '../@types/astro.js'; import { shouldAppendForwardSlash } from '../core/build/util.js'; import { MissingLocale } from '../core/errors/errors-data.js'; import { AstroError } from '../core/errors/index.js'; +import type { RoutingStrategies } from '../core/config/schema.js'; type GetLocaleRelativeUrl = GetLocaleOptions & { locale: string; @@ -10,7 +11,7 @@ type GetLocaleRelativeUrl = GetLocaleOptions & { locales: Locales; trailingSlash: AstroConfig['trailingSlash']; format: AstroConfig['build']['format']; - routing?: 'pathname-prefix-always' | 'pathname-prefix-other-locales'; + routing?: RoutingStrategies; defaultLocale: string; }; @@ -88,7 +89,7 @@ type GetLocalesBaseUrl = GetLocaleOptions & { locales: Locales; trailingSlash: AstroConfig['trailingSlash']; format: AstroConfig['build']['format']; - routing?: 'pathname-prefix-always' | 'pathname-prefix-other-locales'; + routing?: RoutingStrategies; defaultLocale: string; }; diff --git a/packages/astro/src/i18n/middleware.ts b/packages/astro/src/i18n/middleware.ts index 7b0f820bb13a..502ee61da97e 100644 --- a/packages/astro/src/i18n/middleware.ts +++ b/packages/astro/src/i18n/middleware.ts @@ -2,6 +2,7 @@ import { appendForwardSlash, joinPaths } from '@astrojs/internal-helpers/path'; import type { Locales, MiddlewareHandler, RouteData, SSRManifest } from '../@types/astro.js'; import type { PipelineHookFunction } from '../core/pipeline.js'; import { getPathByLocale, normalizeTheLocale } from './index.js'; +import { shouldAppendForwardSlash } from '../core/build/util.js'; const routeDataSymbol = Symbol.for('astro.routeData'); @@ -68,7 +69,11 @@ export function createI18nMiddleware( } case 'pathname-prefix-always-no-redirect': { - if (!pathnameHasLocale(url.pathname, i18n.locales)) { + // We return a 404 if: + // - the current path isn't a root. e.g. / or / + // - the URL doesn't contain a locale + const isRoot = url.pathname.startsWith(base) || url.pathname === '/'; + if (!(isRoot || pathnameHasLocale(url.pathname, i18n.locales))) { return new Response(null, { status: 404, headers: response.headers, diff --git a/packages/astro/test/fixtures/i18n-routing-prefix-always/src/pages/index.astro b/packages/astro/test/fixtures/i18n-routing-prefix-always/src/pages/index.astro new file mode 100644 index 000000000000..51507e040d25 --- /dev/null +++ b/packages/astro/test/fixtures/i18n-routing-prefix-always/src/pages/index.astro @@ -0,0 +1,8 @@ + + + Astro + + +I am index + + diff --git a/packages/astro/test/i18n-routing.test.js b/packages/astro/test/i18n-routing.test.js index 567dc040e30e..c7a2d99b2e0e 100644 --- a/packages/astro/test/i18n-routing.test.js +++ b/packages/astro/test/i18n-routing.test.js @@ -231,7 +231,37 @@ describe('[DEV] i18n routing', () => { }); }); - describe('i18n routing with routing strategy [prefix-always]', () => { + describe('i18n routing with routing strategy [pathname-prefix-always-no-redirect]', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + /** @type {import('./test-utils').DevServer} */ + let devServer; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/i18n-routing-prefix-always/', + i18n: { + routing: { + prefixDefaultLocale: true, + redirectToDefaultLocale: false, + }, + }, + }); + devServer = await fixture.startDevServer(); + }); + + after(async () => { + await devServer.stop(); + }); + + it('should NOT redirect to the index of the default locale', async () => { + const response = await fixture.fetch('/new-site'); + expect(response.status).to.equal(200); + expect(await response.text()).includes('I am index'); + }); + }); + + describe('i18n routing with routing strategy [pathname-prefix-always]', () => { /** @type {import('./test-utils').Fixture} */ let fixture; /** @type {import('./test-utils').DevServer} */ @@ -607,7 +637,31 @@ describe('[SSG] i18n routing', () => { }); }); - describe('i18n routing with routing strategy [prefix-always]', () => { + describe('i18n routing with routing strategy [pathname-prefix-always-no-redirect]', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/i18n-routing-prefix-always/', + i18n: { + routing: { + prefixDefaultLocale: true, + redirectToDefaultLocale: false, + }, + }, + }); + await fixture.build(); + }); + + it('should NOT redirect to the index of the default locale', async () => { + const html = await fixture.readFile('/index.html'); + let $ = cheerio.load(html); + expect($('body').text()).includes('I am index'); + }); + }); + + describe('i18n routing with routing strategy [pathname-prefix-always]', () => { /** @type {import('./test-utils').Fixture} */ let fixture; @@ -776,7 +830,7 @@ describe('[SSG] i18n routing', () => { }); }); - describe('i18n routing with fallback and [prefix-always]', () => { + describe('i18n routing with fallback and [pathname-prefix-always]', () => { /** @type {import('./test-utils').Fixture} */ let fixture; @@ -1019,7 +1073,35 @@ describe('[SSR] i18n routing', () => { }); }); - describe('i18n routing with routing strategy [prefix-always]', () => { + describe('i18n routing with routing strategy [pathname-prefix-always-no-redirect]', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/i18n-routing-prefix-always/', + output: 'server', + adapter: testAdapter(), + i18n: { + routing: { + prefixDefaultLocale: true, + redirectToDefaultLocale: false, + }, + }, + }); + await fixture.build(); + app = await fixture.loadTestAdapterApp(); + }); + + it('should NOT redirect the index to the default locale', async () => { + let request = new Request('http://example.com/new-site'); + let response = await app.render(request); + expect(response.status).to.equal(200); + expect(await response.text()).includes('I am index'); + }); + }); + + describe('i18n routing with routing strategy [pathname-prefix-always]', () => { /** @type {import('./test-utils').Fixture} */ let fixture; @@ -1158,7 +1240,7 @@ describe('[SSR] i18n routing', () => { expect(response.status).to.equal(404); }); - describe('with routing strategy [prefix-always]', () => { + describe('with routing strategy [pathname-prefix-always]', () => { before(async () => { fixture = await loadFixture({ root: './fixtures/i18n-routing-fallback/', @@ -1351,7 +1433,7 @@ describe('[SSR] i18n routing', () => { }); }); - describe('with [prefix-always]', () => { + describe('with [pathname-prefix-always]', () => { /** @type {import('./test-utils').Fixture} */ let fixture; diff --git a/packages/astro/test/units/i18n/astro_i18n.test.js b/packages/astro/test/units/i18n/astro_i18n.test.js index ad5a2fa23e0e..c9d7615429f6 100644 --- a/packages/astro/test/units/i18n/astro_i18n.test.js +++ b/packages/astro/test/units/i18n/astro_i18n.test.js @@ -275,7 +275,7 @@ describe('getLocaleRelativeUrl', () => { ).to.eq('/blog/en-au/'); }); - it('should return the default locale when routing strategy is [prefix-always]', () => { + it('should return the default locale when routing strategy is [pathname-prefix-always]', () => { /** * * @type {import("../../../dist/@types").AstroUserConfig} @@ -286,7 +286,7 @@ describe('getLocaleRelativeUrl', () => { i18n: { defaultLocale: 'en', locales: ['en', 'es', 'en_US', 'en_AU'], - routing: 'prefix-always', + routing: 'pathname-prefix-always', }, }, }; @@ -520,7 +520,7 @@ describe('getLocaleRelativeUrlList', () => { ).to.have.members(['/blog/', '/blog/en_US/', '/blog/es/']); }); - it('should retrieve the correct list of base URL with locales [format: directory, trailingSlash: never, routingStategy: prefix-always]', () => { + it('should retrieve the correct list of base URL with locales [format: directory, trailingSlash: never, routingStategy: pathname-prefix-always]', () => { /** * * @type {import("../../../dist/@types").AstroUserConfig} @@ -530,7 +530,7 @@ describe('getLocaleRelativeUrlList', () => { i18n: { defaultLocale: 'en', locales: ['en', 'en_US', 'es'], - routing: 'prefix-always', + routing: 'pathname-prefix-always', }, }, }; @@ -829,7 +829,7 @@ describe('getLocaleAbsoluteUrl', () => { ).to.eq('/blog/en-us/'); }); - it('should return the default locale when routing strategy is [prefix-always]', () => { + it('should return the default locale when routing strategy is [pathname-prefix-always]', () => { /** * * @type {import("../../../dist/@types").AstroUserConfig} @@ -840,7 +840,7 @@ describe('getLocaleAbsoluteUrl', () => { i18n: { defaultLocale: 'en', locales: ['en', 'es', 'en_US', 'en_AU'], - routing: 'prefix-always', + routing: 'pathname-prefix-always', }, }, }; @@ -1112,7 +1112,7 @@ describe('getLocaleAbsoluteUrlList', () => { ]); }); - it('should retrieve the correct list of base URL with locales [format: directory, trailingSlash: ignore, routingStategy: prefix-always]', () => { + it('should retrieve the correct list of base URL with locales [format: directory, trailingSlash: ignore, routingStategy: pathname-prefix-always]', () => { /** * * @type {import("../../../dist/@types").AstroUserConfig} @@ -1122,7 +1122,7 @@ describe('getLocaleAbsoluteUrlList', () => { i18n: { defaultLocale: 'en', locales: ['en', 'en_US', 'es'], - routing: 'prefix-always', + routing: 'pathname-prefix-always', }, }, }; From 316534644eab77664eca1bbfa9692b462311f2b9 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Mon, 8 Jan 2024 10:44:40 +0000 Subject: [PATCH 03/23] chore: add schema validation --- packages/astro/src/core/config/schema.ts | 9 +++++++++ .../test/units/config/config-validate.test.js | 20 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/packages/astro/src/core/config/schema.ts b/packages/astro/src/core/config/schema.ts index 90b7432df8c4..e74d74ac439b 100644 --- a/packages/astro/src/core/config/schema.ts +++ b/packages/astro/src/core/config/schema.ts @@ -329,6 +329,15 @@ export const AstroConfigSchema = z.object({ strategy: z.enum(['pathname']).default('pathname'), }) .default({}) + .refine( + ({ prefixDefaultLocale, redirectToDefaultLocale }) => { + return !(prefixDefaultLocale === false && redirectToDefaultLocale === false); + }, + { + message: + 'The option `i18n.redirectToDefaultLocale` has effects only when the `i18n.prefixDefaultLocale` is set to `true`. Remove the option `i18n.redirectToDefaultLocale`, or change the value of `i18n.prefixDefaultLocale` to `true`.', + } + ) .transform((routing) => { let strategy: RoutingStrategies; switch (routing.strategy) { diff --git a/packages/astro/test/units/config/config-validate.test.js b/packages/astro/test/units/config/config-validate.test.js index 341ed47b422c..76fa57b4d5a6 100644 --- a/packages/astro/test/units/config/config-validate.test.js +++ b/packages/astro/test/units/config/config-validate.test.js @@ -193,5 +193,25 @@ describe('Config Validation', () => { "You can't use the default locale as a key. The default locale can only be used as value." ); }); + + it('errors if `i18n.prefixDefaultLocale` is `false` and `i18n.redirectToDefaultLocale` is `true`', async () => { + const configError = await validateConfig( + { + i18n: { + defaultLocale: 'en', + locales: ['es', 'en'], + routing: { + prefixDefaultLocale: false, + redirectToDefaultLocale: false, + }, + }, + }, + process.cwd() + ).catch((err) => err); + expect(configError instanceof z.ZodError).to.equal(true); + expect(configError.errors[0].message).to.equal( + 'The option `i18n.redirectToDefaultLocale` has effects only when the `i18n.prefixDefaultLocale` is set to `true`. Remove the option `i18n.redirectToDefaultLocale`, or change the value of `i18n.prefixDefaultLocale` to `true`.' + ); + }); }); }); From eefdf810090a335ca7aadb841c0c7a9e74caa980 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Mon, 8 Jan 2024 11:57:32 +0000 Subject: [PATCH 04/23] docs --- packages/astro/src/@types/astro.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index ad4960c26739..e323fb2033cd 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -1501,6 +1501,9 @@ export interface AstroUserConfig { * @default `true` * @version 4.2.0 * @description + * + * When `false`, Astro doesn't do a redirect from `/` to `/`. This option comes into play only + * when `prefixDefaultLocale` is `true`. * */ redirectToDefaultLocale: boolean; From 6130aed455ca58070c59f5ce0f5a5e103ebfba7c Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Mon, 8 Jan 2024 12:00:25 +0000 Subject: [PATCH 05/23] changeset --- .changeset/cyan-grapes-suffer.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .changeset/cyan-grapes-suffer.md diff --git a/.changeset/cyan-grapes-suffer.md b/.changeset/cyan-grapes-suffer.md new file mode 100644 index 000000000000..e066f8c24048 --- /dev/null +++ b/.changeset/cyan-grapes-suffer.md @@ -0,0 +1,22 @@ +--- +"astro": minor +--- + +Adds a new property inside `i18n.routing`, called `redirectToDefaultLocale`. + +When `false`, Astro doesn't do a redirect from `/` to `/`. This option comes into play only +when `prefixDefaultLocale` is `true`. + +```js +// astro.config.mjs +export default defineConfig({ + i18n:{ + defaultLocale: "en", + locales: ["en", "fr"], + routing: { + prefixDefaultLocale: true, + redirectToDefaultLocale: false + } + } +}) +``` From 312ec61ef87de729e02ed1a25f0346a32a2e63de Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Thu, 11 Jan 2024 08:51:22 +0000 Subject: [PATCH 06/23] Update packages/astro/src/core/config/schema.ts Co-authored-by: Bjorn Lu --- packages/astro/src/core/config/schema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/astro/src/core/config/schema.ts b/packages/astro/src/core/config/schema.ts index e74d74ac439b..26bdc778d644 100644 --- a/packages/astro/src/core/config/schema.ts +++ b/packages/astro/src/core/config/schema.ts @@ -335,7 +335,7 @@ export const AstroConfigSchema = z.object({ }, { message: - 'The option `i18n.redirectToDefaultLocale` has effects only when the `i18n.prefixDefaultLocale` is set to `true`. Remove the option `i18n.redirectToDefaultLocale`, or change the value of `i18n.prefixDefaultLocale` to `true`.', + 'The option `i18n.redirectToDefaultLocale` is only useful when the `i18n.prefixDefaultLocale` is set to `true`. Remove the option `i18n.redirectToDefaultLocale`, or change its value to `true`.', } ) .transform((routing) => { From 12e139c96d0d778b205a0f7061e186a03be05af1 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Thu, 11 Jan 2024 12:55:27 +0000 Subject: [PATCH 07/23] chore: address feedback --- packages/astro/src/i18n/middleware.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/astro/src/i18n/middleware.ts b/packages/astro/src/i18n/middleware.ts index 502ee61da97e..2ec796a05ef3 100644 --- a/packages/astro/src/i18n/middleware.ts +++ b/packages/astro/src/i18n/middleware.ts @@ -2,7 +2,6 @@ import { appendForwardSlash, joinPaths } from '@astrojs/internal-helpers/path'; import type { Locales, MiddlewareHandler, RouteData, SSRManifest } from '../@types/astro.js'; import type { PipelineHookFunction } from '../core/pipeline.js'; import { getPathByLocale, normalizeTheLocale } from './index.js'; -import { shouldAppendForwardSlash } from '../core/build/util.js'; const routeDataSymbol = Symbol.for('astro.routeData'); @@ -72,7 +71,7 @@ export function createI18nMiddleware( // We return a 404 if: // - the current path isn't a root. e.g. / or / // - the URL doesn't contain a locale - const isRoot = url.pathname.startsWith(base) || url.pathname === '/'; + const isRoot = url.pathname === base + '/' || url.pathname === base; if (!(isRoot || pathnameHasLocale(url.pathname, i18n.locales))) { return new Response(null, { status: 404, From 5a864a1153a4550b55066ac0752e9d274fb27063 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Thu, 11 Jan 2024 12:58:13 +0000 Subject: [PATCH 08/23] fix test --- packages/astro/test/units/config/config-validate.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/astro/test/units/config/config-validate.test.js b/packages/astro/test/units/config/config-validate.test.js index 76fa57b4d5a6..477bc4bedac3 100644 --- a/packages/astro/test/units/config/config-validate.test.js +++ b/packages/astro/test/units/config/config-validate.test.js @@ -210,7 +210,7 @@ describe('Config Validation', () => { ).catch((err) => err); expect(configError instanceof z.ZodError).to.equal(true); expect(configError.errors[0].message).to.equal( - 'The option `i18n.redirectToDefaultLocale` has effects only when the `i18n.prefixDefaultLocale` is set to `true`. Remove the option `i18n.redirectToDefaultLocale`, or change the value of `i18n.prefixDefaultLocale` to `true`.' + 'The option `i18n.redirectToDefaultLocale` is only useful when the `i18n.prefixDefaultLocale` is set to `true`. Remove the option `i18n.redirectToDefaultLocale`, or change its value to `true`.' ); }); }); From 3bd5d5390bae43e8e8ad923a58fd84ea17ab0350 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Fri, 12 Jan 2024 14:18:57 +0000 Subject: [PATCH 09/23] Update .changeset/cyan-grapes-suffer.md Co-authored-by: Sarah Rainsberger --- .changeset/cyan-grapes-suffer.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.changeset/cyan-grapes-suffer.md b/.changeset/cyan-grapes-suffer.md index e066f8c24048..84833c42b9d3 100644 --- a/.changeset/cyan-grapes-suffer.md +++ b/.changeset/cyan-grapes-suffer.md @@ -2,10 +2,12 @@ "astro": minor --- -Adds a new property inside `i18n.routing`, called `redirectToDefaultLocale`. -When `false`, Astro doesn't do a redirect from `/` to `/`. This option comes into play only -when `prefixDefaultLocale` is `true`. +Adds a new `i18n.routing` config option `redirectToDefaultLocale` to disable automatic redirects of the root URL (`/`) to the default locale when `prefixDefaultLocale: true` is set. + +In projects where every route, including the default locale, is prefixed with `/[locale]/` path, this property allows you to control whether or not `src/pages/index.astro` should automatically redirect your site visitors from `/` to `/[defaultLocale]`. + +You can now opt out of this automatic redirection by setting `redirectToDefaultLocale: false`: ```js // astro.config.mjs From 81f9d9f39769e1eec81e4c390d26bbdccf975fd1 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Fri, 12 Jan 2024 14:19:03 +0000 Subject: [PATCH 10/23] Update packages/astro/src/@types/astro.ts Co-authored-by: Sarah Rainsberger --- packages/astro/src/@types/astro.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index e323fb2033cd..de653893d49f 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -1502,8 +1502,23 @@ export interface AstroUserConfig { * @version 4.2.0 * @description * - * When `false`, Astro doesn't do a redirect from `/` to `/`. This option comes into play only - * when `prefixDefaultLocale` is `true`. + * Configures whether or not the home URL (`/`) generated by `src/pages/astro.index` + * will redirect to `/[defaultLocale]` when `prefixDefaultLocale: true` is set. + * + * Set `redirectToDefaultLocale: false` to disable this automatic redirection at the root of your site: + * ```js + * // astro.config.mjs + * export default defineConfig({ + * i18n:{ + * defaultLocale: "en", + * locales: ["en", "fr"], + * routing: { + * prefixDefaultLocale: true, + * redirectToDefaultLocale: false + * } + * } + * }) + *``` * */ redirectToDefaultLocale: boolean; From fad30d51178c7f3e943361b12e20ee976324455c Mon Sep 17 00:00:00 2001 From: Bjorn Lu Date: Thu, 11 Jan 2024 21:35:12 +0800 Subject: [PATCH 11/23] Fix discord fetch code (#9663) --- scripts/notify/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/notify/index.js b/scripts/notify/index.js index 98d406cf01ea..7dd8a3751767 100755 --- a/scripts/notify/index.js +++ b/scripts/notify/index.js @@ -163,9 +163,9 @@ async function run() { } const content = await generateMessage(); - await fetch(JSON.stringify({ content }), { - url: `${process.env.DISCORD_WEBHOOK}?wait=true`, + await fetch(`${process.env.DISCORD_WEBHOOK}?wait=true`, { method: 'POST', + body: JSON.stringify({ content }), headers: { 'content-type': 'application/json', }, From 4e5f64f5a9017c80e233d064eb266085b281914f Mon Sep 17 00:00:00 2001 From: Martin Trapp <94928215+martrapp@users.noreply.github.com> Date: Thu, 11 Jan 2024 19:26:49 +0100 Subject: [PATCH 12/23] Force re-execution of Partytown's head snippet on view transitions (#9666) * Remove the header script before a view transition takes place to force a reload on the next page * Add changeset * Save another char --- .changeset/afraid-suits-beam.md | 5 +++++ packages/integrations/partytown/src/index.ts | 4 ++++ 2 files changed, 9 insertions(+) create mode 100644 .changeset/afraid-suits-beam.md diff --git a/.changeset/afraid-suits-beam.md b/.changeset/afraid-suits-beam.md new file mode 100644 index 000000000000..d93cde160563 --- /dev/null +++ b/.changeset/afraid-suits-beam.md @@ -0,0 +1,5 @@ +--- +"@astrojs/partytown": patch +--- + +Fixes an issue where Partytown scripts didn't execute after view transition diff --git a/packages/integrations/partytown/src/index.ts b/packages/integrations/partytown/src/index.ts index cfe1293c995d..77dcfd76c13c 100644 --- a/packages/integrations/partytown/src/index.ts +++ b/packages/integrations/partytown/src/index.ts @@ -21,6 +21,7 @@ export default function createPlugin(options?: PartytownOptions): AstroIntegrati let partytownSnippetHtml: string; const partytownEntrypoint = resolve('@builder.io/partytown/package.json'); const partytownLibDirectory = path.resolve(partytownEntrypoint, '../lib'); + const SELF_DESTRUCT_ON_VIEW_TRANSITION = `;((d,s)=>(s=d.currentScript,d.addEventListener('astro:before-swap',()=>s.remove(),{once:true})))(document);` return { name: '@astrojs/partytown', hooks: { @@ -32,6 +33,7 @@ export default function createPlugin(options?: PartytownOptions): AstroIntegrati debug: options?.config?.debug ?? command === 'dev', }; partytownSnippetHtml = partytownSnippet(partytownConfig); + partytownSnippetHtml += SELF_DESTRUCT_ON_VIEW_TRANSITION; injectScript('head-inline', partytownSnippetHtml); }, 'astro:server:setup': ({ server }) => { @@ -60,4 +62,6 @@ export default function createPlugin(options?: PartytownOptions): AstroIntegrati }, }, }; + + } From c24746e2a118583a51ce2c769458c03709ce1000 Mon Sep 17 00:00:00 2001 From: Martin Trapp Date: Thu, 11 Jan 2024 18:28:28 +0000 Subject: [PATCH 13/23] [ci] format --- packages/integrations/partytown/src/index.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/integrations/partytown/src/index.ts b/packages/integrations/partytown/src/index.ts index 77dcfd76c13c..85b6af764ac6 100644 --- a/packages/integrations/partytown/src/index.ts +++ b/packages/integrations/partytown/src/index.ts @@ -21,7 +21,7 @@ export default function createPlugin(options?: PartytownOptions): AstroIntegrati let partytownSnippetHtml: string; const partytownEntrypoint = resolve('@builder.io/partytown/package.json'); const partytownLibDirectory = path.resolve(partytownEntrypoint, '../lib'); - const SELF_DESTRUCT_ON_VIEW_TRANSITION = `;((d,s)=>(s=d.currentScript,d.addEventListener('astro:before-swap',()=>s.remove(),{once:true})))(document);` + const SELF_DESTRUCT_ON_VIEW_TRANSITION = `;((d,s)=>(s=d.currentScript,d.addEventListener('astro:before-swap',()=>s.remove(),{once:true})))(document);`; return { name: '@astrojs/partytown', hooks: { @@ -62,6 +62,4 @@ export default function createPlugin(options?: PartytownOptions): AstroIntegrati }, }, }; - - } From 92cca7e41820949ed4faeb478e6e9ff59e7dca74 Mon Sep 17 00:00:00 2001 From: Erika <3019731+Princesseuh@users.noreply.github.com> Date: Thu, 11 Jan 2024 13:29:59 -0500 Subject: [PATCH 14/23] fix(assets): Implement all hooks in the passthrough image service (#9668) * fix(assets): Implement all hooks in the passthrough image service * chore: changeset --- .changeset/long-mangos-walk.md | 5 +++++ packages/astro/src/assets/services/noop.ts | 5 +---- 2 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 .changeset/long-mangos-walk.md diff --git a/.changeset/long-mangos-walk.md b/.changeset/long-mangos-walk.md new file mode 100644 index 000000000000..efa85cb9afb0 --- /dev/null +++ b/.changeset/long-mangos-walk.md @@ -0,0 +1,5 @@ +--- +"astro": patch +--- + +Fix the passthrough image service not generating `srcset` values properly diff --git a/packages/astro/src/assets/services/noop.ts b/packages/astro/src/assets/services/noop.ts index 38d7dbdb65e2..fe6cc7c922e5 100644 --- a/packages/astro/src/assets/services/noop.ts +++ b/packages/astro/src/assets/services/noop.ts @@ -2,11 +2,8 @@ import { baseService, type LocalImageService } from './service.js'; // Empty service used for platforms that neither support Squoosh or Sharp. const noopService: LocalImageService = { + ...baseService, propertiesToHash: ['src'], - validateOptions: baseService.validateOptions, - getURL: baseService.getURL, - parseURL: baseService.parseURL, - getHTMLAttributes: baseService.getHTMLAttributes, async transform(inputBuffer, transformOptions) { return { data: inputBuffer, From 3e408a3a4db80dfbe9ba626aefee82f6a68ed734 Mon Sep 17 00:00:00 2001 From: Erika <3019731+Princesseuh@users.noreply.github.com> Date: Thu, 11 Jan 2024 14:03:45 -0500 Subject: [PATCH 15/23] refactor(toolbar): Rename every internal reference of overlay/plugins to toolbar/apps (#9647) * refactor(toolbar): Rename every internal reference of overlay/plugins to toolbar/apps * refactor: rename vite plugin * fix: update import * nit: add setting fallback --- ...ev-overlay.test.js => dev-toolbar.test.js} | 156 ++++++------ .../astro.config.mjs | 0 .../{dev-overlay => dev-toolbar}/package.json | 2 +- .../src/components/HelloWorld.tsx | 0 .../src/pages/audit-no-warning.astro | 0 .../src/pages/index.astro | 0 .../src/pages/tooltip-position.astro | 0 .../src/pages/xray-no-islands.astro | 0 packages/astro/src/@types/astro.ts | 69 ++--- packages/astro/src/core/create-vite.ts | 6 +- .../client/dev-overlay/ui-library/index.ts | 8 - .../plugins => dev-toolbar/apps}/astro.ts | 8 +- .../apps}/audit/a11y.ts | 0 .../apps}/audit/index.ts | 14 +- .../plugins => dev-toolbar/apps}/settings.ts | 18 +- .../apps}/utils/highlight.ts | 6 +- .../apps}/utils/icons.ts | 0 .../apps}/utils/window.ts | 0 .../plugins => dev-toolbar/apps}/xray.ts | 16 +- .../entrypoint.ts | 149 ++++++----- .../{dev-overlay => dev-toolbar}/settings.ts | 19 +- .../overlay.ts => dev-toolbar/toolbar.ts} | 239 +++++++++--------- .../ui-library/badge.ts | 2 +- .../ui-library/button.ts | 3 +- .../ui-library/card.ts | 2 +- .../ui-library/highlight.ts | 2 +- .../ui-library/icon.ts | 2 +- .../ui-library/icons.ts | 0 .../client/dev-toolbar/ui-library/index.ts | 8 + .../ui-library/toggle.ts | 2 +- .../ui-library/tooltip.ts | 6 +- .../ui-library/window.ts | 4 +- .../src/vite-plugin-astro-server/route.ts | 10 +- .../vite-plugin-dev-toolbar.ts} | 8 +- pnpm-lock.yaml | 2 +- 35 files changed, 374 insertions(+), 387 deletions(-) rename packages/astro/e2e/{dev-overlay.test.js => dev-toolbar.test.js} (50%) rename packages/astro/e2e/fixtures/{dev-overlay => dev-toolbar}/astro.config.mjs (100%) rename packages/astro/e2e/fixtures/{dev-overlay => dev-toolbar}/package.json (84%) rename packages/astro/e2e/fixtures/{dev-overlay => dev-toolbar}/src/components/HelloWorld.tsx (100%) rename packages/astro/e2e/fixtures/{dev-overlay => dev-toolbar}/src/pages/audit-no-warning.astro (100%) rename packages/astro/e2e/fixtures/{dev-overlay => dev-toolbar}/src/pages/index.astro (100%) rename packages/astro/e2e/fixtures/{dev-overlay => dev-toolbar}/src/pages/tooltip-position.astro (100%) rename packages/astro/e2e/fixtures/{dev-overlay => dev-toolbar}/src/pages/xray-no-islands.astro (100%) delete mode 100644 packages/astro/src/runtime/client/dev-overlay/ui-library/index.ts rename packages/astro/src/runtime/client/{dev-overlay/plugins => dev-toolbar/apps}/astro.ts (98%) rename packages/astro/src/runtime/client/{dev-overlay/plugins => dev-toolbar/apps}/audit/a11y.ts (100%) rename packages/astro/src/runtime/client/{dev-overlay/plugins => dev-toolbar/apps}/audit/index.ts (95%) rename packages/astro/src/runtime/client/{dev-overlay/plugins => dev-toolbar/apps}/settings.ts (88%) rename packages/astro/src/runtime/client/{dev-overlay/plugins => dev-toolbar/apps}/utils/highlight.ts (93%) rename packages/astro/src/runtime/client/{dev-overlay/plugins => dev-toolbar/apps}/utils/icons.ts (100%) rename packages/astro/src/runtime/client/{dev-overlay/plugins => dev-toolbar/apps}/utils/window.ts (100%) rename packages/astro/src/runtime/client/{dev-overlay/plugins => dev-toolbar/apps}/xray.ts (94%) rename packages/astro/src/runtime/client/{dev-overlay => dev-toolbar}/entrypoint.ts (60%) rename packages/astro/src/runtime/client/{dev-overlay => dev-toolbar}/settings.ts (57%) rename packages/astro/src/runtime/client/{dev-overlay/overlay.ts => dev-toolbar/toolbar.ts} (58%) rename packages/astro/src/runtime/client/{dev-overlay => dev-toolbar}/ui-library/badge.ts (96%) rename packages/astro/src/runtime/client/{dev-overlay => dev-toolbar}/ui-library/button.ts (94%) rename packages/astro/src/runtime/client/{dev-overlay => dev-toolbar}/ui-library/card.ts (96%) rename packages/astro/src/runtime/client/{dev-overlay => dev-toolbar}/ui-library/highlight.ts (96%) rename packages/astro/src/runtime/client/{dev-overlay => dev-toolbar}/ui-library/icon.ts (95%) rename packages/astro/src/runtime/client/{dev-overlay => dev-toolbar}/ui-library/icons.ts (100%) create mode 100644 packages/astro/src/runtime/client/dev-toolbar/ui-library/index.ts rename packages/astro/src/runtime/client/{dev-overlay => dev-toolbar}/ui-library/toggle.ts (96%) rename packages/astro/src/runtime/client/{dev-overlay => dev-toolbar}/ui-library/tooltip.ts (96%) rename packages/astro/src/runtime/client/{dev-overlay => dev-toolbar}/ui-library/window.ts (97%) rename packages/astro/src/{vite-plugin-dev-overlay/vite-plugin-dev-overlay.ts => vite-plugin-dev-toolbar/vite-plugin-dev-toolbar.ts} (78%) diff --git a/packages/astro/e2e/dev-overlay.test.js b/packages/astro/e2e/dev-toolbar.test.js similarity index 50% rename from packages/astro/e2e/dev-overlay.test.js rename to packages/astro/e2e/dev-toolbar.test.js index 0a0fca6dd4ce..107c80b08f2a 100644 --- a/packages/astro/e2e/dev-overlay.test.js +++ b/packages/astro/e2e/dev-toolbar.test.js @@ -2,7 +2,7 @@ import { expect } from '@playwright/test'; import { testFactory } from './test-utils.js'; const test = testFactory({ - root: './fixtures/dev-overlay/', + root: './fixtures/dev-toolbar/', }); let devServer; @@ -15,54 +15,50 @@ test.afterAll(async () => { await devServer.stop(); }); -test.describe('Dev Overlay', () => { - test('dev overlay exists in the page', async ({ page, astro }) => { +test.describe('Dev Toolbar', () => { + test('dev toolbar exists in the page', async ({ page, astro }) => { await page.goto(astro.resolveUrl('/')); const devToolbar = page.locator('astro-dev-toolbar'); await expect(devToolbar).toHaveCount(1); }); - test('shows plugin name on hover', async ({ page, astro }) => { + test('shows app name on hover', async ({ page, astro }) => { await page.goto(astro.resolveUrl('/')); - const overlay = page.locator('astro-dev-toolbar'); - const pluginButton = overlay.locator('button[data-plugin-id="astro"]'); - const pluginButtonTooltip = pluginButton.locator('.item-tooltip'); - await pluginButton.hover(); + const toolbar = page.locator('astro-dev-toolbar'); + const appButton = toolbar.locator('button[data-app-id="astro"]'); + const appButtonTooltip = appButton.locator('.item-tooltip'); + await appButton.hover(); - await expect(pluginButtonTooltip).toBeVisible(); + await expect(appButtonTooltip).toBeVisible(); }); - test('can open Astro plugin', async ({ page, astro }) => { + test('can open Astro app', async ({ page, astro }) => { await page.goto(astro.resolveUrl('/')); - const overlay = page.locator('astro-dev-toolbar'); - const pluginButton = overlay.locator('button[data-plugin-id="astro"]'); - await pluginButton.click(); + const toolbar = page.locator('astro-dev-toolbar'); + const appButton = toolbar.locator('button[data-app-id="astro"]'); + await appButton.click(); - const astroPluginCanvas = overlay.locator( - 'astro-dev-toolbar-plugin-canvas[data-plugin-id="astro"]' - ); - const astroWindow = astroPluginCanvas.locator('astro-dev-toolbar-window'); + const astroAppCanvas = toolbar.locator('astro-dev-toolbar-app-canvas[data-app-id="astro"]'); + const astroWindow = astroAppCanvas.locator('astro-dev-toolbar-window'); await expect(astroWindow).toHaveCount(1); await expect(astroWindow).toBeVisible(); - // Toggle plugin off - await pluginButton.click(); + // Toggle app off + await appButton.click(); await expect(astroWindow).not.toBeVisible(); }); test('xray shows highlights and tooltips', async ({ page, astro }) => { await page.goto(astro.resolveUrl('/')); - const overlay = page.locator('astro-dev-toolbar'); - const pluginButton = overlay.locator('button[data-plugin-id="astro:xray"]'); - await pluginButton.click(); + const toolbar = page.locator('astro-dev-toolbar'); + const appButton = toolbar.locator('button[data-app-id="astro:xray"]'); + await appButton.click(); - const xrayCanvas = overlay.locator( - 'astro-dev-toolbar-plugin-canvas[data-plugin-id="astro:xray"]' - ); + const xrayCanvas = toolbar.locator('astro-dev-toolbar-app-canvas[data-app-id="astro:xray"]'); const xrayHighlight = xrayCanvas.locator('astro-dev-toolbar-highlight'); await expect(xrayHighlight).toBeVisible(); @@ -70,8 +66,8 @@ test.describe('Dev Overlay', () => { const xrayHighlightTooltip = xrayHighlight.locator('astro-dev-toolbar-tooltip'); await expect(xrayHighlightTooltip).toBeVisible(); - // Toggle plugin off - await pluginButton.click(); + // Toggle app off + await appButton.click(); await expect(xrayHighlight).not.toBeVisible(); await expect(xrayHighlightTooltip).not.toBeVisible(); }); @@ -79,13 +75,11 @@ test.describe('Dev Overlay', () => { test('xray shows no islands message when there are none', async ({ page, astro }) => { await page.goto(astro.resolveUrl('/xray-no-islands')); - const overlay = page.locator('astro-dev-toolbar'); - const pluginButton = overlay.locator('button[data-plugin-id="astro:xray"]'); - await pluginButton.click(); + const toolbar = page.locator('astro-dev-toolbar'); + const appButton = toolbar.locator('button[data-app-id="astro:xray"]'); + await appButton.click(); - const xrayCanvas = overlay.locator( - 'astro-dev-toolbar-plugin-canvas[data-plugin-id="astro:xray"]' - ); + const xrayCanvas = toolbar.locator('astro-dev-toolbar-app-canvas[data-app-id="astro:xray"]'); const auditHighlight = xrayCanvas.locator('astro-dev-toolbar-highlight'); await expect(auditHighlight).not.toBeVisible(); @@ -99,13 +93,11 @@ test.describe('Dev Overlay', () => { test('audit shows higlights and tooltips', async ({ page, astro }) => { await page.goto(astro.resolveUrl('/')); - const overlay = page.locator('astro-dev-toolbar'); - const pluginButton = overlay.locator('button[data-plugin-id="astro:audit"]'); - await pluginButton.click(); + const toolbar = page.locator('astro-dev-toolbar'); + const appButton = toolbar.locator('button[data-app-id="astro:audit"]'); + await appButton.click(); - const auditCanvas = overlay.locator( - 'astro-dev-toolbar-plugin-canvas[data-plugin-id="astro:audit"]' - ); + const auditCanvas = toolbar.locator('astro-dev-toolbar-app-canvas[data-app-id="astro:audit"]'); const auditHighlight = auditCanvas.locator('astro-dev-toolbar-highlight'); await expect(auditHighlight).toBeVisible(); @@ -113,8 +105,8 @@ test.describe('Dev Overlay', () => { const auditHighlightTooltip = auditHighlight.locator('astro-dev-toolbar-tooltip'); await expect(auditHighlightTooltip).toBeVisible(); - // Toggle plugin off - await pluginButton.click(); + // Toggle app off + await appButton.click(); await expect(auditHighlight).not.toBeVisible(); await expect(auditHighlightTooltip).not.toBeVisible(); }); @@ -122,13 +114,11 @@ test.describe('Dev Overlay', () => { test('audit shows no issues message when there are no issues', async ({ page, astro }) => { await page.goto(astro.resolveUrl('/audit-no-warning')); - const overlay = page.locator('astro-dev-toolbar'); - const pluginButton = overlay.locator('button[data-plugin-id="astro:audit"]'); - await pluginButton.click(); + const toolbar = page.locator('astro-dev-toolbar'); + const appButton = toolbar.locator('button[data-app-id="astro:audit"]'); + await appButton.click(); - const auditCanvas = overlay.locator( - 'astro-dev-toolbar-plugin-canvas[data-plugin-id="astro:audit"]' - ); + const auditCanvas = toolbar.locator('astro-dev-toolbar-app-canvas[data-app-id="astro:audit"]'); const auditHighlight = auditCanvas.locator('astro-dev-toolbar-highlight'); await expect(auditHighlight).not.toBeVisible(); @@ -142,13 +132,11 @@ test.describe('Dev Overlay', () => { test('adjusts tooltip position if off-screen', async ({ page, astro }) => { await page.goto(astro.resolveUrl('/tooltip-position')); - const overlay = page.locator('astro-dev-toolbar'); - const pluginButton = overlay.locator('button[data-plugin-id="astro:audit"]'); - await pluginButton.click(); + const toolbar = page.locator('astro-dev-toolbar'); + const appButton = toolbar.locator('button[data-app-id="astro:audit"]'); + await appButton.click(); - const auditCanvas = overlay.locator( - 'astro-dev-toolbar-plugin-canvas[data-plugin-id="astro:audit"]' - ); + const auditCanvas = toolbar.locator('astro-dev-toolbar-app-canvas[data-app-id="astro:audit"]'); const auditHighlights = auditCanvas.locator('astro-dev-toolbar-highlight'); for (const highlight of await auditHighlights.all()) { await expect(highlight).toBeVisible(); @@ -165,68 +153,66 @@ test.describe('Dev Overlay', () => { } }); - test('can open Settings plugin', async ({ page, astro }) => { + test('can open Settings app', async ({ page, astro }) => { await page.goto(astro.resolveUrl('/')); - const overlay = page.locator('astro-dev-toolbar'); - const pluginButton = overlay.locator('button[data-plugin-id="astro:settings"]'); - await pluginButton.click(); + const toolbar = page.locator('astro-dev-toolbar'); + const appButton = toolbar.locator('button[data-app-id="astro:settings"]'); + await appButton.click(); - const settingsPluginCanvas = overlay.locator( - 'astro-dev-toolbar-plugin-canvas[data-plugin-id="astro:settings"]' + const settingsAppCanvas = toolbar.locator( + 'astro-dev-toolbar-app-canvas[data-app-id="astro:settings"]' ); - const settingsWindow = settingsPluginCanvas.locator('astro-dev-toolbar-window'); + const settingsWindow = settingsAppCanvas.locator('astro-dev-toolbar-window'); await expect(settingsWindow).toHaveCount(1); await expect(settingsWindow).toBeVisible(); - // Toggle plugin off - await pluginButton.click(); + // Toggle app off + await appButton.click(); await expect(settingsWindow).not.toBeVisible(); }); - test('Opening a plugin closes the currently opened plugin', async ({ page, astro }) => { + test('Opening a app closes the currently opened app', async ({ page, astro }) => { await page.goto(astro.resolveUrl('/')); - const overlay = page.locator('astro-dev-toolbar'); - let pluginButton = overlay.locator('button[data-plugin-id="astro:settings"]'); - await pluginButton.click(); + const toolbar = page.locator('astro-dev-toolbar'); + let appButton = toolbar.locator('button[data-app-id="astro:settings"]'); + await appButton.click(); - const settingsPluginCanvas = overlay.locator( - 'astro-dev-toolbar-plugin-canvas[data-plugin-id="astro:settings"]' + const settingsAppCanvas = toolbar.locator( + 'astro-dev-toolbar-app-canvas[data-app-id="astro:settings"]' ); - const settingsWindow = settingsPluginCanvas.locator('astro-dev-toolbar-window'); + const settingsWindow = settingsAppCanvas.locator('astro-dev-toolbar-window'); await expect(settingsWindow).toHaveCount(1); await expect(settingsWindow).toBeVisible(); - // Click the astro plugin - pluginButton = overlay.locator('button[data-plugin-id="astro"]'); - await pluginButton.click(); + // Click the astro app + appButton = toolbar.locator('button[data-app-id="astro"]'); + await appButton.click(); - const astroPluginCanvas = overlay.locator( - 'astro-dev-toolbar-plugin-canvas[data-plugin-id="astro"]' - ); - const astroWindow = astroPluginCanvas.locator('astro-dev-toolbar-window'); + const astroAppCanvas = toolbar.locator('astro-dev-toolbar-app-canvas[data-app-id="astro"]'); + const astroWindow = astroAppCanvas.locator('astro-dev-toolbar-window'); await expect(astroWindow).toHaveCount(1); await expect(astroWindow).toBeVisible(); await expect(settingsWindow).not.toBeVisible(); }); - test('Settings plugin contains message on disabling the overlay', async ({ page, astro }) => { + test('Settings app contains message on disabling the toolbar', async ({ page, astro }) => { await page.goto(astro.resolveUrl('/')); - const overlay = page.locator('astro-dev-toolbar'); - let pluginButton = overlay.locator('button[data-plugin-id="astro:settings"]'); - await pluginButton.click(); + const toolbar = page.locator('astro-dev-toolbar'); + let appButton = toolbar.locator('button[data-app-id="astro:settings"]'); + await appButton.click(); - const settingsPluginCanvas = overlay.locator( - 'astro-dev-toolbar-plugin-canvas[data-plugin-id="astro:settings"]' + const settingsAppCanvas = toolbar.locator( + 'astro-dev-toolbar-app-canvas[data-app-id="astro:settings"]' ); - const settingsWindow = settingsPluginCanvas.locator('astro-dev-toolbar-window'); + const settingsWindow = settingsAppCanvas.locator('astro-dev-toolbar-window'); await expect(settingsWindow).toHaveCount(1); await expect(settingsWindow).toBeVisible(); - const hideOverlay = settingsWindow.getByRole('heading', { name: 'Hide toolbar' }); - await expect(hideOverlay).toBeVisible(); + const hideToolbar = settingsWindow.getByRole('heading', { name: 'Hide toolbar' }); + await expect(hideToolbar).toBeVisible(); }); }); diff --git a/packages/astro/e2e/fixtures/dev-overlay/astro.config.mjs b/packages/astro/e2e/fixtures/dev-toolbar/astro.config.mjs similarity index 100% rename from packages/astro/e2e/fixtures/dev-overlay/astro.config.mjs rename to packages/astro/e2e/fixtures/dev-toolbar/astro.config.mjs diff --git a/packages/astro/e2e/fixtures/dev-overlay/package.json b/packages/astro/e2e/fixtures/dev-toolbar/package.json similarity index 84% rename from packages/astro/e2e/fixtures/dev-overlay/package.json rename to packages/astro/e2e/fixtures/dev-toolbar/package.json index 6cd05404d6a2..611f6aae4ede 100644 --- a/packages/astro/e2e/fixtures/dev-overlay/package.json +++ b/packages/astro/e2e/fixtures/dev-toolbar/package.json @@ -1,5 +1,5 @@ { - "name": "@e2e/dev-overlay", + "name": "@e2e/dev-toolbar", "version": "0.0.0", "private": true, "dependencies": { diff --git a/packages/astro/e2e/fixtures/dev-overlay/src/components/HelloWorld.tsx b/packages/astro/e2e/fixtures/dev-toolbar/src/components/HelloWorld.tsx similarity index 100% rename from packages/astro/e2e/fixtures/dev-overlay/src/components/HelloWorld.tsx rename to packages/astro/e2e/fixtures/dev-toolbar/src/components/HelloWorld.tsx diff --git a/packages/astro/e2e/fixtures/dev-overlay/src/pages/audit-no-warning.astro b/packages/astro/e2e/fixtures/dev-toolbar/src/pages/audit-no-warning.astro similarity index 100% rename from packages/astro/e2e/fixtures/dev-overlay/src/pages/audit-no-warning.astro rename to packages/astro/e2e/fixtures/dev-toolbar/src/pages/audit-no-warning.astro diff --git a/packages/astro/e2e/fixtures/dev-overlay/src/pages/index.astro b/packages/astro/e2e/fixtures/dev-toolbar/src/pages/index.astro similarity index 100% rename from packages/astro/e2e/fixtures/dev-overlay/src/pages/index.astro rename to packages/astro/e2e/fixtures/dev-toolbar/src/pages/index.astro diff --git a/packages/astro/e2e/fixtures/dev-overlay/src/pages/tooltip-position.astro b/packages/astro/e2e/fixtures/dev-toolbar/src/pages/tooltip-position.astro similarity index 100% rename from packages/astro/e2e/fixtures/dev-overlay/src/pages/tooltip-position.astro rename to packages/astro/e2e/fixtures/dev-toolbar/src/pages/tooltip-position.astro diff --git a/packages/astro/e2e/fixtures/dev-overlay/src/pages/xray-no-islands.astro b/packages/astro/e2e/fixtures/dev-toolbar/src/pages/xray-no-islands.astro similarity index 100% rename from packages/astro/e2e/fixtures/dev-overlay/src/pages/xray-no-islands.astro rename to packages/astro/e2e/fixtures/dev-toolbar/src/pages/xray-no-islands.astro diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index de653893d49f..9561971ed29f 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -21,18 +21,18 @@ import type { TSConfig } from '../core/config/tsconfig.js'; import type { AstroCookies } from '../core/cookies/index.js'; import type { AstroIntegrationLogger, Logger, LoggerLevel } from '../core/logger/core.js'; import type { AstroPreferences } from '../preferences/index.js'; -import type { AstroDevOverlay, DevOverlayCanvas } from '../runtime/client/dev-overlay/overlay.js'; -import type { Icon } from '../runtime/client/dev-overlay/ui-library/icons.js'; +import type { AstroDevToolbar, DevToolbarCanvas } from '../runtime/client/dev-toolbar/toolbar.js'; +import type { Icon } from '../runtime/client/dev-toolbar/ui-library/icons.js'; import type { - DevOverlayBadge, - DevOverlayButton, - DevOverlayCard, - DevOverlayHighlight, - DevOverlayIcon, - DevOverlayToggle, - DevOverlayTooltip, - DevOverlayWindow, -} from '../runtime/client/dev-overlay/ui-library/index.js'; + DevToolbarBadge, + DevToolbarButton, + DevToolbarCard, + DevToolbarHighlight, + DevToolbarIcon, + DevToolbarToggle, + DevToolbarTooltip, + DevToolbarWindow, +} from '../runtime/client/dev-toolbar/ui-library/index.js'; import type { AstroComponentFactory, AstroComponentInstance } from '../runtime/server/index.js'; import type { DeepPartial, OmitIndexSignature, Simplify } from '../type-utils.js'; import type { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from './../core/constants.js'; @@ -2360,6 +2360,7 @@ export interface AstroIntegration { addClientDirective: (directive: ClientDirectiveConfig) => void; /** * @deprecated Use `addDevToolbarApp` instead. + * TODO: Fully remove in Astro 5.0 */ addDevOverlayPlugin: (entrypoint: string) => void; addDevToolbarApp: (entrypoint: string) => void; @@ -2627,11 +2628,12 @@ export interface DevToolbarApp { beforeTogglingOff?(canvas: ShadowRoot): boolean | Promise; } +// TODO: Remove in Astro 5.0 export type DevOverlayPlugin = DevToolbarApp; -export type DevOverlayMetadata = Window & +export type DevToolbarMetadata = Window & typeof globalThis & { - __astro_dev_overlay__: { + __astro_dev_toolbar__: { root: string; version: string; debugInfo: string; @@ -2640,27 +2642,28 @@ export type DevOverlayMetadata = Window & declare global { interface HTMLElementTagNameMap { - 'astro-dev-toolbar': AstroDevOverlay; - 'astro-dev-toolbar-window': DevOverlayWindow; - 'astro-dev-toolbar-plugin-canvas': DevOverlayCanvas; - 'astro-dev-toolbar-tooltip': DevOverlayTooltip; - 'astro-dev-toolbar-highlight': DevOverlayHighlight; - 'astro-dev-toolbar-toggle': DevOverlayToggle; - 'astro-dev-toolbar-badge': DevOverlayBadge; - 'astro-dev-toolbar-button': DevOverlayButton; - 'astro-dev-toolbar-icon': DevOverlayIcon; - 'astro-dev-toolbar-card': DevOverlayCard; + 'astro-dev-toolbar': AstroDevToolbar; + 'astro-dev-toolbar-window': DevToolbarWindow; + 'astro-dev-toolbar-app-canvas': DevToolbarCanvas; + 'astro-dev-toolbar-tooltip': DevToolbarTooltip; + 'astro-dev-toolbar-highlight': DevToolbarHighlight; + 'astro-dev-toolbar-toggle': DevToolbarToggle; + 'astro-dev-toolbar-badge': DevToolbarBadge; + 'astro-dev-toolbar-button': DevToolbarButton; + 'astro-dev-toolbar-icon': DevToolbarIcon; + 'astro-dev-toolbar-card': DevToolbarCard; // Deprecated names - 'astro-dev-overlay': AstroDevOverlay; - 'astro-dev-overlay-window': DevOverlayWindow; - 'astro-dev-overlay-plugin-canvas': DevOverlayCanvas; - 'astro-dev-overlay-tooltip': DevOverlayTooltip; - 'astro-dev-overlay-highlight': DevOverlayHighlight; - 'astro-dev-overlay-toggle': DevOverlayToggle; - 'astro-dev-overlay-badge': DevOverlayBadge; - 'astro-dev-overlay-button': DevOverlayButton; - 'astro-dev-overlay-icon': DevOverlayIcon; - 'astro-dev-overlay-card': DevOverlayCard; + // TODO: Remove in Astro 5.0 + 'astro-dev-overlay': AstroDevToolbar; + 'astro-dev-overlay-window': DevToolbarWindow; + 'astro-dev-overlay-plugin-canvas': DevToolbarCanvas; + 'astro-dev-overlay-tooltip': DevToolbarTooltip; + 'astro-dev-overlay-highlight': DevToolbarHighlight; + 'astro-dev-overlay-toggle': DevToolbarToggle; + 'astro-dev-overlay-badge': DevToolbarBadge; + 'astro-dev-overlay-button': DevToolbarButton; + 'astro-dev-overlay-icon': DevToolbarIcon; + 'astro-dev-overlay-card': DevToolbarCard; } } diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index 4ce605b9f52d..99082b830419 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -16,8 +16,9 @@ import astroPostprocessVitePlugin from '../vite-plugin-astro-postprocess/index.j import { vitePluginAstroServer } from '../vite-plugin-astro-server/index.js'; import astroVitePlugin from '../vite-plugin-astro/index.js'; import configAliasVitePlugin from '../vite-plugin-config-alias/index.js'; -import astroDevOverlay from '../vite-plugin-dev-overlay/vite-plugin-dev-overlay.js'; +import astroDevToolbar from '../vite-plugin-dev-toolbar/vite-plugin-dev-toolbar.js'; import envVitePlugin from '../vite-plugin-env/index.js'; +import vitePluginFileURL from '../vite-plugin-fileurl/index.js'; import astroHeadPlugin from '../vite-plugin-head/index.js'; import htmlVitePlugin from '../vite-plugin-html/index.js'; import { astroInjectEnvTsPlugin } from '../vite-plugin-inject-env-ts/index.js'; @@ -33,7 +34,6 @@ import type { Logger } from './logger/core.js'; import { createViteLogger } from './logger/vite.js'; import { vitePluginMiddleware } from './middleware/vite-plugin.js'; import { joinPaths } from './path.js'; -import vitePluginFileURL from '../vite-plugin-fileurl/index.js'; interface CreateViteOptions { settings: AstroSettings; @@ -141,7 +141,7 @@ export async function createVite( astroAssetsPlugin({ settings, logger, mode }), astroPrefetch({ settings }), astroTransitions({ settings }), - astroDevOverlay({ settings, logger }), + astroDevToolbar({ settings, logger }), vitePluginFileURL({}), !!settings.config.i18n && astroInternationalization({ settings }), ], diff --git a/packages/astro/src/runtime/client/dev-overlay/ui-library/index.ts b/packages/astro/src/runtime/client/dev-overlay/ui-library/index.ts deleted file mode 100644 index a9c039a417ff..000000000000 --- a/packages/astro/src/runtime/client/dev-overlay/ui-library/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export { DevOverlayBadge } from './badge.js'; -export { DevOverlayButton } from './button.js'; -export { DevOverlayCard } from './card.js'; -export { DevOverlayHighlight } from './highlight.js'; -export { DevOverlayIcon } from './icon.js'; -export { DevOverlayToggle } from './toggle.js'; -export { DevOverlayTooltip } from './tooltip.js'; -export { DevOverlayWindow } from './window.js'; diff --git a/packages/astro/src/runtime/client/dev-overlay/plugins/astro.ts b/packages/astro/src/runtime/client/dev-toolbar/apps/astro.ts similarity index 98% rename from packages/astro/src/runtime/client/dev-overlay/plugins/astro.ts rename to packages/astro/src/runtime/client/dev-toolbar/apps/astro.ts index 3321b995346a..bbc52d12ba8f 100644 --- a/packages/astro/src/runtime/client/dev-overlay/plugins/astro.ts +++ b/packages/astro/src/runtime/client/dev-toolbar/apps/astro.ts @@ -1,4 +1,4 @@ -import type { DevOverlayMetadata, DevOverlayPlugin } from '../../../../@types/astro.js'; +import type { DevToolbarApp, DevToolbarMetadata } from '../../../../@types/astro.js'; import { isDefinedIcon, type Icon } from '../ui-library/icons.js'; import { colorForIntegration, iconForIntegration } from './utils/icons.js'; import { createWindowElement } from './utils/window.js'; @@ -321,7 +321,7 @@ export default {
${astroLogo} ${ - (window as DevOverlayMetadata).__astro_dev_overlay__.version + (window as DevToolbarMetadata).__astro_dev_toolbar__.version }
Copy debug info @@ -360,7 +360,7 @@ export default { copyDebugButton?.addEventListener('click', () => { navigator.clipboard.writeText( - '```\n' + (window as DevOverlayMetadata).__astro_dev_overlay__.debugInfo + '\n```' + '```\n' + (window as DevToolbarMetadata).__astro_dev_toolbar__.debugInfo + '\n```' ); copyDebugButton.textContent = 'Copied to clipboard!'; @@ -436,4 +436,4 @@ export default { integrationList.append(fragment); } }, -} satisfies DevOverlayPlugin; +} satisfies DevToolbarApp; diff --git a/packages/astro/src/runtime/client/dev-overlay/plugins/audit/a11y.ts b/packages/astro/src/runtime/client/dev-toolbar/apps/audit/a11y.ts similarity index 100% rename from packages/astro/src/runtime/client/dev-overlay/plugins/audit/a11y.ts rename to packages/astro/src/runtime/client/dev-toolbar/apps/audit/a11y.ts diff --git a/packages/astro/src/runtime/client/dev-overlay/plugins/audit/index.ts b/packages/astro/src/runtime/client/dev-toolbar/apps/audit/index.ts similarity index 95% rename from packages/astro/src/runtime/client/dev-overlay/plugins/audit/index.ts rename to packages/astro/src/runtime/client/dev-toolbar/apps/audit/index.ts index 82f348a95aae..639e16269cf4 100644 --- a/packages/astro/src/runtime/client/dev-overlay/plugins/audit/index.ts +++ b/packages/astro/src/runtime/client/dev-toolbar/apps/audit/index.ts @@ -1,5 +1,5 @@ -import type { DevOverlayMetadata, DevOverlayPlugin } from '../../../../../@types/astro.js'; -import type { DevOverlayHighlight } from '../../ui-library/highlight.js'; +import type { DevToolbarApp, DevToolbarMetadata } from '../../../../../@types/astro.js'; +import type { DevToolbarHighlight } from '../../ui-library/highlight.js'; import { attachTooltipToHighlight, createHighlight, @@ -49,7 +49,7 @@ export default { name: 'Audit', icon: icon, async init(canvas, eventTarget) { - let audits: { highlightElement: DevOverlayHighlight; auditedElement: HTMLElement }[] = []; + let audits: { highlightElement: DevToolbarHighlight; auditedElement: HTMLElement }[] = []; await lint(); @@ -62,7 +62,7 @@ export default { if (!target.closest) return; if (target.closest('astro-dev-toolbar')) return; eventTarget.dispatchEvent( - new CustomEvent('toggle-plugin', { + new CustomEvent('toggle-app', { detail: { state: false, }, @@ -167,7 +167,7 @@ export default { if (noAuditBlock) { const devOverlayRect = document .querySelector('astro-dev-toolbar') - ?.shadowRoot.querySelector('#dev-overlay') + ?.shadowRoot.querySelector('#dev-toolbar-root') ?.getBoundingClientRect(); noAuditBlock.style.top = `${ @@ -240,7 +240,7 @@ export default { tooltip.sections.push({ content: elementFileWithPosition.slice( - (window as DevOverlayMetadata).__astro_dev_overlay__.root.length - 1 // We want to keep the final slash, so minus one. + (window as DevToolbarMetadata).__astro_dev_toolbar__.root.length - 1 // We want to keep the final slash, so minus one. ), clickDescription: 'Click to go to file', async clickAction() { @@ -263,4 +263,4 @@ export default { .replace(/'/g, '''); } }, -} satisfies DevOverlayPlugin; +} satisfies DevToolbarApp; diff --git a/packages/astro/src/runtime/client/dev-overlay/plugins/settings.ts b/packages/astro/src/runtime/client/dev-toolbar/apps/settings.ts similarity index 88% rename from packages/astro/src/runtime/client/dev-overlay/plugins/settings.ts rename to packages/astro/src/runtime/client/dev-toolbar/apps/settings.ts index dc5fe4ae3637..7e7fa5e3a594 100644 --- a/packages/astro/src/runtime/client/dev-overlay/plugins/settings.ts +++ b/packages/astro/src/runtime/client/dev-toolbar/apps/settings.ts @@ -1,4 +1,4 @@ -import type { DevOverlayPlugin } from '../../../../@types/astro.js'; +import type { DevToolbarApp } from '../../../../@types/astro.js'; import { settings, type Settings } from '../settings.js'; import { createWindowElement } from './utils/window.js'; @@ -15,24 +15,24 @@ const settingsRows = [ name: 'Disable notifications', description: 'Hide notification badges in the toolbar.', input: 'checkbox', - settingKey: 'disablePluginNotification', + settingKey: 'disableAppNotification', changeEvent: (evt: Event) => { if (evt.currentTarget instanceof HTMLInputElement) { - const devOverlay = document.querySelector('astro-dev-toolbar'); + const devToolbar = document.querySelector('astro-dev-toolbar'); - if (devOverlay) { - devOverlay.setNotificationVisible(!evt.currentTarget.checked); + if (devToolbar) { + devToolbar.setNotificationVisible(!evt.currentTarget.checked); } - settings.updateSetting('disablePluginNotification', evt.currentTarget.checked); + settings.updateSetting('disableAppNotification', evt.currentTarget.checked); const action = evt.currentTarget.checked ? 'disabled' : 'enabled'; - settings.log(`Plugin notification badges ${action}`); + settings.log(`App notification badges ${action}`); } }, }, { name: 'Verbose logging', - description: 'Logs dev overlay events in the browser console.', + description: 'Logs dev toolbar events in the browser console.', input: 'checkbox', settingKey: 'verbose', changeEvent: (evt: Event) => { @@ -168,4 +168,4 @@ export default { } } }, -} satisfies DevOverlayPlugin; +} satisfies DevToolbarApp; diff --git a/packages/astro/src/runtime/client/dev-overlay/plugins/utils/highlight.ts b/packages/astro/src/runtime/client/dev-toolbar/apps/utils/highlight.ts similarity index 93% rename from packages/astro/src/runtime/client/dev-overlay/plugins/utils/highlight.ts rename to packages/astro/src/runtime/client/dev-toolbar/apps/utils/highlight.ts index 726905b71d4b..1ceb1f4e6c93 100644 --- a/packages/astro/src/runtime/client/dev-overlay/plugins/utils/highlight.ts +++ b/packages/astro/src/runtime/client/dev-toolbar/apps/utils/highlight.ts @@ -1,4 +1,4 @@ -import type { DevOverlayHighlight } from '../../ui-library/highlight.js'; +import type { DevToolbarHighlight } from '../../ui-library/highlight.js'; import type { Icon } from '../../ui-library/icons.js'; export function createHighlight(rect: DOMRect, icon?: Icon) { @@ -33,7 +33,7 @@ export function getElementsPositionInDocument(el: Element) { }; } -export function positionHighlight(highlight: DevOverlayHighlight, rect: DOMRect) { +export function positionHighlight(highlight: DevToolbarHighlight, rect: DOMRect) { highlight.style.display = 'block'; // If the highlight is fixed, don't position based on scroll const scrollY = highlight.style.position === 'fixed' ? 0 : window.scrollY; @@ -45,7 +45,7 @@ export function positionHighlight(highlight: DevOverlayHighlight, rect: DOMRect) } export function attachTooltipToHighlight( - highlight: DevOverlayHighlight, + highlight: DevToolbarHighlight, tooltip: HTMLElement, originalElement: Element ) { diff --git a/packages/astro/src/runtime/client/dev-overlay/plugins/utils/icons.ts b/packages/astro/src/runtime/client/dev-toolbar/apps/utils/icons.ts similarity index 100% rename from packages/astro/src/runtime/client/dev-overlay/plugins/utils/icons.ts rename to packages/astro/src/runtime/client/dev-toolbar/apps/utils/icons.ts diff --git a/packages/astro/src/runtime/client/dev-overlay/plugins/utils/window.ts b/packages/astro/src/runtime/client/dev-toolbar/apps/utils/window.ts similarity index 100% rename from packages/astro/src/runtime/client/dev-overlay/plugins/utils/window.ts rename to packages/astro/src/runtime/client/dev-toolbar/apps/utils/window.ts diff --git a/packages/astro/src/runtime/client/dev-overlay/plugins/xray.ts b/packages/astro/src/runtime/client/dev-toolbar/apps/xray.ts similarity index 94% rename from packages/astro/src/runtime/client/dev-overlay/plugins/xray.ts rename to packages/astro/src/runtime/client/dev-toolbar/apps/xray.ts index 4e7d5d8ed00c..e6f8638a4469 100644 --- a/packages/astro/src/runtime/client/dev-overlay/plugins/xray.ts +++ b/packages/astro/src/runtime/client/dev-toolbar/apps/xray.ts @@ -1,5 +1,5 @@ -import type { DevOverlayMetadata, DevOverlayPlugin } from '../../../../@types/astro.js'; -import type { DevOverlayHighlight } from '../ui-library/highlight.js'; +import type { DevToolbarApp, DevToolbarMetadata } from '../../../../@types/astro.js'; +import type { DevToolbarHighlight } from '../ui-library/highlight.js'; import { attachTooltipToHighlight, createHighlight, @@ -16,7 +16,7 @@ export default { name: 'Inspect', icon: icon, init(canvas, eventTarget) { - let islandsOverlays: { highlightElement: DevOverlayHighlight; island: HTMLElement }[] = []; + let islandsOverlays: { highlightElement: DevToolbarHighlight; island: HTMLElement }[] = []; addIslandsOverlay(); @@ -31,7 +31,7 @@ export default { event.preventDefault(); event.stopPropagation(); eventTarget.dispatchEvent( - new CustomEvent('toggle-plugin', { + new CustomEvent('toggle-app', { detail: { state: false, }, @@ -61,7 +61,7 @@ export default { header { display: flex; } - + h1 { display: flex; align-items: center; @@ -71,7 +71,7 @@ export default { margin: 0; font-size: 22px; } - + astro-dev-toolbar-icon { width: 1em; height: 1em; @@ -181,7 +181,7 @@ export default { await fetch( '/__open-in-editor?file=' + encodeURIComponent( - (window as DevOverlayMetadata).__astro_dev_overlay__.root + + (window as DevToolbarMetadata).__astro_dev_toolbar__.root + islandComponentPath.slice(1) ) ); @@ -192,4 +192,4 @@ export default { return tooltip; } }, -} satisfies DevOverlayPlugin; +} satisfies DevToolbarApp; diff --git a/packages/astro/src/runtime/client/dev-overlay/entrypoint.ts b/packages/astro/src/runtime/client/dev-toolbar/entrypoint.ts similarity index 60% rename from packages/astro/src/runtime/client/dev-overlay/entrypoint.ts rename to packages/astro/src/runtime/client/dev-toolbar/entrypoint.ts index fa3020b03c98..a1d5484e3ac2 100644 --- a/packages/astro/src/runtime/client/dev-overlay/entrypoint.ts +++ b/packages/astro/src/runtime/client/dev-toolbar/entrypoint.ts @@ -1,73 +1,71 @@ -import type { DevOverlayPlugin as DevOverlayPluginDefinition } from '../../../@types/astro.js'; -import type { AstroDevOverlay, DevOverlayPlugin } from './overlay.js'; +import type { DevToolbarApp as DevToolbarAppDefinition } from '../../../@types/astro.js'; import { settings } from './settings.js'; +import type { AstroDevToolbar, DevToolbarApp } from './toolbar.js'; // @ts-expect-error -import { loadDevOverlayPlugins } from 'astro:dev-overlay'; +import { loadDevToolbarApps } from 'astro:dev-toolbar'; -let overlay: AstroDevOverlay; +let overlay: AstroDevToolbar; document.addEventListener('DOMContentLoaded', async () => { const [ - customPluginsDefinitions, - { default: astroDevToolPlugin }, - { default: astroAuditPlugin }, - { default: astroXrayPlugin }, - { default: astroSettingsPlugin }, - { AstroDevOverlay, DevOverlayCanvas, getPluginIcon }, + customAppsDefinitions, + { default: astroDevToolApp }, + { default: astroAuditApp }, + { default: astroXrayApp }, + { default: astroSettingsApp }, + { AstroDevToolbar, DevToolbarCanvas, getAppIcon }, { - DevOverlayCard, - DevOverlayHighlight, - DevOverlayTooltip, - DevOverlayWindow, - DevOverlayToggle, - DevOverlayButton, - DevOverlayBadge, - DevOverlayIcon, + DevToolbarCard, + DevToolbarHighlight, + DevToolbarTooltip, + DevToolbarWindow, + DevToolbarToggle, + DevToolbarButton, + DevToolbarBadge, + DevToolbarIcon, }, ] = await Promise.all([ - loadDevOverlayPlugins() as DevOverlayPluginDefinition[], - import('./plugins/astro.js'), - import('./plugins/audit/index.js'), - import('./plugins/xray.js'), - import('./plugins/settings.js'), - import('./overlay.js'), + loadDevToolbarApps() as DevToolbarAppDefinition[], + import('./apps/astro.js'), + import('./apps/audit/index.js'), + import('./apps/xray.js'), + import('./apps/settings.js'), + import('./toolbar.js'), import('./ui-library/index.js'), ]); // Register custom elements - customElements.define('astro-dev-toolbar', AstroDevOverlay); - customElements.define('astro-dev-toolbar-window', DevOverlayWindow); - customElements.define('astro-dev-toolbar-plugin-canvas', DevOverlayCanvas); - customElements.define('astro-dev-toolbar-tooltip', DevOverlayTooltip); - customElements.define('astro-dev-toolbar-highlight', DevOverlayHighlight); - customElements.define('astro-dev-toolbar-card', DevOverlayCard); - customElements.define('astro-dev-toolbar-toggle', DevOverlayToggle); - customElements.define('astro-dev-toolbar-button', DevOverlayButton); - customElements.define('astro-dev-toolbar-badge', DevOverlayBadge); - customElements.define('astro-dev-toolbar-icon', DevOverlayIcon); + customElements.define('astro-dev-toolbar', AstroDevToolbar); + customElements.define('astro-dev-toolbar-window', DevToolbarWindow); + customElements.define('astro-dev-toolbar-app-canvas', DevToolbarCanvas); + customElements.define('astro-dev-toolbar-tooltip', DevToolbarTooltip); + customElements.define('astro-dev-toolbar-highlight', DevToolbarHighlight); + customElements.define('astro-dev-toolbar-card', DevToolbarCard); + customElements.define('astro-dev-toolbar-toggle', DevToolbarToggle); + customElements.define('astro-dev-toolbar-button', DevToolbarButton); + customElements.define('astro-dev-toolbar-badge', DevToolbarBadge); + customElements.define('astro-dev-toolbar-icon', DevToolbarIcon); // Add deprecated names + // TODO: Remove in Astro 5.0 const deprecated = (Parent: any) => class extends Parent {}; - customElements.define('astro-dev-overlay', deprecated(AstroDevOverlay)); - customElements.define('astro-dev-overlay-window', deprecated(DevOverlayWindow)); - customElements.define('astro-dev-overlay-plugin-canvas', deprecated(DevOverlayCanvas)); - customElements.define('astro-dev-overlay-tooltip', deprecated(DevOverlayTooltip)); - customElements.define('astro-dev-overlay-highlight', deprecated(DevOverlayHighlight)); - customElements.define('astro-dev-overlay-card', deprecated(DevOverlayCard)); - customElements.define('astro-dev-overlay-toggle', deprecated(DevOverlayToggle)); - customElements.define('astro-dev-overlay-button', deprecated(DevOverlayButton)); - customElements.define('astro-dev-overlay-badge', deprecated(DevOverlayBadge)); - customElements.define('astro-dev-overlay-icon', deprecated(DevOverlayIcon)); + customElements.define('astro-dev-overlay', deprecated(AstroDevToolbar)); + customElements.define('astro-dev-overlay-window', deprecated(DevToolbarWindow)); + customElements.define('astro-dev-overlay-plugin-canvas', deprecated(DevToolbarCanvas)); + customElements.define('astro-dev-overlay-tooltip', deprecated(DevToolbarTooltip)); + customElements.define('astro-dev-overlay-highlight', deprecated(DevToolbarHighlight)); + customElements.define('astro-dev-overlay-card', deprecated(DevToolbarCard)); + customElements.define('astro-dev-overlay-toggle', deprecated(DevToolbarToggle)); + customElements.define('astro-dev-overlay-button', deprecated(DevToolbarButton)); + customElements.define('astro-dev-overlay-badge', deprecated(DevToolbarBadge)); + customElements.define('astro-dev-overlay-icon', deprecated(DevToolbarIcon)); overlay = document.createElement('astro-dev-toolbar'); - const preparePlugin = ( - pluginDefinition: DevOverlayPluginDefinition, - builtIn: boolean - ): DevOverlayPlugin => { + const prepareApp = (appDefinition: DevToolbarAppDefinition, builtIn: boolean): DevToolbarApp => { const eventTarget = new EventTarget(); - const plugin = { - ...pluginDefinition, + const app = { + ...appDefinition, builtIn: builtIn, active: false, status: 'loading' as const, @@ -75,9 +73,9 @@ document.addEventListener('DOMContentLoaded', async () => { eventTarget: eventTarget, }; - // Events plugins can send to the overlay to update their status + // Events apps can send to the overlay to update their status eventTarget.addEventListener('toggle-notification', (evt) => { - const target = overlay.shadowRoot?.querySelector(`[data-plugin-id="${plugin.id}"]`); + const target = overlay.shadowRoot?.querySelector(`[data-app-id="${app.id}"]`); if (!target) return; let newState = true; @@ -85,7 +83,7 @@ document.addEventListener('DOMContentLoaded', async () => { newState = evt.detail.state ?? true; } - plugin.notification.state = newState; + app.notification.state = newState; target.querySelector('.notification')?.toggleAttribute('data-active', newState); }); @@ -96,22 +94,23 @@ document.addEventListener('DOMContentLoaded', async () => { newState = evt.detail.state ?? true; } - await overlay.setPluginStatus(plugin, newState); + await overlay.setAppStatus(app, newState); }; eventTarget.addEventListener('toggle-app', onToggleApp); // Deprecated + // TODO: Remove in Astro 5.0 eventTarget.addEventListener('toggle-plugin', onToggleApp); - return plugin; + return app; }; - const astromorePlugin = { + const astroMoreApp = { id: 'astro:more', name: 'More', icon: 'dots-three', init(canvas, eventTarget) { - const hiddenPlugins = plugins.filter((p) => !p.builtIn).slice(overlay.customPluginsToShow); + const hiddenApps = apps.filter((p) => !p.builtIn).slice(overlay.customAppsToShow); createDropdown(); @@ -192,17 +191,17 @@ document.addEventListener('DOMContentLoaded', async () => { const dropdown = document.createElement('div'); dropdown.id = 'dropdown'; - dropdown.toggleAttribute('data-no-notification', settings.config.disablePluginNotification); + dropdown.toggleAttribute('data-no-notification', settings.config.disableAppNotification); - for (const plugin of hiddenPlugins) { + for (const app of hiddenApps) { const buttonContainer = document.createElement('div'); buttonContainer.classList.add('item'); const button = document.createElement('button'); - button.setAttribute('data-plugin-id', plugin.id); + button.setAttribute('data-app-id', app.id); const iconContainer = document.createElement('div'); const iconElement = document.createElement('template'); - iconElement.innerHTML = getPluginIcon(plugin.icon); + iconElement.innerHTML = getAppIcon(app.icon); iconContainer.append(iconElement.content.cloneNode(true)); const notification = document.createElement('div'); @@ -211,16 +210,16 @@ document.addEventListener('DOMContentLoaded', async () => { iconContainer.classList.add('icon'); button.append(iconContainer); - button.append(document.createTextNode(plugin.name)); + button.append(document.createTextNode(app.name)); button.addEventListener('click', () => { - overlay.togglePluginStatus(plugin); + overlay.toggleAppStatus(app); }); buttonContainer.append(button); dropdown.append(buttonContainer); - plugin.eventTarget.addEventListener('toggle-notification', (evt) => { + app.eventTarget.addEventListener('toggle-notification', (evt) => { if (!(evt instanceof CustomEvent)) return; notification.toggleAttribute('data-active', evt.detail.state ?? true); @@ -228,7 +227,7 @@ document.addEventListener('DOMContentLoaded', async () => { eventTarget.dispatchEvent( new CustomEvent('toggle-notification', { detail: { - state: hiddenPlugins.some((p) => p.notification.state === true), + state: hiddenApps.some((p) => p.notification.state === true), }, }) ); @@ -238,20 +237,16 @@ document.addEventListener('DOMContentLoaded', async () => { canvas.append(dropdown); } }, - } satisfies DevOverlayPluginDefinition; - - const plugins: DevOverlayPlugin[] = [ - ...[ - astroDevToolPlugin, - astroXrayPlugin, - astroAuditPlugin, - astroSettingsPlugin, - astromorePlugin, - ].map((pluginDef) => preparePlugin(pluginDef, true)), - ...customPluginsDefinitions.map((pluginDef) => preparePlugin(pluginDef, false)), + } satisfies DevToolbarAppDefinition; + + const apps: DevToolbarApp[] = [ + ...[astroDevToolApp, astroXrayApp, astroAuditApp, astroSettingsApp, astroMoreApp].map( + (appDef) => prepareApp(appDef, true) + ), + ...customAppsDefinitions.map((appDef) => prepareApp(appDef, false)), ]; - overlay.plugins = plugins; + overlay.apps = apps; document.body.append(overlay); diff --git a/packages/astro/src/runtime/client/dev-overlay/settings.ts b/packages/astro/src/runtime/client/dev-toolbar/settings.ts similarity index 57% rename from packages/astro/src/runtime/client/dev-overlay/settings.ts rename to packages/astro/src/runtime/client/dev-toolbar/settings.ts index d3f1c0d36989..6e3656dca32c 100644 --- a/packages/astro/src/runtime/client/dev-overlay/settings.ts +++ b/packages/astro/src/runtime/client/dev-toolbar/settings.ts @@ -1,10 +1,10 @@ export interface Settings { - disablePluginNotification: boolean; + disableAppNotification: boolean; verbose: boolean; } export const defaultSettings = { - disablePluginNotification: false, + disableAppNotification: false, verbose: false, } satisfies Settings; @@ -12,15 +12,22 @@ export const settings = getSettings(); function getSettings() { let _settings: Settings = { ...defaultSettings }; - const overlaySettings = localStorage.getItem('astro:dev-overlay:settings'); + const toolbarSettings = localStorage.getItem('astro:dev-toolbar:settings'); - if (overlaySettings) { - _settings = { ..._settings, ...JSON.parse(overlaySettings) }; + // TODO: Remove in Astro 5.0 + const oldSettings = localStorage.getItem('astro:dev-overlay:settings'); + if (oldSettings && !toolbarSettings) { + localStorage.setItem('astro:dev-toolbar:settings', oldSettings); + localStorage.removeItem('astro:dev-overlay:settings'); + } + + if (toolbarSettings) { + _settings = { ..._settings, ...JSON.parse(toolbarSettings) }; } function updateSetting(key: keyof Settings, value: Settings[typeof key]) { _settings[key] = value; - localStorage.setItem('astro:dev-overlay:settings', JSON.stringify(_settings)); + localStorage.setItem('astro:dev-toolbar:settings', JSON.stringify(_settings)); } function log(message: string) { diff --git a/packages/astro/src/runtime/client/dev-overlay/overlay.ts b/packages/astro/src/runtime/client/dev-toolbar/toolbar.ts similarity index 58% rename from packages/astro/src/runtime/client/dev-overlay/overlay.ts rename to packages/astro/src/runtime/client/dev-toolbar/toolbar.ts index f85972418aaa..8e1c714014d6 100644 --- a/packages/astro/src/runtime/client/dev-overlay/overlay.ts +++ b/packages/astro/src/runtime/client/dev-toolbar/toolbar.ts @@ -1,12 +1,9 @@ /* eslint-disable no-console */ -import type { - DevOverlayMetadata, - DevOverlayPlugin as DevOverlayPluginDefinition, -} from '../../../@types/astro.js'; +import type { DevToolbarApp as DevToolbarAppDefinition } from '../../../@types/astro.js'; import { settings } from './settings.js'; import { getIconElement, isDefinedIcon, type Icon } from './ui-library/icons.js'; -export type DevOverlayPlugin = DevOverlayPluginDefinition & { +export type DevToolbarApp = DevToolbarAppDefinition & { builtIn: boolean; active: boolean; status: 'ready' | 'loading' | 'error'; @@ -16,18 +13,20 @@ export type DevOverlayPlugin = DevOverlayPluginDefinition & { eventTarget: EventTarget; }; const WS_EVENT_NAME = 'astro-dev-toolbar'; +// TODO: Remove in Astro 5.0 const WS_EVENT_NAME_DEPRECATED = 'astro-dev-overlay'; + const HOVER_DELAY = 2 * 1000; const DEVBAR_HITBOX_ABOVE = 42; -export class AstroDevOverlay extends HTMLElement { +export class AstroDevToolbar extends HTMLElement { shadowRoot: ShadowRoot; delayedHideTimeout: number | undefined; - devOverlay: HTMLDivElement | undefined; - plugins: DevOverlayPlugin[] = []; + devToolbarContainer: HTMLDivElement | undefined; + apps: DevToolbarApp[] = []; hasBeenInitialized = false; // TODO: This should be dynamic based on the screen size or at least configurable, erika - 2023-11-29 - customPluginsToShow = 3; + customAppsToShow = 3; constructor() { super(); @@ -54,7 +53,7 @@ export class AstroDevOverlay extends HTMLElement { animation: none; } - #dev-overlay { + #dev-toolbar-root { position: fixed; bottom: 0px; left: 50%; @@ -67,11 +66,11 @@ export class AstroDevOverlay extends HTMLElement { pointer-events: none; } - #dev-overlay[data-hidden] { + #dev-toolbar-root[data-hidden] { bottom: -40px; } - #dev-overlay[data-hidden] #dev-bar .item { + #dev-toolbar-root[data-hidden] #dev-bar .item { opacity: 0.2; } @@ -215,7 +214,7 @@ export class AstroDevOverlay extends HTMLElement { background: #B33E66; } - #dev-overlay:not([data-no-notification]) #dev-bar .item .notification[data-active] { + #dev-toolbar-root:not([data-no-notification]) #dev-bar .item .notification[data-active] { display: block; } @@ -229,66 +228,62 @@ export class AstroDevOverlay extends HTMLElement { width: 1px; } -
- ${this.plugins - .filter( - (plugin) => plugin.builtIn && !['astro:settings', 'astro:more'].includes(plugin.id) - ) - .map((plugin) => this.getPluginTemplate(plugin)) + ${this.apps + .filter((app) => app.builtIn && !['astro:settings', 'astro:more'].includes(app.id)) + .map((app) => this.getAppTemplate(app)) .join('')} ${ - this.plugins.filter((plugin) => !plugin.builtIn).length > 0 - ? `
${this.plugins - .filter((plugin) => !plugin.builtIn) - .slice(0, this.customPluginsToShow) - .map((plugin) => this.getPluginTemplate(plugin)) + this.apps.filter((app) => !app.builtIn).length > 0 + ? `
${this.apps + .filter((app) => !app.builtIn) + .slice(0, this.customAppsToShow) + .map((app) => this.getAppTemplate(app)) .join('')}` : '' } ${ - this.plugins.filter((plugin) => !plugin.builtIn).length > this.customPluginsToShow - ? this.getPluginTemplate( - this.plugins.find((plugin) => plugin.builtIn && plugin.id === 'astro:more')! + this.apps.filter((app) => !app.builtIn).length > this.customAppsToShow + ? this.getAppTemplate( + this.apps.find((app) => app.builtIn && app.id === 'astro:more')! ) : '' }
- ${this.getPluginTemplate( - this.plugins.find((plugin) => plugin.builtIn && plugin.id === 'astro:settings')! - )} + ${this.getAppTemplate(this.apps.find((app) => app.builtIn && app.id === 'astro:settings')!)}
`; - this.devOverlay = this.shadowRoot.querySelector('#dev-overlay')!; + this.devToolbarContainer = this.shadowRoot.querySelector('#dev-toolbar-root')!; this.attachEvents(); - // Create plugin canvases - this.plugins.forEach(async (plugin) => { - if (settings.config.verbose) console.log(`Creating plugin canvas for ${plugin.id}`); - const pluginCanvas = document.createElement('astro-dev-toolbar-plugin-canvas'); - pluginCanvas.dataset.pluginId = plugin.id; - this.shadowRoot?.append(pluginCanvas); + // Create app canvases + this.apps.forEach(async (app) => { + if (settings.config.verbose) console.log(`Creating app canvas for ${app.id}`); + const appCanvas = document.createElement('astro-dev-toolbar-app-canvas'); + appCanvas.dataset.appId = app.id; + this.shadowRoot?.append(appCanvas); }); - // Init plugin lazily, so that the page can load faster. + // Init app lazily, so that the page can load faster. // Fallback to setTimeout for Safari (sad!) if ('requestIdleCallback' in window) { window.requestIdleCallback( async () => { - this.plugins.map((plugin) => this.initPlugin(plugin)); + this.apps.map((app) => this.initApp(app)); }, { timeout: 300 } ); } else { setTimeout(async () => { - this.plugins.map((plugin) => this.initPlugin(plugin)); + this.apps.map((app) => this.initApp(app)); }, 300); } } @@ -302,9 +297,9 @@ export class AstroDevOverlay extends HTMLElement { this.hasBeenInitialized = true; } - // Run this every time to make sure the correct plugin is open. - this.plugins.forEach(async (plugin) => { - await this.setPluginStatus(plugin, plugin.active); + // Run this every time to make sure the correct app is open. + this.apps.forEach(async (app) => { + await this.setAppStatus(app, app.active); }); } @@ -314,28 +309,28 @@ export class AstroDevOverlay extends HTMLElement { item.addEventListener('click', async (event) => { const target = event.currentTarget; if (!target || !(target instanceof HTMLElement)) return; - const id = target.dataset.pluginId; + const id = target.dataset.appId; if (!id) return; - const plugin = this.getPluginById(id); - if (!plugin) return; + const app = this.getAppById(id); + if (!app) return; event.stopPropagation(); - await this.togglePluginStatus(plugin); + await this.toggleAppStatus(app); }); }); (['mouseenter', 'focusin'] as const).forEach((event) => { - this.devOverlay!.addEventListener(event, () => { + this.devToolbarContainer!.addEventListener(event, () => { this.clearDelayedHide(); if (this.isHidden()) { - this.setOverlayVisible(true); + this.setToolbarVisible(true); } }); }); (['mouseleave', 'focusout'] as const).forEach((event) => { - this.devOverlay!.addEventListener(event, () => { + this.devToolbarContainer!.addEventListener(event, () => { this.clearDelayedHide(); - if (this.getActivePlugin() || this.isHidden()) { + if (this.getActiveApp() || this.isHidden()) { return; } this.triggerDelayedHide(); @@ -345,124 +340,125 @@ export class AstroDevOverlay extends HTMLElement { document.addEventListener('keyup', (event) => { if (event.key !== 'Escape') return; if (this.isHidden()) return; - const activePlugin = this.getActivePlugin(); - if (activePlugin) { - this.togglePluginStatus(activePlugin); + const activeApp = this.getActiveApp(); + if (activeApp) { + this.toggleAppStatus(activeApp); } else { - this.setOverlayVisible(false); + this.setToolbarVisible(false); } }); } - async initPlugin(plugin: DevOverlayPlugin) { - const shadowRoot = this.getPluginCanvasById(plugin.id)!.shadowRoot!; - plugin.status = 'loading'; + async initApp(app: DevToolbarApp) { + const shadowRoot = this.getAppCanvasById(app.id)!.shadowRoot!; + app.status = 'loading'; try { - if (settings.config.verbose) console.info(`Initializing plugin ${plugin.id}`); + if (settings.config.verbose) console.info(`Initializing app ${app.id}`); - await plugin.init?.(shadowRoot, plugin.eventTarget); - plugin.status = 'ready'; + await app.init?.(shadowRoot, app.eventTarget); + app.status = 'ready'; if (import.meta.hot) { - import.meta.hot.send(`${WS_EVENT_NAME}:${plugin.id}:initialized`); - import.meta.hot.send(`${WS_EVENT_NAME_DEPRECATED}:${plugin.id}:initialized`); + import.meta.hot.send(`${WS_EVENT_NAME}:${app.id}:initialized`); + import.meta.hot.send(`${WS_EVENT_NAME_DEPRECATED}:${app.id}:initialized`); } } catch (e) { - console.error(`Failed to init plugin ${plugin.id}, error: ${e}`); - plugin.status = 'error'; + console.error(`Failed to init app ${app.id}, error: ${e}`); + app.status = 'error'; } } - getPluginTemplate(plugin: DevOverlayPlugin) { - return ``; } - getPluginById(id: string) { - return this.plugins.find((plugin) => plugin.id === id); + getAppById(id: string) { + return this.apps.find((app) => app.id === id); } - getPluginCanvasById(id: string) { + getAppCanvasById(id: string) { return this.shadowRoot.querySelector( - `astro-dev-toolbar-plugin-canvas[data-plugin-id="${id}"]` + `astro-dev-toolbar-app-canvas[data-app-id="${id}"]` ); } - async togglePluginStatus(plugin: DevOverlayPlugin) { - const activePlugin = this.getActivePlugin(); - if (activePlugin) { - const closePlugin = await this.setPluginStatus(activePlugin, false); + async toggleAppStatus(app: DevToolbarApp) { + const activeApp = this.getActiveApp(); + if (activeApp) { + const closeApp = await this.setAppStatus(activeApp, false); - // If the plugin returned false, don't open the new plugin, the old plugin didn't want to close - if (!closePlugin) return; + // If the app returned false, don't open the new app, the old app didn't want to close + if (!closeApp) return; } - // TODO(fks): Handle a plugin that hasn't loaded yet. + // TODO(fks): Handle a app that hasn't loaded yet. // Currently, this will just do nothing. - if (plugin.status !== 'ready') return; + if (app.status !== 'ready') return; - // Open the selected plugin. If the selected plugin was - // already the active plugin then the desired outcome - // was to close that plugin, so no action needed. - if (plugin !== activePlugin) { - await this.setPluginStatus(plugin, true); + // Open the selected app. If the selected app was + // already the active app then the desired outcome + // was to close that app, so no action needed. + if (app !== activeApp) { + await this.setAppStatus(app, true); } } - async setPluginStatus(plugin: DevOverlayPlugin, newStatus: boolean) { - const pluginCanvas = this.getPluginCanvasById(plugin.id); - if (!pluginCanvas) return false; + async setAppStatus(app: DevToolbarApp, newStatus: boolean) { + const appCanvas = this.getAppCanvasById(app.id); + if (!appCanvas) return false; - if (plugin.active && !newStatus && plugin.beforeTogglingOff) { - const shouldToggleOff = await plugin.beforeTogglingOff(pluginCanvas.shadowRoot!); + if (app.active && !newStatus && app.beforeTogglingOff) { + const shouldToggleOff = await app.beforeTogglingOff(appCanvas.shadowRoot!); - // If the plugin returned false, don't toggle it off, maybe the plugin showed a confirmation dialog or similar + // If the app returned false, don't toggle it off, maybe the app showed a confirmation dialog or similar if (!shouldToggleOff) return false; } - plugin.active = newStatus ?? !plugin.active; - const mainBarButton = this.shadowRoot.querySelector(`[data-plugin-id="${plugin.id}"]`); - const moreBarButton = this.getPluginCanvasById('astro:more')?.shadowRoot?.querySelector( - `[data-plugin-id="${plugin.id}"]` + app.active = newStatus ?? !app.active; + const mainBarButton = this.shadowRoot.querySelector(`[data-app-id="${app.id}"]`); + const moreBarButton = this.getAppCanvasById('astro:more')?.shadowRoot?.querySelector( + `[data-app-id="${app.id}"]` ); if (mainBarButton) { - mainBarButton.classList.toggle('active', plugin.active); + mainBarButton.classList.toggle('active', app.active); } if (moreBarButton) { - moreBarButton.classList.toggle('active', plugin.active); + moreBarButton.classList.toggle('active', app.active); } - if (plugin.active) { - pluginCanvas.style.display = 'block'; - pluginCanvas.setAttribute('data-active', ''); + if (app.active) { + appCanvas.style.display = 'block'; + appCanvas.setAttribute('data-active', ''); } else { - pluginCanvas.style.display = 'none'; - pluginCanvas.removeAttribute('data-active'); + appCanvas.style.display = 'none'; + appCanvas.removeAttribute('data-active'); } [ 'app-toggled', // Deprecated + // TODO: Remove in Astro 5.0 'plugin-toggled', ].forEach((eventName) => { - plugin.eventTarget.dispatchEvent( + app.eventTarget.dispatchEvent( new CustomEvent(eventName, { detail: { - state: plugin.active, - plugin, + state: app.active, + app, }, }) ); }); if (import.meta.hot) { - import.meta.hot.send(`${WS_EVENT_NAME}:${plugin.id}:toggled`, { state: plugin.active }); - import.meta.hot.send(`${WS_EVENT_NAME_DEPRECATED}:${plugin.id}:toggled`, { - state: plugin.active, + import.meta.hot.send(`${WS_EVENT_NAME}:${app.id}:toggled`, { state: app.active }); + import.meta.hot.send(`${WS_EVENT_NAME_DEPRECATED}:${app.id}:toggled`, { + state: app.active, }); } @@ -470,11 +466,11 @@ export class AstroDevOverlay extends HTMLElement { } isHidden(): boolean { - return this.devOverlay?.hasAttribute('data-hidden') ?? true; + return this.devToolbarContainer?.hasAttribute('data-hidden') ?? true; } - getActivePlugin(): DevOverlayPlugin | undefined { - return this.plugins.find((plugin) => plugin.active); + getActiveApp(): DevToolbarApp | undefined { + return this.apps.find((app) => app.active); } clearDelayedHide() { @@ -485,25 +481,25 @@ export class AstroDevOverlay extends HTMLElement { triggerDelayedHide() { this.clearDelayedHide(); this.delayedHideTimeout = window.setTimeout(() => { - this.setOverlayVisible(false); + this.setToolbarVisible(false); this.delayedHideTimeout = undefined; }, HOVER_DELAY); } - setOverlayVisible(newStatus: boolean) { + setToolbarVisible(newStatus: boolean) { const barContainer = this.shadowRoot.querySelector('#bar-container'); const devBar = this.shadowRoot.querySelector('#dev-bar'); const devBarHitboxAbove = this.shadowRoot.querySelector('#dev-bar-hitbox-above'); if (newStatus === true) { - this.devOverlay?.removeAttribute('data-hidden'); + this.devToolbarContainer?.removeAttribute('data-hidden'); barContainer?.removeAttribute('inert'); devBar?.removeAttribute('tabindex'); if (devBarHitboxAbove) devBarHitboxAbove.style.height = '0'; return; } if (newStatus === false) { - this.devOverlay?.setAttribute('data-hidden', ''); + this.devToolbarContainer?.setAttribute('data-hidden', ''); barContainer?.setAttribute('inert', ''); devBar?.setAttribute('tabindex', '0'); if (devBarHitboxAbove) devBarHitboxAbove.style.height = `${DEVBAR_HITBOX_ABOVE}px`; @@ -512,17 +508,16 @@ export class AstroDevOverlay extends HTMLElement { } setNotificationVisible(newStatus: boolean) { - const devOverlayElement = this.shadowRoot.querySelector('#dev-overlay'); - devOverlayElement?.toggleAttribute('data-no-notification', !newStatus); + this.devToolbarContainer?.toggleAttribute('data-no-notification', !newStatus); - const moreCanvas = this.getPluginCanvasById('astro:more'); + const moreCanvas = this.getAppCanvasById('astro:more'); moreCanvas?.shadowRoot ?.querySelector('#dropdown') ?.toggleAttribute('data-no-notification', !newStatus); } } -export class DevOverlayCanvas extends HTMLElement { +export class DevToolbarCanvas extends HTMLElement { shadowRoot: ShadowRoot; constructor() { @@ -542,7 +537,7 @@ export class DevOverlayCanvas extends HTMLElement { } } -export function getPluginIcon(icon: Icon) { +export function getAppIcon(icon: Icon) { if (isDefinedIcon(icon)) { return getIconElement(icon).outerHTML; } diff --git a/packages/astro/src/runtime/client/dev-overlay/ui-library/badge.ts b/packages/astro/src/runtime/client/dev-toolbar/ui-library/badge.ts similarity index 96% rename from packages/astro/src/runtime/client/dev-overlay/ui-library/badge.ts rename to packages/astro/src/runtime/client/dev-toolbar/ui-library/badge.ts index 5a8eea07ed9f..9d10b65be27a 100644 --- a/packages/astro/src/runtime/client/dev-overlay/ui-library/badge.ts +++ b/packages/astro/src/runtime/client/dev-toolbar/ui-library/badge.ts @@ -1,7 +1,7 @@ type BadgeSize = 'small' | 'large'; type BadgeStyle = 'purple' | 'gray' | 'red' | 'green' | 'yellow'; -export class DevOverlayBadge extends HTMLElement { +export class DevToolbarBadge extends HTMLElement { size: BadgeSize = 'small'; badgeStyle: BadgeStyle = 'purple'; diff --git a/packages/astro/src/runtime/client/dev-overlay/ui-library/button.ts b/packages/astro/src/runtime/client/dev-toolbar/ui-library/button.ts similarity index 94% rename from packages/astro/src/runtime/client/dev-overlay/ui-library/button.ts rename to packages/astro/src/runtime/client/dev-toolbar/ui-library/button.ts index f2bd75d70269..3f625376d774 100644 --- a/packages/astro/src/runtime/client/dev-overlay/ui-library/button.ts +++ b/packages/astro/src/runtime/client/dev-toolbar/ui-library/button.ts @@ -1,7 +1,7 @@ type ButtonSize = 'small' | 'medium' | 'large'; type ButtonStyle = 'ghost' | 'outline' | 'purple' | 'gray' | 'red'; -export class DevOverlayButton extends HTMLElement { +export class DevToolbarButton extends HTMLElement { size: ButtonSize = 'small'; buttonStyle: ButtonStyle = 'purple'; @@ -72,6 +72,7 @@ export class DevOverlayButton extends HTMLElement { border-color: rgba(249, 196, 215, 0.33); } + /* TODO: Remove "astro-dev-overlay-icon" in Astro 5.0 */ ::slotted(astro-dev-overlay-icon), ::slotted(astro-dev-toolbar-icon) { display: inline-block; diff --git a/packages/astro/src/runtime/client/dev-overlay/ui-library/card.ts b/packages/astro/src/runtime/client/dev-toolbar/ui-library/card.ts similarity index 96% rename from packages/astro/src/runtime/client/dev-overlay/ui-library/card.ts rename to packages/astro/src/runtime/client/dev-toolbar/ui-library/card.ts index 90d4739f165f..7331a88b2a3f 100644 --- a/packages/astro/src/runtime/client/dev-overlay/ui-library/card.ts +++ b/packages/astro/src/runtime/client/dev-toolbar/ui-library/card.ts @@ -1,4 +1,4 @@ -export class DevOverlayCard extends HTMLElement { +export class DevToolbarCard extends HTMLElement { link?: string | undefined | null; clickAction?: () => void | (() => Promise); shadowRoot: ShadowRoot; diff --git a/packages/astro/src/runtime/client/dev-overlay/ui-library/highlight.ts b/packages/astro/src/runtime/client/dev-toolbar/ui-library/highlight.ts similarity index 96% rename from packages/astro/src/runtime/client/dev-overlay/ui-library/highlight.ts rename to packages/astro/src/runtime/client/dev-toolbar/ui-library/highlight.ts index 16d02df0cc7a..7605ef4760b2 100644 --- a/packages/astro/src/runtime/client/dev-overlay/ui-library/highlight.ts +++ b/packages/astro/src/runtime/client/dev-toolbar/ui-library/highlight.ts @@ -1,6 +1,6 @@ import { getIconElement, isDefinedIcon, type Icon } from './icons.js'; -export class DevOverlayHighlight extends HTMLElement { +export class DevToolbarHighlight extends HTMLElement { icon?: Icon | undefined | null; shadowRoot: ShadowRoot; diff --git a/packages/astro/src/runtime/client/dev-overlay/ui-library/icon.ts b/packages/astro/src/runtime/client/dev-toolbar/ui-library/icon.ts similarity index 95% rename from packages/astro/src/runtime/client/dev-overlay/ui-library/icon.ts rename to packages/astro/src/runtime/client/dev-toolbar/ui-library/icon.ts index 3211e1857620..193be65f33cf 100644 --- a/packages/astro/src/runtime/client/dev-overlay/ui-library/icon.ts +++ b/packages/astro/src/runtime/client/dev-toolbar/ui-library/icon.ts @@ -1,6 +1,6 @@ import { getIconElement, isDefinedIcon, type Icon } from './icons.js'; -export class DevOverlayIcon extends HTMLElement { +export class DevToolbarIcon extends HTMLElement { _icon: Icon | undefined = undefined; shadowRoot: ShadowRoot; diff --git a/packages/astro/src/runtime/client/dev-overlay/ui-library/icons.ts b/packages/astro/src/runtime/client/dev-toolbar/ui-library/icons.ts similarity index 100% rename from packages/astro/src/runtime/client/dev-overlay/ui-library/icons.ts rename to packages/astro/src/runtime/client/dev-toolbar/ui-library/icons.ts diff --git a/packages/astro/src/runtime/client/dev-toolbar/ui-library/index.ts b/packages/astro/src/runtime/client/dev-toolbar/ui-library/index.ts new file mode 100644 index 000000000000..c515dccfc721 --- /dev/null +++ b/packages/astro/src/runtime/client/dev-toolbar/ui-library/index.ts @@ -0,0 +1,8 @@ +export { DevToolbarBadge } from './badge.js'; +export { DevToolbarButton } from './button.js'; +export { DevToolbarCard } from './card.js'; +export { DevToolbarHighlight } from './highlight.js'; +export { DevToolbarIcon } from './icon.js'; +export { DevToolbarToggle } from './toggle.js'; +export { DevToolbarTooltip } from './tooltip.js'; +export { DevToolbarWindow } from './window.js'; diff --git a/packages/astro/src/runtime/client/dev-overlay/ui-library/toggle.ts b/packages/astro/src/runtime/client/dev-toolbar/ui-library/toggle.ts similarity index 96% rename from packages/astro/src/runtime/client/dev-overlay/ui-library/toggle.ts rename to packages/astro/src/runtime/client/dev-toolbar/ui-library/toggle.ts index 1fb0b686a9d1..9b45fb14d78a 100644 --- a/packages/astro/src/runtime/client/dev-overlay/ui-library/toggle.ts +++ b/packages/astro/src/runtime/client/dev-toolbar/ui-library/toggle.ts @@ -1,4 +1,4 @@ -export class DevOverlayToggle extends HTMLElement { +export class DevToolbarToggle extends HTMLElement { shadowRoot: ShadowRoot; input: HTMLInputElement; diff --git a/packages/astro/src/runtime/client/dev-overlay/ui-library/tooltip.ts b/packages/astro/src/runtime/client/dev-toolbar/ui-library/tooltip.ts similarity index 96% rename from packages/astro/src/runtime/client/dev-overlay/ui-library/tooltip.ts rename to packages/astro/src/runtime/client/dev-toolbar/ui-library/tooltip.ts index 9f7224ca479f..b0d92cd931c1 100644 --- a/packages/astro/src/runtime/client/dev-overlay/ui-library/tooltip.ts +++ b/packages/astro/src/runtime/client/dev-toolbar/ui-library/tooltip.ts @@ -1,6 +1,6 @@ import { getIconElement, isDefinedIcon, type Icon } from './icons.js'; -export interface DevOverlayTooltipSection { +export interface DevToolbarTooltipSection { title?: string; inlineTitle?: string; icon?: Icon; @@ -9,8 +9,8 @@ export interface DevOverlayTooltipSection { clickDescription?: string; } -export class DevOverlayTooltip extends HTMLElement { - sections: DevOverlayTooltipSection[] = []; +export class DevToolbarTooltip extends HTMLElement { + sections: DevToolbarTooltipSection[] = []; shadowRoot: ShadowRoot; constructor() { diff --git a/packages/astro/src/runtime/client/dev-overlay/ui-library/window.ts b/packages/astro/src/runtime/client/dev-toolbar/ui-library/window.ts similarity index 97% rename from packages/astro/src/runtime/client/dev-overlay/ui-library/window.ts rename to packages/astro/src/runtime/client/dev-toolbar/ui-library/window.ts index bab936d4bbd8..f1032d2a9860 100644 --- a/packages/astro/src/runtime/client/dev-overlay/ui-library/window.ts +++ b/packages/astro/src/runtime/client/dev-toolbar/ui-library/window.ts @@ -1,4 +1,4 @@ -export class DevOverlayWindow extends HTMLElement { +export class DevToolbarWindow extends HTMLElement { shadowRoot: ShadowRoot; constructor() { @@ -34,7 +34,7 @@ export class DevOverlayWindow extends HTMLElement { background: white; } } - + @media (max-width: 640px) { :host { border-radius: 0; diff --git a/packages/astro/src/vite-plugin-astro-server/route.ts b/packages/astro/src/vite-plugin-astro-server/route.ts index 679fa6505b82..e24b7ca26774 100644 --- a/packages/astro/src/vite-plugin-astro-server/route.ts +++ b/packages/astro/src/vite-plugin-astro-server/route.ts @@ -2,7 +2,7 @@ import type http from 'node:http'; import { fileURLToPath } from 'node:url'; import type { ComponentInstance, - DevOverlayMetadata, + DevToolbarMetadata, ManifestData, MiddlewareHandler, RouteData, @@ -24,6 +24,7 @@ import { import { createRequest } from '../core/request.js'; import { matchAllRoutes } from '../core/routing/index.js'; import { isPage, resolveIdToUrl } from '../core/util.js'; +import { normalizeTheLocale } from '../i18n/index.js'; import { createI18nMiddleware, i18nPipelineHook } from '../i18n/middleware.js'; import { getSortedPreloadedMatches } from '../prerender/routing.js'; import { isServerLikeOutput } from '../prerender/utils.js'; @@ -34,7 +35,6 @@ import { preload } from './index.js'; import { getComponentMetadata } from './metadata.js'; import { handle404Response, writeSSRResult, writeWebResponse } from './response.js'; import { getScriptsForURL } from './scripts.js'; -import { normalizeTheLocale } from '../i18n/index.js'; const clientLocalsSymbol = Symbol.for('astro.locals'); @@ -404,12 +404,12 @@ async function getScriptsAndStyles({ pipeline, filePath }: GetScriptsAndStylesPa scripts.add({ props: { type: 'module', - src: await resolveIdToUrl(moduleLoader, 'astro/runtime/client/dev-overlay/entrypoint.js'), + src: await resolveIdToUrl(moduleLoader, 'astro/runtime/client/dev-toolbar/entrypoint.js'), }, children: '', }); - const additionalMetadata: DevOverlayMetadata['__astro_dev_overlay__'] = { + const additionalMetadata: DevToolbarMetadata['__astro_dev_toolbar__'] = { root: fileURLToPath(settings.config.root), version: ASTRO_VERSION, debugInfo: await getInfoOutput({ userConfig: settings.config, print: false }), @@ -418,7 +418,7 @@ async function getScriptsAndStyles({ pipeline, filePath }: GetScriptsAndStylesPa // Additional data for the dev overlay scripts.add({ props: {}, - children: `window.__astro_dev_overlay__ = ${JSON.stringify(additionalMetadata)}`, + children: `window.__astro_dev_toolbar__ = ${JSON.stringify(additionalMetadata)}`, }); } } diff --git a/packages/astro/src/vite-plugin-dev-overlay/vite-plugin-dev-overlay.ts b/packages/astro/src/vite-plugin-dev-toolbar/vite-plugin-dev-toolbar.ts similarity index 78% rename from packages/astro/src/vite-plugin-dev-overlay/vite-plugin-dev-overlay.ts rename to packages/astro/src/vite-plugin-dev-toolbar/vite-plugin-dev-toolbar.ts index d8227a8af082..4e9526993ca6 100644 --- a/packages/astro/src/vite-plugin-dev-overlay/vite-plugin-dev-overlay.ts +++ b/packages/astro/src/vite-plugin-dev-toolbar/vite-plugin-dev-toolbar.ts @@ -1,12 +1,12 @@ import type * as vite from 'vite'; import type { AstroPluginOptions } from '../@types/astro.js'; -const VIRTUAL_MODULE_ID = 'astro:dev-overlay'; +const VIRTUAL_MODULE_ID = 'astro:dev-toolbar'; const resolvedVirtualModuleId = '\0' + VIRTUAL_MODULE_ID; -export default function astroDevOverlay({ settings }: AstroPluginOptions): vite.Plugin { +export default function astroDevToolbar({ settings }: AstroPluginOptions): vite.Plugin { return { - name: 'astro:dev-overlay', + name: 'astro:dev-toolbar', config() { return { optimizeDeps: { @@ -23,7 +23,7 @@ export default function astroDevOverlay({ settings }: AstroPluginOptions): vite. async load(id) { if (id === resolvedVirtualModuleId) { return ` - export const loadDevOverlayPlugins = async () => { + export const loadDevToolbarApps = async () => { return [${settings.devToolbarApps .map((plugin) => `(await import(${JSON.stringify(plugin)})).default`) .join(',')}]; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e4415b1d4a9f..c1a32f4441b3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -959,7 +959,7 @@ importers: specifier: ^18.0.0 version: 18.2.0(react@18.2.0) - packages/astro/e2e/fixtures/dev-overlay: + packages/astro/e2e/fixtures/dev-toolbar: dependencies: '@astrojs/preact': specifier: workspace:* From 9bb779d2f9c60de29954f4912a2aa229fcd2be2e Mon Sep 17 00:00:00 2001 From: Bjorn Lu Date: Fri, 12 Jan 2024 11:19:08 +0800 Subject: [PATCH 16/23] Disable file watcher for internal one-off vite servers (#9665) --- .changeset/blue-pets-battle.md | 5 +++++ packages/astro/src/core/config/vite-load.ts | 2 +- packages/astro/src/core/sync/index.ts | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 .changeset/blue-pets-battle.md diff --git a/.changeset/blue-pets-battle.md b/.changeset/blue-pets-battle.md new file mode 100644 index 000000000000..1752627435dc --- /dev/null +++ b/.changeset/blue-pets-battle.md @@ -0,0 +1,5 @@ +--- +"astro": patch +--- + +Disables internal file watcher for one-off Vite servers to improve start-up performance diff --git a/packages/astro/src/core/config/vite-load.ts b/packages/astro/src/core/config/vite-load.ts index 8ef15d9a2074..8939c2a8f9a4 100644 --- a/packages/astro/src/core/config/vite-load.ts +++ b/packages/astro/src/core/config/vite-load.ts @@ -6,7 +6,7 @@ import { debug } from '../logger/core.js'; async function createViteServer(root: string, fs: typeof fsType): Promise { const viteServer = await createServer({ - server: { middlewareMode: true, hmr: false, watch: { ignored: ['**'] } }, + server: { middlewareMode: true, hmr: false, watch: null }, optimizeDeps: { disabled: true }, clearScreen: false, appType: 'custom', diff --git a/packages/astro/src/core/sync/index.ts b/packages/astro/src/core/sync/index.ts index bfaed6c2f9fd..395f431771e4 100644 --- a/packages/astro/src/core/sync/index.ts +++ b/packages/astro/src/core/sync/index.ts @@ -81,7 +81,7 @@ export async function syncInternal( const tempViteServer = await createServer( await createVite( { - server: { middlewareMode: true, hmr: false, watch: { ignored: ['**'] } }, + server: { middlewareMode: true, hmr: false, watch: null }, optimizeDeps: { disabled: true }, ssr: { external: [] }, logLevel: 'silent', From 9b1f2517836141d4b30f9de315b165383d6191f2 Mon Sep 17 00:00:00 2001 From: Bjorn Lu Date: Fri, 12 Jan 2024 15:53:00 +0800 Subject: [PATCH 17/23] Use node:test and node:assert/strict (#9649) --- CONTRIBUTING.md | 14 ++++++- packages/upgrade/package.json | 4 +- packages/upgrade/test/context.test.js | 13 +++--- packages/upgrade/test/install.test.js | 57 ++++++++++++++------------- packages/upgrade/test/utils.js | 1 + packages/upgrade/test/verify.test.js | 23 +++++------ pnpm-lock.yaml | 6 --- scripts/cmd/test.js | 51 ++++++++++++++++++++++++ scripts/index.js | 5 +++ 9 files changed, 119 insertions(+), 55 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 05a07e125484..73f4cc2aec65 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -88,13 +88,25 @@ DEBUG=vite:[name] astro dev # debug specific process, e.g. "vite:deps" or "vit # run this in the top-level project root to run all tests pnpm run test # run only a few tests in the `astro` package, great for working on a single feature -# (example - `pnpm run test:match "cli"` runs `cli.test.js`) +# (example - `pnpm run test:match "cli"` runs tests with "cli" in the name) pnpm run test:match "$STRING_MATCH" # run tests on another package # (example - `pnpm --filter @astrojs/rss run test` runs `packages/astro-rss/test/rss.test.js`) pnpm --filter $STRING_MATCH run test ``` +Most tests use [`mocha`](https://mochajs.org) as the test runner. We're slowly migrating to use [`node:test`](https://nodejs.org/api/test.html) instead through the custom [`astro-scripts test`](./scripts/cmd/test.js) command. For packages that use `node:test`, you can run these commands in their directories: + +```shell +# run all of the package's tests +pnpm run test +# run only a few tests in the package +# (example - `pnpm run test -m "cli"` runs tests with "cli" in the name) +pnpm run test -m "$STRING_MATCH" +# run a single test file, you can use `node --test` directly +node --test ./test/foo.test.js +``` + #### E2E tests Certain features, like HMR and client hydration, need end-to-end tests to verify functionality in the dev server. [Playwright](https://playwright.dev/) is used to test against the dev server. diff --git a/packages/upgrade/package.json b/packages/upgrade/package.json index 802b180157ec..a15c90616ed7 100644 --- a/packages/upgrade/package.json +++ b/packages/upgrade/package.json @@ -20,7 +20,7 @@ "build": "astro-scripts build \"src/index.ts\" --bundle && tsc", "build:ci": "astro-scripts build \"src/index.ts\" --bundle", "dev": "astro-scripts dev \"src/**/*.ts\"", - "test": "mocha --exit --timeout 20000 --parallel" + "test": "astro-scripts test \"test/**/*.test.js\"" }, "files": [ "dist", @@ -39,8 +39,6 @@ "@types/which-pm-runs": "^1.0.0", "arg": "^5.0.2", "astro-scripts": "workspace:*", - "chai": "^4.3.7", - "mocha": "^10.2.0", "strip-ansi": "^7.1.0" }, "engines": { diff --git a/packages/upgrade/test/context.test.js b/packages/upgrade/test/context.test.js index 5b6b8c6b2201..714a7b64ac43 100644 --- a/packages/upgrade/test/context.test.js +++ b/packages/upgrade/test/context.test.js @@ -1,19 +1,20 @@ -import { expect } from 'chai'; +import { describe, it } from 'node:test'; +import * as assert from 'node:assert/strict'; import { getContext } from '../dist/index.js'; describe('context', () => { it('no arguments', async () => { const ctx = await getContext([]); - expect(ctx.version).to.eq('latest'); - expect(ctx.dryRun).to.be.undefined; + assert.equal(ctx.version, 'latest'); + assert.equal(ctx.dryRun, undefined); }); it('tag', async () => { const ctx = await getContext(['beta']); - expect(ctx.version).to.eq('beta'); - expect(ctx.dryRun).to.be.undefined; + assert.equal(ctx.version, 'beta'); + assert.equal(ctx.dryRun, undefined); }); it('dry run', async () => { const ctx = await getContext(['--dry-run']); - expect(ctx.dryRun).to.eq(true); + assert.equal(ctx.dryRun, true); }); }); diff --git a/packages/upgrade/test/install.test.js b/packages/upgrade/test/install.test.js index 05c46cdce9ef..b4158d264845 100644 --- a/packages/upgrade/test/install.test.js +++ b/packages/upgrade/test/install.test.js @@ -1,4 +1,5 @@ -import { expect } from 'chai'; +import { describe, it } from 'node:test'; +import * as assert from 'node:assert/strict'; import { setup } from './utils.js'; import { install } from '../dist/index.js'; @@ -23,7 +24,7 @@ describe('install', () => { ], }; await install(context); - expect(fixture.hasMessage('◼ astro is up to date on v1.0.0')).to.be.true; + assert.equal(fixture.hasMessage('◼ astro is up to date on v1.0.0'), true); }); it('patch', async () => { @@ -38,7 +39,7 @@ describe('install', () => { ], }; await install(context); - expect(fixture.hasMessage('● astro can be updated to v1.0.1')).to.be.true; + assert.equal(fixture.hasMessage('● astro can be updated to v1.0.1'), true); }); it('minor', async () => { @@ -53,7 +54,7 @@ describe('install', () => { ], }; await install(context); - expect(fixture.hasMessage('● astro can be updated to v1.2.0')).to.be.true; + assert.equal(fixture.hasMessage('● astro can be updated to v1.2.0'), true); }); it('major (reject)', async () => { @@ -80,10 +81,10 @@ describe('install', () => { ], }; await install(context); - expect(fixture.hasMessage('▲ astro can be updated to v2.0.0')).to.be.true; - expect(prompted).to.be.true; - expect(exitCode).to.eq(0); - expect(fixture.hasMessage('check Be sure to follow the CHANGELOG.')).to.be.false; + assert.equal(fixture.hasMessage('▲ astro can be updated to v2.0.0'), true); + assert.equal(prompted, true); + assert.equal(exitCode, 0); + assert.equal(fixture.hasMessage('check Be sure to follow the CHANGELOG.'), false); }); it('major (accept)', async () => { @@ -110,10 +111,10 @@ describe('install', () => { ], }; await install(context); - expect(fixture.hasMessage('▲ astro can be updated to v2.0.0')).to.be.true; - expect(prompted).to.be.true; - expect(exitCode).to.be.undefined; - expect(fixture.hasMessage('check Be sure to follow the CHANGELOG.')).to.be.true; + assert.equal(fixture.hasMessage('▲ astro can be updated to v2.0.0'), true); + assert.equal(prompted, true); + assert.equal(exitCode, undefined); + assert.equal(fixture.hasMessage('check Be sure to follow the CHANGELOG.'), true); }); it('multiple major', async () => { @@ -148,14 +149,14 @@ describe('install', () => { ], }; await install(context); - expect(fixture.hasMessage('▲ a can be updated to v2.0.0')).to.be.true; - expect(fixture.hasMessage('▲ b can be updated to v7.0.0')).to.be.true; - expect(prompted).to.be.true; - expect(exitCode).to.be.undefined; + assert.equal(fixture.hasMessage('▲ a can be updated to v2.0.0'), true); + assert.equal(fixture.hasMessage('▲ b can be updated to v7.0.0'), true); + assert.equal(prompted, true); + assert.equal(exitCode, undefined); const [changelog, a, b] = fixture.messages().slice(-5); - expect(changelog).to.match(/^check/); - expect(a).to.match(/^a/); - expect(b).to.match(/^b/); + assert.match(changelog, /^check/); + assert.match(a, /^a/); + assert.match(b, /^b/); }); it('current patch minor major', async () => { @@ -197,15 +198,15 @@ describe('install', () => { ], }; await install(context); - expect(fixture.hasMessage('◼ current is up to date on v1.0.0')).to.be.true; - expect(fixture.hasMessage('● patch can be updated to v1.0.1')).to.be.true; - expect(fixture.hasMessage('● minor can be updated to v1.2.0')).to.be.true; - expect(fixture.hasMessage('▲ major can be updated to v3.0.0')).to.be.true; - expect(prompted).to.be.true; - expect(exitCode).to.be.undefined; - expect(fixture.hasMessage('check Be sure to follow the CHANGELOG.')).to.be.true; + assert.equal(fixture.hasMessage('◼ current is up to date on v1.0.0'), true); + assert.equal(fixture.hasMessage('● patch can be updated to v1.0.1'), true); + assert.equal(fixture.hasMessage('● minor can be updated to v1.2.0'), true); + assert.equal(fixture.hasMessage('▲ major can be updated to v3.0.0'), true); + assert.equal(prompted, true); + assert.equal(exitCode, undefined); + assert.equal(fixture.hasMessage('check Be sure to follow the CHANGELOG.'), true); const [changelog, major] = fixture.messages().slice(-4); - expect(changelog).to.match(/^check/); - expect(major).to.match(/^major/); + assert.match(changelog, /^check/); + assert.match(major, /^major/) }); }); diff --git a/packages/upgrade/test/utils.js b/packages/upgrade/test/utils.js index ff5d5dd832af..691e63d90a81 100644 --- a/packages/upgrade/test/utils.js +++ b/packages/upgrade/test/utils.js @@ -1,3 +1,4 @@ +import { before, beforeEach } from 'node:test'; import { setStdout } from '../dist/index.js'; import stripAnsi from 'strip-ansi'; diff --git a/packages/upgrade/test/verify.test.js b/packages/upgrade/test/verify.test.js index a54cb6bb5085..3b9d4b3bc12d 100644 --- a/packages/upgrade/test/verify.test.js +++ b/packages/upgrade/test/verify.test.js @@ -1,4 +1,5 @@ -import { expect } from 'chai'; +import { describe, it, beforeEach } from 'node:test'; +import * as assert from 'node:assert/strict'; import { collectPackageInfo } from '../dist/index.js'; describe('collectPackageInfo', () => { @@ -16,61 +17,61 @@ describe('collectPackageInfo', () => { it('detects astro', async () => { collectPackageInfo(context, { astro: '1.0.0' }, {}); - expect(context.packages).deep.equal([ + assert.deepEqual(context.packages, [ { name: 'astro', currentVersion: '1.0.0', targetVersion: 'latest' }, ]); }); it('detects @astrojs', async () => { collectPackageInfo(context, { '@astrojs/preact': '1.0.0' }, {}); - expect(context.packages).deep.equal([ + assert.deepEqual(context.packages, [ { name: '@astrojs/preact', currentVersion: '1.0.0', targetVersion: 'latest' }, ]); }); it('supports ^ prefixes', async () => { collectPackageInfo(context, { astro: '^1.0.0' }, {}); - expect(context.packages).deep.equal([ + assert.deepEqual(context.packages, [ { name: 'astro', currentVersion: '^1.0.0', targetVersion: 'latest' }, ]); }); it('supports ~ prefixes', async () => { collectPackageInfo(context, { astro: '~1.0.0' }, {}); - expect(context.packages).deep.equal([ + assert.deepEqual(context.packages, [ { name: 'astro', currentVersion: '~1.0.0', targetVersion: 'latest' }, ]); }); it('supports prereleases', async () => { collectPackageInfo(context, { astro: '1.0.0-beta.0' }, {}); - expect(context.packages).deep.equal([ + assert.deepEqual(context.packages, [ { name: 'astro', currentVersion: '1.0.0-beta.0', targetVersion: 'latest' }, ]); }); it('ignores self', async () => { collectPackageInfo(context, { '@astrojs/upgrade': '0.0.1' }, {}); - expect(context.packages).deep.equal([]); + assert.deepEqual(context.packages, []); }); it('ignores linked packages', async () => { collectPackageInfo(context, { '@astrojs/preact': 'link:../packages/preact' }, {}); - expect(context.packages).deep.equal([]); + assert.deepEqual(context.packages, []); }); it('ignores workspace packages', async () => { collectPackageInfo(context, { '@astrojs/preact': 'workspace:*' }, {}); - expect(context.packages).deep.equal([]); + assert.deepEqual(context.packages, []); }); it('ignores github packages', async () => { collectPackageInfo(context, { '@astrojs/preact': 'github:withastro/astro' }, {}); - expect(context.packages).deep.equal([]); + assert.deepEqual(context.packages, []); }); it('ignores tag', async () => { collectPackageInfo(context, { '@astrojs/preact': 'beta' }, {}); - expect(context.packages).deep.equal([]); + assert.deepEqual(context.packages, []); }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c1a32f4441b3..ab455435e4b4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5148,12 +5148,6 @@ importers: astro-scripts: specifier: workspace:* version: link:../../scripts - chai: - specifier: ^4.3.7 - version: 4.3.10 - mocha: - specifier: ^10.2.0 - version: 10.2.0 strip-ansi: specifier: ^7.1.0 version: 7.1.0 diff --git a/scripts/cmd/test.js b/scripts/cmd/test.js index e69de29bb2d1..84f6d9742187 100644 --- a/scripts/cmd/test.js +++ b/scripts/cmd/test.js @@ -0,0 +1,51 @@ +import { run } from 'node:test'; +import { spec } from 'node:test/reporters'; +import arg from 'arg'; +import glob from 'tiny-glob'; + +const isCI = !!process.env.CI; +const defaultTimeout = isCI ? 30000 : 20000; + +export default async function test() { + const args = arg({ + '--match': String, // aka --test-name-pattern: https://nodejs.org/api/test.html#filtering-tests-by-name + '--only': Boolean, // aka --test-only: https://nodejs.org/api/test.html#only-tests + '--parallel': Boolean, // aka --test-concurrency: https://nodejs.org/api/test.html#test-runner-execution-model + '--watch': Boolean, // experimental: https://nodejs.org/api/test.html#watch-mode + '--timeout': Number, // Test timeout in milliseconds (default: 30000ms) + '--setup': String, // Test setup file + // Aliases + '-m': '--match', + '-o': '--only', + '-p': '--parallel', + '-w': '--watch', + '-t': '--timeout', + '-s': '--setup', + }); + + const pattern = args._[1]; + if (!pattern) throw new Error('Missing test glob pattern'); + + const files = await glob(pattern, { filesOnly: true, absolute: true }); + + // For some reason, the `only` option does not work and we need to explicitly set the CLI flag instead. + // Node.js requires opt-in to run .only tests :( + // https://nodejs.org/api/test.html#only-tests + if (args['--only']) { + process.env.NODE_OPTIONS ??= ''; + process.env.NODE_OPTIONS += ' --test-only'; + } + + // https://nodejs.org/api/test.html#runoptions + run({ + files, + testNamePatterns: args['--match'], + concurrency: args['--parallel'], + only: args['--only'], + setup: args['--setup'], + watch: args['--watch'], + timeout: args['--timeout'] ?? defaultTimeout, // Node.js defaults to Infinity, so set better fallback + }) + .pipe(new spec()) + .pipe(process.stdout); +} diff --git a/scripts/index.js b/scripts/index.js index 249eac53d135..381500ac4e69 100755 --- a/scripts/index.js +++ b/scripts/index.js @@ -18,6 +18,11 @@ export default async function run() { await prebuild(...args); break; } + case 'test': { + const { default: test } = await import('./cmd/test.js'); + await test(...args); + break; + } } } From 529148476377f20b04065f0475142bc58b6581b1 Mon Sep 17 00:00:00 2001 From: Bjorn Lu Date: Fri, 12 Jan 2024 07:54:11 +0000 Subject: [PATCH 18/23] [ci] format --- packages/upgrade/test/install.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/upgrade/test/install.test.js b/packages/upgrade/test/install.test.js index b4158d264845..a2a0e5144eae 100644 --- a/packages/upgrade/test/install.test.js +++ b/packages/upgrade/test/install.test.js @@ -207,6 +207,6 @@ describe('install', () => { assert.equal(fixture.hasMessage('check Be sure to follow the CHANGELOG.'), true); const [changelog, major] = fixture.messages().slice(-4); assert.match(changelog, /^check/); - assert.match(major, /^major/) + assert.match(major, /^major/); }); }); From 22152bc0c11bcefca2a545b331ebfe53cc0a0ec5 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Fri, 12 Jan 2024 14:14:26 +0000 Subject: [PATCH 19/23] fix(i18n): emit an error when the index isn't found (#9678) * fix(i18n): emit an error when the index isn't found * changeset * Update .changeset/proud-guests-bake.md Co-authored-by: Sarah Rainsberger * rename * Update packages/astro/src/core/errors/errors-data.ts Co-authored-by: Florian Lefebvre --------- Co-authored-by: Sarah Rainsberger Co-authored-by: Florian Lefebvre --- .changeset/proud-guests-bake.md | 5 +++++ packages/astro/src/core/errors/errors-data.ts | 7 +++++++ .../astro/src/core/routing/manifest/create.ts | 17 +++++++++++++++++ 3 files changed, 29 insertions(+) create mode 100644 .changeset/proud-guests-bake.md diff --git a/.changeset/proud-guests-bake.md b/.changeset/proud-guests-bake.md new file mode 100644 index 000000000000..9788786a9469 --- /dev/null +++ b/.changeset/proud-guests-bake.md @@ -0,0 +1,5 @@ +--- +"astro": patch +--- + +Adds an error during the build phase in case `i18n.routing.prefixDefaultLocale` is set to `true` and the index page is missing. diff --git a/packages/astro/src/core/errors/errors-data.ts b/packages/astro/src/core/errors/errors-data.ts index cb9a430b5a2f..fb463a77fe1c 100644 --- a/packages/astro/src/core/errors/errors-data.ts +++ b/packages/astro/src/core/errors/errors-data.ts @@ -1001,6 +1001,13 @@ export const MissingLocale = { `The locale/path \`${locale}\` does not exist in the configured \`i18n.locales\`.`, } satisfies ErrorData; +export const MissingIndexForInternationalization = { + name: 'MissingIndexForInternationalizationError', + title: 'Index page not found.', + message: (src: string) => + `Astro couldn't find the index URL. This index page is required to create a redirect from the index URL to the index URL of the default locale. \nCreate an index page in \`${src}\``, +} satisfies ErrorData; + /** * @docs * @description diff --git a/packages/astro/src/core/routing/manifest/create.ts b/packages/astro/src/core/routing/manifest/create.ts index 22f399f9fc26..8aadd6e66c5e 100644 --- a/packages/astro/src/core/routing/manifest/create.ts +++ b/packages/astro/src/core/routing/manifest/create.ts @@ -18,6 +18,8 @@ import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from '../../constants.js'; import { removeLeadingForwardSlash, slash } from '../../path.js'; import { resolvePages } from '../../util.js'; import { getRouteGenerator } from './generator.js'; +import { AstroError } from '../../errors/index.js'; +import { MissingIndexForInternationalization } from '../../errors/errors-data.js'; const require = createRequire(import.meta.url); interface Item { @@ -513,6 +515,21 @@ export function createRouteManifest( }); const i18n = settings.config.i18n; if (i18n) { + // First we check if the user doesn't have an index page. + if (i18n.routing === 'prefix-always') { + let index = routes.find((route) => route.route === '/'); + if (!index) { + let relativePath = path.relative( + fileURLToPath(settings.config.root), + fileURLToPath(new URL('pages', settings.config.srcDir)) + ); + throw new AstroError({ + ...MissingIndexForInternationalization, + message: MissingIndexForInternationalization.message(relativePath), + }); + } + } + // In this block of code we group routes based on their locale // A map like: locale => RouteData[] From 85741406fc9aced97dbf9610ef3cebc8020d6333 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Mon, 8 Jan 2024 08:47:32 +0000 Subject: [PATCH 20/23] feat(i18n): add option to disable redirect to default language --- packages/astro/src/i18n/middleware.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/astro/src/i18n/middleware.ts b/packages/astro/src/i18n/middleware.ts index 2ec796a05ef3..ee23a0421b01 100644 --- a/packages/astro/src/i18n/middleware.ts +++ b/packages/astro/src/i18n/middleware.ts @@ -2,6 +2,7 @@ import { appendForwardSlash, joinPaths } from '@astrojs/internal-helpers/path'; import type { Locales, MiddlewareHandler, RouteData, SSRManifest } from '../@types/astro.js'; import type { PipelineHookFunction } from '../core/pipeline.js'; import { getPathByLocale, normalizeTheLocale } from './index.js'; +import { shouldAppendForwardSlash } from '../core/build/util.js'; const routeDataSymbol = Symbol.for('astro.routeData'); From e3255cbd8027872951bc31221617496848f8d94a Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Fri, 12 Jan 2024 14:18:05 +0000 Subject: [PATCH 21/23] chore: rebase --- packages/astro/src/core/routing/manifest/create.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/astro/src/core/routing/manifest/create.ts b/packages/astro/src/core/routing/manifest/create.ts index 8aadd6e66c5e..e74df1eed47e 100644 --- a/packages/astro/src/core/routing/manifest/create.ts +++ b/packages/astro/src/core/routing/manifest/create.ts @@ -516,7 +516,7 @@ export function createRouteManifest( const i18n = settings.config.i18n; if (i18n) { // First we check if the user doesn't have an index page. - if (i18n.routing === 'prefix-always') { + if (i18n.routing === 'pathname-prefix-always') { let index = routes.find((route) => route.route === '/'); if (!index) { let relativePath = path.relative( From 668fb66f38c5b002f5fb7aafad2551032bc1f064 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Tue, 16 Jan 2024 09:55:29 +0000 Subject: [PATCH 22/23] Update packages/astro/src/@types/astro.ts Co-authored-by: Bjorn Lu --- packages/astro/src/@types/astro.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 9561971ed29f..fca4790491d1 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -1502,7 +1502,7 @@ export interface AstroUserConfig { * @version 4.2.0 * @description * - * Configures whether or not the home URL (`/`) generated by `src/pages/astro.index` + * Configures whether or not the home URL (`/`) generated by `src/pages/index.astro` * will redirect to `/[defaultLocale]` when `prefixDefaultLocale: true` is set. * * Set `redirectToDefaultLocale: false` to disable this automatic redirection at the root of your site: From 92c83ede9488b40f520d6ff2dc61969c0a553cc2 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Tue, 16 Jan 2024 10:00:06 +0000 Subject: [PATCH 23/23] lock file update --- pnpm-lock.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ab455435e4b4..f8047767165a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2508,6 +2508,12 @@ importers: specifier: workspace:* version: link:../../.. + packages/astro/test/fixtures/core-image-deletion: + dependencies: + astro: + specifier: workspace:* + version: link:../../.. + packages/astro/test/fixtures/core-image-errors: dependencies: astro: