aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Ben Holmes <hey@bholmes.dev> 2022-08-05 14:43:50 -0500
committerGravatar GitHub <noreply@github.com> 2022-08-05 15:43:50 -0400
commit471c6f784e21399676c8b2002665ffdf83a1c59e (patch)
treeb39134eb9b53b3a9ef9b987286061215a2af3531
parent838eb3e5cc523d20aa5fb6b136ed5f6701568b1d (diff)
downloadastro-471c6f784e21399676c8b2002665ffdf83a1c59e.tar.gz
astro-471c6f784e21399676c8b2002665ffdf83a1c59e.tar.zst
astro-471c6f784e21399676c8b2002665ffdf83a1c59e.zip
[markdown] Harder, better, faster, stronger `vite-plugin-markdown` (#4137)
* refactor: vite-plugin-md -> vite-plugin-md-legacy * wip: add vite-plugin-md * feat: always apply jsx renderer * fix: markHTMLString on VNode result * feat: apply new vite-plugin-markdown! * fix: add meta export to md * fix: remove needless $$metadata export * fix: toggle to legacy plugin on flag * fix: pass fileId to renderMarkdown * test: raw and compiled content on plain md * fix: escape vite env refs * refactor: astro-md -> legacy-astro-flavored-md, astro-md-mode -> astro-markdown * fix: import.meta.env refs with tests * fix: add pkg.json to clientAddress * fix: prefer JSX integration over Astro runtime * Revert "fix: prefer JSX integration over Astro runtime" This reverts commit 3e5fa49344be9c857393da9af095faab152e92e1. * fix: remove .mdx check on importSource * chore: changeset * chore: remove TODO * fix: add back getHeadings * fix: add pkg.json to astro-head fixture * fix: default to Astro renderer for MDX and MD * feat: add "headings" and "frontmatter" to md layouts * refactor: remove legacy flag conditionals from legacy plugin * fix: add back MDX warning when legacy is off * test: getHeadings() glob * fix: add error on "astro.headings" access * feat: update docs example astro.headings => headings * refactor: readFile as string w/ utf-8 * chore: remove astro metadata TODO * refactor: stringify HTML once * fix: add pkg.json to glob-pages-css
-rw-r--r--.changeset/hip-dancers-move.md6
-rw-r--r--examples/docs/src/components/PageContent/PageContent.astro7
-rw-r--r--examples/docs/src/components/RightSidebar/RightSidebar.astro4
-rw-r--r--packages/astro/package.json2
-rw-r--r--packages/astro/src/core/config.ts8
-rw-r--r--packages/astro/src/core/create-vite.ts5
-rw-r--r--packages/astro/src/vite-plugin-jsx/index.ts32
-rw-r--r--packages/astro/src/vite-plugin-markdown-legacy/README.md3
-rw-r--r--packages/astro/src/vite-plugin-markdown-legacy/index.ts260
-rw-r--r--packages/astro/src/vite-plugin-markdown/README.md2
-rw-r--r--packages/astro/src/vite-plugin-markdown/index.ts289
-rw-r--r--packages/astro/test/astro-markdown-md-mode.test.js51
-rw-r--r--packages/astro/test/astro-markdown.test.js239
-rw-r--r--packages/astro/test/fixtures/astro-head/package.json8
-rw-r--r--packages/astro/test/fixtures/astro-markdown-md-mode/src/pages/code-in-md.md7
-rw-r--r--packages/astro/test/fixtures/astro-markdown/astro.config.mjs6
-rw-r--r--packages/astro/test/fixtures/astro-markdown/package.json3
-rw-r--r--packages/astro/test/fixtures/astro-markdown/src/components/Counter.svelte (renamed from packages/astro/test/fixtures/astro-markdown-md-mode/src/components/Counter.svelte)0
-rw-r--r--packages/astro/test/fixtures/astro-markdown/src/layouts/Base.astro28
-rw-r--r--packages/astro/test/fixtures/astro-markdown/src/pages/basic.md3
-rw-r--r--packages/astro/test/fixtures/astro-markdown/src/pages/code-in-md.md9
-rw-r--r--packages/astro/test/fixtures/astro-markdown/src/pages/components.md (renamed from packages/astro/test/fixtures/astro-markdown-md-mode/src/pages/components.md)0
-rw-r--r--packages/astro/test/fixtures/astro-markdown/src/pages/headings-glob.json.js9
-rw-r--r--packages/astro/test/fixtures/astro-markdown/src/pages/raw-content.json.js2
-rw-r--r--packages/astro/test/fixtures/astro-markdown/src/pages/vite-env-vars-glob.json.js7
-rw-r--r--packages/astro/test/fixtures/astro-markdown/src/pages/vite-env-vars.md5
-rw-r--r--packages/astro/test/fixtures/astro-markdown/src/pages/with-layout.md8
-rw-r--r--packages/astro/test/fixtures/client-address/package.json8
-rw-r--r--packages/astro/test/fixtures/glob-pages-css/package.json8
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/astro.config.mjs (renamed from packages/astro/test/fixtures/astro-markdown-md-mode/astro.config.mjs)6
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/package.json (renamed from packages/astro/test/fixtures/astro-markdown-md-mode/package.json)3
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/components/Counter.jsx (renamed from packages/astro/test/fixtures/astro-markdown/src/components/Counter.jsx)0
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/components/Example.jsx (renamed from packages/astro/test/fixtures/astro-markdown/src/components/Example.jsx)0
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/components/Hello.jsx (renamed from packages/astro/test/fixtures/astro-markdown/src/components/Hello.jsx)0
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/components/SlotComponent.astro (renamed from packages/astro/test/fixtures/astro-markdown/src/components/SlotComponent.astro)0
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/components/SvelteButton.svelte (renamed from packages/astro/test/fixtures/astro-markdown/src/components/SvelteButton.svelte)0
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/components/TextBlock.jsx (renamed from packages/astro/test/fixtures/astro-markdown/src/components/TextBlock.jsx)0
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/components/index.js (renamed from packages/astro/test/fixtures/astro-markdown/src/components/index.js)0
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/content/code-element.md (renamed from packages/astro/test/fixtures/astro-markdown/src/content/code-element.md)0
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/imported-md/plain.md (renamed from packages/astro/test/fixtures/astro-markdown/src/imported-md/plain.md)0
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/imported-md/with-components.md (renamed from packages/astro/test/fixtures/astro-markdown/src/imported-md/with-components.md)0
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/layouts/content.astro (renamed from packages/astro/test/fixtures/astro-markdown/src/layouts/content.astro)0
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/layouts/layout-props.astro (renamed from packages/astro/test/fixtures/astro-markdown/src/layouts/layout-props.astro)0
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/children.md (renamed from packages/astro/test/fixtures/astro-markdown/src/pages/children.md)0
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/code-element.astro (renamed from packages/astro/test/fixtures/astro-markdown/src/pages/code-element.astro)0
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/code-in-md.md16
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/comment-with-js.md (renamed from packages/astro/test/fixtures/astro-markdown/src/pages/comment-with-js.md)0
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/comment.md (renamed from packages/astro/test/fixtures/astro-markdown/src/pages/comment.md)0
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/dash.md (renamed from packages/astro/test/fixtures/astro-markdown/src/pages/dash.md)0
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/empty-code.md (renamed from packages/astro/test/fixtures/astro-markdown/src/pages/empty-code.md)0
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/imported-md/with-components.astro (renamed from packages/astro/test/fixtures/astro-markdown/src/pages/imported-md/with-components.astro)0
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/jsx-expressions.md (renamed from packages/astro/test/fixtures/astro-markdown-md-mode/src/pages/jsx-expressions.md)0
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/layout-props.md (renamed from packages/astro/test/fixtures/astro-markdown/src/pages/layout-props.md)0
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/namespace.md (renamed from packages/astro/test/fixtures/astro-markdown/src/pages/namespace.md)0
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/raw-content.json.js10
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/script.md (renamed from packages/astro/test/fixtures/astro-markdown/src/pages/script.md)0
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/slots.md (renamed from packages/astro/test/fixtures/astro-markdown/src/pages/slots.md)0
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/slug.md (renamed from packages/astro/test/fixtures/astro-markdown/src/pages/slug.md)0
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/vite-env-vars.md35
-rw-r--r--packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/scripts/test.js (renamed from packages/astro/test/fixtures/astro-markdown/src/scripts/test.js)0
-rw-r--r--packages/astro/test/legacy-astro-flavored-markdown.test.js200
-rw-r--r--packages/markdown/remark/src/remark-escape.ts2
-rw-r--r--pnpm-lock.yaml38
63 files changed, 835 insertions, 494 deletions
diff --git a/.changeset/hip-dancers-move.md b/.changeset/hip-dancers-move.md
new file mode 100644
index 000000000..bcebddf5c
--- /dev/null
+++ b/.changeset/hip-dancers-move.md
@@ -0,0 +1,6 @@
+---
+'astro': minor
+'@astrojs/markdown-remark': patch
+---
+
+Speed up internal markdown builds with new vite-plugin markdown
diff --git a/examples/docs/src/components/PageContent/PageContent.astro b/examples/docs/src/components/PageContent/PageContent.astro
index e0d96b57c..9af4056c9 100644
--- a/examples/docs/src/components/PageContent/PageContent.astro
+++ b/examples/docs/src/components/PageContent/PageContent.astro
@@ -2,9 +2,8 @@
import MoreMenu from "../RightSidebar/MoreMenu.astro";
import TableOfContents from "../RightSidebar/TableOfContents";
-const { content, githubEditUrl } = Astro.props;
+const { content, headings, githubEditUrl } = Astro.props;
const title = content.title;
-const headings = content.astro.headings;
---
<article id="article" class="content">
@@ -29,9 +28,11 @@ const headings = content.astro.headings;
display: flex;
flex-direction: column;
}
- .content > section {
+
+ .content>section {
margin-bottom: 4rem;
}
+
.block {
display: block;
}
diff --git a/examples/docs/src/components/RightSidebar/RightSidebar.astro b/examples/docs/src/components/RightSidebar/RightSidebar.astro
index c009d2202..71ad5e1c2 100644
--- a/examples/docs/src/components/RightSidebar/RightSidebar.astro
+++ b/examples/docs/src/components/RightSidebar/RightSidebar.astro
@@ -1,8 +1,7 @@
---
import TableOfContents from "./TableOfContents";
import MoreMenu from "./MoreMenu.astro";
-const { content, githubEditUrl } = Astro.props;
-const headings = content.astro.headings;
+const { content, headings, githubEditUrl } = Astro.props;
---
<nav class="sidebar-nav" aria-labelledby="grid-right">
@@ -18,6 +17,7 @@ const headings = content.astro.headings;
position: sticky;
top: 0;
}
+
.sidebar-nav-inner {
height: 100%;
padding: 0;
diff --git a/packages/astro/package.json b/packages/astro/package.json
index 9a47eebee..22321bf46 100644
--- a/packages/astro/package.json
+++ b/packages/astro/package.json
@@ -48,6 +48,8 @@
"./vite-plugin-astro-postprocess/*": "./dist/vite-plugin-astro-postprocess/*",
"./vite-plugin-jsx/*": "./dist/vite-plugin-jsx/*",
"./vite-plugin-jsx": "./dist/vite-plugin-jsx/index.js",
+ "./vite-plugin-markdown-legacy": "./dist/vite-plugin-markdown-legacy/index.js",
+ "./vite-plugin-markdown-legacy/*": "./dist/vite-plugin-markdown-legacy/*",
"./vite-plugin-markdown": "./dist/vite-plugin-markdown/index.js",
"./vite-plugin-markdown/*": "./dist/vite-plugin-markdown/*",
"./dist/jsx/*": "./dist/jsx/*"
diff --git a/packages/astro/src/core/config.ts b/packages/astro/src/core/config.ts
index 182b6b35a..3329dfb77 100644
--- a/packages/astro/src/core/config.ts
+++ b/packages/astro/src/core/config.ts
@@ -17,6 +17,7 @@ import { z } from 'zod';
import { LogOptions } from './logger/core.js';
import { appendForwardSlash, prependForwardSlash, trimSlashes } from './path.js';
import { arraify, isObject } from './util.js';
+import jsxRenderer from '../jsx/renderer.js';
load.use([loadTypeScript]);
@@ -343,16 +344,11 @@ export async function validateConfig(
_ctx: {
pageExtensions: ['.astro', '.md', '.html'],
scripts: [],
- renderers: [],
+ renderers: [jsxRenderer],
injectedRoutes: [],
adapter: undefined,
},
};
- if (result.integrations.find((integration) => integration.name === '@astrojs/mdx')) {
- // Enable default JSX integration. It needs to come first, so unshift rather than push!
- const { default: jsxRenderer } = await import('../jsx/renderer.js');
- (result._ctx.renderers as any[]).unshift(jsxRenderer);
- }
// If successful, return the result as a verified AstroConfig object.
return result;
diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts
index d197fb409..37a31ed66 100644
--- a/packages/astro/src/core/create-vite.ts
+++ b/packages/astro/src/core/create-vite.ts
@@ -12,6 +12,7 @@ import envVitePlugin from '../vite-plugin-env/index.js';
import htmlVitePlugin from '../vite-plugin-html/index.js';
import astroIntegrationsContainerPlugin from '../vite-plugin-integrations-container/index.js';
import jsxVitePlugin from '../vite-plugin-jsx/index.js';
+import legacyMarkdownVitePlugin from '../vite-plugin-markdown-legacy/index.js';
import markdownVitePlugin from '../vite-plugin-markdown/index.js';
import astroScriptsPlugin from '../vite-plugin-scripts/index.js';
import astroScriptsPageSSRPlugin from '../vite-plugin-scripts/page-ssr.js';
@@ -76,7 +77,9 @@ export async function createVite(
// the build to run very slow as the filewatcher is triggered often.
mode !== 'build' && astroViteServerPlugin({ config: astroConfig, logging }),
envVitePlugin({ config: astroConfig }),
- markdownVitePlugin({ config: astroConfig, logging }),
+ astroConfig.legacy.astroFlavoredMarkdown
+ ? legacyMarkdownVitePlugin({ config: astroConfig, logging })
+ : markdownVitePlugin({ config: astroConfig, logging }),
htmlVitePlugin(),
jsxVitePlugin({ config: astroConfig, logging }),
astroPostprocessVitePlugin({ config: astroConfig }),
diff --git a/packages/astro/src/vite-plugin-jsx/index.ts b/packages/astro/src/vite-plugin-jsx/index.ts
index 56005413e..3a6f47e86 100644
--- a/packages/astro/src/vite-plugin-jsx/index.ts
+++ b/packages/astro/src/vite-plugin-jsx/index.ts
@@ -137,9 +137,31 @@ export default function jsx({ config, logging }: AstroPluginJSXOptions): Plugin
JSX_RENDERER_CACHE.set(config, jsxRenderers);
}
- // Attempt: Single JSX renderer
+ const astroRenderer = jsxRenderers.get('astro');
+
+ // Shortcut: only use Astro renderer for MD and MDX files
+ if ((id.includes('.mdx') || id.includes('.md')) && astroRenderer) {
+ const { code: jsxCode } = await esbuild.transform(code, {
+ loader: getEsbuildLoader(path.extname(id)) as esbuild.Loader,
+ jsx: 'preserve',
+ sourcefile: id,
+ sourcemap: 'inline',
+ });
+ return transformJSX({
+ code: jsxCode,
+ id,
+ renderer: astroRenderer,
+ mode,
+ ssr,
+ });
+ }
+
+ // Attempt: Single JSX integration
// If we only have one renderer, we can skip a bunch of work!
- if (jsxRenderers.size === 1) {
+ const nonAstroJsxRenderers = new Map(
+ [...jsxRenderers.entries()].filter(([key]) => key !== 'astro')
+ );
+ if (nonAstroJsxRenderers.size === 1) {
// downlevel any non-standard syntax, but preserve JSX
const { code: jsxCode } = await esbuild.transform(code, {
loader: getEsbuildLoader(path.extname(id)) as esbuild.Loader,
@@ -150,7 +172,7 @@ export default function jsx({ config, logging }: AstroPluginJSXOptions): Plugin
return transformJSX({
code: jsxCode,
id,
- renderer: [...jsxRenderers.values()][0],
+ renderer: [...nonAstroJsxRenderers.values()][0],
mode,
ssr,
});
@@ -196,10 +218,6 @@ export default function jsx({ config, logging }: AstroPluginJSXOptions): Plugin
}
}
- if (!importSource && jsxRenderers.has('astro') && id.includes('.mdx')) {
- importSource = 'astro';
- }
-
// if JSX renderer found, then use that
if (importSource) {
const jsxRenderer = jsxRenderers.get(importSource);
diff --git a/packages/astro/src/vite-plugin-markdown-legacy/README.md b/packages/astro/src/vite-plugin-markdown-legacy/README.md
new file mode 100644
index 000000000..ee7fd0e54
--- /dev/null
+++ b/packages/astro/src/vite-plugin-markdown-legacy/README.md
@@ -0,0 +1,3 @@
+# vite-plugin-markdown-legacy
+
+Adds Markdown support to Vite, both at the top level as well as within `.astro` files.
diff --git a/packages/astro/src/vite-plugin-markdown-legacy/index.ts b/packages/astro/src/vite-plugin-markdown-legacy/index.ts
new file mode 100644
index 000000000..5cb66caa5
--- /dev/null
+++ b/packages/astro/src/vite-plugin-markdown-legacy/index.ts
@@ -0,0 +1,260 @@
+import { renderMarkdown } from '@astrojs/markdown-remark';
+import ancestor from 'common-ancestor-path';
+import esbuild from 'esbuild';
+import fs from 'fs';
+import matter from 'gray-matter';
+import { fileURLToPath } from 'url';
+import type { Plugin } from 'vite';
+import type { AstroConfig } from '../@types/astro';
+import { pagesVirtualModuleId } from '../core/app/index.js';
+import { collectErrorMetadata } from '../core/errors.js';
+import type { LogOptions } from '../core/logger/core.js';
+import { cachedCompilation, CompileProps } from '../vite-plugin-astro/compile.js';
+import { getViteTransform, TransformHook } from '../vite-plugin-astro/styles.js';
+import type { PluginMetadata as AstroPluginMetadata } from '../vite-plugin-astro/types';
+import { getFileInfo } from '../vite-plugin-utils/index.js';
+
+interface AstroPluginOptions {
+ config: AstroConfig;
+ logging: LogOptions;
+}
+
+const MARKDOWN_IMPORT_FLAG = '?mdImport';
+const MARKDOWN_CONTENT_FLAG = '?content';
+
+function safeMatter(source: string, id: string) {
+ try {
+ return matter(source);
+ } catch (e) {
+ (e as any).id = id;
+ throw collectErrorMetadata(e);
+ }
+}
+
+// TODO: Clean up some of the shared logic between this Markdown plugin and the Astro plugin.
+// Both end up connecting a `load()` hook to the Astro compiler, and share some copy-paste
+// logic in how that is done.
+export default function markdown({ config, logging }: AstroPluginOptions): Plugin {
+ function normalizeFilename(filename: string) {
+ if (filename.startsWith('/@fs')) {
+ filename = filename.slice('/@fs'.length);
+ } else if (filename.startsWith('/') && !ancestor(filename, config.root.pathname)) {
+ filename = new URL('.' + filename, config.root).pathname;
+ }
+ return filename;
+ }
+
+ // Weird Vite behavior: Vite seems to use a fake "index.html" importer when you
+ // have `enforce: pre`. This can probably be removed once the vite issue is fixed.
+ // see: https://github.com/vitejs/vite/issues/5981
+ const fakeRootImporter = fileURLToPath(new URL('index.html', config.root));
+ function isRootImport(importer: string | undefined) {
+ if (!importer) {
+ return true;
+ }
+ if (importer === fakeRootImporter) {
+ return true;
+ }
+ if (importer === '\0' + pagesVirtualModuleId) {
+ return true;
+ }
+ return false;
+ }
+
+ let viteTransform: TransformHook;
+
+ return {
+ name: 'astro:markdown',
+ enforce: 'pre',
+ configResolved(_resolvedConfig) {
+ viteTransform = getViteTransform(_resolvedConfig);
+ },
+ async resolveId(id, importer, options) {
+ // Resolve any .md files with the `?content` cache buster. This should only come from
+ // an already-resolved JS module wrapper. Needed to prevent infinite loops in Vite.
+ // Unclear if this is expected or if cache busting is just working around a Vite bug.
+ if (id.endsWith(`.md${MARKDOWN_CONTENT_FLAG}`)) {
+ const resolvedId = await this.resolve(id, importer, { skipSelf: true, ...options });
+ return resolvedId?.id.replace(MARKDOWN_CONTENT_FLAG, '');
+ }
+ // If the markdown file is imported from another file via ESM, resolve a JS representation
+ // that defers the markdown -> HTML rendering until it is needed. This is especially useful
+ // when fetching and then filtering many markdown files, like with import.meta.glob() or Astro.glob().
+ // Otherwise, resolve directly to the actual component.
+ if (id.endsWith('.md') && !isRootImport(importer)) {
+ const resolvedId = await this.resolve(id, importer, { skipSelf: true, ...options });
+ if (resolvedId) {
+ return resolvedId.id + MARKDOWN_IMPORT_FLAG;
+ }
+ }
+ // In all other cases, we do nothing and rely on normal Vite resolution.
+ return undefined;
+ },
+ async load(id, opts) {
+ // A markdown file has been imported via ESM!
+ // Return the file's JS representation, including all Markdown
+ // frontmatter and a deferred `import() of the compiled markdown content.
+ if (id.endsWith(`.md${MARKDOWN_IMPORT_FLAG}`)) {
+ const { fileId, fileUrl } = getFileInfo(id, config);
+
+ const source = await fs.promises.readFile(fileId, 'utf8');
+ const { data: frontmatter, content: rawContent } = safeMatter(source, fileId);
+ return {
+ code: `
+ // Static
+ export const frontmatter = ${escapeViteEnvReferences(JSON.stringify(frontmatter))};
+ export const file = ${JSON.stringify(fileId)};
+ export const url = ${JSON.stringify(fileUrl)};
+ export function rawContent() {
+ return ${escapeViteEnvReferences(JSON.stringify(rawContent))};
+ }
+ export async function compiledContent() {
+ return load().then((m) => m.compiledContent());
+ }
+ export function $$loadMetadata() {
+ return load().then((m) => m.$$metadata);
+ }
+
+ // Deferred
+ export default async function load() {
+ return (await import(${JSON.stringify(fileId + MARKDOWN_CONTENT_FLAG)}));
+ }
+ export function Content(...args) {
+ return load().then((m) => m.default(...args));
+ }
+ Content.isAstroComponentFactory = true;
+ export function getHeadings() {
+ return load().then((m) => m.metadata.headings);
+ }
+ export function getHeaders() {
+ console.warn('getHeaders() have been deprecated. Use getHeadings() function instead.');
+ return load().then((m) => m.metadata.headings);
+ };`,
+ map: null,
+ };
+ }
+
+ // A markdown file is being rendered! This markdown file was either imported
+ // directly as a page in Vite, or it was a deferred render from a JS module.
+ // This returns the compiled markdown -> astro component that renders to HTML.
+ if (id.endsWith('.md')) {
+ const filename = normalizeFilename(id);
+ const source = await fs.promises.readFile(filename, 'utf8');
+ const renderOpts = config.markdown;
+
+ const fileUrl = new URL(`file://${filename}`);
+
+ // Extract special frontmatter keys
+ let { data: frontmatter, content: markdownContent } = safeMatter(source, filename);
+
+ // Turn HTML comments into JS comments while preventing nested `*/` sequences
+ // from ending the JS comment by injecting a zero-width space
+ // Inside code blocks, this is removed during renderMarkdown by the remark-escape plugin.
+ markdownContent = markdownContent.replace(
+ /<\s*!--([^-->]*)(.*?)-->/gs,
+ (whole) => `{/*${whole.replace(/\*\//g, '*\u200b/')}*/}`
+ );
+
+ let renderResult = await renderMarkdown(markdownContent, {
+ ...renderOpts,
+ fileURL: fileUrl,
+ isAstroFlavoredMd: true,
+ } as any);
+ let { code: astroResult, metadata } = renderResult;
+ const { layout = '', components = '', setup = '', ...content } = frontmatter;
+ content.astro = metadata;
+ content.url = getFileInfo(id, config).fileUrl;
+ content.file = filename;
+
+ const prelude = `---
+import Slugger from 'github-slugger';
+${layout ? `import Layout from '${layout}';` : ''}
+${components ? `import * from '${components}';` : ''}
+${setup}
+
+const slugger = new Slugger();
+function $$slug(value) {
+ return slugger.slug(value);
+}
+
+const $$content = ${JSON.stringify(content)};
+
+Object.defineProperty($$content.astro, 'headers', {
+ get() {
+ console.warn('[${JSON.stringify(id)}] content.astro.headers is now content.astro.headings.');
+ return this.headings;
+ }
+});
+---`;
+
+ const imports = `${layout ? `import Layout from '${layout}';` : ''}
+${setup}`.trim();
+
+ // If the user imported "Layout", wrap the content in a Layout
+ if (/\bLayout\b/.test(imports)) {
+ astroResult = `${prelude}\n<Layout content={$$content}>\n\n${astroResult}\n\n</Layout>`;
+ } else {
+ // Note: without a Layout, we need to inject `head` manually so `maybeRenderHead` runs
+ astroResult = `${prelude}\n<head></head>${astroResult}`;
+ }
+
+ // Transform from `.astro` to valid `.ts`
+ const compileProps: CompileProps = {
+ config,
+ filename,
+ moduleId: id,
+ source: astroResult,
+ ssr: Boolean(opts?.ssr),
+ viteTransform,
+ pluginContext: this,
+ };
+
+ let transformResult = await cachedCompilation(compileProps);
+ let { code: tsResult } = transformResult;
+
+ tsResult = `\nexport const metadata = ${JSON.stringify(metadata)};
+export const frontmatter = ${JSON.stringify(content)};
+export function rawContent() {
+ return ${JSON.stringify(markdownContent)};
+}
+export function compiledContent() {
+ return ${JSON.stringify(renderResult.metadata.html)};
+}
+${tsResult}`;
+
+ // Compile from `.ts` to `.js`
+ const { code } = await esbuild.transform(tsResult, {
+ loader: 'ts',
+ sourcemap: false,
+ sourcefile: id,
+ });
+
+ const astroMetadata: AstroPluginMetadata['astro'] = {
+ clientOnlyComponents: transformResult.clientOnlyComponents,
+ hydratedComponents: transformResult.hydratedComponents,
+ scripts: transformResult.scripts,
+ };
+
+ return {
+ code: escapeViteEnvReferences(code),
+ map: null,
+ meta: {
+ astro: astroMetadata,
+ vite: {
+ lang: 'ts',
+ },
+ },
+ };
+ }
+
+ return null;
+ },
+ };
+}
+
+// Converts the first dot in `import.meta.env` to its Unicode escape sequence,
+// which prevents Vite from replacing strings like `import.meta.env.SITE`
+// in our JS representation of loaded Markdown files
+function escapeViteEnvReferences(code: string) {
+ return code.replace(/import\.meta\.env/g, 'import\\u002Emeta.env');
+}
diff --git a/packages/astro/src/vite-plugin-markdown/README.md b/packages/astro/src/vite-plugin-markdown/README.md
index 217d21904..ee7fd0e54 100644
--- a/packages/astro/src/vite-plugin-markdown/README.md
+++ b/packages/astro/src/vite-plugin-markdown/README.md
@@ -1,3 +1,3 @@
-# vite-plugin-markdown
+# vite-plugin-markdown-legacy
Adds Markdown support to Vite, both at the top level as well as within `.astro` files.
diff --git a/packages/astro/src/vite-plugin-markdown/index.ts b/packages/astro/src/vite-plugin-markdown/index.ts
index 6794b499a..d1f04f32d 100644
--- a/packages/astro/src/vite-plugin-markdown/index.ts
+++ b/packages/astro/src/vite-plugin-markdown/index.ts
@@ -1,28 +1,19 @@
import { renderMarkdown } from '@astrojs/markdown-remark';
-import ancestor from 'common-ancestor-path';
-import esbuild from 'esbuild';
-import fs from 'fs';
import matter from 'gray-matter';
-import { fileURLToPath } from 'url';
+import fs from 'fs';
import type { Plugin } from 'vite';
import type { AstroConfig } from '../@types/astro';
-import { pagesVirtualModuleId } from '../core/app/index.js';
import { collectErrorMetadata } from '../core/errors.js';
import type { LogOptions } from '../core/logger/core.js';
-import { warn } from '../core/logger/core.js';
-import { cachedCompilation, CompileProps } from '../vite-plugin-astro/compile.js';
-import { getViteTransform, TransformHook } from '../vite-plugin-astro/styles.js';
-import type { PluginMetadata as AstroPluginMetadata } from '../vite-plugin-astro/types';
+import type { PluginMetadata } from '../vite-plugin-astro/types.js';
import { getFileInfo } from '../vite-plugin-utils/index.js';
+import { warn } from '../core/logger/core.js';
interface AstroPluginOptions {
config: AstroConfig;
logging: LogOptions;
}
-const MARKDOWN_IMPORT_FLAG = '?mdImport';
-const MARKDOWN_CONTENT_FLAG = '?content';
-
function safeMatter(source: string, id: string) {
try {
return matter(source);
@@ -32,146 +23,31 @@ function safeMatter(source: string, id: string) {
}
}
-// TODO: Clean up some of the shared logic between this Markdown plugin and the Astro plugin.
-// Both end up connecting a `load()` hook to the Astro compiler, and share some copy-paste
-// logic in how that is done.
export default function markdown({ config, logging }: AstroPluginOptions): Plugin {
- function normalizeFilename(filename: string) {
- if (filename.startsWith('/@fs')) {
- filename = filename.slice('/@fs'.length);
- } else if (filename.startsWith('/') && !ancestor(filename, config.root.pathname)) {
- filename = new URL('.' + filename, config.root).pathname;
- }
- return filename;
- }
-
- // Weird Vite behavior: Vite seems to use a fake "index.html" importer when you
- // have `enforce: pre`. This can probably be removed once the vite issue is fixed.
- // see: https://github.com/vitejs/vite/issues/5981
- const fakeRootImporter = fileURLToPath(new URL('index.html', config.root));
- function isRootImport(importer: string | undefined) {
- if (!importer) {
- return true;
- }
- if (importer === fakeRootImporter) {
- return true;
- }
- if (importer === '\0' + pagesVirtualModuleId) {
- return true;
- }
- return false;
- }
-
- let viteTransform: TransformHook;
-
return {
- name: 'astro:markdown',
enforce: 'pre',
- configResolved(_resolvedConfig) {
- viteTransform = getViteTransform(_resolvedConfig);
- },
- async resolveId(id, importer, options) {
- // Resolve any .md files with the `?content` cache buster. This should only come from
- // an already-resolved JS module wrapper. Needed to prevent infinite loops in Vite.
- // Unclear if this is expected or if cache busting is just working around a Vite bug.
- if (id.endsWith(`.md${MARKDOWN_CONTENT_FLAG}`)) {
- const resolvedId = await this.resolve(id, importer, { skipSelf: true, ...options });
- return resolvedId?.id.replace(MARKDOWN_CONTENT_FLAG, '');
- }
- // If the markdown file is imported from another file via ESM, resolve a JS representation
- // that defers the markdown -> HTML rendering until it is needed. This is especially useful
- // when fetching and then filtering many markdown files, like with import.meta.glob() or Astro.glob().
- // Otherwise, resolve directly to the actual component.
- if (id.endsWith('.md') && !isRootImport(importer)) {
- const resolvedId = await this.resolve(id, importer, { skipSelf: true, ...options });
- if (resolvedId) {
- return resolvedId.id + MARKDOWN_IMPORT_FLAG;
- }
- }
- // In all other cases, we do nothing and rely on normal Vite resolution.
- return undefined;
- },
- async load(id, opts) {
- // A markdown file has been imported via ESM!
- // Return the file's JS representation, including all Markdown
- // frontmatter and a deferred `import() of the compiled markdown content.
- if (id.endsWith(`.md${MARKDOWN_IMPORT_FLAG}`)) {
- const { fileId, fileUrl } = getFileInfo(id, config);
-
- const source = await fs.promises.readFile(fileId, 'utf8');
- const { data: frontmatter, content: rawContent } = safeMatter(source, fileId);
- return {
- code: `
- // Static
- export const frontmatter = ${escapeViteEnvReferences(JSON.stringify(frontmatter))};
- export const file = ${JSON.stringify(fileId)};
- export const url = ${JSON.stringify(fileUrl)};
- export function rawContent() {
- return ${escapeViteEnvReferences(JSON.stringify(rawContent))};
- }
- export async function compiledContent() {
- return load().then((m) => m.compiledContent());
- }
- export function $$loadMetadata() {
- return load().then((m) => m.$$metadata);
- }
-
- // Deferred
- export default async function load() {
- return (await import(${JSON.stringify(fileId + MARKDOWN_CONTENT_FLAG)}));
- }
- export function Content(...args) {
- return load().then((m) => m.default(...args));
- }
- Content.isAstroComponentFactory = true;
- export function getHeadings() {
- return load().then((m) => m.metadata.headings);
- }
- export function getHeaders() {
- console.warn('getHeaders() have been deprecated. Use getHeadings() function instead.');
- return load().then((m) => m.metadata.headings);
- };`,
- map: null,
- };
- }
-
- // A markdown file is being rendered! This markdown file was either imported
- // directly as a page in Vite, or it was a deferred render from a JS module.
- // This returns the compiled markdown -> astro component that renders to HTML.
+ name: 'astro:markdown',
+ // Why not the "transform" hook instead of "load" + readFile?
+ // A: Vite transforms all "import.meta.env" references to their values before
+ // passing to the transform hook. This lets us get the truly raw value
+ // to escape "import.meta.env" ourselves.
+ async load(id) {
if (id.endsWith('.md')) {
- const filename = normalizeFilename(id);
- const source = await fs.promises.readFile(filename, 'utf8');
- const renderOpts = config.markdown;
- const isAstroFlavoredMd = config.legacy.astroFlavoredMarkdown;
-
- const fileUrl = new URL(`file://${filename}`);
-
- // Extract special frontmatter keys
- let { data: frontmatter, content: markdownContent } = safeMatter(source, filename);
-
- // Turn HTML comments into JS comments while preventing nested `*/` sequences
- // from ending the JS comment by injecting a zero-width space
- // Inside code blocks, this is removed during renderMarkdown by the remark-escape plugin.
- if (isAstroFlavoredMd) {
- markdownContent = markdownContent.replace(
- /<\s*!--([^-->]*)(.*?)-->/gs,
- (whole) => `{/*${whole.replace(/\*\//g, '*\u200b/')}*/}`
- );
- }
-
- let renderResult = await renderMarkdown(markdownContent, {
- ...renderOpts,
- fileURL: fileUrl,
- isAstroFlavoredMd,
+ const { fileId, fileUrl } = getFileInfo(id, config);
+ const rawFile = await fs.promises.readFile(fileId, 'utf-8');
+ const raw = safeMatter(rawFile, id);
+ const renderResult = await renderMarkdown(raw.content, {
+ ...config.markdown,
+ fileURL: new URL(`file://${fileId}`),
+ isAstroFlavoredMd: false,
} as any);
- let { code: astroResult, metadata } = renderResult;
- const { layout = '', components = '', setup = '', ...content } = frontmatter;
- content.astro = metadata;
- content.url = getFileInfo(id, config).fileUrl;
- content.file = filename;
- // Warn when attempting to use setup without the legacy flag
- if (setup && !isAstroFlavoredMd) {
+ const html = renderResult.code;
+ const { headings } = renderResult.metadata;
+ const frontmatter = { ...raw.data, url: fileUrl, file: fileId } as any;
+ const { layout } = frontmatter;
+
+ if (frontmatter.setup) {
warn(
logging,
'markdown',
@@ -179,100 +55,59 @@ export default function markdown({ config, logging }: AstroPluginOptions): Plugi
);
}
- const prelude = `---
-import Slugger from 'github-slugger';
-${layout ? `import Layout from '${layout}';` : ''}
-${isAstroFlavoredMd && components ? `import * from '${components}';` : ''}
-${isAstroFlavoredMd ? setup : ''}
-
-const slugger = new Slugger();
-function $$slug(value) {
- return slugger.slug(value);
-}
-
-const $$content = ${JSON.stringify(
- isAstroFlavoredMd
- ? content
- : // Avoid stripping "setup" and "components"
- // in plain MD mode
- { ...content, setup, components }
- )};
-
-Object.defineProperty($$content.astro, 'headers', {
- get() {
- console.warn('[${JSON.stringify(id)}] content.astro.headers is now content.astro.headings.');
- return this.headings;
- }
-});
----`;
+ const code = escapeViteEnvReferences(`
+ import { Fragment, jsx as h } from 'astro/jsx-runtime';
+ ${layout ? `import Layout from ${JSON.stringify(layout)};` : ''}
- const imports = `${layout ? `import Layout from '${layout}';` : ''}
-${isAstroFlavoredMd ? setup : ''}`.trim();
+ const html = ${JSON.stringify(html)};
- // Wrap with set:html fragment to skip
- // JSX expressions and components in "plain" md mode
- if (!isAstroFlavoredMd) {
- astroResult = `<Fragment set:html={${JSON.stringify(astroResult)}} />`;
+ export const frontmatter = ${JSON.stringify(frontmatter)};
+ export const file = ${JSON.stringify(fileId)};
+ export const url = ${JSON.stringify(fileUrl)};
+ export function rawContent() {
+ return ${JSON.stringify(raw.content)};
}
-
- // If the user imported "Layout", wrap the content in a Layout
- if (/\bLayout\b/.test(imports)) {
- astroResult = `${prelude}\n<Layout content={$$content}>\n\n${astroResult}\n\n</Layout>`;
- } else {
- // Note: without a Layout, we need to inject `head` manually so `maybeRenderHead` runs
- astroResult = `${prelude}\n<head></head>${astroResult}`;
+ export function compiledContent() {
+ return html;
}
-
- // Transform from `.astro` to valid `.ts`
- const compileProps: CompileProps = {
- config,
- filename,
- moduleId: id,
- source: astroResult,
- ssr: Boolean(opts?.ssr),
- viteTransform,
- pluginContext: this,
- };
-
- let transformResult = await cachedCompilation(compileProps);
- let { code: tsResult } = transformResult;
-
- tsResult = `\nexport const metadata = ${JSON.stringify(metadata)};
-export const frontmatter = ${JSON.stringify(content)};
-export function rawContent() {
- return ${JSON.stringify(markdownContent)};
-}
-export function compiledContent() {
- return ${JSON.stringify(renderResult.metadata.html)};
-}
-${tsResult}`;
-
- // Compile from `.ts` to `.js`
- const { code } = await esbuild.transform(tsResult, {
- loader: 'ts',
- sourcemap: false,
- sourcefile: id,
- });
-
- const astroMetadata: AstroPluginMetadata['astro'] = {
- clientOnlyComponents: transformResult.clientOnlyComponents,
- hydratedComponents: transformResult.hydratedComponents,
- scripts: transformResult.scripts,
+ export function getHeadings() {
+ return ${JSON.stringify(headings)};
+ }
+ export function getHeaders() {
+ console.warn('getHeaders() have been deprecated. Use getHeadings() function instead.');
+ return getHeadings();
};
-
+ export async function Content() {
+ const { layout, ...content } = frontmatter;
+ content.astro = {};
+ Object.defineProperty(content.astro, 'headings', {
+ get() {
+ throw new Error('The "astro" property is no longer supported! To access "headings" from your layout, try using "Astro.props.headings."')
+ }
+ });
+ const contentFragment = h(Fragment, { 'set:html': html });
+ return ${
+ layout
+ ? `h(Layout, { content, frontmatter: content, headings: getHeadings(), 'server:root': true, children: contentFragment })`
+ : `contentFragment`
+ };
+ }
+ export default Content;
+ `);
return {
- code: escapeViteEnvReferences(code),
- map: null,
+ code,
meta: {
- astro: astroMetadata,
+ astro: {
+ hydratedComponents: [],
+ clientOnlyComponents: [],
+ scripts: [],
+ } as PluginMetadata['astro'],
vite: {
lang: 'ts',
},
},
};
}
-
- return null;
},
};
}
diff --git a/packages/astro/test/astro-markdown-md-mode.test.js b/packages/astro/test/astro-markdown-md-mode.test.js
deleted file mode 100644
index 89f2a26ab..000000000
--- a/packages/astro/test/astro-markdown-md-mode.test.js
+++ /dev/null
@@ -1,51 +0,0 @@
-import { expect } from 'chai';
-import * as cheerio from 'cheerio';
-import { loadFixture } from './test-utils.js';
-
-describe('Astro Markdown - plain MD mode', () => {
- let fixture;
-
- before(async () => {
- fixture = await loadFixture({
- root: './fixtures/astro-markdown-md-mode/',
- });
- await fixture.build();
- });
-
- it('Leaves JSX expressions unprocessed', async () => {
- const html = await fixture.readFile('/jsx-expressions/index.html');
- const $ = cheerio.load(html);
-
- expect($('h2').html()).to.equal('{frontmatter.title}');
- });
-
- it('Leaves JSX components un-transformed', async () => {
- const html = await fixture.readFile('/components/index.html');
-
- expect(html).to.include('<counter client:load="" count="{0}">');
- });
-
- describe('syntax highlighting', async () => {
- it('handles Shiki', async () => {
- const html = await fixture.readFile('/code-in-md/index.html');
- const $ = cheerio.load(html);
-
- expect($('pre.astro-code').length).to.not.equal(0);
- });
-
- it('handles Prism', async () => {
- fixture = await loadFixture({
- root: './fixtures/astro-markdown-md-mode/',
- markdown: {
- syntaxHighlight: 'prism',
- },
- });
- await fixture.build();
-
- const html = await fixture.readFile('/code-in-md/index.html');
- const $ = cheerio.load(html);
-
- expect($('pre.language-html').length).to.not.equal(0);
- });
- });
-});
diff --git a/packages/astro/test/astro-markdown.test.js b/packages/astro/test/astro-markdown.test.js
index 92b78121a..eb1b43bcc 100644
--- a/packages/astro/test/astro-markdown.test.js
+++ b/packages/astro/test/astro-markdown.test.js
@@ -2,199 +2,130 @@ import { expect } from 'chai';
import * as cheerio from 'cheerio';
import { loadFixture, fixLineEndings } from './test-utils.js';
+const FIXTURE_ROOT = './fixtures/astro-markdown/';
+
describe('Astro Markdown', () => {
let fixture;
before(async () => {
fixture = await loadFixture({
- root: './fixtures/astro-markdown/',
+ root: FIXTURE_ROOT,
});
await fixture.build();
});
- it('Can parse JSX expressions in markdown pages', async () => {
+ it('Leaves JSX expressions unprocessed', async () => {
const html = await fixture.readFile('/jsx-expressions/index.html');
const $ = cheerio.load(html);
- expect($('h2').html()).to.equal('Blog Post with JSX expressions');
-
- expect(html).to.contain('JSX at the start of the line!');
- for (let listItem of ['test-1', 'test-2', 'test-3']) {
- expect($(`#${listItem}`).html()).to.equal(`${listItem}`);
- }
- });
-
- it('Can handle slugs with JSX expressions in markdown pages', async () => {
- const html = await fixture.readFile('/slug/index.html');
- const $ = cheerio.load(html);
-
- expect($('h1').attr('id')).to.equal('my-blog-post');
- });
-
- it('Can handle code elements without extra spacing', async () => {
- const html = await fixture.readFile('/code-element/index.html');
- const $ = cheerio.load(html);
-
- $('code').each((_, el) => {
- expect($(el).html()).to.equal($(el).html().trim());
- });
- });
-
- it('Can handle namespaced components in markdown', async () => {
- const html = await fixture.readFile('/namespace/index.html');
- const $ = cheerio.load(html);
-
- expect($('h1').text()).to.equal('Hello Namespace!');
- expect($('button').length).to.equal(1);
- });
-
- it('Correctly handles component children in markdown pages (#3319)', async () => {
- const html = await fixture.readFile('/children/index.html');
-
- expect(html).not.to.contain('<p></p>');
- });
-
- it('Can handle HTML comments in markdown pages', async () => {
- const html = await fixture.readFile('/comment/index.html');
- const $ = cheerio.load(html);
-
- expect($('h1').text()).to.equal('It works!');
+ expect($('h2').html()).to.equal('{frontmatter.title}');
});
- it('Prevents `*/` sequences from breaking HTML comments (#3476)', async () => {
- const html = await fixture.readFile('/comment-with-js/index.html');
- const $ = cheerio.load(html);
+ it('Leaves JSX components un-transformed', async () => {
+ const html = await fixture.readFile('/components/index.html');
- expect($('h1').text()).to.equal('It still works!');
- });
-
- it('Can handle HTML comments in inline code', async () => {
- const html = await fixture.readFile('/comment-with-js/index.html');
- const $ = cheerio.load(html);
-
- expect($('p code').text()).to.equal('<!-- HTML comments in code -->');
- });
-
- it('Can handle HTML comments in code fences', async () => {
- const html = await fixture.readFile('/comment-with-js/index.html');
- const $ = cheerio.load(html);
-
- expect($('pre > code').text()).to.equal('<!-- HTML comments in code fence -->');
- });
-
- // https://github.com/withastro/astro/issues/3254
- it('Can handle scripts in markdown pages', async () => {
- const html = await fixture.readFile('/script/index.html');
- expect(html).not.to.match(new RegExp('/src/scripts/test.js'));
- });
-
- it('Empty code blocks do not fail', async () => {
- const html = await fixture.readFile('/empty-code/index.html');
- const $ = cheerio.load(html);
-
- // test 1: There is not a `<code>` in the codeblock
- expect($('pre')[0].children).to.have.lengthOf(1);
-
- // test 2: The empty `<pre>` failed to render
- expect($('pre')[1].children).to.have.lengthOf(0);
- });
-
- it('Can render markdown with --- for horizontal rule', async () => {
- const html = await fixture.readFile('/dash/index.html');
- expect(!!html).to.equal(true);
+ expect(html).to.include('<counter client:load="" count="{0}">');
});
+
it('Exposes raw markdown content', async () => {
const { raw } = JSON.parse(await fixture.readFile('/raw-content.json'));
- expect(fixLineEndings(raw)).to.equal(
- `\n## With components\n\n### Non-hydrated\n\n<Hello name="Astro Naut" />\n\n### Hydrated\n\n<Counter client:load />\n<SvelteButton client:load />\n`
+ expect(fixLineEndings(raw).trim()).to.equal(
+ `# Basic page\n\nLets make sure raw and compiled content look right!`
);
});
- it('Exposes HTML parser for raw markdown content', async () => {
+ it('Exposes compiled HTML content', async () => {
const { compiled } = JSON.parse(await fixture.readFile('/raw-content.json'));
- expect(fixLineEndings(compiled)).to.equal(
- `<h2 id="with-components">With components</h2>\n<h3 id="non-hydrated">Non-hydrated</h3>\n<Hello name="Astro Naut" />\n<h3 id="hydrated">Hydrated</h3>\n<Counter client:load />\n<SvelteButton client:load />`
+ expect(fixLineEndings(compiled).trim()).to.equal(
+ `<h1 id="basic-page">Basic page</h1>\n<p>Lets make sure raw and compiled content look right!</p>`
);
});
- it('Allows referencing Vite env var names in markdown (#3412)', async () => {
- const html = await fixture.readFile('/vite-env-vars/index.html');
- const $ = cheerio.load(html);
+ describe('syntax highlighting', async () => {
+ it('handles Shiki', async () => {
+ const html = await fixture.readFile('/code-in-md/index.html');
+ const $ = cheerio.load(html);
- // test 1: referencing an existing var name
- expect($('code').eq(0).text()).to.equal('import.meta.env.SITE');
- expect($('li').eq(0).text()).to.equal('import.meta.env.SITE');
- expect($('code').eq(3).text()).to.contain('site: import.meta.env.SITE');
- expect($('blockquote').text()).to.contain('import.meta.env.SITE');
-
- // test 2: referencing a non-existing var name
- expect($('code').eq(1).text()).to.equal('import.meta.env.TITLE');
- expect($('li').eq(1).text()).to.equal('import.meta.env.TITLE');
- expect($('code').eq(3).text()).to.contain('title: import.meta.env.TITLE');
- expect($('blockquote').text()).to.contain('import.meta.env.TITLE');
-
- // test 3: referencing `import.meta.env` itself (without any var name)
- expect($('code').eq(2).text()).to.equal('import.meta.env');
- expect($('li').eq(2).text()).to.equal('import.meta.env');
- expect($('code').eq(3).text()).to.contain('// Use Vite env vars with import.meta.env');
- expect($('blockquote').text()).to.match(/import\.meta\.env\s*$/);
- });
+ expect($('pre.astro-code').length).to.not.equal(0);
+ });
- it('Escapes HTML tags in code blocks', async () => {
- const html = await fixture.readFile('/code-in-md/index.html');
- const $ = cheerio.load(html);
+ it('handles Prism', async () => {
+ const prismFixture = await loadFixture({
+ root: FIXTURE_ROOT,
+ markdown: {
+ syntaxHighlight: 'prism',
+ },
+ });
+ await prismFixture.build();
- expect($('code').eq(0).html()).to.equal('&lt;script&gt;');
- expect($('blockquote').length).to.equal(1);
- expect($('code').eq(1).html()).to.equal('&lt;/script&gt;');
- expect($('pre').html()).to.contain('&gt;This should also work without any problems.&lt;');
- });
+ const html = await prismFixture.readFile('/code-in-md/index.html');
+ const $ = cheerio.load(html);
- it('Allows defining slot contents in component children', async () => {
- const html = await fixture.readFile('/slots/index.html');
- const $ = cheerio.load(html);
+ expect($('pre.language-html').length).to.not.equal(0);
+ });
+ });
- const slots = $('article').eq(0);
- expect(slots.find('> .fragmentSlot > div').text()).to.contain('1:');
- expect(slots.find('> .fragmentSlot > div + p').text()).to.contain('2:');
- expect(slots.find('> .pSlot > p[title="hello"]').text()).to.contain('3:');
- expect(slots.find('> .defaultSlot').html()).to.match(
- new RegExp(
- `<div>4: Div in default slot</div>` +
- // Optional extra paragraph due to the line breaks between components
- `(<p></p>)?` +
- `<p>5: Paragraph in fragment in default slot</p>` +
- // Optional whitespace due to the line breaks between components
- `[\s\n]*` +
- `6: Regular text in default slot`
- )
- );
- const nestedSlots = $('article').eq(1);
- expect(nestedSlots.find('> .fragmentSlot').html()).to.contain('1:');
- expect(nestedSlots.find('> .pSlot > p').text()).to.contain('2:');
- expect(nestedSlots.find('> .defaultSlot > article').text().replace(/\s+/g, ' ')).to.equal(
- `
- 3: nested fragmentSlot
- 4: nested pSlot
- 5: nested text in default slot
- `.replace(/\s+/g, ' ')
- );
+ it('Passes frontmatter to layout via "content" and "frontmatter" props', async () => {
+ const html = await fixture.readFile('/with-layout/index.html');
+ const $ = cheerio.load(html);
- expect($('article').eq(3).text().replace(/[^❌]/g, '')).to.equal('❌❌❌');
+ const contentTitle = $('[data-content-title]');
+ const frontmatterTitle = $('[data-frontmatter-title]');
- expect($('article').eq(4).text().replace(/[^❌]/g, '')).to.equal('❌❌❌');
+ expect(contentTitle.text()).to.equal('With layout');
+ expect(frontmatterTitle.text()).to.equal('With layout');
});
- it('Generate the right props for the layout', async () => {
- const html = await fixture.readFile('/layout-props/index.html');
+ it('Passes headings to layout via "headings" prop', async () => {
+ const html = await fixture.readFile('/with-layout/index.html');
const $ = cheerio.load(html);
- expect($('#title').text()).to.equal('Hello world!');
- expect($('#url').text()).to.equal('/layout-props');
- expect($('#file').text()).to.match(/.*\/layout-props.md$/);
+ const headingSlugs = [...$('body').find('[data-headings] > li')].map(
+ (el) => $(el).text()
+ );
+
+ expect(headingSlugs.length).to.be.greaterThan(0);
+ expect(headingSlugs).to.contain('section-1');
+ expect(headingSlugs).to.contain('section-2');
+ });
+
+ it('Exposes getHeadings() on glob imports', async () => {
+ const { headings } = JSON.parse(await fixture.readFile('/headings-glob.json'));
+
+ const headingSlugs = headings.map(heading => heading?.slug);
+
+ expect(headingSlugs).to.contain('section-1');
+ expect(headingSlugs).to.contain('section-2');
+ });
+
+ describe('Vite env vars (#3412)', () => {
+ it('Allows referencing import.meta.env in content', async () => {
+ const html = await fixture.readFile('/vite-env-vars/index.html');
+ const $ = cheerio.load(html);
+
+ // test 1: referencing an existing var name
+ expect($('code').eq(0).text()).to.equal('import.meta.env.SITE');
+ expect($('li').eq(0).text()).to.equal('import.meta.env.SITE');
+ expect($('code').eq(3).text()).to.contain('site: import.meta.env.SITE');
+
+ // // test 2: referencing a non-existing var name
+ expect($('code').eq(1).text()).to.equal('import.meta.env.TITLE');
+ expect($('li').eq(1).text()).to.equal('import.meta.env.TITLE');
+ expect($('code').eq(3).text()).to.contain('title: import.meta.env.TITLE');
+
+ // // test 3: referencing `import.meta.env` itself (without any var name)
+ expect($('code').eq(2).text()).to.equal('import.meta.env');
+ expect($('li').eq(2).text()).to.equal('import.meta.env');
+ expect($('code').eq(3).text()).to.contain('// Use Vite env vars with import.meta.env');
+ });
+ it('Allows referencing import.meta.env in frontmatter', async () => {
+ const { title = '' } = JSON.parse(await fixture.readFile('/vite-env-vars-glob.json'));
+ expect(title).to.contain('import.meta.env.SITE');
+ expect(title).to.contain('import.meta.env.TITLE');
+ });
});
});
diff --git a/packages/astro/test/fixtures/astro-head/package.json b/packages/astro/test/fixtures/astro-head/package.json
new file mode 100644
index 000000000..bc4b5a2c2
--- /dev/null
+++ b/packages/astro/test/fixtures/astro-head/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "@test/astro-head",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "astro": "workspace:*"
+ }
+}
diff --git a/packages/astro/test/fixtures/astro-markdown-md-mode/src/pages/code-in-md.md b/packages/astro/test/fixtures/astro-markdown-md-mode/src/pages/code-in-md.md
deleted file mode 100644
index 72206a868..000000000
--- a/packages/astro/test/fixtures/astro-markdown-md-mode/src/pages/code-in-md.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# Fenced code blocks
-
-```html
-<body>
- <div>This should also work without any problems.</div>
-</body>
-```
diff --git a/packages/astro/test/fixtures/astro-markdown/astro.config.mjs b/packages/astro/test/fixtures/astro-markdown/astro.config.mjs
index baefed8cc..908e3442f 100644
--- a/packages/astro/test/fixtures/astro-markdown/astro.config.mjs
+++ b/packages/astro/test/fixtures/astro-markdown/astro.config.mjs
@@ -1,12 +1,8 @@
import { defineConfig } from 'astro/config';
-import preact from '@astrojs/preact';
import svelte from "@astrojs/svelte";
// https://astro.build/config
export default defineConfig({
- integrations: [preact(), svelte()],
+ integrations: [svelte()],
site: 'https://astro.build/',
- legacy: {
- astroFlavoredMarkdown: true,
- }
});
diff --git a/packages/astro/test/fixtures/astro-markdown/package.json b/packages/astro/test/fixtures/astro-markdown/package.json
index e5a54cf7c..48a6c6816 100644
--- a/packages/astro/test/fixtures/astro-markdown/package.json
+++ b/packages/astro/test/fixtures/astro-markdown/package.json
@@ -1,9 +1,8 @@
{
- "name": "@test/astro-markdown",
+ "name": "@test/astro-markdown-md-mode",
"version": "0.0.0",
"private": true,
"dependencies": {
- "@astrojs/preact": "workspace:*",
"@astrojs/svelte": "workspace:*",
"astro": "workspace:*"
}
diff --git a/packages/astro/test/fixtures/astro-markdown-md-mode/src/components/Counter.svelte b/packages/astro/test/fixtures/astro-markdown/src/components/Counter.svelte
index 4e91b2659..4e91b2659 100644
--- a/packages/astro/test/fixtures/astro-markdown-md-mode/src/components/Counter.svelte
+++ b/packages/astro/test/fixtures/astro-markdown/src/components/Counter.svelte
diff --git a/packages/astro/test/fixtures/astro-markdown/src/layouts/Base.astro b/packages/astro/test/fixtures/astro-markdown/src/layouts/Base.astro
new file mode 100644
index 000000000..e4fa7560a
--- /dev/null
+++ b/packages/astro/test/fixtures/astro-markdown/src/layouts/Base.astro
@@ -0,0 +1,28 @@
+---
+const {
+ content = { title: "content didn't work" },
+ frontmatter = { title: "frontmatter didn't work" },
+ headings = [],
+} = Astro.props;
+---
+
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+ <meta charset="UTF-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+</head>
+
+<body>
+ <p data-content-title>{content.title}</p>
+ <p data-frontmatter-title>{frontmatter.title}</p>
+ <p data-layout-rendered>Layout rendered!</p>
+ <ul data-headings>
+ {headings.map(heading => <li>{heading.slug}</li>)}
+ </ul>
+ <slot />
+</body>
+
+</html>
diff --git a/packages/astro/test/fixtures/astro-markdown/src/pages/basic.md b/packages/astro/test/fixtures/astro-markdown/src/pages/basic.md
new file mode 100644
index 000000000..224c48f65
--- /dev/null
+++ b/packages/astro/test/fixtures/astro-markdown/src/pages/basic.md
@@ -0,0 +1,3 @@
+# Basic page
+
+Lets make sure raw and compiled content look right!
diff --git a/packages/astro/test/fixtures/astro-markdown/src/pages/code-in-md.md b/packages/astro/test/fixtures/astro-markdown/src/pages/code-in-md.md
index 52a799ab1..72206a868 100644
--- a/packages/astro/test/fixtures/astro-markdown/src/pages/code-in-md.md
+++ b/packages/astro/test/fixtures/astro-markdown/src/pages/code-in-md.md
@@ -1,12 +1,3 @@
-# Inline code blocks
-
-`<script>` tags in **Astro** components are now built,
-bundled and optimized by default.
-
-> Markdown formatting still works between tags in inline code blocks.
-
-We can also use closing `</script>` tags without any problems.
-
# Fenced code blocks
```html
diff --git a/packages/astro/test/fixtures/astro-markdown-md-mode/src/pages/components.md b/packages/astro/test/fixtures/astro-markdown/src/pages/components.md
index cd27bc09f..cd27bc09f 100644
--- a/packages/astro/test/fixtures/astro-markdown-md-mode/src/pages/components.md
+++ b/packages/astro/test/fixtures/astro-markdown/src/pages/components.md
diff --git a/packages/astro/test/fixtures/astro-markdown/src/pages/headings-glob.json.js b/packages/astro/test/fixtures/astro-markdown/src/pages/headings-glob.json.js
new file mode 100644
index 000000000..631250c33
--- /dev/null
+++ b/packages/astro/test/fixtures/astro-markdown/src/pages/headings-glob.json.js
@@ -0,0 +1,9 @@
+import { getHeadings } from './with-layout.md';
+
+export async function get() {
+ return {
+ body: JSON.stringify({
+ headings: getHeadings(),
+ }),
+ }
+}
diff --git a/packages/astro/test/fixtures/astro-markdown/src/pages/raw-content.json.js b/packages/astro/test/fixtures/astro-markdown/src/pages/raw-content.json.js
index 21be533e1..ef933a373 100644
--- a/packages/astro/test/fixtures/astro-markdown/src/pages/raw-content.json.js
+++ b/packages/astro/test/fixtures/astro-markdown/src/pages/raw-content.json.js
@@ -1,4 +1,4 @@
-import { rawContent, compiledContent } from '../imported-md/with-components.md';
+import { rawContent, compiledContent } from './basic.md';
export async function get() {
return {
diff --git a/packages/astro/test/fixtures/astro-markdown/src/pages/vite-env-vars-glob.json.js b/packages/astro/test/fixtures/astro-markdown/src/pages/vite-env-vars-glob.json.js
new file mode 100644
index 000000000..4a7e4dd78
--- /dev/null
+++ b/packages/astro/test/fixtures/astro-markdown/src/pages/vite-env-vars-glob.json.js
@@ -0,0 +1,7 @@
+import { frontmatter } from './vite-env-vars.md';
+
+export async function get() {
+ return {
+ body: JSON.stringify(frontmatter),
+ }
+}
diff --git a/packages/astro/test/fixtures/astro-markdown/src/pages/vite-env-vars.md b/packages/astro/test/fixtures/astro-markdown/src/pages/vite-env-vars.md
index 30a9ab177..f4983af2c 100644
--- a/packages/astro/test/fixtures/astro-markdown/src/pages/vite-env-vars.md
+++ b/packages/astro/test/fixtures/astro-markdown/src/pages/vite-env-vars.md
@@ -1,6 +1,5 @@
---
title: Referencing Vite Env Vars like import.meta.env.SITE, import.meta.env.TITLE and import.meta.env
-layout: ../layouts/content.astro
---
## Referencing the full name of Vite env vars
@@ -29,7 +28,3 @@ export const get = () => rss({
items: import.meta.glob('./**/*.md'),
});
```
-
-## Usage in frontmatter
-
-> frontmatter.title: {frontmatter.title}
diff --git a/packages/astro/test/fixtures/astro-markdown/src/pages/with-layout.md b/packages/astro/test/fixtures/astro-markdown/src/pages/with-layout.md
new file mode 100644
index 000000000..37985beb8
--- /dev/null
+++ b/packages/astro/test/fixtures/astro-markdown/src/pages/with-layout.md
@@ -0,0 +1,8 @@
+---
+title: 'With layout'
+layout: '../layouts/Base.astro'
+---
+
+## Section 1
+
+## Section 2 \ No newline at end of file
diff --git a/packages/astro/test/fixtures/client-address/package.json b/packages/astro/test/fixtures/client-address/package.json
new file mode 100644
index 000000000..e0bd7e3ed
--- /dev/null
+++ b/packages/astro/test/fixtures/client-address/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "@test/client-address",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "astro": "workspace:*"
+ }
+}
diff --git a/packages/astro/test/fixtures/glob-pages-css/package.json b/packages/astro/test/fixtures/glob-pages-css/package.json
new file mode 100644
index 000000000..bc8412754
--- /dev/null
+++ b/packages/astro/test/fixtures/glob-pages-css/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "@test/glob-pages-css",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "astro": "workspace:*"
+ }
+}
diff --git a/packages/astro/test/fixtures/astro-markdown-md-mode/astro.config.mjs b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/astro.config.mjs
index 908e3442f..baefed8cc 100644
--- a/packages/astro/test/fixtures/astro-markdown-md-mode/astro.config.mjs
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/astro.config.mjs
@@ -1,8 +1,12 @@
import { defineConfig } from 'astro/config';
+import preact from '@astrojs/preact';
import svelte from "@astrojs/svelte";
// https://astro.build/config
export default defineConfig({
- integrations: [svelte()],
+ integrations: [preact(), svelte()],
site: 'https://astro.build/',
+ legacy: {
+ astroFlavoredMarkdown: true,
+ }
});
diff --git a/packages/astro/test/fixtures/astro-markdown-md-mode/package.json b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/package.json
index 48a6c6816..e5a54cf7c 100644
--- a/packages/astro/test/fixtures/astro-markdown-md-mode/package.json
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/package.json
@@ -1,8 +1,9 @@
{
- "name": "@test/astro-markdown-md-mode",
+ "name": "@test/astro-markdown",
"version": "0.0.0",
"private": true,
"dependencies": {
+ "@astrojs/preact": "workspace:*",
"@astrojs/svelte": "workspace:*",
"astro": "workspace:*"
}
diff --git a/packages/astro/test/fixtures/astro-markdown/src/components/Counter.jsx b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/components/Counter.jsx
index a75f858b5..a75f858b5 100644
--- a/packages/astro/test/fixtures/astro-markdown/src/components/Counter.jsx
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/components/Counter.jsx
diff --git a/packages/astro/test/fixtures/astro-markdown/src/components/Example.jsx b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/components/Example.jsx
index e1f67ee50..e1f67ee50 100644
--- a/packages/astro/test/fixtures/astro-markdown/src/components/Example.jsx
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/components/Example.jsx
diff --git a/packages/astro/test/fixtures/astro-markdown/src/components/Hello.jsx b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/components/Hello.jsx
index d30dec516..d30dec516 100644
--- a/packages/astro/test/fixtures/astro-markdown/src/components/Hello.jsx
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/components/Hello.jsx
diff --git a/packages/astro/test/fixtures/astro-markdown/src/components/SlotComponent.astro b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/components/SlotComponent.astro
index f0aa9fc1c..f0aa9fc1c 100644
--- a/packages/astro/test/fixtures/astro-markdown/src/components/SlotComponent.astro
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/components/SlotComponent.astro
diff --git a/packages/astro/test/fixtures/astro-markdown/src/components/SvelteButton.svelte b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/components/SvelteButton.svelte
index 74f3ff6a9..74f3ff6a9 100644
--- a/packages/astro/test/fixtures/astro-markdown/src/components/SvelteButton.svelte
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/components/SvelteButton.svelte
diff --git a/packages/astro/test/fixtures/astro-markdown/src/components/TextBlock.jsx b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/components/TextBlock.jsx
index d9ea2534f..d9ea2534f 100644
--- a/packages/astro/test/fixtures/astro-markdown/src/components/TextBlock.jsx
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/components/TextBlock.jsx
diff --git a/packages/astro/test/fixtures/astro-markdown/src/components/index.js b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/components/index.js
index e7cc94c58..e7cc94c58 100644
--- a/packages/astro/test/fixtures/astro-markdown/src/components/index.js
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/components/index.js
diff --git a/packages/astro/test/fixtures/astro-markdown/src/content/code-element.md b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/content/code-element.md
index b091decc0..b091decc0 100644
--- a/packages/astro/test/fixtures/astro-markdown/src/content/code-element.md
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/content/code-element.md
diff --git a/packages/astro/test/fixtures/astro-markdown/src/imported-md/plain.md b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/imported-md/plain.md
index d548b3356..d548b3356 100644
--- a/packages/astro/test/fixtures/astro-markdown/src/imported-md/plain.md
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/imported-md/plain.md
diff --git a/packages/astro/test/fixtures/astro-markdown/src/imported-md/with-components.md b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/imported-md/with-components.md
index dc6c23bf9..dc6c23bf9 100644
--- a/packages/astro/test/fixtures/astro-markdown/src/imported-md/with-components.md
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/imported-md/with-components.md
diff --git a/packages/astro/test/fixtures/astro-markdown/src/layouts/content.astro b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/layouts/content.astro
index 925a243a9..925a243a9 100644
--- a/packages/astro/test/fixtures/astro-markdown/src/layouts/content.astro
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/layouts/content.astro
diff --git a/packages/astro/test/fixtures/astro-markdown/src/layouts/layout-props.astro b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/layouts/layout-props.astro
index a11abb8fb..a11abb8fb 100644
--- a/packages/astro/test/fixtures/astro-markdown/src/layouts/layout-props.astro
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/layouts/layout-props.astro
diff --git a/packages/astro/test/fixtures/astro-markdown/src/pages/children.md b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/children.md
index a22ee5f96..a22ee5f96 100644
--- a/packages/astro/test/fixtures/astro-markdown/src/pages/children.md
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/children.md
diff --git a/packages/astro/test/fixtures/astro-markdown/src/pages/code-element.astro b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/code-element.astro
index 43ca0bfc5..43ca0bfc5 100644
--- a/packages/astro/test/fixtures/astro-markdown/src/pages/code-element.astro
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/code-element.astro
diff --git a/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/code-in-md.md b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/code-in-md.md
new file mode 100644
index 000000000..52a799ab1
--- /dev/null
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/code-in-md.md
@@ -0,0 +1,16 @@
+# Inline code blocks
+
+`<script>` tags in **Astro** components are now built,
+bundled and optimized by default.
+
+> Markdown formatting still works between tags in inline code blocks.
+
+We can also use closing `</script>` tags without any problems.
+
+# Fenced code blocks
+
+```html
+<body>
+ <div>This should also work without any problems.</div>
+</body>
+```
diff --git a/packages/astro/test/fixtures/astro-markdown/src/pages/comment-with-js.md b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/comment-with-js.md
index 374463d2d..374463d2d 100644
--- a/packages/astro/test/fixtures/astro-markdown/src/pages/comment-with-js.md
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/comment-with-js.md
diff --git a/packages/astro/test/fixtures/astro-markdown/src/pages/comment.md b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/comment.md
index 39a916351..39a916351 100644
--- a/packages/astro/test/fixtures/astro-markdown/src/pages/comment.md
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/comment.md
diff --git a/packages/astro/test/fixtures/astro-markdown/src/pages/dash.md b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/dash.md
index 269a774f5..269a774f5 100644
--- a/packages/astro/test/fixtures/astro-markdown/src/pages/dash.md
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/dash.md
diff --git a/packages/astro/test/fixtures/astro-markdown/src/pages/empty-code.md b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/empty-code.md
index 93cb4eedb..93cb4eedb 100644
--- a/packages/astro/test/fixtures/astro-markdown/src/pages/empty-code.md
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/empty-code.md
diff --git a/packages/astro/test/fixtures/astro-markdown/src/pages/imported-md/with-components.astro b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/imported-md/with-components.astro
index 97cd8f211..97cd8f211 100644
--- a/packages/astro/test/fixtures/astro-markdown/src/pages/imported-md/with-components.astro
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/imported-md/with-components.astro
diff --git a/packages/astro/test/fixtures/astro-markdown-md-mode/src/pages/jsx-expressions.md b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/jsx-expressions.md
index b87efbb2d..b87efbb2d 100644
--- a/packages/astro/test/fixtures/astro-markdown-md-mode/src/pages/jsx-expressions.md
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/jsx-expressions.md
diff --git a/packages/astro/test/fixtures/astro-markdown/src/pages/layout-props.md b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/layout-props.md
index 0f87c1bd0..0f87c1bd0 100644
--- a/packages/astro/test/fixtures/astro-markdown/src/pages/layout-props.md
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/layout-props.md
diff --git a/packages/astro/test/fixtures/astro-markdown/src/pages/namespace.md b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/namespace.md
index abbe26a3b..abbe26a3b 100644
--- a/packages/astro/test/fixtures/astro-markdown/src/pages/namespace.md
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/namespace.md
diff --git a/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/raw-content.json.js b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/raw-content.json.js
new file mode 100644
index 000000000..21be533e1
--- /dev/null
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/raw-content.json.js
@@ -0,0 +1,10 @@
+import { rawContent, compiledContent } from '../imported-md/with-components.md';
+
+export async function get() {
+ return {
+ body: JSON.stringify({
+ raw: rawContent(),
+ compiled: await compiledContent(),
+ }),
+ }
+}
diff --git a/packages/astro/test/fixtures/astro-markdown/src/pages/script.md b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/script.md
index f2b8bca88..f2b8bca88 100644
--- a/packages/astro/test/fixtures/astro-markdown/src/pages/script.md
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/script.md
diff --git a/packages/astro/test/fixtures/astro-markdown/src/pages/slots.md b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/slots.md
index c7e8367b8..c7e8367b8 100644
--- a/packages/astro/test/fixtures/astro-markdown/src/pages/slots.md
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/slots.md
diff --git a/packages/astro/test/fixtures/astro-markdown/src/pages/slug.md b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/slug.md
index 77599b347..77599b347 100644
--- a/packages/astro/test/fixtures/astro-markdown/src/pages/slug.md
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/slug.md
diff --git a/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/vite-env-vars.md b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/vite-env-vars.md
new file mode 100644
index 000000000..30a9ab177
--- /dev/null
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/vite-env-vars.md
@@ -0,0 +1,35 @@
+---
+title: Referencing Vite Env Vars like import.meta.env.SITE, import.meta.env.TITLE and import.meta.env
+layout: ../layouts/content.astro
+---
+
+## Referencing the full name of Vite env vars
+
+You can get the configured site URL with `import.meta.env.SITE`.
+
+The variable `import.meta.env.TITLE` is not configured.
+
+You can reference all env vars through `import.meta.env`.
+
+This should also work outside of code blocks:
+- import.meta.env.SITE
+- import.meta.env.TITLE
+- import.meta.env
+
+## Usage in fenced code blocks with syntax highlighting
+
+```js
+// src/pages/rss.xml.js
+import rss from '@astrojs/rss';
+
+export const get = () => rss({
+ // Use Vite env vars with import.meta.env
+ site: import.meta.env.SITE,
+ title: import.meta.env.TITLE,
+ items: import.meta.glob('./**/*.md'),
+});
+```
+
+## Usage in frontmatter
+
+> frontmatter.title: {frontmatter.title}
diff --git a/packages/astro/test/fixtures/astro-markdown/src/scripts/test.js b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/scripts/test.js
index b179ee953..b179ee953 100644
--- a/packages/astro/test/fixtures/astro-markdown/src/scripts/test.js
+++ b/packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/scripts/test.js
diff --git a/packages/astro/test/legacy-astro-flavored-markdown.test.js b/packages/astro/test/legacy-astro-flavored-markdown.test.js
new file mode 100644
index 000000000..e9199d1a2
--- /dev/null
+++ b/packages/astro/test/legacy-astro-flavored-markdown.test.js
@@ -0,0 +1,200 @@
+import { expect } from 'chai';
+import * as cheerio from 'cheerio';
+import { loadFixture, fixLineEndings } from './test-utils.js';
+
+describe('Legacy Astro-flavored Markdown', () => {
+ let fixture;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/legacy-astro-flavored-markdown/',
+ });
+ await fixture.build();
+ });
+
+ it('Can parse JSX expressions in markdown pages', async () => {
+ const html = await fixture.readFile('/jsx-expressions/index.html');
+ const $ = cheerio.load(html);
+
+ expect($('h2').html()).to.equal('Blog Post with JSX expressions');
+
+ expect(html).to.contain('JSX at the start of the line!');
+ for (let listItem of ['test-1', 'test-2', 'test-3']) {
+ expect($(`#${listItem}`).html()).to.equal(`${listItem}`);
+ }
+ });
+
+ it('Can handle slugs with JSX expressions in markdown pages', async () => {
+ const html = await fixture.readFile('/slug/index.html');
+ const $ = cheerio.load(html);
+
+ expect($('h1').attr('id')).to.equal('my-blog-post');
+ });
+
+ it('Can handle code elements without extra spacing', async () => {
+ const html = await fixture.readFile('/code-element/index.html');
+ const $ = cheerio.load(html);
+
+ $('code').each((_, el) => {
+ expect($(el).html()).to.equal($(el).html().trim());
+ });
+ });
+
+ it('Can handle namespaced components in markdown', async () => {
+ const html = await fixture.readFile('/namespace/index.html');
+ const $ = cheerio.load(html);
+
+ expect($('h1').text()).to.equal('Hello Namespace!');
+ expect($('button').length).to.equal(1);
+ });
+
+ it('Correctly handles component children in markdown pages (#3319)', async () => {
+ const html = await fixture.readFile('/children/index.html');
+
+ expect(html).not.to.contain('<p></p>');
+ });
+
+ it('Can handle HTML comments in markdown pages', async () => {
+ const html = await fixture.readFile('/comment/index.html');
+ const $ = cheerio.load(html);
+
+ expect($('h1').text()).to.equal('It works!');
+ });
+
+ it('Prevents `*/` sequences from breaking HTML comments (#3476)', async () => {
+ const html = await fixture.readFile('/comment-with-js/index.html');
+ const $ = cheerio.load(html);
+
+ expect($('h1').text()).to.equal('It still works!');
+ });
+
+ it('Can handle HTML comments in inline code', async () => {
+ const html = await fixture.readFile('/comment-with-js/index.html');
+ const $ = cheerio.load(html);
+
+ expect($('p code').text()).to.equal('<!-- HTML comments in code -->');
+ });
+
+ it('Can handle HTML comments in code fences', async () => {
+ const html = await fixture.readFile('/comment-with-js/index.html');
+ const $ = cheerio.load(html);
+
+ expect($('pre > code').text()).to.equal('<!-- HTML comments in code fence -->');
+ });
+
+ // https://github.com/withastro/astro/issues/3254
+ it('Can handle scripts in markdown pages', async () => {
+ const html = await fixture.readFile('/script/index.html');
+ expect(html).not.to.match(new RegExp('/src/scripts/test.js'));
+ });
+
+ it('Empty code blocks do not fail', async () => {
+ const html = await fixture.readFile('/empty-code/index.html');
+ const $ = cheerio.load(html);
+
+ // test 1: There is not a `<code>` in the codeblock
+ expect($('pre')[0].children).to.have.lengthOf(1);
+
+ // test 2: The empty `<pre>` failed to render
+ expect($('pre')[1].children).to.have.lengthOf(0);
+ });
+
+ it('Can render markdown with --- for horizontal rule', async () => {
+ const html = await fixture.readFile('/dash/index.html');
+ expect(!!html).to.equal(true);
+ });
+
+ it('Exposes raw markdown content', async () => {
+ const { raw } = JSON.parse(await fixture.readFile('/raw-content.json'));
+
+ expect(fixLineEndings(raw)).to.equal(
+ `\n## With components\n\n### Non-hydrated\n\n<Hello name="Astro Naut" />\n\n### Hydrated\n\n<Counter client:load />\n<SvelteButton client:load />\n`
+ );
+ });
+
+ it('Exposes compiled HTML content', async () => {
+ const { compiled } = JSON.parse(await fixture.readFile('/raw-content.json'));
+
+ expect(fixLineEndings(compiled)).to.equal(
+ `<h2 id="with-components">With components</h2>\n<h3 id="non-hydrated">Non-hydrated</h3>\n<Hello name="Astro Naut" />\n<h3 id="hydrated">Hydrated</h3>\n<Counter client:load />\n<SvelteButton client:load />`
+ );
+ });
+
+ it('Allows referencing Vite env var names in markdown (#3412)', async () => {
+ const html = await fixture.readFile('/vite-env-vars/index.html');
+ const $ = cheerio.load(html);
+
+ // test 1: referencing an existing var name
+ expect($('code').eq(0).text()).to.equal('import.meta.env.SITE');
+ expect($('li').eq(0).text()).to.equal('import.meta.env.SITE');
+ expect($('code').eq(3).text()).to.contain('site: import.meta.env.SITE');
+ expect($('blockquote').text()).to.contain('import.meta.env.SITE');
+
+ // test 2: referencing a non-existing var name
+ expect($('code').eq(1).text()).to.equal('import.meta.env.TITLE');
+ expect($('li').eq(1).text()).to.equal('import.meta.env.TITLE');
+ expect($('code').eq(3).text()).to.contain('title: import.meta.env.TITLE');
+ expect($('blockquote').text()).to.contain('import.meta.env.TITLE');
+
+ // test 3: referencing `import.meta.env` itself (without any var name)
+ expect($('code').eq(2).text()).to.equal('import.meta.env');
+ expect($('li').eq(2).text()).to.equal('import.meta.env');
+ expect($('code').eq(3).text()).to.contain('// Use Vite env vars with import.meta.env');
+ expect($('blockquote').text()).to.match(/import\.meta\.env\s*$/);
+ });
+
+ it('Escapes HTML tags in code blocks', async () => {
+ const html = await fixture.readFile('/code-in-md/index.html');
+ const $ = cheerio.load(html);
+
+ expect($('code').eq(0).html()).to.equal('&lt;script&gt;');
+ expect($('blockquote').length).to.equal(1);
+ expect($('code').eq(1).html()).to.equal('&lt;/script&gt;');
+ expect($('pre').html()).to.contain('&gt;This should also work without any problems.&lt;');
+ });
+
+ it('Allows defining slot contents in component children', async () => {
+ const html = await fixture.readFile('/slots/index.html');
+ const $ = cheerio.load(html);
+
+ const slots = $('article').eq(0);
+ expect(slots.find('> .fragmentSlot > div').text()).to.contain('1:');
+ expect(slots.find('> .fragmentSlot > div + p').text()).to.contain('2:');
+ expect(slots.find('> .pSlot > p[title="hello"]').text()).to.contain('3:');
+ expect(slots.find('> .defaultSlot').html()).to.match(
+ new RegExp(
+ `<div>4: Div in default slot</div>` +
+ // Optional extra paragraph due to the line breaks between components
+ `(<p></p>)?` +
+ `<p>5: Paragraph in fragment in default slot</p>` +
+ // Optional whitespace due to the line breaks between components
+ `[\s\n]*` +
+ `6: Regular text in default slot`
+ )
+ );
+
+ const nestedSlots = $('article').eq(1);
+ expect(nestedSlots.find('> .fragmentSlot').html()).to.contain('1:');
+ expect(nestedSlots.find('> .pSlot > p').text()).to.contain('2:');
+ expect(nestedSlots.find('> .defaultSlot > article').text().replace(/\s+/g, ' ')).to.equal(
+ `
+ 3: nested fragmentSlot
+ 4: nested pSlot
+ 5: nested text in default slot
+ `.replace(/\s+/g, ' ')
+ );
+
+ expect($('article').eq(3).text().replace(/[^❌]/g, '')).to.equal('❌❌❌');
+
+ expect($('article').eq(4).text().replace(/[^❌]/g, '')).to.equal('❌❌❌');
+ });
+
+ it('Generate the right props for the layout', async () => {
+ const html = await fixture.readFile('/layout-props/index.html');
+ const $ = cheerio.load(html);
+
+ expect($('#title').text()).to.equal('Hello world!');
+ expect($('#url').text()).to.equal('/layout-props');
+ expect($('#file').text()).to.match(/.*\/layout-props.md$/);
+ });
+});
diff --git a/packages/markdown/remark/src/remark-escape.ts b/packages/markdown/remark/src/remark-escape.ts
index dd8a921ff..84cdf2efa 100644
--- a/packages/markdown/remark/src/remark-escape.ts
+++ b/packages/markdown/remark/src/remark-escape.ts
@@ -2,7 +2,7 @@ import type { Literal } from 'unist';
import { visit } from 'unist-util-visit';
// In code blocks, this removes the JS comment wrapper added to
-// HTML comments by vite-plugin-markdown.
+// HTML comments by vite-plugin-markdown-legacy.
export default function remarkEscape() {
return (tree: any) => {
visit(tree, 'code', removeCommentWrapper);
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ff53ebbca..85a6b95ed 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1199,6 +1199,12 @@ importers:
dependencies:
astro: link:../../..
+ packages/astro/test/fixtures/astro-head:
+ specifiers:
+ astro: workspace:*
+ dependencies:
+ astro: link:../../..
+
packages/astro/test/fixtures/astro-jsx:
specifiers:
astro: workspace:*
@@ -1207,11 +1213,9 @@ importers:
packages/astro/test/fixtures/astro-markdown:
specifiers:
- '@astrojs/preact': workspace:*
'@astrojs/svelte': workspace:*
astro: workspace:*
dependencies:
- '@astrojs/preact': link:../../../../integrations/preact
'@astrojs/svelte': link:../../../../integrations/svelte
astro: link:../../..
@@ -1227,14 +1231,6 @@ importers:
dependencies:
astro: link:../../..
- packages/astro/test/fixtures/astro-markdown-md-mode:
- specifiers:
- '@astrojs/svelte': workspace:*
- astro: workspace:*
- dependencies:
- '@astrojs/svelte': link:../../../../integrations/svelte
- astro: link:../../..
-
packages/astro/test/fixtures/astro-markdown-plugins:
specifiers:
'@astrojs/preact': workspace:*
@@ -1349,6 +1345,12 @@ importers:
dependencies:
astro: link:../../..
+ packages/astro/test/fixtures/client-address:
+ specifiers:
+ astro: workspace:*
+ dependencies:
+ astro: link:../../..
+
packages/astro/test/fixtures/code-component:
specifiers:
astro: workspace:*
@@ -1479,6 +1481,12 @@ importers:
'@fontsource/montserrat': 4.5.11
astro: link:../../..
+ packages/astro/test/fixtures/glob-pages-css:
+ specifiers:
+ astro: workspace:*
+ dependencies:
+ astro: link:../../..
+
packages/astro/test/fixtures/hmr-css:
specifiers:
astro: workspace:*
@@ -1566,6 +1574,16 @@ importers:
'@astrojs/solid-js': link:../../../../integrations/solid
astro: link:../../..
+ packages/astro/test/fixtures/legacy-astro-flavored-markdown:
+ specifiers:
+ '@astrojs/preact': workspace:*
+ '@astrojs/svelte': workspace:*
+ astro: workspace:*
+ dependencies:
+ '@astrojs/preact': link:../../../../integrations/preact
+ '@astrojs/svelte': link:../../../../integrations/svelte
+ astro: link:../../..
+
packages/astro/test/fixtures/legacy-build:
specifiers:
'@astrojs/vue': workspace:*