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.