0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-03-10 23:01:26 -05:00

feat(next): make defineConfig generic (#12243)

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
This commit is contained in:
Florian Lefebvre 2024-10-21 18:26:00 +02:00 committed by GitHub
parent 166ea961fe
commit eb41d13162
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 127 additions and 84 deletions

View file

@ -0,0 +1,5 @@
---
'astro': minor
---
Improves `defineConfig` type safety. TypeScript will now error if a group of related configuration options do not have consistent types. For example, you will now see an error if your language set for `i18n.defaultLocale` is not one of the supported locales specified in `i18n.locales`.

View file

@ -1,40 +0,0 @@
/// <reference path='./client.d.ts' />
type ViteUserConfig = import('vite').UserConfig;
type ViteUserConfigFn = import('vite').UserConfigFn;
type AstroUserConfig = import('./dist/types/public/config.js').AstroUserConfig;
type AstroInlineConfig = import('./dist/types/public/config.js').AstroInlineConfig;
type ImageServiceConfig = import('./dist/types/public/config.js').ImageServiceConfig;
type SharpImageServiceConfig = import('./dist/assets/services/sharp.js').SharpImageServiceConfig;
type EnvField = typeof import('./dist/env/config.js').envField;
/**
* See the full Astro Configuration API Documentation
* https://astro.build/config
*/
export function defineConfig(config: AstroUserConfig): AstroUserConfig;
/**
* Use Astro to generate a fully resolved Vite config
*/
export function getViteConfig(
config: ViteUserConfig,
inlineAstroConfig?: AstroInlineConfig,
): ViteUserConfigFn;
/**
* Return the configuration needed to use the Sharp-based image service
*/
export function sharpImageService(config?: SharpImageServiceConfig): ImageServiceConfig;
/**
* Return the configuration needed to use the passthrough image service. This image services does not perform
* any image transformations, and is mainly useful when your platform does not support other image services, or you are
* not using Astro's built-in image processing.
* See: https://docs.astro.build/en/guides/images/#configure-no-op-passthrough-service
*/
export function passthroughImageService(): ImageServiceConfig;
/**
* Return a valid env field to use in this Astro config for `experimental.env.schema`.
*/
export declare const envField: EnvField;

View file

@ -1,16 +0,0 @@
export { defineConfig, getViteConfig } from './dist/config/index.js';
export { envField } from './dist/env/config.js';
export function sharpImageService(config = {}) {
return {
entrypoint: 'astro/assets/services/sharp',
config,
};
}
export function passthroughImageService() {
return {
entrypoint: 'astro/assets/services/noop',
config: {},
};
}

View file

@ -46,10 +46,7 @@
},
"./compiler-runtime": "./dist/runtime/compiler/index.js",
"./runtime/*": "./dist/runtime/*",
"./config": {
"types": "./config.d.ts",
"default": "./config.mjs"
},
"./config": "./dist/config/entrypoint.js",
"./container": {
"types": "./dist/container/index.d.ts",
"default": "./dist/container/index.js"
@ -93,8 +90,6 @@
"types",
"astro.js",
"index.d.ts",
"config.d.ts",
"config.mjs",
"zod.d.ts",
"zod.mjs",
"env.d.ts",

View file

@ -0,0 +1,30 @@
// IMPORTANT: this file is the entrypoint for "astro/config". Keep it as light as possible!
import type { SharpImageServiceConfig } from '../assets/services/sharp.js';
import type { ImageServiceConfig } from '../types/public/index.js';
export { defineConfig, getViteConfig } from './index.js';
export { envField } from '../env/config.js';
/**
* Return the configuration needed to use the Sharp-based image service
*/
export function sharpImageService(config: SharpImageServiceConfig = {}): ImageServiceConfig {
return {
entrypoint: 'astro/assets/services/sharp',
config,
};
}
/**
* Return the configuration needed to use the passthrough image service. This image services does not perform
* any image transformations, and is mainly useful when your platform does not support other image services, or you are
* not using Astro's built-in image processing.
* See: https://docs.astro.build/en/guides/images/#configure-no-op-passthrough-service
*/
export function passthroughImageService(): ImageServiceConfig {
return {
entrypoint: 'astro/assets/services/noop',
config: {},
};
}

View file

@ -1,13 +1,22 @@
import type { UserConfig as ViteUserConfig, UserConfigFn as ViteUserConfigFn } from 'vite';
import { Logger } from '../core/logger/core.js';
import { createRouteManifest } from '../core/routing/index.js';
import type { AstroInlineConfig, AstroUserConfig } from '../types/public/config.js';
import type { AstroInlineConfig, AstroUserConfig, Locales } from '../types/public/config.js';
import { createDevelopmentManifest } from '../vite-plugin-astro-server/plugin.js';
export function defineConfig(config: AstroUserConfig) {
/**
* See the full Astro Configuration API Documentation
* https://astro.build/config
*/
export function defineConfig<const TLocales extends Locales = never>(
config: AstroUserConfig<TLocales>,
) {
return config;
}
/**
* Use Astro to generate a fully resolved Vite config
*/
export function getViteConfig(
userViteConfig: ViteUserConfig,
inlineAstroConfig: AstroInlineConfig = {},

View file

@ -1160,8 +1160,8 @@ export const UnhandledRejection = {
* import { defineConfig } from 'astro'
* export default defineConfig({
* i18n: {
* defaultLocale: 'en',
* locales: ['en', 'fr'],
* defaultLocale: 'en',
* },
* })
* ```

View file

@ -10,7 +10,7 @@ import type {
} from './schema.js';
/**
* Return a valid env field to use in this Astro config for `experimental.env.schema`.
* Return a valid env field to use in this Astro config for `env.schema`.
*/
export const envField = {
string: (options: StringFieldInput): StringField => ({

View file

@ -16,6 +16,14 @@ import type { AstroIntegration } from './integrations.js';
export type Locales = (string | { codes: string[]; path: string })[];
type NormalizeLocales<T extends Locales> = {
[K in keyof T]: T[K] extends string
? T[K]
: T[K] extends { codes: Array<string> }
? T[K]['codes'][number]
: never;
}[number];
export interface ImageServiceConfig<T extends Record<string, any> = Record<string, any>> {
entrypoint: 'astro/assets/services/sharp' | (string & {});
config?: T;
@ -101,8 +109,9 @@ export interface ViteUserConfig extends OriginalViteUserConfig {
/**
* Astro User Config
* Docs: https://docs.astro.build/reference/configuration-reference/
*/
export interface AstroUserConfig {
*
* Generics do not follow semver and may change at any time.
*/ export interface AstroUserConfig<TLocales extends Locales = never> {
/**
* @docs
* @kind heading
@ -1203,18 +1212,6 @@ export interface AstroUserConfig {
* See our guide for more information on [internationalization in Astro](/en/guides/internationalization/)
*/
i18n?: {
/**
* @docs
* @name i18n.defaultLocale
* @type {string}
* @version 3.5.0
* @description
*
* The default locale of your website/application. This is a required field.
*
* No particular language format or syntax is enforced, but we suggest using lower-case and hyphens as needed (e.g. "es", "pt-br") for greatest compatibility.
*/
defaultLocale: string;
/**
* @docs
* @name i18n.locales
@ -1222,13 +1219,26 @@ export interface AstroUserConfig {
* @version 3.5.0
* @description
*
* A list of all locales supported by the website, including the `defaultLocale`. This is a required field.
* A list of all locales supported by the website. This is a required field.
*
* Languages can be listed either as individual codes (e.g. `['en', 'es', 'pt-br']`) or mapped to a shared `path` of codes (e.g. `{ path: "english", codes: ["en", "en-US"]}`). These codes will be used to determine the URL structure of your deployed site.
*
* No particular language code format or syntax is enforced, but your project folders containing your content files must match exactly the `locales` items in the list. In the case of multiple `codes` pointing to a custom URL path prefix, store your content files in a folder with the same name as the `path` configured.
*/
locales: Locales;
locales: [TLocales] extends [never] ? Locales : TLocales;
/**
* @docs
* @name i18n.defaultLocale
* @type {string}
* @version 3.5.0
* @description
*
* The default locale of your website/application, that is one of the specified `locales`. This is a required field.
*
* No particular language format or syntax is enforced, but we suggest using lower-case and hyphens as needed (e.g. "es", "pt-br") for greatest compatibility.
*/
defaultLocale: [TLocales] extends [never] ? string : NormalizeLocales<NoInfer<TLocales>>;
/**
* @docs
@ -1258,7 +1268,14 @@ export interface AstroUserConfig {
* })
* ```
*/
fallback?: Record<string, string>;
fallback?: [TLocales] extends [never]
? Record<string, string>
: {
[Locale in NormalizeLocales<NoInfer<TLocales>>]?: Exclude<
NormalizeLocales<NoInfer<TLocales>>,
Locale
>;
};
/**
* @docs
@ -1444,7 +1461,9 @@ export interface AstroUserConfig {
*
* See the [Internationalization Guide](https://docs.astro.build/en/guides/internationalization/#domains) for more details, including the limitations of this feature.
*/
domains?: Record<string, string>;
domains?: [TLocales] extends [never]
? Record<string, string>
: Partial<Record<NormalizeLocales<NoInfer<TLocales>>, string>>;
};
/** ! WARNING: SUBJECT TO CHANGE */

View file

@ -0,0 +1,41 @@
import { describe, it } from 'node:test';
import { defineConfig } from '../../dist/config/index.js';
import type { AstroUserConfig } from '../../dist/types/public/index.js';
import { expectTypeOf } from 'expect-type';
describe('defineConfig()', () => {
it('Infers generics correctly', () => {
const config_0 = defineConfig({});
expectTypeOf(config_0).toEqualTypeOf<AstroUserConfig<never>>();
expectTypeOf(config_0.i18n!.defaultLocale).toEqualTypeOf<string>();
const config_1 = defineConfig({
i18n: {
locales: ['en'],
defaultLocale: 'en',
},
});
expectTypeOf(config_1).toEqualTypeOf<AstroUserConfig<['en']>>();
expectTypeOf(config_1.i18n!.defaultLocale).toEqualTypeOf<'en'>();
const config_2 = defineConfig({
i18n: {
locales: ['en', 'fr'],
defaultLocale: 'fr',
},
});
expectTypeOf(config_2).toEqualTypeOf<AstroUserConfig<['en', 'fr']>>();
expectTypeOf(config_2.i18n!.defaultLocale).toEqualTypeOf<'en' | 'fr'>();
const config_3 = defineConfig({
i18n: {
locales: ['en', { path: 'french', codes: ['fr', 'fr-FR'] }],
defaultLocale: 'en',
},
});
expectTypeOf(config_3).toEqualTypeOf<
AstroUserConfig<['en', { readonly path: 'french'; readonly codes: ['fr', 'fr-FR'] }]>
>();
expectTypeOf(config_3.i18n!.defaultLocale).toEqualTypeOf<'en' | 'fr' | 'fr-FR'>();
});
});