Skip to content

Commit

Permalink
feat(next): make defineConfig generic (#12243)
Browse files Browse the repository at this point in the history
Co-authored-by: Sarah Rainsberger <[email protected]>
  • Loading branch information
florian-lefebvre and sarah11918 authored Oct 21, 2024
1 parent 166ea96 commit eb41d13
Show file tree
Hide file tree
Showing 10 changed files with 125 additions and 82 deletions.
5 changes: 5 additions & 0 deletions .changeset/forty-trains-notice.md
Original file line number Diff line number Diff line change
@@ -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`.
40 changes: 0 additions & 40 deletions packages/astro/config.d.ts

This file was deleted.

16 changes: 0 additions & 16 deletions packages/astro/config.mjs

This file was deleted.

7 changes: 1 addition & 6 deletions packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -93,8 +90,6 @@
"types",
"astro.js",
"index.d.ts",
"config.d.ts",
"config.mjs",
"zod.d.ts",
"zod.mjs",
"env.d.ts",
Expand Down
30 changes: 30 additions & 0 deletions packages/astro/src/config/entrypoint.ts
Original file line number Diff line number Diff line change
@@ -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: {},
};
}
13 changes: 11 additions & 2 deletions packages/astro/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -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 = {},
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/core/errors/errors-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1160,8 +1160,8 @@ export const UnhandledRejection = {
* import { defineConfig } from 'astro'
* export default defineConfig({
* i18n: {
* defaultLocale: 'en',
* locales: ['en', 'fr'],
* defaultLocale: 'en',
* },
* })
* ```
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/env/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 => ({
Expand Down
51 changes: 35 additions & 16 deletions packages/astro/src/types/public/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1205,30 +1214,31 @@ export interface AstroUserConfig {
i18n?: {
/**
* @docs
* @name i18n.defaultLocale
* @type {string}
* @name i18n.locales
* @type {Locales}
* @version 3.5.0
* @description
*
* The default locale of your website/application. This is a required field.
* A list of all locales supported by the website. 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.
* 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.
*/
defaultLocale: string;
locales: [TLocales] extends [never] ? Locales : TLocales;

/**
* @docs
* @name i18n.locales
* @type {Locales}
* @name i18n.defaultLocale
* @type {string}
* @version 3.5.0
* @description
*
* A list of all locales supported by the website, including the `defaultLocale`. This is a required field.
* The default locale of your website/application, that is one of the specified `locales`. 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.
* 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.
*/
locales: Locales;
defaultLocale: [TLocales] extends [never] ? string : NormalizeLocales<NoInfer<TLocales>>;

/**
* @docs
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 */
Expand Down
41 changes: 41 additions & 0 deletions packages/astro/test/types/define-config.ts
Original file line number Diff line number Diff line change
@@ -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'>();
});
});

0 comments on commit eb41d13

Please sign in to comment.