pixifact 0.2.2 → 0.4.0

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.
Files changed (48) hide show
  1. package/README.md +14 -0
  2. package/dist/compiler/compileScenes.d.ts +1 -0
  3. package/dist/compiler/compileScenes.js +8 -9
  4. package/dist/compiler/index.d.ts +0 -4
  5. package/dist/compiler/index.js +0 -4
  6. package/dist/compiler/pixiNodeSchema.d.ts +2 -2
  7. package/dist/compiler/pixiNodeSchema.js +10 -0
  8. package/dist/compiler/sceneValidation.js +1 -1
  9. package/dist/compiler/scriptInterfaceExtractor.js +6 -0
  10. package/dist/compiler/spec.d.ts +2 -2
  11. package/dist/compiler/templateParser.js +1 -0
  12. package/dist/compiler/typescriptCompiler.js +54 -45
  13. package/dist/compiler-node/vite.d.ts +2 -1
  14. package/dist/compiler-node/vite.js +29 -2
  15. package/dist/platform/wechat/adapter.d.ts +316 -0
  16. package/dist/platform/wechat/adapter.js +26 -0
  17. package/dist/platform/wechat/application.d.ts +32 -0
  18. package/dist/platform/wechat/application.js +79 -0
  19. package/dist/platform/wechat/fetch.d.ts +1 -0
  20. package/dist/platform/wechat/fetch.js +145 -0
  21. package/dist/platform/wechat/index.d.ts +4 -0
  22. package/dist/platform/wechat/index.js +3 -0
  23. package/dist/platform/wechat/input.d.ts +12 -0
  24. package/dist/platform/wechat/input.js +93 -0
  25. package/dist/platform/wechat/lifecycle.d.ts +14 -0
  26. package/dist/platform/wechat/lifecycle.js +42 -0
  27. package/dist/platform/wechat/runtime.d.ts +49 -0
  28. package/dist/platform/wechat/runtime.js +189 -0
  29. package/dist/platform/wechat/subpackage.d.ts +1 -0
  30. package/dist/platform/wechat/subpackage.js +27 -0
  31. package/dist/platform/wechat/types.d.ts +105 -0
  32. package/dist/platform/wechat/types.js +3 -0
  33. package/dist/project/index.d.ts +23 -0
  34. package/dist/project/index.js +69 -0
  35. package/dist/{compiler → scene}/decorators.d.ts +1 -1
  36. package/dist/scene/index.d.ts +5 -0
  37. package/dist/scene/index.js +5 -0
  38. package/dist/scene/sceneAssetRuntime.d.ts +11 -0
  39. package/dist/scene/sceneAssetRuntime.js +43 -0
  40. package/dist/{compiler → scene}/sceneRuntime.d.ts +7 -0
  41. package/dist/scene/sceneRuntime.js +85 -0
  42. package/package.json +11 -1
  43. package/dist/compiler/sceneRuntime.js +0 -22
  44. /package/dist/{compiler → scene}/decorators.js +0 -0
  45. /package/dist/{compiler → scene}/eventRuntime.d.ts +0 -0
  46. /package/dist/{compiler → scene}/eventRuntime.js +0 -0
  47. /package/dist/{compiler → scene}/slotRuntime.d.ts +0 -0
  48. /package/dist/{compiler → scene}/slotRuntime.js +0 -0
package/README.md CHANGED
@@ -15,8 +15,12 @@ bun add pixifact pixi.js
15
15
  ```ts
16
16
  import { parseSceneTemplate } from 'pixifact/compiler';
17
17
  import { Group } from 'pixifact/runtime';
18
+ import { prepareSceneClass, scene } from 'pixifact/scene';
19
+ import { createWechatPixiApplication } from 'pixifact/platform/wechat';
18
20
  ```
19
21
 
22
+ `pixifact/compiler` 只包含解析、校验和生成 API。游戏运行时使用 `pixifact/runtime`、`pixifact/scene` 和具体平台入口,不要从 compiler 入口导入 Scene decorator 或运行时加载 API。
23
+
20
24
  Compiler Scene 项目通常还会安装 CLI:
21
25
 
22
26
  ```bash
@@ -30,6 +34,16 @@ pixifact scene validate --all
30
34
  pixifact compile-scenes
31
35
  ```
32
36
 
37
+ 声明 `targets.wechat` 后,可校验和构建微信小游戏:
38
+
39
+ ```bash
40
+ pixifact validate --target wechat
41
+ pixifact build --target wechat
42
+ pixifact dev --target wechat
43
+ ```
44
+
45
+ 完整配置见仓库文档 [微信小游戏构建](https://github.com/hxg2050/pixifact/blob/main/docs/zh/wechat-minigame.md)。
46
+
33
47
  ## Scene 工作流
34
48
 
35
49
  Pixifact 把项目相对 `.scene` 文件作为 source of truth。外部 Agent 直接编辑 `.scene` 源文件,然后运行校验和编译。
@@ -3,6 +3,7 @@ export interface CompileScenesOptions {
3
3
  sourceRoots?: string[];
4
4
  generatedDir?: string;
5
5
  }
6
+ export declare const generatedSceneAssetsFileName = "scene-assets.generated.json";
6
7
  export declare class CompileSceneError extends Error {
7
8
  readonly scene: string;
8
9
  readonly source?: string | undefined;
@@ -9,6 +9,7 @@ import { findMissingScenePartReferences } from './scenePartValidation.js';
9
9
  import { parseSceneTemplate } from './templateParser.js';
10
10
  import { compileSceneTemplateToTs } from './typescriptCompiler.js';
11
11
  import { parsePixifactProjectConfig, pixifactProjectConfigFileName, } from '../project/index.js';
12
+ export const generatedSceneAssetsFileName = 'scene-assets.generated.json';
12
13
  export class CompileSceneError extends Error {
13
14
  scene;
14
15
  source;
@@ -65,6 +66,7 @@ export async function compileScenes(options) {
65
66
  }
66
67
  const projectRegistryImports = [];
67
68
  const builtinRegistryImports = [];
69
+ const projectAssets = new Set();
68
70
  for (const record of sceneRecords) {
69
71
  const { scenePath } = record;
70
72
  const template = templates.get(scenePath);
@@ -84,9 +86,12 @@ export async function compileScenes(options) {
84
86
  },
85
87
  sceneImports: sceneImportsFor(template, templates, projectRoot, generatedFileDir),
86
88
  sceneClassAliases: sceneClassAliasesFor(template),
89
+ sceneDependencies: [...collectSceneInstancePaths(template.children)].sort(),
87
90
  sceneInterfaces: sceneInterfacesFor(template, templates),
88
- textureImports: textureImportsFor(template, projectRoot, generatedFileDir),
89
91
  });
92
+ if (record.kind === 'project') {
93
+ collectTemplateTextures(template, projectAssets);
94
+ }
90
95
  await mkdir(path.dirname(generatedFile), { recursive: true });
91
96
  await writeFile(generatedFile, code);
92
97
  if (record.kind === 'project') {
@@ -100,6 +105,7 @@ export async function compileScenes(options) {
100
105
  ...builtinRegistryImports,
101
106
  ...projectRegistryImports,
102
107
  ].join('\n')}\n`);
108
+ await writeFile(path.join(generatedDir, generatedSceneAssetsFileName), `${JSON.stringify([...projectAssets].sort(), null, 2)}\n`);
103
109
  }
104
110
  async function readProjectConfig(projectRoot) {
105
111
  const configPath = path.join(projectRoot, pixifactProjectConfigFileName);
@@ -294,17 +300,10 @@ function importSourceFor(sourcePath, generatedFileDir) {
294
300
  const source = path.relative(generatedFileDir, sourcePath).replaceAll(path.sep, '/').replace(/\.ts$/, '');
295
301
  return source.startsWith('.') ? source : `./${source}`;
296
302
  }
297
- function textureImportsFor(template, projectRoot, generatedFileDir) {
298
- const textures = new Set();
303
+ function collectTemplateTextures(template, textures) {
299
304
  for (const child of template.children) {
300
305
  collectTextureReferences(child, textures);
301
306
  }
302
- return Object.fromEntries([...textures].map((texture) => {
303
- const normalized = texture.replace(/^\.\/+/, '').replace(/^\/+/, '');
304
- const assetPath = path.resolve(projectRoot, normalized);
305
- const source = path.relative(generatedFileDir, assetPath).replaceAll(path.sep, '/');
306
- return [texture, `${source.startsWith('.') ? source : `./${source}`}?url`];
307
- }));
308
307
  }
309
308
  function collectSceneInstancePaths(nodes, scenePaths = new Set()) {
310
309
  for (const node of nodes) {
@@ -1,15 +1,11 @@
1
1
  export * from './commands/index.js';
2
2
  export * from './builtinScenes.js';
3
- export * from './decorators.js';
4
- export * from './eventRuntime.js';
5
3
  export * from './pixiNodeSchema.js';
6
4
  export * from './spec.js';
7
5
  export * from './scriptInterfaceExtractor.js';
8
- export * from './sceneRuntime.js';
9
6
  export * from './sceneAssetPair.js';
10
7
  export * from './scenePartValidation.js';
11
8
  export * from './sceneValidation.js';
12
- export * from './slotRuntime.js';
13
9
  export * from './templateParser.js';
14
10
  export * from './templateSerializer.js';
15
11
  export * from './typescriptCompiler.js';
@@ -1,15 +1,11 @@
1
1
  export * from './commands/index.js';
2
2
  export * from './builtinScenes.js';
3
- export * from './decorators.js';
4
- export * from './eventRuntime.js';
5
3
  export * from './pixiNodeSchema.js';
6
4
  export * from './spec.js';
7
5
  export * from './scriptInterfaceExtractor.js';
8
- export * from './sceneRuntime.js';
9
6
  export * from './sceneAssetPair.js';
10
7
  export * from './scenePartValidation.js';
11
8
  export * from './sceneValidation.js';
12
- export * from './slotRuntime.js';
13
9
  export * from './templateParser.js';
14
10
  export * from './templateSerializer.js';
15
11
  export * from './typescriptCompiler.js';
@@ -1,5 +1,5 @@
1
1
  import type { SceneTemplatePrimitiveType, SceneTemplateValue } from './spec.js';
2
- export type PixiSceneNodeType = Extract<SceneTemplatePrimitiveType, 'Container' | 'Sprite' | 'NineSliceSprite' | 'TilingSprite' | 'Text' | 'BitmapText' | 'HTMLText' | 'Graphics' | 'Rect' | 'Image' | 'NineImage' | 'TileImage' | 'GridContainer' | 'HBoxContainer' | 'ScrollContainer' | 'VBoxContainer'>;
2
+ export type PixiSceneNodeType = Extract<SceneTemplatePrimitiveType, 'Group' | 'Container' | 'Sprite' | 'NineSliceSprite' | 'TilingSprite' | 'Text' | 'BitmapText' | 'HTMLText' | 'Graphics' | 'Rect' | 'Image' | 'NineImage' | 'TileImage' | 'GridContainer' | 'HBoxContainer' | 'ScrollContainer' | 'VBoxContainer'>;
3
3
  export type PixiSceneFieldType = 'string' | 'number' | 'boolean' | 'color' | 'enum';
4
4
  export type PixiScenePropGroup = 'transform' | 'display' | 'sprite' | 'nineSlice' | 'tiling' | 'text' | 'graphics' | 'stack' | 'props';
5
5
  export interface PixiSceneFieldSchema {
@@ -33,7 +33,7 @@ export declare const pixiSceneScrollProps: readonly ["direction", "scrollX", "sc
33
33
  export declare const pixiSceneTextStyleProps: readonly ["fontSize", "fontFamily", "fontWeight", "fill"];
34
34
  export declare const pixiSceneSpriteLikeProps: readonly ["texture", "anchorX", "anchorY", "tint", "texture", "fit", "anchorX", "anchorY", "tint", "texture", "leftWidth", "rightWidth", "topHeight", "bottomHeight", "anchorX", "anchorY", "tint", "texture", "tilePositionX", "tilePositionY", "tileScaleX", "tileScaleY", "tileRotation", "anchorX", "anchorY", "tint", "leftWidth", "rightWidth", "topHeight", "bottomHeight", "tilePositionX", "tilePositionY", "tileScaleX", "tileScaleY", "tileRotation"];
35
35
  export declare const pixiSceneKnownProps: readonly ["x", "y", "width", "height", "scaleX", "scaleY", "rotation", "pivotX", "pivotY", "skewX", "skewY", "left", "right", "top", "bottom", "horizontal", "vertical", "alpha", "visible", "zIndex", "eventMode", "cursor", "label", "texture", "anchorX", "anchorY", "tint", "texture", "fit", "anchorX", "anchorY", "tint", "texture", "leftWidth", "rightWidth", "topHeight", "bottomHeight", "anchorX", "anchorY", "tint", "texture", "tilePositionX", "tilePositionY", "tileScaleX", "tileScaleY", "tileRotation", "anchorX", "anchorY", "tint", "leftWidth", "rightWidth", "topHeight", "bottomHeight", "tilePositionX", "tilePositionY", "tileScaleX", "tileScaleY", "tileRotation", "text", "fontSize", "fontFamily", "fontWeight", "fill", "shape", "radius", "fill", "fillAlpha", "strokeColor", "strokeWidth", "strokeAlpha", "fillColor", "fillAlpha", "strokeColor", "strokeAlpha", "strokeWidth", "radius", "columns", "gap", "gapX", "gapY", "alignX", "alignY", "justify", "direction", "scrollX", "scrollY"];
36
- export declare const pixiSceneAddableNodeTypes: readonly ["GridContainer", "HBoxContainer", "ScrollContainer", "VBoxContainer", "Container", "Sprite", "NineSliceSprite", "TilingSprite", "Text", "BitmapText", "HTMLText", "Graphics", "Rect", "Image", "NineImage", "TileImage"];
36
+ export declare const pixiSceneAddableNodeTypes: readonly ["Group", "GridContainer", "HBoxContainer", "ScrollContainer", "VBoxContainer", "Container", "Sprite", "NineSliceSprite", "TilingSprite", "Text", "BitmapText", "HTMLText", "Graphics", "Rect", "Image", "NineImage", "TileImage"];
37
37
  export declare const pixiSceneNodePropGroupOrder: readonly ["stack", "sprite", "nineSlice", "tiling", "text", "graphics", "props"];
38
38
  export declare function pixiSceneNodeSchema(type: PixiSceneNodeType): PixiSceneNodeSchema;
39
39
  export declare function isPixiSceneNodeType(type: SceneTemplatePrimitiveType): type is PixiSceneNodeType;
@@ -155,6 +155,7 @@ export const pixiSceneKnownProps = [
155
155
  ...pixiSceneScrollProps,
156
156
  ];
157
157
  export const pixiSceneAddableNodeTypes = [
158
+ 'Group',
158
159
  'GridContainer',
159
160
  'HBoxContainer',
160
161
  'ScrollContainer',
@@ -243,6 +244,15 @@ const pixiSceneFieldSchemas = {
243
244
  scrollY: { key: 'scrollY', type: 'number' },
244
245
  };
245
246
  const pixiSceneNodeSchemas = {
247
+ Group: {
248
+ type: 'Group',
249
+ acceptsChildren: true,
250
+ defaults: {
251
+ width: 100,
252
+ height: 100,
253
+ },
254
+ groups: {},
255
+ },
246
256
  Container: {
247
257
  type: 'Container',
248
258
  acceptsChildren: true,
@@ -149,7 +149,7 @@ function validatePixiNode(node, path, existingAssets) {
149
149
  prop: 'children',
150
150
  expected: 'no child nodes',
151
151
  actual: `${node.children.length} ${node.children.length === 1 ? 'child node' : 'child nodes'}`,
152
- hint: `${node.type} is a leaf drawing node. Wrap it and sibling content in a Container or Group Scene.`,
152
+ hint: `${node.type} is a leaf drawing node. Wrap it and sibling content in a <Container> or <Group>.`,
153
153
  });
154
154
  }
155
155
  const knownProps = new Set([
@@ -28,6 +28,9 @@ function extractSceneScriptClasses(source, fileName, options) {
28
28
  }
29
29
  continue;
30
30
  }
31
+ if (isSceneClass && sceneConstructorHasParameters(statement)) {
32
+ throw new Error(`@scene class "${className}" must not declare constructor parameters. Use @prop() or an explicit method after construction.`);
33
+ }
31
34
  const props = {};
32
35
  const events = {};
33
36
  const slots = {};
@@ -258,6 +261,9 @@ function hasSceneDecorator(node) {
258
261
  }
259
262
  return true;
260
263
  }
264
+ function sceneConstructorHasParameters(node) {
265
+ return node.members.some((member) => ts.isConstructorDeclaration(member) && member.parameters.length > 0);
266
+ }
261
267
  function parentClassNameOf(node) {
262
268
  const heritage = node.heritageClauses?.find((item) => item.token === ts.SyntaxKind.ExtendsKeyword);
263
269
  const parent = heritage?.types[0]?.expression;
@@ -1,4 +1,4 @@
1
- export type SceneTemplatePrimitiveType = 'Container' | 'Sprite' | 'Text' | 'Graphics' | 'Rect' | 'Image' | 'NineImage' | 'TileImage' | 'BitmapText' | 'HTMLText' | 'Mesh' | 'NineSliceSprite' | 'TilingSprite' | 'DOMContainer' | 'GridContainer' | 'HBoxContainer' | 'ScrollContainer' | 'VBoxContainer';
1
+ export type SceneTemplatePrimitiveType = 'Group' | 'Container' | 'Sprite' | 'Text' | 'Graphics' | 'Rect' | 'Image' | 'NineImage' | 'TileImage' | 'BitmapText' | 'HTMLText' | 'Mesh' | 'NineSliceSprite' | 'TilingSprite' | 'DOMContainer' | 'GridContainer' | 'HBoxContainer' | 'ScrollContainer' | 'VBoxContainer';
2
2
  export type SceneTemplateScalarValue = string | number | boolean;
3
3
  export type SceneTemplateStructValue = Record<string, SceneTemplateScalarValue>;
4
4
  export type SceneTemplateValue = SceneTemplateScalarValue | SceneTemplateStructValue;
@@ -73,8 +73,8 @@ export interface CompileSceneTemplateOptions {
73
73
  scriptImport?: SceneTemplateScriptImport;
74
74
  sceneImports?: SceneTemplateScriptImport[];
75
75
  sceneClassAliases?: Record<string, string>;
76
+ sceneDependencies?: string[];
76
77
  sceneInterfaces?: Record<string, SceneTemplateInterface>;
77
- textureImports?: Record<string, string>;
78
78
  }
79
79
  export interface SceneScriptInterface {
80
80
  scene: string;
@@ -1,6 +1,7 @@
1
1
  import { pixiSceneFieldSchema } from './pixiNodeSchema.js';
2
2
  import { builtinSceneAssetId, isBuiltinSceneName } from './builtinScenes.js';
3
3
  const pixiTypes = new Set([
4
+ 'Group',
4
5
  'Container',
5
6
  'Sprite',
6
7
  'Text',
@@ -1,4 +1,5 @@
1
1
  import { pixiSceneDisplayProps, pixiSceneGraphicsProps, pixiSceneLayoutProps, pixiSceneRectProps, pixiSceneSpriteLikeProps, pixiSceneTextStyleProps, pixiSceneTransformProps, } from './pixiNodeSchema.js';
2
+ import { compilerSceneNodeLocator } from './commands/index.js';
2
3
  const transformProps = new Set(pixiSceneTransformProps);
3
4
  const layoutProps = new Set(pixiSceneLayoutProps);
4
5
  const pixiProps = new Set(pixiSceneDisplayProps);
@@ -6,7 +7,7 @@ const spriteProps = new Set(pixiSceneSpriteLikeProps);
6
7
  const graphicsProps = new Set(pixiSceneGraphicsProps);
7
8
  const rectProps = new Set(pixiSceneRectProps);
8
9
  const textStyleProps = new Set(pixiSceneTextStyleProps);
9
- const runtimeNodeTypes = new Set(['GridContainer', 'HBoxContainer', 'ScrollContainer', 'VBoxContainer', 'Rect', 'Image', 'NineImage', 'TileImage']);
10
+ const runtimeNodeTypes = new Set(['Group', 'GridContainer', 'HBoxContainer', 'ScrollContainer', 'VBoxContainer', 'Rect', 'Image', 'NineImage', 'TileImage']);
10
11
  const runtimeNodeProps = new Set(['columns', 'gap', 'gapX', 'gapY', 'alignX', 'alignY', 'justify', 'direction', 'scrollX', 'scrollY']);
11
12
  export function compileSceneTemplateToTs(template, options = {}) {
12
13
  const context = new CompileContext(template, options);
@@ -37,16 +38,18 @@ class CompileContext {
37
38
  const functionName = this.options.functionName || `mount${this.template.name}Scene`;
38
39
  const actionsParameter = this.options.actionsParameter || 'actions';
39
40
  const partsType = this.#formatPartsType();
40
- const textureLoads = this.#formatTextureLoads();
41
+ const texturePreparation = this.#formatTexturePreparation();
41
42
  this.#lines.push(`export function ${functionName}(root: Group${this.#hasEvents() ? `, ${actionsParameter}: Record<string, () => void> = {}` : ''}) {`);
43
+ this.#lines.push(' const __pixifactNodes: Record<string, Container> = {};');
42
44
  this.#lines.push(' const __pixifactSlots: Record<string, Container> = {};');
43
45
  this.#applyRootProps();
44
- for (const child of this.template.children) {
45
- this.#compileNode(child, 'root', actionsParameter);
46
+ for (const [index, child] of this.template.children.entries()) {
47
+ this.#compileNode(child, 'root', actionsParameter, String(index));
46
48
  }
47
49
  this.#lines.push(' return {');
48
50
  this.#lines.push(' root,');
49
51
  this.#lines.push(` parts: { ${this.#parts.map((part) => part.id).join(', ')} },`);
52
+ this.#lines.push(' nodes: __pixifactNodes,');
50
53
  this.#lines.push(' slots: __pixifactSlots,');
51
54
  this.#lines.push(' };');
52
55
  this.#lines.push('}');
@@ -57,7 +60,10 @@ class CompileContext {
57
60
  }
58
61
  this.#lines.push('');
59
62
  this.#lines.push(`registerScene(${JSON.stringify(this.options.registrationPath)}, {`);
63
+ this.#lines.push(` assets: ${JSON.stringify([...this.#textures.keys()])},`);
64
+ this.#lines.push(` dependencies: ${JSON.stringify(this.options.sceneDependencies ?? [])},`);
60
65
  this.#lines.push(` mount: ${functionName},`);
66
+ this.#lines.push(` prepare: ${this.#prepareFunctionName()},`);
61
67
  this.#lines.push('});');
62
68
  if (this.options.scriptImport) {
63
69
  this.#lines.push(`registerSceneClass(${this.options.scriptImport.localName}, ${JSON.stringify(this.options.registrationPath)});`);
@@ -66,7 +72,7 @@ class CompileContext {
66
72
  const imports = this.#formatImports();
67
73
  return [
68
74
  imports,
69
- textureLoads,
75
+ texturePreparation,
70
76
  partsType,
71
77
  this.#lines.join('\n'),
72
78
  ].filter(Boolean).join('\n\n') + '\n';
@@ -77,11 +83,8 @@ class CompileContext {
77
83
  ...[...this.#imports].filter((item) => item !== 'Container').sort(),
78
84
  ...[...this.#pixiImports].sort(),
79
85
  ])];
80
- const runtimeImports = ['Group', ...[...this.#pixiRuntimeImports].sort()];
86
+ const runtimeImports = [...new Set(['Group', ...[...this.#pixiRuntimeImports].sort()])];
81
87
  const lines = [`import { ${imports.join(', ')} } from 'pixi.js';`, `import { ${runtimeImports.join(', ')} } from 'pixifact/runtime';`];
82
- for (const [texture, variable] of this.#textureImportEntries()) {
83
- lines.push(`import ${variable} from ${JSON.stringify(this.options.textureImports?.[texture])};`);
84
- }
85
88
  for (const sceneImport of this.options.sceneImports ?? []) {
86
89
  const classImport = sceneImport.exportName === sceneImport.localName
87
90
  ? sceneImport.exportName
@@ -98,7 +101,7 @@ class CompileContext {
98
101
  lines.push(`import { ${this.options.scriptImport.exportName} as ${this.options.scriptImport.localName} } from ${JSON.stringify(this.options.scriptImport.source)};`);
99
102
  }
100
103
  if (this.#runtimeImports.size > 0) {
101
- lines.push(`import { ${[...this.#runtimeImports].sort().join(', ')} } from 'pixifact/compiler';`);
104
+ lines.push(`import { ${[...this.#runtimeImports].sort().join(', ')} } from 'pixifact/scene';`);
102
105
  }
103
106
  return lines.join('\n');
104
107
  }
@@ -109,6 +112,7 @@ class CompileContext {
109
112
  lines.push(` ${part.id}: ${part.type};`);
110
113
  }
111
114
  lines.push(' };');
115
+ lines.push(' nodes: Record<string, Container>;');
112
116
  lines.push(' slots: Record<string, Container>;');
113
117
  lines.push('};');
114
118
  return lines.join('\n');
@@ -174,16 +178,27 @@ class CompileContext {
174
178
  }
175
179
  }
176
180
  }
177
- #formatTextureLoads() {
181
+ #formatTexturePreparation() {
178
182
  if (this.#textures.size === 0) {
179
- return '';
183
+ return `export async function ${this.#prepareFunctionName()}() {}`;
180
184
  }
181
- this.#pixiImports.add('Assets');
182
- return [...this.#textures.entries()]
183
- .map(([texture, variable]) => `const ${variable} = await Assets.load(${this.#textureLoadSource(texture)});`)
184
- .join('\n');
185
- }
186
- #compileNode(node, parent, actionsParameter) {
185
+ this.#pixiImports.add('Texture');
186
+ this.#runtimeImports.add('loadSceneTexture');
187
+ const entries = [...this.#textures.entries()];
188
+ return [
189
+ ...entries.map(([, variable]) => `let ${variable}: Texture;`),
190
+ '',
191
+ `export async function ${this.#prepareFunctionName()}() {`,
192
+ ` [${entries.map(([, variable]) => variable).join(', ')}] = await Promise.all([`,
193
+ ...entries.map(([texture]) => ` loadSceneTexture(${JSON.stringify(texture)}),`),
194
+ ' ]);',
195
+ '}',
196
+ ].join('\n');
197
+ }
198
+ #prepareFunctionName() {
199
+ return `prepare${this.template.name}Scene`;
200
+ }
201
+ #compileNode(node, parent, actionsParameter, path) {
187
202
  if (node.kind === 'slotOutlet') {
188
203
  this.#runtimeImports.add('registerSlot');
189
204
  this.#lines.push(` __pixifactSlots[${JSON.stringify(node.name)}] = ${parent};`);
@@ -191,24 +206,27 @@ class CompileContext {
191
206
  return;
192
207
  }
193
208
  if (node.kind === 'sceneInstance') {
194
- this.#compileSceneInstance(node, parent, actionsParameter);
209
+ this.#compileSceneInstance(node, parent, actionsParameter, path);
195
210
  return;
196
211
  }
197
- this.#compilePixiNode(node, parent, actionsParameter);
212
+ this.#compilePixiNode(node, parent, actionsParameter, path);
198
213
  }
199
- #compilePixiNode(node, parent, actionsParameter) {
214
+ #compilePixiNode(node, parent, actionsParameter, path) {
200
215
  const variable = node.id || this.#anonymousName(node.type);
216
+ const locator = compilerSceneNodeLocator(node, path);
201
217
  this.#lines.push(` const ${variable} = ${this.#constructPixiNode(node)};`);
202
218
  this.#applyNodeId(variable, node.id);
203
219
  this.#applyPixiProps(variable, node.props, false, undefined, node.type);
204
220
  this.#applyParentSorting(parent, node.props);
205
221
  this.#lines.push(` ${parent}.addChild(${variable});`);
206
- for (const child of node.children) {
207
- this.#compileNode(child, variable, actionsParameter);
222
+ this.#lines.push(` __pixifactNodes[${JSON.stringify(locator)}] = ${variable};`);
223
+ for (const [index, child] of node.children.entries()) {
224
+ this.#compileNode(child, variable, actionsParameter, `${locator}/${index}`);
208
225
  }
209
226
  }
210
- #compileSceneInstance(node, parent, actionsParameter) {
227
+ #compileSceneInstance(node, parent, actionsParameter, path) {
211
228
  const variable = node.id || this.#anonymousName(node.type);
229
+ const locator = compilerSceneNodeLocator(node, path);
212
230
  const constructorName = this.#sceneConstructorName(node);
213
231
  this.#lines.push(` const ${variable} = new ${constructorName}();`);
214
232
  this.#applyPixiProps(variable, node.props, true, node);
@@ -218,20 +236,22 @@ class CompileContext {
218
236
  }
219
237
  this.#applyParentSorting(parent, node.props);
220
238
  this.#lines.push(` ${parent}.addChild(${variable});`);
239
+ this.#lines.push(` __pixifactNodes[${JSON.stringify(locator)}] = ${variable};`);
221
240
  for (const [slot, children] of Object.entries(node.slots)) {
222
- for (const child of children) {
223
- const childVariable = this.#compileSlottedNode(child, variable, actionsParameter);
241
+ for (const [index, child] of children.entries()) {
242
+ const childVariable = this.#compileSlottedNode(child, variable, actionsParameter, `${locator}/slot:${slot}/${index}`);
224
243
  this.#runtimeImports.add('mount');
225
244
  this.#lines.push(` mount(${variable}, ${childVariable}, ${JSON.stringify(slot)});`);
226
245
  }
227
246
  }
228
247
  }
229
- #compileSlottedNode(node, slotTarget, actionsParameter) {
248
+ #compileSlottedNode(node, slotTarget, actionsParameter, path) {
230
249
  if (node.kind === 'slotOutlet') {
231
250
  throw new Error(`Cannot place <slot> inside ${slotTarget}.`);
232
251
  }
233
252
  if (node.kind === 'sceneInstance') {
234
253
  const variable = node.id || this.#anonymousName(node.type);
254
+ const locator = compilerSceneNodeLocator(node, path);
235
255
  const constructorName = this.#sceneConstructorName(node);
236
256
  this.#lines.push(` const ${variable} = new ${constructorName}();`);
237
257
  this.#applyPixiProps(variable, node.props, true, node);
@@ -239,9 +259,10 @@ class CompileContext {
239
259
  this.#runtimeImports.add('connectSceneEvent');
240
260
  this.#lines.push(` connectSceneEvent(${variable}.${name}, ${JSON.stringify(action)}, root, ${actionsParameter});`);
241
261
  }
262
+ this.#lines.push(` __pixifactNodes[${JSON.stringify(locator)}] = ${variable};`);
242
263
  for (const [slot, children] of Object.entries(node.slots)) {
243
- for (const child of children) {
244
- const childVariable = this.#compileSlottedNode(child, variable, actionsParameter);
264
+ for (const [index, child] of children.entries()) {
265
+ const childVariable = this.#compileSlottedNode(child, variable, actionsParameter, `${locator}/slot:${slot}/${index}`);
245
266
  this.#runtimeImports.add('mount');
246
267
  this.#lines.push(` mount(${variable}, ${childVariable}, ${JSON.stringify(slot)});`);
247
268
  }
@@ -249,11 +270,13 @@ class CompileContext {
249
270
  return variable;
250
271
  }
251
272
  const variable = node.id || this.#anonymousName(node.type);
273
+ const locator = compilerSceneNodeLocator(node, path);
252
274
  this.#lines.push(` const ${variable} = ${this.#constructPixiNode(node)};`);
253
275
  this.#applyNodeId(variable, node.id);
254
276
  this.#applyPixiProps(variable, node.props, false, undefined, node.type);
255
- for (const child of node.children) {
256
- this.#compileNode(child, variable, actionsParameter);
277
+ this.#lines.push(` __pixifactNodes[${JSON.stringify(locator)}] = ${variable};`);
278
+ for (const [index, child] of node.children.entries()) {
279
+ this.#compileNode(child, variable, actionsParameter, `${locator}/${index}`);
257
280
  }
258
281
  return variable;
259
282
  }
@@ -578,20 +601,6 @@ class CompileContext {
578
601
  this.#textures.set(texture, variable);
579
602
  return variable;
580
603
  }
581
- #textureImportEntries() {
582
- return [...this.#textures.keys()]
583
- .filter((texture) => this.options.textureImports?.[texture])
584
- .map((texture, index) => [texture, `__pixifactTextureUrl${index + 1}`]);
585
- }
586
- #textureImportVariable(texture) {
587
- const index = [...this.#textures.keys()]
588
- .filter((key) => this.options.textureImports?.[key])
589
- .indexOf(texture);
590
- return index >= 0 ? `__pixifactTextureUrl${index + 1}` : undefined;
591
- }
592
- #textureLoadSource(texture) {
593
- return this.#textureImportVariable(texture) ?? this.#value(texture);
594
- }
595
604
  #textureValue(value) {
596
605
  return typeof value === 'string' ? this.#textureVariable(value) : this.#value(value);
597
606
  }
@@ -4,5 +4,6 @@ export interface PixifactScenesPluginOptions {
4
4
  }
5
5
  export declare function pixifactScenesPlugin(options?: PixifactScenesPluginOptions): {
6
6
  name: string;
7
- resolveId(id: string): string | undefined;
7
+ resolveId(id: string): "\0pixifact:scenes" | undefined;
8
+ load(id: string): Promise<string | undefined>;
8
9
  };
@@ -1,5 +1,7 @@
1
+ import { readFile } from 'node:fs/promises';
1
2
  import path from 'node:path';
2
3
  import { fileURLToPath } from 'node:url';
4
+ import { generatedSceneAssetsFileName } from '../compiler/compileScenes.js';
3
5
  function projectRootPath(projectRoot) {
4
6
  if (typeof projectRoot === 'string') {
5
7
  return projectRoot;
@@ -10,12 +12,37 @@ function projectRootPath(projectRoot) {
10
12
  return process.cwd();
11
13
  }
12
14
  export function pixifactScenesPlugin(options = {}) {
13
- const generatedDir = path.resolve(projectRootPath(options.projectRoot), options.generatedDir ?? '.pixifact/generated');
15
+ const projectRoot = projectRootPath(options.projectRoot);
16
+ const generatedDir = path.resolve(projectRoot, options.generatedDir ?? '.pixifact/generated');
14
17
  const sceneRegistry = path.join(generatedDir, 'scenes.generated.ts');
18
+ const sceneAssets = path.join(generatedDir, generatedSceneAssetsFileName);
19
+ const virtualSceneRegistry = '\0pixifact:scenes';
15
20
  return {
16
21
  name: 'pixifact-scenes',
17
22
  resolveId(id) {
18
- return id === 'pixifact:scenes' ? sceneRegistry : undefined;
23
+ return id === 'pixifact:scenes' ? virtualSceneRegistry : undefined;
24
+ },
25
+ async load(id) {
26
+ if (id !== virtualSceneRegistry) {
27
+ return undefined;
28
+ }
29
+ const assets = JSON.parse(await readFile(sceneAssets, 'utf8'));
30
+ const imports = assets.map((asset, index) => `import __pixifactAsset${index + 1} from ${JSON.stringify(viteFileImport(path.resolve(projectRoot, asset), '?url'))};`);
31
+ const manifest = Object.fromEntries(assets.map((asset, index) => [
32
+ asset,
33
+ { src: `__pixifactAsset${index + 1}` },
34
+ ]));
35
+ const manifestSource = JSON.stringify(manifest, null, 2)
36
+ .replace(/"src": "(__pixifactAsset\d+)"/g, '"src": $1');
37
+ return [
38
+ ...imports,
39
+ "import { configureSceneAssets } from 'pixifact/scene';",
40
+ `import ${JSON.stringify(viteFileImport(sceneRegistry))};`,
41
+ `configureSceneAssets(${manifestSource});`,
42
+ ].join('\n');
19
43
  },
20
44
  };
21
45
  }
46
+ function viteFileImport(filePath, suffix = '') {
47
+ return `/@fs/${filePath.replaceAll(path.sep, '/')}${suffix}`;
48
+ }