summaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/astro/package.json9
-rw-r--r--packages/astro/src/@types/astro.ts8
-rw-r--r--packages/astro/src/compiler/codegen/index.ts1
-rw-r--r--packages/astro/src/compiler/index.ts8
-rw-r--r--packages/astro/test/astro-markdown-plugins.test.js29
-rw-r--r--packages/astro/test/fixtures/astro-markdown-plugins/astro.config.mjs19
-rw-r--r--packages/astro/test/fixtures/astro-markdown-plugins/snowpack.config.json3
-rw-r--r--packages/astro/test/fixtures/astro-markdown-plugins/src/layouts/content.astro10
-rw-r--r--packages/astro/test/fixtures/astro-markdown-plugins/src/pages/astro.astro10
-rw-r--r--packages/astro/test/fixtures/astro-markdown-plugins/src/pages/index.md5
-rw-r--r--packages/markdown-support/package.json14
-rw-r--r--packages/markdown-support/src/index.ts61
-rw-r--r--packages/markdown-support/src/load-plugins.ts27
-rw-r--r--packages/markdown-support/src/rehype-collect-headers.ts4
-rw-r--r--packages/markdown-support/src/rehype-expressions.ts12
-rw-r--r--packages/markdown-support/src/remark-expressions.ts19
-rw-r--r--packages/markdown-support/src/types.ts14
17 files changed, 204 insertions, 49 deletions
diff --git a/packages/astro/package.json b/packages/astro/package.json
index 28b3b3f56..ebec27310 100644
--- a/packages/astro/package.json
+++ b/packages/astro/package.json
@@ -52,7 +52,6 @@
"@babel/generator": "^7.13.9",
"@babel/parser": "^7.13.15",
"@babel/traverse": "^7.13.15",
- "@silvenon/remark-smartypants": "^1.0.0",
"@snowpack/plugin-postcss": "^1.4.3",
"@snowpack/plugin-sass": "^1.4.0",
"acorn": "^7.4.0",
@@ -67,14 +66,12 @@
"fast-xml-parser": "^3.19.0",
"fdir": "^5.0.0",
"find-up": "^5.0.0",
- "gray-matter": "^4.0.2",
+ "unified": "^9.2.1",
"gzip-size": "^6.0.0",
"hast-to-hyperscript": "~9.0.0",
"kleur": "^4.1.4",
"locate-character": "^2.0.5",
"magic-string": "^0.25.3",
- "mdast-util-mdx": "^0.1.1",
- "micromark-extension-mdxjs": "^0.3.0",
"mime": "^2.5.2",
"moize": "^6.0.1",
"node-fetch": "^2.6.1",
@@ -82,10 +79,6 @@
"postcss": "^8.2.15",
"postcss-icss-keyframes": "^0.2.1",
"prismjs": "^1.23.0",
- "remark-footnotes": "^3.0.0",
- "remark-gfm": "^1.0.0",
- "remark-parse": "^9.0.0",
- "remark-rehype": "^8.1.0",
"resolve": "^1.20.0",
"rollup": "^2.43.1",
"rollup-plugin-terser": "^7.0.2",
diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts
index 1fbdd6282..bfb338cdc 100644
--- a/packages/astro/src/@types/astro.ts
+++ b/packages/astro/src/@types/astro.ts
@@ -1,4 +1,5 @@
import type { ImportSpecifier, ImportDefaultSpecifier, ImportNamespaceSpecifier } from '@babel/types';
+import type { AstroMarkdownOptions } from '@astrojs/markdown-support'
export interface AstroConfigRaw {
dist: string;
@@ -9,12 +10,7 @@ export interface AstroConfigRaw {
jsx?: string;
}
-export interface AstroMarkdownOptions {
- /** Enable or disable footnotes syntax extension */
- footnotes: boolean;
- /** Enable or disable GitHub-flavored Markdown syntax extension */
- gfm: boolean;
-}
+export { AstroMarkdownOptions }
export interface AstroConfig {
dist: string;
projectRoot: URL;
diff --git a/packages/astro/src/compiler/codegen/index.ts b/packages/astro/src/compiler/codegen/index.ts
index 0b9780e16..b34077269 100644
--- a/packages/astro/src/compiler/codegen/index.ts
+++ b/packages/astro/src/compiler/codegen/index.ts
@@ -543,7 +543,6 @@ async function compileHtml(enterNode: TemplateNode, state: CodegenState, compile
const { $scope: scopedClassName } = state.markers.insideMarkdown as Record<'$scope', any>;
let { content: rendered } = await renderMarkdown(dedent(md), {
...(markdownOptions as AstroMarkdownOptions),
- mode: 'astro-md',
$: { scopedClassName: scopedClassName && scopedClassName.slice(1, -1) },
});
diff --git a/packages/astro/src/compiler/index.ts b/packages/astro/src/compiler/index.ts
index 0f12cc7f0..f612e6165 100644
--- a/packages/astro/src/compiler/index.ts
+++ b/packages/astro/src/compiler/index.ts
@@ -3,7 +3,7 @@ import type { CompileResult, TransformResult } from '../@types/astro';
import type { CompileOptions } from '../@types/compiler.js';
import path from 'path';
-import { renderMarkdownWithFrontmatter } from '@astrojs/markdown-support';
+import { MarkdownRenderingOptions, renderMarkdownWithFrontmatter } from '@astrojs/markdown-support';
import { parse } from '@astrojs/parser';
import { transform } from './transform/index.js';
@@ -42,12 +42,12 @@ export async function convertAstroToJsx(template: string, opts: ConvertAstroOpti
/**
* .md -> .astro source
*/
-export async function convertMdToAstroSource(contents: string, { filename }: { filename: string }): Promise<string> {
+export async function convertMdToAstroSource(contents: string, { filename }: { filename: string }, opts?: MarkdownRenderingOptions): Promise<string> {
let {
content,
frontmatter: { layout, ...frontmatter },
...data
- } = await renderMarkdownWithFrontmatter(contents);
+ } = await renderMarkdownWithFrontmatter(contents, opts);
if (frontmatter['astro'] !== undefined) {
throw new Error(`"astro" is a reserved word but was used as a frontmatter value!\n\tat ${filename}`);
@@ -75,7 +75,7 @@ async function convertMdToJsx(
contents: string,
{ compileOptions, filename, fileID }: { compileOptions: CompileOptions; filename: string; fileID: string }
): Promise<TransformResult> {
- const raw = await convertMdToAstroSource(contents, { filename });
+ const raw = await convertMdToAstroSource(contents, { filename }, compileOptions.astroConfig.markdownOptions);
const convertOptions = { compileOptions, filename, fileID };
return await convertAstroToJsx(raw, convertOptions);
}
diff --git a/packages/astro/test/astro-markdown-plugins.test.js b/packages/astro/test/astro-markdown-plugins.test.js
new file mode 100644
index 000000000..44744e5d5
--- /dev/null
+++ b/packages/astro/test/astro-markdown-plugins.test.js
@@ -0,0 +1,29 @@
+import { suite } from 'uvu';
+import * as assert from 'uvu/assert';
+import { doc } from './test-utils.js';
+import { setup, setupBuild } from './helpers.js';
+
+const MarkdownPlugin = suite('Astro Markdown plugin tests');
+
+setup(MarkdownPlugin, './fixtures/astro-markdown-plugins');
+setupBuild(MarkdownPlugin, './fixtures/astro-markdown-plugins');
+
+MarkdownPlugin('Can render markdown with plugins', async ({ runtime }) => {
+ const result = await runtime.load('/');
+ if (result.error) throw new Error(result.error);
+
+ const $ = doc(result.contents);
+ assert.equal($('.toc').length, 1, 'Added a TOC');
+ assert.ok($('#hello-world').hasClass('title'), 'Added .title to h1');
+});
+
+MarkdownPlugin('Can render Astro <Markdown> with plugins', async ({ runtime }) => {
+ const result = await runtime.load('/astro');
+ if (result.error) throw new Error(result.error);
+
+ const $ = doc(result.contents);
+ assert.equal($('.toc').length, 1, 'Added a TOC');
+ assert.ok($('#hello-world').hasClass('title'), 'Added .title to h1');
+})
+
+MarkdownPlugin.run();
diff --git a/packages/astro/test/fixtures/astro-markdown-plugins/astro.config.mjs b/packages/astro/test/fixtures/astro-markdown-plugins/astro.config.mjs
new file mode 100644
index 000000000..c236c9215
--- /dev/null
+++ b/packages/astro/test/fixtures/astro-markdown-plugins/astro.config.mjs
@@ -0,0 +1,19 @@
+export default {
+ renderers: [
+ '@astrojs/renderer-preact'
+ ],
+ markdownOptions: {
+ remarkPlugins: [
+ 'remark-code-titles',
+ 'remark-slug',
+ ['remark-autolink-headings', { behavior: 'prepend' }],
+ ],
+ rehypePlugins: [
+ ['rehype-toc', { headings: ["h2", "h3"] }],
+ ['rehype-add-classes', { 'h1,h2,h3': 'title', }],
+ ]
+ },
+ buildOptions: {
+ sitemap: false,
+ },
+};
diff --git a/packages/astro/test/fixtures/astro-markdown-plugins/snowpack.config.json b/packages/astro/test/fixtures/astro-markdown-plugins/snowpack.config.json
new file mode 100644
index 000000000..8f034781d
--- /dev/null
+++ b/packages/astro/test/fixtures/astro-markdown-plugins/snowpack.config.json
@@ -0,0 +1,3 @@
+{
+ "workspaceRoot": "../../../../../"
+}
diff --git a/packages/astro/test/fixtures/astro-markdown-plugins/src/layouts/content.astro b/packages/astro/test/fixtures/astro-markdown-plugins/src/layouts/content.astro
new file mode 100644
index 000000000..925a243a9
--- /dev/null
+++ b/packages/astro/test/fixtures/astro-markdown-plugins/src/layouts/content.astro
@@ -0,0 +1,10 @@
+<html>
+ <head>
+ <!-- Head Stuff -->
+ </head>
+ <body>
+ <div class="container">
+ <slot></slot>
+ </div>
+ </body>
+</html>
diff --git a/packages/astro/test/fixtures/astro-markdown-plugins/src/pages/astro.astro b/packages/astro/test/fixtures/astro-markdown-plugins/src/pages/astro.astro
new file mode 100644
index 000000000..a05a7c3ff
--- /dev/null
+++ b/packages/astro/test/fixtures/astro-markdown-plugins/src/pages/astro.astro
@@ -0,0 +1,10 @@
+---
+import { Markdown } from 'astro/components';
+import Layout from '../layouts/content.astro';
+---
+
+<Layout>
+ <Markdown>
+ # Hello world
+ </Markdown>
+</Layout>
diff --git a/packages/astro/test/fixtures/astro-markdown-plugins/src/pages/index.md b/packages/astro/test/fixtures/astro-markdown-plugins/src/pages/index.md
new file mode 100644
index 000000000..832ccf0bc
--- /dev/null
+++ b/packages/astro/test/fixtures/astro-markdown-plugins/src/pages/index.md
@@ -0,0 +1,5 @@
+---
+layout: ../layouts/content.astro
+---
+
+# Hello world
diff --git a/packages/markdown-support/package.json b/packages/markdown-support/package.json
index 8be2aa4a4..550fef7a7 100644
--- a/packages/markdown-support/package.json
+++ b/packages/markdown-support/package.json
@@ -2,7 +2,7 @@
"name": "@astrojs/markdown-support",
"version": "0.1.2",
"main": "./dist/index.js",
- "type": "commonjs",
+ "type": "module",
"repository": {
"type": "git",
"url": "https://github.com/snowpackjs/astro.git",
@@ -16,6 +16,18 @@
"build": "astro-scripts build --format cjs \"src/**/*.ts\" && tsc -p tsconfig.json",
"dev": "astro-scripts dev \"src/**/*.ts\""
},
+ "dependencies": {
+ "@silvenon/remark-smartypants": "^1.0.0",
+ "gray-matter": "^4.0.2",
+ "mdast-util-mdx-expression": "^1.0.0",
+ "micromark-extension-mdx-expression": "^1.0.0",
+ "remark-footnotes": "^3.0.0",
+ "remark-gfm": "^1.0.0",
+ "remark-parse": "^9.0.0",
+ "remark-rehype": "^8.1.0",
+ "unified": "^9.2.1",
+ "unist-util-map": "^3.0.0"
+ },
"devDependencies": {
"@types/github-slugger": "^1.3.0",
"github-slugger": "^1.3.0",
diff --git a/packages/markdown-support/src/index.ts b/packages/markdown-support/src/index.ts
index 08f171c3c..f311efa7c 100644
--- a/packages/markdown-support/src/index.ts
+++ b/packages/markdown-support/src/index.ts
@@ -1,62 +1,67 @@
-import type { AstroMarkdownOptions } from './types';
+import type { AstroMarkdownOptions, MarkdownRenderingOptions } from './types';
import createCollectHeaders from './rehype-collect-headers.js';
import scopedStyles from './remark-scoped-styles.js';
-import { remarkCodeBlock, rehypeCodeBlock } from './codeblock.js';
+import remarkExpressions from './remark-expressions.js';
+import rehypeExpressions from './rehype-expressions.js';
+import { rehypeCodeBlock } from './codeblock.js';
+import { loadPlugins } from './load-plugins.js';
import raw from 'rehype-raw';
import unified from 'unified';
import markdown from 'remark-parse';
import markdownToHtml from 'remark-rehype';
-// import smartypants from '@silvenon/remark-smartypants';
import rehypeStringify from 'rehype-stringify';
-export interface MarkdownRenderingOptions extends Partial<AstroMarkdownOptions> {
- $?: {
- scopedClassName: string | null;
- };
- mode: 'md' | 'astro-md';
-}
+export { AstroMarkdownOptions, MarkdownRenderingOptions };
/** Internal utility for rendering a full markdown file and extracting Frontmatter data */
export async function renderMarkdownWithFrontmatter(contents: string, opts?: MarkdownRenderingOptions | null) {
// Dynamic import to ensure that "gray-matter" isn't built by Snowpack
const { default: matter } = await import('gray-matter');
const { data: frontmatter, content } = matter(contents);
- const value = await renderMarkdown(content, { ...opts, mode: 'md' });
+ const value = await renderMarkdown(content, opts);
return { ...value, frontmatter };
}
/** Shared utility for rendering markdown */
export async function renderMarkdown(content: string, opts?: MarkdownRenderingOptions | null) {
- const { $: { scopedClassName = null } = {}, mode = 'astro-md', footnotes: useFootnotes = true, gfm: useGfm = true } = opts ?? {};
+ const { $: { scopedClassName = null } = {}, footnotes: useFootnotes = true, gfm: useGfm = true, remarkPlugins = [], rehypePlugins = [] } = opts ?? {};
const { headers, rehypeCollectHeaders } = createCollectHeaders();
+ let parser = unified().use(markdown).use([remarkExpressions, { addResult: true }]);
- let parser = unified().use(markdown).use(remarkCodeBlock());
+ if (remarkPlugins.length === 0) {
+ if (useGfm) {
+ remarkPlugins.push('remark-gfm');
+ }
- if (scopedClassName) {
- parser = parser.use(scopedStyles(scopedClassName));
- }
+ if (useFootnotes) {
+ remarkPlugins.push('remark-footnotes');
+ }
- if (useGfm) {
- const { default: gfm } = await import('remark-gfm');
- parser = parser.use(gfm);
+ remarkPlugins.push('@silvenon/remark-smartypants');
}
+ const loadedRemarkPlugins = await Promise.all(loadPlugins(remarkPlugins));
+ const loadedRehypePlugins = await Promise.all(loadPlugins(rehypePlugins));
- if (useFootnotes) {
- const { default: footnotes } = await import('remark-footnotes');
- parser = parser.use(footnotes);
+ loadedRemarkPlugins.forEach(([plugin, opts]) => {
+ parser.use(plugin, opts);
+ });
+
+ if (scopedClassName) {
+ parser.use(scopedStyles(scopedClassName));
}
+ parser.use(markdownToHtml, { allowDangerousHtml: true, passThrough: ['raw', 'mdxTextExpression'] });
+ parser.use(rehypeExpressions);
+
+ loadedRehypePlugins.forEach(([plugin, opts]) => {
+ parser.use(plugin, opts);
+ });
+
let result: string;
try {
- const vfile = await parser
- .use(markdownToHtml, { allowDangerousHtml: true, passThrough: ['raw'] })
- .use(raw)
- .use(rehypeCollectHeaders)
- .use(rehypeCodeBlock())
- .use(rehypeStringify)
- .process(content);
+ const vfile = await parser.use(raw).use(rehypeCollectHeaders).use(rehypeCodeBlock()).use(rehypeStringify, { entities: { useNamedReferences: true }}).process(content);
result = vfile.contents.toString();
} catch (err) {
throw err;
diff --git a/packages/markdown-support/src/load-plugins.ts b/packages/markdown-support/src/load-plugins.ts
new file mode 100644
index 000000000..52bc287f8
--- /dev/null
+++ b/packages/markdown-support/src/load-plugins.ts
@@ -0,0 +1,27 @@
+import unified from "unified";
+import type { Plugin, UnifiedPluginImport } from "./types";
+
+async function importPlugin(p: string | UnifiedPluginImport): UnifiedPluginImport {
+ if (typeof p === 'string') {
+ return await import(p);
+ }
+
+ return await p;
+}
+
+export function loadPlugins(items: Plugin[]): Promise<[unified.Plugin] | [unified.Plugin, unified.Settings]>[] {
+ return items.map((p) => {
+ return new Promise((resolve, reject) => {
+ if (Array.isArray(p)) {
+ const [plugin, opts] = p;
+ return importPlugin(plugin)
+ .then((m) => resolve([m.default, opts]))
+ .catch((e) => reject(e));
+ }
+
+ return importPlugin(p)
+ .then((m) => resolve([m.default]))
+ .catch((e) => reject(e));
+ });
+ });
+}
diff --git a/packages/markdown-support/src/rehype-collect-headers.ts b/packages/markdown-support/src/rehype-collect-headers.ts
index edfcd29bc..de9b78692 100644
--- a/packages/markdown-support/src/rehype-collect-headers.ts
+++ b/packages/markdown-support/src/rehype-collect-headers.ts
@@ -14,11 +14,13 @@ export default function createCollectHeaders() {
depth = Number.parseInt(depth);
let text = '';
+
visit(node, 'text', (child) => {
text += child.value;
});
- let slug = slugger.slug(text);
+ let slug = node.properties.id || slugger.slug(text);
+
node.properties = node.properties || {};
node.properties.id = slug;
headers.push({ depth, slug, text });
diff --git a/packages/markdown-support/src/rehype-expressions.ts b/packages/markdown-support/src/rehype-expressions.ts
new file mode 100644
index 000000000..2762f54fc
--- /dev/null
+++ b/packages/markdown-support/src/rehype-expressions.ts
@@ -0,0 +1,12 @@
+import { map } from 'unist-util-map'
+
+export default function rehypeExpressions(): any {
+ return function(node: any): any {
+ return map(node, (child) => {
+ if (child.type === 'mdxTextExpression') {
+ return { type: 'text', value: `{${child.value}}` }
+ }
+ return child;
+ })
+ }
+}
diff --git a/packages/markdown-support/src/remark-expressions.ts b/packages/markdown-support/src/remark-expressions.ts
new file mode 100644
index 000000000..1cdb37894
--- /dev/null
+++ b/packages/markdown-support/src/remark-expressions.ts
@@ -0,0 +1,19 @@
+import {mdxExpression} from 'micromark-extension-mdx-expression'
+import {mdxExpressionFromMarkdown, mdxExpressionToMarkdown} from 'mdast-util-mdx-expression'
+
+function remarkExpressions(this: any, options: any) {
+ let settings = options || {}
+ let data = this.data()
+
+ add('micromarkExtensions', mdxExpression({}))
+ add('fromMarkdownExtensions', mdxExpressionFromMarkdown)
+ add('toMarkdownExtensions', mdxExpressionToMarkdown)
+
+ function add(field: any, value: any) {
+ /* istanbul ignore if - other extensions. */
+ if (data[field]) data[field].push(value)
+ else data[field] = [value]
+ }
+}
+
+export default remarkExpressions;
diff --git a/packages/markdown-support/src/types.ts b/packages/markdown-support/src/types.ts
index b69f7fc28..6df601ae4 100644
--- a/packages/markdown-support/src/types.ts
+++ b/packages/markdown-support/src/types.ts
@@ -1,6 +1,20 @@
+import unified from 'unified';
+
+export type UnifiedPluginImport = Promise<{ default: unified.Plugin }>;
+export type Plugin = string | [string, unified.Settings] | UnifiedPluginImport | [UnifiedPluginImport, unified.Settings];
+
export interface AstroMarkdownOptions {
/** Enable or disable footnotes syntax extension */
footnotes: boolean;
/** Enable or disable GitHub-flavored Markdown syntax extension */
gfm: boolean;
+ remarkPlugins: Plugin[];
+ rehypePlugins: Plugin[];
+}
+
+export interface MarkdownRenderingOptions extends Partial<AstroMarkdownOptions> {
+ /** @internal */
+ $?: {
+ scopedClassName: string | null;
+ };
}