Fix codeblocks in markdown components (#264)
* Fix codeblocks in markdown components * Debugging * More debugging * remove extra debugging stuff
This commit is contained in:
parent
3dc141b868
commit
dd7cc798e0
11 changed files with 340 additions and 12 deletions
|
@ -2,5 +2,6 @@ export default {
|
||||||
extensions: {
|
extensions: {
|
||||||
'.jsx': 'react',
|
'.jsx': 'react',
|
||||||
'.tsx': 'preact',
|
'.tsx': 'preact',
|
||||||
}
|
},
|
||||||
|
public: './public'
|
||||||
};
|
};
|
||||||
|
|
6
examples/astro-markdown/public/styles/global.scss
Normal file
6
examples/astro-markdown/public/styles/global.scss
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
@use "./prism.scss";
|
||||||
|
|
||||||
|
body {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
228
examples/astro-markdown/public/styles/prism.scss
Normal file
228
examples/astro-markdown/public/styles/prism.scss
Normal file
|
@ -0,0 +1,228 @@
|
||||||
|
pre,
|
||||||
|
code {
|
||||||
|
color: #d4d4d4;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||||
|
line-height: 1.5;
|
||||||
|
direction: ltr;
|
||||||
|
white-space: pre;
|
||||||
|
text-align: left;
|
||||||
|
text-shadow: none;
|
||||||
|
word-break: normal;
|
||||||
|
word-spacing: normal;
|
||||||
|
-moz-tab-size: 4;
|
||||||
|
-o-tab-size: 4;
|
||||||
|
tab-size: 4;
|
||||||
|
-webkit-hyphens: none;
|
||||||
|
-moz-hyphens: none;
|
||||||
|
-ms-hyphens: none;
|
||||||
|
hyphens: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre::selection,
|
||||||
|
code::selection {
|
||||||
|
text-shadow: none;
|
||||||
|
background: #b3d4fc;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
pre,
|
||||||
|
code {
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
margin: 0.5rem 0 16px;
|
||||||
|
padding: 0.8rem 1rem 0.9rem;
|
||||||
|
overflow: auto;
|
||||||
|
background: #282a36;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:not(pre) > code {
|
||||||
|
padding: 0.1em 0.3em;
|
||||||
|
color: #db4c69;
|
||||||
|
background: #f9f2f4;
|
||||||
|
border-radius: 0.3em;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************************************************
|
||||||
|
* Tokens
|
||||||
|
*/
|
||||||
|
.namespace {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.comment,
|
||||||
|
.token.prolog,
|
||||||
|
.token.doctype,
|
||||||
|
.token.cdata {
|
||||||
|
color: #6a9955;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.punctuation {
|
||||||
|
color: #d4d4d4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.property,
|
||||||
|
.token.tag,
|
||||||
|
.token.boolean,
|
||||||
|
.token.number,
|
||||||
|
.token.constant,
|
||||||
|
.token.symbol,
|
||||||
|
.token.deleted {
|
||||||
|
color: #b5cea8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.selector,
|
||||||
|
.token.attr-name,
|
||||||
|
.token.string,
|
||||||
|
.token.char,
|
||||||
|
.token.builtin,
|
||||||
|
.token.inserted {
|
||||||
|
color: #ce9178;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.operator,
|
||||||
|
.token.entity,
|
||||||
|
.token.url,
|
||||||
|
.language-css .token.string,
|
||||||
|
.style .token.string {
|
||||||
|
color: #d4d4d4;
|
||||||
|
background: rgb(45, 55, 72);
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.atrule,
|
||||||
|
.token.attr-value,
|
||||||
|
.token.keyword {
|
||||||
|
color: #c586c0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.function {
|
||||||
|
color: #dcdcaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.regex,
|
||||||
|
.token.important,
|
||||||
|
.token.variable {
|
||||||
|
color: #d16969;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.important,
|
||||||
|
.token.bold {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.italic {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.constant {
|
||||||
|
color: #9cdcfe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.class-name {
|
||||||
|
color: #4ec9b0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.parameter {
|
||||||
|
color: #9cdcfe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.interpolation {
|
||||||
|
color: #9cdcfe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.punctuation.interpolation-punctuation {
|
||||||
|
color: #569cd6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.boolean {
|
||||||
|
color: #569cd6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.property {
|
||||||
|
color: #9cdcfe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.selector {
|
||||||
|
color: #d7ba7d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.tag {
|
||||||
|
color: #569cd6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.attr-name {
|
||||||
|
color: #9cdcfe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.attr-value {
|
||||||
|
color: #ce9178;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.entity {
|
||||||
|
color: #4ec9b0;
|
||||||
|
cursor: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.namespace {
|
||||||
|
color: #4ec9b0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************************************************
|
||||||
|
* Language Specific
|
||||||
|
*/
|
||||||
|
pre[class*='language-javascript'],
|
||||||
|
code[class*='language-javascript'] {
|
||||||
|
color: #4ec9b0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre[class*='language-css'],
|
||||||
|
code[class*='language-css'] {
|
||||||
|
color: #ce9178;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre[class*='language-html'],
|
||||||
|
code[class*='language-html'] {
|
||||||
|
color: #d4d4d4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-html .token.punctuation {
|
||||||
|
color: #808080;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************************************************
|
||||||
|
* Line highlighting
|
||||||
|
*/
|
||||||
|
pre[data-line] {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre > code {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-highlight {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 0;
|
||||||
|
margin-top: 1em;
|
||||||
|
padding: inherit 0;
|
||||||
|
line-height: inherit;
|
||||||
|
white-space: pre;
|
||||||
|
background: #f7ebc6;
|
||||||
|
box-shadow: inset 5px 0 0 #f7d87c;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre[class*='language-bash'] .token.function {
|
||||||
|
color: #d4d4d4;
|
||||||
|
}
|
||||||
|
.token.comment {
|
||||||
|
color: #fff7;
|
||||||
|
}
|
|
@ -4,9 +4,10 @@ export let content;
|
||||||
|
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
<title>{content.title}</title>
|
<title>{content.title}</title>
|
||||||
|
<link rel="stylesheet" href="/styles/global.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<slot/>
|
<slot/>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -21,6 +21,8 @@ const items = ['A', 'B', 'C'];
|
||||||
|
|
||||||
The best part? It comes with all the Astro features you expect.
|
The best part? It comes with all the Astro features you expect.
|
||||||
|
|
||||||
|
[Other example](./other)
|
||||||
|
|
||||||
## Embed framework components
|
## Embed framework components
|
||||||
|
|
||||||
<ReactCounter:visible />
|
<ReactCounter:visible />
|
||||||
|
@ -40,5 +42,15 @@ const items = ['A', 'B', 'C'];
|
||||||
### Markdown can be embedded in any child component
|
### Markdown can be embedded in any child component
|
||||||
</ReactCounter:visible>
|
</ReactCounter:visible>
|
||||||
|
|
||||||
|
## Code
|
||||||
|
|
||||||
|
Should work!
|
||||||
|
|
||||||
|
```js
|
||||||
|
import Something from './another';
|
||||||
|
|
||||||
|
const thing = new Something();
|
||||||
|
```
|
||||||
|
|
||||||
</Markdown>
|
</Markdown>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
18
examples/astro-markdown/src/pages/other.md
Normal file
18
examples/astro-markdown/src/pages/other.md
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
---
|
||||||
|
title: Some Markdown Page
|
||||||
|
layout: ../layouts/main.astro
|
||||||
|
---
|
||||||
|
|
||||||
|
# Code
|
||||||
|
|
||||||
|
```js
|
||||||
|
var foo = 'bar';
|
||||||
|
|
||||||
|
function doSomething() {
|
||||||
|
return foo;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Paragraph
|
||||||
|
|
||||||
|
text here.
|
|
@ -43,4 +43,4 @@ if (grammar) {
|
||||||
let className = lang ? `language-${lang}` : '';
|
let className = lang ? `language-${lang}` : '';
|
||||||
---
|
---
|
||||||
|
|
||||||
<pre class={className}><code class={className} />{html}</pre>
|
<pre class={className}><code class={className}>{html}</code></pre>
|
|
@ -1,7 +1,7 @@
|
||||||
import type { Ast, Script, Style, TemplateNode } from 'astro-parser';
|
import type { Ast, Script, Style, TemplateNode } from 'astro-parser';
|
||||||
import type { CompileOptions } from '../../@types/compiler';
|
import type { CompileOptions } from '../../@types/compiler';
|
||||||
import type { AstroConfig, AstroMarkdownOptions, TransformResult, ComponentInfo, Components } from '../../@types/astro';
|
import type { AstroConfig, AstroMarkdownOptions, TransformResult, ComponentInfo, Components } from '../../@types/astro';
|
||||||
import type { ImportDeclaration, ExportNamedDeclaration, VariableDeclarator, Identifier } from '@babel/types';
|
import type { ImportDeclaration, ExportNamedDeclaration, VariableDeclarator, Identifier, ImportDefaultSpecifier } from '@babel/types';
|
||||||
|
|
||||||
import 'source-map-support/register.js';
|
import 'source-map-support/register.js';
|
||||||
import eslexer from 'es-module-lexer';
|
import eslexer from 'es-module-lexer';
|
||||||
|
@ -18,6 +18,8 @@ import { fetchContent } from './content.js';
|
||||||
import { isFetchContent } from './utils.js';
|
import { isFetchContent } from './utils.js';
|
||||||
import { yellow } from 'kleur/colors';
|
import { yellow } from 'kleur/colors';
|
||||||
import { isComponentTag, renderMarkdown } from '../utils';
|
import { isComponentTag, renderMarkdown } from '../utils';
|
||||||
|
import { transform } from '../transform/index.js';
|
||||||
|
import { PRISM_IMPORT } from '../transform/prism.js';
|
||||||
|
|
||||||
const traverse: typeof babelTraverse.default = (babelTraverse.default as any).default;
|
const traverse: typeof babelTraverse.default = (babelTraverse.default as any).default;
|
||||||
|
|
||||||
|
@ -186,6 +188,7 @@ interface CompileResult {
|
||||||
|
|
||||||
interface CodegenState {
|
interface CodegenState {
|
||||||
filename: string;
|
filename: string;
|
||||||
|
fileID: string;
|
||||||
components: Components;
|
components: Components;
|
||||||
css: string[];
|
css: string[];
|
||||||
markers: {
|
markers: {
|
||||||
|
@ -418,7 +421,7 @@ function dedent(str: string) {
|
||||||
/** Compile page markup */
|
/** Compile page markup */
|
||||||
async function compileHtml(enterNode: TemplateNode, state: CodegenState, compileOptions: CompileOptions): Promise<string> {
|
async function compileHtml(enterNode: TemplateNode, state: CodegenState, compileOptions: CompileOptions): Promise<string> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const { components, css, importExportStatements, filename } = state;
|
const { components, css, importExportStatements, filename, fileID } = state;
|
||||||
const { astroConfig } = compileOptions;
|
const { astroConfig } = compileOptions;
|
||||||
|
|
||||||
let paren = -1;
|
let paren = -1;
|
||||||
|
@ -438,7 +441,18 @@ async function compileHtml(enterNode: TemplateNode, state: CodegenState, compile
|
||||||
mode: 'astro-md',
|
mode: 'astro-md',
|
||||||
$: { scopedClassName: scopedClassName.slice(1, -1) },
|
$: { scopedClassName: scopedClassName.slice(1, -1) },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 1. Parse
|
||||||
const ast = parse(rendered);
|
const ast = parse(rendered);
|
||||||
|
// 2. Transform the AST
|
||||||
|
|
||||||
|
await transform(ast, {
|
||||||
|
compileOptions,
|
||||||
|
filename,
|
||||||
|
fileID
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. Codegen
|
||||||
const result = await compileHtml(ast.html, { ...state, markers: { ...state.markers, insideMarkdown: false } }, compileOptions);
|
const result = await compileHtml(ast.html, { ...state, markers: { ...state.markers, insideMarkdown: false } }, compileOptions);
|
||||||
|
|
||||||
buffers.out += ',' + result;
|
buffers.out += ',' + result;
|
||||||
|
@ -476,7 +490,26 @@ async function compileHtml(enterNode: TemplateNode, state: CodegenState, compile
|
||||||
break;
|
break;
|
||||||
case 'Slot':
|
case 'Slot':
|
||||||
case 'Head':
|
case 'Head':
|
||||||
case 'InlineComponent':
|
case 'InlineComponent': {
|
||||||
|
switch(node.name) {
|
||||||
|
case 'Prism': {
|
||||||
|
if(!importExportStatements.has(PRISM_IMPORT)) {
|
||||||
|
importExportStatements.add(PRISM_IMPORT);
|
||||||
|
}
|
||||||
|
if(!components.has('Prism')) {
|
||||||
|
components.set('Prism', {
|
||||||
|
importSpecifier: {
|
||||||
|
type: 'ImportDefaultSpecifier',
|
||||||
|
local: { type: 'Identifier', name: 'Prism' } as Identifier,
|
||||||
|
} as ImportDefaultSpecifier,
|
||||||
|
url: 'astro/components/Prism.astro'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Do not break.
|
||||||
|
}
|
||||||
case 'Title':
|
case 'Title':
|
||||||
case 'Element': {
|
case 'Element': {
|
||||||
const name: string = node.name;
|
const name: string = node.name;
|
||||||
|
@ -590,9 +623,15 @@ async function compileHtml(enterNode: TemplateNode, state: CodegenState, compile
|
||||||
case 'Head':
|
case 'Head':
|
||||||
case 'Body':
|
case 'Body':
|
||||||
case 'Title':
|
case 'Title':
|
||||||
case 'Element':
|
case 'Element': {
|
||||||
|
if (paren !== -1) {
|
||||||
|
buffers.out += ')';
|
||||||
|
paren--;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
case 'InlineComponent': {
|
case 'InlineComponent': {
|
||||||
if (node.type === 'InlineComponent' && curr === 'markdown' && buffers.markdown !== '') {
|
if (curr === 'markdown' && buffers.markdown !== '') {
|
||||||
await pushMarkdownToBuffer();
|
await pushMarkdownToBuffer();
|
||||||
}
|
}
|
||||||
if (paren !== -1) {
|
if (paren !== -1) {
|
||||||
|
@ -626,11 +665,12 @@ async function compileHtml(enterNode: TemplateNode, state: CodegenState, compile
|
||||||
* @param {Ast} AST The parsed AST to crawl
|
* @param {Ast} AST The parsed AST to crawl
|
||||||
* @param {object} CodeGenOptions
|
* @param {object} CodeGenOptions
|
||||||
*/
|
*/
|
||||||
export async function codegen(ast: Ast, { compileOptions, filename }: CodeGenOptions): Promise<TransformResult> {
|
export async function codegen(ast: Ast, { compileOptions, filename, fileID }: CodeGenOptions): Promise<TransformResult> {
|
||||||
await eslexer.init;
|
await eslexer.init;
|
||||||
|
|
||||||
const state: CodegenState = {
|
const state: CodegenState = {
|
||||||
filename,
|
filename,
|
||||||
|
fileID,
|
||||||
components: new Map(),
|
components: new Map(),
|
||||||
css: [],
|
css: [],
|
||||||
markers: {
|
markers: {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import type { Transformer } from '../../@types/transformer';
|
||||||
import type { Script, TemplateNode } from 'astro-parser';
|
import type { Script, TemplateNode } from 'astro-parser';
|
||||||
import { getAttrValue } from '../../ast.js';
|
import { getAttrValue } from '../../ast.js';
|
||||||
|
|
||||||
const PRISM_IMPORT = `import Prism from 'astro/components/Prism.astro';\n`;
|
export const PRISM_IMPORT = `import Prism from 'astro/components/Prism.astro';`;
|
||||||
const prismImportExp = /import Prism from ['"]astro\/components\/Prism.astro['"]/;
|
const prismImportExp = /import Prism from ['"]astro\/components\/Prism.astro['"]/;
|
||||||
/** escaping code samples that contain template string replacement parts, ${foo} or example. */
|
/** escaping code samples that contain template string replacement parts, ${foo} or example. */
|
||||||
function escape(code: string) {
|
function escape(code: string) {
|
||||||
|
@ -100,8 +100,8 @@ export default function (module: Script): Transformer {
|
||||||
},
|
},
|
||||||
async finalize() {
|
async finalize() {
|
||||||
// Add the Prism import if needed.
|
// Add the Prism import if needed.
|
||||||
if (usesPrism && !prismImportExp.test(module.content)) {
|
if (usesPrism && module && !prismImportExp.test(module.content)) {
|
||||||
module.content = PRISM_IMPORT + module.content;
|
module.content = PRISM_IMPORT + '\n' + module.content;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,6 +26,15 @@ Markdown('Can load more complex jsxy stuff', async ({ runtime }) => {
|
||||||
assert.equal($el.text(), 'Hello world');
|
assert.equal($el.text(), 'Hello world');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Markdown('Runs code blocks through syntax highlighter', async ({ runtime }) => {
|
||||||
|
const result = await runtime.load('/code');
|
||||||
|
if (result.error) throw new Error(result.error);
|
||||||
|
|
||||||
|
const $ = doc(result.contents);
|
||||||
|
const $el = $('code span');
|
||||||
|
assert.ok($el.length > 0, 'There are child spans in code blocks');
|
||||||
|
});
|
||||||
|
|
||||||
Markdown('Bundles client-side JS for prod', async (context) => {
|
Markdown('Bundles client-side JS for prod', async (context) => {
|
||||||
await context.build();
|
await context.build();
|
||||||
|
|
||||||
|
|
13
packages/astro/test/fixtures/astro-markdown/src/pages/code.astro
vendored
Normal file
13
packages/astro/test/fixtures/astro-markdown/src/pages/code.astro
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
import Markdown from 'astro/components/Markdown.astro';
|
||||||
|
export const title = 'My Blog Post';
|
||||||
|
export const description = 'This is a post about some stuff.';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Markdown>
|
||||||
|
## Interesting Topic
|
||||||
|
|
||||||
|
```js
|
||||||
|
const thing = () => {};
|
||||||
|
```
|
||||||
|
</Markdown>
|
Loading…
Reference in a new issue