summaryrefslogtreecommitdiff
path: root/packages/markdown/remark/src
diff options
context:
space:
mode:
authorGravatar Drew Powers <1369770+drwpow@users.noreply.github.com> 2021-10-29 13:30:22 -0600
committerGravatar GitHub <noreply@github.com> 2021-10-29 15:30:22 -0400
commitd84bfe719a546ad855640338d5ed49ad3aa4ccb4 (patch)
treeac63062651c36bcd73194c2950e160d225b5575b /packages/markdown/remark/src
parent61e6bff5b1bac9f48e31ce10236858c420cd6ce7 (diff)
downloadastro-d84bfe719a546ad855640338d5ed49ad3aa4ccb4.tar.gz
astro-d84bfe719a546ad855640338d5ed49ad3aa4ccb4.tar.zst
astro-d84bfe719a546ad855640338d5ed49ad3aa4ccb4.zip
🚀 Astro Next (0.21.0) (#1406)
* make astro-root uids unique * Move Astro to Vite * Update tests * More test improvements * fred fixes * Update compiler, improve tests * Fix runtime, improve code frame * Add Markdown support * Tycho fixes * Fred fixes part 2 * Throw Error for WIP Features * Improve testing suite * Allow users to pass config to Vite * Fix npm install (#1407) * Automate publish on merge (#1408) * Add NPM_TOKEN to publish script (#1409) * Create .npmrc * Clean up astro deps (#1411) * Use new renderers (#1412) * feat: update compiler (#1421) * Try mocha/chai test runners (#1418) * Try mocha/chai test runners * Disable failing smoke test for now Will revert when next can build docs * Enable mocha in parallel mode * Remove warning * Update docs * Fix Windows bug * Fix internal imports * Fix styles * Fix CI release on merge to next (#1427) * Fix logger locale parsing (#1439) * fix(logger): locale parsing * Fixed issue of compiler crash when "c" locale was encountered * Return default locale if parsed locale is less than 2 chars long * chore: add changeset * Apply changes from #1387 * Add back in support for children (#1486) * Add back in support for children * Be more careful * Enables most slot tests (#1494) * Enables most slot tests * Use spreadAttributes * Add hydration to Solid renderer (#1479) (#1495) * feat: add hydration to Solid renderer * fix: intersection observer, move script to the end Co-authored-by: Ryan Carniato <ryansolid@gmail.com> * [next] support Astro.slots API (#1516) * [next] Support for custom elements (#1528) * [next] Support for custom elements * Fix eslint errors * eslint again * [next] Fix Astro.fetchContent (#1480) * fix Astro.fetchContent * fix(fetchContent): cast type Co-authored-by: Nate Moore <nate@skypack.dev> * Move hydration to the compiler (#1547) * Move hydration to the compiler * Move extracting url, export to util fn * Brings back astro-dynamic tests (#1548) * Implements top-level Astro + Astro.resolve (#1556) * Implements top-level Astro + Astro.resolve * Fix linting * [next] Update renderers (#1509) * chore: update vite * fix(renderers): point renderers to resolved server/client entrypoints * Chore: Enable more tests with new compiler changes (#1558) * [Next] `fetch` support (#1563) * fix: polyfill fetch in every ssr scenario * test(fetch): update fetch tests * docs: update data fetching guide to remove caveats about `fetch` and isomorphic usage * refactor: update regex for clarity * Restructure (#1569) * Upgrade to @astrojs/compiler 0.2.0 (#1584) * Use Vite fork (#1585) * Use Vite fork * Fix linting * Move Vite to vendor/ and add a license * Fix linting * Include the dist folder * Update files config * Markdown compilation (#1593) * Markdown compilation * remove debugger * Gets lit hydration working (#1595) * Gets Astro.fetchContent compilation to work (#1596) * Gets Astro.fetchContent compilation to work This fixes Astro.fetchContent so that we handle esbuild transforming the name of the nested Astro call. * Remove debugging * Update the tests * Remove another debugger * Update Vite to latest (#1597) * Add Prism syntax highlighting (#1598) * Scoped styles with markdown (#1599) * Bugfix: fix getStaticPaths() cache miss (#1602) * Fix build order (#1609) * Bugfix: restore build to get all paths earlier, when build. Same as main. * Also re-add timings * [next] blog example fully working (#1610) * Add environment variables docs (Closes #873) (#1587) * Added environment variables docs (Closes #873) * Fixed prefix * Remove numbered comments (#1611) * Chore: remove numbered comments * Clean up block comments * comment style fixes (#1614) * [next] Upgrade compiler (#1619) * [next] Upgrade compiler * Upgrade to latest compiler * Fix the path to global css * Removed debugger * feat: add fragment support to vite-plugin-astro (#1600) * [next] fix `.tsx` handling (#1620) * fix: support tsx in JSX plugin * fix: preserve JSX via esbuild, only use Babel for JSX compilation * fix: handle upcoming Vite API for `ssr` flag * [next] Add CSS preprocessing (#1589) * Add concept for style support in Astro * Update style preprocessor to use new compiler * fix: massage preprocessStyle type * fix: @astrojs/compiler types Co-authored-by: Nate Moore <nate@skypack.dev> * fix issues in blog-multiple-authors (#1621) * Move Sass to deps (#1622) * Update renderer API for Vite (#1623) * Update renderer API for Vite * Fix lit-element tests * Clean up comments * Throw friendly error if renderer provides viteConfig in a bad format * Fix changesets (#1628) * Remove cheerio scanning from build stats (#1629) * Minor change to jsxTransformOptions, update Renderer API docs (#1630) * [next] docs example fully working (#1627) * [next] docs example fully working * Upgrade compiler to unlock docs * Add `class:list` directive (#1612) * Add support for class:list directive The `class:list` directive serializes an expression of css class names. For React components, `className:list` is also supported. * Remove `className` support and React tests * Add tests for the absence of omitted classes * fix: `define:vars` scoping for styles (#1632) * feat: fix Debug component (#1633) * [next] Fix `<Markdown>` component (#1631) * fix: cleanup issues with <Markdown> component * fix: fix `content` usage with Markdown * [next] Fix `<Code>` component (#1635) * fix: enable Code component * test: update expect to chai format * Fixes solid (#1634) * Fixes solid * Rename the test * Rebase with next * Skip solid test for now * Add support for markdown plugins (#1650) * Fix broken next release (#1652) * Prevent passing to Svelte components * Prevent passing class to Vue components * Add CSS injection, fix portfolio example (#1648) * Fix portfolio example * Add .pcss extension * Update load ssr opts * Update packages/astro/src/runtime/server/index.ts Co-authored-by: Jonathan Neal <jonathantneal@hotmail.com> Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com> Co-authored-by: Jonathan Neal <jonathantneal@hotmail.com> * Fixes external HMR (#1654) * Upgrade compiler version (#1655) Fixes docs and blog examples * Resolve renderers relative to the projectRoot (#1659) * Template fixes (#1656) * fix: dedupe hashes for identical islands (#1660) * fix: scope `define:vars` to `:root` for `<style global>` (#1663) * chore: update compiler to latest (#1664) * [next] fix island hydration inside of `<Markdown>` (#1665) * fix: create rehype plugin to smooth over island hydration bugs * refactor: remove debug code * chore: explain need for `rehypeIslands` * Bugfix: renderer-lit missing files on npm (#1669) * Force Vite to rebuild dependencies (#1670) * [next] Add `preact/compat` renderer (#1668) * feat: add preact/compat entry for `@astrojs/renderer-preact` * Update index.js * Bugfix: plugin-astro-fetch tries to append node-fetch to node-fetch (#1671) * Fix Vite race condition (#1674) * Fix with-nanostore deps (#1675) Adds missing Solid renderer * [next] Fix `resolveDependency` on Windows (#1666) * fix: Windows issue with resolveDependency util * chore: add comment * Update CONTRIBUTING.md (#1677) * Prevent scanning a user's deps (#1678) * Prevent scanning a user's deps * Remove unused things * remove unused util * Adding a changeset for the remark plugin * Config changes needed for stater template (#1680) This does 2 things: 1. Adds prismjs as a dep. 2. Adds shiki as an external. * Next bugs (#1681) * fix(#1679): hoisted <script> rendering * fix(#1679): do not print global for styles, but do for scripts * fix: update ObjectSet implementation * fix: dedupe elements in sets * [next] update compiler (#1683) * chore: update compiler * chore: update compiler (again) * Fix Astro HMR bottleneck (#1684) * Bugfix: JSX renderers can be declared in any order (#1686) * chore: update compiler (#1690) * Exclude lit-server from being optimized (#1691) This should get the lit example working from `npm`. * fix: exclude all renderer server entrypoints (#1692) * chore: update compiler (#1705) * fix: do not crash when Markdown has no content (#1702) * feat: improve support for third-party React packages (#1701) * Remove prism warning when no language is provided (#1703) * Remove prism warning when no language is provided * Add the plaintext language instead * retry deploy * chore: enter prerelease mode under `next` (#1707) * Updates to the changesets (#1708) * Updates to the changesets * Adds a changeset for astro-prism Co-authored-by: Fred K. Schott <fkschott@gmail.com> Co-authored-by: Nate Moore <nate@skypack.dev> Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com> Co-authored-by: Pranav Karawale <52596591+obnoxiousnerd@users.noreply.github.com> Co-authored-by: Matthew Phillips <matthew@skypack.dev> Co-authored-by: Matthew Phillips <matthew@matthewphillips.info> Co-authored-by: Ryan Carniato <ryansolid@gmail.com> Co-authored-by: AsyncBanana <58297401+AsyncBanana@users.noreply.github.com> Co-authored-by: Jonathan Neal <jonathantneal@hotmail.com>
Diffstat (limited to 'packages/markdown/remark/src')
-rw-r--r--packages/markdown/remark/src/index.ts99
-rw-r--r--packages/markdown/remark/src/load-plugins.ts27
-rw-r--r--packages/markdown/remark/src/rehype-collect-headers.ts38
-rw-r--r--packages/markdown/remark/src/rehype-expressions.ts12
-rw-r--r--packages/markdown/remark/src/rehype-islands.ts31
-rw-r--r--packages/markdown/remark/src/rehype-jsx.ts29
-rw-r--r--packages/markdown/remark/src/remark-expressions.ts31
-rw-r--r--packages/markdown/remark/src/remark-jsx.ts31
-rw-r--r--packages/markdown/remark/src/remark-prism.ts73
-rw-r--r--packages/markdown/remark/src/remark-scoped-styles.ts18
-rw-r--r--packages/markdown/remark/src/remark-slug.ts34
-rw-r--r--packages/markdown/remark/src/remark-unwrap.ts38
-rw-r--r--packages/markdown/remark/src/types.ts17
13 files changed, 478 insertions, 0 deletions
diff --git a/packages/markdown/remark/src/index.ts b/packages/markdown/remark/src/index.ts
new file mode 100644
index 000000000..a5fb403d4
--- /dev/null
+++ b/packages/markdown/remark/src/index.ts
@@ -0,0 +1,99 @@
+import type { AstroMarkdownOptions, MarkdownRenderingOptions } from './types';
+
+import createCollectHeaders from './rehype-collect-headers.js';
+import scopedStyles from './remark-scoped-styles.js';
+import { remarkExpressions, loadRemarkExpressions } from './remark-expressions.js';
+import rehypeExpressions from './rehype-expressions.js';
+import rehypeIslands from './rehype-islands.js';
+import { remarkJsx, loadRemarkJsx } from './remark-jsx.js';
+import rehypeJsx from './rehype-jsx.js';
+import remarkPrism from './remark-prism.js';
+import remarkUnwrap from './remark-unwrap.js';
+import { loadPlugins } from './load-plugins.js';
+
+import { unified } from 'unified';
+import markdown from 'remark-parse';
+import markdownToHtml from 'remark-rehype';
+import rehypeStringify from 'rehype-stringify';
+import rehypeRaw from 'rehype-raw';
+import matter from 'gray-matter';
+
+export { AstroMarkdownOptions, MarkdownRenderingOptions };
+
+/** 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-footnotes',
+ // TODO: reenable smartypants!
+ // '@silvenon/remark-smartypants'
+]
+
+export const DEFAULT_REHYPE_PLUGINS = [
+ // empty
+]
+
+/** Shared utility for rendering markdown */
+export async function renderMarkdown(content: string, opts?: MarkdownRenderingOptions | null) {
+ const { remarkPlugins = DEFAULT_REMARK_PLUGINS, rehypePlugins = DEFAULT_REHYPE_PLUGINS } = opts ?? {};
+ const scopedClassName = opts?.$?.scopedClassName;
+ const mode = opts?.mode ?? "mdx";
+ const isMDX = mode === 'mdx';
+ const { headers, rehypeCollectHeaders } = createCollectHeaders();
+
+ await Promise.all([loadRemarkExpressions(), loadRemarkJsx()]); // Vite bug: dynamically import() these because of CJS interop (this will cache)
+
+ let parser = unified()
+ .use(markdown)
+ .use(isMDX ? [remarkJsx] : [])
+ .use(isMDX ? [remarkExpressions] : [])
+ .use([remarkUnwrap])
+
+ const loadedRemarkPlugins = await Promise.all(loadPlugins(remarkPlugins));
+ const loadedRehypePlugins = await Promise.all(loadPlugins(rehypePlugins));
+
+ loadedRemarkPlugins.forEach(([plugin, opts]) => {
+ parser.use([[plugin, opts]]);
+ });
+
+ if (scopedClassName) {
+ parser.use([scopedStyles(scopedClassName)]);
+ }
+
+ parser.use([remarkPrism(scopedClassName)]);
+ parser.use([[markdownToHtml as any, { allowDangerousHtml: true, passThrough: ['raw', 'mdxTextExpression', 'mdxJsxTextElement', 'mdxJsxFlowElement']}]]);
+
+ loadedRehypePlugins.forEach(([plugin, opts]) => {
+ parser.use([[plugin, opts]]);
+ });
+
+ parser
+ .use(isMDX ? [rehypeJsx] : [])
+ .use(isMDX ? [rehypeExpressions] : [])
+ .use(isMDX ? [] : [rehypeRaw])
+ .use(rehypeIslands)
+
+ let result: string;
+ try {
+ const vfile = await parser
+ .use([rehypeCollectHeaders])
+ .use(rehypeStringify, { allowDangerousHtml: true })
+ .process(content);
+ result = vfile.toString();
+ } catch (err) {
+ console.error(err);
+ throw err;
+ }
+
+ return {
+ metadata: { headers, source: content, html: result.toString() },
+ code: result.toString(),
+ };
+}
+
+export default renderMarkdownWithFrontmatter;
diff --git a/packages/markdown/remark/src/load-plugins.ts b/packages/markdown/remark/src/load-plugins.ts
new file mode 100644
index 000000000..6d30e8361
--- /dev/null
+++ b/packages/markdown/remark/src/load-plugins.ts
@@ -0,0 +1,27 @@
+import * as 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, any]>[] {
+ 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/remark/src/rehype-collect-headers.ts b/packages/markdown/remark/src/rehype-collect-headers.ts
new file mode 100644
index 000000000..fc157c9b7
--- /dev/null
+++ b/packages/markdown/remark/src/rehype-collect-headers.ts
@@ -0,0 +1,38 @@
+import { visit } from 'unist-util-visit';
+import type { Root, Properties } from 'hast';
+import slugger from 'github-slugger';
+
+/** */
+export default function createCollectHeaders() {
+ const headers: any[] = [];
+
+ function rehypeCollectHeaders() {
+ return function (tree: Root) {
+ visit(tree, (node) => {
+ if (node.type !== 'element') return;
+ const { tagName } = node;
+ if (tagName[0] !== 'h') return;
+ const [_, level] = tagName.match(/h([0-6])/) ?? [];
+ if (!level) return;
+ const depth = Number.parseInt(level);
+
+ let text = '';
+
+ visit(node, 'text', (child) => {
+ text += child.value;
+ });
+
+ let slug = node?.properties?.id || slugger.slug(text);
+
+ node.properties = node.properties || {};
+ node.properties.id = slug;
+ headers.push({ depth, slug, text });
+ });
+ };
+ }
+
+ return {
+ headers,
+ rehypeCollectHeaders,
+ };
+}
diff --git a/packages/markdown/remark/src/rehype-expressions.ts b/packages/markdown/remark/src/rehype-expressions.ts
new file mode 100644
index 000000000..d296c2afe
--- /dev/null
+++ b/packages/markdown/remark/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 as any).value}}` };
+ }
+ return child;
+ });
+ };
+}
diff --git a/packages/markdown/remark/src/rehype-islands.ts b/packages/markdown/remark/src/rehype-islands.ts
new file mode 100644
index 000000000..099dc4d75
--- /dev/null
+++ b/packages/markdown/remark/src/rehype-islands.ts
@@ -0,0 +1,31 @@
+import {SKIP, visit} from 'unist-util-visit';
+
+// This fixes some confusing bugs coming from somewhere inside of our Markdown pipeline.
+// `unist`/`remark`/`rehype` (not sure) often generate malformed HTML inside of <astro-root>
+// For hydration to work properly, frameworks need the DOM to be the exact same on server/client.
+// This reverts some "helpful corrections" that are applied to our perfectly valid HTML!
+export default function rehypeIslands(): any {
+ return function (node: any): any {
+ return visit(node, 'element', (el) => {
+ // Bugs only happen inside of <astro-root> islands
+ if (el.tagName == 'astro-root') {
+ visit(el, 'text', (child, index, parent) => {
+ if (child.type === 'text') {
+ // Sometimes comments can be trapped as text, which causes them to be escaped
+ // This casts them back to real HTML comments
+ if (parent && child.value.indexOf('<!--') > -1 && index != null) {
+ parent.children.splice(index, 1, { ...child, type: 'comment', value: child.value.replace('<!--', '').replace('-->', '').trim()});
+ return [SKIP, index]
+ }
+ // For some reason `rehype` likes to inject extra linebreaks,
+ // but React and Vue throw hydration errors when they see these!
+ // This removes any extra linebreaks, which is fine because
+ // framework compilers don't preserve them anyway
+ child.value = child.value.replace(/\n+/g, '');
+ return child;
+ }
+ })
+ }
+ });
+ };
+}
diff --git a/packages/markdown/remark/src/rehype-jsx.ts b/packages/markdown/remark/src/rehype-jsx.ts
new file mode 100644
index 000000000..c5270e2af
--- /dev/null
+++ b/packages/markdown/remark/src/rehype-jsx.ts
@@ -0,0 +1,29 @@
+import { map } from 'unist-util-map';
+
+const MDX_ELEMENTS = new Set(['mdxJsxFlowElement', 'mdxJsxTextElement']);
+export default function rehypeJsx(): any {
+ return function (node: any): any {
+ return map(node, (child: any) => {
+ if (child.type === 'element') {
+ return { ...child, tagName: `${child.tagName}` }
+ }
+ if (MDX_ELEMENTS.has(child.type)) {
+ return {
+ ...child,
+ type: 'element',
+ tagName: `${child.name}`,
+ properties: child.attributes.reduce((acc: any[], entry: any) => {
+ let attr = entry.value;
+ if (attr && typeof attr === 'object') {
+ attr = `{${attr.value}}`
+ } else if (attr === null) {
+ attr = `{true}`
+ }
+ return Object.assign(acc, { [entry.name]: attr });
+ }, {})
+ };
+ }
+ return child;
+ });
+ };
+}
diff --git a/packages/markdown/remark/src/remark-expressions.ts b/packages/markdown/remark/src/remark-expressions.ts
new file mode 100644
index 000000000..0966cc0be
--- /dev/null
+++ b/packages/markdown/remark/src/remark-expressions.ts
@@ -0,0 +1,31 @@
+// Vite bug: dynamically import() modules needed for CJS. Cache in memory to keep side effects
+let mdxExpression: any;
+let mdxExpressionFromMarkdown: any;
+let mdxExpressionToMarkdown: any;
+
+export 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 async function loadRemarkExpressions() {
+ if (!mdxExpression) {
+ const micromarkMdxExpression = await import('micromark-extension-mdx-expression');
+ mdxExpression = micromarkMdxExpression.mdxExpression;
+ }
+ if (!mdxExpressionFromMarkdown || !mdxExpressionToMarkdown) {
+ const mdastUtilMdxExpression = await import('mdast-util-mdx-expression');
+ mdxExpressionFromMarkdown = mdastUtilMdxExpression.mdxExpressionFromMarkdown;
+ mdxExpressionToMarkdown = mdastUtilMdxExpression.mdxExpressionToMarkdown;
+ }
+}
diff --git a/packages/markdown/remark/src/remark-jsx.ts b/packages/markdown/remark/src/remark-jsx.ts
new file mode 100644
index 000000000..7c7333781
--- /dev/null
+++ b/packages/markdown/remark/src/remark-jsx.ts
@@ -0,0 +1,31 @@
+// Vite bug: dynamically import() modules needed for CJS. Cache in memory to keep side effects
+let mdxJsx: any;
+let mdxJsxFromMarkdown: any;
+let mdxJsxToMarkdown: any;
+
+export function remarkJsx(this: any, options: any) {
+ let settings = options || {};
+ let data = this.data();
+
+ // TODO this seems to break adding slugs, no idea why add('micromarkExtensions', mdxJsx({}));
+ add('fromMarkdownExtensions', mdxJsxFromMarkdown);
+ add('toMarkdownExtensions', mdxJsxToMarkdown);
+
+ function add(field: any, value: any) {
+ /* istanbul ignore if - other extensions. */
+ if (data[field]) data[field].push(value);
+ else data[field] = [value];
+ }
+}
+
+export async function loadRemarkJsx() {
+ if (!mdxJsx) {
+ const micromarkMdxJsx = await import('micromark-extension-mdx-jsx');
+ mdxJsx = micromarkMdxJsx.mdxJsx;
+ }
+ if (!mdxJsxFromMarkdown || !mdxJsxToMarkdown) {
+ const mdastUtilMdxJsx = await import('mdast-util-mdx-jsx');
+ mdxJsxFromMarkdown = mdastUtilMdxJsx.mdxJsxFromMarkdown;
+ mdxJsxToMarkdown = mdastUtilMdxJsx.mdxJsxToMarkdown;
+ }
+}
diff --git a/packages/markdown/remark/src/remark-prism.ts b/packages/markdown/remark/src/remark-prism.ts
new file mode 100644
index 000000000..d8dd8d922
--- /dev/null
+++ b/packages/markdown/remark/src/remark-prism.ts
@@ -0,0 +1,73 @@
+import { visit } from 'unist-util-visit';
+import Prism from 'prismjs';
+import { addAstro } from '@astrojs/prism';
+import loadLanguages from 'prismjs/components/index.js';
+const noVisit = new Set(['root', 'html', 'text']);
+
+const languageMap = new Map([
+ ['ts', 'typescript']
+]);
+
+function runHighlighter(lang: string, code: string) {
+ let classLanguage = `language-${lang}`
+
+ if (lang == null) {
+ lang = 'plaintext';
+ }
+
+ const ensureLoaded = (lang: string) => {
+ if(lang && !Prism.languages[lang]) {
+ loadLanguages([lang]);
+ }
+ };
+
+ if(languageMap.has(lang)) {
+ ensureLoaded(languageMap.get(lang)!);
+ } else if(lang === 'astro') {
+ ensureLoaded('typescript');
+ addAstro(Prism);
+ } else {
+ ensureLoaded('markup-templating'); // Prism expects this to exist for a number of other langs
+ ensureLoaded(lang);
+ }
+
+ if(lang && !Prism.languages[lang]) {
+ console.warn(`Unable to load the language: ${lang}`);
+ }
+
+ const grammar = Prism.languages[lang];
+ let html = code;
+ if (grammar) {
+ html = Prism.highlight(code, grammar, lang);
+ }
+
+ return { classLanguage, html };
+}
+
+type MaybeString = string | null | undefined;
+
+/** */
+function transformer(className: MaybeString) {
+ return function(tree: any) {
+ const visitor = (node: any) => {
+ let {lang, value} = node;
+ node.type = 'html';
+
+ let { html, classLanguage } = runHighlighter(lang, value);
+ let classes = [classLanguage];
+ if(className) {
+ classes.push(className);
+ }
+ node.value = `<pre class="${classes.join(' ')}"><code data-astro-raw class="${classLanguage}">${html}</code></pre>`;
+ return node;
+ };
+ return visit(tree, 'code', visitor)
+ }
+}
+
+
+function plugin(className: MaybeString) {
+ return transformer.bind(null, className);
+}
+
+export default plugin; \ No newline at end of file
diff --git a/packages/markdown/remark/src/remark-scoped-styles.ts b/packages/markdown/remark/src/remark-scoped-styles.ts
new file mode 100644
index 000000000..9ca70c029
--- /dev/null
+++ b/packages/markdown/remark/src/remark-scoped-styles.ts
@@ -0,0 +1,18 @@
+import { visit } from 'unist-util-visit';
+const noVisit = new Set(['root', 'html', 'text']);
+
+/** */
+export default function scopedStyles(className: string) {
+ const visitor = (node: any) => {
+ if (noVisit.has(node.type)) return;
+
+ const { data } = node;
+ let currentClassName = data?.hProperties?.class ?? '';
+ node.data = node.data || {};
+ node.data.hProperties = node.data.hProperties || {};
+ node.data.hProperties.class = `${className} ${currentClassName}`.trim();
+
+ return node;
+ };
+ return () => (tree: any) => visit(tree, visitor);
+}
diff --git a/packages/markdown/remark/src/remark-slug.ts b/packages/markdown/remark/src/remark-slug.ts
new file mode 100644
index 000000000..b7c9c29de
--- /dev/null
+++ b/packages/markdown/remark/src/remark-slug.ts
@@ -0,0 +1,34 @@
+/**
+ * @typedef {import('mdast').Root} Root
+ * @typedef {import('hast').Properties} Properties
+ */
+
+import {toString} from 'mdast-util-to-string'
+import {visit} from 'unist-util-visit'
+import BananaSlug from 'github-slugger'
+
+const slugs = new BananaSlug()
+
+/**
+ * Plugin to add anchors headings using GitHub’s algorithm.
+ *
+ * @type {import('unified').Plugin<void[], Root>}
+ */
+export default function remarkSlug() {
+ return (tree: any) => {
+ slugs.reset()
+ visit(tree, (node) => {
+ console.log(node);
+ });
+ visit(tree, 'heading', (node) => {
+ const data = node.data || (node.data = {})
+ const props = /** @type {Properties} */ (
+ data.hProperties || (data.hProperties = {})
+ )
+ let id = props.id
+ id = id ? slugs.slug(String(id), true) : slugs.slug(toString(node))
+ data.id = id;
+ props.id = id;
+ })
+ }
+}
diff --git a/packages/markdown/remark/src/remark-unwrap.ts b/packages/markdown/remark/src/remark-unwrap.ts
new file mode 100644
index 000000000..e43a57a0c
--- /dev/null
+++ b/packages/markdown/remark/src/remark-unwrap.ts
@@ -0,0 +1,38 @@
+import {visit, SKIP} from 'unist-util-visit'
+
+// Remove the wrapping paragraph for <astro-root> islands
+export default function remarkUnwrap() {
+ const astroRootNodes = new Set();
+ let insideAstroRoot = false;
+
+ return (tree: any) => {
+ // reset state
+ insideAstroRoot = false;
+ astroRootNodes.clear();
+
+ visit(tree, 'html', (node) => {
+ if (node.value.indexOf('<astro-root') > -1 && !insideAstroRoot) {
+ insideAstroRoot = true;
+ }
+ if (node.value.indexOf('</astro-root') > -1 && insideAstroRoot) {
+ insideAstroRoot = false;
+ }
+ astroRootNodes.add(node);
+ })
+
+ visit(tree, 'paragraph', (node, index, parent) => {
+ if (
+ parent &&
+ typeof index === 'number' &&
+ containsAstroRootNode(node)
+ ) {
+ parent.children.splice(index, 1, ...node.children)
+ return [SKIP, index]
+ }
+ })
+ }
+
+ function containsAstroRootNode(node: any) {
+ return node.children.map((child: any) => astroRootNodes.has(child)).reduce((all: boolean, v: boolean) => all ? all : v, false)
+ }
+}
diff --git a/packages/markdown/remark/src/types.ts b/packages/markdown/remark/src/types.ts
new file mode 100644
index 000000000..201e50931
--- /dev/null
+++ b/packages/markdown/remark/src/types.ts
@@ -0,0 +1,17 @@
+import * as unified from 'unified';
+
+export type UnifiedPluginImport = Promise<{ default: unified.Plugin }>;
+export type Plugin = string | [string, any] | UnifiedPluginImport | [UnifiedPluginImport, any];
+
+export interface AstroMarkdownOptions {
+ mode?: 'md'|'mdx';
+ remarkPlugins?: Plugin[];
+ rehypePlugins?: Plugin[];
+}
+
+export interface MarkdownRenderingOptions extends Partial<AstroMarkdownOptions> {
+ /** @internal */
+ $?: {
+ scopedClassName: string | null;
+ };
+}