diff --git a/examples/astro-markdown/astro.config.mjs b/examples/astro-markdown/astro.config.mjs
index e73531d1a0..a66babaf3e 100644
--- a/examples/astro-markdown/astro.config.mjs
+++ b/examples/astro-markdown/astro.config.mjs
@@ -2,5 +2,6 @@ export default {
extensions: {
'.jsx': 'react',
'.tsx': 'preact',
- }
+ },
+ public: './public'
};
diff --git a/examples/astro-markdown/public/styles/global.scss b/examples/astro-markdown/public/styles/global.scss
new file mode 100644
index 0000000000..1eda4646f7
--- /dev/null
+++ b/examples/astro-markdown/public/styles/global.scss
@@ -0,0 +1,6 @@
+@use "./prism.scss";
+
+body {
+ max-width: 900px;
+ margin: auto;
+}
\ No newline at end of file
diff --git a/examples/astro-markdown/public/styles/prism.scss b/examples/astro-markdown/public/styles/prism.scss
new file mode 100644
index 0000000000..29a3cf5d83
--- /dev/null
+++ b/examples/astro-markdown/public/styles/prism.scss
@@ -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;
+}
diff --git a/examples/astro-markdown/src/layouts/main.astro b/examples/astro-markdown/src/layouts/main.astro
index bd7e96adf6..61326cc5c9 100644
--- a/examples/astro-markdown/src/layouts/main.astro
+++ b/examples/astro-markdown/src/layouts/main.astro
@@ -4,9 +4,10 @@ export let content;
+
{content.title}
+
-
diff --git a/examples/astro-markdown/src/pages/index.astro b/examples/astro-markdown/src/pages/index.astro
index e05db0155e..b46f3698fb 100644
--- a/examples/astro-markdown/src/pages/index.astro
+++ b/examples/astro-markdown/src/pages/index.astro
@@ -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
@@ -40,5 +42,15 @@ const items = ['A', 'B', 'C'];
### Markdown can be embedded in any child component
+ ## Code
+
+ Should work!
+
+ ```js
+ import Something from './another';
+
+ const thing = new Something();
+ ```
+
diff --git a/examples/astro-markdown/src/pages/other.md b/examples/astro-markdown/src/pages/other.md
new file mode 100644
index 0000000000..2a059e3d11
--- /dev/null
+++ b/examples/astro-markdown/src/pages/other.md
@@ -0,0 +1,18 @@
+---
+title: Some Markdown Page
+layout: ../layouts/main.astro
+---
+
+# Code
+
+```js
+var foo = 'bar';
+
+function doSomething() {
+ return foo;
+}
+```
+
+# Paragraph
+
+text here.
\ No newline at end of file
diff --git a/packages/astro/components/Prism.astro b/packages/astro/components/Prism.astro
index 5462cba0e6..cc971a11be 100644
--- a/packages/astro/components/Prism.astro
+++ b/packages/astro/components/Prism.astro
@@ -43,4 +43,4 @@ if (grammar) {
let className = lang ? `language-${lang}` : '';
---
-
{html}
+{html}
\ No newline at end of file
diff --git a/packages/astro/src/compiler/codegen/index.ts b/packages/astro/src/compiler/codegen/index.ts
index b6027b9467..edb83623af 100644
--- a/packages/astro/src/compiler/codegen/index.ts
+++ b/packages/astro/src/compiler/codegen/index.ts
@@ -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 {
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 {
+export async function codegen(ast: Ast, { compileOptions, filename, fileID }: CodeGenOptions): Promise {
await eslexer.init;
const state: CodegenState = {
filename,
+ fileID,
components: new Map(),
css: [],
markers: {
diff --git a/packages/astro/src/compiler/transform/prism.ts b/packages/astro/src/compiler/transform/prism.ts
index 5d235377ac..0439b3a360 100644
--- a/packages/astro/src/compiler/transform/prism.ts
+++ b/packages/astro/src/compiler/transform/prism.ts
@@ -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;
}
},
};
diff --git a/packages/astro/test/astro-markdown.test.js b/packages/astro/test/astro-markdown.test.js
index edc18c576c..c252ab95a2 100644
--- a/packages/astro/test/astro-markdown.test.js
+++ b/packages/astro/test/astro-markdown.test.js
@@ -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();
diff --git a/packages/astro/test/fixtures/astro-markdown/src/pages/code.astro b/packages/astro/test/fixtures/astro-markdown/src/pages/code.astro
new file mode 100644
index 0000000000..16a1158c9b
--- /dev/null
+++ b/packages/astro/test/fixtures/astro-markdown/src/pages/code.astro
@@ -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.';
+---
+
+
+ ## Interesting Topic
+
+ ```js
+ const thing = () => {};
+ ```
+