@canvasengine/compiler 2.0.0-beta.26 → 2.0.0-beta.27

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
@@ -66,7 +66,8 @@ function canvasengine() {
66
66
  "Mesh",
67
67
  "Svg",
68
68
  "DOMContainer",
69
- "DOMElement"
69
+ "DOMElement",
70
+ "Button"
70
71
  ];
71
72
  return {
72
73
  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 \"DOMElement\"\n ];\n\n return {\n name: \"vite-plugin-ce\",\n transform(code: string, id: string) {\n if (!filter(id)) return null;\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 \"Button\"\n ];\n\n return {\n name: \"vite-plugin-ce\",\n transform(code: string, id: string) {\n if (!filter(id)) return null;\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', 'DOMElement', 'Svg'
39
+ 'Canvas', 'Container', 'Sprite', 'Text', 'DOMElement', 'Svg', 'Button'
40
40
  ]);
41
41
 
42
42
  // DisplayObject special attributes that should not be in attrs
@@ -396,12 +396,14 @@ dynamicAttribute "dynamic attribute"
396
396
  const needsQuotes = /[^a-zA-Z0-9_$]/.test(attributeName);
397
397
  const formattedName = needsQuotes ? `'${attributeName}'` : attributeName;
398
398
 
399
- // If it's a complex object literal starting with curly braces, preserve it as is
400
- if (attributeValue.trim().startsWith('{') && attributeValue.trim().endsWith('}')) {
401
- return `${formattedName}: ${attributeValue}`;
402
- }
403
399
 
404
- // If it's a template string, preserve it as is
400
+ // If it's a complex object with strings, preserve it as is
401
+ if (attributeValue.trim().startsWith('{') && attributeValue.trim().endsWith('}') &&
402
+ (attributeValue.includes('"') || attributeValue.includes("'"))) {
403
+ return `${formattedName}: ${attributeValue}`;
404
+ }
405
+
406
+ // If it's a template string, preserve it as is
405
407
  if (attributeValue.trim().startsWith('`') && attributeValue.trim().endsWith('`')) {
406
408
  return `${formattedName}: ${attributeValue}`;
407
409
  }
@@ -413,16 +415,52 @@ dynamicAttribute "dynamic attribute"
413
415
  return `${formattedName}: ${attributeValue}`;
414
416
  } else {
415
417
  let foundSignal = false;
416
- const computedValue = attributeValue.replace(/@?[a-zA-Z_][a-zA-Z0-9_]*(?!:)/g, (match) => {
418
+ let hasLiterals = false;
419
+ const computedValue = attributeValue.replace(/@?([a-zA-Z_][a-zA-Z0-9_]*)\b(?!\s*:)/g, (match, p1, offset) => {
420
+ // Don't transform keywords, numbers, or if we're inside quotes
421
+ if (['true', 'false', 'null'].includes(p1) || /^\d+(\.\d+)?$/.test(p1)) {
422
+ return match;
423
+ }
424
+
425
+ // Check if we're inside a string literal
426
+ const beforeMatch = attributeValue.substring(0, offset);
427
+ const singleQuotesBefore = (beforeMatch.match(/'/g) || []).length;
428
+ const doubleQuotesBefore = (beforeMatch.match(/"/g) || []).length;
429
+
430
+ // If we're inside quotes, don't transform
431
+ if (singleQuotesBefore % 2 === 1 || doubleQuotesBefore % 2 === 1) {
432
+ return match;
433
+ }
434
+
417
435
  if (match.startsWith('@')) {
418
- return match.substring(1);
436
+ hasLiterals = true;
437
+ return p1; // Remove @ prefix
419
438
  }
420
439
  foundSignal = true;
421
- return `${match}()`;
440
+ return `${p1}()`;
422
441
  });
442
+
423
443
  if (foundSignal) {
444
+ // For objects, wrap in parentheses
445
+ if (attributeValue.trim().startsWith('{') && attributeValue.trim().endsWith('}')) {
446
+ // Remove spaces for objects in parentheses
447
+ const cleanedObject = computedValue.replace(/{ /g, '{').replace(/ }/g, '}');
448
+ return `${formattedName}: computed(() => (${cleanedObject}))`;
449
+ }
424
450
  return `${formattedName}: computed(() => ${computedValue})`;
425
451
  }
452
+
453
+ // If only literals (all @), don't use computed
454
+ if (hasLiterals && !foundSignal) {
455
+ return `${formattedName}: ${computedValue}`;
456
+ }
457
+
458
+ // For static objects, add parentheses if it's an object
459
+ if (attributeValue.trim().startsWith('{') && attributeValue.trim().endsWith('}')) {
460
+ // Remove spaces for objects in parentheses
461
+ const cleanedObject = computedValue.replace(/{ /g, '{').replace(/ }/g, '}');
462
+ return `${formattedName}: (${cleanedObject})`;
463
+ }
426
464
  return `${formattedName}: ${computedValue}`;
427
465
  }
428
466
  }
@@ -467,6 +505,7 @@ propertyValue
467
505
  / element
468
506
  / functionWithElement
469
507
  / stringLiteral
508
+ / number
470
509
  / identifier
471
510
 
472
511
  nestedObject
@@ -548,8 +587,31 @@ tupleDestructuring "destructuring pattern"
548
587
  }
549
588
 
550
589
  ifCondition "if condition"
551
- = _ "@if" _ "(" _ condition:condition _ ")" _ "{" _ content:content _ "}" _ {
552
- return `cond(${condition}, () => ${content})`;
590
+ = _ "@if" _ "(" _ condition:condition _ ")" _ "{" _ content:content _ "}" _ elseIfs:elseIfClause* elseClause:elseClause? _ {
591
+ let result = `cond(${condition}, () => ${content}`;
592
+
593
+ // Add else if clauses
594
+ elseIfs.forEach(elseIf => {
595
+ result += `, [${elseIf.condition}, () => ${elseIf.content}]`;
596
+ });
597
+
598
+ // Add else clause if present
599
+ if (elseClause) {
600
+ result += `, () => ${elseClause}`;
601
+ }
602
+
603
+ result += ')';
604
+ return result;
605
+ }
606
+
607
+ elseIfClause "else if clause"
608
+ = _ "@else" _ "if" _ "(" _ condition:condition _ ")" _ "{" _ content:content _ "}" _ {
609
+ return { condition, content };
610
+ }
611
+
612
+ elseClause "else clause"
613
+ = _ "@else" _ "{" _ content:content _ "}" _ {
614
+ return content;
553
615
  }
554
616
 
555
617
  tagName "tag name"
@@ -601,12 +663,26 @@ condition "condition expression"
601
663
  // Transform simple identifiers to function calls like "foo" to "foo()"
602
664
  // This regex matches identifiers not followed by an opening parenthesis.
603
665
  // This transformation should only apply if we are wrapping in 'computed'.
604
- if (originalText.includes('!') || originalText.includes('&&') || originalText.includes('||')) {
605
- const transformedText = originalText.replace(/\b([a-zA-Z_][a-zA-Z0-9_]*)\b(?!\s*\()/g, (match) => {
666
+ if (originalText.includes('!') || originalText.includes('&&') || originalText.includes('||') ||
667
+ originalText.includes('>=') || originalText.includes('<=') || originalText.includes('===') ||
668
+ originalText.includes('!==') || originalText.includes('==') || originalText.includes('!=') ||
669
+ originalText.includes('>') || originalText.includes('<')) {
670
+ const transformedText = originalText.replace(/\b([a-zA-Z_][a-zA-Z0-9_]*)\b(?!\s*\()/g, (match, p1, offset) => {
606
671
  // Do not transform keywords (true, false, null) or numeric literals
607
672
  if (['true', 'false', 'null'].includes(match) || /^\d+(\.\d+)?$/.test(match)) {
608
673
  return match;
609
674
  }
675
+ // Check if the match is inside quotes
676
+ const beforeMatch = originalText.substring(0, offset);
677
+ const afterMatch = originalText.substring(offset + match.length);
678
+ const singleQuotesBefore = (beforeMatch.match(/'/g) || []).length;
679
+ const doubleQuotesBefore = (beforeMatch.match(/"/g) || []).length;
680
+
681
+ // If we're inside quotes, don't transform
682
+ if (singleQuotesBefore % 2 === 1 || doubleQuotesBefore % 2 === 1) {
683
+ return match;
684
+ }
685
+
610
686
  return `${match}()`;
611
687
  });
612
688
  return `computed(() => ${transformedText})`;
package/index.ts CHANGED
@@ -121,7 +121,8 @@ export default function canvasengine() {
121
121
  "Mesh",
122
122
  "Svg",
123
123
  "DOMContainer",
124
- "DOMElement"
124
+ "DOMElement",
125
+ "Button"
125
126
  ];
126
127
 
127
128
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canvasengine/compiler",
3
- "version": "2.0.0-beta.26",
3
+ "version": "2.0.0-beta.27",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -131,7 +131,161 @@ describe("Compiler", () => {
131
131
  expect(output).toBe(`h(Canvas, { width: 20 })`);
132
132
  });
133
133
 
134
- // TODO
134
+ test("should compile if/else condition", () => {
135
+ const input = `
136
+ @if (sprite) {
137
+ <Sprite />
138
+ }
139
+ @else {
140
+ <Text text="No sprite" />
141
+ }
142
+ `;
143
+ const output = parser.parse(input);
144
+ expect(output).toBe(`cond(sprite, () => h(Sprite), () => h(Text, { text: 'No sprite' }))`);
145
+ });
146
+
147
+ test("should compile if/else if condition", () => {
148
+ const input = `
149
+ @if (score >= 90) {
150
+ <Text text="A+" />
151
+ }
152
+ @else if (score >= 80) {
153
+ <Text text="A" />
154
+ }
155
+ `;
156
+ const output = parser.parse(input);
157
+ expect(output).toBe(`cond(computed(() => score() >= 90), () => h(Text, { text: 'A+' }), [computed(() => score() >= 80), () => h(Text, { text: 'A' })])`);
158
+ });
159
+
160
+ test("should compile if/else if/else condition", () => {
161
+ const input = `
162
+ @if (score >= 90) {
163
+ <Text text="A+" />
164
+ }
165
+ @else if (score >= 80) {
166
+ <Text text="A" />
167
+ }
168
+ @else {
169
+ <Text text="F" />
170
+ }
171
+ `;
172
+ const output = parser.parse(input);
173
+ expect(output).toBe(`cond(computed(() => score() >= 90), () => h(Text, { text: 'A+' }), [computed(() => score() >= 80), () => h(Text, { text: 'A' })], () => h(Text, { text: 'F' }))`);
174
+ });
175
+
176
+ test("should compile if/else if/else condition within canvas", () => {
177
+ const input = `<Canvas>
178
+ <Container>
179
+ @if (score >= 90) {
180
+ <Text text="Grade: A+" x={100} y={100} color="gold" size={24} />
181
+ <Text text="Excellent work!" x={100} y={130} color="gold" size={16} />
182
+ }
183
+ </Container>
184
+ </Canvas>
185
+ `;
186
+
187
+ const output = parser.parse(input);
188
+ expect(output).toBe(`h(Canvas, null, h(Container, null, cond(computed(() => score() >= 90), () => [h(Text, { text: 'Grade: A+', x: 100, y: 100, color: 'gold', size: 24 }), h(Text, { text: 'Excellent work!', x: 100, y: 130, color: 'gold', size: 16 })])))`);
189
+ });
190
+
191
+ test("should compile multiple else if conditions", () => {
192
+ const input = `
193
+ @if (score >= 90) {
194
+ <Text text="A+" />
195
+ }
196
+ @else if (score >= 80) {
197
+ <Text text="A" />
198
+ }
199
+ @else if (score >= 70) {
200
+ <Text text="B" />
201
+ }
202
+ @else if (score >= 60) {
203
+ <Text text="C" />
204
+ }
205
+ @else {
206
+ <Text text="F" />
207
+ }
208
+ `;
209
+ const output = parser.parse(input);
210
+ expect(output).toBe(`cond(computed(() => score() >= 90), () => h(Text, { text: 'A+' }), [computed(() => score() >= 80), () => h(Text, { text: 'A' })], [computed(() => score() >= 70), () => h(Text, { text: 'B' })], [computed(() => score() >= 60), () => h(Text, { text: 'C' })], () => h(Text, { text: 'F' }))`);
211
+ });
212
+
213
+ test("should compile if/else with multiple elements", () => {
214
+ const input = `
215
+ @if (user.role === 'admin') {
216
+ <Text text="Admin Panel" />
217
+ <Text text="Settings" />
218
+ }
219
+ @else {
220
+ <Text text="Please log in" />
221
+ }
222
+ `;
223
+ const output = parser.parse(input);
224
+ expect(output).toBe(`cond(computed(() => user().role() === 'admin'), () => [h(Text, { text: 'Admin Panel' }), h(Text, { text: 'Settings' })], () => h(Text, { text: 'Please log in' }))`);
225
+ });
226
+
227
+ test("should compile nested if/else conditions", () => {
228
+ const input = `
229
+ @if (user) {
230
+ @if (user.isActive) {
231
+ <Text text="Active user" />
232
+ }
233
+ @else {
234
+ <Text text="Inactive user" />
235
+ }
236
+ }
237
+ @else {
238
+ <Text text="No user" />
239
+ }
240
+ `;
241
+ const output = parser.parse(input);
242
+ expect(output).toBe(`cond(user, () => cond(user.isActive, () => h(Text, { text: 'Active user' }), () => h(Text, { text: 'Inactive user' })), () => h(Text, { text: 'No user' }))`);
243
+ });
244
+
245
+ test("should compile if/else if with simple conditions", () => {
246
+ const input = `
247
+ @if (theme === 'dark') {
248
+ <Text text="Dark mode" />
249
+ }
250
+ @else if (theme === 'light') {
251
+ <Text text="Light mode" />
252
+ }
253
+ @else {
254
+ <Text text="Auto mode" />
255
+ }
256
+ `;
257
+ const output = parser.parse(input);
258
+ expect(output).toBe(`cond(computed(() => theme() === 'dark'), () => h(Text, { text: 'Dark mode' }), [computed(() => theme() === 'light'), () => h(Text, { text: 'Light mode' })], () => h(Text, { text: 'Auto mode' }))`);
259
+ });
260
+
261
+ test("should compile if/else with function conditions", () => {
262
+ const input = `
263
+ @if (isVisible()) {
264
+ <Sprite />
265
+ }
266
+ @else {
267
+ <Text text="Hidden" />
268
+ }
269
+ `;
270
+ const output = parser.parse(input);
271
+ expect(output).toBe(`cond(isVisible(), () => h(Sprite), () => h(Text, { text: 'Hidden' }))`);
272
+ });
273
+
274
+ test("should compile if/else if with object property conditions", () => {
275
+ const input = `
276
+ @if (sprite.visible) {
277
+ <Sprite />
278
+ }
279
+ @else if (sprite.loading) {
280
+ <Text text="Loading..." />
281
+ }
282
+ @else {
283
+ <Text text="Not available" />
284
+ }
285
+ `;
286
+ const output = parser.parse(input);
287
+ expect(output).toBe(`cond(sprite.visible, () => h(Sprite), [sprite.loading, () => h(Text, { text: 'Loading...' })], () => h(Text, { text: 'Not available' }))`);
288
+ });
135
289
  // test("should compile component with templating string", () => {
136
290
  // const input = `<Canvas width={\`direction: \${direction}\`} />`;
137
291
  // const output = parser.parse(input);
@@ -171,6 +325,24 @@ describe("Compiler", () => {
171
325
  expect(output).toBe(`h(Canvas, { width: computed(() => deep().value()) })`);
172
326
  });
173
327
 
328
+ test('should compile component with deep object attribute', () => {
329
+ const input = `<Button
330
+ style={{
331
+ backgroundColor: {
332
+ normal: "#6c757d",
333
+ hover: "#5a6268",
334
+ pressed: "#545b62"
335
+ },
336
+ text: {
337
+ fontSize: 16,
338
+ color: "#ffffff"
339
+ }
340
+ }}
341
+ />`;
342
+ const output = parser.parse(input);
343
+ expect(output).toBe(`h(Button, { style: { backgroundColor: { normal: "#6c757d", hover: "#5a6268", pressed: "#545b62" }, text: { fontSize: 16, color: "#ffffff" } } })`);
344
+ });
345
+
174
346
  test("should compile component with deep object attribute but not transform to signal", () => {
175
347
  const input = `<Canvas width={@deep.value} />`;
176
348
  const output = parser.parse(input);
@@ -576,6 +748,43 @@ describe("Condition", () => {
576
748
  `cond(sprite.visible, () => [h(Sprite), cond(deep, () => h(Sprite)), h(Sprite)])`
577
749
  );
578
750
  });
751
+
752
+ test("should compile if/else within canvas", () => {
753
+ const input = `
754
+ <Canvas>
755
+ @if (showSprite) {
756
+ <Sprite />
757
+ }
758
+ @else {
759
+ <Text text="No sprite" />
760
+ }
761
+ </Canvas>
762
+ `;
763
+ const output = parser.parse(input);
764
+ expect(output).toBe(
765
+ `h(Canvas, null, cond(showSprite, () => h(Sprite), () => h(Text, { text: 'No sprite' })))`
766
+ );
767
+ });
768
+
769
+ test("should compile if/else if/else within loop", () => {
770
+ const input = `
771
+ @for (item of items) {
772
+ @if (item.type === 'sprite') {
773
+ <Sprite />
774
+ }
775
+ @else if (item.type === 'text') {
776
+ <Text />
777
+ }
778
+ @else {
779
+ <Container />
780
+ }
781
+ }
782
+ `;
783
+ const output = parser.parse(input);
784
+ expect(output.replace(/\s+/g, "")).toBe(
785
+ `loop(items,item=>cond(computed(()=>item().type()==='sprite'),()=>h(Sprite),[computed(()=>item().type()==='text'),()=>h(Text)],()=>h(Container)))`.replace(/\s+/g, "")
786
+ );
787
+ });
579
788
  });
580
789
 
581
790
  describe("Condition in Loops", () => {