[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
This commit is contained in:
parent
838eb3e5cc
commit
471c6f784e
63 changed files with 831 additions and 490 deletions
6
.changeset/hip-dancers-move.md
Normal file
6
.changeset/hip-dancers-move.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
'astro': minor
|
||||
'@astrojs/markdown-remark': patch
|
||||
---
|
||||
|
||||
Speed up internal markdown builds with new vite-plugin markdown
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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/*"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 }),
|
||||
|
|
|
@ -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);
|
||||
|
|
3
packages/astro/src/vite-plugin-markdown-legacy/README.md
Normal file
3
packages/astro/src/vite-plugin-markdown-legacy/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# vite-plugin-markdown-legacy
|
||||
|
||||
Adds Markdown support to Vite, both at the top level as well as within `.astro` files.
|
260
packages/astro/src/vite-plugin-markdown-legacy/index.ts
Normal file
260
packages/astro/src/vite-plugin-markdown-legacy/index.ts
Normal file
|
@ -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');
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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 code = escapeViteEnvReferences(`
|
||||
import { Fragment, jsx as h } from 'astro/jsx-runtime';
|
||||
${layout ? `import Layout from ${JSON.stringify(layout)};` : ''}
|
||||
|
||||
const slugger = new Slugger();
|
||||
function $$slug(value) {
|
||||
return slugger.slug(value);
|
||||
}
|
||||
const html = ${JSON.stringify(html)};
|
||||
|
||||
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 imports = `${layout ? `import Layout from '${layout}';` : ''}
|
||||
${isAstroFlavoredMd ? setup : ''}`.trim();
|
||||
|
||||
// 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,
|
||||
export function getHeadings() {
|
||||
return ${JSON.stringify(headings)};
|
||||
}
|
||||
export function getHeaders() {
|
||||
console.warn('getHeaders() have been deprecated. Use getHeadings() function instead.');
|
||||
return getHeadings();
|
||||
};
|
||||
|
||||
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 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;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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}`);
|
||||
}
|
||||
expect($('h2').html()).to.equal('{frontmatter.title}');
|
||||
});
|
||||
|
||||
it('Can handle slugs with JSX expressions in markdown pages', async () => {
|
||||
const html = await fixture.readFile('/slug/index.html');
|
||||
const $ = cheerio.load(html);
|
||||
it('Leaves JSX components un-transformed', async () => {
|
||||
const html = await fixture.readFile('/components/index.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);
|
||||
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');
|
||||
expect($('pre.astro-code').length).to.not.equal(0);
|
||||
});
|
||||
|
||||
// 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');
|
||||
it('handles Prism', async () => {
|
||||
const prismFixture = await loadFixture({
|
||||
root: FIXTURE_ROOT,
|
||||
markdown: {
|
||||
syntaxHighlight: 'prism',
|
||||
},
|
||||
});
|
||||
await prismFixture.build();
|
||||
|
||||
// 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*$/);
|
||||
const html = await prismFixture.readFile('/code-in-md/index.html');
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
expect($('pre.language-html').length).to.not.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('Escapes HTML tags in code blocks', async () => {
|
||||
const html = await fixture.readFile('/code-in-md/index.html');
|
||||
|
||||
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($('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 contentTitle = $('[data-content-title]');
|
||||
const frontmatterTitle = $('[data-frontmatter-title]');
|
||||
|
||||
expect(contentTitle.text()).to.equal('With layout');
|
||||
expect(frontmatterTitle.text()).to.equal('With layout');
|
||||
});
|
||||
|
||||
it('Allows defining slot contents in component children', async () => {
|
||||
const html = await fixture.readFile('/slots/index.html');
|
||||
it('Passes headings to layout via "headings" prop', async () => {
|
||||
const html = await fixture.readFile('/with-layout/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 headingSlugs = [...$('body').find('[data-headings] > li')].map(
|
||||
(el) => $(el).text()
|
||||
);
|
||||
|
||||
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('❌❌❌');
|
||||
expect(headingSlugs.length).to.be.greaterThan(0);
|
||||
expect(headingSlugs).to.contain('section-1');
|
||||
expect(headingSlugs).to.contain('section-2');
|
||||
});
|
||||
|
||||
it('Generate the right props for the layout', async () => {
|
||||
const html = await fixture.readFile('/layout-props/index.html');
|
||||
const $ = cheerio.load(html);
|
||||
it('Exposes getHeadings() on glob imports', async () => {
|
||||
const { headings } = JSON.parse(await fixture.readFile('/headings-glob.json'));
|
||||
|
||||
expect($('#title').text()).to.equal('Hello world!');
|
||||
expect($('#url').text()).to.equal('/layout-props');
|
||||
expect($('#file').text()).to.match(/.*\/layout-props.md$/);
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
8
packages/astro/test/fixtures/astro-head/package.json
vendored
Normal file
8
packages/astro/test/fixtures/astro-head/package.json
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "@test/astro-head",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"astro": "workspace:*"
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
# Fenced code blocks
|
||||
|
||||
```html
|
||||
<body>
|
||||
<div>This should also work without any problems.</div>
|
||||
</body>
|
||||
```
|
|
@ -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,
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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:*"
|
||||
}
|
||||
|
|
28
packages/astro/test/fixtures/astro-markdown/src/layouts/Base.astro
vendored
Normal file
28
packages/astro/test/fixtures/astro-markdown/src/layouts/Base.astro
vendored
Normal file
|
@ -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>
|
3
packages/astro/test/fixtures/astro-markdown/src/pages/basic.md
vendored
Normal file
3
packages/astro/test/fixtures/astro-markdown/src/pages/basic.md
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Basic page
|
||||
|
||||
Lets make sure raw and compiled content look right!
|
|
@ -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
|
||||
|
|
9
packages/astro/test/fixtures/astro-markdown/src/pages/headings-glob.json.js
vendored
Normal file
9
packages/astro/test/fixtures/astro-markdown/src/pages/headings-glob.json.js
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { getHeadings } from './with-layout.md';
|
||||
|
||||
export async function get() {
|
||||
return {
|
||||
body: JSON.stringify({
|
||||
headings: getHeadings(),
|
||||
}),
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { rawContent, compiledContent } from '../imported-md/with-components.md';
|
||||
import { rawContent, compiledContent } from './basic.md';
|
||||
|
||||
export async function get() {
|
||||
return {
|
||||
|
|
7
packages/astro/test/fixtures/astro-markdown/src/pages/vite-env-vars-glob.json.js
vendored
Normal file
7
packages/astro/test/fixtures/astro-markdown/src/pages/vite-env-vars-glob.json.js
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { frontmatter } from './vite-env-vars.md';
|
||||
|
||||
export async function get() {
|
||||
return {
|
||||
body: JSON.stringify(frontmatter),
|
||||
}
|
||||
}
|
|
@ -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}
|
||||
|
|
8
packages/astro/test/fixtures/astro-markdown/src/pages/with-layout.md
vendored
Normal file
8
packages/astro/test/fixtures/astro-markdown/src/pages/with-layout.md
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
title: 'With layout'
|
||||
layout: '../layouts/Base.astro'
|
||||
---
|
||||
|
||||
## Section 1
|
||||
|
||||
## Section 2
|
8
packages/astro/test/fixtures/client-address/package.json
vendored
Normal file
8
packages/astro/test/fixtures/client-address/package.json
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "@test/client-address",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"astro": "workspace:*"
|
||||
}
|
||||
}
|
8
packages/astro/test/fixtures/glob-pages-css/package.json
vendored
Normal file
8
packages/astro/test/fixtures/glob-pages-css/package.json
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "@test/glob-pages-css",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"astro": "workspace:*"
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
});
|
|
@ -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:*"
|
||||
}
|
16
packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/code-in-md.md
vendored
Normal file
16
packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/code-in-md.md
vendored
Normal file
|
@ -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>
|
||||
```
|
10
packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/raw-content.json.js
vendored
Normal file
10
packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/raw-content.json.js
vendored
Normal file
|
@ -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(),
|
||||
}),
|
||||
}
|
||||
}
|
35
packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/vite-env-vars.md
vendored
Normal file
35
packages/astro/test/fixtures/legacy-astro-flavored-markdown/src/pages/vite-env-vars.md
vendored
Normal file
|
@ -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}
|
200
packages/astro/test/legacy-astro-flavored-markdown.test.js
Normal file
200
packages/astro/test/legacy-astro-flavored-markdown.test.js
Normal file
|
@ -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$/);
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
|
|
|
@ -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:*
|
||||
|
|
Loading…
Reference in a new issue