diff --git a/.changeset/hungry-lobsters-happen.md b/.changeset/hungry-lobsters-happen.md
new file mode 100644
index 000000000..00ef92584
--- /dev/null
+++ b/.changeset/hungry-lobsters-happen.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Add types for `import.meta.env.ASSETS_PREFIX` and `import.meta.env.SITE`
diff --git a/.changeset/odd-geese-shop.md b/.changeset/odd-geese-shop.md
new file mode 100644
index 000000000..2f238d726
--- /dev/null
+++ b/.changeset/odd-geese-shop.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+value of var can be undefined when using `define:vars`
diff --git a/examples/with-markdoc/src/content/config.ts b/examples/with-markdoc/src/content/config.ts
deleted file mode 100644
index 2eccab0a3..000000000
--- a/examples/with-markdoc/src/content/config.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { defineCollection, z } from 'astro:content';
-
-const docs = defineCollection({
- schema: z.object({
- title: z.string(),
- }),
-});
-
-export const collections = { docs };
diff --git a/packages/astro/CHANGELOG.md b/packages/astro/CHANGELOG.md
index 7c16de997..ced9a16df 100644
--- a/packages/astro/CHANGELOG.md
+++ b/packages/astro/CHANGELOG.md
@@ -1,5 +1,13 @@
# astro
+## 2.5.3
+
+### Patch Changes
+
+- [#6758](https://github.com/withastro/astro/pull/6758) [`f558a9e20`](https://github.com/withastro/astro/commit/f558a9e2056fc8f2e2d5814e74f199e398159fc4) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Improve style and script handling across content collection files. This addresses style bleed present in `@astrojs/markdoc` v0.1.0
+
+- [#7143](https://github.com/withastro/astro/pull/7143) [`b41963b77`](https://github.com/withastro/astro/commit/b41963b775149b802eea9e12c5fe266bb9a02944) Thanks [@johannesspohr](https://github.com/johannesspohr)! - Render 404 page content when a `Response` with status 404 is returned from a page
+
## 2.5.2
### Patch Changes
diff --git a/packages/astro/client-base.d.ts b/packages/astro/client-base.d.ts
index 5186468c8..37bae7b1c 100644
--- a/packages/astro/client-base.d.ts
+++ b/packages/astro/client-base.d.ts
@@ -1,5 +1,32 @@
///
+// eslint-disable-next-line @typescript-eslint/no-namespace
+declare namespace App {
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
+ export interface Locals {}
+}
+
+interface ImportMetaEnv {
+ /**
+ * The prefix for Astro-generated asset links if the build.assetsPrefix config option is set. This can be used to create asset links not handled by Astro.
+ */
+ readonly ASSETS_PREFIX: string;
+ /**
+ * This is set to the site option specified in your project’s Astro config file.
+ */
+ readonly SITE: string;
+}
+
+interface ImportMeta {
+ /**
+ * Astro and Vite expose environment variables through `import.meta.env`. For a complete list of the environment variables available, see the two references below.
+ *
+ * - [Astro reference](https://docs.astro.build/en/guides/environment-variables/#default-environment-variables)
+ * - [Vite reference](https://vitejs.dev/guide/env-and-mode.html#env-variables)
+ */
+ readonly env: ImportMetaEnv;
+}
+
declare module 'astro:assets' {
// Exporting things one by one is a bit cumbersome, not sure if there's a better way - erika, 2023-02-03
type AstroAssets = {
@@ -387,9 +414,3 @@ declare module '*?inline' {
const src: string;
export default src;
}
-
-// eslint-disable-next-line @typescript-eslint/no-namespace
-declare namespace App {
- // eslint-disable-next-line @typescript-eslint/no-empty-interface
- export interface Locals {}
-}
diff --git a/packages/astro/client-image.d.ts b/packages/astro/client-image.d.ts
index 5148014a4..ffcc1c63c 100644
--- a/packages/astro/client-image.d.ts
+++ b/packages/astro/client-image.d.ts
@@ -2,7 +2,7 @@
// TODO: Merge this file with `client-base.d.ts` in 3.0, when the `astro:assets` feature isn't under a flag anymore.
-type InputFormat = import('./dist/assets/types.js').InputFormat;
+type InputFormat = import('./dist/assets/types.js').ImageInputFormat;
interface ImageMetadata {
src: string;
diff --git a/packages/astro/client.d.ts b/packages/astro/client.d.ts
index 44da8654b..96f59d586 100644
--- a/packages/astro/client.d.ts
+++ b/packages/astro/client.d.ts
@@ -21,10 +21,6 @@ declare module '*.svg' {
const src: string;
export default src;
}
-declare module '*.ico' {
- const src: string;
- export default src;
-}
declare module '*.webp' {
const src: string;
export default src;
diff --git a/packages/astro/import-meta.d.ts b/packages/astro/import-meta.d.ts
index 2b05d0a65..23d951cf2 100644
--- a/packages/astro/import-meta.d.ts
+++ b/packages/astro/import-meta.d.ts
@@ -1,4 +1,4 @@
-// File vendored from Vite itself, as a workaround to https://github.com/vitejs/vite/pull/9827 until Vite 4 comes out
+// File vendored from Vite itself, as a workaround to https://github.com/vitejs/vite/issues/13309 until Vite 5 comes out
// This file is an augmentation to the built-in ImportMeta interface
// Thus cannot contain any top-level imports
@@ -6,13 +6,6 @@
/* eslint-disable @typescript-eslint/consistent-type-imports */
-// Duplicate of import('../src/node/importGlob').GlobOptions in order to
-// avoid breaking the production client type. Because this file is referenced
-// in vite/client.d.ts and in production src/node/importGlob.ts doesn't exist.
-interface GlobOptions {
- as?: string;
-}
-
interface ImportMeta {
url: string;
diff --git a/packages/astro/package.json b/packages/astro/package.json
index a29ee4eb8..5094ec543 100644
--- a/packages/astro/package.json
+++ b/packages/astro/package.json
@@ -1,6 +1,6 @@
{
"name": "astro",
- "version": "2.5.2",
+ "version": "2.5.3",
"description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.",
"type": "module",
"author": "withastro",
diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts
index 9d09d4260..cf9ced260 100644
--- a/packages/astro/src/@types/astro.ts
+++ b/packages/astro/src/@types/astro.ts
@@ -1280,6 +1280,12 @@ export interface ContentEntryType {
}
): rollup.LoadResult | Promise;
contentModuleTypes?: string;
+ /**
+ * Handle asset propagation for rendered content to avoid bleed.
+ * Ex. MDX content can import styles and scripts, so `handlePropagation` should be true.
+ * @default true
+ */
+ handlePropagation?: boolean;
}
type GetContentEntryInfoReturnType = {
diff --git a/packages/astro/src/content/consts.ts b/packages/astro/src/content/consts.ts
index bda154692..9bfb2e865 100644
--- a/packages/astro/src/content/consts.ts
+++ b/packages/astro/src/content/consts.ts
@@ -1,11 +1,18 @@
export const PROPAGATED_ASSET_FLAG = 'astroPropagatedAssets';
+export const CONTENT_RENDER_FLAG = 'astroRenderContent';
export const CONTENT_FLAG = 'astroContentCollectionEntry';
export const DATA_FLAG = 'astroDataCollectionEntry';
-export const CONTENT_FLAGS = [CONTENT_FLAG, DATA_FLAG, PROPAGATED_ASSET_FLAG] as const;
export const VIRTUAL_MODULE_ID = 'astro:content';
export const LINKS_PLACEHOLDER = '@@ASTRO-LINKS@@';
export const STYLES_PLACEHOLDER = '@@ASTRO-STYLES@@';
export const SCRIPTS_PLACEHOLDER = '@@ASTRO-SCRIPTS@@';
+export const CONTENT_FLAGS = [
+ CONTENT_FLAG,
+ CONTENT_RENDER_FLAG,
+ DATA_FLAG,
+ PROPAGATED_ASSET_FLAG,
+] as const;
+
export const CONTENT_TYPES_FILE = 'types.d.ts';
diff --git a/packages/astro/src/content/runtime.ts b/packages/astro/src/content/runtime.ts
index f38d9192a..ece6f7cb1 100644
--- a/packages/astro/src/content/runtime.ts
+++ b/packages/astro/src/content/runtime.ts
@@ -270,62 +270,82 @@ async function render({
const baseMod = await renderEntryImport();
if (baseMod == null || typeof baseMod !== 'object') throw UnexpectedRenderError;
- const { collectedStyles, collectedLinks, collectedScripts, getMod } = baseMod;
- if (typeof getMod !== 'function') throw UnexpectedRenderError;
- const mod = await getMod();
- if (mod == null || typeof mod !== 'object') throw UnexpectedRenderError;
+ if (
+ baseMod.default != null &&
+ typeof baseMod.default === 'object' &&
+ baseMod.default.__astroPropagation === true
+ ) {
+ const { collectedStyles, collectedLinks, collectedScripts, getMod } = baseMod.default;
+ if (typeof getMod !== 'function') throw UnexpectedRenderError;
+ const propagationMod = await getMod();
+ if (propagationMod == null || typeof propagationMod !== 'object') throw UnexpectedRenderError;
- const Content = createComponent({
- factory(result, baseProps, slots) {
- let styles = '',
- links = '',
- scripts = '';
- if (Array.isArray(collectedStyles)) {
- styles = collectedStyles
- .map((style: any) => {
- return renderUniqueStylesheet(result, {
- type: 'inline',
- content: style,
- });
- })
- .join('');
- }
- if (Array.isArray(collectedLinks)) {
- links = collectedLinks
- .map((link: any) => {
- return renderUniqueStylesheet(result, {
- type: 'external',
- src: prependForwardSlash(link),
- });
- })
- .join('');
- }
- if (Array.isArray(collectedScripts)) {
- scripts = collectedScripts.map((script: any) => renderScriptElement(script)).join('');
- }
+ const Content = createComponent({
+ factory(result, baseProps, slots) {
+ let styles = '',
+ links = '',
+ scripts = '';
+ if (Array.isArray(collectedStyles)) {
+ styles = collectedStyles
+ .map((style: any) => {
+ return renderUniqueStylesheet(result, {
+ type: 'inline',
+ content: style,
+ });
+ })
+ .join('');
+ }
+ if (Array.isArray(collectedLinks)) {
+ links = collectedLinks
+ .map((link: any) => {
+ return renderUniqueStylesheet(result, {
+ type: 'external',
+ src: prependForwardSlash(link),
+ });
+ })
+ .join('');
+ }
+ if (Array.isArray(collectedScripts)) {
+ scripts = collectedScripts.map((script: any) => renderScriptElement(script)).join('');
+ }
- let props = baseProps;
- // Auto-apply MDX components export
- if (id.endsWith('mdx')) {
- props = {
- components: mod.components ?? {},
- ...baseProps,
- };
- }
+ let props = baseProps;
+ // Auto-apply MDX components export
+ if (id.endsWith('mdx')) {
+ props = {
+ components: propagationMod.components ?? {},
+ ...baseProps,
+ };
+ }
- return createHeadAndContent(
- unescapeHTML(styles + links + scripts) as any,
- renderTemplate`${renderComponent(result, 'Content', mod.Content, props, slots)}`
- );
- },
- propagation: 'self',
- });
+ return createHeadAndContent(
+ unescapeHTML(styles + links + scripts) as any,
+ renderTemplate`${renderComponent(
+ result,
+ 'Content',
+ propagationMod.Content,
+ props,
+ slots
+ )}`
+ );
+ },
+ propagation: 'self',
+ });
- return {
- Content,
- headings: mod.getHeadings?.() ?? [],
- remarkPluginFrontmatter: mod.frontmatter ?? {},
- };
+ return {
+ Content,
+ headings: propagationMod.getHeadings?.() ?? [],
+ remarkPluginFrontmatter: propagationMod.frontmatter ?? {},
+ };
+ } else if (baseMod.Content && typeof baseMod.Content === 'function') {
+ return {
+ Content: baseMod.Content,
+ headings: baseMod.getHeadings?.() ?? [],
+ remarkPluginFrontmatter: baseMod.frontmatter ?? {},
+ };
+ } else {
+ throw UnexpectedRenderError;
+ }
}
export function createReference({ lookupMap }: { lookupMap: ContentLookupMap }) {
diff --git a/packages/astro/src/content/template/virtual-mod.mjs b/packages/astro/src/content/template/virtual-mod.mjs
index 491e594a6..e0ac7a564 100644
--- a/packages/astro/src/content/template/virtual-mod.mjs
+++ b/packages/astro/src/content/template/virtual-mod.mjs
@@ -46,7 +46,7 @@ function createGlobLookup(glob) {
}
const renderEntryGlob = import.meta.glob('@@RENDER_ENTRY_GLOB_PATH@@', {
- query: { astroPropagatedAssets: true },
+ query: { astroRenderContent: true },
});
const collectionToRenderEntryMap = createCollectionToGlobResultMap({
globResult: renderEntryGlob,
diff --git a/packages/astro/src/content/utils.ts b/packages/astro/src/content/utils.ts
index 831b25fb4..82a0f7992 100644
--- a/packages/astro/src/content/utils.ts
+++ b/packages/astro/src/content/utils.ts
@@ -14,6 +14,7 @@ import type {
} from '../@types/astro.js';
import { VALID_INPUT_FORMATS } from '../assets/consts.js';
import { AstroError, AstroErrorData } from '../core/errors/index.js';
+
import { formatYAMLException, isYAMLException } from '../core/errors/utils.js';
import { CONTENT_FLAGS, CONTENT_TYPES_FILE } from './consts.js';
import { errorMap } from './error-map.js';
@@ -328,7 +329,7 @@ export function parseFrontmatter(fileContents: string, filePath: string) {
*/
export const globalContentConfigObserver = contentObservable({ status: 'init' });
-export function hasContentFlag(viteId: string, flag: (typeof CONTENT_FLAGS)[number]) {
+export function hasContentFlag(viteId: string, flag: (typeof CONTENT_FLAGS)[number]): boolean {
const flags = new URLSearchParams(viteId.split('?')[1] ?? '');
return flags.has(flag);
}
diff --git a/packages/astro/src/content/vite-plugin-content-assets.ts b/packages/astro/src/content/vite-plugin-content-assets.ts
index 7e73f9f6b..bbd9749a8 100644
--- a/packages/astro/src/content/vite-plugin-content-assets.ts
+++ b/packages/astro/src/content/vite-plugin-content-assets.ts
@@ -1,4 +1,5 @@
-import { pathToFileURL } from 'url';
+import { extname } from 'node:path';
+import { pathToFileURL } from 'node:url';
import type { Plugin } from 'vite';
import type { AstroSettings } from '../@types/astro.js';
import { moduleIsTopLevelPage, walkParentInfos } from '../core/build/graph.js';
@@ -11,16 +12,13 @@ import { joinPaths, prependForwardSlash } from '../core/path.js';
import { getStylesForURL } from '../core/render/dev/css.js';
import { getScriptsForURL } from '../core/render/dev/scripts.js';
import {
+ CONTENT_RENDER_FLAG,
LINKS_PLACEHOLDER,
PROPAGATED_ASSET_FLAG,
SCRIPTS_PLACEHOLDER,
STYLES_PLACEHOLDER,
} from './consts.js';
-
-function isPropagatedAsset(viteId: string) {
- const flags = new URLSearchParams(viteId.split('?')[1]);
- return flags.has(PROPAGATED_ASSET_FLAG);
-}
+import { hasContentFlag } from './utils.js';
export function astroContentAssetPropagationPlugin({
mode,
@@ -32,13 +30,31 @@ export function astroContentAssetPropagationPlugin({
let devModuleLoader: ModuleLoader;
return {
name: 'astro:content-asset-propagation',
+ enforce: 'pre',
+ async resolveId(id, importer, opts) {
+ if (hasContentFlag(id, CONTENT_RENDER_FLAG)) {
+ const base = id.split('?')[0];
+
+ for (const { extensions, handlePropagation = true } of settings.contentEntryTypes) {
+ if (handlePropagation && extensions.includes(extname(base))) {
+ return this.resolve(`${base}?${PROPAGATED_ASSET_FLAG}`, importer, {
+ skipSelf: true,
+ ...opts,
+ });
+ }
+ }
+ // Resolve to the base id (no content flags)
+ // if Astro doesn't need to handle propagation.
+ return this.resolve(base, importer, { skipSelf: true, ...opts });
+ }
+ },
configureServer(server) {
if (mode === 'dev') {
devModuleLoader = createViteLoader(server);
}
},
async transform(_, id, options) {
- if (isPropagatedAsset(id)) {
+ if (hasContentFlag(id, PROPAGATED_ASSET_FLAG)) {
const basePath = id.split('?')[0];
let stringifiedLinks: string, stringifiedStyles: string, stringifiedScripts: string;
@@ -73,14 +89,17 @@ export function astroContentAssetPropagationPlugin({
}
const code = `
- export async function getMod() {
+ async function getMod() {
return import(${JSON.stringify(basePath)});
}
- export const collectedLinks = ${stringifiedLinks};
- export const collectedStyles = ${stringifiedStyles};
- export const collectedScripts = ${stringifiedScripts};
+ const collectedLinks = ${stringifiedLinks};
+ const collectedStyles = ${stringifiedStyles};
+ const collectedScripts = ${stringifiedScripts};
+ const defaultMod = { __astroPropagation: true, getMod, collectedLinks, collectedStyles, collectedScripts };
+ export default defaultMod;
`;
-
+ // ^ Use a default export for tools like Markdoc
+ // to catch the `__astroPropagation` identifier
return { code, map: { mappings: '' } };
}
},
diff --git a/packages/astro/src/core/app/index.ts b/packages/astro/src/core/app/index.ts
index 078d43e50..1e2dd1d24 100644
--- a/packages/astro/src/core/app/index.ts
+++ b/packages/astro/src/core/app/index.ts
@@ -144,19 +144,19 @@ export class App {
if (routeData.type === 'page') {
let response = await this.#renderPage(request, routeData, mod, defaultStatus);
- // If there was a 500 error, try sending the 500 page.
- if (response.status === 500) {
- const fiveHundredRouteData = matchRoute('/500', this.#manifestData);
- if (fiveHundredRouteData) {
- mod = await this.#manifest.pageMap.get(fiveHundredRouteData.component)!();
+ // If there was a known error code, try sending the according page (e.g. 404.astro / 500.astro).
+ if (response.status === 500 || response.status === 404) {
+ const errorPageData = matchRoute('/' + response.status, this.#manifestData);
+ if (errorPageData && errorPageData.route !== routeData.route) {
+ mod = await this.#manifest.pageMap.get(errorPageData.component)!();
try {
- let fiveHundredResponse = await this.#renderPage(
+ let errorResponse = await this.#renderPage(
request,
- fiveHundredRouteData,
+ errorPageData,
mod,
- 500
+ response.status
);
- return fiveHundredResponse;
+ return errorResponse;
} catch {}
}
}
diff --git a/packages/astro/src/core/render/dev/vite.ts b/packages/astro/src/core/render/dev/vite.ts
index 724ad172f..bb8209ce8 100644
--- a/packages/astro/src/core/render/dev/vite.ts
+++ b/packages/astro/src/core/render/dev/vite.ts
@@ -1,7 +1,6 @@
import type { ModuleLoader, ModuleNode } from '../../module-loader/index';
import npath from 'path';
-import { PROPAGATED_ASSET_FLAG } from '../../../content/consts.js';
import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from '../../constants.js';
import { unwrapId } from '../../util.js';
import { isCSSRequest } from './util.js';
@@ -10,9 +9,10 @@ import { isCSSRequest } from './util.js';
* List of file extensions signalling we can (and should) SSR ahead-of-time
* See usage below
*/
-const fileExtensionsToSSR = new Set(['.astro', ...SUPPORTED_MARKDOWN_FILE_EXTENSIONS]);
+const fileExtensionsToSSR = new Set(['.astro', '.mdoc', ...SUPPORTED_MARKDOWN_FILE_EXTENSIONS]);
const STRIP_QUERY_PARAMS_REGEX = /\?.*$/;
+const ASTRO_PROPAGATED_ASSET_REGEX = /\?astroPropagatedAssets/;
/** recursively crawl the module graph to get all style files imported by parent id */
export async function* crawlGraph(
@@ -23,7 +23,6 @@ export async function* crawlGraph(
): AsyncGenerator {
const id = unwrapId(_id);
const importedModules = new Set();
- if (new URL(id, 'file://').searchParams.has(PROPAGATED_ASSET_FLAG)) return;
const moduleEntriesForId = isRootFile
? // "getModulesByFile" pulls from a delayed module cache (fun implementation detail),
@@ -44,6 +43,7 @@ export async function* crawlGraph(
if (id === entry.id) {
scanned.add(id);
const entryIsStyle = isCSSRequest(id);
+
for (const importedModule of entry.importedModules) {
// some dynamically imported modules are *not* server rendered in time
// to only SSR modules that we can safely transform, we check against
@@ -60,15 +60,13 @@ export async function* crawlGraph(
if (entryIsStyle && !isCSSRequest(importedModulePathname)) {
continue;
}
+ const isFileTypeNeedingSSR = fileExtensionsToSSR.has(
+ npath.extname(importedModulePathname)
+ );
if (
- fileExtensionsToSSR.has(
- npath.extname(
- // Use `id` instead of `pathname` to preserve query params.
- // Should not SSR a module with an unexpected query param,
- // like "?astroPropagatedAssets"
- importedModule.id
- )
- )
+ isFileTypeNeedingSSR &&
+ // Should not SSR a module with ?astroPropagatedAssets
+ !ASTRO_PROPAGATED_ASSET_REGEX.test(importedModule.id)
) {
const mod = loader.getModuleById(importedModule.id);
if (!mod?.ssrModule) {
diff --git a/packages/astro/src/runtime/server/render/util.ts b/packages/astro/src/runtime/server/render/util.ts
index 5ddd97716..c2599401d 100644
--- a/packages/astro/src/runtime/server/render/util.ts
+++ b/packages/astro/src/runtime/server/render/util.ts
@@ -43,7 +43,7 @@ export function defineScriptVars(vars: Record) {
for (const [key, value] of Object.entries(vars)) {
// Use const instead of let as let global unsupported with Safari
// https://stackoverflow.com/questions/29194024/cant-use-let-keyword-in-safari-javascript
- output += `const ${toIdent(key)} = ${JSON.stringify(value).replace(
+ output += `const ${toIdent(key)} = ${JSON.stringify(value)?.replace(
/<\/script>/g,
'\\x3C/script>'
)};\n`;
diff --git a/packages/astro/src/vite-plugin-head/index.ts b/packages/astro/src/vite-plugin-head/index.ts
index fc19893e8..4f44aaf6e 100644
--- a/packages/astro/src/vite-plugin-head/index.ts
+++ b/packages/astro/src/vite-plugin-head/index.ts
@@ -9,7 +9,8 @@ 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/;
+// Detect this in comments, both in .astro components and in js/ts files.
+const injectExp = /(^\/\/|\/\/!)\s*astro-head-inject/;
export default function configHeadVitePlugin({
settings,
@@ -32,6 +33,7 @@ export default function configHeadVitePlugin({
seen.add(id);
const mod = server.moduleGraph.getModuleById(id);
const info = this.getModuleInfo(id);
+
if (info?.meta.astro) {
const astroMetadata = getAstroMetadata(info);
if (astroMetadata) {
diff --git a/packages/astro/src/vite-plugin-markdown/content-entry-type.ts b/packages/astro/src/vite-plugin-markdown/content-entry-type.ts
index cbf5cc957..a3489c940 100644
--- a/packages/astro/src/vite-plugin-markdown/content-entry-type.ts
+++ b/packages/astro/src/vite-plugin-markdown/content-entry-type.ts
@@ -13,6 +13,8 @@ export const markdownContentEntryType: ContentEntryType = {
rawData: parsed.matter,
};
},
+ // We need to handle propagation for Markdown because they support layouts which will bring in styles.
+ handlePropagation: true,
};
/**
@@ -30,6 +32,9 @@ export const mdxContentEntryType: ContentEntryType = {
rawData: parsed.matter,
};
},
+ // MDX can import scripts and styles,
+ // so wrap all MDX files with script / style propagation checks
+ handlePropagation: true,
contentModuleTypes: `declare module 'astro:content' {
interface Render {
'.mdx': Promise<{
diff --git a/packages/astro/test/astro-directives.test.js b/packages/astro/test/astro-directives.test.js
index c4098b4fe..9afbbbe97 100644
--- a/packages/astro/test/astro-directives.test.js
+++ b/packages/astro/test/astro-directives.test.js
@@ -14,7 +14,7 @@ describe('Directives', async () => {
const html = await fixture.readFile('/define-vars/index.html');
const $ = cheerio.load(html);
- expect($('script')).to.have.lengthOf(4);
+ expect($('script')).to.have.lengthOf(5);
let i = 0;
for (const script of $('script').toArray()) {
@@ -27,9 +27,12 @@ describe('Directives', async () => {
} else if (i < 3) {
// Convert invalid keys to valid identifiers
expect($(script).toString()).to.include('const dashCase = "bar"');
- } else {
+ } else if (i < 4) {
// Closing script tags in strings are escaped
expect($(script).toString()).to.include('const bar = "'
+let undef: undefined;
---
@@ -32,6 +33,9 @@ let bar = ''
+
diff --git a/packages/astro/test/fixtures/ssr-api-route-custom-404/src/pages/causes-404.astro b/packages/astro/test/fixtures/ssr-api-route-custom-404/src/pages/causes-404.astro
new file mode 100644
index 000000000..9331037be
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-api-route-custom-404/src/pages/causes-404.astro
@@ -0,0 +1,6 @@
+---
+return new Response(null, {
+ status: 404,
+ statusText: 'Not found',
+});
+---
diff --git a/packages/astro/test/ssr-404-500-pages.test.js b/packages/astro/test/ssr-404-500-pages.test.js
index 2216aa762..10e311ef9 100644
--- a/packages/astro/test/ssr-404-500-pages.test.js
+++ b/packages/astro/test/ssr-404-500-pages.test.js
@@ -62,6 +62,16 @@ describe('404 and 500 pages', () => {
expect($('h1').text()).to.equal('Something went horribly wrong!');
});
+ it('404 page returned when there is an 404 response returned from route', async () => {
+ const app = await fixture.loadTestAdapterApp();
+ const request = new Request('http://example.com/causes-404');
+ const response = await app.render(request);
+ expect(response.status).to.equal(404);
+ const html = await response.text();
+ const $ = cheerio.load(html);
+ expect($('h1').text()).to.equal('Something went horribly wrong!');
+ });
+
it('500 page returned when there is an error', async () => {
const app = await fixture.loadTestAdapterApp();
const request = new Request('http://example.com/causes-error');
diff --git a/packages/integrations/cloudflare/package.json b/packages/integrations/cloudflare/package.json
index 29b58521b..f37dd13d0 100644
--- a/packages/integrations/cloudflare/package.json
+++ b/packages/integrations/cloudflare/package.json
@@ -42,7 +42,7 @@
"tiny-glob": "^0.2.9"
},
"peerDependencies": {
- "astro": "workspace:^2.5.2"
+ "astro": "workspace:^2.5.3"
},
"devDependencies": {
"astro": "workspace:*",
diff --git a/packages/integrations/deno/package.json b/packages/integrations/deno/package.json
index 6d4aafd49..6b6fbffbb 100644
--- a/packages/integrations/deno/package.json
+++ b/packages/integrations/deno/package.json
@@ -36,7 +36,7 @@
"esbuild": "^0.15.18"
},
"peerDependencies": {
- "astro": "workspace:^2.5.2"
+ "astro": "workspace:^2.5.3"
},
"devDependencies": {
"astro": "workspace:*",
diff --git a/packages/integrations/image/package.json b/packages/integrations/image/package.json
index 4768f6591..b7dff730f 100644
--- a/packages/integrations/image/package.json
+++ b/packages/integrations/image/package.json
@@ -62,7 +62,7 @@
"vite": "^4.3.1"
},
"peerDependencies": {
- "astro": "workspace:^2.5.2",
+ "astro": "workspace:^2.5.3",
"sharp": ">=0.31.0"
},
"peerDependenciesMeta": {
diff --git a/packages/integrations/markdoc/CHANGELOG.md b/packages/integrations/markdoc/CHANGELOG.md
index f39c514c4..ad8b7fb57 100644
--- a/packages/integrations/markdoc/CHANGELOG.md
+++ b/packages/integrations/markdoc/CHANGELOG.md
@@ -1,5 +1,14 @@
# @astrojs/markdoc
+## 0.2.2
+
+### Patch Changes
+
+- [#6758](https://github.com/withastro/astro/pull/6758) [`f558a9e20`](https://github.com/withastro/astro/commit/f558a9e2056fc8f2e2d5814e74f199e398159fc4) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Improve style and script handling across content collection files. This addresses style bleed present in `@astrojs/markdoc` v0.1.0
+
+- Updated dependencies [[`f558a9e20`](https://github.com/withastro/astro/commit/f558a9e2056fc8f2e2d5814e74f199e398159fc4), [`b41963b77`](https://github.com/withastro/astro/commit/b41963b775149b802eea9e12c5fe266bb9a02944)]:
+ - astro@2.5.3
+
## 0.2.1
### Patch Changes
diff --git a/packages/integrations/markdoc/components/Renderer.astro b/packages/integrations/markdoc/components/Renderer.astro
index 5e2b6833a..6571e8c71 100644
--- a/packages/integrations/markdoc/components/Renderer.astro
+++ b/packages/integrations/markdoc/components/Renderer.astro
@@ -1,4 +1,5 @@
---
+//! astro-head-inject
import type { Config } from '@markdoc/markdoc';
import Markdoc from '@markdoc/markdoc';
import { ComponentNode, createTreeNode } from './TreeNode.js';
@@ -14,4 +15,4 @@ const ast = Markdoc.Ast.fromJSON(stringifiedAst);
const content = Markdoc.transform(ast, config);
---
-
+
diff --git a/packages/integrations/markdoc/components/TreeNode.ts b/packages/integrations/markdoc/components/TreeNode.ts
index a60597a0d..e491d1dc9 100644
--- a/packages/integrations/markdoc/components/TreeNode.ts
+++ b/packages/integrations/markdoc/components/TreeNode.ts
@@ -2,7 +2,16 @@ import type { AstroInstance } from 'astro';
import { Fragment } from 'astro/jsx-runtime';
import type { RenderableTreeNode } from '@markdoc/markdoc';
import Markdoc from '@markdoc/markdoc';
-import { createComponent, renderComponent, render } from 'astro/runtime/server/index.js';
+import {
+ createComponent,
+ renderComponent,
+ render,
+ renderScriptElement,
+ renderUniqueStylesheet,
+ createHeadAndContent,
+ unescapeHTML,
+ renderTemplate,
+} from 'astro/runtime/server/index.js';
export type TreeNode =
| {
@@ -12,6 +21,9 @@ export type TreeNode =
| {
type: 'component';
component: AstroInstance['default'];
+ collectedLinks?: string[];
+ collectedStyles?: string[];
+ collectedScripts?: string[];
props: Record;
children: TreeNode[];
}
@@ -32,20 +44,67 @@ export const ComponentNode = createComponent({
)}`,
};
if (treeNode.type === 'component') {
- return renderComponent(
- result,
- treeNode.component.name,
- treeNode.component,
- treeNode.props,
- slots
+ let styles = '',
+ links = '',
+ scripts = '';
+ if (Array.isArray(treeNode.collectedStyles)) {
+ styles = treeNode.collectedStyles
+ .map((style: any) =>
+ renderUniqueStylesheet({
+ type: 'inline',
+ content: style,
+ })
+ )
+ .join('');
+ }
+ if (Array.isArray(treeNode.collectedLinks)) {
+ links = treeNode.collectedLinks
+ .map((link: any) => {
+ return renderUniqueStylesheet(result, {
+ href: link[0] === '/' ? link : '/' + link,
+ });
+ })
+ .join('');
+ }
+ if (Array.isArray(treeNode.collectedScripts)) {
+ scripts = treeNode.collectedScripts
+ .map((script: any) => renderScriptElement(script))
+ .join('');
+ }
+
+ const head = unescapeHTML(styles + links + scripts);
+
+ let headAndContent = createHeadAndContent(
+ head,
+ renderTemplate`${renderComponent(
+ result,
+ treeNode.component.name,
+ treeNode.component,
+ treeNode.props,
+ slots
+ )}`
);
+
+ // Let the runtime know that this component is being used.
+ result.propagators.set(
+ {},
+ {
+ init() {
+ return headAndContent;
+ },
+ }
+ );
+
+ return headAndContent;
}
return renderComponent(result, treeNode.tag, treeNode.tag, treeNode.attributes, slots);
},
- propagation: 'none',
+ propagation: 'self',
});
-export function createTreeNode(node: RenderableTreeNode | RenderableTreeNode[]): TreeNode {
+export async function createTreeNode(
+ node: RenderableTreeNode | RenderableTreeNode[]
+): Promise {
if (typeof node === 'string' || typeof node === 'number') {
return { type: 'text', content: String(node) };
} else if (Array.isArray(node)) {
@@ -53,16 +112,17 @@ export function createTreeNode(node: RenderableTreeNode | RenderableTreeNode[]):
type: 'component',
component: Fragment,
props: {},
- children: node.map((child) => createTreeNode(child)),
+ children: await Promise.all(node.map((child) => createTreeNode(child))),
};
} else if (node === null || typeof node !== 'object' || !Markdoc.Tag.isTag(node)) {
return { type: 'text', content: '' };
}
+ const children = await Promise.all(node.children.map((child) => createTreeNode(child)));
+
if (typeof node.name === 'function') {
const component = node.name;
const props = node.attributes;
- const children = node.children.map((child) => createTreeNode(child));
return {
type: 'component',
@@ -70,12 +130,38 @@ export function createTreeNode(node: RenderableTreeNode | RenderableTreeNode[]):
props,
children,
};
+ } else if (isPropagatedAssetsModule(node.name)) {
+ const { collectedStyles, collectedLinks, collectedScripts } = node.name;
+ const component = (await node.name.getMod())?.default ?? Fragment;
+ const props = node.attributes;
+
+ return {
+ type: 'component',
+ component,
+ collectedStyles,
+ collectedLinks,
+ collectedScripts,
+ props,
+ children,
+ };
} else {
return {
type: 'element',
tag: node.name,
attributes: node.attributes,
- children: node.children.map((child) => createTreeNode(child)),
+ children,
};
}
}
+
+type PropagatedAssetsModule = {
+ __astroPropagation: true;
+ getMod: () => Promise;
+ collectedStyles: string[];
+ collectedLinks: string[];
+ collectedScripts: string[];
+};
+
+function isPropagatedAssetsModule(module: any): module is PropagatedAssetsModule {
+ return typeof module === 'object' && module != null && '__astroPropagation' in module;
+}
diff --git a/packages/integrations/markdoc/package.json b/packages/integrations/markdoc/package.json
index 4bdc84deb..dfe41caca 100644
--- a/packages/integrations/markdoc/package.json
+++ b/packages/integrations/markdoc/package.json
@@ -1,7 +1,7 @@
{
"name": "@astrojs/markdoc",
"description": "Add support for Markdoc pages in your Astro site",
- "version": "0.2.1",
+ "version": "0.2.2",
"type": "module",
"types": "./dist/index.d.ts",
"author": "withastro",
@@ -47,7 +47,7 @@
"zod": "^3.17.3"
},
"peerDependencies": {
- "astro": "workspace:^2.5.2"
+ "astro": "workspace:^2.5.3"
},
"devDependencies": {
"@astrojs/markdown-remark": "^2.2.1",
diff --git a/packages/integrations/markdoc/src/index.ts b/packages/integrations/markdoc/src/index.ts
index 627f08c77..ba8a0af84 100644
--- a/packages/integrations/markdoc/src/index.ts
+++ b/packages/integrations/markdoc/src/index.ts
@@ -32,7 +32,11 @@ export default function markdocIntegration(legacyConfig?: any): AstroIntegration
name: '@astrojs/markdoc',
hooks: {
'astro:config:setup': async (params) => {
- const { config: astroConfig, addContentEntryType } = params as SetupHookParams;
+ const {
+ config: astroConfig,
+ updateConfig,
+ addContentEntryType,
+ } = params as SetupHookParams;
markdocConfigResult = await loadMarkdocConfig(astroConfig);
const userMarkdocConfig = markdocConfigResult?.config ?? {};
@@ -49,6 +53,9 @@ export default function markdocIntegration(legacyConfig?: any): AstroIntegration
addContentEntryType({
extensions: ['.mdoc'],
getEntryInfo,
+ // Markdoc handles script / style propagation
+ // for Astro components internally
+ handlePropagation: false,
async getRenderModule({ entry, viteId }) {
const ast = Markdoc.parse(entry.body);
const pluginContext = this;
@@ -88,7 +95,10 @@ export default function markdocIntegration(legacyConfig?: any): AstroIntegration
});
}
- const res = `import { jsx as h } from 'astro/jsx-runtime';
+ const res = `import {
+ createComponent,
+ renderComponent,
+ } from 'astro/runtime/server/index.js';
import { Renderer } from '@astrojs/markdoc/components';
import { collectHeadings, setupConfig, Markdoc } from '@astrojs/markdoc/runtime';
import * as entry from ${JSON.stringify(viteId + '?astroContentCollectionEntry')};
@@ -119,14 +129,24 @@ export function getHeadings() {
const content = Markdoc.transform(ast, config);
return collectHeadings(Array.isArray(content) ? content : content.children);
}
-export async function Content (props) {
- const config = setupConfig({
- ...userConfig,
- variables: { ...userConfig.variables, ...props },
- }, entry);
- return h(Renderer, { config, stringifiedAst });
-}`;
+export const Content = createComponent({
+ factory(result, props) {
+ const config = setupConfig({
+ ...userConfig,
+ variables: { ...userConfig.variables, ...props },
+ }, entry);
+
+ return renderComponent(
+ result,
+ Renderer.name,
+ Renderer,
+ { stringifiedAst, config },
+ {}
+ );
+ },
+ propagation: 'self',
+});`;
return { code: res };
},
contentModuleTypes: await fs.promises.readFile(
@@ -134,6 +154,27 @@ export async function Content (props) {
'utf-8'
),
});
+
+ updateConfig({
+ vite: {
+ plugins: [
+ {
+ name: '@astrojs/markdoc:astro-propagated-assets',
+ enforce: 'pre',
+ // Astro component styles and scripts should only be injected
+ // When a given Markdoc file actually uses that component.
+ // Add the `astroPropagatedAssets` flag to inject only when rendered.
+ resolveId(this: rollup.TransformPluginContext, id: string, importer: string) {
+ if (importer === markdocConfigResult?.fileUrl.pathname && id.endsWith('.astro')) {
+ return this.resolve(id + '?astroPropagatedAssets', importer, {
+ skipSelf: true,
+ });
+ }
+ },
+ },
+ ],
+ },
+ });
},
'astro:server:setup': async ({ server }) => {
server.watcher.on('all', (event, entry) => {
diff --git a/packages/integrations/markdoc/src/nodes/heading.ts b/packages/integrations/markdoc/src/nodes/heading.ts
index 0210e9b90..cb50dd231 100644
--- a/packages/integrations/markdoc/src/nodes/heading.ts
+++ b/packages/integrations/markdoc/src/nodes/heading.ts
@@ -37,13 +37,14 @@ export const heading: Schema = {
const slug = getSlug(attributes, children, config.ctx.headingSlugger);
const render = config.nodes?.heading?.render ?? `h${level}`;
+
const tagProps =
// For components, pass down `level` as a prop,
// alongside `__collectHeading` for our `headings` collector.
// Avoid accidentally rendering `level` as an HTML attribute otherwise!
- typeof render === 'function'
- ? { ...attributes, id: slug, __collectHeading: true, level }
- : { ...attributes, id: slug };
+ typeof render === 'string'
+ ? { ...attributes, id: slug }
+ : { ...attributes, id: slug, __collectHeading: true, level };
return new Markdoc.Tag(render, tagProps, children);
},
diff --git a/packages/integrations/mdx/CHANGELOG.md b/packages/integrations/mdx/CHANGELOG.md
index 8f23d98ba..7f7ff251b 100644
--- a/packages/integrations/mdx/CHANGELOG.md
+++ b/packages/integrations/mdx/CHANGELOG.md
@@ -1,5 +1,11 @@
# @astrojs/mdx
+## 0.19.3
+
+### Patch Changes
+
+- [#6758](https://github.com/withastro/astro/pull/6758) [`f558a9e20`](https://github.com/withastro/astro/commit/f558a9e2056fc8f2e2d5814e74f199e398159fc4) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Improve style and script handling across content collection files. This addresses style bleed present in `@astrojs/markdoc` v0.1.0
+
## 0.19.2
### Patch Changes
diff --git a/packages/integrations/mdx/package.json b/packages/integrations/mdx/package.json
index 52627a5ca..a3308dc55 100644
--- a/packages/integrations/mdx/package.json
+++ b/packages/integrations/mdx/package.json
@@ -1,7 +1,7 @@
{
"name": "@astrojs/mdx",
"description": "Add support for MDX pages in your Astro site",
- "version": "0.19.2",
+ "version": "0.19.3",
"type": "module",
"types": "./dist/index.d.ts",
"author": "withastro",
diff --git a/packages/integrations/mdx/src/index.ts b/packages/integrations/mdx/src/index.ts
index 2ccf66266..1ef23e1af 100644
--- a/packages/integrations/mdx/src/index.ts
+++ b/packages/integrations/mdx/src/index.ts
@@ -55,6 +55,9 @@ export default function mdx(partialMdxOptions: Partial = {}): AstroI
new URL('../template/content-module-types.d.ts', import.meta.url),
'utf-8'
),
+ // MDX can import scripts and styles,
+ // so wrap all MDX files with script / style propagation checks
+ handlePropagation: true,
});
const extendMarkdownConfig =
diff --git a/packages/integrations/netlify/package.json b/packages/integrations/netlify/package.json
index 1c2fe497c..6c3cfbac2 100644
--- a/packages/integrations/netlify/package.json
+++ b/packages/integrations/netlify/package.json
@@ -42,7 +42,7 @@
"esbuild": "^0.15.18"
},
"peerDependencies": {
- "astro": "workspace:^2.5.2"
+ "astro": "workspace:^2.5.3"
},
"devDependencies": {
"@netlify/edge-functions": "^2.0.0",
diff --git a/packages/integrations/node/package.json b/packages/integrations/node/package.json
index b045a3601..b49d0b1a6 100644
--- a/packages/integrations/node/package.json
+++ b/packages/integrations/node/package.json
@@ -38,7 +38,7 @@
"server-destroy": "^1.0.1"
},
"peerDependencies": {
- "astro": "workspace:^2.5.2"
+ "astro": "workspace:^2.5.3"
},
"devDependencies": {
"@types/send": "^0.17.1",
diff --git a/packages/integrations/svelte/package.json b/packages/integrations/svelte/package.json
index 1d012a4ac..4926e6afe 100644
--- a/packages/integrations/svelte/package.json
+++ b/packages/integrations/svelte/package.json
@@ -48,7 +48,7 @@
"vite": "^4.3.1"
},
"peerDependencies": {
- "astro": "workspace:^2.5.2",
+ "astro": "workspace:^2.5.3",
"svelte": "^3.54.0"
},
"engines": {
diff --git a/packages/integrations/tailwind/package.json b/packages/integrations/tailwind/package.json
index 86e690a02..8fba3cf39 100644
--- a/packages/integrations/tailwind/package.json
+++ b/packages/integrations/tailwind/package.json
@@ -44,7 +44,7 @@
"vite": "^4.3.1"
},
"peerDependencies": {
- "astro": "workspace:^2.5.2",
+ "astro": "workspace:^2.5.3",
"tailwindcss": "^3.0.24"
},
"pnpm": {
diff --git a/packages/integrations/vercel/package.json b/packages/integrations/vercel/package.json
index 4e04c67a8..72bb5758f 100644
--- a/packages/integrations/vercel/package.json
+++ b/packages/integrations/vercel/package.json
@@ -59,7 +59,7 @@
"web-vitals": "^3.1.1"
},
"peerDependencies": {
- "astro": "workspace:^2.5.2"
+ "astro": "workspace:^2.5.3"
},
"devDependencies": {
"@types/set-cookie-parser": "^2.4.2",
diff --git a/packages/integrations/vue/package.json b/packages/integrations/vue/package.json
index e422ff82a..9855ccf4c 100644
--- a/packages/integrations/vue/package.json
+++ b/packages/integrations/vue/package.json
@@ -56,7 +56,7 @@
"vue": "^3.2.37"
},
"peerDependencies": {
- "astro": "workspace:^2.5.2",
+ "astro": "workspace:^2.5.3",
"vue": "^3.2.30"
},
"engines": {