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: {
|
||||
'.jsx': 'react',
|
||||
'.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>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{content.title}</title>
|
||||
<link rel="stylesheet" href="/styles/global.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<slot/>
|
||||
</body>
|
||||
|
|
|
@ -21,6 +21,8 @@ const items = ['A', 'B', 'C'];
|
|||
|
||||
The best part? It comes with all the Astro features you expect.
|
||||
|
||||
[Other example](./other)
|
||||
|
||||
## Embed framework components
|
||||
|
||||
<ReactCounter:visible />
|
||||
|
@ -40,5 +42,15 @@ const items = ['A', 'B', 'C'];
|
|||
### Markdown can be embedded in any child component
|
||||
</ReactCounter:visible>
|
||||
|
||||
## Code
|
||||
|
||||
Should work!
|
||||
|
||||
```js
|
||||
import Something from './another';
|
||||
|
||||
const thing = new Something();
|
||||
```
|
||||
|
||||
</Markdown>
|
||||
</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}` : '';
|
||||
---
|
||||
|
||||
<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 { CompileOptions } from '../../@types/compiler';
|
||||
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 eslexer from 'es-module-lexer';
|
||||
|
@ -18,6 +18,8 @@ import { fetchContent } from './content.js';
|
|||
import { isFetchContent } from './utils.js';
|
||||
import { yellow } from 'kleur/colors';
|
||||
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;
|
||||
|
||||
|
@ -186,6 +188,7 @@ interface CompileResult {
|
|||
|
||||
interface CodegenState {
|
||||
filename: string;
|
||||
fileID: string;
|
||||
components: Components;
|
||||
css: string[];
|
||||
markers: {
|
||||
|
@ -418,7 +421,7 @@ function dedent(str: string) {
|
|||
/** Compile page markup */
|
||||
async function compileHtml(enterNode: TemplateNode, state: CodegenState, compileOptions: CompileOptions): Promise<string> {
|
||||
return new Promise((resolve) => {
|
||||
const { components, css, importExportStatements, filename } = state;
|
||||
const { components, css, importExportStatements, filename, fileID } = state;
|
||||
const { astroConfig } = compileOptions;
|
||||
|
||||
let paren = -1;
|
||||
|
@ -438,7 +441,18 @@ async function compileHtml(enterNode: TemplateNode, state: CodegenState, compile
|
|||
mode: 'astro-md',
|
||||
$: { scopedClassName: scopedClassName.slice(1, -1) },
|
||||
});
|
||||
|
||||
// 1. Parse
|
||||
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);
|
||||
|
||||
buffers.out += ',' + result;
|
||||
|
@ -476,7 +490,26 @@ async function compileHtml(enterNode: TemplateNode, state: CodegenState, compile
|
|||
break;
|
||||
case 'Slot':
|
||||
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 'Element': {
|
||||
const name: string = node.name;
|
||||
|
@ -590,9 +623,15 @@ async function compileHtml(enterNode: TemplateNode, state: CodegenState, compile
|
|||
case 'Head':
|
||||
case 'Body':
|
||||
case 'Title':
|
||||
case 'Element':
|
||||
case 'Element': {
|
||||
if (paren !== -1) {
|
||||
buffers.out += ')';
|
||||
paren--;
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 'InlineComponent': {
|
||||
if (node.type === 'InlineComponent' && curr === 'markdown' && buffers.markdown !== '') {
|
||||
if (curr === 'markdown' && buffers.markdown !== '') {
|
||||
await pushMarkdownToBuffer();
|
||||
}
|
||||
if (paren !== -1) {
|
||||
|
@ -626,11 +665,12 @@ async function compileHtml(enterNode: TemplateNode, state: CodegenState, compile
|
|||
* @param {Ast} AST The parsed AST to crawl
|
||||
* @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;
|
||||
|
||||
const state: CodegenState = {
|
||||
filename,
|
||||
fileID,
|
||||
components: new Map(),
|
||||
css: [],
|
||||
markers: {
|
||||
|
|
|
@ -2,7 +2,7 @@ import type { Transformer } from '../../@types/transformer';
|
|||
import type { Script, TemplateNode } from 'astro-parser';
|
||||
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['"]/;
|
||||
/** escaping code samples that contain template string replacement parts, ${foo} or example. */
|
||||
function escape(code: string) {
|
||||
|
@ -100,8 +100,8 @@ export default function (module: Script): Transformer {
|
|||
},
|
||||
async finalize() {
|
||||
// Add the Prism import if needed.
|
||||
if (usesPrism && !prismImportExp.test(module.content)) {
|
||||
module.content = PRISM_IMPORT + module.content;
|
||||
if (usesPrism && module && !prismImportExp.test(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');
|
||||
});
|
||||
|
||||
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) => {
|
||||
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