pixifact 0.18.0 → 0.20.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.
- package/README.md +1 -1
- package/dist/compiler-node/vite.d.ts +2 -0
- package/dist/compiler-node/vite.js +177 -6
- package/dist/platform/minigame/application.js +7 -0
- package/dist/platform/minigame/fetch.d.ts +2 -0
- package/dist/platform/minigame/fetch.js +2 -2
- package/dist/platform/minigame/runtime.js +8 -7
- package/dist/platform/minigame/subpackage.js +9 -3
- package/dist/platform/minigame/types.d.ts +8 -0
- package/dist/runtime-dev/runtimeClient.d.ts +3 -1
- package/dist/runtime-dev/runtimeClient.js +4 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type Plugin } from 'vite';
|
|
2
2
|
export type PixifactPlatform = 'web' | 'wechat' | 'douyin';
|
|
3
3
|
export interface PixifactPluginOptions {
|
|
4
|
+
/** Code subpackage names mapped to project-relative TypeScript/JavaScript entries. */
|
|
5
|
+
codePackages?: Record<string, string>;
|
|
4
6
|
projectRoot?: string | URL;
|
|
5
7
|
}
|
|
6
8
|
export interface PixifactBuildReport {
|
|
@@ -2,6 +2,7 @@ import { access, readFile, readdir, stat } from 'node:fs/promises';
|
|
|
2
2
|
import { realpathSync } from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import ts from 'typescript';
|
|
5
6
|
import { loadEnv, } from 'vite';
|
|
6
7
|
import { parsePixifactProjectConfig, pixifactProjectConfigFileName, } from '../project/index.js';
|
|
7
8
|
import { compileScenes, generatedSceneAssetsFileName } from '../compiler/compileScenes.js';
|
|
@@ -26,13 +27,15 @@ const descriptors = {
|
|
|
26
27
|
const virtualPlatformId = '\0pixifact:platform';
|
|
27
28
|
const virtualAssetsId = '\0pixifact:assets';
|
|
28
29
|
const virtualScenesId = '\0pixifact:scenes';
|
|
30
|
+
const virtualCodeLoaderId = '\0pixifact:code-loader';
|
|
29
31
|
export function pixifact(options = {}) {
|
|
30
|
-
const state = {};
|
|
32
|
+
const state = { codePackages: new Map() };
|
|
31
33
|
return [
|
|
32
34
|
scenePlugin(state, options),
|
|
33
35
|
platformPlugin(state),
|
|
34
36
|
assetsPlugin(state),
|
|
35
37
|
resourcePlugin(state),
|
|
38
|
+
codePackagesPlugin(state),
|
|
36
39
|
miniGamePlugin(state),
|
|
37
40
|
];
|
|
38
41
|
}
|
|
@@ -45,7 +48,7 @@ function scenePlugin(state, options) {
|
|
|
45
48
|
const platform = required(state.platform);
|
|
46
49
|
const output = platform === 'web'
|
|
47
50
|
? {}
|
|
48
|
-
: miniGameBuildConfig(projectRoot, platform, config);
|
|
51
|
+
: miniGameBuildConfig(projectRoot, platform, config, state.codePackages.size > 0);
|
|
49
52
|
return {
|
|
50
53
|
...output,
|
|
51
54
|
root: projectRoot,
|
|
@@ -130,6 +133,8 @@ function resourcePlugin(state) {
|
|
|
130
133
|
}
|
|
131
134
|
if (platform !== 'web') {
|
|
132
135
|
for (const pack of localPacks(required(state.config))) {
|
|
136
|
+
if (state.codePackages.has(pack))
|
|
137
|
+
continue;
|
|
133
138
|
this.emitFile({
|
|
134
139
|
type: 'asset',
|
|
135
140
|
fileName: path.posix.join('subpackages', pack, 'game.js'),
|
|
@@ -140,6 +145,141 @@ function resourcePlugin(state) {
|
|
|
140
145
|
},
|
|
141
146
|
};
|
|
142
147
|
}
|
|
148
|
+
function codePackagesPlugin(state) {
|
|
149
|
+
return {
|
|
150
|
+
name: 'pixifact-code-packages',
|
|
151
|
+
apply: 'build',
|
|
152
|
+
enforce: 'post',
|
|
153
|
+
async buildStart(input) {
|
|
154
|
+
if (state.platform === 'web' || state.codePackages.size === 0)
|
|
155
|
+
return;
|
|
156
|
+
const entries = typeof input.input === 'string' ? [input.input]
|
|
157
|
+
: Array.isArray(input.input) ? input.input : Object.values(input.input);
|
|
158
|
+
const entryIds = await Promise.all(entries.map(async (entry) => (await this.resolve(entry))?.id));
|
|
159
|
+
for (const [name, id] of state.codePackages) {
|
|
160
|
+
if (entryIds.includes(id)) {
|
|
161
|
+
this.error(`代码分包 ${name} 不能使用应用主入口 ${id}。`);
|
|
162
|
+
}
|
|
163
|
+
this.emitFile({ type: 'chunk', id, fileName: `subpackages/${name}/game.js`, preserveSignature: 'strict' });
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
resolveId(id) {
|
|
167
|
+
return id === virtualCodeLoaderId ? id : undefined;
|
|
168
|
+
},
|
|
169
|
+
load(id) {
|
|
170
|
+
if (id !== virtualCodeLoaderId)
|
|
171
|
+
return;
|
|
172
|
+
const api = state.platform === 'wechat' ? 'wx' : 'tt';
|
|
173
|
+
return [
|
|
174
|
+
"import { createMiniGameSubpackageLoader } from 'pixifact/internal/minigame';",
|
|
175
|
+
`export const loadCodePackage = createMiniGameSubpackageLoader(() => ${api}, ${JSON.stringify(state.platform)});`,
|
|
176
|
+
].join('\n');
|
|
177
|
+
},
|
|
178
|
+
async transform(code, id) {
|
|
179
|
+
if (state.platform === 'web' || state.codePackages.size === 0)
|
|
180
|
+
return;
|
|
181
|
+
// Native require() loads chunks. Vite's browser preload/error helper
|
|
182
|
+
// would otherwise access Event/window and mask module exceptions.
|
|
183
|
+
if (id === '\0vite/preload-helper.js') {
|
|
184
|
+
return 'export function __vitePreload(load) { return load(); }';
|
|
185
|
+
}
|
|
186
|
+
if (!code.includes('import'))
|
|
187
|
+
return;
|
|
188
|
+
const source = ts.createSourceFile(id, code, ts.ScriptTarget.Latest, true);
|
|
189
|
+
const specifiers = new Set();
|
|
190
|
+
function visit(node) {
|
|
191
|
+
if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
192
|
+
const argument = node.arguments[0];
|
|
193
|
+
if (argument && ts.isStringLiteralLike(argument))
|
|
194
|
+
specifiers.add(argument.text);
|
|
195
|
+
}
|
|
196
|
+
ts.forEachChild(node, visit);
|
|
197
|
+
}
|
|
198
|
+
visit(source);
|
|
199
|
+
const packagesById = new Map([...state.codePackages].map(([name, entry]) => [entry, name]));
|
|
200
|
+
const imports = new Map();
|
|
201
|
+
for (const specifier of specifiers) {
|
|
202
|
+
const resolved = await this.resolve(specifier, id);
|
|
203
|
+
const name = resolved && packagesById.get(resolved.id);
|
|
204
|
+
if (name)
|
|
205
|
+
imports.set(specifier, name);
|
|
206
|
+
}
|
|
207
|
+
if (imports.size === 0)
|
|
208
|
+
return;
|
|
209
|
+
const result = ts.transpileModule(code, {
|
|
210
|
+
fileName: id,
|
|
211
|
+
compilerOptions: {
|
|
212
|
+
target: ts.ScriptTarget.ESNext,
|
|
213
|
+
module: ts.ModuleKind.ESNext,
|
|
214
|
+
verbatimModuleSyntax: true,
|
|
215
|
+
sourceMap: true,
|
|
216
|
+
inlineSources: true,
|
|
217
|
+
},
|
|
218
|
+
transformers: { before: [(context) => (file) => {
|
|
219
|
+
const factory = context.factory;
|
|
220
|
+
const loader = factory.createUniqueName('__pixifactLoadCodePackage');
|
|
221
|
+
const transform = (node) => {
|
|
222
|
+
if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
223
|
+
const argument = node.arguments[0];
|
|
224
|
+
const name = argument && ts.isStringLiteralLike(argument) && imports.get(argument.text);
|
|
225
|
+
if (name) {
|
|
226
|
+
return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createCallExpression(loader, undefined, [factory.createStringLiteral(name)]), 'then'), undefined, [factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), node)]);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return ts.visitEachChild(node, transform, context);
|
|
230
|
+
};
|
|
231
|
+
const transformed = ts.visitEachChild(file, transform, context);
|
|
232
|
+
return factory.updateSourceFile(transformed, [
|
|
233
|
+
factory.createImportDeclaration(undefined, factory.createImportClause(false, undefined, factory.createNamedImports([
|
|
234
|
+
factory.createImportSpecifier(false, factory.createIdentifier('loadCodePackage'), loader),
|
|
235
|
+
])), factory.createStringLiteral(virtualCodeLoaderId)),
|
|
236
|
+
...transformed.statements,
|
|
237
|
+
]);
|
|
238
|
+
}] },
|
|
239
|
+
});
|
|
240
|
+
return { code: result.outputText, map: result.sourceMapText };
|
|
241
|
+
},
|
|
242
|
+
buildEnd(error) {
|
|
243
|
+
if (error || state.platform === 'web' || state.codePackages.size === 0)
|
|
244
|
+
return;
|
|
245
|
+
const packagesById = new Map([...state.codePackages].map(([name, id]) => [id, name]));
|
|
246
|
+
for (const root of this.getModuleIds()) {
|
|
247
|
+
if (!this.getModuleInfo(root)?.isEntry)
|
|
248
|
+
continue;
|
|
249
|
+
const owner = packagesById.get(root);
|
|
250
|
+
const visited = new Set();
|
|
251
|
+
const visit = (id) => {
|
|
252
|
+
if (visited.has(id))
|
|
253
|
+
return;
|
|
254
|
+
visited.add(id);
|
|
255
|
+
for (const dependency of this.getModuleInfo(id)?.importedIds ?? []) {
|
|
256
|
+
const name = packagesById.get(dependency);
|
|
257
|
+
if (name && name !== owner) {
|
|
258
|
+
this.error(`不能静态引用代码分包 ${name}(${id});请从主包使用 import() 加载分包入口。`);
|
|
259
|
+
}
|
|
260
|
+
visit(dependency);
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
visit(root);
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
generateBundle(_options, bundle) {
|
|
267
|
+
if (state.platform === 'web' || state.codePackages.size === 0)
|
|
268
|
+
return;
|
|
269
|
+
for (const chunk of Object.values(bundle)) {
|
|
270
|
+
if (chunk.type !== 'chunk')
|
|
271
|
+
continue;
|
|
272
|
+
const owner = chunk.fileName.match(/^subpackages\/([^/]+)\//)?.[1];
|
|
273
|
+
for (const dependency of [...chunk.imports, ...chunk.dynamicImports]) {
|
|
274
|
+
const name = dependency.match(/^subpackages\/([^/]+)\//)?.[1];
|
|
275
|
+
if (name && owner && name !== owner) {
|
|
276
|
+
this.error(`代码分包 ${owner} 不能引用分包 ${name};请由主包发起分包加载。`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
}
|
|
143
283
|
function miniGamePlugin(state) {
|
|
144
284
|
return {
|
|
145
285
|
name: 'pixifact-minigame',
|
|
@@ -154,9 +294,11 @@ function miniGamePlugin(state) {
|
|
|
154
294
|
const gameJson = JSON.parse(await readFile(path.join(configDir, 'game.json'), 'utf8'));
|
|
155
295
|
const projectConfig = JSON.parse(await readFile(path.join(configDir, 'project.config.json'), 'utf8'));
|
|
156
296
|
if (gameJson[descriptor.subpackageProperty] !== undefined) {
|
|
157
|
-
throw new Error(`platforms/${platform}/game.json
|
|
297
|
+
throw new Error(`platforms/${platform}/game.json 的 ${descriptor.subpackageProperty} 由 Pixifact 根据资源包和代码分包生成,不能在源模板中重复声明。`);
|
|
158
298
|
}
|
|
159
|
-
gameJson[descriptor.subpackageProperty] =
|
|
299
|
+
gameJson[descriptor.subpackageProperty] = [...new Set([
|
|
300
|
+
...localPacks(required(state.config)), ...state.codePackages.keys(),
|
|
301
|
+
])].map((name) => ({
|
|
160
302
|
name,
|
|
161
303
|
root: `subpackages/${name}`,
|
|
162
304
|
}));
|
|
@@ -212,8 +354,23 @@ async function configureState(state, options, config, env) {
|
|
|
212
354
|
state.env = loaded.env;
|
|
213
355
|
state.platform = loaded.platform;
|
|
214
356
|
state.config = parsePixifactProjectConfig(JSON.parse(await readFile(path.join(projectRoot, pixifactProjectConfigFileName), 'utf8')));
|
|
357
|
+
state.codePackages = new Map();
|
|
358
|
+
for (const [name, entry] of Object.entries(options.codePackages ?? {})) {
|
|
359
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(name)) {
|
|
360
|
+
throw new Error(`代码分包名称 ${name} 只能包含字母、数字、连字符和下划线,并以字母或数字开头。`);
|
|
361
|
+
}
|
|
362
|
+
const absolute = realpathSync(path.resolve(projectRoot, entry)).replaceAll(path.sep, '/');
|
|
363
|
+
const relative = path.relative(projectRoot, absolute);
|
|
364
|
+
if (path.isAbsolute(entry) || relative.startsWith(`..${path.sep}`) || relative === '..') {
|
|
365
|
+
throw new Error(`代码分包 ${name} 的入口必须位于项目内并使用项目相对路径。`);
|
|
366
|
+
}
|
|
367
|
+
if ([...state.codePackages.values()].includes(absolute)) {
|
|
368
|
+
throw new Error(`代码分包 ${name} 重复声明入口 ${entry}。`);
|
|
369
|
+
}
|
|
370
|
+
state.codePackages.set(name, absolute);
|
|
371
|
+
}
|
|
215
372
|
}
|
|
216
|
-
function miniGameBuildConfig(projectRoot, platform, config) {
|
|
373
|
+
function miniGameBuildConfig(projectRoot, platform, config, hasCodePackages) {
|
|
217
374
|
const descriptor = descriptors[platform];
|
|
218
375
|
return {
|
|
219
376
|
appType: 'custom',
|
|
@@ -227,6 +384,15 @@ function miniGameBuildConfig(projectRoot, platform, config) {
|
|
|
227
384
|
modulePreload: false,
|
|
228
385
|
rollupOptions: {
|
|
229
386
|
...config.build?.rollupOptions,
|
|
387
|
+
...(hasCodePackages ? {
|
|
388
|
+
preserveEntrySignatures: 'strict',
|
|
389
|
+
// Rolldown's cross-chunk constant inlining crashes when Scene
|
|
390
|
+
// registration and Pixi's async Application imports share CJS chunks.
|
|
391
|
+
optimization: {
|
|
392
|
+
...config.build?.rollupOptions?.optimization,
|
|
393
|
+
inlineConst: false,
|
|
394
|
+
},
|
|
395
|
+
} : {}),
|
|
230
396
|
input: config.build?.rollupOptions?.input ?? path.join(projectRoot, 'src/main.ts'),
|
|
231
397
|
output: {
|
|
232
398
|
...(Array.isArray(config.build?.rollupOptions?.output)
|
|
@@ -237,7 +403,12 @@ function miniGameBuildConfig(projectRoot, platform, config) {
|
|
|
237
403
|
`var navigator=globalThis.navigator||(globalThis.navigator={gpu:null,userAgent:${JSON.stringify(descriptor.userAgent)}});`,
|
|
238
404
|
].join('\n'),
|
|
239
405
|
entryFileNames: 'game.js',
|
|
240
|
-
format: 'iife',
|
|
406
|
+
format: hasCodePackages ? 'cjs' : 'iife',
|
|
407
|
+
...(hasCodePackages ? {
|
|
408
|
+
chunkFileNames: 'chunks/[name]-[hash].js',
|
|
409
|
+
codeSplitting: true,
|
|
410
|
+
dynamicImportInCjs: false,
|
|
411
|
+
} : {}),
|
|
241
412
|
},
|
|
242
413
|
},
|
|
243
414
|
target: descriptor.ecmascriptTarget,
|
|
@@ -39,12 +39,19 @@ export async function createMiniGameApplication(api, userAgent, fetchResource, c
|
|
|
39
39
|
const lifecycle = bindMiniGameLifecycle(api, app.ticker, {
|
|
40
40
|
onHide: () => input.cancelAll(),
|
|
41
41
|
});
|
|
42
|
+
const resize = (size) => {
|
|
43
|
+
runtime.width = size.windowWidth;
|
|
44
|
+
runtime.height = size.windowHeight;
|
|
45
|
+
app.renderer.resize(runtime.width, runtime.height);
|
|
46
|
+
};
|
|
47
|
+
api.onWindowResize?.(resize);
|
|
42
48
|
let destroyed = false;
|
|
43
49
|
const destroy = app.destroy.bind(app);
|
|
44
50
|
app.destroy = ((...args) => {
|
|
45
51
|
if (destroyed)
|
|
46
52
|
return;
|
|
47
53
|
destroyed = true;
|
|
54
|
+
api.offWindowResize?.(resize);
|
|
48
55
|
lifecycle.dispose();
|
|
49
56
|
input.dispose();
|
|
50
57
|
destroy(...args);
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
import type { MiniGameApi, MiniGameFetch } from './types.js';
|
|
2
|
+
export declare function decodeUtf8(buffer: ArrayBuffer): string;
|
|
3
|
+
export declare function encodeUtf8(value: string): ArrayBuffer;
|
|
2
4
|
export declare function fetchMiniGameResource(api: MiniGameApi, input: RequestInfo | URL, init: RequestInit | undefined, platformName: string): ReturnType<MiniGameFetch>;
|
|
@@ -7,7 +7,7 @@ class MiniGameResponseHeaders {
|
|
|
7
7
|
return this.values.get(name.toLowerCase()) ?? null;
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
|
-
function decodeUtf8(buffer) {
|
|
10
|
+
export function decodeUtf8(buffer) {
|
|
11
11
|
const bytes = new Uint8Array(buffer);
|
|
12
12
|
let output = '';
|
|
13
13
|
for (let index = 0; index < bytes.length;) {
|
|
@@ -34,7 +34,7 @@ function decodeUtf8(buffer) {
|
|
|
34
34
|
}
|
|
35
35
|
return output;
|
|
36
36
|
}
|
|
37
|
-
function encodeUtf8(value) {
|
|
37
|
+
export function encodeUtf8(value) {
|
|
38
38
|
const bytes = [];
|
|
39
39
|
for (const character of value) {
|
|
40
40
|
const codePoint = character.codePointAt(0);
|
|
@@ -85,19 +85,19 @@ function installEventTarget(target, nativeTypes = new Set()) {
|
|
|
85
85
|
return !event.defaultPrevented;
|
|
86
86
|
};
|
|
87
87
|
}
|
|
88
|
-
function installCanvasSurface(canvas,
|
|
88
|
+
function installCanvasSurface(canvas, viewport) {
|
|
89
89
|
installEventTarget(canvas, new Set(['webglcontextlost', 'webglcontextrestored']));
|
|
90
90
|
canvas.style ??= {};
|
|
91
91
|
Object.defineProperty(canvas, 'isConnected', { configurable: true, value: true });
|
|
92
92
|
Object.defineProperty(canvas, 'getBoundingClientRect', {
|
|
93
93
|
configurable: true,
|
|
94
94
|
value: () => ({
|
|
95
|
-
bottom: height,
|
|
96
|
-
height,
|
|
95
|
+
bottom: viewport.height,
|
|
96
|
+
height: viewport.height,
|
|
97
97
|
left: 0,
|
|
98
|
-
right: width,
|
|
98
|
+
right: viewport.width,
|
|
99
99
|
top: 0,
|
|
100
|
-
width,
|
|
100
|
+
width: viewport.width,
|
|
101
101
|
x: 0,
|
|
102
102
|
y: 0,
|
|
103
103
|
}),
|
|
@@ -176,8 +176,9 @@ export function createMiniGameRuntime(api, userAgent) {
|
|
|
176
176
|
const width = Math.max(1, Math.round(Number(info.windowWidth ?? info.screenWidth)));
|
|
177
177
|
const height = Math.max(1, Math.round(Number(info.windowHeight ?? info.screenHeight)));
|
|
178
178
|
const resolution = Math.min(2, Math.max(1, Number(info.pixelRatio ?? 1)));
|
|
179
|
-
|
|
179
|
+
const viewport = { width, height };
|
|
180
|
+
installCanvasSurface(canvas, viewport);
|
|
180
181
|
installGlobals(api, canvas, userAgent);
|
|
181
182
|
const { context, webGLVersion } = createContext(canvas);
|
|
182
|
-
return { canvas, context,
|
|
183
|
+
return Object.assign(viewport, { canvas, context, resolution, userAgent, webGLVersion });
|
|
183
184
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const subpackageStates = new WeakMap();
|
|
1
2
|
export function miniGameResourceSubpackage(input) {
|
|
2
3
|
const url = typeof input === 'string' ? input : (input.url ?? String(input));
|
|
3
4
|
if (/^[a-zA-Z][a-zA-Z\d+.-]*:/.test(url))
|
|
@@ -7,9 +8,14 @@ export function miniGameResourceSubpackage(input) {
|
|
|
7
8
|
}
|
|
8
9
|
export function createMiniGameSubpackageLoader(apiOrFactory, platformName) {
|
|
9
10
|
const getApi = typeof apiOrFactory === 'function' ? apiOrFactory : () => apiOrFactory;
|
|
10
|
-
const loaded = new Set();
|
|
11
|
-
const loading = new Map();
|
|
12
11
|
return function loadSubpackage(name) {
|
|
12
|
+
const api = getApi();
|
|
13
|
+
let state = subpackageStates.get(api);
|
|
14
|
+
if (!state) {
|
|
15
|
+
state = { loaded: new Set(), loading: new Map() };
|
|
16
|
+
subpackageStates.set(api, state);
|
|
17
|
+
}
|
|
18
|
+
const { loaded, loading } = state;
|
|
13
19
|
if (loaded.has(name)) {
|
|
14
20
|
return Promise.resolve();
|
|
15
21
|
}
|
|
@@ -18,7 +24,7 @@ export function createMiniGameSubpackageLoader(apiOrFactory, platformName) {
|
|
|
18
24
|
return existing;
|
|
19
25
|
}
|
|
20
26
|
const pending = new Promise((resolve, reject) => {
|
|
21
|
-
|
|
27
|
+
api.loadSubpackage({
|
|
22
28
|
name,
|
|
23
29
|
fail: (error) => reject(new Error(`Failed to load ${platformName} subpackage ${name}: ${error.errMsg}`)),
|
|
24
30
|
success: resolve,
|
|
@@ -96,6 +96,14 @@ export interface MiniGameApi {
|
|
|
96
96
|
offHide?(callback: () => void): void;
|
|
97
97
|
onShow(callback: () => void): void;
|
|
98
98
|
offShow?(callback: () => void): void;
|
|
99
|
+
onWindowResize?(callback: (size: {
|
|
100
|
+
windowWidth: number;
|
|
101
|
+
windowHeight: number;
|
|
102
|
+
}) => void): void;
|
|
103
|
+
offWindowResize?(callback: (size: {
|
|
104
|
+
windowWidth: number;
|
|
105
|
+
windowHeight: number;
|
|
106
|
+
}) => void): void;
|
|
99
107
|
onTouchStart(callback: (event: MiniGameTouchEvent) => void): void;
|
|
100
108
|
offTouchStart?(callback: (event: MiniGameTouchEvent) => void): void;
|
|
101
109
|
onTouchMove(callback: (event: MiniGameTouchEvent) => void): void;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { type Application } from 'pixi.js';
|
|
2
|
-
import { type RuntimeJsonValue, type RuntimeRequest } from './protocol.js';
|
|
2
|
+
import { type RuntimeJsonValue, type RuntimeRequest, type RuntimeScreenshotResult } from './protocol.js';
|
|
3
3
|
export interface RegisterPixiRuntimeOptions {
|
|
4
4
|
getState?: () => RuntimeJsonValue;
|
|
5
|
+
/** 为共享 Canvas 的渲染插件提供完整合成截图。 */
|
|
6
|
+
captureScreenshot?: () => Pick<RuntimeScreenshotResult, 'width' | 'height' | 'dataUrl'> | Promise<Pick<RuntimeScreenshotResult, 'width' | 'height' | 'dataUrl'>>;
|
|
5
7
|
}
|
|
6
8
|
export interface PixifactRuntimeHotContext {
|
|
7
9
|
on(event: string, listener: (data: unknown) => void): void;
|
|
@@ -299,6 +299,7 @@ export function createPixifactRuntimeClient(options) {
|
|
|
299
299
|
const logs = createRuntimeLogCapture(options.console, options.window, now);
|
|
300
300
|
let app;
|
|
301
301
|
let getState;
|
|
302
|
+
let captureComposite;
|
|
302
303
|
function descriptor() {
|
|
303
304
|
return {
|
|
304
305
|
runtimeId: options.runtimeId,
|
|
@@ -316,6 +317,8 @@ export function createPixifactRuntimeClient(options) {
|
|
|
316
317
|
return app;
|
|
317
318
|
}
|
|
318
319
|
async function captureScreenshot(currentApp) {
|
|
320
|
+
if (captureComposite)
|
|
321
|
+
return { ...await captureComposite(), runtimeId: options.runtimeId };
|
|
319
322
|
const width = Math.max(1, Math.floor(currentApp.screen.width));
|
|
320
323
|
const height = Math.max(1, Math.floor(currentApp.screen.height));
|
|
321
324
|
const target = RenderTexture.create({ width, height, resolution: 1 });
|
|
@@ -412,6 +415,7 @@ export function createPixifactRuntimeClient(options) {
|
|
|
412
415
|
throw new Error('This page already has a registered PixiJS Application.');
|
|
413
416
|
app = application;
|
|
414
417
|
getState = registerOptions.getState;
|
|
418
|
+
captureComposite = registerOptions.captureScreenshot;
|
|
415
419
|
announce();
|
|
416
420
|
},
|
|
417
421
|
handleRequest,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pixifact",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "Pixifact 2D UI 和轻量游戏 Scene 语义框架。",
|
|
5
5
|
"packageManager": "bun@1.3.13",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
},
|
|
79
79
|
"peerDependencies": {
|
|
80
80
|
"pixi.js": ">=8.3.0",
|
|
81
|
-
"vite": ">=
|
|
81
|
+
"vite": ">=8.0.0"
|
|
82
82
|
},
|
|
83
83
|
"dependencies": {
|
|
84
84
|
"typescript": "^5.3.3"
|