diff --git a/package.json b/package.json index 4e9eab44..fbb999f3 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/plugins/__snapshots__/svlelte-compiler.test.ts.snap b/plugins/__snapshots__/svlelte-compiler.test.ts.snap new file mode 100644 index 00000000..35e16af7 --- /dev/null +++ b/plugins/__snapshots__/svlelte-compiler.test.ts.snap @@ -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": [ + " +
+ + + + +
This div has no title attribute
+ + + +

{a} + {b} = {a + b}.

+
{(/^[A-Za-z ]+$/).test(value) ? x : y}
+{#if expression}...{:else}...{/if} +{#if answer === 42} +

what was the question?

+{/if} +----- +{#if porridge.temperature > 100} +

too hot!

+{:else if 80 > porridge.temperature} +

too cold!

+{:else} +

just right!

+{/if} +--- +{#each expression as name}...{/each} + + + + +{#each items as item, i} +
  • {i + 1}: {item.name} x {item.qty}
  • +{/each} + +{#each items as item (item.id)} +
  • {item.name} x {item.qty}
  • +{/each} + +{#each items as item, i (item.id)} +
  • {i + 1}: {item.name} x {item.qty}
  • +{/each} + + + + + +
    +
    ...
    +
    ...
    +
    ...
    +
    ...
    + +page {p} + + + + +

    Copyright (c) 2019 Svelte Industries

    +
    +", + ], + "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": [ + " +
    + + + + +
    This div has no title attribute
    + + + +

    {a} + {b} = {a + b}.

    +
    {(/^[A-Za-z ]+$/).test(value) ? x : y}
    +{#if expression}...{:else}...{/if} +{#if answer === 42} +

    what was the question?

    +{/if} +----- +{#if porridge.temperature > 100} +

    too hot!

    +{:else if 80 > porridge.temperature} +

    too cold!

    +{:else} +

    just right!

    +{/if} +--- +{#each expression as name}...{/each} + +
      + {#each items as item} +
    • {item.name} x {item.qty}
    • + {/each} +
    + + +{#each items as item, i} +
  • {i + 1}: {item.name} x {item.qty}
  • +{/each} + +{#each items as item (item.id)} +
  • {item.name} x {item.qty}
  • +{/each} + +{#each items as item, i (item.id)} +
  • {i + 1}: {item.name} x {item.qty}
  • +{/each} + + + + + +
    +
    ...
    +
    ...
    +
    ...
    +
    ...
    + +page {p} + + + + +

    Copyright (c) 2019 Svelte Industries

    +
    +", + ], + "version": 3, + }, +} +`; diff --git a/plugins/svelte-compiler.ts b/plugins/svelte-compiler.ts index df7d2725..b375d1f3 100644 --- a/plugins/svelte-compiler.ts +++ b/plugins/svelte-compiler.ts @@ -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, @@ -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)); @@ -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) { @@ -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; @@ -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( @@ -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}`); diff --git a/plugins/svlelte-compiler.test.ts b/plugins/svlelte-compiler.test.ts index 107bbdaf..8f76b5b0 100644 --- a/plugins/svlelte-compiler.test.ts +++ b/plugins/svlelte-compiler.test.ts @@ -1,4 +1,7 @@ -let code = ` +import { expect, test, describe } from 'vitest'; +import { compile } from './svelte-compiler'; + +let sample1 = `
    @@ -65,8 +68,19 @@ let code = ` `; -code = `
    {name}
    `; + +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(); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7b2dad58..396277a7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,9 @@ dependencies: decorator-transforms: specifier: 1.1.0 version: 1.1.0(@babel/core@7.23.6) + magic-string: + specifier: ^0.30.7 + version: 0.30.7 svelte: specifier: ^4.2.10 version: 4.2.10 @@ -2550,7 +2553,7 @@ packages: /@vitest/snapshot@1.1.1: resolution: {integrity: sha512-WnMHjv4VdHLbFGgCdVVvyRkRPnOKN75JJg+LLTdr6ah7YnL75W+7CTIMdzPEPzaDxA8r5yvSVlc1d8lH3yE28w==} dependencies: - magic-string: 0.30.5 + magic-string: 0.30.7 pathe: 1.1.1 pretty-format: 29.7.0 dev: true @@ -5745,8 +5748,8 @@ packages: sourcemap-codec: 1.4.8 dev: true - /magic-string@0.30.5: - resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==} + /magic-string@0.30.7: + resolution: {integrity: sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==} engines: {node: '>=12'} dependencies: '@jridgewell/sourcemap-codec': 1.4.15 @@ -7346,7 +7349,7 @@ packages: estree-walker: 3.0.3 is-reference: 3.0.2 locate-character: 3.0.0 - magic-string: 0.30.5 + magic-string: 0.30.7 periscopic: 3.1.0 dev: false @@ -7997,7 +8000,7 @@ packages: execa: 8.0.1 happy-dom: 13.0.6 local-pkg: 0.5.0 - magic-string: 0.30.5 + magic-string: 0.30.7 pathe: 1.1.1 picocolors: 1.0.0 std-env: 3.7.0 diff --git a/src/components/World.svelte b/src/components/World.svelte index ec5a46cf..9d9896e6 100644 --- a/src/components/World.svelte +++ b/src/components/World.svelte @@ -1,7 +1,11 @@ +