Skip to content

Commit

Permalink
Merge pull request #4 from TorstenDittmann/feat-pass-proper-types-to-…
Browse files Browse the repository at this point in the history
…svelte

feat: pass proper types to svelte
  • Loading branch information
TorstenDittmann authored Aug 23, 2023
2 parents 97d22a6 + 2a22102 commit d3c4071
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 15 deletions.
1 change: 1 addition & 0 deletions apps/demo/src/lib/Tags.svelte
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script context="module">
export { default as Addition } from './tags/Addition.svelte';
export { default as Multiply } from './tags/Multiply.svelte';
export { default as Types } from './tags/Types.svelte';
</script>
10 changes: 10 additions & 0 deletions apps/demo/src/lib/tags/Types.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script lang="ts">
export let string: string;
export let number: number;
export let boolean: boolean;
</script>

<b>Types</b>
<p><b>{string}</b> is typeof <b>{typeof string}</b></p>
<p><b>{number}</b> is typeof <b>{typeof number}</b></p>
<p><b>{boolean}</b> is typeof <b>{typeof boolean}</b></p>
2 changes: 2 additions & 0 deletions apps/demo/src/routes/playground/tags/+page.markdoc
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
{% addition a=4 b=4 /%}

{% multiply a=3 b=8 /%}

{% types string="lorem ipsum" number=123 boolean=true /%}
10 changes: 10 additions & 0 deletions apps/demo/tests/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ test('tags work', async ({ page }) => {

expect(await page.content()).toContain('Addition');
expect(await page.content()).toContain('Multiply');
expect(await page.content()).toContain('Types');
});

test('tags work with types', async ({ page }) => {
await page.goto('http://localhost:4173/playground/tags');

expect(await page.content()).toContain('Types');
expect(await page.content()).toContain('<b>lorem ipsum</b> is typeof <b>string</b>');
expect(await page.content()).toContain('<b>123</b> is typeof <b>number</b>');
expect(await page.content()).toContain('<b>true</b> is typeof <b>boolean</b>');
});

test('partials work', async ({ page }) => {
Expand Down
15 changes: 14 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion packages/process/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "svelte-markdoc-preprocess",
"version": "0.1.6",
"version": "0.2.0",
"description": "A Svelte preprocessor that allows you to use Markdoc.",
"type": "commonjs",
"keywords": [
Expand All @@ -25,6 +25,7 @@
"license": "MIT",
"dependencies": {
"@markdoc/markdoc": "^0.3.0",
"html-escaper": "^3.0.3",
"js-yaml": "^4.1.0",
"svelte": "^4.0.0",
"typescript": "^5.0.0"
Expand All @@ -35,6 +36,7 @@
},
"homepage": "https://svelte-markdoc-preprocess.pages.dev",
"devDependencies": {
"@types/html-escaper": "^3.0.0",
"@types/js-yaml": "^4.0.5",
"@types/node": "^20.4.5",
"prettier": "*",
Expand Down
109 changes: 109 additions & 0 deletions packages/process/src/renderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { RenderableTreeNodes, Tag } from '@markdoc/markdoc';
import { sanitize_for_svelte } from './transformer';
import { escape } from 'html-escaper';

export function render_html(node: RenderableTreeNodes): string {
/**
* if the node is a string or number, it's a text node.
*/
if (typeof node === 'string' || typeof node === 'number') {
return sanitize_for_svelte(escape(String(node)));
}

/**
* if the node is an array, render its items.
*/
if (Array.isArray(node)) {
return node.map(render_html).join('');
}

/**
* if the node is not a Tag, it's invalid.
*/
if (node === null || typeof node !== 'object' || !Tag.isTag(node)) {
return '';
}

const { name, attributes, children = [] } = node;

if (!name) {
return render_html(children);
}

const is_svelte = is_svelte_component(node);

/**
* add attributes to the tag.
*/
let output = `<${name}`;
for (const [key, value] of Object.entries(attributes ?? {})) {
if (is_svelte) {
output += ` ${key.toLowerCase()}=${generate_svelte_attribute_value(
value,
)}`;
} else {
output += ` ${key.toLowerCase()}="${sanitize_for_svelte(
escape(String(value)),
)}"`;
}
}
output += '>';

/**
* if the tag is a void element, it doesn't need a closing tag.
*/
if (is_void_element(name)) {
return output;
}

/**
* render the children if present.
*/
if (children.length) {
output += render_html(children);
}

/**
* close the tag.
*/
output += `</${name}>`;

return output;
}

function is_void_element(name: string): boolean {
return [
'area',
'base',
'br',
'col',
'embed',
'hr',
'img',
'input',
'link',
'meta',
'param',
'source',
'track',
'wbr',
].includes(name);
}

function is_svelte_component(node: RenderableTreeNodes): boolean {
return Tag.isTag(node) && node.name.startsWith('INTERNAL__');
}

function generate_svelte_attribute_value(value: unknown): string {
switch (typeof value) {
case 'string':
return `"${sanitize_for_svelte(escape(String(value)))}"`;
case 'number':
case 'boolean':
return `{${String(value)}}`;
case 'object':
return `{${JSON.stringify(value)}}`;
default:
throw new Error(`Invalid attribute value: ${value}`);
}
}
4 changes: 2 additions & 2 deletions packages/process/src/transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
SchemaAttribute,
parse as markdocParse,
transform,
renderers,
NodeType,
Tag,
ConfigType,
Expand All @@ -20,6 +19,7 @@ import {
import { dirname, join } from 'path';
import { load as loadYaml } from 'js-yaml';
import { parse as svelteParse, walk } from 'svelte/compiler';
import { render_html } from './renderer';
import { get_all_files, path_exists, read_file, write_to_file } from './utils';
import * as default_schema from './default_schema';
import type { Config } from './config';
Expand Down Expand Up @@ -130,7 +130,7 @@ export function transformer({
/**
* render to html
*/
const code = sanitize_for_svelte(renderers.html(nast));
const code = render_html(nast);

let transformed = '';

Expand Down
22 changes: 11 additions & 11 deletions packages/process/tests/processor.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ test('preprocessor', async (context) => {
filename: 'test.markdoc',
}),
{
code: `${script}<article><INTERNAL__NODES.Heading level="1">Hello World</INTERNAL__NODES.Heading></article>`,
code: `${script}<article><INTERNAL__NODES.Heading level={1}>Hello World</INTERNAL__NODES.Heading></article>`,
},
);
assert.deepEqual(
Expand All @@ -138,7 +138,7 @@ test('preprocessor', async (context) => {
filename: 'test.markdoc',
}),
{
code: `${script}<article><INTERNAL__NODES.Heading level="2">Hello World</INTERNAL__NODES.Heading></article>`,
code: `${script}<article><INTERNAL__NODES.Heading level={2}>Hello World</INTERNAL__NODES.Heading></article>`,
},
);
assert.deepEqual(
Expand All @@ -147,7 +147,7 @@ test('preprocessor', async (context) => {
filename: 'test.markdoc',
}),
{
code: `${script}<article><INTERNAL__NODES.Heading id="my-id" level="1">Hello World</INTERNAL__NODES.Heading></article>`,
code: `${script}<article><INTERNAL__NODES.Heading id="my-id" level={1}>Hello World</INTERNAL__NODES.Heading></article>`,
},
);
assert.deepEqual(
Expand All @@ -156,7 +156,7 @@ test('preprocessor', async (context) => {
filename: 'test.markdoc',
}),
{
code: `${script}<article><INTERNAL__NODES.Heading class="my-class" level="1">Hello World</INTERNAL__NODES.Heading></article>`,
code: `${script}<article><INTERNAL__NODES.Heading class="my-class" level={1}>Hello World</INTERNAL__NODES.Heading></article>`,
},
);
});
Expand All @@ -180,7 +180,7 @@ test('preprocessor', async (context) => {
filename: 'test.markdoc',
}),
{
code: `${script}<article><INTERNAL__NODES.Heading level="1">Hello World</INTERNAL__NODES.Heading></article>`,
code: `${script}<article><INTERNAL__NODES.Heading level={1}>Hello World</INTERNAL__NODES.Heading></article>`,
},
);
assert.deepEqual(
Expand All @@ -189,7 +189,7 @@ test('preprocessor', async (context) => {
filename: 'test.markdoc',
}),
{
code: `${script}<article><INTERNAL__NODES.Heading level="2">Hello World</INTERNAL__NODES.Heading></article>`,
code: `${script}<article><INTERNAL__NODES.Heading level={2}>Hello World</INTERNAL__NODES.Heading></article>`,
},
);
assert.deepEqual(
Expand All @@ -198,7 +198,7 @@ test('preprocessor', async (context) => {
filename: 'test.markdoc',
}),
{
code: `${script}<article><INTERNAL__NODES.Heading id="my-id" level="1">Hello World</INTERNAL__NODES.Heading></article>`,
code: `${script}<article><INTERNAL__NODES.Heading id="my-id" level={1}>Hello World</INTERNAL__NODES.Heading></article>`,
},
);
assert.deepEqual(
Expand All @@ -207,7 +207,7 @@ test('preprocessor', async (context) => {
filename: 'test.markdoc',
}),
{
code: `${script}<article><INTERNAL__NODES.Heading class="my-class" level="1">Hello World</INTERNAL__NODES.Heading></article>`,
code: `${script}<article><INTERNAL__NODES.Heading class="my-class" level={1}>Hello World</INTERNAL__NODES.Heading></article>`,
},
);
assert.deepEqual(
Expand Down Expand Up @@ -302,7 +302,7 @@ test('preprocessor', async (context) => {
filename: 'test.markdoc',
}),
{
code: `${script}<article><INTERNAL__NODES.Heading level="1">I am a partial</INTERNAL__NODES.Heading></article>`,
code: `${script}<article><INTERNAL__NODES.Heading level={1}>I am a partial</INTERNAL__NODES.Heading></article>`,
},
);
assert.deepEqual(
Expand All @@ -311,7 +311,7 @@ test('preprocessor', async (context) => {
filename: 'test.markdoc',
}),
{
code: `${script}<article><INTERNAL__NODES.Heading level="1">Lorem Ipsum</INTERNAL__NODES.Heading></article>`,
code: `${script}<article><INTERNAL__NODES.Heading level={1}>Lorem Ipsum</INTERNAL__NODES.Heading></article>`,
},
);
assert.deepEqual(
Expand All @@ -329,7 +329,7 @@ test('preprocessor', async (context) => {
filename: 'test.markdoc',
}),
{
code: `${script}<article><INTERNAL__NODES.Heading level="1">I am nested</INTERNAL__NODES.Heading></article>`,
code: `${script}<article><INTERNAL__NODES.Heading level={1}>I am nested</INTERNAL__NODES.Heading></article>`,
},
);
assert.deepEqual(
Expand Down

0 comments on commit d3c4071

Please sign in to comment.