feat: markdown config typechecking (#2970)
* Added schemas to markdown plugin * Added new schemas to main package * Changesets * typeraw * Explaination about the weird type hack * Added markdown.mode to config * Added comment * Formatted * Moved validation to `astro` and added RemarkPlugin ad RehypePlugin * Removed the ability to have a custom markdown renderer internally * Fixed plugin type * Removed unused renderMarkdownWithFrontmatter * Added missing dependency * Dynamically import astro markdown * Cache import
This commit is contained in:
parent
11766acec9
commit
b835e285de
19 changed files with 150 additions and 141 deletions
5
.changeset/forty-plants-hope.md
Normal file
5
.changeset/forty-plants-hope.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'@astrojs/markdown-remark': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Improved type checking
|
5
.changeset/small-months-speak.md
Normal file
5
.changeset/small-months-speak.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Improved markdown config type checking
|
|
@ -100,6 +100,7 @@
|
||||||
"execa": "^6.1.0",
|
"execa": "^6.1.0",
|
||||||
"fast-glob": "^3.2.11",
|
"fast-glob": "^3.2.11",
|
||||||
"fast-xml-parser": "^4.0.7",
|
"fast-xml-parser": "^4.0.7",
|
||||||
|
"gray-matter": "^4.0.3",
|
||||||
"html-entities": "^2.3.3",
|
"html-entities": "^2.3.3",
|
||||||
"html-escaper": "^3.0.3",
|
"html-escaper": "^3.0.3",
|
||||||
"htmlparser2": "^7.2.0",
|
"htmlparser2": "^7.2.0",
|
||||||
|
@ -156,6 +157,7 @@
|
||||||
"@types/resolve": "^1.20.1",
|
"@types/resolve": "^1.20.1",
|
||||||
"@types/rimraf": "^3.0.2",
|
"@types/rimraf": "^3.0.2",
|
||||||
"@types/send": "^0.17.1",
|
"@types/send": "^0.17.1",
|
||||||
|
"@types/unist": "^2.0.6",
|
||||||
"@types/yargs-parser": "^21.0.0",
|
"@types/yargs-parser": "^21.0.0",
|
||||||
"astro-scripts": "workspace:*",
|
"astro-scripts": "workspace:*",
|
||||||
"chai": "^4.3.6",
|
"chai": "^4.3.6",
|
||||||
|
|
|
@ -2,7 +2,7 @@ import type { AddressInfo } from 'net';
|
||||||
import type * as babel from '@babel/core';
|
import type * as babel from '@babel/core';
|
||||||
import type * as vite from 'vite';
|
import type * as vite from 'vite';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import type { ShikiConfig, Plugin } from '@astrojs/markdown-remark';
|
import type { ShikiConfig, RemarkPlugins, RehypePlugins } from '@astrojs/markdown-remark';
|
||||||
import type { AstroConfigSchema } from '../core/config';
|
import type { AstroConfigSchema } from '../core/config';
|
||||||
import type { AstroComponentFactory, Metadata } from '../runtime/server';
|
import type { AstroComponentFactory, Metadata } from '../runtime/server';
|
||||||
import type { ViteConfigWithSSR } from '../core/create-vite';
|
import type { ViteConfigWithSSR } from '../core/create-vite';
|
||||||
|
@ -481,14 +481,24 @@ export interface AstroUserConfig {
|
||||||
*/
|
*/
|
||||||
drafts?: boolean;
|
drafts?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @docs
|
||||||
|
* @name markdown.mode
|
||||||
|
* @type {'md' | 'mdx'}
|
||||||
|
* @default `mdx`
|
||||||
|
* @description
|
||||||
|
* Control wheater to allow components inside markdown files ('mdx') or not ('md').
|
||||||
|
*/
|
||||||
|
mode?: 'md' | 'mdx';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @docs
|
* @docs
|
||||||
* @name markdown.shikiConfig
|
* @name markdown.shikiConfig
|
||||||
* @type {ShikiConfig}
|
* @typeraw {Partial<ShikiConfig>}
|
||||||
* @description
|
* @description
|
||||||
* Shiki configuration options. See [the markdown configuration docs](https://docs.astro.build/en/guides/markdown-content/#shiki-configuration) for usage.
|
* Shiki configuration options. See [the markdown configuration docs](https://docs.astro.build/en/guides/markdown-content/#shiki-configuration) for usage.
|
||||||
*/
|
*/
|
||||||
shikiConfig?: ShikiConfig;
|
shikiConfig?: Partial<ShikiConfig>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @docs
|
* @docs
|
||||||
|
@ -515,7 +525,7 @@ export interface AstroUserConfig {
|
||||||
/**
|
/**
|
||||||
* @docs
|
* @docs
|
||||||
* @name markdown.remarkPlugins
|
* @name markdown.remarkPlugins
|
||||||
* @type {Plugin[]}
|
* @type {RemarkPlugins}
|
||||||
* @description
|
* @description
|
||||||
* Pass a custom [Remark](https://github.com/remarkjs/remark) plugin to customize how your Markdown is built.
|
* Pass a custom [Remark](https://github.com/remarkjs/remark) plugin to customize how your Markdown is built.
|
||||||
*
|
*
|
||||||
|
@ -530,11 +540,11 @@ export interface AstroUserConfig {
|
||||||
* };
|
* };
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
remarkPlugins?: Plugin[];
|
remarkPlugins?: RemarkPlugins;
|
||||||
/**
|
/**
|
||||||
* @docs
|
* @docs
|
||||||
* @name markdown.rehypePlugins
|
* @name markdown.rehypePlugins
|
||||||
* @type {Plugin[]}
|
* @type {RehypePlugins}
|
||||||
* @description
|
* @description
|
||||||
* Pass a custom [Rehype](https://github.com/remarkjs/remark-rehype) plugin to customize how your Markdown is built.
|
* Pass a custom [Rehype](https://github.com/remarkjs/remark-rehype) plugin to customize how your Markdown is built.
|
||||||
*
|
*
|
||||||
|
@ -549,7 +559,7 @@ export interface AstroUserConfig {
|
||||||
* };
|
* };
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
rehypePlugins?: Plugin[];
|
rehypePlugins?: RehypePlugins;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -757,12 +767,6 @@ export interface ManifestData {
|
||||||
routes: RouteData[];
|
routes: RouteData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MarkdownRenderOptions = [string | MarkdownParser, Record<string, any>];
|
|
||||||
export type MarkdownParser = (
|
|
||||||
contents: string,
|
|
||||||
options?: Record<string, any>
|
|
||||||
) => MarkdownParserResponse | PromiseLike<MarkdownParserResponse>;
|
|
||||||
|
|
||||||
export interface MarkdownParserResponse {
|
export interface MarkdownParserResponse {
|
||||||
frontmatter: {
|
frontmatter: {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
|
|
|
@ -82,7 +82,7 @@ export class App {
|
||||||
legacyBuild: false,
|
legacyBuild: false,
|
||||||
links,
|
links,
|
||||||
logging: this.#logging,
|
logging: this.#logging,
|
||||||
markdownRender: manifest.markdown.render,
|
markdown: manifest.markdown,
|
||||||
mod,
|
mod,
|
||||||
origin: url.origin,
|
origin: url.origin,
|
||||||
pathname: url.pathname,
|
pathname: url.pathname,
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import type {
|
import type {
|
||||||
RouteData,
|
RouteData,
|
||||||
SerializedRouteData,
|
SerializedRouteData,
|
||||||
MarkdownRenderOptions,
|
|
||||||
ComponentInstance,
|
ComponentInstance,
|
||||||
SSRLoadedRenderer,
|
SSRLoadedRenderer,
|
||||||
} from '../../@types/astro';
|
} from '../../@types/astro';
|
||||||
|
import type { MarkdownRenderingOptions } from '@astrojs/markdown-remark';
|
||||||
|
|
||||||
export type ComponentPath = string;
|
export type ComponentPath = string;
|
||||||
|
|
||||||
|
@ -22,9 +22,7 @@ export type SerializedRouteInfo = Omit<RouteInfo, 'routeData'> & {
|
||||||
export interface SSRManifest {
|
export interface SSRManifest {
|
||||||
routes: RouteInfo[];
|
routes: RouteInfo[];
|
||||||
site?: string;
|
site?: string;
|
||||||
markdown: {
|
markdown: MarkdownRenderingOptions;
|
||||||
render: MarkdownRenderOptions;
|
|
||||||
};
|
|
||||||
pageMap: Map<ComponentPath, ComponentInstance>;
|
pageMap: Map<ComponentPath, ComponentInstance>;
|
||||||
renderers: SSRLoadedRenderer[];
|
renderers: SSRLoadedRenderer[];
|
||||||
entryModules: Record<string, string>;
|
entryModules: Record<string, string>;
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import astroRemark from '@astrojs/markdown-remark';
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { bgGreen, black, cyan, dim, green, magenta } from 'kleur/colors';
|
import { bgGreen, black, cyan, dim, green, magenta } from 'kleur/colors';
|
||||||
import npath from 'path';
|
import npath from 'path';
|
||||||
|
@ -197,7 +196,7 @@ async function generatePath(
|
||||||
legacyBuild: false,
|
legacyBuild: false,
|
||||||
links,
|
links,
|
||||||
logging,
|
logging,
|
||||||
markdownRender: [astroRemark, astroConfig.markdown],
|
markdown: astroConfig.markdown,
|
||||||
mod,
|
mod,
|
||||||
origin,
|
origin,
|
||||||
pathname,
|
pathname,
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import astroRemark from '@astrojs/markdown-remark';
|
|
||||||
import type { Plugin as VitePlugin } from 'vite';
|
import type { Plugin as VitePlugin } from 'vite';
|
||||||
import type { BuildInternals } from './internal.js';
|
import type { BuildInternals } from './internal.js';
|
||||||
import type { AstroAdapter } from '../../@types/astro';
|
import type { AstroAdapter } from '../../@types/astro';
|
||||||
|
@ -110,9 +109,7 @@ function buildManifest(opts: StaticBuildOptions, internals: BuildInternals): Ser
|
||||||
const ssrManifest: SerializedSSRManifest = {
|
const ssrManifest: SerializedSSRManifest = {
|
||||||
routes,
|
routes,
|
||||||
site: astroConfig.site,
|
site: astroConfig.site,
|
||||||
markdown: {
|
markdown: astroConfig.markdown,
|
||||||
render: [astroRemark, astroConfig.markdown],
|
|
||||||
},
|
|
||||||
pageMap: null as any,
|
pageMap: null as any,
|
||||||
renderers: [],
|
renderers: [],
|
||||||
entryModules,
|
entryModules,
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
import type { AstroConfig, AstroUserConfig, CLIFlags } from '../@types/astro';
|
import type { AstroConfig, AstroUserConfig, CLIFlags } from '../@types/astro';
|
||||||
import type { Arguments as Flags } from 'yargs-parser';
|
import type { Arguments as Flags } from 'yargs-parser';
|
||||||
import type * as Postcss from 'postcss';
|
import type * as Postcss from 'postcss';
|
||||||
|
import type { ILanguageRegistration, IThemeRegistration, Theme } from 'shiki';
|
||||||
|
import type { RemarkPlugin, RehypePlugin } from '@astrojs/markdown-remark';
|
||||||
|
|
||||||
import * as colors from 'kleur/colors';
|
import * as colors from 'kleur/colors';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { pathToFileURL, fileURLToPath } from 'url';
|
import { pathToFileURL, fileURLToPath } from 'url';
|
||||||
import { mergeConfig as mergeViteConfig } from 'vite';
|
import { mergeConfig as mergeViteConfig } from 'vite';
|
||||||
|
import { BUNDLED_THEMES } from 'shiki';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import load, { ProloadError } from '@proload/core';
|
import load, { ProloadError } from '@proload/core';
|
||||||
import loadTypeScript from '@proload/plugin-tsm';
|
import loadTypeScript from '@proload/plugin-tsm';
|
||||||
|
@ -142,24 +145,42 @@ export const AstroConfigSchema = z.object({
|
||||||
.default({}),
|
.default({}),
|
||||||
markdown: z
|
markdown: z
|
||||||
.object({
|
.object({
|
||||||
drafts: z.boolean().optional().default(false),
|
|
||||||
mode: z
|
|
||||||
.union([z.literal('md'), z.literal('mdx')])
|
|
||||||
.optional()
|
|
||||||
// NOTE: "mdx" allows us to parse/compile Astro components in markdown.
|
// NOTE: "mdx" allows us to parse/compile Astro components in markdown.
|
||||||
// TODO: This should probably be updated to something more like "md" | "astro"
|
// TODO: This should probably be updated to something more like "md" | "astro"
|
||||||
.default('mdx'),
|
mode: z.enum(['md', 'mdx']).default('mdx'),
|
||||||
|
drafts: z.boolean().default(false),
|
||||||
syntaxHighlight: z
|
syntaxHighlight: z
|
||||||
.union([z.literal('shiki'), z.literal('prism'), z.literal(false)])
|
.union([z.literal('shiki'), z.literal('prism'), z.literal(false)])
|
||||||
.optional()
|
|
||||||
.default('shiki'),
|
.default('shiki'),
|
||||||
// TODO: add better type checking
|
shikiConfig: z
|
||||||
shikiConfig: z.any().optional().default({}),
|
.object({
|
||||||
remarkPlugins: z.array(z.any()).optional().default([]),
|
langs: z.custom<ILanguageRegistration>().array().default([]),
|
||||||
rehypePlugins: z.array(z.any()).optional().default([]),
|
theme: z
|
||||||
|
.enum(BUNDLED_THEMES as [Theme, ...Theme[]])
|
||||||
|
.or(z.custom<IThemeRegistration>())
|
||||||
|
.default('github-dark'),
|
||||||
|
wrap: z.boolean().or(z.null()).default(false),
|
||||||
|
})
|
||||||
|
.default({}),
|
||||||
|
remarkPlugins: z
|
||||||
|
.union([
|
||||||
|
z.string(),
|
||||||
|
z.tuple([z.string(), z.any()]),
|
||||||
|
z.custom<RemarkPlugin>((data) => typeof data === 'function'),
|
||||||
|
z.tuple([z.custom<RemarkPlugin>((data) => typeof data === 'function'), z.any()]),
|
||||||
|
])
|
||||||
|
.array()
|
||||||
|
.default([]),
|
||||||
|
rehypePlugins: z
|
||||||
|
.union([
|
||||||
|
z.string(),
|
||||||
|
z.tuple([z.string(), z.any()]),
|
||||||
|
z.custom<RehypePlugin>((data) => typeof data === 'function'),
|
||||||
|
z.tuple([z.custom<RehypePlugin>((data) => typeof data === 'function'), z.any()]),
|
||||||
|
])
|
||||||
|
.array()
|
||||||
|
.default([]),
|
||||||
})
|
})
|
||||||
.passthrough()
|
|
||||||
.optional()
|
|
||||||
.default({}),
|
.default({}),
|
||||||
vite: z.any().optional().default({}),
|
vite: z.any().optional().default({}),
|
||||||
experimental: z
|
experimental: z
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
import type {
|
import type {
|
||||||
ComponentInstance,
|
ComponentInstance,
|
||||||
EndpointHandler,
|
|
||||||
MarkdownRenderOptions,
|
|
||||||
Params,
|
Params,
|
||||||
Props,
|
Props,
|
||||||
SSRLoadedRenderer,
|
SSRLoadedRenderer,
|
||||||
RouteData,
|
RouteData,
|
||||||
SSRElement,
|
SSRElement,
|
||||||
} from '../../@types/astro';
|
} from '../../@types/astro';
|
||||||
|
import type { MarkdownRenderingOptions } from '@astrojs/markdown-remark';
|
||||||
import type { LogOptions } from '../logger/core.js';
|
import type { LogOptions } from '../logger/core.js';
|
||||||
|
|
||||||
import { renderHead, renderPage } from '../../runtime/server/index.js';
|
import { renderHead, renderPage } from '../../runtime/server/index.js';
|
||||||
|
@ -70,7 +69,7 @@ export interface RenderOptions {
|
||||||
legacyBuild: boolean;
|
legacyBuild: boolean;
|
||||||
logging: LogOptions;
|
logging: LogOptions;
|
||||||
links: Set<SSRElement>;
|
links: Set<SSRElement>;
|
||||||
markdownRender: MarkdownRenderOptions;
|
markdown: MarkdownRenderingOptions;
|
||||||
mod: ComponentInstance;
|
mod: ComponentInstance;
|
||||||
origin: string;
|
origin: string;
|
||||||
pathname: string;
|
pathname: string;
|
||||||
|
@ -92,7 +91,7 @@ export async function render(
|
||||||
links,
|
links,
|
||||||
logging,
|
logging,
|
||||||
origin,
|
origin,
|
||||||
markdownRender,
|
markdown,
|
||||||
mod,
|
mod,
|
||||||
pathname,
|
pathname,
|
||||||
scripts,
|
scripts,
|
||||||
|
@ -132,7 +131,7 @@ export async function render(
|
||||||
legacyBuild,
|
legacyBuild,
|
||||||
links,
|
links,
|
||||||
logging,
|
logging,
|
||||||
markdownRender,
|
markdown,
|
||||||
origin,
|
origin,
|
||||||
params,
|
params,
|
||||||
pathname,
|
pathname,
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import astroRemark from '@astrojs/markdown-remark';
|
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import type * as vite from 'vite';
|
import type * as vite from 'vite';
|
||||||
import type {
|
import type {
|
||||||
|
@ -164,7 +163,7 @@ export async function render(
|
||||||
legacyBuild: isLegacyBuild,
|
legacyBuild: isLegacyBuild,
|
||||||
links,
|
links,
|
||||||
logging,
|
logging,
|
||||||
markdownRender: [astroRemark, astroConfig.markdown],
|
markdown: astroConfig.markdown,
|
||||||
mod,
|
mod,
|
||||||
origin,
|
origin,
|
||||||
pathname,
|
pathname,
|
||||||
|
|
|
@ -2,13 +2,12 @@ import { bold } from 'kleur/colors';
|
||||||
import type {
|
import type {
|
||||||
AstroGlobal,
|
AstroGlobal,
|
||||||
AstroGlobalPartial,
|
AstroGlobalPartial,
|
||||||
MarkdownParser,
|
|
||||||
MarkdownRenderOptions,
|
|
||||||
Params,
|
Params,
|
||||||
SSRElement,
|
SSRElement,
|
||||||
SSRLoadedRenderer,
|
SSRLoadedRenderer,
|
||||||
SSRResult,
|
SSRResult,
|
||||||
} from '../../@types/astro';
|
} from '../../@types/astro';
|
||||||
|
import type { MarkdownRenderingOptions } from '@astrojs/markdown-remark';
|
||||||
import { renderSlot } from '../../runtime/server/index.js';
|
import { renderSlot } from '../../runtime/server/index.js';
|
||||||
import { LogOptions, warn } from '../logger/core.js';
|
import { LogOptions, warn } from '../logger/core.js';
|
||||||
import { createCanonicalURL, isCSSRequest } from './util.js';
|
import { createCanonicalURL, isCSSRequest } from './util.js';
|
||||||
|
@ -26,7 +25,7 @@ export interface CreateResultArgs {
|
||||||
legacyBuild: boolean;
|
legacyBuild: boolean;
|
||||||
logging: LogOptions;
|
logging: LogOptions;
|
||||||
origin: string;
|
origin: string;
|
||||||
markdownRender: MarkdownRenderOptions;
|
markdown: MarkdownRenderingOptions;
|
||||||
params: Params;
|
params: Params;
|
||||||
pathname: string;
|
pathname: string;
|
||||||
renderers: SSRLoadedRenderer[];
|
renderers: SSRLoadedRenderer[];
|
||||||
|
@ -99,8 +98,10 @@ class Slots {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let renderMarkdown: any = null;
|
||||||
|
|
||||||
export function createResult(args: CreateResultArgs): SSRResult {
|
export function createResult(args: CreateResultArgs): SSRResult {
|
||||||
const { legacyBuild, markdownRender, params, pathname, renderers, request, resolve, site } = args;
|
const { legacyBuild, markdown, params, pathname, renderers, request, resolve, site } = args;
|
||||||
|
|
||||||
const url = new URL(request.url);
|
const url = new URL(request.url);
|
||||||
const canonicalURL = createCanonicalURL('.' + pathname, site ?? url.origin);
|
const canonicalURL = createCanonicalURL('.' + pathname, site ?? url.origin);
|
||||||
|
@ -179,31 +180,22 @@ ${extra}`
|
||||||
// Ensure this API is not exposed to users
|
// Ensure this API is not exposed to users
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
writable: false,
|
writable: false,
|
||||||
// TODO: remove 1. markdown parser logic 2. update MarkdownRenderOptions to take a function only
|
// TODO: Remove this hole "Deno" logic once our plugin gets Deno support
|
||||||
// <Markdown> also needs the same `astroConfig.markdownOptions.render` as `.md` pages
|
value: async function (content: string, opts: MarkdownRenderingOptions) {
|
||||||
value: async function (content: string, opts: any) {
|
// @ts-ignore
|
||||||
let [mdRender, renderOpts] = markdownRender;
|
if (typeof Deno !== 'undefined') {
|
||||||
let parser: MarkdownParser | null = null;
|
throw new Error('Markdown is not supported in Deno SSR');
|
||||||
//let renderOpts = {};
|
|
||||||
if (Array.isArray(mdRender)) {
|
|
||||||
renderOpts = mdRender[1];
|
|
||||||
mdRender = mdRender[0];
|
|
||||||
}
|
}
|
||||||
// ['rehype-toc', opts]
|
|
||||||
if (typeof mdRender === 'string') {
|
if (!renderMarkdown) {
|
||||||
const mod: { default: MarkdownParser } = await import(mdRender);
|
// The package is saved in this variable because Vite is too smart
|
||||||
parser = mod.default;
|
// and will try to inline it in buildtime
|
||||||
|
let astroRemark = '@astrojs/markdown-remark';
|
||||||
|
|
||||||
|
renderMarkdown = (await import(astroRemark)).renderMarkdown;
|
||||||
}
|
}
|
||||||
// [import('rehype-toc'), opts]
|
|
||||||
else if (mdRender instanceof Promise) {
|
const { code } = await renderMarkdown(content, { ...markdown, ...(opts ?? {}) });
|
||||||
const mod: { default: MarkdownParser } = await mdRender;
|
|
||||||
parser = mod.default;
|
|
||||||
} else if (typeof mdRender === 'function') {
|
|
||||||
parser = mdRender;
|
|
||||||
} else {
|
|
||||||
throw new Error('No Markdown parser found.');
|
|
||||||
}
|
|
||||||
const { code } = await parser(content, { ...renderOpts, ...(opts ?? {}) });
|
|
||||||
return code;
|
return code;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import astroRemark from '@astrojs/markdown-remark';
|
import { renderMarkdown } from '@astrojs/markdown-remark';
|
||||||
import { transform } from '@astrojs/compiler';
|
import { transform } from '@astrojs/compiler';
|
||||||
import ancestor from 'common-ancestor-path';
|
import ancestor from 'common-ancestor-path';
|
||||||
import esbuild from 'esbuild';
|
import esbuild from 'esbuild';
|
||||||
|
@ -118,7 +118,6 @@ export default function markdown({ config }: AstroPluginOptions): Plugin {
|
||||||
// This returns the compiled markdown -> astro component that renders to HTML.
|
// This returns the compiled markdown -> astro component that renders to HTML.
|
||||||
if (id.endsWith('.md')) {
|
if (id.endsWith('.md')) {
|
||||||
const source = await fs.promises.readFile(id, 'utf8');
|
const source = await fs.promises.readFile(id, 'utf8');
|
||||||
const render = astroRemark;
|
|
||||||
const renderOpts = config.markdown;
|
const renderOpts = config.markdown;
|
||||||
|
|
||||||
const filename = normalizeFilename(id);
|
const filename = normalizeFilename(id);
|
||||||
|
@ -128,7 +127,7 @@ export default function markdown({ config }: AstroPluginOptions): Plugin {
|
||||||
|
|
||||||
// Extract special frontmatter keys
|
// Extract special frontmatter keys
|
||||||
const { data: frontmatter, content: markdownContent } = matter(source);
|
const { data: frontmatter, content: markdownContent } = matter(source);
|
||||||
let renderResult = await render(markdownContent, renderOpts);
|
let renderResult = await renderMarkdown(markdownContent, renderOpts);
|
||||||
let { code: astroResult, metadata } = renderResult;
|
let { code: astroResult, metadata } = renderResult;
|
||||||
const { layout = '', components = '', setup = '', ...content } = frontmatter;
|
const { layout = '', components = '', setup = '', ...content } = frontmatter;
|
||||||
content.astro = metadata;
|
content.astro = metadata;
|
||||||
|
|
|
@ -26,7 +26,6 @@
|
||||||
"@astrojs/prism": "^0.4.1",
|
"@astrojs/prism": "^0.4.1",
|
||||||
"assert": "^2.0.0",
|
"assert": "^2.0.0",
|
||||||
"github-slugger": "^1.4.0",
|
"github-slugger": "^1.4.0",
|
||||||
"gray-matter": "^4.0.3",
|
|
||||||
"mdast-util-mdx-expression": "^1.2.0",
|
"mdast-util-mdx-expression": "^1.2.0",
|
||||||
"mdast-util-mdx-jsx": "^1.2.0",
|
"mdast-util-mdx-jsx": "^1.2.0",
|
||||||
"mdast-util-to-string": "^3.1.0",
|
"mdast-util-to-string": "^3.1.0",
|
||||||
|
@ -47,7 +46,10 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/github-slugger": "^1.3.0",
|
"@types/github-slugger": "^1.3.0",
|
||||||
|
"@types/hast": "^2.3.4",
|
||||||
|
"@types/mdast": "^3.0.10",
|
||||||
"@types/prismjs": "^1.26.0",
|
"@types/prismjs": "^1.26.0",
|
||||||
|
"@types/unist": "^2.0.6",
|
||||||
"astro-scripts": "workspace:*"
|
"astro-scripts": "workspace:*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { AstroMarkdownOptions, MarkdownRenderingOptions, ShikiConfig, Plugin } from './types';
|
import type { MarkdownRenderingOptions } from './types';
|
||||||
|
|
||||||
import createCollectHeaders from './rehype-collect-headers.js';
|
import createCollectHeaders from './rehype-collect-headers.js';
|
||||||
import scopedStyles from './remark-scoped-styles.js';
|
import scopedStyles from './remark-scoped-styles.js';
|
||||||
|
@ -18,31 +18,17 @@ import markdown from 'remark-parse';
|
||||||
import markdownToHtml from 'remark-rehype';
|
import markdownToHtml from 'remark-rehype';
|
||||||
import rehypeStringify from 'rehype-stringify';
|
import rehypeStringify from 'rehype-stringify';
|
||||||
import rehypeRaw from 'rehype-raw';
|
import rehypeRaw from 'rehype-raw';
|
||||||
import matter from 'gray-matter';
|
|
||||||
|
|
||||||
export { AstroMarkdownOptions, MarkdownRenderingOptions, ShikiConfig, Plugin };
|
export * from './types.js';
|
||||||
|
|
||||||
/** Internal utility for rendering a full markdown file and extracting Frontmatter data */
|
|
||||||
export async function renderMarkdownWithFrontmatter(
|
|
||||||
contents: string,
|
|
||||||
opts?: MarkdownRenderingOptions | null
|
|
||||||
) {
|
|
||||||
const { data: frontmatter, content } = matter(contents);
|
|
||||||
const value = await renderMarkdown(content, opts);
|
|
||||||
return { ...value, frontmatter };
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DEFAULT_REMARK_PLUGINS = ['remark-gfm', 'remark-smartypants'];
|
export const DEFAULT_REMARK_PLUGINS = ['remark-gfm', 'remark-smartypants'];
|
||||||
|
|
||||||
export const DEFAULT_REHYPE_PLUGINS = ['rehype-slug'];
|
export const DEFAULT_REHYPE_PLUGINS = ['rehype-slug'];
|
||||||
|
|
||||||
/** Shared utility for rendering markdown */
|
/** Shared utility for rendering markdown */
|
||||||
export async function renderMarkdown(content: string, opts?: MarkdownRenderingOptions | null) {
|
export async function renderMarkdown(content: string, opts: MarkdownRenderingOptions) {
|
||||||
let { remarkPlugins = [], rehypePlugins = [] } = opts ?? {};
|
let { mode, syntaxHighlight, shikiConfig, remarkPlugins, rehypePlugins } = opts;
|
||||||
const scopedClassName = opts?.$?.scopedClassName;
|
const scopedClassName = opts.$?.scopedClassName;
|
||||||
const mode = opts?.mode ?? 'mdx';
|
|
||||||
const syntaxHighlight = opts?.syntaxHighlight ?? 'shiki';
|
|
||||||
const shikiConfig = opts?.shikiConfig ?? {};
|
|
||||||
const isMDX = mode === 'mdx';
|
const isMDX = mode === 'mdx';
|
||||||
const { headers, rehypeCollectHeaders } = createCollectHeaders();
|
const { headers, rehypeCollectHeaders } = createCollectHeaders();
|
||||||
|
|
||||||
|
@ -111,5 +97,3 @@ export async function renderMarkdown(content: string, opts?: MarkdownRenderingOp
|
||||||
code: result.toString(),
|
code: result.toString(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default renderMarkdownWithFrontmatter;
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import * as unified from 'unified';
|
import * as unified from 'unified';
|
||||||
import type { Plugin } from './types';
|
|
||||||
|
|
||||||
async function importPlugin(p: string | unified.Plugin): Promise<unified.Plugin> {
|
async function importPlugin(p: string | unified.Plugin): Promise<unified.Plugin> {
|
||||||
if (typeof p === 'string') {
|
if (typeof p === 'string') {
|
||||||
|
@ -10,7 +9,9 @@ async function importPlugin(p: string | unified.Plugin): Promise<unified.Plugin>
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadPlugins(items: Plugin[]): Promise<[unified.Plugin, any?]>[] {
|
export function loadPlugins(
|
||||||
|
items: (string | [string, any] | unified.Plugin<any[], any> | [unified.Plugin<any[], any>, any])[]
|
||||||
|
): Promise<[unified.Plugin, any?]>[] {
|
||||||
return items.map((p) => {
|
return items.map((p) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (Array.isArray(p)) {
|
if (Array.isArray(p)) {
|
||||||
|
|
|
@ -1,34 +1,7 @@
|
||||||
import type * as shiki from 'shiki';
|
import type * as shiki from 'shiki';
|
||||||
import { getHighlighter } from 'shiki';
|
import { getHighlighter } from 'shiki';
|
||||||
import { visit } from 'unist-util-visit';
|
import { visit } from 'unist-util-visit';
|
||||||
|
import type { ShikiConfig } from './types.js';
|
||||||
export interface ShikiConfig {
|
|
||||||
/**
|
|
||||||
* The languages loaded to Shiki.
|
|
||||||
* Supports all languages listed here: https://github.com/shikijs/shiki/blob/main/docs/languages.md#all-languages
|
|
||||||
* Instructions for loading a custom language: https://github.com/shikijs/shiki/blob/main/docs/languages.md#supporting-your-own-languages-with-shiki
|
|
||||||
*
|
|
||||||
* @default []
|
|
||||||
*/
|
|
||||||
langs?: shiki.ILanguageRegistration[];
|
|
||||||
/**
|
|
||||||
* The styling theme.
|
|
||||||
* Supports all themes listed here: https://github.com/shikijs/shiki/blob/main/docs/themes.md#all-themes
|
|
||||||
* Instructions for loading a custom theme: https://github.com/shikijs/shiki/blob/main/docs/themes.md#loading-theme
|
|
||||||
*
|
|
||||||
* @default "github-dark"
|
|
||||||
*/
|
|
||||||
theme?: shiki.IThemeRegistration;
|
|
||||||
/**
|
|
||||||
* Enable word wrapping.
|
|
||||||
* - true: enabled.
|
|
||||||
* - false: enabled.
|
|
||||||
* - null: All overflow styling removed. Code will overflow the element by default.
|
|
||||||
*
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
wrap?: boolean | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* getHighlighter() is the most expensive step of Shiki. Instead of calling it on every page,
|
* getHighlighter() is the most expensive step of Shiki. Instead of calling it on every page,
|
||||||
|
@ -38,7 +11,7 @@ export interface ShikiConfig {
|
||||||
const highlighterCacheAsync = new Map<string, Promise<shiki.Highlighter>>();
|
const highlighterCacheAsync = new Map<string, Promise<shiki.Highlighter>>();
|
||||||
|
|
||||||
const remarkShiki = async (
|
const remarkShiki = async (
|
||||||
{ langs = [], theme = 'github-dark', wrap = false }: ShikiConfig,
|
{ langs, theme, wrap }: ShikiConfig,
|
||||||
scopedClassName?: string | null
|
scopedClassName?: string | null
|
||||||
) => {
|
) => {
|
||||||
const cacheID: string = typeof theme === 'string' ? theme : theme.name;
|
const cacheID: string = typeof theme === 'string' ? theme : theme.name;
|
||||||
|
|
|
@ -1,18 +1,40 @@
|
||||||
import type * as unified from 'unified';
|
import type * as unified from 'unified';
|
||||||
import type { ShikiConfig } from './remark-shiki';
|
import type * as mdast from 'mdast';
|
||||||
export { ShikiConfig };
|
import type * as hast from 'hast';
|
||||||
|
import type { ILanguageRegistration, IThemeRegistration, Theme } from 'shiki';
|
||||||
|
|
||||||
export type Plugin = string | [string, any] | unified.Plugin | [unified.Plugin, any];
|
export type { Node } from 'unist';
|
||||||
|
|
||||||
export interface AstroMarkdownOptions {
|
export type RemarkPlugin<PluginParameters extends any[] = any[]> = unified.Plugin<
|
||||||
mode?: 'md' | 'mdx';
|
PluginParameters,
|
||||||
syntaxHighlight?: 'shiki' | 'prism' | false;
|
mdast.Root
|
||||||
shikiConfig?: ShikiConfig;
|
>;
|
||||||
remarkPlugins?: Plugin[];
|
|
||||||
rehypePlugins?: Plugin[];
|
export type RemarkPlugins = (string | [string, any] | RemarkPlugin | [RemarkPlugin, any])[];
|
||||||
|
|
||||||
|
export type RehypePlugin<PluginParameters extends any[] = any[]> = unified.Plugin<
|
||||||
|
PluginParameters,
|
||||||
|
hast.Root
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type RehypePlugins = (string | [string, any] | RehypePlugin | [RehypePlugin, any])[];
|
||||||
|
|
||||||
|
export interface ShikiConfig {
|
||||||
|
langs: ILanguageRegistration[];
|
||||||
|
theme: Theme | IThemeRegistration;
|
||||||
|
wrap: boolean | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MarkdownRenderingOptions extends Partial<AstroMarkdownOptions> {
|
export interface AstroMarkdownOptions {
|
||||||
|
mode: 'md' | 'mdx';
|
||||||
|
drafts: boolean;
|
||||||
|
syntaxHighlight: 'shiki' | 'prism' | false;
|
||||||
|
shikiConfig: ShikiConfig;
|
||||||
|
remarkPlugins: RemarkPlugins;
|
||||||
|
rehypePlugins: RehypePlugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MarkdownRenderingOptions extends AstroMarkdownOptions {
|
||||||
/** @internal */
|
/** @internal */
|
||||||
$?: {
|
$?: {
|
||||||
scopedClassName: string | null;
|
scopedClassName: string | null;
|
||||||
|
|
|
@ -477,6 +477,7 @@ importers:
|
||||||
'@types/resolve': ^1.20.1
|
'@types/resolve': ^1.20.1
|
||||||
'@types/rimraf': ^3.0.2
|
'@types/rimraf': ^3.0.2
|
||||||
'@types/send': ^0.17.1
|
'@types/send': ^0.17.1
|
||||||
|
'@types/unist': ^2.0.6
|
||||||
'@types/yargs-parser': ^21.0.0
|
'@types/yargs-parser': ^21.0.0
|
||||||
'@web/parse5-utils': ^1.3.0
|
'@web/parse5-utils': ^1.3.0
|
||||||
ast-types: ^0.14.2
|
ast-types: ^0.14.2
|
||||||
|
@ -495,6 +496,7 @@ importers:
|
||||||
execa: ^6.1.0
|
execa: ^6.1.0
|
||||||
fast-glob: ^3.2.11
|
fast-glob: ^3.2.11
|
||||||
fast-xml-parser: ^4.0.7
|
fast-xml-parser: ^4.0.7
|
||||||
|
gray-matter: ^4.0.3
|
||||||
html-entities: ^2.3.3
|
html-entities: ^2.3.3
|
||||||
html-escaper: ^3.0.3
|
html-escaper: ^3.0.3
|
||||||
htmlparser2: ^7.2.0
|
htmlparser2: ^7.2.0
|
||||||
|
@ -558,6 +560,7 @@ importers:
|
||||||
execa: 6.1.0
|
execa: 6.1.0
|
||||||
fast-glob: 3.2.11
|
fast-glob: 3.2.11
|
||||||
fast-xml-parser: 4.0.7
|
fast-xml-parser: 4.0.7
|
||||||
|
gray-matter: 4.0.3
|
||||||
html-entities: 2.3.3
|
html-entities: 2.3.3
|
||||||
html-escaper: 3.0.3
|
html-escaper: 3.0.3
|
||||||
htmlparser2: 7.2.0
|
htmlparser2: 7.2.0
|
||||||
|
@ -613,6 +616,7 @@ importers:
|
||||||
'@types/resolve': 1.20.1
|
'@types/resolve': 1.20.1
|
||||||
'@types/rimraf': 3.0.2
|
'@types/rimraf': 3.0.2
|
||||||
'@types/send': 0.17.1
|
'@types/send': 0.17.1
|
||||||
|
'@types/unist': 2.0.6
|
||||||
'@types/yargs-parser': 21.0.0
|
'@types/yargs-parser': 21.0.0
|
||||||
astro-scripts: link:../../scripts
|
astro-scripts: link:../../scripts
|
||||||
chai: 4.3.6
|
chai: 4.3.6
|
||||||
|
@ -1412,11 +1416,13 @@ importers:
|
||||||
specifiers:
|
specifiers:
|
||||||
'@astrojs/prism': ^0.4.1
|
'@astrojs/prism': ^0.4.1
|
||||||
'@types/github-slugger': ^1.3.0
|
'@types/github-slugger': ^1.3.0
|
||||||
|
'@types/hast': ^2.3.4
|
||||||
|
'@types/mdast': ^3.0.10
|
||||||
'@types/prismjs': ^1.26.0
|
'@types/prismjs': ^1.26.0
|
||||||
|
'@types/unist': ^2.0.6
|
||||||
assert: ^2.0.0
|
assert: ^2.0.0
|
||||||
astro-scripts: workspace:*
|
astro-scripts: workspace:*
|
||||||
github-slugger: ^1.4.0
|
github-slugger: ^1.4.0
|
||||||
gray-matter: ^4.0.3
|
|
||||||
mdast-util-mdx-expression: ^1.2.0
|
mdast-util-mdx-expression: ^1.2.0
|
||||||
mdast-util-mdx-jsx: ^1.2.0
|
mdast-util-mdx-jsx: ^1.2.0
|
||||||
mdast-util-to-string: ^3.1.0
|
mdast-util-to-string: ^3.1.0
|
||||||
|
@ -1438,7 +1444,6 @@ importers:
|
||||||
'@astrojs/prism': link:../../astro-prism
|
'@astrojs/prism': link:../../astro-prism
|
||||||
assert: 2.0.0
|
assert: 2.0.0
|
||||||
github-slugger: 1.4.0
|
github-slugger: 1.4.0
|
||||||
gray-matter: 4.0.3
|
|
||||||
mdast-util-mdx-expression: 1.2.0
|
mdast-util-mdx-expression: 1.2.0
|
||||||
mdast-util-mdx-jsx: 1.2.0
|
mdast-util-mdx-jsx: 1.2.0
|
||||||
mdast-util-to-string: 3.1.0
|
mdast-util-to-string: 3.1.0
|
||||||
|
@ -1458,7 +1463,10 @@ importers:
|
||||||
unist-util-visit: 4.1.0
|
unist-util-visit: 4.1.0
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@types/github-slugger': 1.3.0
|
'@types/github-slugger': 1.3.0
|
||||||
|
'@types/hast': 2.3.4
|
||||||
|
'@types/mdast': 3.0.10
|
||||||
'@types/prismjs': 1.26.0
|
'@types/prismjs': 1.26.0
|
||||||
|
'@types/unist': 2.0.6
|
||||||
astro-scripts: link:../../../scripts
|
astro-scripts: link:../../../scripts
|
||||||
|
|
||||||
packages/webapi:
|
packages/webapi:
|
||||||
|
@ -3870,7 +3878,6 @@ packages:
|
||||||
resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==}
|
resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/unist': 2.0.6
|
'@types/unist': 2.0.6
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/mdurl/1.0.2:
|
/@types/mdurl/1.0.2:
|
||||||
resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==}
|
resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==}
|
||||||
|
|
Loading…
Reference in a new issue