@canvasengine/compiler 2.0.0-beta.20 → 2.0.0-beta.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -65,7 +65,8 @@ function canvasengine() {
65
65
  "Video",
66
66
  "Mesh",
67
67
  "Svg",
68
- "DOMContainer"
68
+ "DOMContainer",
69
+ "DOMElement"
69
70
  ];
70
71
  return {
71
72
  name: "vite-plugin-ce",
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../index.ts"],"sourcesContent":["import { createFilter } from \"vite\";\nimport { parse } from \"acorn\";\nimport fs from \"fs\";\nimport pkg from \"peggy\";\nimport path from \"path\";\nimport * as ts from \"typescript\";\nimport { fileURLToPath } from 'url';\n\nconst { generate } = pkg;\n\nconst DEV_SRC = \"../../src\"\n\n/**\n * Formats a syntax error message with visual pointer to the error location\n * \n * @param {string} template - The template content that failed to parse\n * @param {object} error - The error object with location information\n * @returns {string} - Formatted error message with a visual pointer\n * \n * @example\n * ```\n * const errorMessage = showErrorMessage(\"<Canvas>test(d)</Canvas>\", syntaxError);\n * // Returns a formatted error message with an arrow pointing to 'd'\n * ```\n */\nfunction showErrorMessage(template: string, error: any): string {\n if (!error.location) {\n return `Syntax error: ${error.message}`;\n }\n\n const lines = template.split('\\n');\n const { line, column } = error.location.start;\n const errorLine = lines[line - 1] || '';\n \n // Create a visual pointer with an arrow\n const pointer = ' '.repeat(column - 1) + '^';\n \n return `Syntax error at line ${line}, column ${column}: ${error.message}\\n\\n` +\n `${errorLine}\\n${pointer}\\n`;\n}\n\n/**\n * Vite plugin to load shader files (.frag, .vert, .wgsl) as text strings\n * \n * This plugin allows importing shader files directly as string literals in your code.\n * It supports fragment shaders (.frag), vertex shaders (.vert), and WebGPU shaders (.wgsl).\n * The content is loaded as a raw string and can be used directly with graphics APIs.\n * \n * @returns {object} - Vite plugin configuration object\n * \n * @example\n * ```typescript\n * // In your vite.config.ts\n * import { shaderLoader } from './path/to/compiler'\n * \n * export default defineConfig({\n * plugins: [shaderLoader()]\n * })\n * \n * // In your code\n * import fragmentShader from './shader.frag'\n * import vertexShader from './shader.vert'\n * import computeShader from './shader.wgsl'\n * \n * console.log(fragmentShader) // Raw shader code as string\n * ```\n */\nexport function shaderLoader() {\n const filter = createFilter(/\\.(frag|vert|wgsl)$/);\n\n return {\n name: \"vite-plugin-shader-loader\",\n transform(code: string, id: string) {\n if (!filter(id)) return;\n\n // Escape the shader code to be safely embedded in a JavaScript string\n const escapedCode = code\n .replace(/\\\\/g, '\\\\\\\\') // Escape backslashes\n .replace(/`/g, '\\\\`') // Escape backticks\n .replace(/\\$/g, '\\\\$'); // Escape dollar signs\n\n // Return the shader content as a default export string\n return {\n code: `export default \\`${escapedCode}\\`;`,\n map: null,\n };\n },\n };\n}\n\nexport default function canvasengine() {\n const filter = createFilter(\"**/*.ce\");\n\n // Convert import.meta.url to a file path\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = path.dirname(__filename);\n\n const grammar = fs.readFileSync(\n path.join(__dirname, \"grammar.pegjs\").replace(\"dist/grammar.pegjs\", \"grammar.pegjs\"), \n \"utf8\");\n const parser = generate(grammar);\n const isDev = process.env.NODE_ENV === \"dev\";\n const FLAG_COMMENT = \"/*--[TPL]--*/\";\n\n const PRIMITIVE_COMPONENTS = [\n \"Canvas\",\n \"Sprite\",\n \"Text\",\n \"Viewport\",\n \"Graphics\",\n \"Container\",\n \"ImageMap\",\n \"NineSliceSprite\",\n \"Rect\",\n \"Circle\",\n \"Ellipse\",\n \"Triangle\",\n \"TilingSprite\",\n \"svg\",\n \"Video\",\n \"Mesh\",\n \"Svg\",\n \"DOMContainer\"\n ];\n\n return {\n name: \"vite-plugin-ce\",\n transform(code: string, id: string) {\n if (!filter(id)) return;\n\n // Extract the script content\n const scriptMatch = code.match(/<script>([\\s\\S]*?)<\\/script>/);\n let scriptContent = scriptMatch ? scriptMatch[1].trim() : \"\";\n \n // Transform SVG tags to Svg components\n let template = code.replace(/<script>[\\s\\S]*?<\\/script>/, \"\")\n .replace(/^\\s+|\\s+$/g, '');\n\n let parsedTemplate;\n try {\n parsedTemplate = parser.parse(template);\n } catch (error) {\n const errorMsg = showErrorMessage(template, error);\n throw new Error(`Error parsing template in file ${id}:\\n${errorMsg}`);\n }\n\n // trick to avoid typescript remove imports in scriptContent\n scriptContent += FLAG_COMMENT + parsedTemplate\n\n let transpiledCode = ts.transpileModule(scriptContent, {\n compilerOptions: {\n module: ts.ModuleKind.Preserve,\n },\n }).outputText;\n\n // remove code after /*---*/\n transpiledCode = transpiledCode.split(FLAG_COMMENT)[0]\n\n // Use Acorn to parse the script content\n const parsed = parse(transpiledCode, {\n sourceType: \"module\",\n ecmaVersion: 2020,\n });\n\n // Extract imports\n const imports = parsed.body.filter(\n (node) => node.type === \"ImportDeclaration\"\n );\n\n // Extract non-import statements from scriptContent\n const nonImportCode = parsed.body\n .filter((node) => node.type !== \"ImportDeclaration\")\n .map((node) => transpiledCode.slice(node.start, node.end))\n .join(\"\\n\");\n\n let importsCode = imports\n .map((imp) => {\n let importCode = transpiledCode.slice(imp.start, imp.end);\n if (isDev && importCode.includes(\"from 'canvasengine'\")) {\n importCode = importCode.replace(\n \"from 'canvasengine'\",\n `from '${DEV_SRC}'`\n );\n }\n return importCode;\n })\n .join(\"\\n\");\n\n // Define an array for required imports\n const requiredImports = [\"h\", \"computed\", \"cond\", \"loop\"];\n\n // Check for missing imports\n const missingImports = requiredImports.filter(\n (importName) =>\n !imports.some(\n (imp) =>\n imp.specifiers &&\n imp.specifiers.some(\n (spec) =>\n spec.type === \"ImportSpecifier\" &&\n spec.imported && \n 'name' in spec.imported &&\n spec.imported.name === importName\n )\n )\n );\n\n // Add missing imports\n if (missingImports.length > 0) {\n const additionalImportCode = `import { ${missingImports.join(\n \", \"\n )} } from ${isDev ? `'${DEV_SRC}'` : \"'canvasengine'\"};`;\n importsCode = `${additionalImportCode}\\n${importsCode}`;\n }\n\n // Check for primitive components in parsedTemplate\n const primitiveImports = PRIMITIVE_COMPONENTS.filter((component) =>\n parsedTemplate.includes(`h(${component}`)\n );\n\n // Add missing imports for primitive components\n primitiveImports.forEach((component) => {\n const importStatement = `import { ${component} } from ${\n isDev ? `'${DEV_SRC}'` : \"'canvasengine'\"\n };`;\n if (!importsCode.includes(importStatement)) {\n importsCode = `${importStatement}\\n${importsCode}`;\n }\n });\n\n // Generate the output\n const output = String.raw`\n ${importsCode}\n import { useProps, useDefineProps } from ${isDev ? `'${DEV_SRC}'` : \"'canvasengine'\"}\n\n export default function component($$props) {\n const $props = useProps($$props)\n const defineProps = useDefineProps($$props)\n ${nonImportCode}\n let $this = ${parsedTemplate}\n return $this\n }\n `;\n\n return {\n code: output,\n map: null,\n };\n },\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../index.ts"],"sourcesContent":["import { createFilter } from \"vite\";\nimport { parse } from \"acorn\";\nimport fs from \"fs\";\nimport pkg from \"peggy\";\nimport path from \"path\";\nimport * as ts from \"typescript\";\nimport { fileURLToPath } from 'url';\n\nconst { generate } = pkg;\n\nconst DEV_SRC = \"../../src\"\n\n/**\n * Formats a syntax error message with visual pointer to the error location\n * \n * @param {string} template - The template content that failed to parse\n * @param {object} error - The error object with location information\n * @returns {string} - Formatted error message with a visual pointer\n * \n * @example\n * ```\n * const errorMessage = showErrorMessage(\"<Canvas>test(d)</Canvas>\", syntaxError);\n * // Returns a formatted error message with an arrow pointing to 'd'\n * ```\n */\nfunction showErrorMessage(template: string, error: any): string {\n if (!error.location) {\n return `Syntax error: ${error.message}`;\n }\n\n const lines = template.split('\\n');\n const { line, column } = error.location.start;\n const errorLine = lines[line - 1] || '';\n \n // Create a visual pointer with an arrow\n const pointer = ' '.repeat(column - 1) + '^';\n \n return `Syntax error at line ${line}, column ${column}: ${error.message}\\n\\n` +\n `${errorLine}\\n${pointer}\\n`;\n}\n\n/**\n * Vite plugin to load shader files (.frag, .vert, .wgsl) as text strings\n * \n * This plugin allows importing shader files directly as string literals in your code.\n * It supports fragment shaders (.frag), vertex shaders (.vert), and WebGPU shaders (.wgsl).\n * The content is loaded as a raw string and can be used directly with graphics APIs.\n * \n * @returns {object} - Vite plugin configuration object\n * \n * @example\n * ```typescript\n * // In your vite.config.ts\n * import { shaderLoader } from './path/to/compiler'\n * \n * export default defineConfig({\n * plugins: [shaderLoader()]\n * })\n * \n * // In your code\n * import fragmentShader from './shader.frag'\n * import vertexShader from './shader.vert'\n * import computeShader from './shader.wgsl'\n * \n * console.log(fragmentShader) // Raw shader code as string\n * ```\n */\nexport function shaderLoader() {\n const filter = createFilter(/\\.(frag|vert|wgsl)$/);\n\n return {\n name: \"vite-plugin-shader-loader\",\n transform(code: string, id: string) {\n if (!filter(id)) return;\n\n // Escape the shader code to be safely embedded in a JavaScript string\n const escapedCode = code\n .replace(/\\\\/g, '\\\\\\\\') // Escape backslashes\n .replace(/`/g, '\\\\`') // Escape backticks\n .replace(/\\$/g, '\\\\$'); // Escape dollar signs\n\n // Return the shader content as a default export string\n return {\n code: `export default \\`${escapedCode}\\`;`,\n map: null,\n };\n },\n };\n}\n\nexport default function canvasengine() {\n const filter = createFilter(\"**/*.ce\");\n\n // Convert import.meta.url to a file path\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = path.dirname(__filename);\n\n const grammar = fs.readFileSync(\n path.join(__dirname, \"grammar.pegjs\").replace(\"dist/grammar.pegjs\", \"grammar.pegjs\"), \n \"utf8\");\n const parser = generate(grammar);\n const isDev = process.env.NODE_ENV === \"dev\";\n const FLAG_COMMENT = \"/*--[TPL]--*/\";\n\n const PRIMITIVE_COMPONENTS = [\n \"Canvas\",\n \"Sprite\",\n \"Text\",\n \"Viewport\",\n \"Graphics\",\n \"Container\",\n \"ImageMap\",\n \"NineSliceSprite\",\n \"Rect\",\n \"Circle\",\n \"Ellipse\",\n \"Triangle\",\n \"TilingSprite\",\n \"svg\",\n \"Video\",\n \"Mesh\",\n \"Svg\",\n \"DOMContainer\",\n \"DOMElement\"\n ];\n\n return {\n name: \"vite-plugin-ce\",\n transform(code: string, id: string) {\n if (!filter(id)) return;\n\n // Extract the script content\n const scriptMatch = code.match(/<script>([\\s\\S]*?)<\\/script>/);\n let scriptContent = scriptMatch ? scriptMatch[1].trim() : \"\";\n \n // Transform SVG tags to Svg components\n let template = code.replace(/<script>[\\s\\S]*?<\\/script>/, \"\")\n .replace(/^\\s+|\\s+$/g, '');\n\n let parsedTemplate;\n try {\n parsedTemplate = parser.parse(template);\n } catch (error) {\n const errorMsg = showErrorMessage(template, error);\n throw new Error(`Error parsing template in file ${id}:\\n${errorMsg}`);\n }\n\n // trick to avoid typescript remove imports in scriptContent\n scriptContent += FLAG_COMMENT + parsedTemplate\n\n let transpiledCode = ts.transpileModule(scriptContent, {\n compilerOptions: {\n module: ts.ModuleKind.Preserve,\n },\n }).outputText;\n\n // remove code after /*---*/\n transpiledCode = transpiledCode.split(FLAG_COMMENT)[0]\n\n // Use Acorn to parse the script content\n const parsed = parse(transpiledCode, {\n sourceType: \"module\",\n ecmaVersion: 2020,\n });\n\n // Extract imports\n const imports = parsed.body.filter(\n (node) => node.type === \"ImportDeclaration\"\n );\n\n // Extract non-import statements from scriptContent\n const nonImportCode = parsed.body\n .filter((node) => node.type !== \"ImportDeclaration\")\n .map((node) => transpiledCode.slice(node.start, node.end))\n .join(\"\\n\");\n\n let importsCode = imports\n .map((imp) => {\n let importCode = transpiledCode.slice(imp.start, imp.end);\n if (isDev && importCode.includes(\"from 'canvasengine'\")) {\n importCode = importCode.replace(\n \"from 'canvasengine'\",\n `from '${DEV_SRC}'`\n );\n }\n return importCode;\n })\n .join(\"\\n\");\n\n // Define an array for required imports\n const requiredImports = [\"h\", \"computed\", \"cond\", \"loop\"];\n\n // Check for missing imports\n const missingImports = requiredImports.filter(\n (importName) =>\n !imports.some(\n (imp) =>\n imp.specifiers &&\n imp.specifiers.some(\n (spec) =>\n spec.type === \"ImportSpecifier\" &&\n spec.imported && \n 'name' in spec.imported &&\n spec.imported.name === importName\n )\n )\n );\n\n // Add missing imports\n if (missingImports.length > 0) {\n const additionalImportCode = `import { ${missingImports.join(\n \", \"\n )} } from ${isDev ? `'${DEV_SRC}'` : \"'canvasengine'\"};`;\n importsCode = `${additionalImportCode}\\n${importsCode}`;\n }\n\n // Check for primitive components in parsedTemplate\n const primitiveImports = PRIMITIVE_COMPONENTS.filter((component) =>\n parsedTemplate.includes(`h(${component}`)\n );\n\n // Add missing imports for primitive components\n primitiveImports.forEach((component) => {\n const importStatement = `import { ${component} } from ${\n isDev ? `'${DEV_SRC}'` : \"'canvasengine'\"\n };`;\n if (!importsCode.includes(importStatement)) {\n importsCode = `${importStatement}\\n${importsCode}`;\n }\n });\n\n // Generate the output\n const output = String.raw`\n ${importsCode}\n import { useProps, useDefineProps } from ${isDev ? `'${DEV_SRC}'` : \"'canvasengine'\"}\n\n export default function component($$props) {\n const $props = useProps($$props)\n const defineProps = useDefineProps($$props)\n ${nonImportCode}\n let $this = ${parsedTemplate}\n return $this\n }\n `;\n\n return {\n code: output,\n map: null,\n };\n },\n };\n}\n"],"mappings":"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","names":[]}
package/grammar.pegjs CHANGED
@@ -36,7 +36,7 @@
36
36
 
37
37
  // Framework components that should NOT be transformed to DOM elements
38
38
  const frameworkComponents = new Set([
39
- 'Canvas', 'Container', 'Sprite', 'Text', 'DOMContainer', 'Svg'
39
+ 'Canvas', 'Container', 'Sprite', 'Text', 'DOMElement', 'Svg'
40
40
  ]);
41
41
 
42
42
  // DisplayObject special attributes that should not be in attrs
@@ -88,7 +88,7 @@
88
88
 
89
89
  function formatDOMElement(tagName, attributes) {
90
90
  if (attributes.length === 0) {
91
- return `h(DOMContainer, { element: "${tagName}" })`;
91
+ return `h(DOMElement, { element: "${tagName}" })`;
92
92
  }
93
93
 
94
94
  // Separate DisplayObject attributes from DOM attributes
@@ -131,7 +131,7 @@
131
131
  parts.push(...displayObjectAttrs);
132
132
  }
133
133
 
134
- return `h(DOMContainer, { ${parts.join(', ')} })`;
134
+ return `h(DOMElement, { ${parts.join(', ')} })`;
135
135
  }
136
136
  }
137
137
 
@@ -175,7 +175,7 @@ domElementWithText "DOM element with text content"
175
175
 
176
176
  if (isDOMElement(tagName)) {
177
177
  if (attributes.length === 0) {
178
- return `h(DOMContainer, { element: "${tagName}", textContent: ${text} })`;
178
+ return `h(DOMElement, { element: "${tagName}", textContent: ${text} })`;
179
179
  }
180
180
 
181
181
  // Separate DisplayObject attributes from DOM attributes
@@ -220,7 +220,7 @@ domElementWithText "DOM element with text content"
220
220
  parts.push(...displayObjectAttrs);
221
221
  }
222
222
 
223
- return `h(DOMContainer, { ${parts.join(', ')} })`;
223
+ return `h(DOMElement, { ${parts.join(', ')} })`;
224
224
  }
225
225
 
226
226
  // If not a DOM element, fall back to regular parsing
@@ -249,8 +249,8 @@ simpleTextPart "simple text part"
249
249
 
250
250
  simpleDynamicPart "simple dynamic part"
251
251
  = "{" _ expr:attributeValue _ "}" {
252
- // Handle dynamic expressions like {item.name}
253
- if (expr.trim().match(/^[a-zA-Z_][a-zA-Z0-9_.]*$/)) {
252
+ // Handle dynamic expressions like {item.name} or {@text}
253
+ if (expr.trim().match(/^@?[a-zA-Z_][a-zA-Z0-9_.]*$/)) {
254
254
  let foundSignal = false;
255
255
  const computedValue = expr.replace(/@?[a-zA-Z_][a-zA-Z0-9_]*(?!:)/g, (match) => {
256
256
  if (match.startsWith('@')) {
@@ -282,9 +282,9 @@ openCloseElement "component with content"
282
282
 
283
283
  if (attributes.length === 0) {
284
284
  if (children) {
285
- return `h(DOMContainer, { element: "${tagName}" }, ${children})`;
285
+ return `h(DOMElement, { element: "${tagName}" }, ${children})`;
286
286
  } else {
287
- return `h(DOMContainer, { element: "${tagName}" })`;
287
+ return `h(DOMElement, { element: "${tagName}" })`;
288
288
  }
289
289
  }
290
290
 
@@ -329,9 +329,9 @@ openCloseElement "component with content"
329
329
  }
330
330
 
331
331
  if (children) {
332
- return `h(DOMContainer, { ${parts.join(', ')} }, ${children})`;
332
+ return `h(DOMElement, { ${parts.join(', ')} }, ${children})`;
333
333
  } else {
334
- return `h(DOMContainer, { ${parts.join(', ')} })`;
334
+ return `h(DOMElement, { ${parts.join(', ')} })`;
335
335
  }
336
336
  }
337
337
 
package/index.ts CHANGED
@@ -120,7 +120,8 @@ export default function canvasengine() {
120
120
  "Video",
121
121
  "Mesh",
122
122
  "Svg",
123
- "DOMContainer"
123
+ "DOMContainer",
124
+ "DOMElement"
124
125
  ];
125
126
 
126
127
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canvasengine/compiler",
3
- "version": "2.0.0-beta.20",
3
+ "version": "2.0.0-beta.21",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -637,31 +637,31 @@ describe('DOM', () => {
637
637
  test('should compile input DOM', () => {
638
638
  const input = `<input type="text" />`;
639
639
  const output = parser.parse(input);
640
- expect(output).toBe('h(DOMContainer, { element: "input", attrs: { type: \'text\' } })');
640
+ expect(output).toBe('h(DOMElement, { element: "input", attrs: { type: \'text\' } })');
641
641
  });
642
642
 
643
643
  test('should compile div DOM', () => {
644
644
  const input = `<div class="container" />`;
645
645
  const output = parser.parse(input);
646
- expect(output).toBe('h(DOMContainer, { element: "div", attrs: { class: \'container\' } })');
646
+ expect(output).toBe('h(DOMElement, { element: "div", attrs: { class: \'container\' } })');
647
647
  });
648
648
 
649
649
  test('should compile button DOM', () => {
650
650
  const input = `<button type="submit" />`;
651
651
  const output = parser.parse(input);
652
- expect(output).toBe('h(DOMContainer, { element: "button", attrs: { type: \'submit\' } })');
652
+ expect(output).toBe('h(DOMElement, { element: "button", attrs: { type: \'submit\' } })');
653
653
  });
654
654
 
655
655
  test('should compile button DOM', () => {
656
656
  const input = `<button>Text</button>`;
657
657
  const output = parser.parse(input);
658
- expect(output).toBe('h(DOMContainer, { element: "button", textContent: \'Text\' })');
658
+ expect(output).toBe('h(DOMElement, { element: "button", textContent: \'Text\' })');
659
659
  });
660
660
 
661
661
  test('should compile textarea DOM with dynamic attributes', () => {
662
662
  const input = `<textarea rows={rows} cols={cols} />`;
663
663
  const output = parser.parse(input);
664
- expect(output).toBe('h(DOMContainer, { element: "textarea", attrs: { rows: rows, cols: cols } })');
664
+ expect(output).toBe('h(DOMElement, { element: "textarea", attrs: { rows: rows, cols: cols } })');
665
665
  });
666
666
 
667
667
  test('should not transform Canvas to DOM', () => {
@@ -682,7 +682,7 @@ describe('DOM', () => {
682
682
  <p>Hello World</p>
683
683
  </div>`;
684
684
  const output = parser.parse(input);
685
- expect(output).toBe('h(DOMContainer, { element: "div", attrs: { class: \'container\' } }, h(DOMContainer, { element: "p", textContent: \'Hello World\' }))');
685
+ expect(output).toBe('h(DOMElement, { element: "div", attrs: { class: \'container\' } }, h(DOMElement, { element: "p", textContent: \'Hello World\' }))');
686
686
  });
687
687
 
688
688
  test('should compile deeply nested DOM elements', () => {
@@ -694,7 +694,7 @@ describe('DOM', () => {
694
694
  </div>`;
695
695
  const output = parser.parse(input);
696
696
  expect(output.replace(/\s+/g, "")).toBe(
697
- `h(DOMContainer, { element: "div", attrs: { class: 'wrapper' } }, h(DOMContainer, { element: "section" }, [h(DOMContainer, { element: "h1", textContent: 'Title' }), h(DOMContainer, { element: "p", textContent: 'Content' })]))`.replace(/\s+/g, "")
697
+ `h(DOMElement, { element: "div", attrs: { class: 'wrapper' } }, h(DOMElement, { element: "section" }, [h(DOMElement, { element: "h1", textContent: 'Title' }), h(DOMElement, { element: "p", textContent: 'Content' })]))`.replace(/\s+/g, "")
698
698
  );
699
699
  });
700
700
 
@@ -706,7 +706,7 @@ describe('DOM', () => {
706
706
  </ul>`;
707
707
  const output = parser.parse(input);
708
708
  expect(output.replace(/\s+/g, "")).toBe(
709
- `h(DOMContainer, { element: "ul" }, [h(DOMContainer, { element: "li", textContent: 'Item 1' }), h(DOMContainer, { element: "li", textContent: 'Item 2' }), h(DOMContainer, { element: "li", textContent: 'Item 3' })])`.replace(/\s+/g, "")
709
+ `h(DOMElement, { element: "ul" }, [h(DOMElement, { element: "li", textContent: 'Item 1' }), h(DOMElement, { element: "li", textContent: 'Item 2' }), h(DOMElement, { element: "li", textContent: 'Item 3' })])`.replace(/\s+/g, "")
710
710
  );
711
711
  });
712
712
 
@@ -719,14 +719,14 @@ describe('DOM', () => {
719
719
  </div>`;
720
720
  const output = parser.parse(input);
721
721
  expect(output.replace(/\s+/g, "")).toBe(
722
- `h(DOMContainer, { element: "div", attrs: { class: 'game-container' } }, [h(Canvas, { width: 800, height: 600 }), h(DOMContainer, { element: "div", attrs: { class: 'ui' } }, h(DOMContainer, { element: "button", textContent: 'Start Game' }))])`.replace(/\s+/g, "")
722
+ `h(DOMElement, { element: "div", attrs: { class: 'game-container' } }, [h(Canvas, { width: 800, height: 600 }), h(DOMElement, { element: "div", attrs: { class: 'ui' } }, h(DOMElement, { element: "button", textContent: 'Start Game' }))])`.replace(/\s+/g, "")
723
723
  );
724
724
  });
725
725
 
726
726
  test('should compile DOM with attributes and text content', () => {
727
727
  const input = `<button class="btn primary" type="submit">Submit Form</button>`;
728
728
  const output = parser.parse(input);
729
- expect(output).toBe('h(DOMContainer, { element: "button", attrs: { class: \'btn primary\', type: \'submit\' }, textContent: \'Submit Form\' })');
729
+ expect(output).toBe('h(DOMElement, { element: "button", attrs: { class: \'btn primary\', type: \'submit\' }, textContent: \'Submit Form\' })');
730
730
  });
731
731
  });
732
732
 
@@ -734,7 +734,7 @@ describe('DOM with special attributes', () => {
734
734
  test('should compile DOM with special attributes', () => {
735
735
  const input = `<input type="password" x={100} y={100} />`;
736
736
  const output = parser.parse(input);
737
- expect(output).toBe('h(DOMContainer, { element: "input", attrs: { type: \'password\' }, x: 100, y: 100 })');
737
+ expect(output).toBe('h(DOMElement, { element: "input", attrs: { type: \'password\' }, x: 100, y: 100 })');
738
738
  });
739
739
  });
740
740
 
@@ -747,10 +747,20 @@ describe('DOM with Control Structures', () => {
747
747
  `;
748
748
  const output = parser.parse(input);
749
749
  expect(output.replace(/\s+/g, "")).toBe(
750
- `loop(items, item => h(DOMContainer, { element: "li", textContent: computed(() => item().name()) }))`.replace(/\s+/g, "")
750
+ `loop(items, item => h(DOMElement, { element: "li", textContent: computed(() => item().name()) }))`.replace(/\s+/g, "")
751
751
  );
752
752
  });
753
753
 
754
+ test('Use literal text content', () => {
755
+ const input = `
756
+ <p>{@text}</p>
757
+ `;
758
+ const output = parser.parse(input);
759
+ expect(output.replace(/\s+/g, "")).toBe(
760
+ `h(DOMElement, { element: "p", textContent: text })`.replace(/\s+/g, "")
761
+ );
762
+ })
763
+
754
764
  test('should compile @for loop with nested DOM structure', () => {
755
765
  const input = `
756
766
  <ul class="menu">
@@ -763,7 +773,7 @@ describe('DOM with Control Structures', () => {
763
773
  `;
764
774
  const output = parser.parse(input);
765
775
  expect(output.replace(/\s+/g, "")).toBe(
766
- `h(DOMContainer, { element: "ul", attrs: { class: 'menu' } }, loop(menuItems, item => h(DOMContainer, { element: "li", attrs: { class: 'menu-item' } }, h(DOMContainer, { element: "a", attrs: { href: computed(() => item().url()) }, textContent: computed(() => item().title()) }))))`.replace(/\s+/g, "")
776
+ `h(DOMElement, { element: "ul", attrs: { class: 'menu' } }, loop(menuItems, item => h(DOMElement, { element: "li", attrs: { class: 'menu-item' } }, h(DOMElement, { element: "a", attrs: { href: computed(() => item().url()) }, textContent: computed(() => item().title()) }))))`.replace(/\s+/g, "")
767
777
  );
768
778
  });
769
779
 
@@ -777,7 +787,7 @@ describe('DOM with Control Structures', () => {
777
787
  `;
778
788
  const output = parser.parse(input);
779
789
  expect(output.replace(/\s+/g, "")).toBe(
780
- `cond(showMessage, () => h(DOMContainer, { element: "div", attrs: { class: 'alert' } }, h(DOMContainer, { element: "p", textContent: 'Important message!' })))`.replace(/\s+/g, "")
790
+ `cond(showMessage, () => h(DOMElement, { element: "div", attrs: { class: 'alert' } }, h(DOMElement, { element: "p", textContent: 'Important message!' })))`.replace(/\s+/g, "")
781
791
  );
782
792
  });
783
793
 
@@ -789,7 +799,7 @@ describe('DOM with Control Structures', () => {
789
799
  `;
790
800
  const output = parser.parse(input);
791
801
  expect(output.replace(/\s+/g, "")).toBe(
792
- `cond(isVisible, () => h(DOMContainer, { element: "button", textContent: 'Click me' }))`.replace(/\s+/g, "")
802
+ `cond(isVisible, () => h(DOMElement, { element: "button", textContent: 'Click me' }))`.replace(/\s+/g, "")
793
803
  );
794
804
  });
795
805
 
@@ -812,7 +822,7 @@ describe('DOM with Control Structures', () => {
812
822
  `;
813
823
  const output = parser.parse(input);
814
824
  expect(output.replace(/\s+/g, "")).toBe(
815
- `h(DOMContainer, { element: "div", attrs: { class: 'container' } }, loop(sections, section => cond(section.visible, () => h(DOMContainer, { element: "section", attrs: { class: 'content' } }, [h(DOMContainer, { element: "h2", textContent: computed(() => section().title()) }), h(DOMContainer, { element: "div", attrs: { class: 'items' } }, loop(section.items, item => h(DOMContainer, { element: "div", attrs: { class: 'item' }, textContent: computed(() => item().name()) })))]))))`.replace(/\s+/g, "")
825
+ `h(DOMElement, { element: "div", attrs: { class: 'container' } }, loop(sections, section => cond(section.visible, () => h(DOMElement, { element: "section", attrs: { class: 'content' } }, [h(DOMElement, { element: "h2", textContent: computed(() => section().title()) }), h(DOMElement, { element: "div", attrs: { class: 'items' } }, loop(section.items, item => h(DOMElement, { element: "div", attrs: { class: 'item' }, textContent: computed(() => item().name()) })))]))))`.replace(/\s+/g, "")
816
826
  );
817
827
  });
818
828
 
@@ -837,7 +847,7 @@ describe('DOM with Control Structures', () => {
837
847
  `;
838
848
  const output = parser.parse(input);
839
849
  expect(output.replace(/\s+/g, "")).toBe(
840
- `h(DOMContainer, { element: "table" }, [h(DOMContainer, { element: "thead" }, h(DOMContainer, { element: "tr" }, [h(DOMContainer, { element: "th", textContent: 'Name' }), h(DOMContainer, { element: "th", textContent: 'Age' })])), h(DOMContainer, { element: "tbody" }, loop(users, user => h(DOMContainer, { element: "tr" }, [h(DOMContainer, { element: "td", textContent: computed(() => user().name()) }), h(DOMContainer, { element: "td", textContent: computed(() => user().age()) })])))])`.replace(/\s+/g, "")
850
+ `h(DOMElement, { element: "table" }, [h(DOMElement, { element: "thead" }, h(DOMElement, { element: "tr" }, [h(DOMElement, { element: "th", textContent: 'Name' }), h(DOMElement, { element: "th", textContent: 'Age' })])), h(DOMElement, { element: "tbody" }, loop(users, user => h(DOMElement, { element: "tr" }, [h(DOMElement, { element: "td", textContent: computed(() => user().name()) }), h(DOMElement, { element: "td", textContent: computed(() => user().age()) })])))])`.replace(/\s+/g, "")
841
851
  );
842
852
  });
843
853
 
@@ -857,7 +867,7 @@ describe('DOM with Control Structures', () => {
857
867
  `;
858
868
  const output = parser.parse(input);
859
869
  expect(output.replace(/\s+/g, "")).toBe(
860
- `h(DOMContainer, { element: "div", attrs: { class: 'status' } }, [cond(isLoading, () => h(DOMContainer, { element: "div", attrs: { class: 'spinner' }, textContent: 'Loading...' })), cond(hasError, () => h(DOMContainer, { element: "div", attrs: { class: 'error' }, textContent: 'Error occurred!' })), cond(isSuccess, () => h(DOMContainer, { element: "div", attrs: { class: 'success' }, textContent: 'Success!' }))])`.replace(/\s+/g, "")
870
+ `h(DOMElement, { element: "div", attrs: { class: 'status' } }, [cond(isLoading, () => h(DOMElement, { element: "div", attrs: { class: 'spinner' }, textContent: 'Loading...' })), cond(hasError, () => h(DOMElement, { element: "div", attrs: { class: 'error' }, textContent: 'Error occurred!' })), cond(isSuccess, () => h(DOMElement, { element: "div", attrs: { class: 'success' }, textContent: 'Success!' }))])`.replace(/\s+/g, "")
861
871
  );
862
872
  });
863
873
 
@@ -885,7 +895,7 @@ describe('DOM with Control Structures', () => {
885
895
  `;
886
896
  const output = parser.parse(input);
887
897
  expect(output.replace(/\s+/g, "")).toBe(
888
- `h(DOMContainer, { element: "div", attrs: { class: 'game-wrapper' } }, [h(Canvas, { width: 800, height: 600 }, loop(sprites, sprite => h(Sprite, { x: computed(() => sprite().x()), y: computed(() => sprite().y()) }))), h(DOMContainer, { element: "div", attrs: { class: 'ui-overlay' } }, [cond(showScore, () => h(DOMContainer, { element: "div", attrs: { class: 'score' }, textContent: computed(() => 'Score: ' + computed(() => score())) })), cond(showMenu, () => h(DOMContainer, { element: "div", attrs: { class: 'menu' } }, loop(menuOptions, option => h(DOMContainer, { element: "button", attrs: { class: 'menu-btn' }, textContent: computed(() => option().label()) }))))])])`.replace(/\s+/g, "")
898
+ `h(DOMElement, { element: "div", attrs: { class: 'game-wrapper' } }, [h(Canvas, { width: 800, height: 600 }, loop(sprites, sprite => h(Sprite, { x: computed(() => sprite().x()), y: computed(() => sprite().y()) }))), h(DOMElement, { element: "div", attrs: { class: 'ui-overlay' } }, [cond(showScore, () => h(DOMElement, { element: "div", attrs: { class: 'score' }, textContent: computed(() => 'Score: ' + computed(() => score())) })), cond(showMenu, () => h(DOMElement, { element: "div", attrs: { class: 'menu' } }, loop(menuOptions, option => h(DOMElement, { element: "button", attrs: { class: 'menu-btn' }, textContent: computed(() => option().label()) }))))])])`.replace(/\s+/g, "")
889
899
  );
890
900
  });
891
901
  });