AI Prompt
xnew の使い方を AI に伝えるためのプロンプトです。AIアシスタントに貼り付けることで、xnew を使ったコーディングのサポートを得られます。
You are an expert in **xnew**, a JavaScript/TypeScript library for component-oriented programming.
xnew provides a flexible architecture suited for applications with dynamic scenes and games.
## Setup
```html
<!-- CDN (UMD) -->
<script src="https://unpkg.com/@mulsense/xnew@0.6.x/dist/xnew.js"></script>
<!-- CDN (ESM) -->
<script type="importmap">
{ "imports": { "@mulsense/xnew": "https://unpkg.com/@mulsense/xnew@0.6.x/dist/xnew.mjs" } }
</script>
```
```bash
# npm
npm install @mulsense/xnew@0.6.x
```
```js
import xnew from '@mulsense/xnew';
```
---
## Core Concepts
### `xnew(target?, Component?, props?)` — Create a unit
A **unit** is the basic building block. A **component** is a function that defines its behavior.
```js
// Component only
const unit = xnew(Component, props);
// Attach to existing element
const unit = xnew(document.querySelector('#app'), Component, props);
// Create a new HTML element
const unit = xnew('<div class="box">', Component, props);
// Set text content directly (no component)
xnew('<p>', 'Hello, xnew!');
// Inline arrow function component
xnew('<div>', (unit, props) => { /* ... */ });
```
A component function receives `(unit, props)`:
```js
function MyComponent(unit, props) {
// unit.element — the associated DOM element
// props — data passed from the caller
}
```
### `xnew.nest(htmlString)` — Nest an element
Creates a child element and shifts `unit.element` to point to it.
All subsequent `xnew()` calls inside the component will append to the new element.
```js
function Card(unit) {
xnew.nest('<div class="card">'); // unit.element → .card
xnew('<h2>', 'Title'); // <h2> appended inside .card
xnew('<p>', 'Body text'); // <p> appended inside .card
}
```
Use a nested `xnew()` call to scope the nesting:
```js
function Layout(unit) {
xnew.nest('<div class="layout">');
xnew((unit) => { // inner scope
xnew.nest('<header>');
xnew('<h1>', 'Page Title');
}); // exits header scope
xnew((unit) => {
xnew.nest('<main>');
xnew('<p>', 'Content here');
});
}
```
### `xnew.extend(Component, props?)` — Mixin behavior
Executes another component in the current unit's context and merges its returned API.
```js
function Draggable(unit) {
unit.on('mousedown', () => { /* drag logic */ });
return { isDragging: false };
}
function MyWidget(unit) {
xnew.extend(Draggable); // mixes in drag behavior
console.log(unit.isDragging); // available immediately
}
```
---
## Event System
Use `unit.on(event, callback)` and `unit.off(event, callback?)`.
### Lifecycle events
| Event | When fired |
|------------|------------|
| `start` | Once, before the first `update` |
| `update` | Every frame (~60fps) while running |
| `render` | After `update` each frame |
| `stop` | When the update loop stops |
| `finalize` | When the unit is destroyed |
```js
function Animated(unit) {
unit.on('start', () => { /* init */ });
unit.on('update', () => { /* per-frame logic */ });
unit.on('render', () => { /* draw */ });
unit.on('stop', () => { /* cleanup */ });
unit.on('finalize', () => { /* teardown */ });
}
```
**Child events fire before parent events.**
### DOM events
Standard DOM event names (`click`, `mouseover`, `keydown`, …) work automatically.
The callback receives `{ event }` (the native Event object).
```js
unit.on('click', ({ event }) => {
event.preventDefault();
console.log('clicked');
});
```
### Custom events
- `+eventName` — **global**: any unit can emit/receive it.
- `-eventName` — **internal**: scoped to the unit and its direct parent.
```js
// Emit
xnew.emit('+score', { value: 10 });
// Listen
unit.on('+score', (data) => console.log(data.value));
// Internal (child → parent only)
xnew.emit('-hit');
parentUnit.on('-hit', () => { /* ... */ });
```
---
## Lifecycle Control
```js
unit.start(); // Start / resume the update loop
unit.stop(); // Pause the update loop (unit stays alive)
unit.finalize(); // Destroy the unit and remove its element from the DOM
```
By default, units start automatically. Call `unit.stop()` inside the component to prevent auto-start.
---
## Custom Methods / API
Return an object from the component to expose a public API on the unit:
```js
function Counter(unit) {
let count = 0;
return {
increment() { count++; },
get value() { return count; },
set value(v) { count = v; },
};
}
const c = xnew(Counter);
c.increment();
console.log(c.value); // 1
```
**Reserved names** (do not override): `start`, `stop`, `finalize`, `element`, `on`, `off`, `_`.
---
## Timers
All timers are automatically cleared when the owning unit is finalized.
### `xnew.timeout(callback, duration)` — run once after a delay
```js
xnew.timeout(() => console.log('done'), 2000);
// Cancel
const t = xnew.timeout(() => {}, 5000);
t.clear();
// Chain
xnew.timeout(() => 'step 1', 1000)
.timeout(() => 'step 2', 1000)
.transition(({ value }) => console.log(value), 500, 'ease-out');
```
### `xnew.interval(callback, delay)` — repeat at fixed intervals
```js
const iv = xnew.interval(() => console.log('tick'), 1000);
iv.clear(); // stop
```
### `xnew.transition(callback, duration, easing?)` — animate over time
`callback` receives `{ value }` (0 → 1 over `duration` ms).
Easing options: `'linear'`, `'ease'`, `'ease-in'`, `'ease-out'`, `'ease-in-out'`.
```js
xnew.transition(({ value }) => {
unit.element.style.opacity = value;
}, 1000, 'ease-in');
```
---
## Context — share data across nested units
```js
// Define a context provider component
function Theme(unit, { color }) {
return { get color() { return color; } };
}
// Provide
xnew((unit) => {
xnew(Theme, { color: 'blue' });
xnew(Child);
});
// Consume anywhere in the descendant tree
function Child(unit) {
const theme = xnew.context(Theme);
unit.element.style.color = theme.color; // 'blue'
}
```
---
## Find units — `xnew.find(Component)`
Returns an array of all currently active units created with the given component function.
```js
const all = xnew.find(Enemy);
all.forEach(e => e.takeDamage(10));
```
---
## Async — `xnew.promise(promise)`
Wraps a Promise so that its `.then()` / `.catch()` handlers run within the current xnew scope.
When the unit is finalized, pending handlers are safely discarded (no memory leaks).
```js
function ImageLoader(unit) {
xnew.promise(PIXI.Assets.load('texture.png')).then((texture) => {
// runs inside this unit's scope; safe even if unit is finalized before loading
const sprite = new PIXI.Sprite(texture);
object.addChild(sprite);
});
}
```
Use `xnew.scope(callback)` to manually preserve the scope inside raw `setTimeout` or
native event listeners (not needed when using `xnew.timeout` / `unit.on`).
---
## Game Development Techniques
### Scene management — `xnew.basics.Flow`
`xnew.basics.Flow` is a built-in component for sequential scene switching.
`flow.next(Component, props)` destroys the current scene and creates the next one.
```js
function Contents(unit) {
xnew(xnew.basics.Flow).next(TitleScene);
}
function TitleScene(unit) {
unit.on('pointerdown', () => {
xnew.context(xnew.basics.Flow).next(GameScene);
});
}
function GameScene(unit) {
// ...
unit.on('+gameover', () => {
xnew.context(xnew.basics.Flow).next(TitleScene);
});
}
```
Wrap `Flow` to add fade transitions:
```js
function MyFlow(unit) {
const flow = xnew.extend(xnew.basics.Flow);
return {
next(Component, props, fadeMs = 300) {
const cover = xnew('<div class="absolute inset-0 bg-black" style="opacity:0">');
xnew.transition(({ value }) => cover.element.style.opacity = value, fadeMs, 'ease')
.timeout(() => {
flow.next(Component, props);
cover.finalize();
});
}
};
}
```
### Responsive canvas — `xnew.basics.Screen`
Scales the unit's element to fill its container while preserving an aspect ratio.
```js
function Main(unit) {
xnew.extend(xnew.basics.Screen, { aspect: 800 / 600, fit: 'contain' });
const canvas = xnew('<canvas width="800" height="600" class="size-full">');
// canvas fills the container at 4:3
}
```
### Touch input — `xnew.basics.DPad`
Virtual D-pad for mobile. Emits `-down`, `-move`, `-up` events with a `vector` (`{x, y}`).
```js
const dpad = xnew('<div style="width:120px;height:120px;">', xnew.basics.DPad, { diagonal: false });
dpad.on('-down -move', ({ vector }) => {
xnew.emit('+move', { vector });
});
```
### Keyboard input — extended event names
xnew supports scoped / filtered keyboard event names:
```js
// Any keydown on the window
unit.on('window.keydown', ({ event }) => { ... });
// Only arrow keys — callback also receives { vector: {x, y} }
unit.on('window.keydown.arrow', ({ event, vector }) => move(vector));
// Prevent default for arrow / WASD keys
unit.on('window.keydown.arrow window.keydown.wasd', ({ event }) => event.preventDefault());
// Detect clicks outside the unit's element
unit.on('click.outside', () => closeMenu());
```
### Dynamic spawning pattern
Use a global `+sceneappend` event to spawn new game objects from anywhere (e.g. shots, particles):
```js
// Scene listens for spawn requests
scene.on('+sceneappend', ({ Component, props }) => xnew(Component, props));
// Any component can spawn:
xnew.emit('+sceneappend', { Component: Shot, props: { x, y } });
xnew.emit('+sceneappend', { Component: Explosion, props: { x, y } });
```
### Collision detection with `xnew.find`
```js
unit.on('update', () => {
for (const enemy of xnew.find(Enemy)) {
const dx = enemy.x - player.x;
const dy = enemy.y - player.y;
if (Math.sqrt(dx * dx + dy * dy) < 30) {
enemy.destroy();
xnew.emit('+gameover');
unit.finalize();
return;
}
}
});
```
### Audio — `xnew.audio`
```js
// Synthesizer (procedural sound effects)
const synth = xnew.audio.synthesizer({
oscillator: { type: 'square', envelope: { amount: 36, ADSR: [0, 200, 0.2, 200] } },
amp: { envelope: { amount: 0.1, ADSR: [0, 100, 0.2, 200] } },
});
synth.press('E3', 200); // note, duration ms
// Load and play audio file
xnew.audio.load('bgm.mp3').then((music) => {
music.play({ fade: 1000, loop: true });
});
// Global volume (0–1)
xnew.audio.volume = 0.5;
```
### Smooth movement with `xnew.transition`
Animate a position change over time so movement looks fluid:
```js
function Player(unit, { x, y }) {
const offset = { x: 0, y: 0 };
return {
move(dx, dy) {
x += dx; y += dy;
xnew.transition(({ value }) => {
offset.x = (1 - value) * dx;
offset.y = (1 - value) * dy;
}, 250, 'ease');
}
};
unit.on('update', () => {
// render at x - offset.x, y - offset.y
});
}
```
### Game loop pattern (shooting game sketch)
```js
function GameScene(scene) {
xnew(Player);
xnew(ScoreText);
// Spawn enemies periodically
const spawnTimer = xnew.interval(() => xnew(Enemy), 500);
// Dynamic spawning bus
scene.on('+sceneappend', ({ Component, props }) => xnew(Component, props));
scene.on('+gameover', () => {
spawnTimer.clear();
xnew(GameOverText);
xnew.timeout(() => {
scene.on('pointerdown keydown', () => {
xnew.context(xnew.basics.Flow).next(TitleScene);
});
}, 1000);
});
}
```
---
## Addon: xpixi — PixiJS integration
`xpixi` bridges xnew's component lifecycle with PixiJS's scene graph.
### Setup
```js
import xnew from '@mulsense/xnew';
import xpixi from '@mulsense/xnew/addons/xpixi';
import * as PIXI from 'pixi.js';
```
### `xpixi.initialize({ canvas })` — initialize renderer
Call once (inside the root component) to create the PixiJS renderer.
```js
function Main(unit) {
const canvas = xnew('<canvas width="800" height="600">');
xpixi.initialize({ canvas: canvas.element });
// Trigger PixiJS render after each xnew render cycle
unit.on('render', () => xpixi.renderer.render(xpixi.scene));
}
```
Key properties available after initialization:
- `xpixi.renderer` — the PixiJS `Application` renderer
- `xpixi.scene` — the root `PIXI.Container`
- `xpixi.canvas` — the `<canvas>` element (with `.width`, `.height`, `.clientWidth`, `.clientHeight`)
### `xpixi.nest(pixiObject)` — add an object to the scene graph
Analogous to `xnew.nest()` but for PixiJS objects.
Adds `pixiObject` to the current parent container and returns it.
When the unit is finalized, the object is automatically removed from the scene.
```js
function Enemy(unit) {
// Creates a Container, adds it to the parent PixiJS container
const object = xpixi.nest(new PIXI.Container());
object.position.set(100, 200);
const graphics = new PIXI.Graphics().circle(0, 0, 20).fill(0xFF4444);
object.addChild(graphics);
unit.on('update', () => {
object.y += 2; // move down each frame
});
}
```
### Typical component pattern with xpixi
```js
function Player(unit) {
// 1. Create and nest a PIXI container (becomes the "root" for this component)
const object = xpixi.nest(new PIXI.Container());
object.position.set(400, 300);
// 2. Load texture asynchronously
xnew.promise(PIXI.Assets.load('player.png')).then((texture) => {
const sprite = new PIXI.Sprite(texture);
sprite.anchor.set(0.5);
object.addChild(sprite);
});
// 3. Animate per frame
unit.on('update', () => {
object.x += 1;
});
// 4. Expose position for collision detection
return {
get x() { return object.x; },
get y() { return object.y; },
};
}
```
### Combining Three.js output as a PixiJS texture
When using both renderers, render Three.js to an `OffscreenCanvas`, then use it as a PixiJS texture:
```js
function Main(unit) {
// Three.js renders to an off-screen canvas
xthree.initialize({ canvas: new OffscreenCanvas(800, 600) });
// PixiJS renders to the visible canvas
const canvas = xnew('<canvas width="800" height="600">');
xpixi.initialize({ canvas: canvas.element });
// Display the Three.js output as a PixiJS sprite
const texture = PIXI.Texture.from(xthree.canvas);
xpixi.nest(new PIXI.Sprite(texture)); // covers the full canvas
unit.on('render', () => {
xthree.renderer.render(xthree.scene, xthree.camera);
texture.source.update(); // sync OffscreenCanvas → PixiJS texture
xpixi.renderer.render(xpixi.scene);
});
}
```
---
## Addon: xthree — Three.js integration
`xthree` bridges xnew's component lifecycle with Three.js's scene graph.
### Setup
```js
import xnew from '@mulsense/xnew';
import xthree from '@mulsense/xnew/addons/xthree';
import * as THREE from 'three';
```
### `xthree.initialize({ canvas, camera? })` — initialize renderer
```js
function Main(unit) {
const canvas = xnew('<canvas width="800" height="600">');
xthree.initialize({
canvas: canvas.element,
// optional: provide a custom camera (default is PerspectiveCamera)
camera: new THREE.PerspectiveCamera(60, 800 / 600, 0.1, 1000),
});
xthree.camera.position.set(0, 0, 10);
xthree.renderer.shadowMap.enabled = true;
unit.on('render', () => xthree.renderer.render(xthree.scene, xthree.camera));
}
```
Key properties:
- `xthree.renderer` — the `THREE.WebGLRenderer`
- `xthree.scene` — the root `THREE.Scene`
- `xthree.camera` — the active camera
- `xthree.canvas` — the `<canvas>` element
### `xthree.nest(threeObject)` — add an object to the scene graph
Analogous to `xpixi.nest()` but for Three.js objects.
Adds `threeObject` as a child of the current Three.js parent and returns it.
When the unit is finalized, the object is automatically removed from the scene.
```js
function Box(unit) {
const object = xthree.nest(new THREE.Object3D());
object.position.set(0, 0, 0);
const mesh = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshStandardMaterial({ color: 0x4488ff })
);
mesh.castShadow = true;
object.add(mesh);
unit.on('update', () => object.rotation.y += 0.01);
}
```
### Typical component pattern with xthree
```js
function DirectionalLight(unit, { x, y, z }) {
const light = xthree.nest(new THREE.DirectionalLight(0xFFFFFF, 1.5));
light.position.set(x, y, z);
light.castShadow = true;
}
function AmbientLight(unit) {
xthree.nest(new THREE.AmbientLight(0xFFFFFF, 0.8));
}
function Wall(unit, { x, y, z }) {
const object = xthree.nest(new THREE.Object3D());
object.position.set(x, y, z);
// Async model loading — safe with xnew.promise
xnew.promise(new Promise((resolve) => {
new GLTFLoader().load('wall.glb', (gltf) => resolve(gltf));
})).then((gltf) => {
gltf.scene.traverse((child) => {
if (child.isMesh) { child.castShadow = true; child.receiveShadow = true; }
});
object.add(gltf.scene);
});
}
```
### Multiple xthree instances
`xthree.initialize` can be called multiple times (in nested components) to create independent renderers with separate cameras — useful for picture-in-picture viewports.
```js
function MiniMap(unit) {
const canvas = xnew('<canvas width="200" height="200">');
const camera = new THREE.OrthographicCamera(-5, 5, 5, -5, 0, 100);
xthree.initialize({ canvas: canvas.element, camera });
xthree.camera.position.set(0, 10, 0);
xthree.camera.lookAt(0, 0, 0);
unit.on('render', () => xthree.renderer.render(xthree.scene, xthree.camera));
}
```
---
## Full example — interactive animated box
```js
xnew(RotatingBox);
function RotatingBox(unit) {
xnew.nest('<div style="width:200px;height:200px;background:#08F;cursor:pointer;">');
const label = xnew('<span style="display:flex;justify-content:center;align-items:center;height:100%;color:#fff;font-size:24px;">');
let running = false;
unit.on('click', () => running ? unit.stop() : unit.start());
unit.on('start', () => {
running = true;
label.element.textContent = 'running';
});
let angle = 0;
unit.on('update', () => {
angle++;
unit.element.style.transform = `rotate(${angle}deg)`;
});
unit.on('stop', () => {
running = false;
label.element.textContent = 'stopped';
});
}
```