diff --git a/packages/astro/src/container/index.ts b/packages/astro/src/container/index.ts index acacdf22b8d3..dddd6b09e09e 100644 --- a/packages/astro/src/container/index.ts +++ b/packages/astro/src/container/index.ts @@ -164,7 +164,6 @@ function createManifest( clientScriptHashes: manifest?.clientScriptHashes ?? [], clientStyleHashes: manifest?.clientStyleHashes ?? [], shouldInjectCspMetaTags: manifest?.shouldInjectCspMetaTags ?? false, - astroIslandHashes: manifest?.astroIslandHashes ?? {}, }; } @@ -253,7 +252,6 @@ type AstroContainerManifest = Pick< | 'clientScriptHashes' | 'clientStyleHashes' | 'shouldInjectCspMetaTags' - | 'astroIslandHashes' >; type AstroContainerConstructor = { diff --git a/packages/astro/src/core/app/types.ts b/packages/astro/src/core/app/types.ts index 62211ea1f937..18f926eb37f0 100644 --- a/packages/astro/src/core/app/types.ts +++ b/packages/astro/src/core/app/types.ts @@ -92,7 +92,6 @@ export type SSRManifest = { * When enabled, Astro tracks the hashes of script and styles, and eventually it will render the `` tag */ shouldInjectCspMetaTags: boolean; - astroIslandHashes: Record; }; export type SSRActions = { diff --git a/packages/astro/src/core/astro-islands-hashes.ts b/packages/astro/src/core/astro-islands-hashes.ts deleted file mode 100644 index 56f4ad6fbffe..000000000000 --- a/packages/astro/src/core/astro-islands-hashes.ts +++ /dev/null @@ -1,10 +0,0 @@ -// This file is code-generated, please don't change it manually -export const ASTRO_ISLAND_HASHES = { - "astro-island": "p9VbHs/ClkQc+x63XdUjvCAgeWxA4ZGvpebJtMn9jbs=", - "idle": "BF0290pkb3jxQsE7z00xR8Imp8X34FLC88L0lkMnrGw=", - "load": "QzWFZi+FLIx23tnm9SBU4aEgx4x8DsuASP07mfqol/c=", - "media": "0chmwFk0zaA528yFfGV7J9ppIpdfTPPULncDF3WG7Zs=", - "only": "eIXWvAmxkr251LJZkjniEK5LcPF3NkapbJepohwYRIc=", - "visible": "Q2BPg90ZMplYY+FSdApNErhpWafg2hcRRbndmvxuL/Q=", - "astro-island-styles": "s81ZcLcyAa7P/Jh5M5hUxYthTGwW+iZY3e6aHrQ8H9E=" -}; \ No newline at end of file diff --git a/packages/astro/src/core/base-pipeline.ts b/packages/astro/src/core/base-pipeline.ts index 7e3174a48c46..d239a4a2fae2 100644 --- a/packages/astro/src/core/base-pipeline.ts +++ b/packages/astro/src/core/base-pipeline.ts @@ -13,6 +13,7 @@ import type { } from '../types/public/internal.js'; import { createOriginCheckMiddleware } from './app/middlewares.js'; import type { SSRActions } from './app/types.js'; +import { createCSPMiddleware } from './csp/middleware.js'; import { ActionNotFoundError } from './errors/errors-data.js'; import { AstroError } from './errors/index.js'; import type { Logger } from './logger/core.js'; @@ -20,7 +21,6 @@ import { NOOP_MIDDLEWARE_FN } from './middleware/noop-middleware.js'; import { sequence } from './middleware/sequence.js'; import { RouteCache } from './render/route-cache.js'; import { createDefaultRoutes } from './routing/default.js'; -import { createCSPMiddleware } from './csp/middleware.js'; /** * The `Pipeline` represents the static parts of rendering that do not change between requests. diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index f6170516b36a..b143fc06a3fd 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -9,7 +9,6 @@ import { getStaticImageList, prepareAssetsGenerationEnv, } from '../../assets/build/generate.js'; -import { type BuildInternals, hasPrerenderedPages } from './internal.js'; import { isRelativePath, joinPaths, @@ -29,6 +28,7 @@ import type { SSRLoadedRenderer, } from '../../types/public/internal.js'; import type { SSRActions, SSRManifest, SSRManifestI18n } from '../app/types.js'; +import { shouldTrackCspHashes, trackScriptHashes, trackStyleHashes } from '../csp/common.js'; import { NoPrerenderedRoutesWithDomains } from '../errors/errors-data.js'; import { AstroError, AstroErrorData } from '../errors/index.js'; import { NOOP_MIDDLEWARE_FN } from '../middleware/noop-middleware.js'; @@ -41,6 +41,7 @@ import { matchRoute } from '../routing/match.js'; import { stringifyParams } from '../routing/params.js'; import { getOutputFilename } from '../util.js'; import { getOutFile, getOutFolder } from './common.js'; +import { type BuildInternals, hasPrerenderedPages } from './internal.js'; import { cssOrder, mergeInlineCss } from './internal.js'; import { BuildPipeline } from './pipeline.js'; import type { @@ -50,8 +51,6 @@ import type { StylesheetAsset, } from './types.js'; import { getTimeStat, shouldAppendForwardSlash } from './util.js'; -import { shouldTrackCspHashes, trackScriptHashes, trackStyleHashes } from '../csp/common.js'; -import { ASTRO_ISLAND_HASHES } from '../astro-islands-hashes.js'; export async function generatePages(options: StaticBuildOptions, internals: BuildInternals) { const generatePagesTimer = performance.now(); @@ -70,7 +69,7 @@ export async function generatePages(options: StaticBuildOptions, internals: Buil const actions: SSRActions = internals.astroActionsEntryPoint ? await import(internals.astroActionsEntryPoint.toString()).then((mod) => mod) : NOOP_ACTIONS_MOD; - manifest = createBuildManifest( + manifest = await createBuildManifest( options.settings, internals, renderers.renderers as SSRLoadedRenderer[], @@ -604,22 +603,22 @@ function getPrettyRouteName(route: RouteData): string { * * Renderers needs to be pulled out from the page module emitted during the build. */ -function createBuildManifest( +async function createBuildManifest( settings: AstroSettings, internals: BuildInternals, renderers: SSRLoadedRenderer[], middleware: MiddlewareHandler, actions: SSRActions, key: Promise, -): SSRManifest { +): Promise { let i18nManifest: SSRManifestI18n | undefined = undefined; let clientStyleHashes: string[] = []; let clientScriptHashes: string[] = []; if (shouldTrackCspHashes(settings.config)) { - clientScriptHashes = trackScriptHashes(internals, settings); - clientStyleHashes = trackStyleHashes(internals); + clientScriptHashes = await trackScriptHashes(internals, settings); + clientStyleHashes = await trackStyleHashes(internals, settings); } if (settings.config.i18n) { @@ -668,6 +667,5 @@ function createBuildManifest( clientStyleHashes, clientScriptHashes, shouldInjectCspMetaTags: shouldTrackCspHashes(settings.config), - astroIslandHashes: ASTRO_ISLAND_HASHES, }; } diff --git a/packages/astro/src/core/build/internal.ts b/packages/astro/src/core/build/internal.ts index a726480b8cef..5f303596b620 100644 --- a/packages/astro/src/core/build/internal.ts +++ b/packages/astro/src/core/build/internal.ts @@ -80,6 +80,10 @@ export interface BuildInternals { // A list of all static files created during the build. Used for SSR. staticFiles: Set; + + // A list of all statics chunks and assets that are built in the client + clientChunksAndAssets: Set; + // The SSR entry chunk. Kept in internals to share between ssr/client build steps ssrEntryChunk?: Rollup.OutputChunk; // The SSR manifest entry chunk. @@ -121,6 +125,7 @@ export function createBuildInternals(): BuildInternals { prerenderOnlyChunks: [], astroActionsEntryPoint: undefined, middlewareEntryPoint: undefined, + clientChunksAndAssets: new Set(), }; } diff --git a/packages/astro/src/core/build/plugins/plugin-internals.ts b/packages/astro/src/core/build/plugins/plugin-internals.ts index 2d4dfc3603f3..6bef4414b3a5 100644 --- a/packages/astro/src/core/build/plugins/plugin-internals.ts +++ b/packages/astro/src/core/build/plugins/plugin-internals.ts @@ -40,7 +40,9 @@ function vitePluginInternals(input: Set, internals: BuildInternals): Vit ); } await Promise.all(promises); - for (const [, chunk] of Object.entries(bundle)) { + for (const [_, chunk] of Object.entries(bundle)) { + internals.clientChunksAndAssets.add(chunk.fileName); + if (chunk.type === 'chunk' && chunk.facadeModuleId) { const specifiers = mapping.get(chunk.facadeModuleId) || new Set([chunk.facadeModuleId]); for (const specifier of specifiers) { diff --git a/packages/astro/src/core/build/plugins/plugin-manifest.ts b/packages/astro/src/core/build/plugins/plugin-manifest.ts index 015e79ff20d0..75fb1b466c0a 100644 --- a/packages/astro/src/core/build/plugins/plugin-manifest.ts +++ b/packages/astro/src/core/build/plugins/plugin-manifest.ts @@ -12,6 +12,7 @@ import type { SerializedRouteInfo, SerializedSSRManifest, } from '../../app/types.js'; +import { shouldTrackCspHashes, trackScriptHashes, trackStyleHashes } from '../../csp/common.js'; import { encodeKey } from '../../encryption.js'; import { fileExtension, joinPaths, prependForwardSlash } from '../../path.js'; import { DEFAULT_COMPONENTS } from '../../routing/default.js'; @@ -23,8 +24,6 @@ import { type BuildInternals, cssOrder, mergeInlineCss } from '../internal.js'; import type { AstroBuildPlugin } from '../plugin.js'; import type { StaticBuildOptions } from '../types.js'; import { makePageDataKey } from './util.js'; -import { shouldTrackCspHashes, trackScriptHashes, trackStyleHashes } from '../../csp/common.js'; -import { ASTRO_ISLAND_HASHES } from '../../astro-islands-hashes.js'; const manifestReplace = '@@ASTRO_MANIFEST_REPLACE@@'; const replaceExp = new RegExp(`['"]${manifestReplace}['"]`, 'g'); @@ -143,7 +142,7 @@ async function createManifest( const staticFiles = internals.staticFiles; const encodedKey = await encodeKey(await buildOpts.key); - return buildManifest(buildOpts, internals, Array.from(staticFiles), encodedKey); + return await buildManifest(buildOpts, internals, Array.from(staticFiles), encodedKey); } /** @@ -157,12 +156,12 @@ function injectManifest(manifest: SerializedSSRManifest, chunk: Readonly { const { settings } = opts; const routes: SerializedRouteInfo[] = []; @@ -281,8 +280,8 @@ function buildManifest( let clientStyleHashes: string[] = []; if (shouldTrackCspHashes(settings.config)) { - clientScriptHashes = trackScriptHashes(internals, opts.settings); - clientStyleHashes = trackStyleHashes(internals); + clientScriptHashes = await trackScriptHashes(internals, opts.settings); + clientStyleHashes = await trackStyleHashes(internals, opts.settings); } return { @@ -317,6 +316,5 @@ function buildManifest( shouldInjectCspMetaTags: shouldTrackCspHashes(opts.settings.config), clientStyleHashes, clientScriptHashes, - astroIslandHashes: ASTRO_ISLAND_HASHES, }; } diff --git a/packages/astro/src/core/build/plugins/plugin-ssr.ts b/packages/astro/src/core/build/plugins/plugin-ssr.ts index 649bdc39ebec..60bf63ef2289 100644 --- a/packages/astro/src/core/build/plugins/plugin-ssr.ts +++ b/packages/astro/src/core/build/plugins/plugin-ssr.ts @@ -113,7 +113,6 @@ function vitePluginSSR( internals.staticFiles.add(chunk.fileName); } } - for (const [, chunk] of Object.entries(bundle)) { if (chunk.type === 'asset') { continue; diff --git a/packages/astro/src/core/csp/common.ts b/packages/astro/src/core/csp/common.ts index 74c7c2f67652..d1ed301f7432 100644 --- a/packages/astro/src/core/csp/common.ts +++ b/packages/astro/src/core/csp/common.ts @@ -1,42 +1,82 @@ +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { ISLAND_STYLES } from '../../runtime/server/astro-island-styles.js'; +import astroIslandPrebuiltDev from '../../runtime/server/astro-island.prebuilt-dev.js'; +import astroIslandPrebuilt from '../../runtime/server/astro-island.prebuilt.js'; +import type { AstroSettings } from '../../types/astro.js'; import type { AstroConfig } from '../../types/public/index.js'; import type { BuildInternals } from '../build/internal.js'; -import crypto from 'node:crypto'; -import type { AstroSettings } from '../../types/astro.js'; +import { generateDigest } from '../encryption.js'; export function shouldTrackCspHashes(config: AstroConfig): boolean { return config.experimental?.csp === true; } -export function trackStyleHashes(internals: BuildInternals): string[] { +export async function trackStyleHashes( + internals: BuildInternals, + settings: AstroSettings, +): Promise { const clientStyleHashes: string[] = []; for (const [_, page] of internals.pagesByViteID.entries()) { for (const style of page.styles) { if (style.sheet.type === 'inline') { - clientStyleHashes.push(generateHash(style.sheet.content)); + clientStyleHashes.push(await generateDigest(style.sheet.content)); } } } + for (const clientAsset in internals.clientChunksAndAssets) { + const contents = readFileSync( + fileURLToPath(new URL(clientAsset, settings.config.build.client)), + 'utf-8', + ); + if (clientAsset.endsWith('.css') || clientAsset.endsWith('.css')) { + clientStyleHashes.push(await generateDigest(contents)); + } + } + + if (settings.renderers.length > 0) { + clientStyleHashes.push(await generateDigest(ISLAND_STYLES)); + } + return clientStyleHashes; } -export function trackScriptHashes(internals: BuildInternals, settings: AstroSettings): string[] { +export async function trackScriptHashes( + internals: BuildInternals, + settings: AstroSettings, +): Promise { const clientScriptHashes: string[] = []; for (const script of internals.inlinedScripts.values()) { - clientScriptHashes.push(generateHash(script)); + clientScriptHashes.push(await generateDigest(script)); + } + + for (const directiveContent of Array.from(settings.clientDirectives.values())) { + clientScriptHashes.push(await generateDigest(directiveContent)); + } + + for (const clientAsset in internals.clientChunksAndAssets) { + const contents = readFileSync( + fileURLToPath(new URL(clientAsset, settings.config.build.client)), + 'utf-8', + ); + if (clientAsset.endsWith('.js') || clientAsset.endsWith('.mjs')) { + clientScriptHashes.push(await generateDigest(contents)); + } } for (const script of settings.scripts) { const { content, stage } = script; if (stage === 'head-inline' || stage === 'before-hydration') { - clientScriptHashes.push(generateHash(content)); + clientScriptHashes.push(await generateDigest(content)); } } - return clientScriptHashes; -} + if (settings.renderers.length > 0) { + clientScriptHashes.push(await generateDigest(astroIslandPrebuilt)); + clientScriptHashes.push(await generateDigest(astroIslandPrebuiltDev)); + } -function generateHash(content: string): string { - return crypto.createHash('sha256').update(content).digest('base64'); + return clientScriptHashes; } diff --git a/packages/astro/src/core/encryption.ts b/packages/astro/src/core/encryption.ts index 5f72e7367f16..e3668ab5d865 100644 --- a/packages/astro/src/core/encryption.ts +++ b/packages/astro/src/core/encryption.ts @@ -109,3 +109,13 @@ export async function decryptString(key: CryptoKey, encoded: string) { const decryptedString = decoder.decode(decryptedBuffer); return decryptedString; } + +/** + * Generates an SHA-256 digest of the given string. + * @param {string} data The string to hash. + */ +export async function generateDigest(data: string): Promise { + const hashBuffer = await crypto.subtle.digest('SHA-256', encoder.encode(data)); + + return encodeBase64(new Uint8Array(hashBuffer)); +} diff --git a/packages/astro/src/core/render-context.ts b/packages/astro/src/core/render-context.ts index 81610f42635a..b877b4c859fe 100644 --- a/packages/astro/src/core/render-context.ts +++ b/packages/astro/src/core/render-context.ts @@ -465,7 +465,6 @@ export class RenderContext { shouldInjectCspMetaTags: manifest.shouldInjectCspMetaTags, clientScriptHashes: manifest.clientScriptHashes, clientStyleHashes: manifest.clientStyleHashes, - astroIslandHashes: manifest.astroIslandHashes, }; return result; diff --git a/packages/astro/src/runtime/server/astro-island-styles.ts b/packages/astro/src/runtime/server/astro-island-styles.ts index ca816c9204b9..422f6bbc49f4 100644 --- a/packages/astro/src/runtime/server/astro-island-styles.ts +++ b/packages/astro/src/runtime/server/astro-island-styles.ts @@ -1,2 +1 @@ -export const ISLAND_STYLES = - ''; +export const ISLAND_STYLES = 'astro-island,astro-slot,astro-static-slot{display:contents}'; diff --git a/packages/astro/src/runtime/server/render/common.ts b/packages/astro/src/runtime/server/render/common.ts index de70c6127e82..5c765d6b2b35 100644 --- a/packages/astro/src/runtime/server/render/common.ts +++ b/packages/astro/src/runtime/server/render/common.ts @@ -1,4 +1,3 @@ -import type { RenderInstruction } from './instruction.js'; import type { SSRResult } from '../../../types/public/internal.js'; import type { HTMLBytes, HTMLString } from '../escape.js'; import { markHTMLString } from '../escape.js'; @@ -8,6 +7,7 @@ import { getPrescripts, } from '../scripts.js'; import { renderAllHeadContent } from './head.js'; +import type { RenderInstruction } from './instruction.js'; import { isRenderInstruction } from './instruction.js'; import { renderServerIslandRuntime } from './server-islands.js'; import { type SlotString, isSlotString } from './slot.js'; diff --git a/packages/astro/src/runtime/server/render/csp.ts b/packages/astro/src/runtime/server/render/csp.ts index 143bf0746b9a..2a148c35006b 100644 --- a/packages/astro/src/runtime/server/render/csp.ts +++ b/packages/astro/src/runtime/server/render/csp.ts @@ -12,16 +12,6 @@ export function renderCspContent(result: SSRResult): string { finalStyleHashes.add(`'sha256-${styleHash}'`); } - if (result.renderers.length > 0) { - for (const [ name, hash ] of Object.entries(result.astroIslandHashes)) { - if (name === 'astro-island-styles') { - finalStyleHashes.add(`'sha256-${hash}'`); - } else { - finalScriptHashes.add(`'sha256-${hash}'`); - } - } - } - const scriptSrc = `style-src 'self' ${Array.from(finalStyleHashes).join(' ')};`; const styleSrc = `script-src 'self' ${Array.from(finalScriptHashes).join(' ')};`; return `${scriptSrc} ${styleSrc}`; diff --git a/packages/astro/src/runtime/server/render/head.ts b/packages/astro/src/runtime/server/render/head.ts index 7989cb7acb5d..3af8be5a87b6 100644 --- a/packages/astro/src/runtime/server/render/head.ts +++ b/packages/astro/src/runtime/server/render/head.ts @@ -1,9 +1,9 @@ import type { SSRResult } from '../../../types/public/internal.js'; import { markHTMLString } from '../escape.js'; +import { renderCspContent } from './csp.js'; import type { MaybeRenderHeadInstruction, RenderHeadInstruction } from './instruction.js'; import { createRenderInstruction } from './instruction.js'; import { renderElement } from './util.js'; -import { renderCspContent } from './csp.js'; // Filter out duplicate elements in our set const uniqueElements = (item: any, index: number, all: any[]) => { diff --git a/packages/astro/src/runtime/server/scripts.ts b/packages/astro/src/runtime/server/scripts.ts index e77947de9168..fa87fcd631ca 100644 --- a/packages/astro/src/runtime/server/scripts.ts +++ b/packages/astro/src/runtime/server/scripts.ts @@ -1,7 +1,7 @@ import type { SSRResult } from '../../types/public/internal.js'; +import { ISLAND_STYLES } from './astro-island-styles.js'; import islandScriptDev from './astro-island.prebuilt-dev.js'; import islandScript from './astro-island.prebuilt.js'; -import { ISLAND_STYLES } from './astro-island-styles.js'; export function determineIfNeedsHydrationScript(result: SSRResult): boolean { if (result._metadata.hasHydrationScript) { @@ -36,7 +36,7 @@ export function getPrescripts(result: SSRResult, type: PrescriptType, directive: // deps to be loaded immediately. switch (type) { case 'both': - return `${ISLAND_STYLES}`; case 'directive': diff --git a/packages/astro/src/types/public/internal.ts b/packages/astro/src/types/public/internal.ts index 50d202389a5d..73e1502c99a6 100644 --- a/packages/astro/src/types/public/internal.ts +++ b/packages/astro/src/types/public/internal.ts @@ -1,13 +1,13 @@ // TODO: Should the types here really be public? import type { ErrorPayload as ViteErrorPayload } from 'vite'; +import type { SSRManifest } from '../../core/app/types.js'; import type { AstroCookies } from '../../core/cookies/cookies.js'; import type { AstroComponentInstance } from '../../runtime/server/index.js'; import type { Params } from './common.js'; import type { AstroConfig, RedirectConfig } from './config.js'; import type { AstroGlobal, AstroGlobalPartial } from './context.js'; import type { AstroRenderer } from './integrations.js'; -import type { SSRManifest } from '../../core/app/types.js'; export type { SSRManifest } from '../../core/app/types.js'; @@ -253,7 +253,6 @@ export interface SSRResult { shouldInjectCspMetaTags: boolean; clientScriptHashes: SSRManifest['clientScriptHashes']; clientStyleHashes: SSRManifest['clientStyleHashes']; - astroIslandHashes: SSRManifest['astroIslandHashes']; } /** diff --git a/packages/astro/src/vite-plugin-astro-server/plugin.ts b/packages/astro/src/vite-plugin-astro-server/plugin.ts index e43e45dd71af..567afee158f2 100644 --- a/packages/astro/src/vite-plugin-astro-server/plugin.ts +++ b/packages/astro/src/vite-plugin-astro-server/plugin.ts @@ -5,6 +5,7 @@ import { fileURLToPath } from 'node:url'; import type * as vite from 'vite'; import { normalizePath } from 'vite'; import type { SSRManifest, SSRManifestI18n } from '../core/app/types.js'; +import { shouldTrackCspHashes } from '../core/csp/common.js'; import { warnMissingAdapter } from '../core/dev/adapter-validation.js'; import { createKey, getEnvironmentKey, hasEnvironmentKey } from '../core/encryption.js'; import { getViteErrorPayload } from '../core/errors/dev/index.js'; @@ -25,8 +26,6 @@ import { DevPipeline } from './pipeline.js'; import { handleRequest } from './request.js'; import { setRouteError } from './server-state.js'; import { trailingSlashMiddleware } from './trailing-slash.js'; -import { ASTRO_ISLAND_HASHES } from '../core/astro-islands-hashes.js'; -import { shouldTrackCspHashes } from '../core/csp/common.js'; interface AstroPluginOptions { settings: AstroSettings; @@ -211,6 +210,5 @@ export function createDevelopmentManifest(settings: AstroSettings): SSRManifest clientScriptHashes: [], clientStyleHashes: [], shouldInjectCspMetaTags: shouldTrackCspHashes(settings.config), - astroIslandHashes: ASTRO_ISLAND_HASHES, }; } diff --git a/packages/astro/test/astro-dynamic.test.js b/packages/astro/test/astro-dynamic.test.js index 47344bf0ea97..0af25560e3c8 100644 --- a/packages/astro/test/astro-dynamic.test.js +++ b/packages/astro/test/astro-dynamic.test.js @@ -17,7 +17,7 @@ describe('Dynamic components', () => { const html = await fixture.readFile('/index.html'); const $ = cheerio.load(html); - assert.equal($('script').length, 1); + assert.equal($('script').length, 2, "to have directive and astro island script"); }); it('Loads pages using client:media hydrator', async () => { @@ -25,7 +25,7 @@ describe('Dynamic components', () => { const $ = cheerio.load(html); // test 1: static value rendered - assert.equal($('script').length, 1); + assert.equal($('script').length, 2, "to have directive and astro island script"); }); it('Loads pages using client:only hydrator', async () => { @@ -56,7 +56,7 @@ describe('Dynamic components subpath', () => { const html = await fixture.readFile('/index.html'); const $ = cheerio.load(html); - assert.equal($('script').length, 1); + assert.equal($('script').length, 2); }); it('Loads pages using client:media hydrator', async () => { @@ -64,7 +64,7 @@ describe('Dynamic components subpath', () => { const $ = cheerio.load(html); // test 1: static value rendered - assert.equal($('script').length, 1); + assert.equal($('script').length, 2, "to have directive and astro island script"); }); it('Loads pages using client:only hydrator', async () => { diff --git a/packages/astro/test/astro-slot-with-client.test.js b/packages/astro/test/astro-slot-with-client.test.js index 8f34b8fc9158..cf762a9db051 100644 --- a/packages/astro/test/astro-slot-with-client.test.js +++ b/packages/astro/test/astro-slot-with-client.test.js @@ -15,7 +15,7 @@ describe('Slots with client: directives', () => { it('Tags of dynamic tags works', async () => { const html = await fixture.readFile('/index.html'); const $ = cheerio.load(html); - assert.equal($('script').length, 1); + assert.equal($('script').length, 2); }); it('Astro slot tags are kept', async () => { diff --git a/packages/astro/test/astro-slots-nested.test.js b/packages/astro/test/astro-slots-nested.test.js index 3d04b00e27ba..7ab56249c32e 100644 --- a/packages/astro/test/astro-slots-nested.test.js +++ b/packages/astro/test/astro-slots-nested.test.js @@ -15,7 +15,7 @@ describe('Nested Slots', () => { it('Hidden nested slots see their hydration scripts hoisted', async () => { const html = await fixture.readFile('/hidden-nested/index.html'); const $ = cheerio.load(html); - assert.equal($('script').length, 1, 'script rendered'); + assert.equal($('script').length, 2, 'script rendered'); const scriptInTemplate = $($('template')[0].children[0]).find('script'); assert.equal(scriptInTemplate.length, 0, 'script defined outside of the inner template'); }); @@ -23,7 +23,7 @@ describe('Nested Slots', () => { it('Slots rendered via Astro.slots.render have the hydration script', async () => { const html = await fixture.readFile('/component-slot/index.html'); const $ = cheerio.load(html); - assert.equal($('script').length, 1, 'script rendered'); + assert.equal($('script').length, 2, 'script rendered'); }); describe('Client components nested inside server-only framework components', () => { diff --git a/packages/astro/test/csp.test.js b/packages/astro/test/csp.test.js index fc5703c0a016..6671483dceed 100644 --- a/packages/astro/test/csp.test.js +++ b/packages/astro/test/csp.test.js @@ -1,8 +1,8 @@ -import { before, describe, it } from 'node:test'; -import { loadFixture } from './test-utils.js'; -import testAdapter from './test-adapter.js'; import assert from 'node:assert/strict'; +import { before, describe, it } from 'node:test'; import * as cheerio from 'cheerio'; +import testAdapter from './test-adapter.js'; +import { loadFixture } from './test-utils.js'; describe('CSP', () => { let app; @@ -39,24 +39,7 @@ describe('CSP', () => { `Should have a CSP meta tag for ${hash}`, ); } - - let [, astroStyleHash] = Object.entries(manifest.astroIslandHashes).find( - ([name, _]) => name === 'astro-island-styles', - ); - astroStyleHash = `sha256-${astroStyleHash}`; - - let [, astroIsland] = Object.entries(manifest.astroIslandHashes).find(([name, _]) => name === 'astro-island'); - astroIsland = `sha256-${astroIsland}`; - - assert.ok( - meta.attr('content').includes(astroStyleHash), - `Should have a CSP meta tag for ${astroStyleHash}`, - ); - - assert.ok( - meta.attr('content').includes(astroIsland), - `Should have a CSP meta tag for ${astroIsland}`, - ); + } else { assert.fail('Should have the manifest'); } diff --git a/packages/astro/test/hydration-race.test.js b/packages/astro/test/hydration-race.test.js index 00837fdd9bc3..1916c20d06ad 100644 --- a/packages/astro/test/hydration-race.test.js +++ b/packages/astro/test/hydration-race.test.js @@ -25,7 +25,7 @@ describe('Hydration script ordering', async () => { // Sanity check that we're only rendering them once. assert.equal($('style').length, 1, 'hydration style added once'); - assert.equal($('script').length, 1, 'only one hydration script needed'); + assert.equal($('script').length, 2, 'only 2 hydration scripts needed'); }); it('Hydration script included when inside dynamic slot', async () => { @@ -35,7 +35,7 @@ describe('Hydration script ordering', async () => { // First, let's make sure all islands rendered assert.equal($('astro-island').length, 1); - // There should be 1 script - assert.equal($('script').length, 1); + // There should be 2 scripts: directive and astro island + assert.equal($('script').length, 2); }); }); diff --git a/scripts/cmd/prebuild.js b/scripts/cmd/prebuild.js index 4b26aa529bd5..68a3a4f91386 100644 --- a/scripts/cmd/prebuild.js +++ b/scripts/cmd/prebuild.js @@ -4,14 +4,11 @@ import { fileURLToPath, pathToFileURL } from 'node:url'; import esbuild from 'esbuild'; import { red } from 'kleur/colors'; import { glob } from 'tinyglobby'; -import crypto from 'node:crypto'; function escapeTemplateLiterals(str) { return str.replace(/\`/g, '\\`').replace(/\$\{/g, '\\${'); } -const ASTRO_ISLAND_STYLE_REGEX = /'([^']*)'/; - export default async function prebuild(...args) { let buildToString = args.indexOf('--to-string'); if (buildToString !== -1) { @@ -43,8 +40,6 @@ export default async function prebuild(...args) { return outURL; } - const hashes = new Map(); - async function prebuildFile(filepath) { let tscode = await fs.promises.readFile(filepath, 'utf-8'); // If we're bundling a client directive, modify the code to match `packages/astro/src/core/client-directive/build.ts`. @@ -114,35 +109,9 @@ export default async function prebuild(...args) { export default \`${generatedCode}\`;`; const url = getPrebuildURL(filepath, result.dev); await fs.promises.writeFile(url, mod, 'utf-8'); - const hash = crypto.createHash('sha256').update(code).digest('base64'); - const basename = path.basename(filepath); - hashes.set(basename.slice(0, basename.indexOf('.')), hash); } } for (const entrypoint of entryPoints) { await prebuildFile(entrypoint); } - - const fileContent = await fs.promises.readFile( - new URL('../../packages/astro/src/runtime/server/astro-island-styles.ts', import.meta.url), - 'utf-8', - ); - const styleContent = fileContent.match(ASTRO_ISLAND_STYLE_REGEX)[1]; - hashes.set( - 'astro-island-styles', - crypto.createHash('sha256').update(styleContent).digest('base64'), - ); - - const entries = JSON.stringify(Object.fromEntries(hashes.entries()), null, 2); - const content = `// This file is code-generated, please don't change it manually -export const ASTRO_ISLAND_HASHES = ${entries};`; - await fs.promises.writeFile( - path.join( - fileURLToPath(import.meta.url), - '../../../packages/astro/src/core', - 'astro-islands-hashes.ts', - ), - content, - 'utf-8', - ); }