pixifact 0.6.1 → 0.8.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 (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -29
  3. package/dist/client.d.ts +23 -0
  4. package/dist/compiler/compileScenes.js +13 -4
  5. package/dist/compiler/typescriptCompiler.js +2 -2
  6. package/dist/compiler-node/runtimeVite.js +96 -23
  7. package/dist/compiler-node/vite.d.ts +16 -6
  8. package/dist/compiler-node/vite.js +418 -32
  9. package/dist/errors.d.ts +1 -1
  10. package/dist/errors.js +6 -0
  11. package/dist/platform/{wechat → minigame}/adapter.d.ts +7 -6
  12. package/dist/platform/{wechat → minigame}/adapter.js +10 -8
  13. package/dist/platform/minigame/application.d.ts +4 -0
  14. package/dist/platform/minigame/application.js +53 -0
  15. package/dist/platform/minigame/fetch.d.ts +2 -0
  16. package/dist/platform/{wechat → minigame}/fetch.js +13 -15
  17. package/dist/platform/minigame/index.d.ts +8 -0
  18. package/dist/platform/minigame/index.js +8 -0
  19. package/dist/platform/minigame/input.d.ts +16 -0
  20. package/dist/platform/{wechat → minigame}/input.js +18 -19
  21. package/dist/platform/minigame/lifecycle.d.ts +15 -0
  22. package/dist/platform/{wechat → minigame}/lifecycle.js +5 -7
  23. package/dist/platform/{wechat → minigame}/runtime.d.ts +5 -5
  24. package/dist/platform/{wechat → minigame}/runtime.js +14 -20
  25. package/dist/platform/minigame/subpackage.d.ts +3 -0
  26. package/dist/platform/minigame/subpackage.js +36 -0
  27. package/dist/platform/{wechat → minigame}/types.d.ts +24 -21
  28. package/dist/platform/minigame/types.js +1 -0
  29. package/dist/platform/web/index.d.ts +2 -0
  30. package/dist/platform/web/index.js +6 -0
  31. package/dist/project/index.d.ts +5 -24
  32. package/dist/project/index.js +45 -59
  33. package/dist/runtime-dev/index.d.ts +1 -1
  34. package/dist/runtime-dev/protocol.d.ts +12 -1
  35. package/dist/runtime-dev/runtimeClient.js +21 -1
  36. package/dist/scene/index.d.ts +0 -1
  37. package/dist/scene/index.js +0 -1
  38. package/package.json +16 -7
  39. package/dist/platform/wechat/application.d.ts +0 -32
  40. package/dist/platform/wechat/application.js +0 -79
  41. package/dist/platform/wechat/fetch.d.ts +0 -1
  42. package/dist/platform/wechat/index.d.ts +0 -4
  43. package/dist/platform/wechat/index.js +0 -3
  44. package/dist/platform/wechat/input.d.ts +0 -12
  45. package/dist/platform/wechat/lifecycle.d.ts +0 -14
  46. package/dist/platform/wechat/subpackage.d.ts +0 -1
  47. package/dist/platform/wechat/subpackage.js +0 -27
  48. package/dist/platform/wechat/types.js +0 -3
  49. package/dist/scene/sceneAssetRuntime.d.ts +0 -11
  50. package/dist/scene/sceneAssetRuntime.js +0 -43
@@ -1,48 +1,434 @@
1
- import { readFile } from 'node:fs/promises';
1
+ import { access, readFile, readdir, stat } from 'node:fs/promises';
2
+ import { realpathSync } from 'node:fs';
2
3
  import path from 'node:path';
3
4
  import { fileURLToPath } from 'node:url';
4
- import { generatedSceneAssetsFileName } from '../compiler/compileScenes.js';
5
- function projectRootPath(projectRoot) {
6
- if (typeof projectRoot === 'string') {
7
- return projectRoot;
8
- }
9
- if (projectRoot) {
10
- return fileURLToPath(projectRoot);
11
- }
12
- return process.cwd();
5
+ import { loadEnv, } from 'vite';
6
+ import { parsePixifactProjectConfig, pixifactProjectConfigFileName, } from '../project/index.js';
7
+ import { compileScenes, generatedSceneAssetsFileName } from '../compiler/compileScenes.js';
8
+ const totalPackageLimit = 20 * 1024 * 1024;
9
+ const mainPackageLimit = 4 * 1024 * 1024;
10
+ const descriptors = {
11
+ wechat: {
12
+ ecmascriptTarget: 'es2020',
13
+ mainPackageLimit: () => mainPackageLimit,
14
+ module: '@pixifact/platform-wechat',
15
+ subpackageProperty: 'subpackages',
16
+ userAgent: 'WeChatMiniGame',
17
+ },
18
+ douyin: {
19
+ ecmascriptTarget: 'es2018',
20
+ mainPackageLimit: (hasSubpackages) => hasSubpackages ? mainPackageLimit : undefined,
21
+ module: '@pixifact/platform-douyin',
22
+ subpackageProperty: 'subPackages',
23
+ userAgent: 'DouyinMiniGame',
24
+ },
25
+ };
26
+ const virtualPlatformId = '\0pixifact:platform';
27
+ const virtualAssetsId = '\0pixifact:assets';
28
+ const virtualScenesId = '\0pixifact:scenes';
29
+ export function pixifact(options = {}) {
30
+ const state = {};
31
+ return [
32
+ scenePlugin(state, options),
33
+ platformPlugin(state),
34
+ assetsPlugin(state),
35
+ resourcePlugin(state),
36
+ miniGamePlugin(state),
37
+ ];
13
38
  }
14
- export function pixifactScenesPlugin(options = {}) {
15
- const projectRoot = projectRootPath(options.projectRoot);
16
- const generatedDir = path.resolve(projectRoot, options.generatedDir ?? '.pixifact/generated');
17
- const sceneRegistry = path.join(generatedDir, 'scenes.generated.ts');
18
- const sceneAssets = path.join(generatedDir, generatedSceneAssetsFileName);
19
- const virtualSceneRegistry = '\0pixifact:scenes';
39
+ function scenePlugin(state, options) {
20
40
  return {
21
41
  name: 'pixifact-scenes',
42
+ async config(config, env) {
43
+ await configureState(state, options, config, env);
44
+ const projectRoot = required(state.projectRoot);
45
+ const platform = required(state.platform);
46
+ const output = platform === 'web'
47
+ ? {}
48
+ : miniGameBuildConfig(projectRoot, platform, config);
49
+ return {
50
+ ...output,
51
+ root: projectRoot,
52
+ resolve: {
53
+ dedupe: config.resolve?.dedupe?.includes('pixi.js') ? [] : ['pixi.js'],
54
+ },
55
+ build: {
56
+ outDir: path.resolve(projectRoot, config.build?.outDir ?? `dist/${platform}`),
57
+ ...output.build,
58
+ },
59
+ };
60
+ },
61
+ configResolved(config) {
62
+ state.resolved = config;
63
+ state.projectRoot = config.root;
64
+ assertSafeOutputDirectory(config.root, config.build.outDir);
65
+ },
66
+ async buildStart() {
67
+ state.prepared = prepareProject(state);
68
+ const assets = await state.prepared;
69
+ for (const file of watchedFiles(state, assets)) {
70
+ this.addWatchFile(file);
71
+ }
72
+ },
22
73
  resolveId(id) {
23
- return id === 'pixifact:scenes' ? virtualSceneRegistry : undefined;
74
+ return id === 'pixifact:scenes' ? virtualScenesId : undefined;
75
+ },
76
+ load(id) {
77
+ if (id !== virtualScenesId)
78
+ return undefined;
79
+ return `import ${JSON.stringify(viteFileImport(path.join(required(state.projectRoot), '.pixifact/generated/scenes.generated.ts')))};`;
80
+ },
81
+ };
82
+ }
83
+ function platformPlugin(state) {
84
+ return {
85
+ name: 'pixifact-platform',
86
+ resolveId(id) {
87
+ return id === 'pixifact:platform' ? virtualPlatformId : undefined;
88
+ },
89
+ load(id) {
90
+ if (id !== virtualPlatformId)
91
+ return undefined;
92
+ const platform = required(state.platform);
93
+ const module = platform === 'web' ? 'pixifact/platform/web' : descriptors[platform].module;
94
+ return `export { createApplication } from ${JSON.stringify(module)};`;
95
+ },
96
+ };
97
+ }
98
+ function assetsPlugin(state) {
99
+ return {
100
+ name: 'pixifact-assets',
101
+ resolveId(id) {
102
+ return id === 'pixifact:assets' ? virtualAssetsId : undefined;
24
103
  },
25
104
  async load(id) {
26
- if (id !== virtualSceneRegistry) {
105
+ if (id !== virtualAssetsId)
27
106
  return undefined;
107
+ const assets = await required(state.prepared);
108
+ return assetsModuleSource(state, assets);
109
+ },
110
+ };
111
+ }
112
+ function resourcePlugin(state) {
113
+ return {
114
+ name: 'pixifact-resources',
115
+ async buildStart() {
116
+ if (required(state.resolved).command !== 'build')
117
+ return;
118
+ const platform = required(state.platform);
119
+ for (const asset of await required(state.prepared)) {
120
+ if (!asset.pack || asset.remoteBaseUrl)
121
+ continue;
122
+ const fileName = platform === 'web'
123
+ ? asset.logicalPath
124
+ : path.posix.join('subpackages', asset.pack, required(asset.relativePath));
125
+ this.emitFile({
126
+ type: 'asset',
127
+ fileName,
128
+ source: await readFile(asset.absolutePath),
129
+ });
130
+ }
131
+ if (platform !== 'web') {
132
+ for (const pack of localPacks(required(state.config))) {
133
+ this.emitFile({
134
+ type: 'asset',
135
+ fileName: path.posix.join('subpackages', pack, 'game.js'),
136
+ source: '// Pixifact asset-only subpackage.\n',
137
+ });
138
+ }
139
+ }
140
+ },
141
+ };
142
+ }
143
+ function miniGamePlugin(state) {
144
+ return {
145
+ name: 'pixifact-minigame',
146
+ async buildStart() {
147
+ const platform = required(state.platform);
148
+ if (platform === 'web')
149
+ return;
150
+ await required(state.prepared);
151
+ const projectRoot = required(state.projectRoot);
152
+ const configDir = path.join(projectRoot, 'platforms', platform);
153
+ const descriptor = descriptors[platform];
154
+ const gameJson = JSON.parse(await readFile(path.join(configDir, 'game.json'), 'utf8'));
155
+ const projectConfig = JSON.parse(await readFile(path.join(configDir, 'project.config.json'), 'utf8'));
156
+ if (gameJson[descriptor.subpackageProperty] !== undefined) {
157
+ throw new Error(`platforms/${platform}/game.json must not declare ${descriptor.subpackageProperty}; Pixifact manages resource subpackages.`);
28
158
  }
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');
159
+ gameJson[descriptor.subpackageProperty] = localPacks(required(state.config)).map((name) => ({
160
+ name,
161
+ root: `subpackages/${name}`,
162
+ }));
163
+ projectConfig.appid = required(state.env).VITE_APP_ID;
164
+ this.emitFile({
165
+ type: 'asset',
166
+ fileName: 'game.json',
167
+ source: `${JSON.stringify(gameJson, null, 2)}\n`,
168
+ });
169
+ this.emitFile({
170
+ type: 'asset',
171
+ fileName: 'project.config.json',
172
+ source: `${JSON.stringify(projectConfig, null, 2)}\n`,
173
+ });
174
+ },
175
+ generateBundle(_options, bundle) {
176
+ const platform = required(state.platform);
177
+ if (platform === 'web')
178
+ return;
179
+ enforcePackageLimits(createBuildReport(platform, required(state.resolved).build.outDir, bundle));
180
+ },
181
+ };
182
+ }
183
+ export function pixifactBuildReporter(platform, onReport) {
184
+ let outputDirectory = '';
185
+ return {
186
+ name: 'pixifact-build-reporter',
187
+ configResolved(config) {
188
+ outputDirectory = config.build.outDir;
189
+ },
190
+ generateBundle(_options, bundle) {
191
+ onReport(createBuildReport(platform, outputDirectory, bundle));
192
+ },
193
+ };
194
+ }
195
+ export function loadPixifactEnv(projectRoot, mode) {
196
+ const env = loadEnv(mode, projectRoot, 'VITE_');
197
+ const platform = env.VITE_PLATFORM;
198
+ if (platform !== 'web' && platform !== 'wechat' && platform !== 'douyin') {
199
+ throw new Error('VITE_PLATFORM must be web, wechat, or douyin.');
200
+ }
201
+ if (platform !== 'web' && !env.VITE_APP_ID) {
202
+ throw new Error(`VITE_APP_ID is required for the ${platform} platform.`);
203
+ }
204
+ return { env, platform: platform };
205
+ }
206
+ async function configureState(state, options, config, env) {
207
+ const configuredRoot = options.projectRoot ?? config.root;
208
+ const projectRoot = realpathSync(path.resolve(projectRootPath(configuredRoot)));
209
+ const loaded = loadPixifactEnv(projectRoot, env.mode);
210
+ state.projectRoot = projectRoot;
211
+ state.mode = env.mode;
212
+ state.env = loaded.env;
213
+ state.platform = loaded.platform;
214
+ state.config = parsePixifactProjectConfig(JSON.parse(await readFile(path.join(projectRoot, pixifactProjectConfigFileName), 'utf8')));
215
+ }
216
+ function miniGameBuildConfig(projectRoot, platform, config) {
217
+ const descriptor = descriptors[platform];
218
+ return {
219
+ appType: 'custom',
220
+ experimental: {
221
+ ...config.experimental,
222
+ renderBuiltUrl: (filename) => filename,
223
+ },
224
+ publicDir: false,
225
+ build: {
226
+ assetsInlineLimit: 0,
227
+ modulePreload: false,
228
+ rollupOptions: {
229
+ ...config.build?.rollupOptions,
230
+ input: config.build?.rollupOptions?.input ?? path.join(projectRoot, 'src/main.ts'),
231
+ output: {
232
+ ...(Array.isArray(config.build?.rollupOptions?.output)
233
+ ? {}
234
+ : config.build?.rollupOptions?.output),
235
+ banner: [
236
+ 'var Intl=globalThis.Intl||{};',
237
+ `var navigator=globalThis.navigator||(globalThis.navigator={gpu:null,userAgent:${JSON.stringify(descriptor.userAgent)}});`,
238
+ ].join('\n'),
239
+ entryFileNames: 'game.js',
240
+ format: 'iife',
241
+ },
242
+ },
243
+ target: descriptor.ecmascriptTarget,
43
244
  },
44
245
  };
45
246
  }
247
+ async function prepareProject(state) {
248
+ const projectRoot = required(state.projectRoot);
249
+ state.config = parsePixifactProjectConfig(JSON.parse(await readFile(path.join(projectRoot, pixifactProjectConfigFileName), 'utf8')));
250
+ await compileScenes({ projectRoot });
251
+ const config = required(state.config);
252
+ const sceneAssets = JSON.parse(await readFile(path.join(projectRoot, '.pixifact/generated', generatedSceneAssetsFileName), 'utf8'));
253
+ const assets = new Map();
254
+ for (const pack of config.resourcePacks ?? []) {
255
+ const root = path.join(projectRoot, 'resources', pack);
256
+ for (const absolutePath of await collectFiles(root)) {
257
+ const relativePath = projectPath(root, absolutePath);
258
+ const logicalPath = path.posix.join('resources', pack, relativePath);
259
+ assets.set(logicalPath, {
260
+ absolutePath,
261
+ logicalPath,
262
+ pack,
263
+ relativePath,
264
+ remoteBaseUrl: config.remoteResourcePacks?.[pack],
265
+ });
266
+ }
267
+ }
268
+ for (const logicalPath of sceneAssets) {
269
+ if (!assets.has(logicalPath)) {
270
+ assets.set(logicalPath, {
271
+ absolutePath: path.resolve(projectRoot, logicalPath),
272
+ logicalPath,
273
+ });
274
+ }
275
+ }
276
+ return [...assets.values()];
277
+ }
278
+ function assetsModuleSource(state, assets) {
279
+ const platform = required(state.platform);
280
+ const resolved = required(state.resolved);
281
+ const imports = [];
282
+ const sourceByPath = new Map();
283
+ for (const asset of assets) {
284
+ if (asset.remoteBaseUrl) {
285
+ sourceByPath.set(asset.logicalPath, JSON.stringify(`${asset.remoteBaseUrl}/${required(asset.relativePath)}`));
286
+ }
287
+ else if (asset.pack) {
288
+ if (platform === 'web' && resolved.command === 'serve') {
289
+ sourceByPath.set(asset.logicalPath, JSON.stringify(viteFileImport(asset.absolutePath)));
290
+ }
291
+ else {
292
+ const output = platform === 'web'
293
+ ? asset.logicalPath
294
+ : path.posix.join('subpackages', asset.pack, required(asset.relativePath));
295
+ sourceByPath.set(asset.logicalPath, platform === 'web'
296
+ ? `import.meta.env.BASE_URL + ${JSON.stringify(output)}`
297
+ : JSON.stringify(output));
298
+ }
299
+ }
300
+ else {
301
+ const identifier = `__pixifactAsset${imports.length + 1}`;
302
+ imports.push(`import ${identifier} from ${JSON.stringify(viteFileImport(asset.absolutePath, '?url'))};`);
303
+ sourceByPath.set(asset.logicalPath, identifier);
304
+ }
305
+ }
306
+ const bundles = (required(state.config).resourcePacks ?? []).map((name) => ({
307
+ name,
308
+ assets: assets.filter((asset) => asset.pack === name),
309
+ }));
310
+ const main = assets.filter((asset) => !asset.pack);
311
+ if (main.length > 0)
312
+ bundles.unshift({ name: '__pixifact_main__', assets: main });
313
+ const bundleSource = bundles.map((bundle) => `{
314
+ name: ${JSON.stringify(bundle.name)},
315
+ assets: [${bundle.assets.map((asset) => `{
316
+ alias: ${JSON.stringify(asset.logicalPath)},
317
+ src: ${required(sourceByPath.get(asset.logicalPath))},
318
+ }`).join(',')}],
319
+ }`).join(',');
320
+ return `${imports.join('\n')}\nconst manifest = { bundles: [${bundleSource}] };\nexport default manifest;\n`;
321
+ }
322
+ function watchedFiles(state, assets) {
323
+ const projectRoot = required(state.projectRoot);
324
+ const config = required(state.config);
325
+ const files = [
326
+ path.join(projectRoot, pixifactProjectConfigFileName),
327
+ ...Object.values(config.scenes).flatMap((scene) => [
328
+ path.join(projectRoot, scene),
329
+ path.join(projectRoot, scene.replace(/\.scene$/, '.ts')),
330
+ ]),
331
+ ...assets.map((asset) => asset.absolutePath),
332
+ ];
333
+ const platform = required(state.platform);
334
+ if (platform !== 'web') {
335
+ files.push(path.join(projectRoot, 'platforms', platform, 'game.json'), path.join(projectRoot, 'platforms', platform, 'project.config.json'));
336
+ }
337
+ return files;
338
+ }
339
+ function createBuildReport(platform, outputDirectory, bundle) {
340
+ const subpackages = {};
341
+ let mainPackageBytes = 0;
342
+ let totalPackageBytes = 0;
343
+ for (const output of Object.values(bundle)) {
344
+ if (output.fileName.endsWith('.map') || output.fileName === 'project.config.json')
345
+ continue;
346
+ const bytes = output.code === undefined
347
+ ? typeof output.source === 'string' ? Buffer.byteLength(output.source) : output.source?.byteLength ?? 0
348
+ : Buffer.byteLength(output.code);
349
+ const match = output.fileName.match(/^subpackages\/([^/]+)\//);
350
+ if (match) {
351
+ subpackages[match[1]] = (subpackages[match[1]] ?? 0) + bytes;
352
+ }
353
+ else {
354
+ mainPackageBytes += bytes;
355
+ }
356
+ totalPackageBytes += bytes;
357
+ }
358
+ return { mainPackageBytes, outputDirectory, platform, subpackages, totalPackageBytes };
359
+ }
360
+ function enforcePackageLimits(report) {
361
+ if (report.platform === 'web')
362
+ return;
363
+ const descriptor = descriptors[report.platform];
364
+ const limit = descriptor.mainPackageLimit(Object.keys(report.subpackages).length > 0);
365
+ if (limit !== undefined && report.mainPackageBytes > limit) {
366
+ throw new Error(`${report.platform} main package exceeds ${limit} bytes.`);
367
+ }
368
+ if (report.totalPackageBytes > totalPackageLimit) {
369
+ throw new Error(`${report.platform} total package exceeds ${totalPackageLimit} bytes.`);
370
+ }
371
+ }
372
+ function localPacks(config) {
373
+ return (config.resourcePacks ?? []).filter((name) => !config.remoteResourcePacks?.[name]);
374
+ }
375
+ function projectRootPath(projectRoot) {
376
+ if (typeof projectRoot === 'string')
377
+ return projectRoot;
378
+ if (projectRoot)
379
+ return fileURLToPath(projectRoot);
380
+ return process.cwd();
381
+ }
382
+ function assertSafeOutputDirectory(projectRoot, outputDirectory) {
383
+ const root = path.resolve(projectRoot);
384
+ const output = path.resolve(root, outputDirectory);
385
+ if (output === root || !output.startsWith(`${root}${path.sep}`)) {
386
+ throw new Error('Vite build.outDir must be a directory inside the project root.');
387
+ }
388
+ const relative = projectPath(root, output);
389
+ const inputRoots = ['.pixifact', 'platforms', 'resources', 'src'];
390
+ if (inputRoots.some((input) => relative === input || relative.startsWith(`${input}/`))) {
391
+ throw new Error('Vite build.outDir must not contain project inputs.');
392
+ }
393
+ }
394
+ async function collectFiles(root) {
395
+ if (!await isDirectory(root))
396
+ return [];
397
+ const files = [];
398
+ for (const entry of await readdir(root, { withFileTypes: true })) {
399
+ const absolute = path.join(root, entry.name);
400
+ if (entry.isDirectory())
401
+ files.push(...await collectFiles(absolute));
402
+ else if (entry.isFile())
403
+ files.push(absolute);
404
+ }
405
+ return files;
406
+ }
407
+ async function isDirectory(filePath) {
408
+ try {
409
+ return (await stat(filePath)).isDirectory();
410
+ }
411
+ catch {
412
+ return false;
413
+ }
414
+ }
415
+ export async function fileExists(filePath) {
416
+ try {
417
+ await access(filePath);
418
+ return true;
419
+ }
420
+ catch {
421
+ return false;
422
+ }
423
+ }
46
424
  function viteFileImport(filePath, suffix = '') {
47
425
  return `/@fs/${filePath.replaceAll(path.sep, '/')}${suffix}`;
48
426
  }
427
+ function projectPath(root, filePath) {
428
+ return path.relative(root, filePath).replaceAll(path.sep, '/');
429
+ }
430
+ function required(value) {
431
+ if (value === undefined)
432
+ throw new Error('Pixifact Vite plugin state is not ready.');
433
+ return value;
434
+ }
package/dist/errors.d.ts CHANGED
@@ -1 +1 @@
1
- export declare function hintForCommandError(error: string): "Use a project-relative scene path that stays inside projectRoot." | "Choose a different scene path or inspect the existing file before writing." | "Use one of: button, progressBar, scrollView, loginForm." | "Run scene inspect --scene <scene-path> and pass one of the returned node locator values." | "Inspect the current project state again before regenerating the command." | "Choose a container node as the parent, or create a container template before adding children." | "Verify that the field belongs to the target node or component schema before retrying." | "Check the target schema and send a value with the expected type." | undefined;
1
+ export declare function hintForCommandError(error: string): "Install the WeChat platform package with: bun add @pixifact/platform-wechat" | "Install the Douyin platform package with: bun add @pixifact/platform-douyin" | "Use a project-relative scene path that stays inside projectRoot." | "Choose a different scene path or inspect the existing file before writing." | "Use one of: button, progressBar, scrollView, loginForm." | "Run scene inspect --scene <scene-path> and pass one of the returned node locator values." | "Inspect the current project state again before regenerating the command." | "Choose a container node as the parent, or create a container template before adding children." | "Verify that the field belongs to the target node or component schema before retrying." | "Check the target schema and send a value with the expected type." | undefined;
package/dist/errors.js CHANGED
@@ -1,4 +1,10 @@
1
1
  export function hintForCommandError(error) {
2
+ if (error.includes('@pixifact/platform-wechat')) {
3
+ return 'Install the WeChat platform package with: bun add @pixifact/platform-wechat';
4
+ }
5
+ if (error.includes('@pixifact/platform-douyin')) {
6
+ return 'Install the Douyin platform package with: bun add @pixifact/platform-douyin';
7
+ }
2
8
  if (error.includes('inside projectRoot')) {
3
9
  return 'Use a project-relative scene path that stays inside projectRoot.';
4
10
  }
@@ -1,8 +1,8 @@
1
- import { fetchWechatResource } from './fetch.js';
2
- import type { WechatRuntime } from './runtime.js';
3
- export declare function createWechatPixiAdapter(runtime: WechatRuntime): {
4
- createCanvas(width?: number, height?: number): import("./types").WechatCanvas;
5
- createImage: () => import("./types").WechatImage;
1
+ import type { MiniGameApi, MiniGameFetch } from './types.js';
2
+ import type { MiniGameRuntime } from './runtime.js';
3
+ export declare function createMiniGamePixiAdapter(api: MiniGameApi, runtime: MiniGameRuntime, fetchResource: MiniGameFetch): {
4
+ createCanvas(width?: number, height?: number): import("./types").MiniGameCanvas;
5
+ createImage: () => import("./types").MiniGameImage;
6
6
  getCanvasRenderingContext2D: () => ObjectConstructor;
7
7
  getWebGLRenderingContext: () => {
8
8
  new (): WebGLRenderingContext;
@@ -311,6 +311,7 @@ export declare function createWechatPixiAdapter(runtime: WechatRuntime): {
311
311
  };
312
312
  getBaseUrl: () => string;
313
313
  getFontFaceSet: () => null;
314
- fetch: typeof fetchWechatResource;
314
+ fetch: MiniGameFetch;
315
315
  parseXML: () => Document;
316
316
  };
317
+ export declare function installMiniGamePixiAdapter(api: MiniGameApi, runtime: MiniGameRuntime, fetchResource: MiniGameFetch): Promise<void>;
@@ -1,26 +1,28 @@
1
- import { fetchWechatResource } from './fetch.js';
2
- import { wechatApi } from './types.js';
3
- export function createWechatPixiAdapter(runtime) {
4
- const wx = wechatApi();
1
+ export function createMiniGamePixiAdapter(api, runtime, fetchResource) {
5
2
  const webGL1Constructor = globalThis.WebGLRenderingContext
6
3
  ?? (runtime.webGLVersion === 1 ? runtime.context.constructor : class WebGLRenderingContextFallback {
7
4
  });
8
5
  return {
9
6
  createCanvas(width = 1, height = 1) {
10
- const canvas = wx.createOffscreenCanvas?.({ type: '2d', width, height }) ?? wx.createCanvas();
7
+ const canvas = api.createOffscreenCanvas?.({ type: '2d', width, height }) ?? api.createCanvas();
11
8
  canvas.width = width;
12
9
  canvas.height = height;
13
10
  return canvas;
14
11
  },
15
- createImage: () => wx.createImage(),
12
+ createImage: () => api.createImage(),
16
13
  getCanvasRenderingContext2D: () => Object,
17
14
  getWebGLRenderingContext: () => webGL1Constructor,
18
15
  getNavigator: () => ({ gpu: null, userAgent: runtime.userAgent }),
19
16
  getBaseUrl: () => '',
20
17
  getFontFaceSet: () => null,
21
- fetch: fetchWechatResource,
18
+ fetch: fetchResource,
22
19
  parseXML: () => {
23
- throw new Error('XML parsing is not implemented for the WeChat target.');
20
+ throw new Error('XML parsing is not implemented for the Mini Game target.');
24
21
  },
25
22
  };
26
23
  }
24
+ export async function installMiniGamePixiAdapter(api, runtime, fetchResource) {
25
+ const { DOMAdapter, loadTextures } = await import('pixi.js');
26
+ DOMAdapter.set(createMiniGamePixiAdapter(api, runtime, fetchResource));
27
+ loadTextures.config.preferCreateImageBitmap = false;
28
+ }
@@ -0,0 +1,4 @@
1
+ import type { Application, ApplicationOptions } from 'pixi.js';
2
+ import { type MiniGameTouchCoordinates } from './input.js';
3
+ import type { MiniGameApi, MiniGameFetch } from './types.js';
4
+ export declare function createMiniGameApplication(api: MiniGameApi, userAgent: string, fetchResource: MiniGameFetch, coordinates: MiniGameTouchCoordinates, options?: Partial<ApplicationOptions>): Promise<Application>;
@@ -0,0 +1,53 @@
1
+ import { installMiniGamePixiAdapter } from './adapter.js';
2
+ import { bindMiniGameLifecycle } from './lifecycle.js';
3
+ import { bindMiniGamePointerEvents } from './input.js';
4
+ import { createMiniGameRuntime } from './runtime.js';
5
+ export async function createMiniGameApplication(api, userAgent, fetchResource, coordinates, options = {}) {
6
+ await Promise.all([
7
+ import('pixi.js/unsafe-eval'),
8
+ import('pixi.js/events'),
9
+ ]);
10
+ const { Application } = await import('pixi.js');
11
+ const runtime = createMiniGameRuntime(api, userAgent);
12
+ await installMiniGamePixiAdapter(api, runtime, fetchResource);
13
+ const app = new Application();
14
+ await app.init({
15
+ antialias: true,
16
+ ...options,
17
+ autoDensity: false,
18
+ canvas: runtime.canvas,
19
+ context: runtime.context,
20
+ eventFeatures: options.eventFeatures ?? {
21
+ click: true,
22
+ globalMove: true,
23
+ move: true,
24
+ wheel: false,
25
+ },
26
+ height: runtime.height,
27
+ preference: 'webgl',
28
+ resolution: options.resolution ?? runtime.resolution,
29
+ skipExtensionImports: true,
30
+ width: runtime.width,
31
+ });
32
+ const events = app.renderer.events;
33
+ const sink = {
34
+ pointerdown: (event) => events._onPointerDown(event),
35
+ pointermove: (event) => events._onPointerMove(event),
36
+ pointerup: (event) => events._onPointerUp(event),
37
+ };
38
+ const input = bindMiniGamePointerEvents(api, runtime.canvas, sink, coordinates);
39
+ const lifecycle = bindMiniGameLifecycle(api, app.ticker, {
40
+ onHide: () => input.cancelAll(),
41
+ });
42
+ let destroyed = false;
43
+ const destroy = app.destroy.bind(app);
44
+ app.destroy = ((...args) => {
45
+ if (destroyed)
46
+ return;
47
+ destroyed = true;
48
+ lifecycle.dispose();
49
+ input.dispose();
50
+ destroy(...args);
51
+ });
52
+ return app;
53
+ }
@@ -0,0 +1,2 @@
1
+ import type { MiniGameApi, MiniGameFetch } from './types.js';
2
+ export declare function fetchMiniGameResource(api: MiniGameApi, input: RequestInfo | URL, init: RequestInit | undefined, platformName: string): ReturnType<MiniGameFetch>;