├── _assets ├── astro-icon-dark.psd ├── astro-mdx-code-blocks-light.png ├── icon-astro-mdx-code-blocks.jpg └── icon-astro-mdx-code-blocks-64.png ├── .gitignore ├── .prettierrc.json ├── tsconfig.json ├── .editorconfig ├── package.json ├── LICENSE ├── src ├── index.ts └── remarkCodeBlock.ts └── README.md /_assets/astro-icon-dark.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnzanussi/astro-mdx-code-blocks/HEAD/_assets/astro-icon-dark.psd -------------------------------------------------------------------------------- /_assets/astro-mdx-code-blocks-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnzanussi/astro-mdx-code-blocks/HEAD/_assets/astro-mdx-code-blocks-light.png -------------------------------------------------------------------------------- /_assets/icon-astro-mdx-code-blocks.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnzanussi/astro-mdx-code-blocks/HEAD/_assets/icon-astro-mdx-code-blocks.jpg -------------------------------------------------------------------------------- /_assets/icon-astro-mdx-code-blocks-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnzanussi/astro-mdx-code-blocks/HEAD/_assets/icon-astro-mdx-code-blocks-64.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules/ 3 | 4 | # build output 5 | dist/ 6 | 7 | # logs 8 | *.log 9 | 10 | # npm 11 | package-lock.json 12 | 13 | # macOS 14 | .DS_Store 15 | 16 | # env 17 | *.env 18 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": false, 3 | "tabWidth": 4, 4 | "semi": true, 5 | "singleQuote": true, 6 | "quoteProps": "as-needed", 7 | "jsxSingleQuote": false, 8 | "trailingComma": "es5", 9 | "bracketSpacing": true, 10 | "bracketSameLine": false, 11 | "arrowParens": "always" 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src"], 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "module": "ES2020", 9 | "moduleResolution": "node", 10 | "outDir": "./dist", 11 | "skipLibCheck": true, 12 | "strict": true, 13 | "target": "ES2021" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Unix-style newlines with a newline ending every file 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | use_tab_stops = true 12 | 13 | # Matches multiple files with brace expansion notation 14 | # Set default charset 15 | [*.{js,ts,astro}] 16 | charset = utf-8 17 | 18 | [*.{md,mdx}] 19 | trim_trailing_whitespace = false 20 | 21 | # Matches the exact files either package.json or .travis.yml 22 | [{package.json,.travis.yml}] 23 | indent_size = 2 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astro-mdx-code-blocks", 3 | "version": "0.0.6", 4 | "type": "module", 5 | "author": "John Zanussi (https://johnzanussi.com/)", 6 | "description": "An easy way to customize the syntax highlighting of MDX fenced code blocks by providing your own Astro component.", 7 | "types": "./dist/index.d.ts", 8 | "license": "MIT", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/johnzanussi/astro-mdx-code-blocks.git" 12 | }, 13 | "bugs": "https://github.com/johnzanussi/astro-mdx-code-blocks/issues", 14 | "exports": { 15 | ".": "./dist/index.js" 16 | }, 17 | "files": [ 18 | "dist" 19 | ], 20 | "scripts": { 21 | "build": "tsc", 22 | "dev": "tsc --watch" 23 | }, 24 | "keywords": [ 25 | "withastro", 26 | "ui", 27 | "mdx-code-blocks" 28 | ], 29 | "devDependencies": { 30 | "mdast-util-mdx-jsx": "^2.1.2", 31 | "unist-util-visit": "^4.1.2" 32 | }, 33 | "peerDependencies": { 34 | "astro": ">=2.0.11" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 John Zanussi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import type { AstroIntegration } from 'astro'; 2 | import makeRemarkCodeBlocks from './remarkCodeBlock.js'; 3 | 4 | export declare type CodeBlockProps = { 5 | code: string; 6 | lang?: string; 7 | filename?: string; 8 | }; 9 | 10 | const CodeBlockTagName = 'AutoImportedCodeBlock'; 11 | 12 | export const mdxCodeBlockAutoImport = (componentPath: string) => { 13 | // https://github.com/delucis/astro-auto-import/tree/main/packages/astro-auto-import#import-aliasing 14 | const codeBlockComponent: Record = { 15 | [componentPath]: [['default', CodeBlockTagName]], 16 | }; 17 | 18 | return codeBlockComponent; 19 | }; 20 | 21 | export default function MDXCodeBlocks(): AstroIntegration { 22 | return { 23 | name: 'astro-mdx-code-blocks', 24 | hooks: { 25 | 'astro:config:setup': ({ updateConfig }) => { 26 | updateConfig({ 27 | markdown: { 28 | remarkPlugins: [ 29 | makeRemarkCodeBlocks({ 30 | tagName: CodeBlockTagName, 31 | }), 32 | ], 33 | }, 34 | }); 35 | }, 36 | }, 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /src/remarkCodeBlock.ts: -------------------------------------------------------------------------------- 1 | import { visit, Visitor } from 'unist-util-visit'; 2 | import type { Code } from 'mdast'; 3 | import type { Transformer } from 'unified'; 4 | import type { MdxJsxFlowElement, MdxJsxAttribute } from 'mdast-util-mdx-jsx'; 5 | 6 | type MetaAttributes = { 7 | [key: string]: string | boolean; 8 | }; 9 | 10 | type CodeBlocksOptions = { 11 | tagName: string; 12 | }; 13 | 14 | export default function makeRemarkCodeBlocks({ tagName }: CodeBlocksOptions) { 15 | return function remarkCodeBlocks(): Transformer { 16 | return function transformer(tree): void { 17 | const visitor: Visitor = function (node, index, parent) { 18 | if (node.lang && parent && index !== null) { 19 | const { lang, meta } = node; 20 | try { 21 | let metaAttributes: MetaAttributes = {}; 22 | if (meta) { 23 | const metaMatches = Array.from( 24 | meta.matchAll(/([^=\s]+)=['"]([^'"\s]+)/g) 25 | ); 26 | metaAttributes = metaMatches.reduce( 27 | (accum, match) => { 28 | const [_, key, value] = match; 29 | return { 30 | ...accum, 31 | [key]: value, 32 | }; 33 | }, 34 | {} 35 | ); 36 | } 37 | if (!metaAttributes.filename) { 38 | node.value = node.value.replace( 39 | /^\/\/\s?([^\n]+)\n/, 40 | (_: string, filename: string) => { 41 | metaAttributes.filename = filename; 42 | return ''; 43 | } 44 | ); 45 | } 46 | const props = { 47 | code: node.value, 48 | lang, 49 | ...metaAttributes, 50 | }; 51 | 52 | const attributes: MdxJsxAttribute[] = Object.entries( 53 | props 54 | ).map(([name, value]) => ({ 55 | type: 'mdxJsxAttribute', 56 | name, 57 | value, 58 | })); 59 | 60 | const codeSnippetWrapper: MdxJsxFlowElement = { 61 | type: 'mdxJsxFlowElement', 62 | name: tagName, 63 | attributes, 64 | children: [], 65 | }; 66 | 67 | parent.children.splice(index, 1, codeSnippetWrapper); 68 | } catch (error) { 69 | // eslint-disable-next-line no-console 70 | console.error(error); 71 | } 72 | } 73 | }; 74 | visit(tree, 'code', visitor); 75 | }; 76 | }; 77 | } 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🚀 Astro MDX Code Blocks 2 | 3 | > [!CAUTION] 4 | > 5 | > ## Deprecated 6 | > This repository is no longer maintained, as Astro has released a more robust solution for rendering code blocks: [Expressive Code](https://expressive-code.com/) 7 | > 8 | 9 | Use a custom Astro component to render and syntax highlight code snippets in your MDX files. 10 | 11 | ## Demo 12 | 13 | View a demo of the integration in action on [StackBlitz](https://stackblitz.com/edit/astro-mdx-code-blocks-example?file=src%2Fpages%2Findex.mdx,astro.config.mjs,src%2Fcomponents%2FCodeBlock.astro). 14 | 15 | ## Installation 16 | 17 | Due to the extra configuration needed, you must manually install this integration. 18 | 19 | > This integration depends on the [`AutoImport`](https://github.com/delucis/astro-auto-import) and [`@astrojs/mdx`](https://docs.astro.build/en/guides/integrations-guide/mdx/) integrations. 20 | 21 | ```bash 22 | npm install -D astro-mdx-code-blocks astro-auto-import @astrojs/mdx 23 | ``` 24 | 25 | 26 | ## Code Block Component 27 | 28 | Create a component in your project that will be used to render fenced code blocks. 29 | 30 | ### Props 31 | 32 | The code block component provided to the integration receives the following props. 33 | 34 | | Prop | Type | Optional | Description | 35 | | ---- | ---- | -------- | ------------| 36 | | code | `String` | No | The raw contents of the fenced code block from the `.mdx` file. 37 | | lang | `String` | Yes | The language detected from the fenced code block. | 38 | | filename | `String` | Optional | If a `// filename.ts` is provided at the top of the code block it will be removed and sent in in the `filename` prop. | 39 | 40 | In addition, you can export the following type definition from the integration. 41 | 42 | ```ts 43 | type CodeBlockProps = { 44 | code: string; 45 | lang?: string; 46 | filename?: string; 47 | }; 48 | ``` 49 | 50 | ```ts 51 | import type { CodeBlockProps } from 'astro-mdx-code-blocks'; 52 | ``` 53 | 54 | 55 | ### Example Component 56 | 57 | > This example uses Astro's [Prism component](https://docs.astro.build/en/reference/api-reference/#prism-) for syntax highlighting. However, you can use any library you'd like as the component has access to the raw `code` string. 58 | 59 | `src/components/CodeBlock.astro` 60 | 61 | ```astro 62 | --- 63 | import { Prism } from '@astrojs/prism'; 64 | 65 | const { 66 | code, 67 | lang, 68 | filename, 69 | } = Astro.props; 70 | 71 | const hasLang = !!lang; 72 | const hasFileName = !!filename; 73 | 74 | const showHeader = hasLang || hasFileName; 75 | --- 76 | 77 |
78 | 79 | {showHeader && ( 80 |
81 | {hasFileName && ( 82 | 83 | {filename} 84 | 85 | )} 86 | {hasLang && ( 87 | 88 | {lang} 89 | 90 | )} 91 |
92 | )} 93 | 94 | 98 | 99 |
100 | ``` 101 | 102 | ## Configuration 103 | 104 | * Import the `AutoImport` and `mdx` integrations. 105 | * Import `MDXCodeBlocks` and `mdxCodeBlockAutoImport` from `astro-mdx-code-blocks`. 106 | * Add `AutoImport`, `MDXCodeBlocks`, and `mdx` to the `integrations` config option. 107 | * Use the `mdxCodeBlockAutoImport` function to provide the `AutoImport` integration the path to your custom Astro component. 108 | 109 | ```js 110 | import { defineConfig } from 'astro/config'; 111 | 112 | import AutoImport from 'astro-auto-import'; 113 | import mdx from '@astrojs/mdx'; 114 | 115 | import MDXCodeBlocks, { mdxCodeBlockAutoImport } from 'astro-mdx-code-blocks'; 116 | 117 | export default defineConfig({ 118 | // ... 119 | integrations: [ 120 | AutoImport({ 121 | imports: [ 122 | mdxCodeBlockAutoImport('src/components/CodeBlock.astro') 123 | ], 124 | }), 125 | MDXCodeBlocks(), 126 | mdx(), 127 | ], 128 | }); 129 | ``` 130 | 131 | > AutoImport must come before `MDXCodeBlocks` and `MDXCodeBlocks` must come before `mdx`. 132 | 133 | ## Usage 134 | 135 | Use [fenced code blocks](https://www.markdownguide.org/extended-syntax/#fenced-code-blocks) in your MDX files as you normally would. As noted above, the integration will pull out certain metadata from the block and provide it to your custom Astro component. 136 | 137 | ## Contributing 138 | 139 | Issues and pull requests are welcome. 140 | 141 | ## License 142 | 143 | [MIT](LICENSE) 144 | --------------------------------------------------------------------------------