Skip to content

Commit

Permalink
Add support for the build to Server Islands (#11372)
Browse files Browse the repository at this point in the history
* Add support for the build to Server Islands

* Use command instead

* editor tips

* Add comment about defaultRoutes

* Use renderChunk instead of generateBundle
  • Loading branch information
matthewp authored Jul 2, 2024
1 parent 324f74e commit 9eaff96
Show file tree
Hide file tree
Showing 16 changed files with 102 additions and 107 deletions.
2 changes: 1 addition & 1 deletion examples/server-islands/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
},
"devDependencies": {
"@astrojs/node": "^8.2.6",
"@astrojs/react": "workspace:*",
"@astrojs/react": "^3.6.0",
"@astrojs/tailwind": "^5.1.0",
"@fortawesome/fontawesome-free": "^6.5.2",
"@tailwindcss/forms": "^0.5.7",
Expand Down
17 changes: 0 additions & 17 deletions examples/server-islands/src/components/Cart.astro

This file was deleted.

15 changes: 0 additions & 15 deletions examples/server-islands/src/components/Cart.tsx

This file was deleted.

24 changes: 0 additions & 24 deletions examples/server-islands/src/components/Header.astro

This file was deleted.

3 changes: 1 addition & 2 deletions examples/server-islands/src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import '../base.css';
import AddToCart from '../components/AddToCart';
import PersonalBar from '../components/PersonalBar.astro';
import '@fortawesome/fontawesome-free/css/all.min.css';
---
<!DOCTYPE html>
<html lang="en">
Expand All @@ -19,8 +20,6 @@ import PersonalBar from '../components/PersonalBar.astro';
<link
href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&family=Roboto:wght@400;500;700&display=swap"
rel="stylesheet">

<link rel="stylesheet" href="../../node_modules/@fortawesome/fontawesome-free/css/all.min.css">
</head>

<body>
Expand Down
11 changes: 0 additions & 11 deletions examples/server-islands/src/pages/old-index.astro

This file was deleted.

2 changes: 2 additions & 0 deletions packages/astro/src/core/app/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export function deserializeManifest(serializedManifest: SerializedSSRManifest):
const componentMetadata = new Map(serializedManifest.componentMetadata);
const inlinedScripts = new Map(serializedManifest.inlinedScripts);
const clientDirectives = new Map(serializedManifest.clientDirectives);
const serverIslandNameMap = new Map(serializedManifest.serverIslandNameMap);

return {
// in case user middleware exists, this no-op middleware will be reassigned (see plugin-ssr.ts)
Expand All @@ -29,5 +30,6 @@ export function deserializeManifest(serializedManifest: SerializedSSRManifest):
inlinedScripts,
clientDirectives,
routes,
serverIslandNameMap,
};
}
4 changes: 2 additions & 2 deletions packages/astro/src/core/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
} from '../path.js';
import { RenderContext } from '../render-context.js';
import { createAssetLink } from '../render/ssr-element.js';
import { ensure404Route } from '../routing/astro-designed-error-pages.js';
import { injectDefaultRoutes } from '../routing/default.js';
import { matchRoute } from '../routing/match.js';
import { createOriginCheckMiddleware } from './middlewares.js';
import { AppPipeline } from './pipeline.js';
Expand Down Expand Up @@ -87,7 +87,7 @@ export class App {

constructor(manifest: SSRManifest, streaming = true) {
this.#manifest = manifest;
this.#manifestData = ensure404Route({
this.#manifestData = injectDefaultRoutes({
routes: manifest.routes.map((route) => route.routeData),
});
this.#baseWithoutTrailingSlash = removeTrailingForwardSlash(this.#manifest.base);
Expand Down
18 changes: 8 additions & 10 deletions packages/astro/src/core/app/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,8 @@ import type {
} from '../../@types/astro.js';
import { Pipeline } from '../base-pipeline.js';
import type { SinglePageBuiltModule } from '../build/types.js';
import { DEFAULT_404_COMPONENT } from '../constants.js';
import { RewriteEncounteredAnError } from '../errors/errors-data.js';
import { AstroError } from '../errors/index.js';
import { RedirectSinglePageBuiltModule } from '../redirects/component.js';
import { createModuleScriptElement, createStylesheetElementSet } from '../render/ssr-element.js';
import { DEFAULT_404_ROUTE } from '../routing/astro-designed-error-pages.js';
import { findRouteToRewrite } from '../routing/rewrite.js';

export class AppPipeline extends Pipeline {
Expand Down Expand Up @@ -103,13 +99,15 @@ export class AppPipeline extends Pipeline {
}

async getModuleForRoute(route: RouteData): Promise<SinglePageBuiltModule> {
if (route.component === DEFAULT_404_COMPONENT) {
return {
page: async () =>
({ default: () => new Response(null, { status: 404 }) }) as ComponentInstance,
renderers: [],
};
for(const defaultRoute of this.defaultRoutes) {
if(route.component === defaultRoute.component) {
return {
page: () => Promise.resolve(defaultRoute.instance),
renderers: []
};
}
}

if (route.type === 'redirect') {
return RedirectSinglePageBuiltModule;
} else {
Expand Down
3 changes: 2 additions & 1 deletion packages/astro/src/core/app/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,12 @@ export type SSRManifestI18n = {

export type SerializedSSRManifest = Omit<
SSRManifest,
'middleware' | 'routes' | 'assets' | 'componentMetadata' | 'inlinedScripts' | 'clientDirectives'
'middleware' | 'routes' | 'assets' | 'componentMetadata' | 'inlinedScripts' | 'clientDirectives' | 'serverIslandNameMap'
> & {
routes: SerializedRouteInfo[];
assets: string[];
componentMetadata: [string, SSRComponentMetadata][];
inlinedScripts: [string, string][];
clientDirectives: [string, string][];
serverIslandNameMap: [string, string][];
};
8 changes: 7 additions & 1 deletion packages/astro/src/core/base-pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { AstroError } from './errors/errors.js';
import { AstroErrorData } from './errors/index.js';
import type { Logger } from './logger/core.js';
import { RouteCache } from './render/route-cache.js';
import { createDefaultRoutes } from './routing/default.js';

/**
* The `Pipeline` represents the static parts of rendering that do not change between requests.
Expand Down Expand Up @@ -52,7 +53,12 @@ export abstract class Pipeline {
* Used for `Astro.site`.
*/
readonly site = manifest.site ? new URL(manifest.site) : undefined,
readonly callSetGetEnv = true
readonly callSetGetEnv = true,
/**
* Array of built-in, internal, routes.
* Used to find the route module
*/
readonly defaultRoutes = createDefaultRoutes(manifest, new URL(import.meta.url))
) {
this.internalMiddleware = [];
// We do use our middleware only if the user isn't using the manual setup
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/core/build/plugins/plugin-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ function buildManifest(
buildFormat: settings.config.build.format,
checkOrigin: settings.config.security?.checkOrigin ?? false,
rewritingEnabled: settings.config.experimental.rewriting,
serverIslandNameMap: Array.from(settings.serverIslandNameMap),
experimentalEnvGetSecretEnabled:
settings.config.experimental.env !== undefined &&
(settings.adapter?.supportedAstroFeatures.envGetSecret ?? 'unsupported') !== 'unsupported',
Expand Down
3 changes: 3 additions & 0 deletions packages/astro/src/core/build/plugins/plugin-ssr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { SSR_MANIFEST_VIRTUAL_MODULE_ID } from './plugin-manifest.js';
import { MIDDLEWARE_MODULE_ID } from './plugin-middleware.js';
import { ASTRO_PAGE_MODULE_ID } from './plugin-pages.js';
import { RENDERERS_MODULE_ID } from './plugin-renderers.js';
import { VIRTUAL_ISLAND_MAP_ID } from '../../server-islands/vite-plugin-server-islands.js';
import { getComponentFromVirtualModulePageName, getVirtualModulePageName } from './util.js';

export const SSR_VIRTUAL_MODULE_ID = '@astrojs-ssr-virtual-entry';
Expand Down Expand Up @@ -249,12 +250,14 @@ function generateSSRCode(adapter: AstroAdapter, middlewareId: string) {
`import { manifest as defaultManifest } from '${SSR_MANIFEST_VIRTUAL_MODULE_ID}';`,
`import * as serverEntrypointModule from '${adapter.serverEntrypoint}';`,
edgeMiddleware ? `` : `import { onRequest as middleware } from '${middlewareId}';`,
`import { serverIslandMap } from '${VIRTUAL_ISLAND_MAP_ID}';`
];

const contents = [
edgeMiddleware ? `const middleware = (_, next) => next()` : '',
`const _manifest = Object.assign(defaultManifest, {`,
` ${pageMap},`,
` serverIslandMap,`,
` renderers,`,
` middleware`,
`});`,
Expand Down
13 changes: 10 additions & 3 deletions packages/astro/src/core/server-islands/endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,19 @@ export function createEndpoint(manifest: SSRManifest) {
const request = result.request;
const raw = await request.text();
const data = JSON.parse(raw) as RenderOptions;
const componentId = params.name! as string;
if(!params.name) {
return new Response(null, {
status: 400,
statusText: 'Bad request'
});
}
const componentId = params.name;

const imp = manifest.serverIslandMap?.get(componentId);
if(!imp) {
return new Response('Not found', {
status: 404
return new Response(null, {
status: 404,
statusText: 'Not found'
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,45 +1,90 @@
import type { AstroPluginMetadata } from '../../vite-plugin-astro/index.js';
import type { AstroSettings, ComponentInstance } from '../../@types/astro.js';
import type { ViteDevServer, Plugin as VitePlugin } from 'vite';
import type { AstroSettings } from '../../@types/astro.js';
import type { ConfigEnv, ViteDevServer, Plugin as VitePlugin } from 'vite';

export const VIRTUAL_ISLAND_MAP_ID = '@astro-server-islands';
export const RESOLVED_VIRTUAL_ISLAND_MAP_ID = '\0' + VIRTUAL_ISLAND_MAP_ID;
const serverIslandPlaceholder = '\'$$server-islands$$\'';

export function vitePluginServerIslands({ settings }: { settings: AstroSettings }): VitePlugin {
let command: ConfigEnv['command'] = 'serve';
let viteServer: ViteDevServer | null = null;
const referenceIdMap = new Map<string, string>();
return {
name: 'astro:server-islands',
enforce: 'post',
config(_config, { command: _command }) {
command = _command;
},
configureServer(_server) {
viteServer = _server;
},
transform(code, id, options) {
resolveId(name) {
if(name === VIRTUAL_ISLAND_MAP_ID) {
return RESOLVED_VIRTUAL_ISLAND_MAP_ID;
}
},
load(id) {
if(id === RESOLVED_VIRTUAL_ISLAND_MAP_ID) {
return `export const serverIslandMap = ${serverIslandPlaceholder};`;
}
},
transform(_code, id) {
if(id.endsWith('.astro')) {
const info = this.getModuleInfo(id);
if(info?.meta) {
const astro = info.meta.astro as AstroPluginMetadata['astro'] | undefined;
if(astro?.serverComponents.length) {
if(viteServer) {
for(const comp of astro.serverComponents) {
if(!settings.serverIslandNameMap.has(comp.resolvedPath)) {
let name = comp.localName;
let idx = 1;
for(const comp of astro.serverComponents) {
if(!settings.serverIslandNameMap.has(comp.resolvedPath)) {
let name = comp.localName;
let idx = 1;

while(true) {
// Name not taken, let's use it.
if(!settings.serverIslandMap.has(name)) {
break;
}
// Increment a number onto the name: Avatar -> Avatar1
name += idx++;
while(true) {
// Name not taken, let's use it.
if(!settings.serverIslandMap.has(name)) {
break;
}
settings.serverIslandNameMap.set(comp.resolvedPath, name);
settings.serverIslandMap.set(name, () => {
return viteServer?.ssrLoadModule(comp.resolvedPath) as any;
// Increment a number onto the name: Avatar -> Avatar1
name += idx++;
}

// Append the name map, for prod
settings.serverIslandNameMap.set(comp.resolvedPath, name);

settings.serverIslandMap.set(name, () => {
return viteServer?.ssrLoadModule(comp.resolvedPath) as any;
});

// Build mode
if(command === 'build') {
let referenceId = this.emitFile({
type: 'chunk',
id: comp.specifier,
importer: id,
name: comp.localName
});

referenceIdMap.set(comp.resolvedPath, referenceId);
}
}
}
}
}
}
}
},
renderChunk(code) {
if(code.includes(serverIslandPlaceholder)) {
let mapSource = 'new Map([';
for(let [resolvedPath, referenceId] of referenceIdMap) {
const fileName = this.getFileName(referenceId);
const islandName = settings.serverIslandNameMap.get(resolvedPath)!;
mapSource += `\n\t['${islandName}', () => import('./${fileName}')],`
}
mapSource += '\n]);';
referenceIdMap.clear();
return code.replace(serverIslandPlaceholder, mapSource);
}
},
}
}
2 changes: 1 addition & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 9eaff96

Please sign in to comment.