diff options
author | 2022-08-05 14:43:50 -0500 | |
---|---|---|
committer | 2022-08-05 15:43:50 -0400 | |
commit | 471c6f784e21399676c8b2002665ffdf83a1c59e (patch) | |
tree | b39134eb9b53b3a9ef9b987286061215a2af3531 | |
parent | 838eb3e5cc523d20aa5fb6b136ed5f6701568b1d (diff) | |
download | astro-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
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('<script>'); - expect($('blockquote').length).to.equal(1); - expect($('code').eq(1).html()).to.equal('</script>'); - expect($('pre').html()).to.contain('>This should also work without any problems.<'); - }); + 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('<script>'); + expect($('blockquote').length).to.equal(1); + expect($('code').eq(1).html()).to.equal('</script>'); + expect($('pre').html()).to.contain('>This should also work without any problems.<'); + }); + + 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:* |