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:
Juan Martín Seery 2022-04-11 20:01:12 -03:00 committed by GitHub
parent 11766acec9
commit b835e285de
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 150 additions and 141 deletions

View file

@ -0,0 +1,5 @@
---
'@astrojs/markdown-remark': patch
---
Improved type checking

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Improved markdown config type checking

View file

@ -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",

View file

@ -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;

View file

@ -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,

View file

@ -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>;

View file

@ -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,

View file

@ -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,

View file

@ -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), // NOTE: "mdx" allows us to parse/compile Astro components in markdown.
mode: z // TODO: This should probably be updated to something more like "md" | "astro"
.union([z.literal('md'), z.literal('mdx')]) mode: z.enum(['md', 'mdx']).default('mdx'),
.optional() drafts: z.boolean().default(false),
// NOTE: "mdx" allows us to parse/compile Astro components in markdown.
// TODO: This should probably be updated to something more like "md" | "astro"
.default('mdx'),
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

View file

@ -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,

View file

@ -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,

View file

@ -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;
}, },
}); });

View file

@ -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;

View file

@ -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:*"
} }
} }

View file

@ -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;

View file

@ -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)) {

View file

@ -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;

View file

@ -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;

View file

@ -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==}