diff --git a/package.json b/package.json index 1656de49..4e9eab44 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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" } } diff --git a/plugins/svelte-compiler.js b/plugins/svelte-compiler.js new file mode 100644 index 00000000..81bed007 --- /dev/null +++ b/plugins/svelte-compiler.js @@ -0,0 +1,231 @@ +import * as parser from 'svelte/compiler'; +import { print } from 'code-red'; + +export function compile( + code = ` +
+ + + + +
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} + + + + + +
    +
    ...
    +
    ...
    +
    ...
    +
    ...
    + +`, +) { + // 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()); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 374d14f9..7b2dad58 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,12 +14,18 @@ dependencies: '@glimmer/syntax': specifier: ^0.87.1 version: 0.87.1 + code-red: + specifier: ^1.0.4 + version: 1.0.4 content-tag: specifier: ^1.2.2 version: 1.2.2 decorator-transforms: specifier: 1.1.0 version: 1.1.0(@babel/core@7.23.6) + svelte: + specifier: ^4.2.10 + version: 4.2.10 devDependencies: '@glint/core': @@ -2447,7 +2453,6 @@ packages: /@types/estree@1.0.5: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - dev: true /@types/fs-extra@11.0.4: resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} @@ -2641,7 +2646,6 @@ packages: resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==} engines: {node: '>=0.4.0'} hasBin: true - dev: true /aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} @@ -2837,6 +2841,12 @@ packages: sprintf-js: 1.0.3 dev: true + /aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + dependencies: + dequal: 2.0.3 + dev: false + /array-binsearch@1.0.1: resolution: {integrity: sha512-KZw1m6nCIGsjuUHnY2e1mOZPxH7widuwutZChvgoXwe8+ZCKM7GiIBtgBMNiUKBycPoh6tLOnJBQApjm3wMelw==} dev: true @@ -2942,6 +2952,12 @@ packages: engines: {node: '>= 0.4'} dev: true + /axobject-query@4.0.0: + resolution: {integrity: sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==} + dependencies: + dequal: 2.0.3 + dev: false + /babel-import-util@0.2.0: resolution: {integrity: sha512-CtWYYHU/MgK88rxMrLfkD356dApswtR/kWZ/c6JifG1m10e7tBBrs/366dFzWMAoqYmG5/JSh+94tUSpIwh+ag==} engines: {node: '>= 12.*'} @@ -3529,6 +3545,16 @@ packages: engines: {node: '>=0.10.0'} dev: true + /code-red@1.0.4: + resolution: {integrity: sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + '@types/estree': 1.0.5 + acorn: 8.11.2 + estree-walker: 3.0.3 + periscopic: 3.1.0 + dev: false + /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: @@ -3675,6 +3701,14 @@ packages: which: 2.0.2 dev: true + /css-tree@2.3.1: + resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + dependencies: + mdn-data: 2.0.30 + source-map-js: 1.0.2 + dev: false + /css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} dev: true @@ -3826,6 +3860,11 @@ packages: engines: {node: '>= 0.8'} dev: true + /dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + dev: false + /destroy@1.2.0: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -4215,6 +4254,12 @@ packages: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} dev: true + /estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + dependencies: + '@types/estree': 1.0.5 + dev: false + /esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -5180,6 +5225,12 @@ packages: resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} dev: true + /is-reference@3.0.2: + resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} + dependencies: + '@types/estree': 1.0.5 + dev: false + /is-regex@1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} @@ -5556,6 +5607,10 @@ packages: pkg-types: 1.0.3 dev: true + /locate-character@3.0.0: + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + dev: false + /locate-path@2.0.0: resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} engines: {node: '>=4'} @@ -5695,7 +5750,6 @@ packages: engines: {node: '>=12'} dependencies: '@jridgewell/sourcemap-codec': 1.4.15 - dev: true /make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} @@ -5737,6 +5791,10 @@ packages: minimatch: 3.1.2 dev: true + /mdn-data@2.0.30: + resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + dev: false + /media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} @@ -6299,6 +6357,14 @@ packages: through: 2.3.8 dev: true + /periscopic@3.1.0: + resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} + dependencies: + '@types/estree': 1.0.5 + estree-walker: 3.0.3 + is-reference: 3.0.2 + dev: false + /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} @@ -6996,7 +7062,6 @@ packages: /source-map-js@1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} - dev: true /source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} @@ -7265,6 +7330,26 @@ packages: engines: {node: '>= 0.4'} dev: true + /svelte@4.2.10: + resolution: {integrity: sha512-Ep06yCaCdgG1Mafb/Rx8sJ1QS3RW2I2BxGp2Ui9LBHSZ2/tO/aGLc5WqPjgiAP6KAnLJGaIr/zzwQlOo1b8MxA==} + engines: {node: '>=16'} + dependencies: + '@ampproject/remapping': 2.2.1 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.20 + '@types/estree': 1.0.5 + acorn: 8.11.2 + aria-query: 5.3.0 + axobject-query: 4.0.0 + code-red: 1.0.4 + css-tree: 2.3.1 + estree-walker: 3.0.3 + is-reference: 3.0.2 + locate-character: 3.0.0 + magic-string: 0.30.5 + periscopic: 3.1.0 + dev: false + /symbol-observable@1.2.0: resolution: {integrity: sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==} engines: {node: '>=0.10.0'}