@caperjs/core 0.1.1 → 0.2.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/{config → build}/assetpack.mjs +103 -45
- package/build/defaults.mjs +157 -0
- package/build/index.mjs +96 -0
- package/build/internal/ast.mjs +255 -0
- package/build/internal/buildFlags.mjs +50 -0
- package/build/internal/discovery.mjs +402 -0
- package/build/internal/manifest.mjs +38 -0
- package/build/internal/schema.mjs +65 -0
- package/build/internal/util.mjs +30 -0
- package/build/internal/validate.mjs +186 -0
- package/build/plugins/assetTypes.mjs +460 -0
- package/build/plugins/caperConfig.mjs +489 -0
- package/build/plugins/devHelper.mjs +31 -0
- package/build/plugins/lists.mjs +286 -0
- package/build/plugins/pruneFallbacks.mjs +150 -0
- package/build/plugins/pwa.mjs +240 -0
- package/build/plugins/runtime.mjs +129 -0
- package/cli.mjs +9 -6
- package/extras/llms.txt +23 -17
- package/lib/{CaptionsPlugin-Dc8fIrjy.js → CaptionsPlugin-DKhbmJvH.js} +15 -15
- package/lib/{CaptionsPlugin-Dc8fIrjy.js.map → CaptionsPlugin-DKhbmJvH.js.map} +1 -1
- package/lib/{DataAdapter-BY2114sp.js → DataAdapter-BZFTkFQ4.js} +4 -4
- package/lib/{DataAdapter-BY2114sp.js.map → DataAdapter-BZFTkFQ4.js.map} +1 -1
- package/lib/{DebugRenderer-BJMLILqI.js → DebugRenderer-C7X-nlXE.js} +2 -2
- package/lib/{DebugRenderer-BJMLILqI.js.map → DebugRenderer-C7X-nlXE.js.map} +1 -1
- package/lib/{DevToolsPlugin-wQ5rOkn0.js → DevToolsPlugin-P-atJ-5U.js} +2 -2
- package/lib/{DevToolsPlugin-wQ5rOkn0.js.map → DevToolsPlugin-P-atJ-5U.js.map} +1 -1
- package/lib/{GSAPPlugin-DHhSZKv8.js → GSAPPlugin-CeH9275z.js} +2 -2
- package/lib/{GSAPPlugin-DHhSZKv8.js.map → GSAPPlugin-CeH9275z.js.map} +1 -1
- package/lib/{LayoutPlugin-B-DF9SYM.js → LayoutPlugin-iZ3k6RsK.js} +4 -4
- package/lib/{LayoutPlugin-B-DF9SYM.js.map → LayoutPlugin-iZ3k6RsK.js.map} +1 -1
- package/lib/{SpinePlugin-Dz_3dljz.js → SpinePlugin-BWiavCpO.js} +2 -2
- package/lib/{SpinePlugin-Dz_3dljz.js.map → SpinePlugin-BWiavCpO.js.map} +1 -1
- package/lib/{StatsPlugin-Cg82JMBD.js → StatsPlugin-CBpzDJL0.js} +4 -4
- package/lib/{StatsPlugin-Cg82JMBD.js.map → StatsPlugin-CBpzDJL0.js.map} +1 -1
- package/lib/{VoiceOverPlugin-BR3ImT8C.js → VoiceOverPlugin-YaiKJtJ6.js} +26 -26
- package/lib/{VoiceOverPlugin-BR3ImT8C.js.map → VoiceOverPlugin-YaiKJtJ6.js.map} +1 -1
- package/lib/caper.mjs +95 -95
- package/lib/caper.mjs.map +1 -1
- package/lib/core/Application.d.ts +1 -1
- package/lib/core/Application.d.ts.map +1 -1
- package/lib/core/interfaces/IApplicationOptions.d.ts +2 -1
- package/lib/core/interfaces/IApplicationOptions.d.ts.map +1 -1
- package/lib/core/interfaces/ICoreSignals.d.ts +5 -1
- package/lib/core/interfaces/ICoreSignals.d.ts.map +1 -1
- package/lib/display/Container.d.ts +1 -1
- package/lib/display/Container.d.ts.map +1 -1
- package/lib/display/ParticleContainer.d.ts +1 -1
- package/lib/display/ParticleContainer.d.ts.map +1 -1
- package/lib/plugins/Plugin.d.ts +1 -1
- package/lib/plugins/Plugin.d.ts.map +1 -1
- package/lib/plugins/defaults.d.ts.map +1 -1
- package/lib/plugins/gesture/GesturePlugin.d.ts +58 -0
- package/lib/plugins/gesture/GesturePlugin.d.ts.map +1 -0
- package/lib/plugins/gesture/gestureMath.d.ts +32 -0
- package/lib/plugins/gesture/gestureMath.d.ts.map +1 -0
- package/lib/plugins/gesture/gestureMath.test.d.ts +2 -0
- package/lib/plugins/gesture/gestureMath.test.d.ts.map +1 -0
- package/lib/plugins/gesture/index.d.ts +3 -0
- package/lib/plugins/gesture/index.d.ts.map +1 -0
- package/lib/plugins/gesture/types.d.ts +45 -0
- package/lib/plugins/gesture/types.d.ts.map +1 -0
- package/lib/plugins/index.d.ts +1 -0
- package/lib/plugins/index.d.ts.map +1 -1
- package/lib/plugins/input/Controls.d.ts +5 -2
- package/lib/plugins/input/Controls.d.ts.map +1 -1
- package/lib/plugins/input/InputPlugin.d.ts +9 -2
- package/lib/plugins/input/InputPlugin.d.ts.map +1 -1
- package/lib/plugins/input/interfaces.d.ts +5 -3
- package/lib/plugins/input/interfaces.d.ts.map +1 -1
- package/lib/plugins/input/touch/{TouchControls.d.ts → VirtualControls.d.ts} +5 -4
- package/lib/plugins/input/touch/VirtualControls.d.ts.map +1 -0
- package/lib/plugins/input/touch/index.d.ts +1 -1
- package/lib/plugins/input/touch/index.d.ts.map +1 -1
- package/lib/{registries-CYCaYKZI.js → registries-qQmLeN0b.js} +505 -354
- package/lib/registries-qQmLeN0b.js.map +1 -0
- package/lib/types/caper-app.d.ts +1 -0
- package/lib/utils/bind.test.d.ts +2 -0
- package/lib/utils/bind.test.d.ts.map +1 -0
- package/package.json +33 -33
- package/src/core/Application.ts +1 -1
- package/src/core/interfaces/IApplicationOptions.ts +2 -0
- package/src/core/interfaces/ICoreSignals.ts +9 -0
- package/src/display/Container.ts +1 -1
- package/src/display/ParticleContainer.ts +1 -1
- package/src/plugins/Plugin.ts +1 -1
- package/src/plugins/defaults.ts +6 -0
- package/src/plugins/gesture/GesturePlugin.ts +215 -0
- package/src/plugins/gesture/gestureMath.test.ts +75 -0
- package/src/plugins/gesture/gestureMath.ts +59 -0
- package/src/plugins/gesture/index.ts +2 -0
- package/src/plugins/gesture/types.ts +54 -0
- package/src/plugins/index.ts +1 -0
- package/src/plugins/input/Controls.ts +9 -3
- package/src/plugins/input/InputPlugin.ts +61 -14
- package/src/plugins/input/interfaces.ts +5 -3
- package/src/plugins/input/touch/{TouchControls.ts → VirtualControls.ts} +4 -1
- package/src/plugins/input/touch/index.ts +1 -1
- package/src/types/caper-app.d.ts +1 -0
- package/src/utils/bind.test.ts +99 -0
- package/src/version.ts +1 -1
- package/templates/app/default/package.template.json +8 -4
- package/templates/app/default/vite.config.ts +15 -0
- package/LICENSE +0 -22
- package/cli/vite.mjs +0 -55
- package/config/vite.mjs +0 -2464
- package/lib/plugins/input/touch/TouchControls.d.ts.map +0 -1
- package/lib/registries-CYCaYKZI.js.map +0 -1
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ This package is **not yet published to npm**. The recommended way to use it is t
|
|
|
18
18
|
- **Per-bundle asset types** generated from your AssetPack manifest
|
|
19
19
|
- **Plugin `requires: [...]`** declarations + topological sort at bootstrap
|
|
20
20
|
- **Build-time validation** for asset bundles, plugin IDs, dependency cycles, duplicate IDs
|
|
21
|
-
- **
|
|
21
|
+
- **A Vite preset** (`@caperjs/core/vite`) — `plugins: [caper()]`, then plain `vite` / `vite build`
|
|
22
22
|
- **`caper add` CLI** for scaffolding scenes, plugins, entities, popups
|
|
23
23
|
- **Signals** via `typed-signals`
|
|
24
24
|
- **Tested** — Vitest covers `Plugin`, `Store`, `Scene`, `SignalRegistry`
|
|
@@ -1,36 +1,8 @@
|
|
|
1
1
|
import { AssetPack, Logger } from '@assetpack/core';
|
|
2
2
|
import { pixiPipes } from '@assetpack/core/pixi';
|
|
3
|
-
import fs from 'node:fs';
|
|
4
3
|
import process from 'node:process';
|
|
5
4
|
import path from 'path';
|
|
6
5
|
|
|
7
|
-
// Check if user config exists (synchronously)
|
|
8
|
-
function hasUserAssetpackConfig() {
|
|
9
|
-
const configPath = path.join(process.cwd(), '.assetpack.mjs');
|
|
10
|
-
try {
|
|
11
|
-
return fs.existsSync(configPath);
|
|
12
|
-
} catch {
|
|
13
|
-
return false;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// Async function to load user config
|
|
18
|
-
async function loadUserAssetpackConfig() {
|
|
19
|
-
if (!hasUserAssetpackConfig()) {
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
const configPath = path.join(process.cwd(), '.assetpack.mjs');
|
|
25
|
-
const userConfig = await import(configPath);
|
|
26
|
-
Logger.info('Caper assetpack plugin:: Using user assetpack config from .assetpack.mjs');
|
|
27
|
-
return userConfig.default || userConfig;
|
|
28
|
-
} catch (error) {
|
|
29
|
-
Logger.error('Caper assetpack plugin:: Error loading user assetpack config:', error);
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
6
|
const cwd = process.cwd();
|
|
35
7
|
|
|
36
8
|
/**
|
|
@@ -67,7 +39,8 @@ const defaultPixiPipesConfig = {
|
|
|
67
39
|
// Retina-first: raw art is authored at 2x native game pixels. AssetPack
|
|
68
40
|
// treats the source as the largest resolution listed, so this emits @2x
|
|
69
41
|
// plus downscaled 1x and 0.5x. Apps whose art is authored at 1x override
|
|
70
|
-
// `resolutions`
|
|
42
|
+
// `resolutions` via `caper({ assets: { resolutions } })` (or tag individual
|
|
43
|
+
// assets {fix}).
|
|
71
44
|
resolutions: { high: 2, default: 1, low: 0.5 },
|
|
72
45
|
// Quality-first compression: the upstream webp default (quality 80, lossy
|
|
73
46
|
// alpha) rings on anti-aliased UI edges, and browsers load .webp over .png.
|
|
@@ -85,7 +58,27 @@ const defaultPixiPipesConfig = {
|
|
|
85
58
|
removeFileExtension: true,
|
|
86
59
|
texturePacker: { nameStyle: 'relative', removeFileExtension: true },
|
|
87
60
|
},
|
|
88
|
-
audio:
|
|
61
|
+
// AssetPack's audio defaults are a trap: the mp3 output has
|
|
62
|
+
// `recompress: false`, so an mp3 source is *copied* — a 320kbps master ships
|
|
63
|
+
// untouched, cover art included — while the ogg twin is re-encoded down to
|
|
64
|
+
// 32kbps mono. Since pixi's resolver prefers ogg, desktop ends up playing the
|
|
65
|
+
// worst copy of a file the project also ships at ten times the size. Both
|
|
66
|
+
// outputs get the same music-grade target instead, and `noVideo` drops the
|
|
67
|
+
// embedded cover art ffmpeg would otherwise copy into every output.
|
|
68
|
+
audio: {
|
|
69
|
+
outputs: [
|
|
70
|
+
{
|
|
71
|
+
formats: ['.mp3'],
|
|
72
|
+
recompress: true,
|
|
73
|
+
options: { audioBitrate: 128, audioChannels: 2, audioFrequency: 44100, noVideo: true },
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
formats: ['.ogg'],
|
|
77
|
+
recompress: true,
|
|
78
|
+
options: { audioBitrate: 128, audioChannels: 2, audioFrequency: 44100, noVideo: true },
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
},
|
|
89
82
|
webfont: {},
|
|
90
83
|
manifest: { trimExtensions: true, createShortcuts: true, output: defaultManifestUrl },
|
|
91
84
|
};
|
|
@@ -108,22 +101,84 @@ function devCompression(compression) {
|
|
|
108
101
|
return result;
|
|
109
102
|
}
|
|
110
103
|
|
|
111
|
-
|
|
112
|
-
|
|
104
|
+
const isPlainObject = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Keys whose value is one whole decision rather than a bag of options, so an
|
|
108
|
+
* override replaces it outright instead of merging into it.
|
|
109
|
+
*
|
|
110
|
+
* `resolutions` is the important one, and AssetPack's own `pixiPipes()` makes the
|
|
111
|
+
* same exception ("don't merge the resolutions, just overwrite them"): the object
|
|
112
|
+
* is a *set of tiers*, so merging `{ default: 1, low: 0.5 }` into caper's
|
|
113
|
+
* retina-first default would silently put `high: 2` back and render 1x art at
|
|
114
|
+
* half size.
|
|
115
|
+
*/
|
|
116
|
+
const REPLACE_WHOLE = new Set(['resolutions']);
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Deep-merge `overrides` over `base`: plain objects recurse, everything else
|
|
120
|
+
* (arrays, scalars, `false`) replaces. Deep so that overriding one knob keeps
|
|
121
|
+
* its siblings — `{ compression: { png: false } }` must not silently discard
|
|
122
|
+
* caper's webp settings. Arrays replace rather than merge because the arrays
|
|
123
|
+
* here are whole specifications (an audio `outputs` list), not sets to extend.
|
|
124
|
+
*/
|
|
125
|
+
function deepMerge(base, overrides) {
|
|
126
|
+
if (!isPlainObject(overrides)) return overrides === undefined ? base : overrides;
|
|
127
|
+
const out = { ...base };
|
|
128
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
129
|
+
const mergeable = isPlainObject(value) && isPlainObject(base?.[key]) && !REPLACE_WHOLE.has(key);
|
|
130
|
+
out[key] = mergeable ? deepMerge(base[key], value) : value;
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Whether this is a production build.
|
|
137
|
+
*
|
|
138
|
+
* Vite is the authority here, not `process.env.NODE_ENV`: nothing guarantees
|
|
139
|
+
* vite sets that variable, and depending on it silently shipped a kitchen-sink
|
|
140
|
+
* build with no cache-busting hashes and dev-effort compression. Callers pass
|
|
141
|
+
* `resolvedConfig.isProduction` from `configResolved`; the NODE_ENV read is only
|
|
142
|
+
* a fallback for direct callers outside a vite build.
|
|
143
|
+
*/
|
|
144
|
+
function resolveIsProduction(isProduction) {
|
|
145
|
+
return isProduction ?? process.env.NODE_ENV === 'production';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Caper's pixi-pipes defaults with a project's overrides merged in, plus the two
|
|
150
|
+
* environment-dependent adjustments: cache-busting in production, and cheaper
|
|
151
|
+
* compression effort in dev.
|
|
152
|
+
*
|
|
153
|
+
* Exported for tests — the merged pipes themselves are opaque, so this plain
|
|
154
|
+
* object is the only place the merge is observable.
|
|
155
|
+
*/
|
|
156
|
+
export function resolvePixiPipesConfig(overrides = {}, { cacheBust, isProduction } = {}) {
|
|
157
|
+
const config = deepMerge(defaultPixiPipesConfig, overrides);
|
|
158
|
+
const production = resolveIsProduction(isProduction);
|
|
113
159
|
|
|
114
160
|
if (cacheBust !== undefined) {
|
|
115
|
-
|
|
161
|
+
config.cacheBust = cacheBust;
|
|
116
162
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
// assetpackConfig runs after (configResolved / user .assetpack.mjs import).
|
|
120
|
-
if (pixiPipesConfig.cacheBust === undefined) {
|
|
121
|
-
pixiPipesConfig.cacheBust = process.env.NODE_ENV === 'production';
|
|
163
|
+
if (config.cacheBust === undefined) {
|
|
164
|
+
config.cacheBust = production;
|
|
122
165
|
}
|
|
123
|
-
if (
|
|
124
|
-
|
|
166
|
+
if (!production) {
|
|
167
|
+
config.compression = devCompression(config.compression);
|
|
168
|
+
}
|
|
169
|
+
return config;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export const assetpackConfig = (manifestUrl = defaultManifestUrl, pixiPipesConfig = {}, env = {}) => {
|
|
173
|
+
const resolved = resolvePixiPipesConfig(pixiPipesConfig, env);
|
|
174
|
+
|
|
175
|
+
// A custom manifest filename has to reach the manifest pipe too, or the file
|
|
176
|
+
// gets written as assets.json while everything else looks for the new name.
|
|
177
|
+
if (!pixiPipesConfig?.manifest?.output) {
|
|
178
|
+
resolved.manifest = { ...resolved.manifest, output: manifestUrl };
|
|
125
179
|
}
|
|
126
|
-
|
|
180
|
+
|
|
181
|
+
const pipes = pixiPipes({ ...resolved });
|
|
127
182
|
|
|
128
183
|
// Insert fontWeights before the webfont pipe so that the {weight} tag
|
|
129
184
|
// is converted to a weights[] array before webfont processes the asset.
|
|
@@ -140,17 +195,18 @@ export const assetpackConfig = (manifestUrl = defaultManifestUrl, pixiPipesConfi
|
|
|
140
195
|
|
|
141
196
|
export default assetpackConfig;
|
|
142
197
|
|
|
143
|
-
export function assetpackPlugin(manifestUrl = defaultManifestUrl, pixiPipesConfig =
|
|
198
|
+
export function assetpackPlugin(manifestUrl = defaultManifestUrl, pixiPipesConfig = {}) {
|
|
144
199
|
let mode;
|
|
145
200
|
let ap;
|
|
146
201
|
let apConfig;
|
|
147
202
|
let isBuilding = false;
|
|
148
203
|
let server;
|
|
149
204
|
|
|
205
|
+
let isProduction;
|
|
206
|
+
|
|
150
207
|
async function getConfig() {
|
|
151
208
|
if (!apConfig) {
|
|
152
|
-
|
|
153
|
-
apConfig = userConfig || assetpackConfig(manifestUrl, pixiPipesConfig);
|
|
209
|
+
apConfig = assetpackConfig(manifestUrl, pixiPipesConfig, { isProduction });
|
|
154
210
|
}
|
|
155
211
|
}
|
|
156
212
|
|
|
@@ -158,10 +214,12 @@ export function assetpackPlugin(manifestUrl = defaultManifestUrl, pixiPipesConfi
|
|
|
158
214
|
name: 'vite-plugin-assetpack',
|
|
159
215
|
async configResolved(resolvedConfig) {
|
|
160
216
|
mode = resolvedConfig.command;
|
|
217
|
+
// Captured before getConfig() below, which is what consumes it.
|
|
218
|
+
isProduction = resolvedConfig.isProduction;
|
|
161
219
|
if (!resolvedConfig.publicDir) return;
|
|
162
220
|
await getConfig();
|
|
163
221
|
if (apConfig.output) return;
|
|
164
|
-
const publicDir = resolvedConfig.publicDir.replace(
|
|
222
|
+
const publicDir = resolvedConfig.publicDir.replace(resolvedConfig.root, '');
|
|
165
223
|
const outputPath = path.join(publicDir, 'assets');
|
|
166
224
|
apConfig.output = path.isAbsolute(publicDir)
|
|
167
225
|
? path.join('.', outputPath).replace(/\\/g, '/')
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Vite settings caper contributes, expressed as *defaults* rather than
|
|
3
|
+
* overrides.
|
|
4
|
+
*
|
|
5
|
+
* `caperDefaults(userConfig, env)` returns a partial config containing only what
|
|
6
|
+
* the project left unset. That shape matters: Vite deep-merges a plugin's
|
|
7
|
+
* returned partial *over* the existing config, so returning values
|
|
8
|
+
* unconditionally would silently beat the project's own. Filling only the gaps
|
|
9
|
+
* means the project always wins, which is the whole point of the preset.
|
|
10
|
+
*
|
|
11
|
+
* Three merge rules, applied by `fillMissing`:
|
|
12
|
+
*
|
|
13
|
+
* - **Plain objects recurse.** A project that sets `server.port` keeps its port
|
|
14
|
+
* and still gets `host` and `open`.
|
|
15
|
+
* - **Arrays are always contributed.** Vite concatenates them, which is what we
|
|
16
|
+
* want: a project adding to `resolve.dedupe` must not silently drop pixi from
|
|
17
|
+
* it, or two copies of pixi split its global registries.
|
|
18
|
+
* - **Everything else fills only when absent**, including functions like
|
|
19
|
+
* `manualChunks`.
|
|
20
|
+
*/
|
|
21
|
+
import fs from 'node:fs';
|
|
22
|
+
import path from 'node:path';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* App identity for the `__CAPER_APP_*` defines and the PWA manifest skeleton.
|
|
26
|
+
*
|
|
27
|
+
* `npm_package_*` is only set when a package manager runs the script, so a bare
|
|
28
|
+
* `vite build` from a shell used to bake `undefined` into the bundle. Fall back
|
|
29
|
+
* to reading package.json, which is where the env vars came from anyway.
|
|
30
|
+
*/
|
|
31
|
+
export function readAppIdentity(root = process.cwd()) {
|
|
32
|
+
const fromEnv = {
|
|
33
|
+
name: process.env.npm_package_name,
|
|
34
|
+
version: process.env.npm_package_version,
|
|
35
|
+
description: process.env.npm_package_description,
|
|
36
|
+
};
|
|
37
|
+
if (fromEnv.name && fromEnv.version && fromEnv.description) return fromEnv;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
|
|
41
|
+
return {
|
|
42
|
+
name: fromEnv.name ?? pkg.name,
|
|
43
|
+
version: fromEnv.version ?? pkg.version,
|
|
44
|
+
description: fromEnv.description ?? pkg.description,
|
|
45
|
+
};
|
|
46
|
+
} catch {
|
|
47
|
+
return fromEnv;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const isPlainObject = (value) =>
|
|
52
|
+
value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The subset of `defaults` that `target` does not already specify. Returns
|
|
56
|
+
* `undefined` when there is nothing to contribute, so callers can omit the key
|
|
57
|
+
* entirely rather than merging an empty object.
|
|
58
|
+
*/
|
|
59
|
+
export function fillMissing(target, defaults) {
|
|
60
|
+
const out = {};
|
|
61
|
+
|
|
62
|
+
for (const [key, value] of Object.entries(defaults)) {
|
|
63
|
+
if (value === undefined) continue;
|
|
64
|
+
|
|
65
|
+
const current = target?.[key];
|
|
66
|
+
|
|
67
|
+
if (Array.isArray(value)) {
|
|
68
|
+
// Vite concatenates arrays on merge — always contribute.
|
|
69
|
+
out[key] = value;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (isPlainObject(value)) {
|
|
74
|
+
const nested = fillMissing(isPlainObject(current) ? current : undefined, value);
|
|
75
|
+
if (nested !== undefined) out[key] = nested;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (current === undefined) out[key] = value;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return Object.keys(out).length ? out : undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Caper's own values, before any gap-filling.
|
|
87
|
+
*
|
|
88
|
+
* `root` is the project root vite will use — taken from the project's own config
|
|
89
|
+
* when it sets one, so the `@` alias follows `vite --root somewhere/else`. It is
|
|
90
|
+
* resolved here rather than at module load, which is what a module-level
|
|
91
|
+
* `process.cwd()` would have baked in.
|
|
92
|
+
*/
|
|
93
|
+
export function caperDefaultValues(env, userConfig = {}) {
|
|
94
|
+
const isServe = env.command === 'serve';
|
|
95
|
+
const root = userConfig.root ? path.resolve(userConfig.root) : process.cwd();
|
|
96
|
+
const app = readAppIdentity(root);
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
cacheDir: '.cache',
|
|
100
|
+
logLevel: 'info',
|
|
101
|
+
publicDir: './public',
|
|
102
|
+
// Dev serves from root; builds emit relative URLs so a bundle can be hosted
|
|
103
|
+
// from a subdirectory.
|
|
104
|
+
base: isServe ? '/' : './',
|
|
105
|
+
server: {
|
|
106
|
+
port: 3000,
|
|
107
|
+
host: true,
|
|
108
|
+
open: true,
|
|
109
|
+
},
|
|
110
|
+
preview: {
|
|
111
|
+
host: true,
|
|
112
|
+
port: 8080,
|
|
113
|
+
},
|
|
114
|
+
build: {
|
|
115
|
+
sourcemap: env.mode === 'development',
|
|
116
|
+
rolldownOptions: {
|
|
117
|
+
external: ['caper-globals'],
|
|
118
|
+
output: {
|
|
119
|
+
// Rolldown requires manualChunks as a function (Rollup allowed an object).
|
|
120
|
+
manualChunks(id) {
|
|
121
|
+
if (id.includes('node_modules/gsap/')) return 'gsap';
|
|
122
|
+
},
|
|
123
|
+
chunkFileNames: 'assets/[name]-[hash].js',
|
|
124
|
+
entryFileNames: 'assets/[name]-[hash].js',
|
|
125
|
+
assetFileNames: 'assets/[name]-[hash][extname]',
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
resolve: {
|
|
130
|
+
alias: {
|
|
131
|
+
'@': path.resolve(root, './src'),
|
|
132
|
+
},
|
|
133
|
+
// Force a single instance of each singleton lib. pixi keeps global
|
|
134
|
+
// registries (extensions, TextureSource cache, Ticker.shared) and relies on
|
|
135
|
+
// instanceof; two copies (via linked/transitive installs) split its state.
|
|
136
|
+
dedupe: ['pixi.js', 'gsap', '@pixi/sound'],
|
|
137
|
+
},
|
|
138
|
+
// Don't prebundle @pixi/ui: esbuild inlines its own copy of pixi.js into the
|
|
139
|
+
// dep chunk, giving two pixi instances — every cross-boundary
|
|
140
|
+
// `instanceof Texture/Sprite` then fails. Served as source, its
|
|
141
|
+
// `import "pixi.js"` resolves to the same optimized pixi as the app. Its
|
|
142
|
+
// nested typed-signals dep (CJS) still needs prebundling for interop.
|
|
143
|
+
optimizeDeps: {
|
|
144
|
+
exclude: ['@pixi/ui'],
|
|
145
|
+
include: ['@pixi/ui > typed-signals'],
|
|
146
|
+
},
|
|
147
|
+
define: {
|
|
148
|
+
__CAPER_APP_NAME: JSON.stringify(app.name),
|
|
149
|
+
__CAPER_APP_VERSION: JSON.stringify(app.version),
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** The partial config caper contributes for this project and command. */
|
|
155
|
+
export function caperDefaults(userConfig, env) {
|
|
156
|
+
return fillMissing(userConfig, caperDefaultValues(env, userConfig)) ?? {};
|
|
157
|
+
}
|
package/build/index.mjs
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `caper()` — the Vite preset. This is caper's whole build-time surface:
|
|
3
|
+
*
|
|
4
|
+
* import { defineConfig } from 'vite';
|
|
5
|
+
* import { caper } from '@caperjs/core/vite';
|
|
6
|
+
*
|
|
7
|
+
* export default defineConfig({ plugins: [caper()] });
|
|
8
|
+
*
|
|
9
|
+
* and then plain `vite` / `vite build`. Caper contributes nothing except through
|
|
10
|
+
* Vite's own mechanisms, so plugin ordering, config merging and precedence are
|
|
11
|
+
* Vite's rules rather than caper's. The project's config is the only config.
|
|
12
|
+
*
|
|
13
|
+
* See `plan/vite-preset-rework.md` for why this replaced the old
|
|
14
|
+
* `defaultConfig` + `caper build` arrangement.
|
|
15
|
+
*/
|
|
16
|
+
import { viteStaticCopy } from 'vite-plugin-static-copy';
|
|
17
|
+
import wasm from 'vite-plugin-wasm';
|
|
18
|
+
import { assetpackPlugin } from './assetpack.mjs';
|
|
19
|
+
import { caperDefaults } from './defaults.mjs';
|
|
20
|
+
import { readCaperBuildFlags } from './internal/buildFlags.mjs';
|
|
21
|
+
import { assetTypesPlugin } from './plugins/assetTypes.mjs';
|
|
22
|
+
import { caperConfigPlugin } from './plugins/caperConfig.mjs';
|
|
23
|
+
import { caperDevHelperPlugin } from './plugins/devHelper.mjs';
|
|
24
|
+
import { entityListPlugin, pluginListPlugin, popupListPlugin, sceneListPlugin, uiListPlugin } from './plugins/lists.mjs';
|
|
25
|
+
import { pngFallbackPrunePlugin } from './plugins/pruneFallbacks.mjs';
|
|
26
|
+
import { caperPwaPlugins } from './plugins/pwa.mjs';
|
|
27
|
+
import { createCaperRuntimePlugin } from './plugins/runtime.mjs';
|
|
28
|
+
|
|
29
|
+
const buildFlags = readCaperBuildFlags();
|
|
30
|
+
|
|
31
|
+
function caperPluginList({ assets = {}, pwa } = {}) {
|
|
32
|
+
// `assets: false` opts out of the asset pipeline entirely — no assetpack run,
|
|
33
|
+
// no generated asset types. Replaces the old `noAssetpackConfig` export.
|
|
34
|
+
const { manifestUrl = 'assets.json', pngFallback = false, ...pipes } = assets === false ? {} : assets;
|
|
35
|
+
const assetPlugins =
|
|
36
|
+
assets === false
|
|
37
|
+
? []
|
|
38
|
+
: [
|
|
39
|
+
assetpackPlugin(manifestUrl, pipes),
|
|
40
|
+
assetTypesPlugin(manifestUrl),
|
|
41
|
+
// Production ships webp only unless the project asks for the fallback.
|
|
42
|
+
...(pngFallback ? [] : [pngFallbackPrunePlugin({ manifestUrl })]),
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
return [
|
|
46
|
+
...(buildFlags.useWasm ? [wasm()] : []),
|
|
47
|
+
createCaperRuntimePlugin({ pwa }),
|
|
48
|
+
viteStaticCopy({
|
|
49
|
+
// The captions plugin's bitmap font. `silent` matters: without it,
|
|
50
|
+
// vite-plugin-static-copy *throws* when the glob matches nothing, so any
|
|
51
|
+
// install layout that doesn't put caper's source at exactly this path
|
|
52
|
+
// fails the whole build over an optional font.
|
|
53
|
+
silent: true,
|
|
54
|
+
targets: [
|
|
55
|
+
{
|
|
56
|
+
src: './node_modules/@caperjs/core/src/plugins/captions/font/*.*',
|
|
57
|
+
dest: './assets/caper/font',
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
}),
|
|
61
|
+
pluginListPlugin(),
|
|
62
|
+
sceneListPlugin(),
|
|
63
|
+
popupListPlugin(),
|
|
64
|
+
entityListPlugin(),
|
|
65
|
+
uiListPlugin(),
|
|
66
|
+
...assetPlugins,
|
|
67
|
+
caperConfigPlugin(),
|
|
68
|
+
caperDevHelperPlugin(),
|
|
69
|
+
...(pwa ? caperPwaPlugins(pwa) : []),
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @typedef {object} CaperOptions
|
|
75
|
+
* @property {object|false} [assets] AssetPack pixi-pipes overrides, deep-merged
|
|
76
|
+
* over caper's defaults. `false` omits the asset plugins entirely. Set
|
|
77
|
+
* `pngFallback: true` to keep the png twins a production build otherwise prunes.
|
|
78
|
+
* @property {object} [pwa] vite-plugin-pwa options, merged over caper's PWA
|
|
79
|
+
* defaults. Absent means no service worker and no web manifest.
|
|
80
|
+
*/
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @param {CaperOptions} [options]
|
|
84
|
+
* @returns {import('vite').PluginOption[]}
|
|
85
|
+
*/
|
|
86
|
+
export function caper(options = {}) {
|
|
87
|
+
return [
|
|
88
|
+
{
|
|
89
|
+
name: 'caper:defaults',
|
|
90
|
+
config: (userConfig, env) => caperDefaults(userConfig, env),
|
|
91
|
+
},
|
|
92
|
+
...caperPluginList(options),
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export default caper;
|