diff options
author | 2021-10-29 13:30:22 -0600 | |
---|---|---|
committer | 2021-10-29 15:30:22 -0400 | |
commit | d84bfe719a546ad855640338d5ed49ad3aa4ccb4 (patch) | |
tree | ac63062651c36bcd73194c2950e160d225b5575b /packages/markdown/remark/src | |
parent | 61e6bff5b1bac9f48e31ce10236858c420cd6ce7 (diff) | |
download | astro-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.ts | 99 | ||||
-rw-r--r-- | packages/markdown/remark/src/load-plugins.ts | 27 | ||||
-rw-r--r-- | packages/markdown/remark/src/rehype-collect-headers.ts | 38 | ||||
-rw-r--r-- | packages/markdown/remark/src/rehype-expressions.ts | 12 | ||||
-rw-r--r-- | packages/markdown/remark/src/rehype-islands.ts | 31 | ||||
-rw-r--r-- | packages/markdown/remark/src/rehype-jsx.ts | 29 | ||||
-rw-r--r-- | packages/markdown/remark/src/remark-expressions.ts | 31 | ||||
-rw-r--r-- | packages/markdown/remark/src/remark-jsx.ts | 31 | ||||
-rw-r--r-- | packages/markdown/remark/src/remark-prism.ts | 73 | ||||
-rw-r--r-- | packages/markdown/remark/src/remark-scoped-styles.ts | 18 | ||||
-rw-r--r-- | packages/markdown/remark/src/remark-slug.ts | 34 | ||||
-rw-r--r-- | packages/markdown/remark/src/remark-unwrap.ts | 38 | ||||
-rw-r--r-- | packages/markdown/remark/src/types.ts | 17 |
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; + }; +} |