summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Okiki Ojo <okikio.dev@gmail.com> 2022-06-14 14:08:14 -0400
committerGravatar GitHub <noreply@github.com> 2022-06-14 14:08:14 -0400
commitd46f8fb14d3c702d62cc327de23562078fca0088 (patch)
tree3ddb113889a3f10782e6ec42642720356cd01ac6
parent8ca284b080a8551203427204cb27040013a40132 (diff)
downloadastro-d46f8fb14d3c702d62cc327de23562078fca0088.tar.gz
astro-d46f8fb14d3c702d62cc327de23562078fca0088.tar.zst
astro-d46f8fb14d3c702d62cc327de23562078fca0088.zip
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
-rw-r--r--.changeset/unlucky-eyes-attend.md16
-rw-r--r--packages/astro/src/integrations/index.ts32
-rw-r--r--packages/astro/test/config-validate.test.js7
-rw-r--r--packages/webapi/mod.d.ts2
4 files changed, 46 insertions, 11 deletions
diff --git a/.changeset/unlucky-eyes-attend.md b/.changeset/unlucky-eyes-attend.md
new file mode 100644
index 000000000..e54c1078a
--- /dev/null
+++ b/.changeset/unlucky-eyes-attend.md
@@ -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
+]
+``` \ No newline at end of file
diff --git a/packages/astro/src/integrations/index.ts b/packages/astro/src/integrations/index.ts
index 4e53dc4e6..f2ca31a67 100644
--- a/packages/astro/src/integrations/index.ts
+++ b/packages/astro/src/integrations/index.ts
@@ -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,
diff --git a/packages/astro/test/config-validate.test.js b/packages/astro/test/config-validate.test.js
index e90eea69c..95c3af17c 100644
--- a/packages/astro/test/config-validate.test.js
+++ b/packages/astro/test/config-validate.test.js
@@ -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 } },
diff --git a/packages/webapi/mod.d.ts b/packages/webapi/mod.d.ts
index a3c49dc5c..b385e82a5 100644
--- a/packages/webapi/mod.d.ts
+++ b/packages/webapi/mod.d.ts
@@ -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;