Skip to content
Merged
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
39 changes: 39 additions & 0 deletions packages/astro/e2e/csp-server-islands.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { expect } from '@playwright/test';
import { testFactory } from './test-utils.js';

const test = testFactory(import.meta.url, {
root: './fixtures/csp-server-islands/',
});

test.describe('CSP Server islands', () => {
test.describe('Production', () => {
let previewServer;

test.beforeAll(async ({ astro }) => {
// Playwright's Node version doesn't have these functions, so stub them.
process.stdout.clearLine = () => {};
process.stdout.cursorTo = () => {};
await astro.build();
previewServer = await astro.preview();
});

test.afterAll(async () => {
await previewServer.stop();
});

test('Only one component in prod', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/base/'));

let el = page.locator('#basics .island');

await expect(el, 'element rendered').toBeVisible();
await expect(el, 'should have content').toHaveText('I am an island');
});

test('Props are encrypted', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
let el = page.locator('#basics .secret');
await expect(el).toHaveText('test');
});
});
});
16 changes: 16 additions & 0 deletions packages/astro/e2e/fixtures/csp-server-islands/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import mdx from '@astrojs/mdx';
import react from '@astrojs/react';
import { defineConfig } from 'astro/config';
import nodejs from '@astrojs/node';

// https://astro.build/config
export default defineConfig({
base: '/base',
output: 'static',
adapter: nodejs({ mode: 'standalone' }),
integrations: [react(), mdx()],
trailingSlash: process.env.TRAILING_SLASH ?? 'always',
experimental: {
csp: true
}
});
16 changes: 16 additions & 0 deletions packages/astro/e2e/fixtures/csp-server-islands/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "@e2e/csp-server-islands",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "astro dev"
},
"dependencies": {
"@astrojs/react": "workspace:*",
"astro": "workspace:*",
"@astrojs/mdx": "workspace:*",
"@astrojs/node": "workspace:*",
"react": "^18.3.1",
"react-dom": "^18.3.1"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div id="first"></div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
const { secret } = Astro.props;
---

<h2 class="island">I am an island</h2>
<slot />
<h3 class="secret">{secret}</h3>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
Astro.response.headers.set('content-type', 'text/html;charset=utf-8');
---

<h2 id="charset-in-content-type">I'm an island with a different content-type response header</h2>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
import Self from './Self.astro';
const now = Date();
---

<p class="now">{now}</p>
{!Astro.props.stop && <Self stop server:defer />}
9 changes: 9 additions & 0 deletions packages/astro/e2e/fixtures/csp-server-islands/src/lorem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const content = `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`;

export function generateLongText(paragraphs = 5) {
let arr = new Array(paragraphs);
for(let i = 0; i < paragraphs; i++) {
arr[i] = content;
}
return arr.join('\n');
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
export const prerender = false;
---

<html>
<head>
<title>Conflicting route</title>
</head>
<body>
This route would conflict with the route generated for server islands.
<br />
This file is here so the tests break if that happens.
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
import Island from '../components/Island.astro';
import HTMLError from '../components/HTMLError.astro';
import { generateLongText } from '../lorem';
import MediaTypeInHeader from '../components/MediaTypeInHeader.astro';
const content = generateLongText(5);
export const prerender = false;
---

<html>
<head>
<!-- Head Stuff -->
</head>
<body>
<div id="basics">
<Island server:defer secret="test">
<h3 id="children">children</h3>
</Island>
</div>

<MediaTypeInHeader server:defer />

<div id="big">
<Island server:defer secret="test" content={content} />
</div>

<div id="error-test">
<HTMLError server:defer>
<script is:inline slot="fallback">
// Delete the previous element, the island comment
document.currentScript.previousSibling.remove();

// This simulates a host which has minified the HTML, destroying the comment
</script>
</HTMLError>
</div>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Island from '../components/Island.astro';

<Island server:defer />
6 changes: 4 additions & 2 deletions packages/astro/e2e/view-transitions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1584,9 +1584,11 @@ test.describe('View Transitions', () => {
await expect(p, 'should have content').toHaveText('Page 1');
});


// It weirdly fails
test.skip('animation get canceled when view transition is interrupted', async ({ page, astro }) => {
test.skip('animation get canceled when view transition is interrupted', async ({
page,
astro,
}) => {
let lines = [];
page.on('console', (msg) => {
msg.text().startsWith('[test]') && lines.push(msg.text());
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@
"test:integration": "astro-scripts test \"test/*.test.js\""
},
"dependencies": {
"@astrojs/compiler": "^2.11.0",
"@astrojs/compiler": "^2.12.0",
"@astrojs/internal-helpers": "workspace:*",
"@astrojs/markdown-remark": "workspace:*",
"@astrojs/telemetry": "workspace:*",
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/core/build/plugins/plugin-internals.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { Plugin as VitePlugin } from 'vite';
import type { BuildInternals } from '../internal.js';
import type { AstroBuildPlugin } from '../plugin.js';
import { normalizeEntryId } from './plugin-component-entry.js';
import type { StaticBuildOptions } from '../types.js';
import { normalizeEntryId } from './plugin-component-entry.js';

function vitePluginInternals(
input: Set<string>,
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 @@ -461,6 +461,7 @@ export class RenderContext {
headInTree: false,
extraHead: [],
extraStyleHashes: [],
extraScriptHashes: [],
propagators: new Set(),
},
shouldInjectCspMetaTags: manifest.shouldInjectCspMetaTags,
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/runtime/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export type {
ComponentSlots,
RenderInstruction,
} from './render/index.js';
export type { ServerIslandComponent } from './render/server-islands.js';
export { createTransitionScope, renderTransition } from './transition.js';

import { markHTMLString } from './escape.js';
Expand Down
14 changes: 11 additions & 3 deletions packages/astro/src/runtime/server/render/astro/factory.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { PropagationHint, SSRResult } from '../../../../types/public/internal.js';
import type { HeadAndContent } from './head-and-content.js';
import type { HeadAndContent, ThinHead } from './head-and-content.js';
import type { RenderTemplateResult } from './render-template.js';

export type AstroFactoryReturnValue = RenderTemplateResult | Response | HeadAndContent;
export type AstroFactoryReturnValue = RenderTemplateResult | Response | HeadAndContent | ThinHead;

// The callback passed to to $$createComponent
export interface AstroComponentFactory {
Expand All @@ -20,9 +20,17 @@ export function isAPropagatingComponent(
result: SSRResult,
factory: AstroComponentFactory,
): boolean {
const hint = getPropagationHint(result, factory);
return hint === 'in-tree' || hint === 'self';
}

export function getPropagationHint(
result: SSRResult,
factory: AstroComponentFactory,
): PropagationHint {
let hint: PropagationHint = factory.propagation || 'none';
if (factory.moduleId && result.componentMetadata.has(factory.moduleId) && hint === 'none') {
hint = result.componentMetadata.get(factory.moduleId)!.propagation;
}
return hint === 'in-tree' || hint === 'self';
return hint;
}
13 changes: 13 additions & 0 deletions packages/astro/src/runtime/server/render/astro/head-and-content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ export type HeadAndContent = {
content: RenderTemplateResult;
};

/**
* A head that doesn't contain any content
*/
export type ThinHead = {
[headAndContentSym]: true;
};

export function isHeadAndContent(obj: unknown): obj is HeadAndContent {
return typeof obj === 'object' && obj !== null && !!(obj as any)[headAndContentSym];
}
Expand All @@ -19,3 +26,9 @@ export function createHeadAndContent(head: string, content: RenderTemplateResult
content,
};
}

export function createThinHead(): ThinHead {
return {
[headAndContentSym]: true,
};
}
2 changes: 1 addition & 1 deletion packages/astro/src/runtime/server/render/astro/instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export class AstroComponentInstance {
}
}

init(result: SSRResult) {
init(result: SSRResult): AstroFactoryReturnValue | Promise<AstroFactoryReturnValue> {
if (this.returnValue !== undefined) {
return this.returnValue;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/runtime/server/render/astro/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ async function bufferHeadContent(result: SSRResult) {
}
// Call component instances that might have head content to be propagated up.
const returnValue = await value.init(result);
if (isHeadAndContent(returnValue)) {
if (isHeadAndContent(returnValue) && returnValue.head) {
result._metadata.extraHead.push(returnValue.head);
}
}
Expand Down
6 changes: 4 additions & 2 deletions packages/astro/src/runtime/server/render/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
} from './common.js';
import { componentIsHTMLElement, renderHTMLElement } from './dom.js';
import { maybeRenderHead } from './head.js';
import { containsServerDirective, renderServerIsland } from './server-islands.js';
import { ServerIslandComponent, containsServerDirective } from './server-islands.js';
import { type ComponentSlots, renderSlotToString, renderSlots } from './slot.js';
import { formatList, internalSpreadAttributes, renderElement, voidElementNames } from './util.js';

Expand Down Expand Up @@ -442,7 +442,9 @@ function renderAstroComponent(
slots: any = {},
): RenderInstance {
if (containsServerDirective(props)) {
return renderServerIsland(result, displayName, props, slots);
const serverIslandComponent = new ServerIslandComponent(result, props, slots, displayName);
result._metadata.propagators.add(serverIslandComponent);
return serverIslandComponent;
}

const instance = createAstroComponentInstance(result, displayName, Component, props, slots);
Expand Down
4 changes: 4 additions & 0 deletions packages/astro/src/runtime/server/render/csp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export function renderCspContent(result: SSRResult): string {
finalStyleHashes.add(`'sha256-${styleHash}'`);
}

for (const scriptHash of result._metadata.extraScriptHashes) {
finalScriptHashes.add(`'sha256-${scriptHash}'`);
}

const scriptSrc = `style-src 'self' ${Array.from(finalStyleHashes).join(' ')};`;
const styleSrc = `script-src 'self' ${Array.from(finalScriptHashes).join(' ')};`;
return `${scriptSrc} ${styleSrc}`;
Expand Down
1 change: 0 additions & 1 deletion packages/astro/src/runtime/server/render/page.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { type NonAstroPageComponent, renderComponentToString } from './component.js';
import type { AstroComponentFactory } from './index.js';

import type { RouteData, SSRResult } from '../../../types/public/internal.js';
import { isAstroComponentFactory } from './astro/index.js';
import { renderToAsyncIterable, renderToReadableStream, renderToString } from './astro/render.js';
Expand Down
Loading