From b695c8aa15cc222c67569063890b5b2d42b00114 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Tue, 19 Oct 2021 16:01:43 -0400 Subject: [PATCH] Add Prism syntax highlighting (#1598) --- packages/astro-prism/index.d.ts | 1 + packages/astro-prism/package.json | 1 + packages/astro/test/astro-markdown.test.js | 2 +- .../src/{skipped-pages => pages}/code.astro | 0 packages/markdown/remark/package.json | 5 +- packages/markdown/remark/src/index.ts | 12 ++-- packages/markdown/remark/src/remark-prism.ts | 65 +++++++++++++++++++ yarn.lock | 7 +- 8 files changed, 85 insertions(+), 8 deletions(-) create mode 100644 packages/astro-prism/index.d.ts rename packages/astro/test/fixtures/astro-markdown/src/{skipped-pages => pages}/code.astro (100%) create mode 100644 packages/markdown/remark/src/remark-prism.ts diff --git a/packages/astro-prism/index.d.ts b/packages/astro-prism/index.d.ts new file mode 100644 index 000000000..eb260d9b9 --- /dev/null +++ b/packages/astro-prism/index.d.ts @@ -0,0 +1 @@ +export function addAstro(Prism: any): void; \ No newline at end of file diff --git a/packages/astro-prism/package.json b/packages/astro-prism/package.json index cc84024e6..675806a35 100644 --- a/packages/astro-prism/package.json +++ b/packages/astro-prism/package.json @@ -15,6 +15,7 @@ "exports": { ".": "./index.mjs" }, + "types": "./index.d.ts", "keywords": [], "author": "Skypack", "license": "MIT", diff --git a/packages/astro/test/astro-markdown.test.js b/packages/astro/test/astro-markdown.test.js index 4492a735b..512a85374 100644 --- a/packages/astro/test/astro-markdown.test.js +++ b/packages/astro/test/astro-markdown.test.js @@ -46,7 +46,7 @@ describe('Astro Markdown', () => { }); // This doesn't work because the markdown plugin doesn't have Prism support yet. - it.skip('Runs code blocks through syntax highlighter', async () => { + it('Runs code blocks through syntax highlighter', async () => { const html = await fixture.readFile('/code/index.html'); const $ = cheerio.load(html); diff --git a/packages/astro/test/fixtures/astro-markdown/src/skipped-pages/code.astro b/packages/astro/test/fixtures/astro-markdown/src/pages/code.astro similarity index 100% rename from packages/astro/test/fixtures/astro-markdown/src/skipped-pages/code.astro rename to packages/astro/test/fixtures/astro-markdown/src/pages/code.astro diff --git a/packages/markdown/remark/package.json b/packages/markdown/remark/package.json index ce7c9d3ac..32f12450b 100644 --- a/packages/markdown/remark/package.json +++ b/packages/markdown/remark/package.json @@ -18,13 +18,15 @@ "dev": "astro-scripts dev \"src/**/*.ts\"" }, "dependencies": { + "@astrojs/prism": "^0.2.2", "@silvenon/remark-smartypants": "^1.0.0", "assert": "^2.0.0", "github-slugger": "^1.3.0", "mdast-util-mdx-expression": "^1.1.0", + "mdast-util-mdx-jsx": "^1.1.0", "micromark-extension-mdx-expression": "^1.0.0", "micromark-extension-mdx-jsx": "^1.0.0", - "mdast-util-mdx-jsx": "^1.1.0", + "prismjs": "^1.25.0", "rehype-raw": "^6.0.0", "rehype-stringify": "^9.0.1", "remark-footnotes": "^4.0.1", @@ -40,6 +42,7 @@ "//": "Important that gray-matter is in devDependencies so it gets bundled by esbuild!", "devDependencies": { "@types/github-slugger": "^1.3.0", + "@types/prismjs": "^1.16.6", "gray-matter": "^4.0.3" } } diff --git a/packages/markdown/remark/src/index.ts b/packages/markdown/remark/src/index.ts index c470d6a0f..090442352 100644 --- a/packages/markdown/remark/src/index.ts +++ b/packages/markdown/remark/src/index.ts @@ -7,6 +7,7 @@ import rehypeExpressions from './rehype-expressions.js'; import { remarkJsx, loadRemarkJsx } from './remark-jsx.js'; import rehypeJsx from './rehype-jsx.js'; //import { remarkCodeBlock } from './codeblock.js'; +import remarkPrism from './remark-prism.js'; import { loadPlugins } from './load-plugins.js'; import { unified } from 'unified'; @@ -46,12 +47,13 @@ export async function renderMarkdown(content: string, opts?: MarkdownRenderingOp .use(markdown) .use([remarkJsx]) .use([remarkExpressions]) + .use([remarkPrism]) const loadedRemarkPlugins = await Promise.all(loadPlugins(remarkPlugins)); const loadedRehypePlugins = await Promise.all(loadPlugins(rehypePlugins)); loadedRemarkPlugins.forEach(([plugin, opts]) => { - parser.use(plugin, opts); + parser.use([[plugin, opts]]); }); // if (scopedClassName) { @@ -59,18 +61,18 @@ export async function renderMarkdown(content: string, opts?: MarkdownRenderingOp // } //parser.use(remarkCodeBlock); - parser.use(markdownToHtml, { allowDangerousHtml: true, passThrough: ['raw', 'mdxTextExpression', 'mdxJsxTextElement', 'mdxJsxFlowElement']}); + parser.use([[markdownToHtml as any, { allowDangerousHtml: true, passThrough: ['raw', 'mdxTextExpression', 'mdxJsxTextElement', 'mdxJsxFlowElement']}]]); loadedRehypePlugins.forEach(([plugin, opts]) => { - parser.use(plugin, opts); + parser.use([[plugin, opts]]); }); - parser.use(rehypeJsx).use(rehypeExpressions) + parser.use([rehypeJsx]).use(rehypeExpressions) let result: string; try { const vfile = await parser - .use(rehypeCollectHeaders) + .use([rehypeCollectHeaders]) .use(rehypeStringify, { allowParseErrors: true, preferUnquoted: true, allowDangerousHtml: true }) .process(content); result = vfile.toString(); diff --git a/packages/markdown/remark/src/remark-prism.ts b/packages/markdown/remark/src/remark-prism.ts new file mode 100644 index 000000000..4e6da3810 --- /dev/null +++ b/packages/markdown/remark/src/remark-prism.ts @@ -0,0 +1,65 @@ +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) { + console.warn('Prism.astro: No language provided.'); + } + + 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 }; +} + +/** */ +function transformer(tree: any) { + const visitor = (node: any) => { + let {lang, value} = node; + node.type = 'html'; + + let { html, classLanguage } = runHighlighter(lang, value); + node.value = `
${html}
`; + return node; + }; + return visit(tree, 'code', visitor) +} + + +function plugin() { + return transformer; +} + +export default plugin; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index c611db918..258943b76 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1963,6 +1963,11 @@ resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.1.tgz#f8ae4fbcd2b9ba4ff934698e28778961f9cb22ca" integrity sha512-ARATsLdrGPUnaBvxLhUlnltcMgn7pQG312S8ccdYlnyijabrX9RN/KN/iGj9Am96CoW8e/K9628BA7Bv4XHdrA== +"@types/prismjs@^1.16.6": + version "1.16.6" + resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.16.6.tgz#377054f72f671b36dbe78c517ce2b279d83ecc40" + integrity sha512-dTvnamRITNqNkqhlBd235kZl3KfVJQQoT5jkXeiWSBK7i4/TLKBNLV0S1wOt8gy4E2TY722KLtdmv2xc6+Wevg== + "@types/prompts@^2.0.12": version "2.0.14" resolved "https://registry.yarnpkg.com/@types/prompts/-/prompts-2.0.14.tgz#10cb8899844bb0771cabe57c1becaaaca9a3b521" @@ -8782,7 +8787,7 @@ pretty-hrtime@^1.0.3: resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= -prismjs@^1.23.0: +prismjs@^1.23.0, prismjs@^1.25.0: version "1.25.0" resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.25.0.tgz#6f822df1bdad965734b310b315a23315cf999756" integrity sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg==