Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Allow integrations to change the base for server-islands URLs #12560

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/famous-beds-tan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"astro": minor
---

feat: Allow integrations to change the base for server-islands URLs
2 changes: 2 additions & 0 deletions packages/astro/src/container/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ function createManifest(
clientDirectives: manifest?.clientDirectives ?? getDefaultClientDirectives(),
renderers: renderers ?? manifest?.renderers ?? [],
base: manifest?.base ?? ASTRO_CONFIG_DEFAULTS.base,
serverIslandDynamicBase: manifest?.serverIslandDynamicBase,
componentMetadata: manifest?.componentMetadata ?? new Map(),
inlinedScripts: manifest?.inlinedScripts ?? new Map(),
i18n: manifest?.i18n,
Expand Down Expand Up @@ -228,6 +229,7 @@ type AstroContainerManifest = Pick<
| 'renderers'
| 'assetsPrefix'
| 'base'
| 'serverIslandDynamicBase'
| 'routes'
| 'assets'
| 'entryModules'
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/core/app/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export type SSRManifest = {
routes: RouteInfo[];
site?: string;
base: string;
serverIslandDynamicBase?: string;
trailingSlash: 'always' | 'never' | 'ignore';
buildFormat: 'file' | 'directory' | 'preserve';
compressHTML: boolean;
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 @@ -259,6 +259,7 @@ function buildManifest(
routes,
site: settings.config.site,
base: settings.config.base,
serverIslandDynamicBase: settings.config.serverIslandDynamicBase,
trailingSlash: settings.config.trailingSlash,
compressHTML: settings.config.compressHTML,
assetsPrefix: settings.config.build.assetsPrefix,
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/core/render-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ export class RenderContext {
// calling the render() function will populate the object with scripts, styles, etc.
const result: SSRResult = {
base: manifest.base,
serverIslandDynamicBase: manifest.serverIslandDynamicBase,
cancelled: false,
clientDirectives,
inlinedScripts,
Expand Down
5 changes: 3 additions & 2 deletions packages/astro/src/runtime/server/render/server-islands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,9 @@ export function renderServerIsland(

const hostId = crypto.randomUUID();

const slash = result.base.endsWith('/') ? '' : '/';
let serverIslandUrl = `${result.base}${slash}_server-islands/${componentId}${result.trailingSlash === 'always' ? '/' : ''}`;
const _base = result.serverIslandDynamicBase ?? result.base;
const slash = _base.endsWith('/') ? '' : '/';
let serverIslandUrl = `${_base}${slash}_server-islands/${componentId}${result.trailingSlash === 'always' ? '/' : ''}`;

// Determine if its safe to use a GET request
const potentialSearchParams = createSearchParams(
Expand Down
9 changes: 9 additions & 0 deletions packages/astro/src/types/public/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1975,6 +1975,15 @@ export interface AstroConfig extends AstroConfigType {
// This is a more detailed type than zod validation gives us.
// TypeScript still confirms zod validation matches this type.
integrations: AstroIntegration[];

// Private:
// This is not configurable directly by the user. But may be configured by an integration.
//
// Setting this option will change the base path to deploy server islands to.
// This changes the path of any server islands that are emitted in the client HTML.
// If this is unset, this will fallback to the user supplied `base` config option, and if that is unset,
// then a relative URL will be used (ie: `/_server-islands/`).
serverIslandDynamicBase?: string;
}
/**
* An inline Astro config that takes highest priority when merging with the user config,
Expand Down
5 changes: 5 additions & 0 deletions packages/astro/src/types/public/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,11 @@ export interface SSRResult {
*/
cancelled: boolean;
base: string;
// serverIslandDynamicBase allows users to specify that server islands will be served from a separate domain.
// This is an advanced option and won't be used in most cases. This should only be used if the static assets and
// SSR server are served on separate domains.
// If this is not set, it will use `base` instead.
serverIslandDynamicBase?: string;
styles: Set<SSRElement>;
scripts: Set<SSRElement>;
links: Set<SSRElement>;
Expand Down
103 changes: 103 additions & 0 deletions packages/astro/test/server-islands.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,61 @@ describe('Server islands', () => {
});
});

describe('SSR (change base URL)', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
before(async () => {
fixture = await loadFixture({
root: './fixtures/server-islands/ssr',
adapter: testAdapter(),
integrations: [
{
name: 'change-base-url',
hooks: {
'astro:config:setup': async ({ updateConfig, command }) => {
if (command === 'build') {
updateConfig({
serverIslandDynamicBase: 'https://api.example.com',
});
}
},
},
},
],
});
});

describe('prod', () => {
before(async () => {
process.env.ASTRO_KEY = 'eKBaVEuI7YjfanEXHuJe/pwZKKt3LkAHeMxvTU7aR0M=';
await fixture.build();
});

after(async () => {
delete process.env.ASTRO_KEY;
});

it('server island script has base URL', async () => {
const app = await fixture.loadTestAdapterApp();
const request = new Request('http://example.com/');
const response = await app.render(request);
const html = await response.text();

const $ = cheerio.load(html);
const serverIslandEl = $('h2#island');
assert.equal(serverIslandEl.length, 0);

const serverIslandScript = $('script[data-island-id]');
assert.equal(serverIslandScript.length, 1, 'has the island script');

assert.match(
serverIslandScript.text(),
/await fetch\('https:\/\/api.example.com\/_server-islands\//i,
);
});
});
});

describe('Hybrid mode', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
Expand Down Expand Up @@ -118,4 +173,52 @@ describe('Server islands', () => {
});
});
});

describe('Hybrid mode (change base URL)', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
before(async () => {
fixture = await loadFixture({
root: './fixtures/server-islands/hybrid',
integrations: [
{
name: 'change-base-url',
hooks: {
'astro:config:setup': async ({ updateConfig, command }) => {
if (command === 'build') {
updateConfig({
serverIslandDynamicBase: 'https://api.example.com',
});
}
},
},
},
],
});
});

describe('build', () => {
before(async () => {
await fixture.build({
adapter: testAdapter(),
});
});

it('Omits the island HTML from the static HTML', async () => {
let html = await fixture.readFile('/client/index.html');

const $ = cheerio.load(html);
const serverIslandEl = $('h2#island');
assert.equal(serverIslandEl.length, 0);

const serverIslandScript = $('script[data-island-id]');
assert.equal(serverIslandScript.length, 1, 'has the island script');

assert.match(
serverIslandScript.text(),
/await fetch\('https:\/\/api.example.com\/_server-islands\//i,
);
});
});
});
});
Loading