Skip to content

Commit

Permalink
Astro Integration System (withastro#2820)
Browse files Browse the repository at this point in the history
* update examples

* add initial integrations

* update tests

* update astro

* update ci

* get final tests working

* update injectelement todo

* update ben code review

* respond to final code review feedback
  • Loading branch information
FredKSchott authored Mar 18, 2022
1 parent 3996f9b commit 6fb1099
Show file tree
Hide file tree
Showing 139 changed files with 1,089 additions and 819 deletions.
6 changes: 1 addition & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,14 @@
"dev": "astro-scripts dev \"src/**/*.ts\"",
"postbuild": "astro-scripts copy \"src/**/*.astro\"",
"benchmark": "node test/benchmark/dev.bench.js && node test/benchmark/build.bench.js",
"test": "mocha --parallel --timeout 20000 --ignore **/lit-element.test.js && mocha --timeout 20000 **/lit-element.test.js",
"test": "mocha --exit --timeout 20000 --ignore **/lit-element.test.js && mocha --timeout 20000 **/lit-element.test.js",
"test:match": "mocha --timeout 20000 -g"
},
"dependencies": {
"@astrojs/compiler": "^0.12.1",
"@astrojs/language-server": "^0.8.10",
"@astrojs/markdown-remark": "^0.6.4",
"@astrojs/prism": "0.4.0",
"@astrojs/renderer-preact": "^0.5.0",
"@astrojs/renderer-react": "0.5.0",
"@astrojs/renderer-svelte": "0.5.2",
"@astrojs/renderer-vue": "0.4.0",
"@astrojs/webapi": "^0.11.0",
"@babel/core": "^7.17.7",
"@babel/traverse": "^7.17.3",
Expand Down
110 changes: 78 additions & 32 deletions src/@types/astro.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { AddressInfo } from 'net';
import type * as babel from '@babel/core';
import type * as vite from 'vite';
import type { z } from 'zod';
import type { AstroConfigSchema } from '../core/config';
import type { AstroComponentFactory, Metadata } from '../runtime/server';
import type { AstroRequest } from '../core/render/request';
import type * as vite from 'vite';

export interface AstroBuiltinProps {
'client:load'?: boolean;
Expand Down Expand Up @@ -127,21 +128,30 @@ export interface AstroUserConfig {

/**
* @docs
* @name renderers
* @type {string[]}
* @default `['@astrojs/renderer-svelte','@astrojs/renderer-vue','@astrojs/renderer-react','@astrojs/renderer-preact']`
* @name integrations
* @type {AstroIntegration[]}
* @default `[]`
* @description
* Set the UI framework renderers for your project. Framework renderers are what power Astro's ability to use other frameworks inside of your project, like React, Svelte, and Vue.
* Add Integrations to your project to extend Astro.
*
* Integrations are your one-stop shop to add new frameworks (like Solid.js), new features (like sitemaps), and new libraries (like Partytown and Turbolinks).
*
* Setting this configuration will disable Astro's default framework support, so you will need to provide a renderer for every framework that you want to use.
* Setting this configuration will disable Astro's default integration, so it is recommended to provide a renderer for every framework that you use:
*
* Note: Integrations are currently under active development, and only first-party integrations are supported. In the future, 3rd-party integrations will be allowed.
*
* ```js
* import react from '@astrojs/react';
* import vue from '@astrojs/vue';
* {
* // Use Astro + React, with no other frameworks.
* renderers: ['@astrojs/renderer-react']
* // Example: Use Astro with Vue + React, and no other frameworks.
* integrations: [react(), vue()]
* }
* ```
*/
integrations?: AstroIntegration[];

/** @deprecated - Use "integrations" instead. Run Astro to learn more about migrating. */
renderers?: string[];

/**
Expand Down Expand Up @@ -170,6 +180,7 @@ export interface AstroUserConfig {
* }
* ```
*/
/** Options for rendering markdown content */
markdownOptions?: {
render?: MarkdownRenderOptions;
};
Expand Down Expand Up @@ -379,7 +390,7 @@ export interface AstroUserConfig {

/**
* @docs
* @name devOptions.vite
* @name vite
* @type {vite.UserConfig}
* @description
*
Expand Down Expand Up @@ -421,11 +432,33 @@ export interface AstroUserConfig {
// export interface AstroUserConfig extends z.input<typeof AstroConfigSchema> {
// }

/**
* IDs for different stages of JS script injection:
* - "before-hydration": Imported client-side, before the hydration script runs. Processed & resolved by Vite.
* - "head-inline": Injected into a script tag in the `<head>` of every page. Not processed or resolved by Vite.
* - "page": Injected into the JavaScript bundle of every page. Processed & resolved by Vite.
* - "page-ssr": Injected into the frontmatter of every Astro page. Processed & resolved by Vite.
*/
type InjectedScriptStage = 'before-hydration' | 'head-inline' | 'page' | 'page-ssr';

/**
* Resolved Astro Config
* Config with user settings along with all defaults filled in.
*/
export type AstroConfig = z.output<typeof AstroConfigSchema>;
export interface AstroConfig extends z.output<typeof AstroConfigSchema> {
// Public:
// This is a more detailed type than zod validation gives us.
// TypeScript still confirms zod validation matches this type.
integrations: AstroIntegration[];
// Private:
// We have a need to pass context based on configured state,
// that is different from the user-exposed configuration.
// TODO: Create an AstroConfig class to manage this, long-term.
_ctx: {
renderers: AstroRenderer[];
scripts: { stage: InjectedScriptStage; content: string }[];
};
}

export type AsyncRendererComponentFn<U> = (Component: any, props: any, children: string | undefined, metadata?: AstroComponentMetadata) => Promise<U>;

Expand Down Expand Up @@ -560,38 +593,51 @@ export interface EndpointHandler {
[method: string]: (params: any, request: AstroRequest) => EndpointOutput | Response;
}

/**
* Astro Renderer
* Docs: https://docs.astro.build/reference/renderer-reference/
*/
export interface Renderer {
/** Name of the renderer (required) */
export interface AstroRenderer {
/** Name of the renderer. */
name: string;
/** Import statement for renderer */
source?: string;
/** Import statement for the server renderer */
serverEntry: string;
/** Scripts to be injected before component */
polyfills?: string[];
/** Polyfills that need to run before hydration ever occurs */
hydrationPolyfills?: string[];
/** Import entrypoint for the client/browser renderer. */
clientEntrypoint?: string;
/** Import entrypoint for the server/build/ssr renderer. */
serverEntrypoint: string;
/** JSX identifier (e.g. 'react' or 'solid-js') */
jsxImportSource?: string;
/** Babel transform options */
jsxTransformOptions?: JSXTransformFn;
/** Utilies for server-side rendering */
}

export interface SSRLoadedRenderer extends AstroRenderer {
ssr: {
check: AsyncRendererComponentFn<boolean>;
renderToStaticMarkup: AsyncRendererComponentFn<{
html: string;
}>;
};
/** Return configuration object for Vite ("options" should match https://vitejs.dev/guide/api-plugin.html#config) */
viteConfig?: (options: { mode: 'string'; command: 'build' | 'serve' }) => Promise<vite.InlineConfig>;
/** @deprecated Don’t try and build these dependencies for client (deprecated in 0.21) */
external?: string[];
/** @deprecated Clientside requirements (deprecated in 0.21) */
knownEntrypoints?: string[];
}

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';
updateConfig: (newConfig: Record<string, any>) => void;
addRenderer: (renderer: AstroRenderer) => void;
injectScript: (stage: InjectedScriptStage, content: string) => void;
// 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;
'astro:config:done'?: (options: { config: AstroConfig }) => void | Promise<void>;
'astro:server:setup'?: (options: { server: vite.ViteDevServer }) => void | Promise<void>;
'astro:server:start'?: (options: { address: AddressInfo }) => void | Promise<void>;
'astro:server:done'?: () => void | Promise<void>;
'astro:build:start'?: () => void | Promise<void>;
'astro:build:done'?: (options: { pages: { pathname: string }[]; dir: URL }) => void | Promise<void>;
};
}

export type RouteType = 'page' | 'endpoint';
Expand Down Expand Up @@ -665,7 +711,7 @@ export interface SSRElement {
}

export interface SSRMetadata {
renderers: Renderer[];
renderers: SSRLoadedRenderer[];
pathname: string;
legacyBuild: boolean;
}
Expand Down
20 changes: 6 additions & 14 deletions src/core/app/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import type { ComponentInstance, ManifestData, RouteData, Renderer } from '../../@types/astro';
import type { ComponentInstance, ManifestData, RouteData, SSRLoadedRenderer } from '../../@types/astro';
import type { SSRManifest as Manifest, RouteInfo } from './types';

import { defaultLogOptions } from '../logger.js';
import { matchRoute } from '../routing/match.js';
import { render } from '../render/core.js';
import { RouteCache } from '../render/route-cache.js';
import { createLinkStylesheetElementSet, createModuleScriptElementWithSrcSet } from '../render/ssr-element.js';
import { createRenderer } from '../render/renderer.js';
import { prependForwardSlash } from '../path.js';

export class App {
Expand All @@ -15,7 +14,7 @@ export class App {
#rootFolder: URL;
#routeDataToRouteInfo: Map<RouteData, RouteInfo>;
#routeCache: RouteCache;
#renderersPromise: Promise<Renderer[]>;
#renderersPromise: Promise<SSRLoadedRenderer[]>;

constructor(manifest: Manifest, rootFolder: URL) {
this.#manifest = manifest;
Expand Down Expand Up @@ -84,18 +83,11 @@ export class App {
status: 200,
});
}
async #loadRenderers(): Promise<Renderer[]> {
const rendererNames = this.#manifest.renderers;
async #loadRenderers(): Promise<SSRLoadedRenderer[]> {
return await Promise.all(
rendererNames.map(async (rendererName) => {
return createRenderer(rendererName, {
renderer(name) {
return import(name);
},
server(entry) {
return import(entry);
},
});
this.#manifest.renderers.map(async (renderer) => {
const mod = (await import(renderer.serverEntrypoint)) as { default: SSRLoadedRenderer['ssr'] };
return { ...renderer, ssr: mod.default };
})
);
}
Expand Down
4 changes: 2 additions & 2 deletions src/core/app/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { RouteData, SerializedRouteData, MarkdownRenderOptions } from '../../@types/astro';
import type { RouteData, SerializedRouteData, MarkdownRenderOptions, AstroRenderer } from '../../@types/astro';

export interface RouteInfo {
routeData: RouteData;
Expand All @@ -17,7 +17,7 @@ export interface SSRManifest {
markdown: {
render: MarkdownRenderOptions;
};
renderers: string[];
renderers: AstroRenderer[];
entryModules: Record<string, string>;
}

Expand Down
21 changes: 12 additions & 9 deletions src/core/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { collectPagesData } from './page-data.js';
import { build as scanBasedBuild } from './scan-based-build.js';
import { staticBuild } from './static-build.js';
import { RouteCache } from '../render/route-cache.js';
import { runHookBuildDone, runHookBuildStart, runHookConfigDone, runHookConfigSetup } from '../../integrations/index.js';

export interface BuildOptions {
mode?: string;
Expand Down Expand Up @@ -57,23 +58,23 @@ class AstroBuilder {
const timer: Record<string, number> = {};
timer.init = performance.now();
timer.viteStart = performance.now();
this.config = await runHookConfigSetup({ config: this.config, command: 'build' });
const viteConfig = await createVite(
vite.mergeConfig(
{
mode: this.mode,
server: {
hmr: false,
middlewareMode: 'ssr',
},
{
mode: this.mode,
server: {
hmr: false,
middlewareMode: 'ssr',
},
this.config.vite || {}
),
},
{ astroConfig: this.config, logging, mode: 'build' }
);
await runHookConfigDone({ config: this.config });
this.viteConfig = viteConfig;
const viteServer = await vite.createServer(viteConfig);
this.viteServer = viteServer;
debug('build', timerMessage('Vite started', timer.viteStart));
await runHookBuildStart({ config: this.config });

timer.loadStart = performance.now();
const { assets, allPages } = await collectPagesData({
Expand Down Expand Up @@ -160,6 +161,8 @@ class AstroBuilder {

// You're done! Time to clean up.
await viteServer.close();
await runHookBuildDone({ config: this.config, pages: pageNames });

if (logging.level && levels[logging.level] <= levels['info']) {
await this.printStats({ logging, timeStart: timer.init, pageCount: pageNames.length });
}
Expand Down
Loading

0 comments on commit 6fb1099

Please sign in to comment.