@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 +2 -1
- package/dist/index.js.map +1 -1
- package/grammar.pegjs +11 -11
- package/index.ts +2 -1
- package/package.json +1 -1
- package/tests/compiler.spec.ts +29 -19
package/dist/index.js
CHANGED
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', '
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
285
|
+
return `h(DOMElement, { element: "${tagName}" }, ${children})`;
|
|
286
286
|
} else {
|
|
287
|
-
return `h(
|
|
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(
|
|
332
|
+
return `h(DOMElement, { ${parts.join(', ')} }, ${children})`;
|
|
333
333
|
} else {
|
|
334
|
-
return `h(
|
|
334
|
+
return `h(DOMElement, { ${parts.join(', ')} })`;
|
|
335
335
|
}
|
|
336
336
|
}
|
|
337
337
|
|
package/index.ts
CHANGED
package/package.json
CHANGED
package/tests/compiler.spec.ts
CHANGED
|
@@ -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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
});
|