0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2024-12-30 22:03:56 -05:00

feat: Refactor integration hooks type (#11304)

* feat: Refactor integration hooks type

* Revert formatting changes

* More detailed changelog

* Add changeset for Astro DB

* Apply suggestions from code review

Co-authored-by: Florian Lefebvre <contact@florian-lefebvre.dev>
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

---------

Co-authored-by: Florian Lefebvre <contact@florian-lefebvre.dev>
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
This commit is contained in:
Luiz Ferraz 2024-07-17 12:58:57 -03:00 committed by GitHub
parent 683417f334
commit 2e70741362
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 179 additions and 91 deletions

View file

@ -0,0 +1,23 @@
---
'@astrojs/db': minor
---
Removes the `AstroDbIntegration` type
Astro integration hooks can now be extended and as such `@astrojs/db` no longer needs to declare it's own integration type. Using `AstroIntegration` will have the same type.
If you were using the `AstroDbIntegration` type, apply this change to your integration code:
```diff
- import { defineDbIntegration, type AstroDbIntegration } from '@astrojs/db/utils';
+ import { defineDbIntegration } from '@astrojs/db/utils';
import type { AstroIntegration } from 'astro';
- export default (): AstroDbIntegration => {
+ export default (): AstroIntegration => {
return defineDbIntegration({
name: 'your-integration',
hooks: {},
});
}
```

View file

@ -0,0 +1,58 @@
---
'astro': minor
---
Refactors the type for integration hooks so that integration authors writing custom integration hooks can now allow runtime interactions between their integration and other integrations.
This internal change should not break existing code for integration authors.
To declare your own hooks for your integration, extend the `Astro.IntegrationHooks` interface:
```ts
// your-integration/types.ts
declare global {
namespace Astro {
interface IntegrationHooks {
'myLib:eventHappened': (your: string, parameters: number) => Promise<void>;
}
}
}
```
Call your hooks on all other integrations installed in a project at the appropriate time. For example, you can call your hook on initialization before either the Vite or Astro config have resolved:
```ts
// your-integration/index.ts
import './types.ts';
export default (): AstroIntegration => {
return {
name: 'your-integration',
hooks: {
'astro:config:setup': async ({ config }) => {
for (const integration of config.integrations) {
await integration.hooks['myLib:eventHappened'].?('your values', 123);
}
},
}
}
}
```
Other integrations can also now declare your hooks:
```ts
// other-integration/index.ts
import 'your-integration/types.ts';
export default (): AstroIntegration => {
return {
name: 'other-integration',
hooks: {
'myLib:eventHappened': async (your, values) => {
// ...
},
}
}
}
```

View file

@ -3056,83 +3056,92 @@ export type HookParameters<
Fn = AstroIntegration['hooks'][Hook],
> = Fn extends (...args: any) => any ? Parameters<Fn>[0] : never;
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Astro {
export interface IntegrationHooks {
'astro:config:setup': (options: {
config: AstroConfig;
command: 'dev' | 'build' | 'preview';
isRestart: boolean;
updateConfig: (newConfig: DeepPartial<AstroConfig>) => AstroConfig;
addRenderer: (renderer: AstroRenderer) => void;
addWatchFile: (path: URL | string) => void;
injectScript: (stage: InjectedScriptStage, content: string) => void;
injectRoute: (injectRoute: InjectedRoute) => void;
addClientDirective: (directive: ClientDirectiveConfig) => void;
/**
* @deprecated Use `addDevToolbarApp` instead.
* TODO: Fully remove in Astro 5.0
*/
addDevOverlayPlugin: (entrypoint: string) => void;
// TODO: Deprecate the `string` overload once a few apps have been migrated to the new API.
addDevToolbarApp: (entrypoint: DevToolbarAppEntry | string) => void;
addMiddleware: (mid: AstroIntegrationMiddleware) => void;
logger: AstroIntegrationLogger;
// TODO: Add support for `injectElement()` for full HTML element injection, not just scripts.
// This may require some refactoring of `scripts`, `styles`, and `links` into something
// more generalized. Consider the SSR use-case as well.
// injectElement: (stage: vite.HtmlTagDescriptor, element: string) => void;
}) => void | Promise<void>;
'astro:config:done': (options: {
config: AstroConfig;
setAdapter: (adapter: AstroAdapter) => void;
logger: AstroIntegrationLogger;
}) => void | Promise<void>;
'astro:server:setup': (options: {
server: vite.ViteDevServer;
logger: AstroIntegrationLogger;
toolbar: ReturnType<typeof getToolbarServerCommunicationHelpers>;
}) => void | Promise<void>;
'astro:server:start': (options: {
address: AddressInfo;
logger: AstroIntegrationLogger;
}) => void | Promise<void>;
'astro:server:done': (options: { logger: AstroIntegrationLogger }) => void | Promise<void>;
'astro:build:ssr': (options: {
manifest: SerializedSSRManifest;
/**
* This maps a {@link RouteData} to an {@link URL}, this URL represents
* the physical file you should import.
*/
entryPoints: Map<RouteData, URL>;
/**
* File path of the emitted middleware
*/
middlewareEntryPoint: URL | undefined;
logger: AstroIntegrationLogger;
}) => void | Promise<void>;
'astro:build:start': (options: { logger: AstroIntegrationLogger }) => void | Promise<void>;
'astro:build:setup': (options: {
vite: vite.InlineConfig;
pages: Map<string, PageBuildData>;
target: 'client' | 'server';
updateConfig: (newConfig: vite.InlineConfig) => void;
logger: AstroIntegrationLogger;
}) => void | Promise<void>;
'astro:build:generated': (options: {
dir: URL;
logger: AstroIntegrationLogger;
}) => void | Promise<void>;
'astro:build:done': (options: {
pages: { pathname: string }[];
dir: URL;
routes: RouteData[];
logger: AstroIntegrationLogger;
cacheManifest: boolean;
}) => void | Promise<void>;
}
}
}
export interface AstroIntegration {
/** The name of the integration. */
name: string;
/** The different hooks available to extend. */
hooks: {
'astro:config:setup'?: (options: {
config: AstroConfig;
command: 'dev' | 'build' | 'preview';
isRestart: boolean;
updateConfig: (newConfig: DeepPartial<AstroConfig>) => AstroConfig;
addRenderer: (renderer: AstroRenderer) => void;
addWatchFile: (path: URL | string) => void;
injectScript: (stage: InjectedScriptStage, content: string) => void;
injectRoute: (injectRoute: InjectedRoute) => void;
addClientDirective: (directive: ClientDirectiveConfig) => void;
/**
* @deprecated Use `addDevToolbarApp` instead.
* TODO: Fully remove in Astro 5.0
*/
addDevOverlayPlugin: (entrypoint: string) => void;
// TODO: Deprecate the `string` overload once a few apps have been migrated to the new API.
addDevToolbarApp: (entrypoint: DevToolbarAppEntry | string) => void;
addMiddleware: (mid: AstroIntegrationMiddleware) => void;
logger: AstroIntegrationLogger;
// TODO: Add support for `injectElement()` for full HTML element injection, not just scripts.
// This may require some refactoring of `scripts`, `styles`, and `links` into something
// more generalized. Consider the SSR use-case as well.
// injectElement: (stage: vite.HtmlTagDescriptor, element: string) => void;
}) => void | Promise<void>;
'astro:config:done'?: (options: {
config: AstroConfig;
setAdapter: (adapter: AstroAdapter) => void;
logger: AstroIntegrationLogger;
}) => void | Promise<void>;
'astro:server:setup'?: (options: {
server: vite.ViteDevServer;
logger: AstroIntegrationLogger;
toolbar: ReturnType<typeof getToolbarServerCommunicationHelpers>;
}) => void | Promise<void>;
'astro:server:start'?: (options: {
address: AddressInfo;
logger: AstroIntegrationLogger;
}) => void | Promise<void>;
'astro:server:done'?: (options: { logger: AstroIntegrationLogger }) => void | Promise<void>;
'astro:build:ssr'?: (options: {
manifest: SerializedSSRManifest;
/**
* This maps a {@link RouteData} to an {@link URL}, this URL represents
* the physical file you should import.
*/
entryPoints: Map<RouteData, URL>;
/**
* File path of the emitted middleware
*/
middlewareEntryPoint: URL | undefined;
logger: AstroIntegrationLogger;
}) => void | Promise<void>;
'astro:build:start'?: (options: { logger: AstroIntegrationLogger }) => void | Promise<void>;
'astro:build:setup'?: (options: {
vite: vite.InlineConfig;
pages: Map<string, PageBuildData>;
target: 'client' | 'server';
updateConfig: (newConfig: vite.InlineConfig) => void;
logger: AstroIntegrationLogger;
}) => void | Promise<void>;
'astro:build:generated'?: (options: {
dir: URL;
logger: AstroIntegrationLogger;
}) => void | Promise<void>;
'astro:build:done'?: (options: {
pages: { pathname: string }[];
dir: URL;
routes: RouteData[];
logger: AstroIntegrationLogger;
cacheManifest: boolean;
}) => void | Promise<void>;
};
[K in keyof Astro.IntegrationHooks]?: Astro.IntegrationHooks[K]
} & Partial<Record<string, unknown>>
}
export type RewritePayload = string | URL | Request;

View file

@ -2,19 +2,16 @@ import { existsSync } from 'node:fs';
import { unlink, writeFile } from 'node:fs/promises';
import { createRequire } from 'node:module';
import { fileURLToPath, pathToFileURL } from 'node:url';
import type { AstroConfig, AstroIntegration } from 'astro';
import type { AstroConfig } from 'astro';
import { build as esbuild } from 'esbuild';
import { CONFIG_FILE_NAMES, VIRTUAL_MODULE_ID } from './consts.js';
import { INTEGRATION_TABLE_CONFLICT_ERROR } from './errors.js';
import { errorMap } from './integration/error-map.js';
import { getConfigVirtualModContents } from './integration/vite-plugin-db.js';
import { dbConfigSchema } from './schemas.js';
import { type AstroDbIntegration } from './types.js';
import './types.js';
import { getAstroEnv, getDbDirectoryUrl } from './utils.js';
const isDbIntegration = (integration: AstroIntegration): integration is AstroDbIntegration =>
'astro:db:setup' in integration.hooks;
/**
* Load a users `astro:db` configuration file and additional configuration files provided by integrations.
*/
@ -31,7 +28,6 @@ export async function resolveDbConfig({
const integrationDbConfigPaths: Array<{ name: string; configEntrypoint: string | URL }> = [];
const integrationSeedPaths: Array<string | URL> = [];
for (const integration of integrations) {
if (!isDbIntegration(integration)) continue;
const { name, hooks } = integration;
if (hooks['astro:db:setup']) {
hooks['astro:db:setup']({

View file

@ -1,4 +1,3 @@
import type { AstroIntegration } from 'astro';
import type { z } from 'zod';
import type {
MaybeArray,
@ -88,13 +87,16 @@ interface LegacyIndexConfig<TColumns extends ColumnsConfig>
export type NumberColumnOpts = z.input<typeof numberColumnOptsSchema>;
export type TextColumnOpts = z.input<typeof textColumnOptsSchema>;
export type AstroDbIntegration = AstroIntegration & {
hooks: {
'astro:db:setup'?: (options: {
extendDb: (options: {
configEntrypoint?: URL | string;
seedEntrypoint?: URL | string;
}) => void;
}) => void | Promise<void>;
};
};
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Astro {
export interface IntegrationHooks {
'astro:db:setup'?: (options: {
extendDb: (options: {
configEntrypoint?: URL | string;
seedEntrypoint?: URL | string;
}) => void;
}) => void | Promise<void>;
}
}
}

View file

@ -1,7 +1,7 @@
import { getAstroStudioEnv } from '@astrojs/studio';
import type { AstroConfig, AstroIntegration } from 'astro';
import { loadEnv } from 'vite';
import type { AstroDbIntegration } from './types.js';
import './types.js';
export type VitePlugin = Required<AstroConfig['vite']>['plugins'][number];
@ -19,7 +19,7 @@ export function getDbDirectoryUrl(root: URL | string) {
return new URL('db/', root);
}
export function defineDbIntegration(integration: AstroDbIntegration): AstroIntegration {
export function defineDbIntegration(integration: AstroIntegration): AstroIntegration {
return integration;
}