Skip to content

Using CanvasEngine Without the Compiler

CanvasEngine provides a powerful template syntax that gets compiled to JavaScript functions. However, you can also use CanvasEngine directly without the compiler by using the core functions: h, loop, and cond from the canvasengine package.

This approach gives you more control and can be useful for:

  • Dynamic template generation
  • Integration with existing build systems
  • Learning how the compiler works under the hood
  • Performance-critical applications where you want to avoid compilation overhead

Core Functions

h(component, props?, children?)

Creates a component instance. This is the equivalent of JSX elements.

loop(iterable, callback)

Renders a list of items. This is the equivalent of @for loops.

cond(condition, callback)

Conditionally renders content. This is the equivalent of @if statements.

Components

Basic Component

Instead of writing:

html
<Canvas />

Use:

javascript
h('Canvas')

Component with Properties

Instead of writing:

html
<Canvas width="800" height="600" />

Use:

javascript
h('Canvas', { width: '800', height: '600' })

Dynamic Properties

Instead of writing:

html
<Canvas width={screenWidth} height={screenHeight} />

Use:

javascript
h('Canvas', { width: screenWidth, height: screenHeight })

Computed Properties

For reactive expressions, instead of writing:

html
<Canvas width={x * 2} />

Use:

javascript
h('Canvas', { width: computed(() => x() * 2) })

Complex Computed Properties

For multiple reactive variables:

html
<Canvas width={x * 2 * y} />

Use:

javascript
h('Canvas', { width: computed(() => x() * 2 * y()) })

Object Properties

Instead of writing:

html
<Sprite sheet={{
    definition,
    playing: "stand",
    params: {
        direction: "right"
    },
    onFinish
}} />

Use:

javascript
h('Sprite', { 
  sheet: { 
    definition, 
    playing: "stand", 
    params: { 
      direction: "right" 
    }, 
    onFinish 
  } 
})

Array Properties

Instead of writing:

html
<Canvas positions={[x, 20]} />

Use:

javascript
h('Canvas', { positions: computed(() => [x(), 20]) })

Event Handlers

Instead of writing:

html
<Sprite click={handleClick} />

Use:

javascript
h('Sprite', { click: handleClick })

Inline Event Handlers

Instead of writing:

html
<Sprite click={() => console.log('clicked')} />

Use:

javascript
h('Sprite', { click: () => console.log('clicked') })

Spread Operator

Instead of writing:

html
<Canvas ...props />

Use:

javascript
h('Canvas', props)

Component as Property

Instead of writing:

html
<Canvas child={<Sprite />} />

Use:

javascript
h('Canvas', { child: h('Sprite') })

Function Returning Component

Instead of writing:

html
<Canvas child={() => <Sprite />} />

Use:

javascript
h('Canvas', { child: () => h('Sprite') })

Children

Single Child

Instead of writing:

html
<Canvas>
    <Sprite />
</Canvas>

Use:

javascript
h('Canvas', null, h('Sprite'))

Multiple Children

Instead of writing:

html
<Canvas>
    <Sprite />
    <Text />
</Canvas>

Use:

javascript
h('Canvas', null, [
    h('Sprite'),
    h('Text')
])

Loops

Basic Loop

Instead of writing:

html
@for (sprite of sprites) {
    <Sprite />
}

Use:

javascript
loop(sprites, sprite => h('Sprite'))

Loop with Properties

Instead of writing:

html
@for (sprite of sprites) {
    <Sprite x={sprite.x} y={sprite.y} />
}

Use:

javascript
loop(sprites, sprite => h('Sprite', { x: sprite.x, y: sprite.y }))

Loop with Index

Instead of writing:

html
@for ((sprite, index) of sprites) {
    <Sprite key={index} />
}

Use:

javascript
loop(sprites, (sprite, index) => h('Sprite', { key: index }))

Loop with Tracking

Instead of writing:

html
@for (sprite of sprites; track sprite.id) {
    <Sprite x={sprite.x} />
}

Use:

javascript
loop(
    sprites,
    sprite => h('Sprite', { x: sprite.x }),
    { track: sprite => sprite.id }
)

Tracking preserves existing children when the array reference changes but item identities stay the same. Without track, replacing the array recreates the loop children.

Loop with Object Property

Instead of writing:

html
@for (sprite of sprites.items) {
    <Sprite />
}

Use:

javascript
loop(sprites.items, sprite => h('Sprite'))

Loop with Function Call

Instead of writing:

html
@for (sprite of getSprites()) {
    <Sprite />
}

Use:

javascript
loop(getSprites(), sprite => h('Sprite'))

Nested Loops

Instead of writing:

html
@for (sprite of sprites) {
    @for (other of others) {
        <Sprite />
    }
}

Use:

javascript
loop(sprites, sprite => 
    loop(others, other => h('Sprite'))
)

Loop in Component

Instead of writing:

html
<Canvas>
    @for (sprite of sprites) {
        <Sprite />
    }
</Canvas>

Use:

javascript
h('Canvas', null, loop(sprites, sprite => h('Sprite')))

Conditions

Basic Condition

Instead of writing:

html
@if (sprite) {
    <Sprite />
}

Use:

javascript
cond(sprite, () => h('Sprite'))

Property-based Condition

Instead of writing:

html
@if (sprite.visible) {
    <Sprite />
}

Use:

javascript
cond(sprite.visible, () => h('Sprite'))

Function-based Condition

Instead of writing:

html
@if (isVisible()) {
    <Sprite />
}

Use:

javascript
cond(computed(() => isVisible()), () => h('Sprite'))

Complex Conditions

Instead of writing:

html
@if (!sprite && other) {
    <Sprite />
}

Use:

javascript
cond(computed(() => !sprite() && other()), () => h('Sprite'))

Multiple Conditions

Instead of writing:

html
@if (sprite) {
    <Sprite />
}
@if (other) {
    <Text />
}

Use:

javascript
[
    cond(sprite, () => h('Sprite')),
    cond(other, () => h('Text'))
]

Nested Conditions

Instead of writing:

html
@if (sprite.visible) {
    @if (deep) {
        <Sprite />
    }
}

Use:

javascript
cond(sprite.visible, () => 
    cond(deep, () => h('Sprite'))
)

Condition with Multiple Elements

Instead of writing:

html
@if (sprite.visible) {
    <Sprite />
    <Text />
}

Use:

javascript
cond(sprite.visible, () => [
    h('Sprite'),
    h('Text')
])

Combining Loops and Conditions

Condition in Loop

Instead of writing:

html
<Canvas>
    @for (sprite of sprites) {
        @if (sprite.visible) {
            <Sprite />
        }
    }
</Canvas>

Use:

javascript
h('Canvas', null, 
    loop(sprites, sprite => 
        cond(sprite.visible, () => h('Sprite'))
    )
)

Multiple Loops

Instead of writing:

html
<Canvas>
    @for (sprite of sprites) {
        <Sprite />
    }
    @for (other of others) {
        <Text />
    }
</Canvas>

Use:

javascript
h('Canvas', null, [
    loop(sprites, sprite => h('Sprite')),
    loop(others, other => h('Text'))
])

Complete Example

Here's a complete example showing how to build a complex component without the compiler:

javascript
import { h, loop, cond, computed } from 'canvasengine';

function GameScene({ sprites, enemies, showUI }) {
    return h('Canvas', { width: 800, height: 600 }, [
        // Background
        h('Sprite', { texture: 'background' }),
        
        // Player sprites
        loop(sprites, sprite => 
            cond(sprite.visible, () => 
                h('Sprite', {
                    x: sprite.x,
                    y: sprite.y,
                    texture: sprite.texture,
                    click: () => sprite.onClick()
                })
            )
        ),
        
        // Enemies
        loop(enemies, (enemy, index) => 
            h('Sprite', {
                key: index,
                x: computed(() => enemy.x() + 10),
                y: computed(() => enemy.y() + 10),
                texture: enemy.texture,
                tint: enemy.isHit ? 0xff0000 : 0xffffff
            })
        ),
        
        // UI overlay
        cond(showUI, () => 
            h('Container', null, [
                h('Text', { text: 'Score: 100', x: 10, y: 10 }),
                h('Text', { text: 'Lives: 3', x: 10, y: 30 })
            ])
        )
    ]);
}

This approach gives you the full power of CanvasEngine while maintaining complete control over your component structure and logic.