Skip to content

Commit

Permalink
+
Browse files Browse the repository at this point in the history
  • Loading branch information
lifeart committed Feb 15, 2024
1 parent d3f0b0b commit 7538240
Show file tree
Hide file tree
Showing 6 changed files with 277 additions and 21 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
"code-red": "^1.0.4",
"content-tag": "^1.2.2",
"decorator-transforms": "1.1.0",
"magic-string": "^0.30.7",
"svelte": "^4.2.10"
}
}
189 changes: 189 additions & 0 deletions plugins/__snapshots__/svlelte-compiler.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`compiler > compile sample case #1 1`] = `
{
"code": "
import { $_fin,$_tag,$_if,$_each,$_eachSync,$_slot,$_edp,$_args,$_text,$_c,$_dc,$SLOTS_SYMBOL,$PROPS_SYMBOL,$_GET_SLOTS,$_GET_ARGS,$_GET_FW,$_componentHelper,$_modifierHelper,$_helperHelper,$template,$_hasBlockParams,$_hasBlock,$nodes,$args,$_maybeHelper,$_maybeModifier,$_inElement,$_ucw,$__if,$__eq,$__debugger,$__log,$__array,$__hash,$__fn } from "@lifeart/gxt";
export default function unknown(args) {
const $fw = $_GET_FW(this, arguments);
const $slots = $_GET_SLOTS(this, arguments);
const roots = ["\\n",$_tag("div", [[], [["id","12"],["name",()=>3 + 2],["label",()=>["1",2].join('')]], []], [],this),"\\n",$_tag("button", [[], [["disabled",()=>!clickable]], []], ["..."],this),"\\n",$_tag("button", [[], [["disabled",true]], []], ["can't touch this"],this),"\\n",$_tag("input", [[], [["type","checkbox"]], []], [],this),"\\n",$_tag("input", [[], [["required",()=>false],["placeholder","This input field is not required"]], []], [],this),"\\n",$_tag("div", [[], [["title",()=>null]], []], ["This div has no title attribute"],this),"\\n",$_tag("button", [[], [["disabled",()=>number !== 42]], []], ["..."],this),"\\n",$_tag("button", [[], [["disabled",()=>disabled]], []], ["..."],this),"\\n",$_c(Widget,{"foo":()=>bar],"answer":()=>42],"text":"hello"],[$SLOTS_SYMBOL]:{default:()=>[]}},this),"\\n",$_tag("p", [[], [], []], [()=>a," + ",()=>b," = ",()=>a + b,"."],this),"\\n",$_tag("div", [[], [], []], [()=>(/^[A-Za-z ]+$/).test(value) ? x : y],this),"\\n",$_if(()=>expression,()=>["..."], ()=>["..."]),"\\n",$_if(()=>answer === 42,()=>[$_tag("p", [[], [], []], ["what was the question?"],this)], null),"\\n-----\\n",$_if(()=>porridge.temperature > 100,()=>[$_tag("p", [[], [], []], ["too hot!"],this)], ()=>[$_if(()=>80 > porridge.temperature,()=>[$_tag("p", [[], [], []], ["too cold!"],this)], ()=>[$_tag("p", [[], [], []], ["just right!"],this)])]),"\\n---\\n",$_each(()=>expression,(name,$index, "@identity")=>["..."],this),"\\n\\n",$_tag("ul", [[], [], []], ["\\n\\t",$_each(()=>items,(item,$index, "@identity")=>[$_tag("li", [[], [], []], [()=>item.name," x ",()=>item.qty],this)],this),"\\n"],this),"\\n\\n\\n",$_each(()=>items,(item,i, "@identity")=>[$_tag("li", [[], [], []], [()=>i + 1,": ",()=>item.name," x ",()=>item.qty],this)],this),"\\n\\n",$_each(()=>items,(item,$index, (item)=>item.id)=>[$_tag("li", [[], [], []], [()=>item.name," x ",()=>item.qty],this)],this),"\\n\\n",$_each(()=>items,(item,i, (item)=>item.id)=>[$_tag("li", [[], [], []], [()=>i + 1,": ",()=>item.name," x ",()=>item.qty],this)],this),"\\n\\n",$_c(MyComponent,{...rest,[$SLOTS_SYMBOL]:{default:()=>[]}},this),"\\n\\n",$_tag("button", [[], [], [["click",()=>handleClick()]]], ["\\n\\tcount: ",()=>count,"\\n"],this),"\\n\\n",$_tag("div", [[['',"name"]], [], []], [],this),"\\n",$_tag("div", [[['',()=>isActive ? 'active' : '']], [], []], ["..."],this),"\\n",$_tag("div", [[['',()=>isActive ? "active" : '']], [], []], ["..."],this),"\\n",$_tag("div", [[['',()=>active ? "active" : ''],['',()=>!active ? "inactive" : ''],['',()=>isAdmin ? "isAdmin" : '']], [], []], ["..."],this),"\\n",$_tag("div", [[], [["color",()=>myColor]], []], ["..."],this),"\\n\\n",$_tag("a", [[], [["href",()=>["page/",p].join('')]], []], ["page ",()=>p],this),"\\n\\n",$_slot("item",() => [], $slots, this),"\\n\\n",$_c(FancyList,{[$SLOTS_SYMBOL]:{footer:()=>["Copyright (c) 2019 Svelte Industries"],default:()=>["\\n ","\\n"]}},this)];
return $_fin(roots, this);
}",
"map": SourceMap {
"file": "unknown.map",
"mappings": ";;;;;EAAA,IACA,gGAA2C,IAC3C,sEAA0C,IAC1C,yEAA0C,IAC1C,wDAAuB,IACvB,8GAAyE,IACzE,sFAAuD,IACvD,yEAA+C,IAC/C,oEAA+B,IAC/B,mGAA6C,IAC7C,sEAA2B,IAC3B,2EAAiD,IACjD,6CAAkC,IAClC,6FAEK,WAEL,0OAMK,SAEL,+DAAoC,MAEpC,qKAIK,QAGL,iIAEO,MAEP,2HAEO,MAEP,qIAEO,MAEP,gEAAyB,MAEzB,6FAES,MAET,8CAAwB,IACxB,yEAAgD,IAChD,yEAAsC,IACtC,4IAAkE,IAClE,6DAAoC,MAEpC,+EAA+B,MAE/B,qCAA2B,MAE3B,qHAEY;",
"names": [],
"sources": [
"unknown",
],
"sourcesContent": [
"
<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>
<a href="page/{p}">page {p}</a>
<slot name="item" {item} />
<FancyList>
<p slot="footer">Copyright (c) 2019 Svelte Industries</p>
</FancyList>
",
],
"version": 3,
},
}
`;

exports[`compiler > compile sample case #2 1`] = `
{
"code": "
import { $_fin,$_tag,$_if,$_each,$_eachSync,$_slot,$_edp,$_args,$_text,$_c,$_dc,$SLOTS_SYMBOL,$PROPS_SYMBOL,$_GET_SLOTS,$_GET_ARGS,$_GET_FW,$_componentHelper,$_modifierHelper,$_helperHelper,$template,$_hasBlockParams,$_hasBlock,$nodes,$args,$_maybeHelper,$_maybeModifier,$_inElement,$_ucw,$__if,$__eq,$__debugger,$__log,$__array,$__hash,$__fn } from "@lifeart/gxt";
export default function unknown(args) {
const $fw = $_GET_FW(this, arguments);
const $slots = $_GET_SLOTS(this, arguments);
const roots = ["\\n",$_tag("div", [[], [["id","12"],["name",()=>3 + 2],["label",()=>["1",2].join('')]], []], [],this),"\\n",$_tag("button", [[], [["disabled",()=>!clickable]], []], ["..."],this),"\\n",$_tag("button", [[], [["disabled",true]], []], ["can't touch this"],this),"\\n",$_tag("input", [[], [["type","checkbox"]], []], [],this),"\\n",$_tag("input", [[], [["required",()=>false],["placeholder","This input field is not required"]], []], [],this),"\\n",$_tag("div", [[], [["title",()=>null]], []], ["This div has no title attribute"],this),"\\n",$_tag("button", [[], [["disabled",()=>number !== 42]], []], ["..."],this),"\\n",$_tag("button", [[], [["disabled",()=>disabled]], []], ["..."],this),"\\n",$_c(Widget,{"foo":()=>bar],"answer":()=>42],"text":"hello"],[$SLOTS_SYMBOL]:{default:()=>[]}},this),"\\n",$_tag("p", [[], [], []], [()=>a," + ",()=>b," = ",()=>a + b,"."],this),"\\n",$_tag("div", [[], [], []], [()=>(/^[A-Za-z ]+$/).test(value) ? x : y],this),"\\n",$_if(()=>expression,()=>["..."], ()=>["..."]),"\\n",$_if(()=>answer === 42,()=>[$_tag("p", [[], [], []], ["what was the question?"],this)], null),"\\n-----\\n",$_if(()=>porridge.temperature > 100,()=>[$_tag("p", [[], [], []], ["too hot!"],this)], ()=>[$_if(()=>80 > porridge.temperature,()=>[$_tag("p", [[], [], []], ["too cold!"],this)], ()=>[$_tag("p", [[], [], []], ["just right!"],this)])]),"\\n---\\n",$_each(()=>expression,(name,$index, "@identity")=>["..."],this),"\\n\\n",$_tag("ul", [[], [], []], ["\\n\\t",$_each(()=>items,(item,$index, "@identity")=>[$_tag("li", [[], [], []], [()=>item.name," x ",()=>item.qty],this)],this),"\\n"],this),"\\n\\n\\n",$_each(()=>items,(item,i, "@identity")=>[$_tag("li", [[], [], []], [()=>i + 1,": ",()=>item.name," x ",()=>item.qty],this)],this),"\\n\\n",$_each(()=>items,(item,$index, (item)=>item.id)=>[$_tag("li", [[], [], []], [()=>item.name," x ",()=>item.qty],this)],this),"\\n\\n",$_each(()=>items,(item,i, (item)=>item.id)=>[$_tag("li", [[], [], []], [()=>i + 1,": ",()=>item.name," x ",()=>item.qty],this)],this),"\\n\\n",$_c(MyComponent,{...rest,[$SLOTS_SYMBOL]:{default:()=>[]}},this),"\\n\\n",$_tag("button", [[], [], [["click",()=>handleClick()]]], ["\\n\\tcount: ",()=>count,"\\n"],this),"\\n\\n",$_tag("div", [[['',"name"]], [], []], [],this),"\\n",$_tag("div", [[['',()=>isActive ? 'active' : '']], [], []], ["..."],this),"\\n",$_tag("div", [[['',()=>isActive ? "active" : '']], [], []], ["..."],this),"\\n",$_tag("div", [[['',()=>active ? "active" : ''],['',()=>!active ? "inactive" : ''],['',()=>isAdmin ? "isAdmin" : '']], [], []], ["..."],this),"\\n",$_tag("div", [[], [["color",()=>myColor]], []], ["..."],this),"\\n\\n",$_tag("a", [[], [["href",()=>["page/",p].join('')]], []], ["page ",()=>p],this),"\\n\\n",$_slot("item",() => [], $slots, this),"\\n\\n",$_c(FancyList,{[$SLOTS_SYMBOL]:{footer:()=>["Copyright (c) 2019 Svelte Industries"],default:()=>["\\n ","\\n"]}},this)];
return $_fin(roots, this);
}",
"map": SourceMap {
"file": "unknown.map",
"mappings": ";;;;;EAAA,IACA,gGAA2C,IAC3C,sEAA0C,IAC1C,yEAA0C,IAC1C,wDAAuB,IACvB,8GAAyE,IACzE,sFAAuD,IACvD,yEAA+C,IAC/C,oEAA+B,IAC/B,mGAA6C,IAC7C,sEAA2B,IAC3B,2EAAiD,IACjD,6CAAkC,IAClC,6FAEK,WAEL,0OAMK,SAEL,+DAAoC,MAEpC,qKAIK,QAGL,iIAEO,MAEP,2HAEO,MAEP,qIAEO,MAEP,gEAAyB,MAEzB,6FAES,MAET,8CAAwB,IACxB,yEAAgD,IAChD,yEAAsC,IACtC,4IAAkE,IAClE,6DAAoC,MAEpC,+EAA+B,MAE/B,qCAA2B,MAE3B,qHAEY;",
"names": [],
"sources": [
"unknown",
],
"sourcesContent": [
"
<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>
<a href="page/{p}">page {p}</a>
<slot name="item" {item} />
<FancyList>
<p slot="footer">Copyright (c) 2019 Svelte Industries</p>
</FancyList>
",
],
"version": 3,
},
}
`;
73 changes: 59 additions & 14 deletions plugins/svelte-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import * as parser from 'svelte/compiler';
import { print } from 'code-red';
import { SYMBOLS, MAIN_IMPORT } from './symbols.js';
import MagicString from 'magic-string';

import type {
Attribute,
BaseDirective,
Expand All @@ -17,15 +19,24 @@ import type {
const importsPrefix = `
import { ${Object.values(SYMBOLS).join(',')} } from "${MAIN_IMPORT}";
`;

let magic = new MagicString('');
export function compile(code = '', fileName = 'unknown') {
const componentName = fileName.split('.svelte').pop();

magic = new MagicString(code, {
filename: fileName,
});
// code-red
const result = parser.compile(code, {
dev: true,
preserveWhitespace: false,
preserveComments: false,
enableSourcemap: false,
generate: 'dom',
varsReport: 'strict',
immutable: false,
hydratable: false,
legacy: false,
css: 'external',
});

// console.log('ast', JSON.stringify(result.ast, null, 2));
Expand All @@ -41,7 +52,18 @@ export function compile(code = '', fileName = 'unknown') {
const imports = script.split('\n').filter((el) => el.includes('import '));
const content = script.split('\n').filter((el) => !el.includes('import '));

return `
magic.prepend(`
${importsPrefix}
${imports.join('\n')}
`);

let map = magic.generateMap({
source: fileName,
file: fileName + '.map',
includeContent: true,
});

let codeResult = `
${importsPrefix}
${imports.join('\n')}
export default function ${componentName}(args) {
Expand All @@ -51,6 +73,7 @@ export function compile(code = '', fileName = 'unknown') {
const roots = [${template}];
return ${SYMBOLS.FINALIZE_COMPONENT}(roots, this);
}`;
return { code: codeResult, map };
}

const p = (expression: any) => print(expression).code;
Expand Down Expand Up @@ -199,7 +222,9 @@ function hasAttribute(element: Element, attrName: string) {

function transformInlineComponent(node: Element): string {
const argsArray = node.attributes.map((attr) => {
return transformArgument(attr);
const result = transformArgument(attr);
magic.update(attr.start, attr.end, result);
return result;
});
const slotNodes =
node.children?.filter(
Expand Down Expand Up @@ -268,25 +293,45 @@ function transform(
} else if (typeof node !== 'object') {
return node;
} else if (t.isElement(node)) {
return transformElement(node);
const result = transformElement(node);
magic.update(node.start, node.end, result);
return result;
} else if (t.isAttribute(node)) {
return transformAttribute(node);
const result = transformAttribute(node);
magic.update(node.start, node.end, result);
return result;
} else if (t.isText(node)) {
return escapeText(node.data);
const result = escapeText(node.data);
magic.update(node.start, node.end, result);
return result;
} else if (t.isMustacheTag(node)) {
return transformMustacheTag(node);
const result = transformMustacheTag(node);
magic.update(node.start, node.end, result);
return result;
} else if (t.isAttributeShorthand(node)) {
return transformAttributeShorthand(node);
const result = transformAttributeShorthand(node);
magic.update(node.start, node.end, result);
return result;
} else if (t.isInlineComponent(node)) {
return transformInlineComponent(node);
const result = transformInlineComponent(node);
magic.update(node.start, node.end, result);
return result;
} else if (t.isIfBlock(node)) {
return transformIfBlock(node);
const result = transformIfBlock(node);
magic.update(node.start, node.end, result);
return result;
} else if (t.isElseBlock(node)) {
return transformElseBlock(node);
const result = transformElseBlock(node);
magic.update(node.start, node.end, result);
return result;
} else if (t.isEachBlock(node)) {
return transformEachBlock(node);
const result = transformEachBlock(node);
magic.update(node.start, node.end, result);
return result;
} else if (t.isSlot(node)) {
return transformSlot(node);
const result = transformSlot(node);
magic.update(node.start, node.end, result);
return result;
}

throw new Error(`Unknown node type: ${node.type}`);
Expand Down
18 changes: 16 additions & 2 deletions plugins/svlelte-compiler.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
let code = `
import { expect, test, describe } from 'vitest';
import { compile } from './svelte-compiler';

let sample1 = `
<div id="12" name={3+2} label="1{2}"></div>
<button disabled={!clickable}>...</button>
<button disabled>can't touch this</button>
Expand Down Expand Up @@ -65,8 +68,19 @@ let code = `
</FancyList>
`;

code = `<script>
let sample2 = `<script>
const name = 'Hello World';
</script>
<div>{name}</div>`;

describe('compiler', () => {
test('compile sample case #1', () => {
const result = compile(sample1);
expect(result).toMatchSnapshot();
});
test('compile sample case #2', () => {
const result = compile(sample1);
expect(result).toMatchSnapshot();
});
});
Loading

0 comments on commit 7538240

Please sign in to comment.