summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/tender-hounds-juggle.md5
-rw-r--r--packages/astro/package.json2
-rw-r--r--packages/astro/src/@types/astro.ts8
-rw-r--r--packages/astro/src/content/runtime.ts3
-rw-r--r--packages/astro/src/core/app/common.ts4
-rw-r--r--packages/astro/src/core/app/index.ts2
-rw-r--r--packages/astro/src/core/app/types.ts8
-rw-r--r--packages/astro/src/core/build/generate.ts2
-rw-r--r--packages/astro/src/core/build/graph.ts1
-rw-r--r--packages/astro/src/core/build/internal.ts4
-rw-r--r--packages/astro/src/core/build/plugins/index.ts4
-rw-r--r--packages/astro/src/core/build/plugins/plugin-ssr.ts2
-rw-r--r--packages/astro/src/core/create-vite.ts4
-rw-r--r--packages/astro/src/core/render/context.ts2
-rw-r--r--packages/astro/src/core/render/core.ts2
-rw-r--r--packages/astro/src/core/render/dev/head.ts34
-rw-r--r--packages/astro/src/core/render/dev/index.ts10
-rw-r--r--packages/astro/src/core/render/dev/metadata.ts47
-rw-r--r--packages/astro/src/core/render/result.ts20
-rw-r--r--packages/astro/src/jsx/babel.ts1
-rw-r--r--packages/astro/src/runtime/server/index.ts4
-rw-r--r--packages/astro/src/runtime/server/jsx.ts4
-rw-r--r--packages/astro/src/runtime/server/render/astro/factory.ts8
-rw-r--r--packages/astro/src/runtime/server/render/astro/instance.ts4
-rw-r--r--packages/astro/src/runtime/server/render/common.ts46
-rw-r--r--packages/astro/src/runtime/server/render/index.ts1
-rw-r--r--packages/astro/src/runtime/server/render/page.ts22
-rw-r--r--packages/astro/src/runtime/server/render/scope.ts36
-rw-r--r--packages/astro/src/runtime/server/render/slot.ts4
-rw-r--r--packages/astro/src/vite-plugin-astro/index.ts1
-rw-r--r--packages/astro/src/vite-plugin-astro/types.ts1
-rw-r--r--packages/astro/src/vite-plugin-head-propagation/index.ts112
-rw-r--r--packages/astro/src/vite-plugin-head/index.ts116
-rw-r--r--packages/astro/src/vite-plugin-markdown/index.ts1
-rw-r--r--packages/astro/test/units/dev/collections-renderentry.test.js18
-rw-r--r--packages/astro/test/units/dev/head-injection.test.js9
-rw-r--r--pnpm-lock.yaml8
37 files changed, 260 insertions, 300 deletions
diff --git a/.changeset/tender-hounds-juggle.md b/.changeset/tender-hounds-juggle.md
new file mode 100644
index 000000000..8ea518f0d
--- /dev/null
+++ b/.changeset/tender-hounds-juggle.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Avoid implicit head injection when a head is in the tree
diff --git a/packages/astro/package.json b/packages/astro/package.json
index e730dfb86..246e360d3 100644
--- a/packages/astro/package.json
+++ b/packages/astro/package.json
@@ -106,7 +106,7 @@
"test:e2e:match": "playwright test -g"
},
"dependencies": {
- "@astrojs/compiler": "^1.2.0",
+ "@astrojs/compiler": "^1.3.0",
"@astrojs/language-server": "^0.28.3",
"@astrojs/markdown-remark": "^2.1.2",
"@astrojs/telemetry": "^2.1.0",
diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts
index 4eca60b34..ebb15de1d 100644
--- a/packages/astro/src/@types/astro.ts
+++ b/packages/astro/src/@types/astro.ts
@@ -1578,6 +1578,7 @@ export interface SSRMetadata {
hasHydrationScript: boolean;
hasDirectives: Set<string>;
hasRenderedHead: boolean;
+ headInTree: boolean;
}
/**
@@ -1592,11 +1593,16 @@ export interface SSRMetadata {
*/
export type PropagationHint = 'none' | 'self' | 'in-tree';
+export type SSRComponentMetadata = {
+ propagation: PropagationHint,
+ containsHead: boolean
+};
+
export interface SSRResult {
styles: Set<SSRElement>;
scripts: Set<SSRElement>;
links: Set<SSRElement>;
- propagation: Map<string, PropagationHint>;
+ componentMetadata: Map<string, SSRComponentMetadata>;
propagators: Map<AstroComponentFactory, AstroComponentInstance>;
extraHead: Array<string>;
cookies: AstroCookies | undefined;
diff --git a/packages/astro/src/content/runtime.ts b/packages/astro/src/content/runtime.ts
index d58e4be62..a8d18e06f 100644
--- a/packages/astro/src/content/runtime.ts
+++ b/packages/astro/src/content/runtime.ts
@@ -4,7 +4,6 @@ import { prependForwardSlash } from '../core/path.js';
import {
createComponent,
createHeadAndContent,
- createScopedResult,
renderComponent,
renderScriptElement,
renderStyleElement,
@@ -180,7 +179,7 @@ async function render({
return createHeadAndContent(
unescapeHTML(styles + links + scripts) as any,
renderTemplate`${renderComponent(
- createScopedResult(result),
+ result,
'Content',
mod.Content,
props,
diff --git a/packages/astro/src/core/app/common.ts b/packages/astro/src/core/app/common.ts
index 4b1432176..6fd13d9b9 100644
--- a/packages/astro/src/core/app/common.ts
+++ b/packages/astro/src/core/app/common.ts
@@ -14,12 +14,12 @@ export function deserializeManifest(serializedManifest: SerializedSSRManifest):
}
const assets = new Set<string>(serializedManifest.assets);
- const propagation = new Map(serializedManifest.propagation);
+ const componentMetadata = new Map(serializedManifest.componentMetadata);
return {
...serializedManifest,
assets,
- propagation,
+ componentMetadata,
routes,
};
}
diff --git a/packages/astro/src/core/app/index.ts b/packages/astro/src/core/app/index.ts
index ca66d87e8..e43e9a8ff 100644
--- a/packages/astro/src/core/app/index.ts
+++ b/packages/astro/src/core/app/index.ts
@@ -193,7 +193,7 @@ export class App {
request,
origin: url.origin,
pathname,
- propagation: this.#manifest.propagation,
+ componentMetadata: this.#manifest.componentMetadata,
scripts,
links,
route: routeData,
diff --git a/packages/astro/src/core/app/types.ts b/packages/astro/src/core/app/types.ts
index cf85d0387..46cb34e80 100644
--- a/packages/astro/src/core/app/types.ts
+++ b/packages/astro/src/core/app/types.ts
@@ -1,7 +1,7 @@
import type { MarkdownRenderingOptions } from '@astrojs/markdown-remark';
import type {
ComponentInstance,
- PropagationHint,
+ SSRComponentMetadata,
RouteData,
SerializedRouteData,
SSRLoadedRenderer,
@@ -36,13 +36,13 @@ export interface SSRManifest {
renderers: SSRLoadedRenderer[];
entryModules: Record<string, string>;
assets: Set<string>;
- propagation: SSRResult['propagation'];
+ componentMetadata: SSRResult['componentMetadata'];
}
-export type SerializedSSRManifest = Omit<SSRManifest, 'routes' | 'assets' | 'propagation'> & {
+export type SerializedSSRManifest = Omit<SSRManifest, 'routes' | 'assets' | 'componentMetadata'> & {
routes: SerializedRouteInfo[];
assets: string[];
- propagation: readonly [string, PropagationHint][];
+ componentMetadata: [string, SSRComponentMetadata][];
};
export type AdapterCreateExports<T = any> = (
diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts
index 3cd2b6a64..52e4204a9 100644
--- a/packages/astro/src/core/build/generate.ts
+++ b/packages/astro/src/core/build/generate.ts
@@ -405,7 +405,7 @@ async function generatePath(
origin,
pathname,
request: createRequest({ url, headers: new Headers(), logging, ssr }),
- propagation: internals.propagation,
+ componentMetadata: internals.componentMetadata,
scripts,
links,
route: pageData.route,
diff --git a/packages/astro/src/core/build/graph.ts b/packages/astro/src/core/build/graph.ts
index 6d7335c09..6d404b572 100644
--- a/packages/astro/src/core/build/graph.ts
+++ b/packages/astro/src/core/build/graph.ts
@@ -1,4 +1,5 @@
import type { GetModuleInfo, ModuleInfo } from 'rollup';
+import type { ViteDevServer } from 'vite';
import { resolvedPagesVirtualModuleId } from '../app/index.js';
diff --git a/packages/astro/src/core/build/internal.ts b/packages/astro/src/core/build/internal.ts
index 693180d37..d4372c0a2 100644
--- a/packages/astro/src/core/build/internal.ts
+++ b/packages/astro/src/core/build/internal.ts
@@ -77,7 +77,7 @@ export interface BuildInternals {
staticFiles: Set<string>;
// The SSR entry chunk. Kept in internals to share between ssr/client build steps
ssrEntryChunk?: OutputChunk;
- propagation: SSRResult['propagation'];
+ componentMetadata: SSRResult['componentMetadata'];
}
/**
@@ -107,7 +107,7 @@ export function createBuildInternals(): BuildInternals {
discoveredClientOnlyComponents: new Map(),
discoveredScripts: new Set(),
staticFiles: new Set(),
- propagation: new Map(),
+ componentMetadata: new Map(),
};
}
diff --git a/packages/astro/src/core/build/plugins/index.ts b/packages/astro/src/core/build/plugins/index.ts
index bb7af6238..982a90ce3 100644
--- a/packages/astro/src/core/build/plugins/index.ts
+++ b/packages/astro/src/core/build/plugins/index.ts
@@ -1,5 +1,5 @@
import { astroConfigBuildPlugin } from '../../../content/vite-plugin-content-assets.js';
-import { astroHeadPropagationBuildPlugin } from '../../../vite-plugin-head-propagation/index.js';
+import { astroHeadBuildPlugin } from '../../../vite-plugin-head/index.js';
import type { AstroBuildPluginContainer } from '../plugin';
import { pluginAliasResolve } from './plugin-alias-resolve.js';
import { pluginAnalyzer } from './plugin-analyzer.js';
@@ -18,7 +18,7 @@ export function registerAllPlugins({ internals, options, register }: AstroBuildP
register(pluginInternals(internals));
register(pluginPages(options, internals));
register(pluginCSS(options, internals));
- register(astroHeadPropagationBuildPlugin(options, internals));
+ register(astroHeadBuildPlugin(options, internals));
register(pluginPrerender(options, internals));
register(astroConfigBuildPlugin(options, internals));
register(pluginHoistedScripts(options, internals));
diff --git a/packages/astro/src/core/build/plugins/plugin-ssr.ts b/packages/astro/src/core/build/plugins/plugin-ssr.ts
index c8883a438..78a1217e0 100644
--- a/packages/astro/src/core/build/plugins/plugin-ssr.ts
+++ b/packages/astro/src/core/build/plugins/plugin-ssr.ts
@@ -209,7 +209,7 @@ function buildManifest(
base: settings.config.base,
markdown: settings.config.markdown,
pageMap: null as any,
- propagation: Array.from(internals.propagation),
+ componentMetadata: Array.from(internals.componentMetadata),
renderers: [],
entryModules,
assets: staticFiles.map((s) => settings.config.base + s),
diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts
index 32717ff7c..1879daac8 100644
--- a/packages/astro/src/core/create-vite.ts
+++ b/packages/astro/src/core/create-vite.ts
@@ -16,7 +16,7 @@ import { vitePluginAstroServer } from '../vite-plugin-astro-server/index.js';
import astroVitePlugin from '../vite-plugin-astro/index.js';
import configAliasVitePlugin from '../vite-plugin-config-alias/index.js';
import envVitePlugin from '../vite-plugin-env/index.js';
-import astroHeadPropagationPlugin from '../vite-plugin-head-propagation/index.js';
+import astroHeadPlugin from '../vite-plugin-head/index.js';
import htmlVitePlugin from '../vite-plugin-html/index.js';
import { astroInjectEnvTsPlugin } from '../vite-plugin-inject-env-ts/index.js';
import astroIntegrationsContainerPlugin from '../vite-plugin-integrations-container/index.js';
@@ -121,7 +121,7 @@ export async function createVite(
astroPostprocessVitePlugin({ settings }),
astroIntegrationsContainerPlugin({ settings, logging }),
astroScriptsPageSSRPlugin({ settings }),
- astroHeadPropagationPlugin({ settings }),
+ astroHeadPlugin({ settings }),
astroScannerPlugin({ settings }),
astroInjectEnvTsPlugin({ settings, logging, fs }),
astroContentVirtualModPlugin({ settings }),
diff --git a/packages/astro/src/core/render/context.ts b/packages/astro/src/core/render/context.ts
index 6e453fea0..f6a82e9ca 100644
--- a/packages/astro/src/core/render/context.ts
+++ b/packages/astro/src/core/render/context.ts
@@ -11,7 +11,7 @@ export interface RenderContext {
scripts?: Set<SSRElement>;
links?: Set<SSRElement>;
styles?: Set<SSRElement>;
- propagation?: SSRResult['propagation'];
+ componentMetadata?: SSRResult['componentMetadata'];
route?: RouteData;
status?: number;
}
diff --git a/packages/astro/src/core/render/core.ts b/packages/astro/src/core/render/core.ts
index 862ada7c8..f3b8e2866 100644
--- a/packages/astro/src/core/render/core.ts
+++ b/packages/astro/src/core/render/core.ts
@@ -98,7 +98,7 @@ export async function renderPage(mod: ComponentInstance, ctx: RenderContext, env
params,
props: pageProps,
pathname: ctx.pathname,
- propagation: ctx.propagation,
+ componentMetadata: ctx.componentMetadata,
resolve: env.resolve,
renderers: env.renderers,
request: ctx.request,
diff --git a/packages/astro/src/core/render/dev/head.ts b/packages/astro/src/core/render/dev/head.ts
deleted file mode 100644
index 56f110a20..000000000
--- a/packages/astro/src/core/render/dev/head.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import type { SSRResult } from '../../../@types/astro';
-
-import type { ModuleInfo, ModuleLoader } from '../../module-loader/index';
-
-import { getAstroMetadata } from '../../../vite-plugin-astro/index.js';
-import { viteID } from '../../util.js';
-import { crawlGraph } from './vite.js';
-
-export async function getPropagationMap(
- filePath: URL,
- loader: ModuleLoader
-): Promise<SSRResult['propagation']> {
- const map: SSRResult['propagation'] = new Map();
-
- const rootID = viteID(filePath);
- addInjection(map, loader.getModuleInfo(rootID));
- for await (const moduleNode of crawlGraph(loader, rootID, true)) {
- const id = moduleNode.id;
- if (id) {
- addInjection(map, loader.getModuleInfo(id));
- }
- }
-
- return map;
-}
-
-function addInjection(map: SSRResult['propagation'], modInfo: ModuleInfo | null) {
- if (modInfo) {
- const astro = getAstroMetadata(modInfo);
- if (astro && astro.propagation) {
- map.set(modInfo.id, astro.propagation);
- }
- }
-}
diff --git a/packages/astro/src/core/render/dev/index.ts b/packages/astro/src/core/render/dev/index.ts
index 92d374d3e..8d26f2e3f 100644
--- a/packages/astro/src/core/render/dev/index.ts
+++ b/packages/astro/src/core/render/dev/index.ts
@@ -15,7 +15,7 @@ import { createRenderContext, renderPage as coreRenderPage } from '../index.js';
import { filterFoundRenderers, loadRenderer } from '../renderer.js';
import { getStylesForURL } from './css.js';
import type { DevelopmentEnvironment } from './environment';
-import { getPropagationMap } from './head.js';
+import { getComponentMetadata } from './metadata.js';
import { getScriptsForURL } from './scripts.js';
export { createDevelopmentEnvironment } from './environment.js';
export type { DevelopmentEnvironment };
@@ -142,9 +142,9 @@ async function getScriptsAndStyles({ env, filePath }: GetScriptsAndStylesParams)
});
});
- const propagationMap = await getPropagationMap(filePath, env.loader);
+ const metadata = await getComponentMetadata(filePath, env.loader);
- return { scripts, styles, links, propagationMap };
+ return { scripts, styles, links, metadata };
}
export async function renderPage(options: SSROptions): Promise<Response> {
@@ -154,7 +154,7 @@ export async function renderPage(options: SSROptions): Promise<Response> {
// The new instances are passed through.
options.env.renderers = renderers;
- const { scripts, links, styles, propagationMap } = await getScriptsAndStyles({
+ const { scripts, links, styles, metadata } = await getScriptsAndStyles({
env: options.env,
filePath: options.filePath,
});
@@ -166,7 +166,7 @@ export async function renderPage(options: SSROptions): Promise<Response> {
scripts,
links,
styles,
- propagation: propagationMap,
+ componentMetadata: metadata,
route: options.route,
});
diff --git a/packages/astro/src/core/render/dev/metadata.ts b/packages/astro/src/core/render/dev/metadata.ts
new file mode 100644
index 000000000..a79c38186
--- /dev/null
+++ b/packages/astro/src/core/render/dev/metadata.ts
@@ -0,0 +1,47 @@
+import type { SSRResult, SSRComponentMetadata } from '../../../@types/astro';
+
+import type { ModuleInfo, ModuleLoader } from '../../module-loader/index';
+
+import { getAstroMetadata } from '../../../vite-plugin-astro/index.js';
+import { viteID } from '../../util.js';
+import { crawlGraph } from './vite.js';
+
+export async function getComponentMetadata(
+ filePath: URL,
+ loader: ModuleLoader
+): Promise<SSRResult['componentMetadata']> {
+ const map: SSRResult['componentMetadata'] = new Map();
+
+ const rootID = viteID(filePath);
+ addMetadata(map, loader.getModuleInfo(rootID));
+ for await (const moduleNode of crawlGraph(loader, rootID, true)) {
+ const id = moduleNode.id;
+ if (id) {
+ addMetadata(map, loader.getModuleInfo(id));
+ }
+ }
+
+ return map;
+}
+
+function addMetadata(
+ map: SSRResult['componentMetadata'],
+ modInfo: ModuleInfo | null
+) {
+ if (modInfo) {
+ const astro = getAstroMetadata(modInfo);
+ if(astro) {
+ let metadata: SSRComponentMetadata = {
+ containsHead: false,
+ propagation: 'none'
+ };
+ if(astro.propagation) {
+ metadata.propagation = astro.propagation;
+ }
+ if(astro.containsHead) {
+ metadata.containsHead = astro.containsHead;
+ }
+ map.set(modInfo.id, metadata);
+ }
+ }
+}
diff --git a/packages/astro/src/core/render/result.ts b/packages/astro/src/core/render/result.ts
index 523a0776a..651ccc74c 100644
--- a/packages/astro/src/core/render/result.ts
+++ b/packages/astro/src/core/render/result.ts
@@ -10,9 +10,7 @@ import type {
SSRResult,
} from '../../@types/astro';
import {
- createScopedResult,
renderSlot,
- ScopeFlags,
stringifyChunk,
type ComponentSlots,
} from '../../runtime/server/index.js';
@@ -48,7 +46,7 @@ export interface CreateResultArgs {
links?: Set<SSRElement>;
scripts?: Set<SSRElement>;
styles?: Set<SSRElement>;
- propagation?: SSRResult['propagation'];
+ componentMetadata?: SSRResult['componentMetadata'];
request: Request;
status: number;
}
@@ -95,7 +93,7 @@ class Slots {
public async render(name: string, args: any[] = []) {
if (!this.#slots || !this.has(name)) return;
- const scoped = createScopedResult(this.#result, ScopeFlags.RenderSlot);
+ const result = this.#result;
if (!Array.isArray(args)) {
warn(
this.#loggingOpts,
@@ -104,24 +102,24 @@ class Slots {
);
} else if (args.length > 0) {
const slotValue = this.#slots[name];
- const component = typeof slotValue === 'function' ? await slotValue(scoped) : await slotValue;
+ const component = typeof slotValue === 'function' ? await slotValue(result) : await slotValue;
// Astro
const expression = getFunctionExpression(component);
if (expression) {
const slot = () => expression(...args);
- return await renderSlot(scoped, slot).then((res) => (res != null ? String(res) : res));
+ return await renderSlot(result, slot).then((res) => (res != null ? String(res) : res));
}
// JSX
if (typeof component === 'function') {
- return await renderJSX(scoped, (component as any)(...args)).then((res) =>
+ return await renderJSX(result, (component as any)(...args)).then((res) =>
res != null ? String(res) : res
);
}
}
- const content = await renderSlot(scoped, this.#slots[name]);
- const outHTML = stringifyChunk(scoped, content);
+ const content = await renderSlot(result, this.#slots[name]);
+ const outHTML = stringifyChunk(result, content);
return outHTML;
}
@@ -150,6 +148,7 @@ export function createResult(args: CreateResultArgs): SSRResult {
// Astro.cookies is defined lazily to avoid the cost on pages that do not use it.
let cookies: AstroCookies | undefined = undefined;
+ let componentMetadata = args.componentMetadata ?? new Map();
// Create the result object that will be passed into the render function.
// This object starts here as an empty shell (not yet the result) but then
@@ -158,7 +157,7 @@ export function createResult(args: CreateResultArgs): SSRResult {
styles: args.styles ?? new Set<SSRElement>(),
scripts: args.scripts ?? new Set<SSRElement>(),
links: args.links ?? new Set<SSRElement>(),
- propagation: args.propagation ?? new Map(),
+ componentMetadata,
propagators: new Map(),
extraHead: [],
scope: 0,
@@ -248,6 +247,7 @@ export function createResult(args: CreateResultArgs): SSRResult {
hasHydrationScript: false,
hasRenderedHead: false,
hasDirectives: new Set(),
+ headInTree: false,
},
response,
};
diff --git a/packages/astro/src/jsx/babel.ts b/packages/astro/src/jsx/babel.ts
index 88b01ad8d..861914336 100644
--- a/packages/astro/src/jsx/babel.ts
+++ b/packages/astro/src/jsx/babel.ts
@@ -145,6 +145,7 @@ export default function astroJSX(): PluginObj {
clientOnlyComponents: [],
hydratedComponents: [],
scripts: [],
+ containsHead: false,
propagation: 'none',
pageOptions: {},
};
diff --git a/packages/astro/src/runtime/server/index.ts b/packages/astro/src/runtime/server/index.ts
index cae48fd41..ebc234ac2 100644
--- a/packages/astro/src/runtime/server/index.ts
+++ b/packages/astro/src/runtime/server/index.ts
@@ -5,13 +5,10 @@ export { escapeHTML, HTMLBytes, HTMLString, markHTMLString, unescapeHTML } from
export { renderJSX } from './jsx.js';
export {
addAttribute,
- addScopeFlag,
createHeadAndContent,
- createScopedResult,
defineScriptVars,
Fragment,
maybeRenderHead,
- removeScopeFlag,
renderAstroTemplateResult as renderAstroComponent,
renderComponent,
renderComponentToIterable,
@@ -26,7 +23,6 @@ export {
renderTemplate,
renderToString,
renderUniqueStylesheet,
- ScopeFlags,
stringifyChunk,
voidElementNames,
} from './render/index.js';
diff --git a/packages/astro/src/runtime/server/jsx.ts b/packages/astro/src/runtime/server/jsx.ts
index 0823b54d7..c8da19eaf 100644
--- a/packages/astro/src/runtime/server/jsx.ts
+++ b/packages/astro/src/runtime/server/jsx.ts
@@ -12,7 +12,6 @@ import {
} from './index.js';
import { HTMLParts } from './render/common.js';
import type { ComponentIterable } from './render/component';
-import { createScopedResult, ScopeFlags } from './render/scope.js';
const ClientOnlyPlaceholder = 'astro-client-only';
@@ -95,8 +94,7 @@ Did you forget to import the component or is it possible there is a typo?`);
props[key] = value;
}
}
- const scoped = createScopedResult(result, ScopeFlags.JSX);
- const html = markHTMLString(await renderToString(scoped, vnode.type as any, props, slots));
+ const html = markHTMLString(await renderToString(result, vnode.type as any, props, slots));
return html;
}
case !vnode.type && (vnode.type as any) !== 0:
diff --git a/packages/astro/src/runtime/server/render/astro/factory.ts b/packages/astro/src/runtime/server/render/astro/factory.ts
index eccf7b4e7..045329c0f 100644
--- a/packages/astro/src/runtime/server/render/astro/factory.ts
+++ b/packages/astro/src/runtime/server/render/astro/factory.ts
@@ -3,7 +3,6 @@ import type { HeadAndContent } from './head-and-content';
import type { RenderTemplateResult } from './render-template';
import { HTMLParts } from '../common.js';
-import { createScopedResult, ScopeFlags } from '../scope.js';
import { isHeadAndContent } from './head-and-content.js';
import { renderAstroTemplateResult } from './render-template.js';
@@ -28,8 +27,7 @@ export async function renderToString(
props: any,
children: any
): Promise<string> {
- const scoped = createScopedResult(result, ScopeFlags.Astro);
- const factoryResult = await componentFactory(scoped, props, children);
+ const factoryResult = await componentFactory(result, props, children);
if (factoryResult instanceof Response) {
const response = factoryResult;
@@ -50,8 +48,8 @@ export function isAPropagatingComponent(
factory: AstroComponentFactory
): boolean {
let hint: PropagationHint = factory.propagation || 'none';
- if (factory.moduleId && result.propagation.has(factory.moduleId) && hint === 'none') {
- hint = result.propagation.get(factory.moduleId)!;
+ if(factory.moduleId && result.componentMetadata.has(factory.moduleId) && hint === 'none') {
+ hint = result.componentMetadata.get(factory.moduleId)!.propagation;
}
return hint === 'in-tree' || hint === 'self';
}
diff --git a/packages/astro/src/runtime/server/render/astro/instance.ts b/packages/astro/src/runtime/server/render/astro/instance.ts
index 9a0839e51..47ce7f495 100644
--- a/packages/astro/src/runtime/server/render/astro/instance.ts
+++ b/packages/astro/src/runtime/server/render/astro/instance.ts
@@ -5,7 +5,6 @@ import type { AstroComponentFactory, AstroFactoryReturnValue } from './factory.j
import { HydrationDirectiveProps } from '../../hydration.js';
import { isPromise } from '../../util.js';
import { renderChild } from '../any.js';
-import { createScopedResult, ScopeFlags } from '../scope.js';
import { isAPropagatingComponent } from './factory.js';
import { isHeadAndContent } from './head-and-content.js';
@@ -31,9 +30,8 @@ export class AstroComponentInstance {
this.props = props;
this.factory = factory;
this.slotValues = {};
- const scoped = createScopedResult(result, ScopeFlags.Slot);
for (const name in slots) {
- const value = slots[name](scoped);
+ const value = slots[name](result);
this.slotValues[name] = () => value;
}
}
diff --git a/packages/astro/src/runtime/server/render/common.ts b/packages/astro/src/runtime/server/render/common.ts
index 3c0f8d50d..6892a0f34 100644
--- a/packages/astro/src/runtime/server/render/common.ts
+++ b/packages/astro/src/runtime/server/render/common.ts
@@ -9,7 +9,6 @@ import {
type PrescriptType,
} from '../scripts.js';
import { renderAllHeadContent } from './head.js';
-import { hasScopeFlag, ScopeFlags } from './scope.js';
import { isSlotString, type SlotString } from './slot.js';
export const Fragment = Symbol.for('astro:fragment');
@@ -50,52 +49,9 @@ export function stringifyChunk(result: SSRResult, chunk: string | SlotString | R
return renderAllHeadContent(result);
}
case 'maybe-head': {
- if (result._metadata.hasRenderedHead) {
+ if (result._metadata.hasRenderedHead || result._metadata.headInTree) {
return '';
}
-
- const scope = instruction.scope;
- switch (scope) {
- // JSX with an Astro slot
- case ScopeFlags.JSX | ScopeFlags.Slot | ScopeFlags.Astro:
- case ScopeFlags.JSX | ScopeFlags.Astro | ScopeFlags.HeadBuffer:
- case ScopeFlags.JSX | ScopeFlags.Slot | ScopeFlags.Astro | ScopeFlags.HeadBuffer: {
- return '';
- }
-
- // Astro rendered within JSX, head will be injected by the page itself.
- case ScopeFlags.JSX | ScopeFlags.Astro: {
- if (hasScopeFlag(result, ScopeFlags.JSX)) {
- return '';
- }
- break;
- }
-
- // If the current scope is with Astro.slots.render()
- case ScopeFlags.Slot:
- case ScopeFlags.Slot | ScopeFlags.HeadBuffer: {
- if (hasScopeFlag(result, ScopeFlags.RenderSlot)) {
- return '';
- }
- break;
- }
-
- // Nested element inside of JSX during head buffering phase
- case ScopeFlags.HeadBuffer: {
- if (hasScopeFlag(result, ScopeFlags.JSX | ScopeFlags.HeadBuffer)) {
- return '';
- }
- break;
- }
-
- // Astro.slots.render() should never render head content.
- case ScopeFlags.RenderSlot | ScopeFlags.Astro:
- case ScopeFlags.RenderSlot | ScopeFlags.Astro | ScopeFlags.JSX:
- case ScopeFlags.RenderSlot | ScopeFlags.Astro | ScopeFlags.JSX | ScopeFlags.HeadBuffer: {
- return '';
- }
- }
-
return renderAllHeadContent(result);
}
}
diff --git a/packages/astro/src/runtime/server/render/index.ts b/packages/astro/src/runtime/server/render/index.ts
index 4f7e14c9d..33e3dfe8f 100644
--- a/packages/astro/src/runtime/server/render/index.ts
+++ b/packages/astro/src/runtime/server/render/index.ts
@@ -10,7 +10,6 @@ export { renderComponent, renderComponentToIterable } from './component.js';
export { renderHTMLElement } from './dom.js';
export { maybeRenderHead, renderHead } from './head.js';
export { renderPage } from './page.js';
-export { addScopeFlag, createScopedResult, removeScopeFlag, ScopeFlags } from './scope.js';
export { renderSlot, type ComponentSlots } from './slot.js';
export { renderScriptElement, renderStyleElement, renderUniqueStylesheet } from './tags.js';
export type { RenderInstruction } from './types';
diff --git a/packages/astro/src/runtime/server/render/page.ts b/packages/astro/src/runtime/server/render/page.ts
index 857cfdd6a..c703b79c5 100644
--- a/packages/astro/src/runtime/server/render/page.ts
+++ b/packages/astro/src/runtime/server/render/page.ts
@@ -15,7 +15,6 @@ import {
import { chunkToByteArray, encoder, HTMLParts } from './common.js';
import { renderComponent } from './component.js';
import { maybeRenderHead } from './head.js';
-import { createScopedResult, ScopeFlags } from './scope.js';
const needsHeadRenderingSymbol = Symbol.for('astro.needsHeadRendering');
@@ -56,13 +55,12 @@ async function iterableToHTMLBytes(
// to be propagated up.
async function bufferHeadContent(result: SSRResult) {
const iterator = result.propagators.values();
- const scoped = createScopedResult(result, ScopeFlags.HeadBuffer);
while (true) {
const { value, done } = iterator.next();
if (done) {
break;
}
- const returnValue = await value.init(scoped);
+ const returnValue = await value.init(result);
if (isHeadAndContent(returnValue)) {
result.extraHead.push(returnValue.head);
}
@@ -81,7 +79,16 @@ export async function renderPage(
const pageProps: Record<string, any> = { ...(props ?? {}), 'server:root': true };
let output: ComponentIterable;
+ let head = '';
try {
+ if (nonAstroPageNeedsHeadInjection(componentFactory)) {
+ const parts = new HTMLParts();
+ for await(const chunk of maybeRenderHead(result)) {
+ parts.append(chunk, result);
+ }
+ head = parts.toString();
+ }
+
const renderResult = await renderComponent(
result,
componentFactory.name,
@@ -106,11 +113,7 @@ export async function renderPage(
// Accumulate the HTML string and append the head if necessary.
const bytes = await iterableToHTMLBytes(result, output, async (parts) => {
- if (nonAstroPageNeedsHeadInjection(componentFactory)) {
- for await (let chunk of maybeRenderHead(result)) {
- parts.append(chunk, result);
- }
- }
+ parts.append(head, result);
});
return new Response(bytes, {
@@ -120,6 +123,9 @@ export async function renderPage(
]),
});
}
+ // Mark if this page component contains a <head> within its tree. If it does
+ // We avoid implicit head injection entirely.
+ result._metadata.headInTree = result.componentMetadata.get(componentFactory.moduleId!)?.containsHead ?? false;
const factoryReturnValue = await componentFactory(result, props, children);
const factoryIsHeadAndContent = isHeadAndContent(factoryReturnValue);
if (isRenderTemplateResult(factoryReturnValue) || factoryIsHeadAndContent) {
diff --git a/packages/astro/src/runtime/server/render/scope.ts b/packages/astro/src/runtime/server/render/scope.ts
deleted file mode 100644
index fce96c0e7..000000000
--- a/packages/astro/src/runtime/server/render/scope.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import type { SSRResult } from '../../../@types/astro';
-
-export const ScopeFlags = {
- Astro: 1 << 0, // 1
- JSX: 1 << 1, // 2
- Slot: 1 << 2, // 4
- HeadBuffer: 1 << 3, // 8
- RenderSlot: 1 << 4, // 16
-} as const;
-
-type ScopeFlagValues = (typeof ScopeFlags)[keyof typeof ScopeFlags];
-
-export function addScopeFlag(result: SSRResult, flag: ScopeFlagValues) {
- result.scope |= flag;
-}
-
-export function removeScopeFlag(result: SSRResult, flag: ScopeFlagValues) {
- result.scope &= ~flag;
-}
-
-export function hasScopeFlag(result: SSRResult, flag: ScopeFlagValues) {
- return (result.scope & flag) === flag;
-}
-
-export function createScopedResult(result: SSRResult, flag?: ScopeFlagValues): SSRResult {
- const scopedResult = Object.create(result, {
- scope: {
- writable: true,
- value: result.scope,
- },
- });
- if (flag != null) {
- addScopeFlag(scopedResult, flag);
- }
- return scopedResult;
-}
diff --git a/packages/astro/src/runtime/server/render/slot.ts b/packages/astro/src/runtime/server/render/slot.ts
index 5e21d1e07..cd6bc064b 100644
--- a/packages/astro/src/runtime/server/render/slot.ts
+++ b/packages/astro/src/runtime/server/render/slot.ts
@@ -4,7 +4,6 @@ import type { RenderInstruction } from './types.js';
import { HTMLString, markHTMLString } from '../escape.js';
import { renderChild } from './any.js';
-import { createScopedResult, ScopeFlags } from './scope.js';
type RenderTemplateResult = ReturnType<typeof renderTemplate>;
export type ComponentSlots = Record<string, ComponentSlotValue>;
@@ -32,8 +31,7 @@ export async function renderSlot(
fallback?: ComponentSlotValue | RenderTemplateResult
): Promise<string> {
if (slotted) {
- const scoped = createScopedResult(result, ScopeFlags.Slot);
- let iterator = renderChild(typeof slotted === 'function' ? slotted(scoped) : slotted);
+ let iterator = renderChild(typeof slotted === 'function' ? slotted(result) : slotted);
let content = '';
let instructions: null | RenderInstruction[] = null;
for await (const chunk of iterator) {
diff --git a/packages/astro/src/vite-plugin-astro/index.ts b/packages/astro/src/vite-plugin-astro/index.ts
index 750061c05..70c4da611 100644
--- a/packages/astro/src/vite-plugin-astro/index.ts
+++ b/packages/astro/src/vite-plugin-astro/index.ts
@@ -153,6 +153,7 @@ export default function astro({ settings, logging }: AstroPluginOptions): vite.P
clientOnlyComponents: transformResult.clientOnlyComponents,
hydratedComponents: transformResult.hydratedComponents,
scripts: transformResult.scripts,
+ containsHead: transformResult.containsHead,
propagation: 'none',
pageOptions: {},
};
diff --git a/packages/astro/src/vite-plugin-astro/types.ts b/packages/astro/src/vite-plugin-astro/types.ts
index ebb1c7d39..8b74d7f33 100644
--- a/packages/astro/src/vite-plugin-astro/types.ts
+++ b/packages/astro/src/vite-plugin-astro/types.ts
@@ -10,6 +10,7 @@ export interface PluginMetadata {
hydratedComponents: TransformResult['hydratedComponents'];
clientOnlyComponents: TransformResult['clientOnlyComponents'];
scripts: TransformResult['scripts'];
+ containsHead: TransformResult['containsHead'];
propagation: PropagationHint;
pageOptions: PageOptions;
};
diff --git a/packages/astro/src/vite-plugin-head-propagation/index.ts b/packages/astro/src/vite-plugin-head-propagation/index.ts
deleted file mode 100644
index 6dbd76169..000000000
--- a/packages/astro/src/vite-plugin-head-propagation/index.ts
+++ /dev/null
@@ -1,112 +0,0 @@
-import type { ModuleInfo } from 'rollup';
-import type { AstroSettings, SSRResult } from '../@types/astro';
-import type { BuildInternals } from '../core/build/internal.js';
-import type { AstroBuildPlugin } from '../core/build/plugin.js';
-import type { StaticBuildOptions } from '../core/build/types';
-
-import type * as vite from 'vite';
-import { walkParentInfos } from '../core/build/graph.js';
-import { getAstroMetadata } from '../vite-plugin-astro/index.js';
-
-const injectExp = /^\/\/\s*astro-head-inject/;
-/**
- * If any component is marked as doing head injection, walk up the tree
- * and mark parent Astro components as having head injection in the tree.
- * This is used at runtime to determine if we should wait for head content
- * to be populated before rendering the entire tree.
- */
-export default function configHeadPropagationVitePlugin({
- settings,
-}: {
- settings: AstroSettings;
-}): vite.Plugin {
- function addHeadInjectionInTree(
- graph: vite.ModuleGraph,
- id: string,
- getInfo: (id: string) => ModuleInfo | null,
- seen: Set<string> = new Set()
- ) {
- const mod = server.moduleGraph.getModuleById(id);
- for (const parent of mod?.importers || []) {
- if (parent.id) {
- if (seen.has(parent.id)) {
- continue;
- } else {
- seen.add(parent.id);
- }
- const info = getInfo(parent.id);
- if (info?.meta.astro) {
- const astroMetadata = getAstroMetadata(info);
- if (astroMetadata) {
- astroMetadata.propagation = 'in-tree';
- }
- }
- addHeadInjectionInTree(graph, parent.id, getInfo, seen);
- }
- }
- }
-
- let server: vite.ViteDevServer;
- return {
- name: 'astro:head-propagation',
- configureServer(_server) {
- server = _server;
- },
- transform(source, id) {
- if (!server) {
- return;
- }
-
- if (injectExp.test(source)) {
- addHeadInjectionInTree(server.moduleGraph, id, (child) => this.getModuleInfo(child));
- }
- },
- };
-}
-
-export function astroHeadPropagationBuildPlugin(
- options: StaticBuildOptions,
- internals: BuildInternals
-): AstroBuildPlugin {
- return {
- build: 'ssr',
- hooks: {
- 'build:before'() {
- const map: SSRResult['propagation'] = new Map();
- return {
- vitePlugin: {
- name: 'vite-plugin-head-propagation-build',
- generateBundle(_opts, bundle) {
- const appendPropagation = (info: ModuleInfo) => {
- const astroMetadata = getAstroMetadata(info);
- if (astroMetadata) {
- astroMetadata.propagation = 'in-tree';
- map.set(info.id, 'in-tree');
- }
- };
-
- for (const [bundleId, output] of Object.entries(bundle)) {
- if (output.type !== 'chunk') continue;
- for (const [id, mod] of Object.entries(output.modules)) {
- if (mod.code && injectExp.test(mod.code)) {
- for (const [info] of walkParentInfos(id, this)) {
- appendPropagation(info);
- }
-
- const info = this.getModuleInfo(id);
- if (info) {
- appendPropagation(info);
- }
- }
- }
- }
-
- // Save the map to internals so it can be passed into SSR and generation
- internals.propagation = map;
- },
- },
- };
- },
- },
- };
-}
diff --git a/packages/astro/src/vite-plugin-head/index.ts b/packages/astro/src/vite-plugin-head/index.ts
new file mode 100644
index 000000000..f0a3cd2b3
--- /dev/null
+++ b/packages/astro/src/vite-plugin-head/index.ts
@@ -0,0 +1,116 @@
+import type * as vite from 'vite';
+import type { ModuleInfo } from 'rollup';
+import type { AstroSettings, SSRResult, SSRComponentMetadata } from '../@types/astro';
+import type { AstroBuildPlugin } from '../core/build/plugin.js';
+import type { StaticBuildOptions } from '../core/build/types';
+import type { PluginMetadata } from '../vite-plugin-astro/types';
+
+import { getTopLevelPages, walkParentInfos } from '../core/build/graph.js';
+import type { BuildInternals } from '../core/build/internal.js';
+import { getAstroMetadata } from '../vite-plugin-astro/index.js';
+
+const injectExp = /^\/\/\s*astro-head-inject/;
+
+export default function configHeadVitePlugin({
+ settings,
+}: {
+ settings: AstroSettings;
+}): vite.Plugin {
+ let server: vite.ViteDevServer;
+
+ function propagateMetadata<
+ P extends keyof PluginMetadata['astro'],
+ V extends PluginMetadata['astro'][P]
+ >(this: { getModuleInfo(id: string): ModuleInfo | null }, id: string, prop: P, value: V, seen = new Set<string>()) {
+ if(seen.has(id)) return;
+ seen.add(id);
+ const mod = server.moduleGraph.getModuleById(id);
+ const info = this.getModuleInfo(id);
+ if (info?.meta.astro) {
+ const astroMetadata = getAstroMetadata(info)
+ if(astroMetadata) {
+ Reflect.set(astroMetadata, prop, value);
+ }
+ }
+
+ for (const parent of mod?.importers || []) {
+ if(parent.id) {
+ propagateMetadata.call(this, parent.id, prop, value, seen);
+ }
+ }
+ }
+
+
+ return {
+ name: 'astro:head-metadata',
+ configureServer(_server) {
+ server = _server;
+ },
+ transform(source, id) {
+ if (!server) {
+ return;
+ }
+
+ let info = this.getModuleInfo(id);
+ if(info && getAstroMetadata(info)?.containsHead) {
+ propagateMetadata.call(this, id, 'containsHead', true);
+ }
+
+ if (injectExp.test(source)) {
+ propagateMetadata.call(this, id, 'propagation', 'in-tree');
+ }
+ },
+ };
+}
+
+export function astroHeadBuildPlugin(
+ options: StaticBuildOptions,
+ internals: BuildInternals
+): AstroBuildPlugin {
+ return {
+ build: 'ssr',
+ hooks: {
+ 'build:before'() {
+ return {
+ vitePlugin: {
+ name: 'astro:head-metadata-build',
+ generateBundle(_opts, bundle) {
+ const map: SSRResult['componentMetadata'] = internals.componentMetadata;
+ function getOrCreateMetadata(id: string): SSRComponentMetadata {
+ if(map.has(id)) return map.get(id)!;
+ const metadata: SSRComponentMetadata = {
+ propagation: 'none',
+ containsHead: false
+ };
+ map.set(id, metadata);
+ return metadata;
+ }
+
+ for (const [,output] of Object.entries(bundle)) {
+ if (output.type !== 'chunk') continue;
+ for (const [id, mod] of Object.entries(output.modules)) {
+ const modinfo = this.getModuleInfo(id);
+
+ // <head> tag in the tree
+ if(modinfo && getAstroMetadata(modinfo)?.containsHead) {
+ for(const [pageInfo] of getTopLevelPages(id, this)) {
+ let metadata = getOrCreateMetadata(pageInfo.id);
+ metadata.containsHead = true;
+ }
+ }
+
+ // Head propagation (aka bubbling)
+ if (mod.code && injectExp.test(mod.code)) {
+ for (const [info] of walkParentInfos(id, this)) {
+ getOrCreateMetadata(info.id).propagation = 'in-tree';
+ }
+ }
+ }
+ }
+ },
+ },
+ };
+ },
+ },
+ };
+}
diff --git a/packages/astro/src/vite-plugin-markdown/index.ts b/packages/astro/src/vite-plugin-markdown/index.ts
index 7f99d9e1b..3e22c59ec 100644
--- a/packages/astro/src/vite-plugin-markdown/index.ts
+++ b/packages/astro/src/vite-plugin-markdown/index.ts
@@ -178,6 +178,7 @@ export default function markdown({ settings, logging }: AstroPluginOptions): Plu
clientOnlyComponents: [],
scripts: [],
propagation: 'none',
+ containsHead: false,
pageOptions: {},
} as PluginMetadata['astro'],
vite: {
diff --git a/packages/astro/test/units/dev/collections-renderentry.test.js b/packages/astro/test/units/dev/collections-renderentry.test.js
index 730ec194f..d44f65af4 100644
--- a/packages/astro/test/units/dev/collections-renderentry.test.js
+++ b/packages/astro/test/units/dev/collections-renderentry.test.js
@@ -41,8 +41,13 @@ describe('Content Collections - render()', () => {
const launchWeekEntry = blog.find(post => post.id === 'promo/launch-week.mdx');
const { Content } = await launchWeekEntry.render();
---
- <h1>testing</h1>
- <Content />
+ <html>
+ <head><title>Testing</title></head>
+ <body>
+ <h1>testing</h1>
+ <Content />
+ </body>
+ </html>
`,
},
root
@@ -250,8 +255,13 @@ description: Astro is launching this week!
---
import { Content } from '../launch-week.ts';
---
- <h1>Testing</h1>
- <Content />
+ <html>
+ <head><title>Testing</title></head>
+ <body>
+ <h1>Testing</h1>
+ <Content />
+ </body>
+ </html>
`,
},
root
diff --git a/packages/astro/test/units/dev/head-injection.test.js b/packages/astro/test/units/dev/head-injection.test.js
index e98c6108c..9d76e0a91 100644
--- a/packages/astro/test/units/dev/head-injection.test.js
+++ b/packages/astro/test/units/dev/head-injection.test.js
@@ -49,8 +49,13 @@ describe('head injection', () => {
import { renderEntry } from '../common/head.js';
const Head = renderEntry();
---
- <h1>testing</h1>
- <Head />
+ <html>
+ <head><title>Testing</title></head>
+ <body>
+ <h1>testing</h1>
+ <Head />
+ </body>
+ </html>
`,
},
root
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 441fe16a7..63f82fce0 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -424,7 +424,7 @@ importers:
packages/astro:
specifiers:
- '@astrojs/compiler': ^1.2.0
+ '@astrojs/compiler': ^1.3.0
'@astrojs/language-server': ^0.28.3
'@astrojs/markdown-remark': ^2.1.2
'@astrojs/telemetry': ^2.1.0
@@ -517,7 +517,7 @@ importers:
yargs-parser: ^21.0.1
zod: ^3.17.3
dependencies:
- '@astrojs/compiler': 1.2.0
+ '@astrojs/compiler': 1.3.0
'@astrojs/language-server': 0.28.3
'@astrojs/markdown-remark': link:../markdown/remark
'@astrojs/telemetry': link:../telemetry
@@ -4185,8 +4185,8 @@ packages:
/@astrojs/compiler/0.31.4:
resolution: {integrity: sha512-6bBFeDTtPOn4jZaiD3p0f05MEGQL9pw2Zbfj546oFETNmjJFWO3nzHz6/m+P53calknCvyVzZ5YhoBLIvzn5iw==}
- /@astrojs/compiler/1.2.0:
- resolution: {integrity: sha512-O8yPCyuq+PU9Fjht2tIW6WzSWiq8qDF1e8uAX2x+SOGFzKqOznp52UlDG2mSf+ekf0Z3R34sb64O7SgX+asTxg==}
+ /@astrojs/compiler/1.3.0:
+ resolution: {integrity: sha512-VxSj3gh/UTB/27rkRCT7SvyGjWtuxUO7Jf7QqDduch7j/gr/uA5P/Q5I/4zIIrZjy2yQAKyKLoox2QI2mM/BSA==}
dev: false
/@astrojs/language-server/0.28.3: