From 93fffd2c2e6a121a4c54e0cc7c988e7e827482c2 Mon Sep 17 00:00:00 2001 From: "Fred K. Schott" Date: Tue, 8 Mar 2022 07:12:11 +0000 Subject: [PATCH 1/4] draft in progress --- proposals/0015-integrations.md | 101 +++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 proposals/0015-integrations.md diff --git a/proposals/0015-integrations.md b/proposals/0015-integrations.md new file mode 100644 index 00000000..59fd34ce --- /dev/null +++ b/proposals/0015-integrations.md @@ -0,0 +1,101 @@ +- Start Date: 03-07-2022 +- Reference Issues: +- Implementation PR: + +# Summary + +An integration system for extending Astro. + +# Example + +```js +// astro.config.js +export default ({ + integrations: [ + import('@astrojs/vue'), + import('@astrojs/tailwind'), + import('@astrojs/sitemap'), + [import('@astrojs/partytown'), {/* options */}], + ], +}); +``` + +# Motivation + +Astro has no integration or plugin system of its own. Astro uses Vite interanlly, and Vite does have a plugin system that some users have bene able to hook into. However, this experience leaves a lot to be desired (doesn't support all use-cases, touching the `vite` config as a user is considered advanced). + +If we were to add an integration system to Astro, we would unlock two huge wins: +1. Empower users to do more by extending Astro themselves (not blocked by what Astro core can/can't do). +2. Make it easier for users to add common features/libraries to their website + +A great example of this in action is to compare Partytown's documentation for Astro vs. Nuxt: +- Astro: [3 steps across frontmatter, template, and a custom npm script](https://partytown.builder.io/astro) +- Nuxt: [1 step](https://partytown.builder.io/nuxt) + +Tailwind is another example of a tool that is difficult and multi-faceted to add to Astro today, but would be quick and easy if we had an integration. + +# Detailed design + +## User Configuration API + +```js +// astro.config.js +export default ({ + integrations: [ + import('@astrojs/vue'), + // or, with options + [import('@astrojs/vue'), {/* options */}], + ], +}); +``` + +## Integration API + +```ts +export interface AstroIntegration { + name: string; + hooks: { + 'astro:config:setup': (options: { + config: AstroConfig; + command: 'dev' | 'build'; + addRenderer: (renderer: AstroRenderer) => void; + injectScript: (stage: 'beforeHydration' | 'head' | 'bundle', content: string) => void; + injectHtml: (stage: 'head' | 'body', element: string) => void; + }) => void | Partial | Promise | Promise>; + 'astro:config:done': (options: { config: AstroConfig }) => void | Promise; + 'astro:server:setup': (options: { server: vite.ViteDevServer }) => void | Promise; + 'astro:server:start': (options: { address: AddressInfo }) => void | Promise; + 'astro:server:done': () => void | Promise; + 'astro:build:start': () => void | Promise; + 'astro:build:done': (options: { pages: string[]; dir: URL }) => void | Promise; + }; +} +``` + +# Drawbacks + +Why should we *not* do this? Please consider: + +- Implementation cost, both in term of code size and complexity. +- Whether the proposed feature can be implemented in user space. +- Impact on teaching people Astro. +- Integration of this feature with other existing and planned features +- Cost of migrating existing Astro applications (_is it a breaking change?_) + +There are tradeoffs to choosing any path. Attempt to identify them here. + +# Alternatives + +What other designs have been considered? What is the impact of not doing this? + +# Adoption strategy + +- This would be a breaking change for most users. +- We are exploring a codemod + `astro setup` / `astro setup [name]` commands to mitigate this cost + +With enough time and effort we could maybe do this in a backwards-compatible way. However, this is a change that is relatively straightforward to make (only change a single file) AND we actually do want people to know that Astro supports integrations now. As far as breaking changes go, this is a pretty positive one. + + +# Unresolved questions + +TODO \ No newline at end of file From 2346dc98e42e952260a08468a05025d2dc26c7fd Mon Sep 17 00:00:00 2001 From: "Fred K. Schott" Date: Thu, 10 Mar 2022 02:39:21 +0000 Subject: [PATCH 2/4] finalize draft --- proposals/0000-integrations.md | 171 +++++++++++++++++++++++++++++++++ proposals/0015-integrations.md | 101 ------------------- 2 files changed, 171 insertions(+), 101 deletions(-) create mode 100644 proposals/0000-integrations.md delete mode 100644 proposals/0015-integrations.md diff --git a/proposals/0000-integrations.md b/proposals/0000-integrations.md new file mode 100644 index 00000000..a7c1a14a --- /dev/null +++ b/proposals/0000-integrations.md @@ -0,0 +1,171 @@ +- Start Date: 03-07-2022 +- Reference Issues: +- Implementation PR: + +# Summary + +An integration system for extending Astro. + +# Example + +```js +// astro.config.js +export default ({ + integrations: [ + import('@astrojs/vue'), + import('@astrojs/tailwind'), + [import('@astrojs/partytown'), {/* options */}], + ], +}); +``` + +```js +// Static imports are also supported, for type checking. +import vuePlugin from '@astrojs/vue'; +import tailwindPlugin from '@astrojs/tailwind'; +import partytownPlugin from '@astrojs/partytown'; + +export default ({ + integrations: [ + vuePlugin(), + tailwindPlugin(), + partytownPlugin({/* options */}) + ], +}); +``` + +# Motivation + +Astro currently has no integration or plugin system of its own. Some users have been able to hook into Astro's internal Vite plugin system to extend Astro, which lets you control the build pipeline. However, this only gives you access to extend one piece of what Astro does (the build). The rest of Astro remains out of touch. + +Adding a first-class integration system would unlock a few huge wins for Astro: +1. Empower users to do more by extending Astro themselves (not blocked by what Astro core can/can't do). +2. Empower users to do more by reusing shared extensions (easy to add "X" to an Astro project). +3. Empower more user-land experimentation, reducing how much Astro core blocks what a user can/can't do with Astro. +4. Organize our codebase by refactoring more logic into internal integrations or moved out of core entirely into external integrations. + +To illustrate this, compare Partytown's documentation for getting started wtih Astro vs. getting started with Nuxt: +- Astro: [3 steps across frontmatter, template, and a custom npm script](https://partytown.builder.io/astro) +- Nuxt: [1 step](https://partytown.builder.io/nuxt) + +Tailwind suffers from a similar difficult setup story in Astro. + +# Detailed design + +**Background:** This API was reached through weeks of experimentation of different designs (see alternatives below). To test the work, I created the following integrations: + +- **Renderers:** `lit`, `svelte`, `react`, `preact`, `vue`, `solid` +- **Libraries:** `tailwind`, `partytown`, `turbolinks` +- **Features:** `sitemap` + +## Integration Usage API + +```js +// astro.config.js +export default ({ + integrations: [ + import('@astrojs/vue'), + // or, with options + [import('@astrojs/vue'), {/* options */}], + // or, as a static ESM import at the top of the file + vuePlugin({/* options */}) + ], +}); +``` + +## Integration Author API + +```ts +export interface AstroIntegration { + name: string; + hooks: { + /** SETUP */ + /** Called on astro startup, lets you modify config */ + 'astro:config:setup': (options: ConfigSetupOptions) => void | Promise; + /** Called after config is finalized, lets you store config object for later */ + 'astro:config:done': (options: { config: Readonly }) => void | Promise; + + /** DEV */ + /** Called on server setup, lets you modify the server */ + 'astro:server:setup': (options: { server: vite.ViteDevServer }) => void | Promise; + /** Called on server startup, lets you read the dev server address/URL */ + 'astro:server:start': (options: { address: AddressInfo }) => void | Promise; + /** Called on server exit */ + 'astro:server:done': () => void | Promise; + + /** BUILD */ + /** Called on build start, lets you modify the server */ + 'astro:build:start': () => void | Promise; + /** Called on build done, lets you read metadata about the build */ + 'astro:build:done': (options: { pages: string[]; dir: URL }) => void | Promise; + }; +} +``` + + +### Integration Author API: Hooks + +- The **Hook** is the main primitive of this proposed integration system. +- Hooks optimize for maximum flexibility for the integration author: you can use our provided helper methods to perform common tasks during each hook, or write your own custom logic for advanced needs. +- Hooks are conceptually aligned with how Rollup and Vite plugins work. This lets us pass some hooks (like 'astro:server:start') to Vite (the Vite `configureServer()` hook) with trivial effort. +- The `hooks: {}` API conceptually matches Rollup & Vite but was designed to avoid the risk of conflict that would have been introduced had we literally extending the Vite plugin idea. + - This is why we prefix all hooks with `astro:`. + + +# Drawbacks + +- **Breaking changes across an integration system are expensive.** This can be mitigated until v1.0.0, see adoption strategy below. + + +# Alternatives + +A previous design used a functional API more like this: + +``` +export default function(integration) { + integration.injectScript(...); + integration.modifyConfig(...); + integration.configureServer(...); + integration.mountDirectory(...); +} +``` + +This produced nice, linear code but at the expense of internal Astro complexity and limited flexibility that eventually blocked some integrations from being possible: + +1. **Complexity:** Astro had to run the entire integration upfront on startup, and then cache these results for later when needed. Many of the methods (like `configureServer`) ended up acting more like hooks anyway. +2. **Inflexible:** Integrations like Partytown couldn't work with a provided `mountDirectory` helper method because because they need to run their own `fs` logic on the final build directory. + +Advanced use-cases like this essentially required hooks to perform custom logic as needed, so the design shifted away from "helpers that do everything for you" and towards "provide hooks with helpers availble if needed". + +# Adoption strategy + +## Experimental Flag + +This proposal suggests only supporting official integrations to start, and mark 3rd-part integrations as experimental via something like a config flag (`--experimental-integrations`) until we hit `v1.0.0-beta`. + +This would let us test the integration system and respond to user feedback before finalizing. + +## Renderers + +The `renderers` API is deprecated by this proposal, with all `renderers` becoming `integrations`: `@astrojs/renderer-vue` -> `@astrojs/vue`. + +With a lot of work, we could do this in a backwards compatible way. However, I would like to avoid that complexity (and the potential for bugs that comes with it) and do this in a breaking change for the following reasons: + +1. **low-effort to upgrade:** updating your renderers to integrations would involve changing your config file only. A codemod could be provided to make this even easier. +1. **easy to assist:** Unlike past breaking changes, this will be fairly easy for Astro to provide helpful output to migrate from one to the other. + +``` +$ astro build + +Astro renderers are no longer supported! +Update your config to use Astros new integration system: + +- renderers: ["@astrojs/vue"] ++ integrations: [import("@astrojs/vue")] +``` + +# Unresolved questions + +- Bikeshedding all the things +- Can we pair this with an `astro add NAME` CLI command? +- Do we use this as a chance to remove the built-in renderers, and force users to install all framework integrations themselves as npm packages? I would like to save that for a later breaking change, since it would make this breaking change more complex (see the adoption strategy defined above). \ No newline at end of file diff --git a/proposals/0015-integrations.md b/proposals/0015-integrations.md deleted file mode 100644 index 59fd34ce..00000000 --- a/proposals/0015-integrations.md +++ /dev/null @@ -1,101 +0,0 @@ -- Start Date: 03-07-2022 -- Reference Issues: -- Implementation PR: - -# Summary - -An integration system for extending Astro. - -# Example - -```js -// astro.config.js -export default ({ - integrations: [ - import('@astrojs/vue'), - import('@astrojs/tailwind'), - import('@astrojs/sitemap'), - [import('@astrojs/partytown'), {/* options */}], - ], -}); -``` - -# Motivation - -Astro has no integration or plugin system of its own. Astro uses Vite interanlly, and Vite does have a plugin system that some users have bene able to hook into. However, this experience leaves a lot to be desired (doesn't support all use-cases, touching the `vite` config as a user is considered advanced). - -If we were to add an integration system to Astro, we would unlock two huge wins: -1. Empower users to do more by extending Astro themselves (not blocked by what Astro core can/can't do). -2. Make it easier for users to add common features/libraries to their website - -A great example of this in action is to compare Partytown's documentation for Astro vs. Nuxt: -- Astro: [3 steps across frontmatter, template, and a custom npm script](https://partytown.builder.io/astro) -- Nuxt: [1 step](https://partytown.builder.io/nuxt) - -Tailwind is another example of a tool that is difficult and multi-faceted to add to Astro today, but would be quick and easy if we had an integration. - -# Detailed design - -## User Configuration API - -```js -// astro.config.js -export default ({ - integrations: [ - import('@astrojs/vue'), - // or, with options - [import('@astrojs/vue'), {/* options */}], - ], -}); -``` - -## Integration API - -```ts -export interface AstroIntegration { - name: string; - hooks: { - 'astro:config:setup': (options: { - config: AstroConfig; - command: 'dev' | 'build'; - addRenderer: (renderer: AstroRenderer) => void; - injectScript: (stage: 'beforeHydration' | 'head' | 'bundle', content: string) => void; - injectHtml: (stage: 'head' | 'body', element: string) => void; - }) => void | Partial | Promise | Promise>; - 'astro:config:done': (options: { config: AstroConfig }) => void | Promise; - 'astro:server:setup': (options: { server: vite.ViteDevServer }) => void | Promise; - 'astro:server:start': (options: { address: AddressInfo }) => void | Promise; - 'astro:server:done': () => void | Promise; - 'astro:build:start': () => void | Promise; - 'astro:build:done': (options: { pages: string[]; dir: URL }) => void | Promise; - }; -} -``` - -# Drawbacks - -Why should we *not* do this? Please consider: - -- Implementation cost, both in term of code size and complexity. -- Whether the proposed feature can be implemented in user space. -- Impact on teaching people Astro. -- Integration of this feature with other existing and planned features -- Cost of migrating existing Astro applications (_is it a breaking change?_) - -There are tradeoffs to choosing any path. Attempt to identify them here. - -# Alternatives - -What other designs have been considered? What is the impact of not doing this? - -# Adoption strategy - -- This would be a breaking change for most users. -- We are exploring a codemod + `astro setup` / `astro setup [name]` commands to mitigate this cost - -With enough time and effort we could maybe do this in a backwards-compatible way. However, this is a change that is relatively straightforward to make (only change a single file) AND we actually do want people to know that Astro supports integrations now. As far as breaking changes go, this is a pretty positive one. - - -# Unresolved questions - -TODO \ No newline at end of file From 169053733d40a405670f748832bcf752b312a569 Mon Sep 17 00:00:00 2001 From: "Fred K. Schott" Date: Thu, 10 Mar 2022 02:49:35 +0000 Subject: [PATCH 3/4] add links to existing integrations --- proposals/0000-integrations.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/proposals/0000-integrations.md b/proposals/0000-integrations.md index a7c1a14a..1b6aebd3 100644 --- a/proposals/0000-integrations.md +++ b/proposals/0000-integrations.md @@ -52,11 +52,11 @@ Tailwind suffers from a similar difficult setup story in Astro. # Detailed design -**Background:** This API was reached through weeks of experimentation of different designs (see alternatives below). To test the work, I created the following integrations: +**Background:** This API was reached through weeks of experimentation of different designs (see alternatives below). To test the work, I designed and build the following integrations, which are useful for illustrating this RFC: -- **Renderers:** `lit`, `svelte`, `react`, `preact`, `vue`, `solid` -- **Libraries:** `tailwind`, `partytown`, `turbolinks` -- **Features:** `sitemap` +- **Renderers:** [`lit`](https://github.com/withastro/astro/blob/wip-integrations-4/packages/integrations/lit/index.js), [`svelte`](https://github.com/withastro/astro/blob/wip-integrations-4/packages/integrations/svelte/index.js), [`react`](https://github.com/withastro/astro/blob/wip-integrations-4/packages/integrations/react/index.js), [`preact`](https://github.com/withastro/astro/blob/wip-integrations-4/packages/integrations/preact/index.js), [`vue`](https://github.com/withastro/astro/blob/wip-integrations-4/packages/integrations/vue/index.js), [`solid`](https://github.com/withastro/astro/blob/wip-integrations-4/packages/integrations/solid/index.js) +- **Libraries:** [`tailwind`](https://github.com/withastro/astro/blob/wip-integrations-4/packages/integrations/tailwind/index.js), [`partytown`](https://github.com/withastro/astro/blob/wip-integrations-4/packages/integrations/partytown/index.js), [`turbolinks`](https://github.com/withastro/astro/blob/wip-integrations-4/packages/integrations/turbolinks/index.js) +- **Features:** [`sitemap`](https://github.com/withastro/astro/blob/wip-integrations-4/packages/integrations/sitemap/index.js) ## Integration Usage API From 80135989e7aafb3fb245ba762d5acaa3c3c9480a Mon Sep 17 00:00:00 2001 From: "Fred K. Schott" Date: Wed, 9 Mar 2022 21:38:07 -0800 Subject: [PATCH 4/4] Update 0000-integrations.md --- proposals/0000-integrations.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/0000-integrations.md b/proposals/0000-integrations.md index 1b6aebd3..f9ac24bc 100644 --- a/proposals/0000-integrations.md +++ b/proposals/0000-integrations.md @@ -1,6 +1,6 @@ - Start Date: 03-07-2022 - Reference Issues: -- Implementation PR: +- Implementation PR: [(git branch, runnable but not yet a PR)](https://github.com/withastro/astro/compare/wip-integrations-4?expand=1) # Summary @@ -168,4 +168,4 @@ Update your config to use Astros new integration system: - Bikeshedding all the things - Can we pair this with an `astro add NAME` CLI command? -- Do we use this as a chance to remove the built-in renderers, and force users to install all framework integrations themselves as npm packages? I would like to save that for a later breaking change, since it would make this breaking change more complex (see the adoption strategy defined above). \ No newline at end of file +- Do we use this as a chance to remove the built-in renderers, and force users to install all framework integrations themselves as npm packages? I would like to save that for a later breaking change, since it would make this breaking change more complex (see the adoption strategy defined above).