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}
+ - {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}
+
+
+
+
+
+
+...
+...
+...
+...
+
+`,
+) {
+ // 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'}