Skip to content

Commit

Permalink
svelte template syntax support
Browse files Browse the repository at this point in the history
  • Loading branch information
lifeart committed Feb 14, 2024
1 parent c377263 commit f68074e
Show file tree
Hide file tree
Showing 3 changed files with 328 additions and 10 deletions.
14 changes: 8 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@
"@types/qunit": "^2.19.9",
"autoprefixer": "^10.4.16",
"backburner.js": "^2.8.0",
"express": "^4.18.2",
"glint-environment-gxt": "file:./glint-environment-gxt",
"happy-dom": "^13.0.6",
"nyc": "^15.1.0",
"postcss": "^8.4.33",
"prettier": "^3.1.1",
Expand All @@ -93,16 +96,15 @@
"vite-plugin-circular-dependency": "^0.2.1",
"vite-plugin-dts": "^3.7.0",
"vitest": "^1.1.1",
"zx": "^7.2.3",
"express": "^4.18.2",
"happy-dom": "^13.0.6",
"glint-environment-gxt": "file:./glint-environment-gxt"
"zx": "^7.2.3"
},
"dependencies": {
"@babel/core": "^7.23.6",
"decorator-transforms": "1.1.0",
"@babel/preset-typescript": "^7.23.3",
"@glimmer/syntax": "^0.87.1",
"content-tag": "^1.2.2"
"code-red": "^1.0.4",
"content-tag": "^1.2.2",
"decorator-transforms": "1.1.0",
"svelte": "^4.2.10"
}
}
231 changes: 231 additions & 0 deletions plugins/svelte-compiler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
import * as parser from 'svelte/compiler';
import { print } from 'code-red';

export function compile(
code = `
<div id="12" name={3+2} label="1{2}"></div>
<button disabled={!clickable}>...</button>
<button disabled>can't touch this</button>
<input type=checkbox />
<input required={false} placeholder="This input field is not required" />
<div title={null}>This div has no title attribute</div>
<button disabled="{number !== 42}">...</button>
<button {disabled}>...</button>
<Widget foo={bar} answer={42} text="hello" />
<p>{a} + {b} = {a + b}.</p>
<div>{(/^[A-Za-z ]+$/).test(value) ? x : y}</div>
{#if expression}...{:else}...{/if}
{#if answer === 42}
<p>what was the question?</p>
{/if}
-----
{#if porridge.temperature > 100}
<p>too hot!</p>
{:else if 80 > porridge.temperature}
<p>too cold!</p>
{:else}
<p>just right!</p>
{/if}
---
{#each expression as name}...{/each}
<ul>
{#each items as item}
<li>{item.name} x {item.qty}</li>
{/each}
</ul>
{#each items as item, i}
<li>{i + 1}: {item.name} x {item.qty}</li>
{/each}
{#each items as item (item.id)}
<li>{item.name} x {item.qty}</li>
{/each}
{#each items as item, i (item.id)}
<li>{i + 1}: {item.name} x {item.qty}</li>
{/each}
<MyComponent {...rest} />
<button on:click={handleClick}>
count: {count}
</button>
<div class="name"></div>
<div class={isActive ? 'active' : ''} >...</div>
<div class:active={isActive}>...</div>
<div class:active class:inactive={!active} class:isAdmin>...</div>
<div style:color={myColor}>...</div>
`,
) {
// code-red
const result = parser.compile(code, {
dev: true,
preserveWhitespace: true,
preserveComments: false,
});

console.log('ast', JSON.stringify(result.ast.html.children, null, 2));

return result.ast.html.children.map(transform).join(',');
}

function transformElement(element) {
const modifiers = element.attributes.filter(
(el) => el.type === 'EventHandler' || el.type === 'StyleDirective',
);
const properties = element.attributes.filter(
(el) =>
el.type === 'Class' || (el.type === 'Attribute' && el.name === 'class'),
);
const attributes = element.attributes.filter(
(el) => !modifiers.includes(el) && !properties.includes(el),
);
const compiledProperties = `[${properties
.map((el) => {
if (el.type === 'Class') {
return `['',()=>${print(el.expression).code} ? ${escapeText(el.name)} : '']`;
}
if (Array.isArray(el.value)) {
if (el.value.length !== 1) {
throw new Error('Unknown attribute type');
}
if (el.value[0].type === 'MustacheTag') {
return `['', () => ${transform(el.value[0])}]`;
}
return `['', ${transform(el.value[0])}]`;
} else {
throw new Error('Unknown attribute type');
}
})
.join(',')}]`;
const compiledModifiers = `[${modifiers
.map((el) => {
if (el.type === 'StyleDirective') {
return `[2, ${escapeText(el.name)}, () => ${print(el.value[0].expression).code}]`;
}
return `[1,${escapeText(el.name)},${print(el.expression).code}]`;
})
.join(',')}]`;
return `$_tag(${escapeText(
element.name,
)}, [${compiledProperties}, [${attributes.map(
transformAttribute,
)}], ${compiledModifiers}], [${element.children.map((node) => {
if (node.type === 'MustacheTag') {
return `()=>${transform(node)}`;
} else {
return transform(node);
}
})}], this)`;
}
function transformArgument(attribute) {
if (!Array.isArray(attribute.value)) {
if (attribute.type === 'Spread') {
return `...${print(attribute.expression).code}`;
}
return `${escapeText(attribute.name)}:${transform(attribute.value)}`;
}
if (attribute.value.length === 1) {
const node = attribute.value[0];
if (node.type === 'MustacheTag') {
return `${escapeText(attribute.name)}:()=>${transform(node)}]`;
} else {
return `${escapeText(attribute.name)}:${transform(node)}]`;
}
} else {
return `${escapeText(attribute.name)}:() => [${attribute.value.map((el) => {
return transform(el);
})}].join('')`;
}
}
function transformAttribute(attribute) {
if (!Array.isArray(attribute.value)) {
return `[${escapeText(attribute.name)},${transform(attribute.value)}]`;
}
if (attribute.value.length === 1) {
const node = attribute.value[0];
if (node.type === 'MustacheTag') {
return `[${escapeText(attribute.name)},()=>${transform(node)}]`;
} else {
return `[${escapeText(attribute.name)},${transform(node)}]`;
}
} else {
return `[${escapeText(attribute.name)},() => [${attribute.value.map(
(el) => {
return transform(el);
},
)}].join('')]`;
}
}

function transformMustacheTag(expression) {
return print(expression).code;
}

function escapeText(text) {
return JSON.stringify(text);
}

function transformInlineComponent(node) {
const argsArray = node.attributes.map((attr) => {
return transformArgument(attr);
});
argsArray.push(`$slots: {default: () => [${node.children.map(transform)}]}`);
return `$_c(${node.name}, {${argsArray.join(',')}}, this)`;
}
function transformAttributeShorthand(node) {
return `()=>${print(node.expression).code}`;
}

function transformIfBlock(node) {
return `$_if(()=>${print(node.expression).code}, () => [${node.children.map(
transform,
)}], ${node.else ? `${transform(node.else)}` : null})`;
}
function transformElseBlock(node) {
return `()=>[${node.children.map(transform).join(',')}]`;
}

function transformEachBlock(node) {
const item = print(node.context).code;
return `$_eachSync(()=>${print(node.expression).code},(${item},${
node.index ? node.index : '$index'
}, ${
node.key
? `(${item})=>${print(node.key).code}`
: `${escapeText('@identity')}`
})=>[${node.children.map(transform).join(',')}], this)`;
}

function transform(node) {
if (typeof node !== 'object') {
return node;
} else if (node.type === 'Element') {
return transformElement(node);
} else if (node.type === 'Attribute') {
return transformAttribute(node);
} else if (node.type === 'Text') {
return escapeText(node.data);
} else if (node.type === 'MustacheTag') {
return transformMustacheTag(node.expression);
} else if (node.type === 'AttributeShorthand') {
return transformAttributeShorthand(node);
} else if (node.type === 'InlineComponent') {
return transformInlineComponent(node);
} else if (node.type === 'IfBlock') {
return transformIfBlock(node);
} else if (node.type === 'ElseBlock') {
return transformElseBlock(node);
} else if (node.type === 'EachBlock') {
return transformEachBlock(node);
}

throw new Error(`Unknown node type: ${node.type}`);
}

console.log(compile());
Loading

0 comments on commit f68074e

Please sign in to comment.