feat: support optional and conditional integrations (#3590)
* feat(integrations): support optional integrations By making integration optional, Astro can now ignore null or undefined Integrations instead of giving an internal error most devs can't read/won't understand. This also enables optional integrations, e.g. ```ts integration: [ // Only run `compress` integration in production environments, etc... import.meta.env.production ? compress() : null ] ``` * ci: add tests for optional integration * docs: add changelog
This commit is contained in:
parent
8ca284b080
commit
d46f8fb14d
4 changed files with 46 additions and 11 deletions
16
.changeset/unlucky-eyes-attend.md
Normal file
16
.changeset/unlucky-eyes-attend.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Add support for optional integrations
|
||||
|
||||
By making integration optional, Astro can now ignore null, undefined or other [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy) "Integration" values instead of giving an internal error most devs can't and/or won't understand.
|
||||
|
||||
This also enables conditional integrations,
|
||||
e.g.
|
||||
```ts
|
||||
integration: [
|
||||
// Only run `compress` integration when in production environments, etc...
|
||||
import.meta.env.production ? compress() : null
|
||||
]
|
||||
```
|
|
@ -21,7 +21,19 @@ export async function runHookConfigSetup({
|
|||
|
||||
let updatedConfig: AstroConfig = { ..._config };
|
||||
for (const integration of _config.integrations) {
|
||||
if (integration.hooks['astro:config:setup']) {
|
||||
/**
|
||||
* By making integration hooks optional, Astro can now ignore null or undefined Integrations
|
||||
* instead of giving an internal error most people can't read
|
||||
*
|
||||
* This also enables optional integrations, e.g.
|
||||
* ```ts
|
||||
* integration: [
|
||||
* // Only run `compress` integration in production environments, etc...
|
||||
* import.meta.env.production ? compress() : null
|
||||
* ]
|
||||
* ```
|
||||
*/
|
||||
if (integration?.hooks?.['astro:config:setup']) {
|
||||
await integration.hooks['astro:config:setup']({
|
||||
config: updatedConfig,
|
||||
command,
|
||||
|
@ -42,7 +54,7 @@ export async function runHookConfigSetup({
|
|||
|
||||
export async function runHookConfigDone({ config }: { config: AstroConfig }) {
|
||||
for (const integration of config.integrations) {
|
||||
if (integration.hooks['astro:config:done']) {
|
||||
if (integration?.hooks?.['astro:config:done']) {
|
||||
await integration.hooks['astro:config:done']({
|
||||
config,
|
||||
setAdapter(adapter) {
|
||||
|
@ -60,7 +72,7 @@ export async function runHookConfigDone({ config }: { config: AstroConfig }) {
|
|||
if (!config._ctx.adapter) {
|
||||
const integration = ssgAdapter();
|
||||
config.integrations.push(integration);
|
||||
if (integration.hooks['astro:config:done']) {
|
||||
if (integration?.hooks?.['astro:config:done']) {
|
||||
await integration.hooks['astro:config:done']({
|
||||
config,
|
||||
setAdapter(adapter) {
|
||||
|
@ -79,7 +91,7 @@ export async function runHookServerSetup({
|
|||
server: ViteDevServer;
|
||||
}) {
|
||||
for (const integration of config.integrations) {
|
||||
if (integration.hooks['astro:server:setup']) {
|
||||
if (integration?.hooks?.['astro:server:setup']) {
|
||||
await integration.hooks['astro:server:setup']({ server });
|
||||
}
|
||||
}
|
||||
|
@ -93,7 +105,7 @@ export async function runHookServerStart({
|
|||
address: AddressInfo;
|
||||
}) {
|
||||
for (const integration of config.integrations) {
|
||||
if (integration.hooks['astro:server:start']) {
|
||||
if (integration?.hooks?.['astro:server:start']) {
|
||||
await integration.hooks['astro:server:start']({ address });
|
||||
}
|
||||
}
|
||||
|
@ -101,7 +113,7 @@ export async function runHookServerStart({
|
|||
|
||||
export async function runHookServerDone({ config }: { config: AstroConfig }) {
|
||||
for (const integration of config.integrations) {
|
||||
if (integration.hooks['astro:server:done']) {
|
||||
if (integration?.hooks?.['astro:server:done']) {
|
||||
await integration.hooks['astro:server:done']();
|
||||
}
|
||||
}
|
||||
|
@ -115,7 +127,7 @@ export async function runHookBuildStart({
|
|||
buildConfig: BuildConfig;
|
||||
}) {
|
||||
for (const integration of config.integrations) {
|
||||
if (integration.hooks['astro:build:start']) {
|
||||
if (integration?.hooks?.['astro:build:start']) {
|
||||
await integration.hooks['astro:build:start']({ buildConfig });
|
||||
}
|
||||
}
|
||||
|
@ -133,7 +145,7 @@ export async function runHookBuildSetup({
|
|||
target: 'server' | 'client';
|
||||
}) {
|
||||
for (const integration of config.integrations) {
|
||||
if (integration.hooks['astro:build:setup']) {
|
||||
if (integration?.hooks?.['astro:build:setup']) {
|
||||
await integration.hooks['astro:build:setup']({
|
||||
vite,
|
||||
pages,
|
||||
|
@ -154,7 +166,7 @@ export async function runHookBuildSsr({
|
|||
manifest: SerializedSSRManifest;
|
||||
}) {
|
||||
for (const integration of config.integrations) {
|
||||
if (integration.hooks['astro:build:ssr']) {
|
||||
if (integration?.hooks?.['astro:build:ssr']) {
|
||||
await integration.hooks['astro:build:ssr']({ manifest });
|
||||
}
|
||||
}
|
||||
|
@ -174,7 +186,7 @@ export async function runHookBuildDone({
|
|||
const dir = isBuildingToSSR(config) ? buildConfig.client : config.outDir;
|
||||
|
||||
for (const integration of config.integrations) {
|
||||
if (integration.hooks['astro:build:done']) {
|
||||
if (integration?.hooks?.['astro:build:done']) {
|
||||
await integration.hooks['astro:build:done']({
|
||||
pages: pages.map((p) => ({ pathname: p })),
|
||||
dir,
|
||||
|
|
|
@ -69,6 +69,13 @@ describe('Config Validation', () => {
|
|||
expect(configError).to.be.instanceOf(Error);
|
||||
expect(configError.message).to.include('Astro integrations are still experimental.');
|
||||
});
|
||||
it('ignores null or falsy "integration" values', async () => {
|
||||
const configError = await validateConfig(
|
||||
{ integrations: [null, undefined, false, '', ``] },
|
||||
process.cwd()
|
||||
).catch((err) => err);
|
||||
expect(configError).to.be.not.instanceOf(Error);
|
||||
});
|
||||
it('allows third-party "integration" values with the --experimental-integrations flag', async () => {
|
||||
await validateConfig(
|
||||
{ integrations: [{ name: '@my-plugin/a' }], experimental: { integrations: true } },
|
||||
|
|
2
packages/webapi/mod.d.ts
vendored
2
packages/webapi/mod.d.ts
vendored
|
@ -1,5 +1,5 @@
|
|||
export { pathToPosix } from './lib/utils';
|
||||
export { AbortController, AbortSignal, alert, atob, Blob, btoa, ByteLengthQueuingStrategy, cancelAnimationFrame, cancelIdleCallback, CanvasRenderingContext2D, CharacterData, clearTimeout, Comment, CountQueuingStrategy, CSSStyleSheet, CustomElementRegistry, CustomEvent, Document, DocumentFragment, DOMException, Element, Event, EventTarget, fetch, File, FormData, Headers, HTMLBodyElement, HTMLCanvasElement, HTMLDivElement, HTMLDocument, HTMLElement, HTMLHeadElement, HTMLHtmlElement, HTMLImageElement, HTMLSpanElement, HTMLStyleElement, HTMLTemplateElement, HTMLUnknownElement, Image, ImageData, IntersectionObserver, MediaQueryList, MutationObserver, Node, NodeFilter, NodeIterator, OffscreenCanvas, ReadableByteStreamController, ReadableStream, ReadableStreamBYOBReader, ReadableStreamBYOBRequest, ReadableStreamDefaultController, ReadableStreamDefaultReader, Request, requestAnimationFrame, requestIdleCallback, ResizeObserver, Response, setTimeout, ShadowRoot, structuredClone, StyleSheet, Text, TransformStream, TreeWalker, URLPattern, Window, WritableStream, WritableStreamDefaultController, WritableStreamDefaultWriter } from './mod.js';
|
||||
export { AbortController, AbortSignal, alert, atob, Blob, btoa, ByteLengthQueuingStrategy, cancelAnimationFrame, cancelIdleCallback, CanvasRenderingContext2D, CharacterData, clearTimeout, Comment, CountQueuingStrategy, CSSStyleSheet, CustomElementRegistry, CustomEvent, Document, DocumentFragment, DOMException, Element, Event, EventTarget, fetch, File, FormData, Headers, HTMLBodyElement, HTMLCanvasElement, HTMLDivElement, HTMLDocument, HTMLElement, HTMLHeadElement, HTMLHtmlElement, HTMLImageElement, HTMLSpanElement, HTMLStyleElement, HTMLTemplateElement, HTMLUnknownElement, Image, ImageData, IntersectionObserver, MediaQueryList, MutationObserver, Node, NodeFilter, NodeIterator, OffscreenCanvas, ReadableByteStreamController, ReadableStream, ReadableStreamBYOBReader, ReadableStreamBYOBRequest, ReadableStreamDefaultController, ReadableStreamDefaultReader, Request, requestAnimationFrame, requestIdleCallback, ResizeObserver, Response, setTimeout, ShadowRoot, structuredClone, StyleSheet, Text, TransformStream, TreeWalker, URLPattern, Window, WritableStream, WritableStreamDefaultController, WritableStreamDefaultWriter, } from './mod.js';
|
||||
export declare const polyfill: {
|
||||
(target: any, options?: PolyfillOptions): any;
|
||||
internals(target: any, name: string): any;
|
||||
|
|
Loading…
Reference in a new issue