Skip to content

Commit 5737e0c

Browse files
Merge pull request #237 from TorstenDittmann/229-feature-request-custom-fencecodeblock-highlighting-function-in-preprocessor-config
feat: add highlighter function
2 parents 28f6056 + e60c44a commit 5737e0c

File tree

17 files changed

+369
-118
lines changed

17 files changed

+369
-118
lines changed

.github/workflows/test.yml

-1
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,4 @@ jobs:
1818
- run: corepack enable
1919
- run: pnpm install --frozen-lockfile
2020
- run: pnpm run build
21-
- run: pnpx playwright install --with-deps chromium
2221
- run: pnpm test

apps/demo/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"dev": "vite dev",
77
"build": "vite build",
88
"preview": "vite preview",
9-
"test": "playwright test",
9+
"test": "playwright install --with-deps && playwright test",
1010
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
1111
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
1212
"lint": "prettier --check . && eslint .",

packages/process/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "svelte-markdoc-preprocess",
3-
"version": "2.0.2",
3+
"version": "2.1.0",
44
"description": "A Svelte preprocessor that allows you to use Markdoc.",
55
"type": "commonjs",
66
"keywords": [

packages/process/src/config.ts

+4
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,8 @@ export type Config = {
4040
* Configuration for the markdoc compiler.
4141
*/
4242
config: ConfigType | null;
43+
/**
44+
* A function that will provide a custom highlighter for code blocks.
45+
*/
46+
highlighter: ((code: string, language: string) => Promise<string>) | null;
4347
};

packages/process/src/processor.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const default_config: Config = {
1212
config: null,
1313
validationThreshold: 'error',
1414
allowComments: false,
15+
highlighter: null,
1516
};
1617

1718
const processor = ({
@@ -24,6 +25,7 @@ const processor = ({
2425
config = default_config.config,
2526
validationThreshold = default_config.validationThreshold,
2627
allowComments = default_config.allowComments,
28+
highlighter: highlighter = default_config.highlighter,
2729
}: Partial<Config> = default_config): PreprocessorGroup => {
2830
return {
2931
name: 'svelte-markdoc-preprocess',
@@ -40,7 +42,7 @@ const processor = ({
4042
/**
4143
* Add svelte components to be used with markdoc tags
4244
*/
43-
const code = transformer({
45+
const code = await transformer({
4446
filename,
4547
config,
4648
content,
@@ -51,6 +53,7 @@ const processor = ({
5153
partials_dir: partials,
5254
validation_threshold: validationThreshold,
5355
allow_comments: allowComments,
56+
highlighter,
5457
});
5558

5659
return {

packages/process/src/renderer.ts

+54-8
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,40 @@ import { sanitize_for_svelte } from './transformer';
33
import { escape } from 'html-escaper';
44
import { IMAGE_PREFIX, IMPORT_PREFIX, NODES_IMPORT } from './constants';
55
import { is_relative_path } from './utils';
6+
import { Config } from './config';
67

7-
export function render_html(
8+
export async function render_html(
89
node: RenderableTreeNodes,
910
dependencies: Map<string, string>,
10-
): string {
11+
highlighter: Config['highlighter'],
12+
escape_html = true,
13+
): Promise<string> {
1114
/**
1215
* if the node is a string or number, it's a text node.
1316
*/
1417
if (typeof node === 'string' || typeof node === 'number') {
15-
return sanitize_for_svelte(escape(String(node)));
18+
if (escape_html) {
19+
return sanitize_for_svelte(escape(String(node)));
20+
} else {
21+
return sanitize_for_svelte(String(node));
22+
}
1623
}
1724

1825
/**
1926
* if the node is an array, render its items.
2027
*/
2128
if (Array.isArray(node)) {
22-
return node.map((item) => render_html(item, dependencies)).join('');
29+
return Promise.all(
30+
node.map(
31+
async (item) =>
32+
await render_html(
33+
item,
34+
dependencies,
35+
highlighter,
36+
escape_html,
37+
),
38+
),
39+
).then((items) => items.join(''));
2340
}
2441

2542
/**
@@ -29,10 +46,10 @@ export function render_html(
2946
return '';
3047
}
3148

32-
const { name, attributes, children = [] } = node;
49+
let { name, attributes, children = [] } = node;
3350

3451
if (!name) {
35-
return render_html(children, dependencies);
52+
return await render_html(children, dependencies, highlighter);
3653
}
3754

3855
const is_svelte = is_svelte_component(node);
@@ -41,9 +58,10 @@ export function render_html(
4158
* add attributes to the tag.
4259
*/
4360
let output = `<${name}`;
44-
for (const [key, value] of Object.entries(attributes ?? {})) {
61+
for (let [key, value] of Object.entries(attributes ?? {})) {
4562
const is_src_key = key === 'src';
4663
const is_imported_image = is_src_key && is_relative_path(value);
64+
4765
if (is_svelte) {
4866
switch (name.toLowerCase()) {
4967
case `${NODES_IMPORT}.image`.toLowerCase():
@@ -97,11 +115,39 @@ export function render_html(
97115
return output;
98116
}
99117

118+
let escape_next = true;
119+
120+
if (highlighter) {
121+
const run_highlighter =
122+
name.toLowerCase() === `${NODES_IMPORT}.fence`.toLowerCase() ||
123+
name.toLowerCase() === 'pre'.toLowerCase();
124+
if (run_highlighter) {
125+
escape_next = false;
126+
children = await Promise.all(
127+
children.map(async (child) =>
128+
typeof child === 'string'
129+
? await highlighter(
130+
child,
131+
(is_svelte
132+
? attributes?.language
133+
: attributes['data-language']) ?? '',
134+
)
135+
: child,
136+
),
137+
);
138+
}
139+
}
140+
100141
/**
101142
* render the children if present.
102143
*/
103144
if (children.length) {
104-
output += render_html(children, dependencies);
145+
output += await render_html(
146+
children,
147+
dependencies,
148+
highlighter,
149+
escape_next,
150+
);
105151
}
106152

107153
/**

packages/process/src/transformer.ts

+7-9
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ type Var = {
3939
type: StringConstructor | NumberConstructor | BooleanConstructor;
4040
};
4141

42-
export function transformer({
42+
export async function transformer({
4343
content,
4444
filename,
4545
nodes_file,
@@ -50,6 +50,7 @@ export function transformer({
5050
config,
5151
validation_threshold,
5252
allow_comments,
53+
highlighter,
5354
}: {
5455
content: string;
5556
filename: string;
@@ -61,7 +62,8 @@ export function transformer({
6162
config: Config['config'];
6263
validation_threshold: Config['validationThreshold'];
6364
allow_comments: Config['allowComments'];
64-
}): string {
65+
highlighter: Config['highlighter'];
66+
}): Promise<string> {
6567
/**
6668
* create tokenizer
6769
*/
@@ -189,7 +191,7 @@ export function transformer({
189191
/**
190192
* render to html
191193
*/
192-
const code = render_html(nast, dependencies);
194+
const code = await render_html(nast, dependencies, highlighter);
193195

194196
let transformed = '';
195197

@@ -406,14 +408,9 @@ function prepare_nodes(
406408
if (nodes_file) {
407409
for (const [name] of each_exported_var(nodes_file)) {
408410
const type = name.toLowerCase() as NodeType;
409-
if (type === 'image') {
410-
}
411411
nodes[name.toLowerCase()] = {
412412
...get_node_defaults(type),
413413
transform(node, config) {
414-
if (type === 'image') {
415-
node.attributes.src;
416-
}
417414
return new Tag(
418415
`${NODES_IMPORT}.${name}`,
419416
node.transformAttributes(config),
@@ -470,7 +467,8 @@ function each_exported_var(filepath: string): Array<[string, string]> {
470467
if (node.type === 'ExportSpecifier') {
471468
if (
472469
parent?.type === 'ExportNamedDeclaration' &&
473-
parent?.source
470+
parent?.source &&
471+
node.exported.type === 'Identifier'
474472
) {
475473
tup.push([node.exported.name, String(parent.source.value)]);
476474
}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script context="module">
22
export { default as Heading } from './mock.svelte';
33
export { default as Image } from './mock.svelte';
4+
export { default as Fence } from './mock.svelte';
45
</script>

packages/process/tests/processor.test.mjs

+4-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,10 @@ test('preprocessor', async (context) => {
6969
files.map(async (entry) => {
7070
return context.test('tests ' + basename(entry), async () => {
7171
const before = read_file(join(entry, 'source.markdoc'));
72-
const after = read_file(join(entry, 'compiled.txt'));
72+
const after = read_file(join(entry, 'compiled.txt')).replace(
73+
/\\n/g,
74+
'\n',
75+
);
7376
const config = await import('../' + join(entry, 'config.mjs'));
7477
const preprocess = config.default;
7578
const exception = config.exception ?? false;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<script>import * as INTERNAL__NODES from 'tests/nodes/module.svelte';</script><article><INTERNAL__NODES.Fence content="console.log(&#39;hello world&#39;);\n" language="javascript" process={true}>javascript:console.log('hello world');\n</INTERNAL__NODES.Fence><INTERNAL__NODES.Fence content="&lt;b&gt;bold&lt;/b&gt;\n" language="html" process={true}>html:<b>bold</b>\n</INTERNAL__NODES.Fence></article>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { markdoc } from '../../../dist/module.js';
2+
import { absoulute } from '../../utils.mjs';
3+
4+
export default markdoc({
5+
nodes: absoulute(import.meta.url, '../../nodes/module.svelte'),
6+
highlighter: (code, lang) => `${lang}:${code}`,
7+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
```javascript
2+
console.log('hello world');
3+
```
4+
5+
```html
6+
<b>bold</b>
7+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<article><pre data-language="javascript">javascript:console.log('hello world');\n</pre><pre data-language="html">html:<b>bold</b>\n</pre></article>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { markdoc } from '../../../dist/module.js';
2+
3+
export default markdoc({
4+
highlighter: (code, lang) => `${lang}:${code}`,
5+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
```javascript
2+
console.log('hello world');
3+
```
4+
5+
```html
6+
<b>bold</b>
7+
```

0 commit comments

Comments
 (0)