Improve markdown rendering performance (#8532)
This commit is contained in:
parent
61ac5c9eaa
commit
7522bb4914
13 changed files with 209 additions and 125 deletions
5
.changeset/clever-parents-do.md
Normal file
5
.changeset/clever-parents-do.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Improve markdown rendering performance by sharing processor instance
|
5
.changeset/shaggy-actors-cheat.md
Normal file
5
.changeset/shaggy-actors-cheat.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'@astrojs/markdown-remark': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Export `createMarkdownProcessor` and deprecate `renderMarkdown` API
|
|
@ -1,8 +1,8 @@
|
||||||
import { renderMarkdown } from '@astrojs/markdown-remark';
|
|
||||||
import {
|
import {
|
||||||
|
createMarkdownProcessor,
|
||||||
InvalidAstroDataError,
|
InvalidAstroDataError,
|
||||||
safelyGetAstroData,
|
type MarkdownProcessor,
|
||||||
} from '@astrojs/markdown-remark/dist/internal.js';
|
} from '@astrojs/markdown-remark';
|
||||||
import matter from 'gray-matter';
|
import matter from 'gray-matter';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
@ -57,9 +57,14 @@ const astroErrorModulePath = normalizePath(
|
||||||
);
|
);
|
||||||
|
|
||||||
export default function markdown({ settings, logger }: AstroPluginOptions): Plugin {
|
export default function markdown({ settings, logger }: AstroPluginOptions): Plugin {
|
||||||
|
let processor: MarkdownProcessor;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
enforce: 'pre',
|
enforce: 'pre',
|
||||||
name: 'astro:markdown',
|
name: 'astro:markdown',
|
||||||
|
async buildStart() {
|
||||||
|
processor = await createMarkdownProcessor(settings.config.markdown);
|
||||||
|
},
|
||||||
// Why not the "transform" hook instead of "load" + readFile?
|
// Why not the "transform" hook instead of "load" + readFile?
|
||||||
// A: Vite transforms all "import.meta.env" references to their values before
|
// 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
|
// passing to the transform hook. This lets us get the truly raw value
|
||||||
|
@ -70,33 +75,32 @@ export default function markdown({ settings, logger }: AstroPluginOptions): Plug
|
||||||
const rawFile = await fs.promises.readFile(fileId, 'utf-8');
|
const rawFile = await fs.promises.readFile(fileId, 'utf-8');
|
||||||
const raw = safeMatter(rawFile, id);
|
const raw = safeMatter(rawFile, id);
|
||||||
|
|
||||||
const renderResult = await renderMarkdown(raw.content, {
|
const renderResult = await processor
|
||||||
...settings.config.markdown,
|
.render(raw.content, {
|
||||||
fileURL: new URL(`file://${fileId}`),
|
fileURL: new URL(`file://${fileId}`),
|
||||||
frontmatter: raw.data,
|
frontmatter: raw.data,
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
// Improve error message for invalid astro data
|
||||||
|
if (err instanceof InvalidAstroDataError) {
|
||||||
|
throw new AstroError(AstroErrorData.InvalidFrontmatterInjectionError);
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
});
|
});
|
||||||
|
|
||||||
let html = renderResult.code;
|
let html = renderResult.code;
|
||||||
const { headings } = renderResult.metadata;
|
const { headings, imagePaths: rawImagePaths, frontmatter } = renderResult.metadata;
|
||||||
|
|
||||||
// Resolve all the extracted images from the content
|
// Resolve all the extracted images from the content
|
||||||
let imagePaths: { raw: string; resolved: string }[] = [];
|
const imagePaths: { raw: string; resolved: string }[] = [];
|
||||||
if (renderResult.vfile.data.imagePaths) {
|
for (const imagePath of rawImagePaths.values()) {
|
||||||
for (let imagePath of renderResult.vfile.data.imagePaths.values()) {
|
|
||||||
imagePaths.push({
|
imagePaths.push({
|
||||||
raw: imagePath,
|
raw: imagePath,
|
||||||
resolved:
|
resolved:
|
||||||
(await this.resolve(imagePath, id))?.id ?? path.join(path.dirname(id), imagePath),
|
(await this.resolve(imagePath, id))?.id ?? path.join(path.dirname(id), imagePath),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const astroData = safelyGetAstroData(renderResult.vfile.data);
|
|
||||||
if (astroData instanceof InvalidAstroDataError) {
|
|
||||||
throw new AstroError(AstroErrorData.InvalidFrontmatterInjectionError);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { frontmatter } = astroData;
|
|
||||||
const { layout } = frontmatter;
|
const { layout } = frontmatter;
|
||||||
|
|
||||||
if (frontmatter.setup) {
|
if (frontmatter.setup) {
|
||||||
|
|
|
@ -27,6 +27,13 @@ export function safelyGetAstroData(vfileData: Data): MarkdownAstroData | Invalid
|
||||||
return astro;
|
return astro;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setAstroData(vfileData: Data, astroData: MarkdownAstroData) {
|
||||||
|
vfileData.astro = astroData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use `setAstroData` instead
|
||||||
|
*/
|
||||||
export function toRemarkInitializeAstroData({
|
export function toRemarkInitializeAstroData({
|
||||||
userFrontmatter,
|
userFrontmatter,
|
||||||
}: {
|
}: {
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
import type {
|
import type {
|
||||||
AstroMarkdownOptions,
|
AstroMarkdownOptions,
|
||||||
|
MarkdownProcessor,
|
||||||
MarkdownRenderingOptions,
|
MarkdownRenderingOptions,
|
||||||
MarkdownRenderingResult,
|
MarkdownRenderingResult,
|
||||||
MarkdownVFile,
|
MarkdownVFile,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
|
|
||||||
import { toRemarkInitializeAstroData } from './frontmatter-injection.js';
|
import {
|
||||||
|
InvalidAstroDataError,
|
||||||
|
safelyGetAstroData,
|
||||||
|
setAstroData,
|
||||||
|
} from './frontmatter-injection.js';
|
||||||
import { loadPlugins } from './load-plugins.js';
|
import { loadPlugins } from './load-plugins.js';
|
||||||
import { rehypeHeadingIds } from './rehype-collect-headings.js';
|
import { rehypeHeadingIds } from './rehype-collect-headings.js';
|
||||||
import { remarkCollectImages } from './remark-collect-images.js';
|
import { remarkCollectImages } from './remark-collect-images.js';
|
||||||
|
@ -15,13 +20,14 @@ import { remarkShiki } from './remark-shiki.js';
|
||||||
import rehypeRaw from 'rehype-raw';
|
import rehypeRaw from 'rehype-raw';
|
||||||
import rehypeStringify from 'rehype-stringify';
|
import rehypeStringify from 'rehype-stringify';
|
||||||
import remarkGfm from 'remark-gfm';
|
import remarkGfm from 'remark-gfm';
|
||||||
import markdown from 'remark-parse';
|
import remarkParse from 'remark-parse';
|
||||||
import markdownToHtml from 'remark-rehype';
|
import remarkRehype from 'remark-rehype';
|
||||||
import remarkSmartypants from 'remark-smartypants';
|
import remarkSmartypants from 'remark-smartypants';
|
||||||
import { unified } from 'unified';
|
import { unified } from 'unified';
|
||||||
import { VFile } from 'vfile';
|
import { VFile } from 'vfile';
|
||||||
import { rehypeImages } from './rehype-images.js';
|
import { rehypeImages } from './rehype-images.js';
|
||||||
|
|
||||||
|
export { InvalidAstroDataError } from './frontmatter-injection.js';
|
||||||
export { rehypeHeadingIds } from './rehype-collect-headings.js';
|
export { rehypeHeadingIds } from './rehype-collect-headings.js';
|
||||||
export { remarkCollectImages } from './remark-collect-images.js';
|
export { remarkCollectImages } from './remark-collect-images.js';
|
||||||
export { remarkPrism } from './remark-prism.js';
|
export { remarkPrism } from './remark-prism.js';
|
||||||
|
@ -45,30 +51,29 @@ export const markdownConfigDefaults: Omit<Required<AstroMarkdownOptions>, 'draft
|
||||||
// Skip nonessential plugins during performance benchmark runs
|
// Skip nonessential plugins during performance benchmark runs
|
||||||
const isPerformanceBenchmark = Boolean(process.env.ASTRO_PERFORMANCE_BENCHMARK);
|
const isPerformanceBenchmark = Boolean(process.env.ASTRO_PERFORMANCE_BENCHMARK);
|
||||||
|
|
||||||
/** Shared utility for rendering markdown */
|
/**
|
||||||
export async function renderMarkdown(
|
* Create a markdown preprocessor to render multiple markdown files
|
||||||
content: string,
|
*/
|
||||||
opts: MarkdownRenderingOptions
|
export async function createMarkdownProcessor(
|
||||||
): Promise<MarkdownRenderingResult> {
|
opts?: AstroMarkdownOptions
|
||||||
let {
|
): Promise<MarkdownProcessor> {
|
||||||
fileURL,
|
const {
|
||||||
syntaxHighlight = markdownConfigDefaults.syntaxHighlight,
|
syntaxHighlight = markdownConfigDefaults.syntaxHighlight,
|
||||||
shikiConfig = markdownConfigDefaults.shikiConfig,
|
shikiConfig = markdownConfigDefaults.shikiConfig,
|
||||||
remarkPlugins = markdownConfigDefaults.remarkPlugins,
|
remarkPlugins = markdownConfigDefaults.remarkPlugins,
|
||||||
rehypePlugins = markdownConfigDefaults.rehypePlugins,
|
rehypePlugins = markdownConfigDefaults.rehypePlugins,
|
||||||
remarkRehype = markdownConfigDefaults.remarkRehype,
|
remarkRehype: remarkRehypeOptions = markdownConfigDefaults.remarkRehype,
|
||||||
gfm = markdownConfigDefaults.gfm,
|
gfm = markdownConfigDefaults.gfm,
|
||||||
smartypants = markdownConfigDefaults.smartypants,
|
smartypants = markdownConfigDefaults.smartypants,
|
||||||
frontmatter: userFrontmatter = {},
|
} = opts ?? {};
|
||||||
} = opts;
|
|
||||||
const input = new VFile({ value: content, path: fileURL });
|
|
||||||
|
|
||||||
let parser = unified()
|
const loadedRemarkPlugins = await Promise.all(loadPlugins(remarkPlugins));
|
||||||
.use(markdown)
|
const loadedRehypePlugins = await Promise.all(loadPlugins(rehypePlugins));
|
||||||
.use(toRemarkInitializeAstroData({ userFrontmatter }))
|
|
||||||
.use([]);
|
|
||||||
|
|
||||||
if (!isPerformanceBenchmark && gfm) {
|
const parser = unified().use(remarkParse);
|
||||||
|
|
||||||
|
// gfm and smartypants
|
||||||
|
if (!isPerformanceBenchmark) {
|
||||||
if (gfm) {
|
if (gfm) {
|
||||||
parser.use(remarkGfm);
|
parser.use(remarkGfm);
|
||||||
}
|
}
|
||||||
|
@ -77,14 +82,13 @@ export async function renderMarkdown(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadedRemarkPlugins = await Promise.all(loadPlugins(remarkPlugins));
|
// User remark plugins
|
||||||
const loadedRehypePlugins = await Promise.all(loadPlugins(rehypePlugins));
|
for (const [plugin, pluginOpts] of loadedRemarkPlugins) {
|
||||||
|
parser.use(plugin, pluginOpts);
|
||||||
loadedRemarkPlugins.forEach(([plugin, pluginOpts]) => {
|
}
|
||||||
parser.use([[plugin, pluginOpts]]);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isPerformanceBenchmark) {
|
if (!isPerformanceBenchmark) {
|
||||||
|
// Syntax highlighting
|
||||||
if (syntaxHighlight === 'shiki') {
|
if (syntaxHighlight === 'shiki') {
|
||||||
parser.use(remarkShiki, shikiConfig);
|
parser.use(remarkShiki, shikiConfig);
|
||||||
} else if (syntaxHighlight === 'prism') {
|
} else if (syntaxHighlight === 'prism') {
|
||||||
|
@ -95,45 +99,88 @@ export async function renderMarkdown(
|
||||||
parser.use(remarkCollectImages);
|
parser.use(remarkCollectImages);
|
||||||
}
|
}
|
||||||
|
|
||||||
parser.use([
|
// Remark -> Rehype
|
||||||
[
|
parser.use(remarkRehype as any, {
|
||||||
markdownToHtml as any,
|
|
||||||
{
|
|
||||||
allowDangerousHtml: true,
|
allowDangerousHtml: true,
|
||||||
passThrough: [],
|
passThrough: [],
|
||||||
...remarkRehype,
|
...remarkRehypeOptions,
|
||||||
},
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
|
|
||||||
loadedRehypePlugins.forEach(([plugin, pluginOpts]) => {
|
|
||||||
parser.use([[plugin, pluginOpts]]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
parser.use(rehypeImages());
|
// User rehype plugins
|
||||||
if (!isPerformanceBenchmark) {
|
for (const [plugin, pluginOpts] of loadedRehypePlugins) {
|
||||||
parser.use([rehypeHeadingIds]);
|
parser.use(plugin, pluginOpts);
|
||||||
}
|
}
|
||||||
|
|
||||||
parser.use([rehypeRaw]).use(rehypeStringify, { allowDangerousHtml: true });
|
// Images / Assets support
|
||||||
|
parser.use(rehypeImages());
|
||||||
|
|
||||||
let vfile: MarkdownVFile;
|
// Headings
|
||||||
try {
|
if (!isPerformanceBenchmark) {
|
||||||
vfile = await parser.process(input);
|
parser.use(rehypeHeadingIds);
|
||||||
} catch (err) {
|
}
|
||||||
|
|
||||||
|
// Stringify to HTML
|
||||||
|
parser.use(rehypeRaw).use(rehypeStringify, { allowDangerousHtml: true });
|
||||||
|
|
||||||
|
return {
|
||||||
|
async render(content, renderOpts) {
|
||||||
|
const vfile = new VFile({ value: content, path: renderOpts?.fileURL });
|
||||||
|
setAstroData(vfile.data, { frontmatter: renderOpts?.frontmatter ?? {} });
|
||||||
|
|
||||||
|
const result: MarkdownVFile = await parser.process(vfile).catch((err) => {
|
||||||
// Ensure that the error message contains the input filename
|
// Ensure that the error message contains the input filename
|
||||||
// to make it easier for the user to fix the issue
|
// to make it easier for the user to fix the issue
|
||||||
err = prefixError(err, `Failed to parse Markdown file "${input.path}"`);
|
err = prefixError(err, `Failed to parse Markdown file "${vfile.path}"`);
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.error(err);
|
console.error(err);
|
||||||
throw err;
|
throw err;
|
||||||
|
});
|
||||||
|
|
||||||
|
const astroData = safelyGetAstroData(result.data);
|
||||||
|
if (astroData instanceof InvalidAstroDataError) {
|
||||||
|
throw astroData;
|
||||||
}
|
}
|
||||||
|
|
||||||
const headings = vfile?.data.__astroHeadings || [];
|
|
||||||
return {
|
return {
|
||||||
metadata: { headings, source: content, html: String(vfile.value) },
|
code: String(result.value),
|
||||||
code: String(vfile.value),
|
metadata: {
|
||||||
vfile,
|
headings: result.data.__astroHeadings ?? [],
|
||||||
|
imagePaths: result.data.imagePaths ?? new Set(),
|
||||||
|
frontmatter: astroData.frontmatter ?? {},
|
||||||
|
},
|
||||||
|
// Compat for `renderMarkdown` only. Do not use!
|
||||||
|
__renderMarkdownCompat: {
|
||||||
|
result,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared utility for rendering markdown
|
||||||
|
*
|
||||||
|
* @deprecated Use `createMarkdownProcessor` instead for better performance
|
||||||
|
*/
|
||||||
|
export async function renderMarkdown(
|
||||||
|
content: string,
|
||||||
|
opts: MarkdownRenderingOptions
|
||||||
|
): Promise<MarkdownRenderingResult> {
|
||||||
|
const processor = await createMarkdownProcessor(opts);
|
||||||
|
|
||||||
|
const result = await processor.render(content, {
|
||||||
|
fileURL: opts.fileURL,
|
||||||
|
frontmatter: opts.frontmatter,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: result.code,
|
||||||
|
metadata: {
|
||||||
|
headings: result.metadata.headings,
|
||||||
|
source: content,
|
||||||
|
html: result.code,
|
||||||
|
},
|
||||||
|
vfile: (result as any).__renderMarkdownCompat.result,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
export {
|
export {
|
||||||
InvalidAstroDataError,
|
InvalidAstroDataError,
|
||||||
safelyGetAstroData,
|
safelyGetAstroData,
|
||||||
|
setAstroData,
|
||||||
toRemarkInitializeAstroData,
|
toRemarkInitializeAstroData,
|
||||||
} from './frontmatter-injection.js';
|
} from './frontmatter-injection.js';
|
||||||
|
|
|
@ -14,7 +14,7 @@ async function importPlugin(p: string | unified.Plugin): Promise<unified.Plugin>
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
// Try import from user project
|
// Try import from user project
|
||||||
const resolved = await importMetaResolve(p, cwdUrlStr);
|
const resolved = importMetaResolve(p, cwdUrlStr);
|
||||||
const importResult = await import(resolved);
|
const importResult = await import(resolved);
|
||||||
return importResult.default;
|
return importResult.default;
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,13 +58,33 @@ export interface ImageMetadata {
|
||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MarkdownRenderingOptions extends AstroMarkdownOptions {
|
export interface MarkdownProcessor {
|
||||||
|
render: (
|
||||||
|
content: string,
|
||||||
|
opts?: MarkdownProcessorRenderOptions
|
||||||
|
) => Promise<MarkdownProcessorRenderResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MarkdownProcessorRenderOptions {
|
||||||
/** @internal */
|
/** @internal */
|
||||||
fileURL?: URL;
|
fileURL?: URL;
|
||||||
/** Used for frontmatter injection plugins */
|
/** Used for frontmatter injection plugins */
|
||||||
frontmatter?: Record<string, any>;
|
frontmatter?: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MarkdownProcessorRenderResult {
|
||||||
|
code: string;
|
||||||
|
metadata: {
|
||||||
|
headings: MarkdownHeading[];
|
||||||
|
imagePaths: Set<string>;
|
||||||
|
frontmatter: Record<string, any>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MarkdownRenderingOptions
|
||||||
|
extends AstroMarkdownOptions,
|
||||||
|
MarkdownProcessorRenderOptions {}
|
||||||
|
|
||||||
export interface MarkdownHeading {
|
export interface MarkdownHeading {
|
||||||
depth: number;
|
depth: number;
|
||||||
slug: string;
|
slug: string;
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
import { renderMarkdown } from '../dist/index.js';
|
import { createMarkdownProcessor } from '../dist/index.js';
|
||||||
import chai from 'chai';
|
import chai from 'chai';
|
||||||
import { mockRenderMarkdownParams } from './test-utils.js';
|
|
||||||
|
|
||||||
describe('autolinking', () => {
|
describe('autolinking', () => {
|
||||||
describe('plain md', () => {
|
describe('plain md', async () => {
|
||||||
|
const processor = await createMarkdownProcessor();
|
||||||
|
|
||||||
it('autolinks URLs starting with a protocol in plain text', async () => {
|
it('autolinks URLs starting with a protocol in plain text', async () => {
|
||||||
const { code } = await renderMarkdown(
|
const { code } = await processor.render(`See https://example.com for more.`);
|
||||||
`See https://example.com for more.`,
|
|
||||||
mockRenderMarkdownParams
|
|
||||||
);
|
|
||||||
|
|
||||||
chai
|
chai
|
||||||
.expect(code.replace(/\n/g, ''))
|
.expect(code.replace(/\n/g, ''))
|
||||||
|
@ -16,10 +14,7 @@ describe('autolinking', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('autolinks URLs starting with "www." in plain text', async () => {
|
it('autolinks URLs starting with "www." in plain text', async () => {
|
||||||
const { code } = await renderMarkdown(
|
const { code } = await processor.render(`See www.example.com for more.`);
|
||||||
`See www.example.com for more.`,
|
|
||||||
mockRenderMarkdownParams
|
|
||||||
);
|
|
||||||
|
|
||||||
chai
|
chai
|
||||||
.expect(code.trim())
|
.expect(code.trim())
|
||||||
|
@ -27,9 +22,8 @@ describe('autolinking', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not autolink URLs in code blocks', async () => {
|
it('does not autolink URLs in code blocks', async () => {
|
||||||
const { code } = await renderMarkdown(
|
const { code } = await processor.render(
|
||||||
'See `https://example.com` or `www.example.com` for more.',
|
'See `https://example.com` or `www.example.com` for more.'
|
||||||
mockRenderMarkdownParams
|
|
||||||
);
|
);
|
||||||
|
|
||||||
chai
|
chai
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import { renderMarkdown } from '../dist/index.js';
|
import { createMarkdownProcessor } from '../dist/index.js';
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { mockRenderMarkdownParams } from './test-utils.js';
|
|
||||||
|
|
||||||
describe('entities', () => {
|
describe('entities', async () => {
|
||||||
|
const processor = await createMarkdownProcessor();
|
||||||
|
|
||||||
it('should not unescape entities in regular Markdown', async () => {
|
it('should not unescape entities in regular Markdown', async () => {
|
||||||
const { code } = await renderMarkdown(
|
const { code } = await processor.render(`<i>This should NOT be italic</i>`);
|
||||||
`<i>This should NOT be italic</i>`,
|
|
||||||
mockRenderMarkdownParams
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(code).to.equal(`<p><i>This should NOT be italic</i></p>`);
|
expect(code).to.equal(`<p><i>This should NOT be italic</i></p>`);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { renderMarkdown } from '../dist/index.js';
|
import { createMarkdownProcessor } from '../dist/index.js';
|
||||||
import { mockRenderMarkdownParams } from './test-utils.js';
|
|
||||||
import chai from 'chai';
|
import chai from 'chai';
|
||||||
|
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
|
@ -8,9 +7,8 @@ describe('plugins', () => {
|
||||||
// https://github.com/withastro/astro/issues/3264
|
// https://github.com/withastro/astro/issues/3264
|
||||||
it('should be able to get file path when passing fileURL', async () => {
|
it('should be able to get file path when passing fileURL', async () => {
|
||||||
let context;
|
let context;
|
||||||
await renderMarkdown(`test`, {
|
|
||||||
...mockRenderMarkdownParams,
|
const processor = await createMarkdownProcessor({
|
||||||
fileURL: new URL('virtual.md', import.meta.url),
|
|
||||||
remarkPlugins: [
|
remarkPlugins: [
|
||||||
function () {
|
function () {
|
||||||
const transformer = (tree, file) => {
|
const transformer = (tree, file) => {
|
||||||
|
@ -22,6 +20,10 @@ describe('plugins', () => {
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await processor.render(`test`, {
|
||||||
|
fileURL: new URL('virtual.md', import.meta.url),
|
||||||
|
});
|
||||||
|
|
||||||
chai.expect(typeof context).to.equal('object');
|
chai.expect(typeof context).to.equal('object');
|
||||||
chai.expect(context.path).to.equal(fileURLToPath(new URL('virtual.md', import.meta.url)));
|
chai.expect(context.path).to.equal(fileURLToPath(new URL('virtual.md', import.meta.url)));
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,28 +1,33 @@
|
||||||
import { renderMarkdown } from '../dist/index.js';
|
import { createMarkdownProcessor } from '../dist/index.js';
|
||||||
import { mockRenderMarkdownParams } from './test-utils.js';
|
|
||||||
import chai from 'chai';
|
import chai from 'chai';
|
||||||
|
|
||||||
describe('collect images', () => {
|
describe('collect images', async () => {
|
||||||
|
const processor = await createMarkdownProcessor();
|
||||||
|
|
||||||
it('should collect inline image paths', async () => {
|
it('should collect inline image paths', async () => {
|
||||||
const { code, vfile } = await renderMarkdown(
|
const {
|
||||||
`Hello ![inline image url](./img.png)`,
|
code,
|
||||||
mockRenderMarkdownParams
|
metadata: { imagePaths },
|
||||||
);
|
} = await processor.render(`Hello ![inline image url](./img.png)`, {
|
||||||
|
fileURL: 'file.md',
|
||||||
|
});
|
||||||
|
|
||||||
chai
|
chai
|
||||||
.expect(code)
|
.expect(code)
|
||||||
.to.equal('<p>Hello <img alt="inline image url" __ASTRO_IMAGE_="./img.png"></p>');
|
.to.equal('<p>Hello <img alt="inline image url" __ASTRO_IMAGE_="./img.png"></p>');
|
||||||
|
|
||||||
chai.expect(Array.from(vfile.data.imagePaths)).to.deep.equal(['./img.png']);
|
chai.expect(Array.from(imagePaths)).to.deep.equal(['./img.png']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add image paths from definition', async () => {
|
it('should add image paths from definition', async () => {
|
||||||
const { code, vfile } = await renderMarkdown(
|
const {
|
||||||
`Hello ![image ref][img-ref]\n\n[img-ref]: ./img.webp`,
|
code,
|
||||||
mockRenderMarkdownParams
|
metadata: { imagePaths },
|
||||||
);
|
} = await processor.render(`Hello ![image ref][img-ref]\n\n[img-ref]: ./img.webp`, {
|
||||||
|
fileURL: 'file.md',
|
||||||
|
});
|
||||||
|
|
||||||
chai.expect(code).to.equal('<p>Hello <img alt="image ref" __ASTRO_IMAGE_="./img.webp"></p>');
|
chai.expect(code).to.equal('<p>Hello <img alt="image ref" __ASTRO_IMAGE_="./img.webp"></p>');
|
||||||
chai.expect(Array.from(vfile.data.imagePaths)).to.deep.equal(['./img.webp']);
|
chai.expect(Array.from(imagePaths)).to.deep.equal(['./img.webp']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
export const mockRenderMarkdownParams = {
|
|
||||||
fileURL: 'file.md',
|
|
||||||
contentDir: new URL('file:///src/content/'),
|
|
||||||
};
|
|
Loading…
Reference in a new issue