@gtkx/config 1.0.0-rc.1 → 1.0.0-rc.2
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/dist/config-error.d.ts +16 -0
- package/dist/config-error.d.ts.map +1 -0
- package/dist/config-error.js +45 -0
- package/dist/config-error.js.map +1 -0
- package/dist/config.d.ts +51 -56
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +90 -42
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +1 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +1 -1
- package/dist/internal.js.map +1 -1
- package/dist/loader.d.ts +6 -5
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +10 -8
- package/dist/loader.js.map +1 -1
- package/dist/user-event-signals.d.ts +4 -0
- package/dist/user-event-signals.d.ts.map +1 -0
- package/dist/user-event-signals.js +54 -0
- package/dist/user-event-signals.js.map +1 -0
- package/dist/virtual.d.ts +8 -3
- package/dist/virtual.d.ts.map +1 -1
- package/dist/virtual.js +26 -6
- package/dist/virtual.js.map +1 -1
- package/dist/vite-plugin.d.ts.map +1 -1
- package/dist/vite-plugin.js +12 -13
- package/dist/vite-plugin.js.map +1 -1
- package/package.json +3 -9
- package/src/config-error.ts +67 -0
- package/src/config.ts +176 -84
- package/src/index.ts +0 -12
- package/src/internal.ts +1 -1
- package/src/loader.ts +22 -13
- package/src/user-event-signals.ts +57 -0
- package/src/virtual.ts +28 -5
- package/src/vite-plugin.ts +25 -11
- package/dist/element-props.d.ts +0 -180
- package/dist/element-props.d.ts.map +0 -1
- package/dist/element-props.js +0 -154
- package/dist/element-props.js.map +0 -1
- package/env.d.ts +0 -8
- package/src/element-props.ts +0 -248
package/src/config.ts
CHANGED
|
@@ -1,28 +1,11 @@
|
|
|
1
1
|
import { createDefineConfig, type DefineConfig } from "c12";
|
|
2
2
|
import { defu } from "defu";
|
|
3
|
+
import { resolve } from "node:path";
|
|
3
4
|
import { z } from "zod";
|
|
4
|
-
import { configError,
|
|
5
|
-
|
|
6
|
-
export const LIBRARIES_WILDCARD = "*";
|
|
7
|
-
|
|
8
|
-
export const GIR_LIBRARY_PATTERN: RegExp = /^[A-Za-z][A-Za-z0-9]*-\d+(?:\.\d+)*$/;
|
|
9
|
-
|
|
10
|
-
const APPLICATION_ID_PATTERN = /^[A-Za-z_][A-Za-z0-9_-]*(\.[A-Za-z_][A-Za-z0-9_-]*)+$/;
|
|
11
|
-
const APPLICATION_ID_MAX_LENGTH = 255;
|
|
12
|
-
|
|
13
|
-
export const isValidApplicationId = (applicationId: string): boolean => {
|
|
14
|
-
if (applicationId.length === 0 || applicationId.length > APPLICATION_ID_MAX_LENGTH) {
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
return APPLICATION_ID_PATTERN.test(applicationId);
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const COMPILATION_MODES = ["infer", "syntax", "annotation", "all"] as const;
|
|
21
|
-
|
|
22
|
-
const PANIC_THRESHOLDS = ["none", "critical_errors", "all_errors"] as const;
|
|
5
|
+
import { configError, isRecord, rawIssue } from "./config-error.js";
|
|
6
|
+
import { resolveUserEventSignals } from "./user-event-signals.js";
|
|
23
7
|
|
|
24
8
|
type ReactCompilerCompilationMode = (typeof COMPILATION_MODES)[number];
|
|
25
|
-
|
|
26
9
|
type ReactCompilerPanicThreshold = (typeof PANIC_THRESHOLDS)[number];
|
|
27
10
|
|
|
28
11
|
type ReactCompilerOptions = {
|
|
@@ -34,61 +17,72 @@ type ReactCompilerOptions = {
|
|
|
34
17
|
* React Compiler options resolved for the build, with the compilation target
|
|
35
18
|
* fixed to React 19.
|
|
36
19
|
*/
|
|
37
|
-
|
|
20
|
+
type ResolvedReactCompilerOptions = ReactCompilerOptions & {
|
|
38
21
|
target: "19";
|
|
39
22
|
};
|
|
40
23
|
|
|
41
|
-
|
|
24
|
+
/**
|
|
25
|
+
* User-facing configuration for a GTKX project, as authored in `gtkx.config.ts`:
|
|
26
|
+
* the GIR libraries to bind, extra `.gir` search paths, the GApplication id,
|
|
27
|
+
* a module of per-element configuration (lazy flags and custom behaviors),
|
|
28
|
+
* per-element component wrappers keyed by GLib type name, the React Compiler and
|
|
29
|
+
* codegen settings, and additional user event signals to suppress during React
|
|
30
|
+
* commits.
|
|
31
|
+
*/
|
|
32
|
+
type Config = z.infer<typeof configSchema>;
|
|
42
33
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Configuration reduced to the values needed at runtime: the GApplication
|
|
36
|
+
* identifier, the resolved React Compiler options (`null` when disabled), the
|
|
37
|
+
* user event signals suppressed while a React commit is in progress, and the
|
|
38
|
+
* module path holding per-element configuration (`null` when unset).
|
|
39
|
+
*/
|
|
40
|
+
type ResolvedConfig = {
|
|
41
|
+
applicationId: string;
|
|
42
|
+
reactCompiler: ResolvedReactCompilerOptions | null;
|
|
43
|
+
userEventSignals: Record<string, string[]>;
|
|
44
|
+
elements: string | null;
|
|
45
|
+
lazyElements: string[];
|
|
47
46
|
};
|
|
48
47
|
|
|
48
|
+
const LIBRARIES_WILDCARD = "*";
|
|
49
|
+
const GIR_LIBRARY_PATTERN = /^[A-Za-z][A-Za-z0-9]*-\d+(?:\.\d+)*$/;
|
|
50
|
+
const APPLICATION_ID_PATTERN = /^[A-Za-z_][A-Za-z0-9_-]*(\.[A-Za-z_][A-Za-z0-9_-]*)+$/;
|
|
51
|
+
const APPLICATION_ID_MAX_LENGTH = 255;
|
|
52
|
+
const COMPILATION_MODES = ["infer", "syntax", "annotation", "all"] as const;
|
|
53
|
+
const PANIC_THRESHOLDS = ["none", "critical_errors", "all_errors"] as const;
|
|
54
|
+
const REACT_COMPILER_TARGET = "19";
|
|
49
55
|
const COMPILATION_MODE_SET: Set<string> = new Set(COMPILATION_MODES);
|
|
50
|
-
|
|
51
56
|
const PANIC_THRESHOLD_SET: Set<string> = new Set(PANIC_THRESHOLDS);
|
|
52
57
|
|
|
53
58
|
const librariesSchema = z.custom<typeof LIBRARIES_WILDCARD | string[]>().check((ctx) => {
|
|
54
59
|
const value = ctx.value;
|
|
55
|
-
|
|
60
|
+
|
|
61
|
+
if (value === LIBRARIES_WILDCARD) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
56
65
|
if (!Array.isArray(value) || value.length === 0) {
|
|
57
66
|
ctx.issues.push(rawIssue(value, [], `must be "${LIBRARIES_WILDCARD}", a non-empty string array, or omitted`));
|
|
67
|
+
|
|
58
68
|
return;
|
|
59
69
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
rawIssue(
|
|
65
|
-
value,
|
|
66
|
-
[index],
|
|
67
|
-
`to generate every library, set \`libraries: "${LIBRARIES_WILDCARD}"\` as a bare string, not an array entry`,
|
|
68
|
-
true,
|
|
69
|
-
),
|
|
70
|
-
);
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
ctx.issues.push(
|
|
74
|
-
rawIssue(
|
|
75
|
-
value,
|
|
76
|
-
[index],
|
|
77
|
-
`invalid library identifier "${String(entry)}", must be of the form "Name-Version" (e.g. "Gtk-4.0")`,
|
|
78
|
-
true,
|
|
79
|
-
),
|
|
80
|
-
);
|
|
81
|
-
});
|
|
70
|
+
|
|
71
|
+
for (const [index, entry] of value.entries()) {
|
|
72
|
+
ctx.issues.push(...libraryEntryIssues(value, index, entry));
|
|
73
|
+
}
|
|
82
74
|
});
|
|
83
75
|
|
|
84
76
|
const applicationIdSchema = z.custom<string>().check((ctx) => {
|
|
85
77
|
const value = ctx.value;
|
|
78
|
+
|
|
86
79
|
if (typeof value !== "string" || !isValidApplicationId(value)) {
|
|
87
80
|
ctx.issues.push(
|
|
88
81
|
rawIssue(
|
|
89
82
|
value,
|
|
90
83
|
[],
|
|
91
|
-
`invalid \`applicationId\` "${
|
|
84
|
+
`invalid \`applicationId\` "${value}", must satisfy g_application_id_is_valid ` +
|
|
85
|
+
'(e.g. "org.example.MyApp")',
|
|
92
86
|
true,
|
|
93
87
|
),
|
|
94
88
|
);
|
|
@@ -97,85 +91,183 @@ const applicationIdSchema = z.custom<string>().check((ctx) => {
|
|
|
97
91
|
|
|
98
92
|
const reactCompilerSchema = z.custom<boolean | ReactCompilerOptions>().check((ctx) => {
|
|
99
93
|
const value = ctx.value;
|
|
100
|
-
|
|
94
|
+
|
|
95
|
+
if (typeof value === "boolean") {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
101
99
|
if (!isRecord(value)) {
|
|
102
100
|
ctx.issues.push(rawIssue(value, [], "must be a boolean or an options object"));
|
|
101
|
+
|
|
103
102
|
return;
|
|
104
103
|
}
|
|
104
|
+
|
|
105
105
|
const compilationMode = value.compilationMode;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
!(typeof compilationMode === "string" && COMPILATION_MODE_SET.has(compilationMode))
|
|
109
|
-
) {
|
|
106
|
+
|
|
107
|
+
if (!isValidReactCompilerOption(compilationMode, COMPILATION_MODE_SET)) {
|
|
110
108
|
ctx.issues.push(
|
|
111
109
|
rawIssue(
|
|
112
110
|
value,
|
|
113
111
|
[],
|
|
114
|
-
`invalid \`reactCompiler.compilationMode\` "${String(compilationMode)}",
|
|
112
|
+
`invalid \`reactCompiler.compilationMode\` "${String(compilationMode)}", ` +
|
|
113
|
+
`must be one of ${COMPILATION_MODES.join(", ")}`,
|
|
115
114
|
true,
|
|
116
115
|
),
|
|
117
116
|
);
|
|
118
117
|
}
|
|
118
|
+
|
|
119
119
|
const panicThreshold = value.panicThreshold;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
!(typeof panicThreshold === "string" && PANIC_THRESHOLD_SET.has(panicThreshold))
|
|
123
|
-
) {
|
|
120
|
+
|
|
121
|
+
if (!isValidReactCompilerOption(panicThreshold, PANIC_THRESHOLD_SET)) {
|
|
124
122
|
ctx.issues.push(
|
|
125
123
|
rawIssue(
|
|
126
124
|
value,
|
|
127
125
|
[],
|
|
128
|
-
`invalid \`reactCompiler.panicThreshold\` "${String(panicThreshold)}",
|
|
126
|
+
`invalid \`reactCompiler.panicThreshold\` "${String(panicThreshold)}", ` +
|
|
127
|
+
`must be one of ${PANIC_THRESHOLDS.join(", ")}`,
|
|
129
128
|
true,
|
|
130
129
|
),
|
|
131
130
|
);
|
|
132
131
|
}
|
|
133
132
|
});
|
|
134
133
|
|
|
134
|
+
const userEventSignalsSchema = z.record(
|
|
135
|
+
z.string(),
|
|
136
|
+
z.array(
|
|
137
|
+
z.string({ error: "must be a non-empty signal name" }).min(1, { error: "must be a non-empty signal name" }),
|
|
138
|
+
{
|
|
139
|
+
error: "must be an array of signal names",
|
|
140
|
+
},
|
|
141
|
+
),
|
|
142
|
+
{ error: "must be a record of GLib type names to signal name arrays" },
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const moduleExportSchema = z.object(
|
|
146
|
+
{
|
|
147
|
+
module: z.string({ error: "must be a module specifier" }).min(1, { error: "must be a module specifier" }),
|
|
148
|
+
export: z.string({ error: "must be an export name" }).min(1, { error: "must be an export name" }),
|
|
149
|
+
},
|
|
150
|
+
{ error: "must be a { module, export } object" },
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const elementConfigSchema = z.object({
|
|
154
|
+
component: moduleExportSchema.optional(),
|
|
155
|
+
props: moduleExportSchema.optional(),
|
|
156
|
+
lazy: z.boolean({ error: "must be a boolean" }).optional(),
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const elementsSchema = z.object({
|
|
160
|
+
behaviors: z
|
|
161
|
+
.string({ error: "must be a path to a module exporting element behaviors" })
|
|
162
|
+
.min(1, { error: "must be a path to a module exporting element behaviors" })
|
|
163
|
+
.optional(),
|
|
164
|
+
config: z.record(z.string(), elementConfigSchema).optional(),
|
|
165
|
+
});
|
|
166
|
+
|
|
135
167
|
const configSchema = z.object({
|
|
136
168
|
libraries: librariesSchema.optional(),
|
|
137
169
|
girPath: z.array(z.string(), { error: "must be an array of strings if provided" }).optional(),
|
|
138
170
|
applicationId: applicationIdSchema,
|
|
139
|
-
elementProps: elementPropsSchema.optional(),
|
|
140
171
|
reactCompiler: reactCompilerSchema.optional(),
|
|
141
172
|
codegen: z.boolean({ error: "must be a boolean" }).optional(),
|
|
173
|
+
userEventSignals: userEventSignalsSchema.optional(),
|
|
174
|
+
elements: elementsSchema.optional(),
|
|
142
175
|
});
|
|
143
176
|
|
|
144
177
|
/**
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
* custom element prop mappings, and the React Compiler and codegen settings.
|
|
178
|
+
* Identity helper that returns the given configuration typed as {@link Config},
|
|
179
|
+
* enabling editor autocompletion and type checking in `gtkx.config.ts`.
|
|
148
180
|
*/
|
|
149
|
-
|
|
181
|
+
const defineConfig: DefineConfig<Config> = createDefineConfig<Config>();
|
|
150
182
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
183
|
+
const libraryEntryIssues = (value: unknown[], index: number, entry: unknown): ReturnType<typeof rawIssue>[] => {
|
|
184
|
+
if (typeof entry === "string" && GIR_LIBRARY_PATTERN.test(entry)) {
|
|
185
|
+
return [];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (entry === LIBRARIES_WILDCARD) {
|
|
189
|
+
const message =
|
|
190
|
+
`to generate every library, set \`libraries: "${LIBRARIES_WILDCARD}"\` as a bare string, ` +
|
|
191
|
+
"not an array entry";
|
|
192
|
+
|
|
193
|
+
return [rawIssue(value, [index], message, true)];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const message =
|
|
197
|
+
`invalid library identifier "${String(entry)}", must be of the form "Name-Version" ` +
|
|
198
|
+
'(e.g. "Gtk-4.0")';
|
|
199
|
+
|
|
200
|
+
return [rawIssue(value, [index], message, true)];
|
|
154
201
|
};
|
|
155
202
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
203
|
+
const isValidApplicationId = (applicationId: string): boolean => {
|
|
204
|
+
if (applicationId.length === 0 || applicationId.length > APPLICATION_ID_MAX_LENGTH) {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return APPLICATION_ID_PATTERN.test(applicationId);
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const resolveReactCompilerOptions = (setting: Config["reactCompiler"]): ResolvedReactCompilerOptions | null => {
|
|
212
|
+
if (setting === false) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const overrides = setting === undefined || setting === true ? {} : setting;
|
|
217
|
+
|
|
218
|
+
return { ...overrides, target: REACT_COMPILER_TARGET };
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const isValidReactCompilerOption = (value: unknown, allowed: Set<string>): boolean =>
|
|
222
|
+
value === undefined || (typeof value === "string" && allowed.has(value));
|
|
223
|
+
|
|
224
|
+
const validateConfig = (config: Config): void => {
|
|
225
|
+
const result = configSchema.safeParse(config);
|
|
226
|
+
|
|
227
|
+
if (!result.success) {
|
|
228
|
+
throw configError(result.error);
|
|
229
|
+
}
|
|
230
|
+
};
|
|
161
231
|
|
|
162
232
|
/**
|
|
163
233
|
* Deep-merges two configurations, with `override` taking precedence over `base`.
|
|
164
234
|
* @param base The lower-priority configuration.
|
|
165
235
|
* @param override The higher-priority configuration whose values win on conflict.
|
|
166
236
|
*/
|
|
167
|
-
|
|
237
|
+
const mergeConfig = (base: Config, override: Config): Config => defu(override, base);
|
|
168
238
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
reactCompiler: ResolvedReactCompilerOptions | null;
|
|
239
|
+
const resolveElementsModule = (behaviors: string | undefined, root: string | undefined): string | null => {
|
|
240
|
+
if (behaviors === undefined) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return root === undefined ? behaviors : resolve(root, behaviors);
|
|
176
245
|
};
|
|
177
246
|
|
|
178
|
-
|
|
247
|
+
const resolveLazyElements = (elements: Config["elements"]): string[] =>
|
|
248
|
+
Object.entries(elements?.config ?? {})
|
|
249
|
+
.filter(([, entry]) => entry.lazy === true)
|
|
250
|
+
.map(([type]) => type);
|
|
251
|
+
|
|
252
|
+
const resolveConfig = (config: Config, root?: string): ResolvedConfig => ({
|
|
179
253
|
applicationId: config.applicationId,
|
|
180
254
|
reactCompiler: resolveReactCompilerOptions(config.reactCompiler),
|
|
255
|
+
userEventSignals: resolveUserEventSignals(config.userEventSignals),
|
|
256
|
+
elements: resolveElementsModule(config.elements?.behaviors, root),
|
|
257
|
+
lazyElements: resolveLazyElements(config.elements),
|
|
181
258
|
});
|
|
259
|
+
|
|
260
|
+
export {
|
|
261
|
+
LIBRARIES_WILDCARD,
|
|
262
|
+
GIR_LIBRARY_PATTERN,
|
|
263
|
+
defineConfig,
|
|
264
|
+
isValidApplicationId,
|
|
265
|
+
resolveReactCompilerOptions,
|
|
266
|
+
validateConfig,
|
|
267
|
+
mergeConfig,
|
|
268
|
+
resolveLazyElements,
|
|
269
|
+
resolveConfig,
|
|
270
|
+
type ResolvedReactCompilerOptions,
|
|
271
|
+
type Config,
|
|
272
|
+
type ResolvedConfig,
|
|
273
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -5,16 +5,4 @@ export {
|
|
|
5
5
|
type ResolvedConfig,
|
|
6
6
|
type ResolvedReactCompilerOptions,
|
|
7
7
|
} from "./config.js";
|
|
8
|
-
export type {
|
|
9
|
-
AppliedProp,
|
|
10
|
-
Arg,
|
|
11
|
-
ArgRef,
|
|
12
|
-
Call,
|
|
13
|
-
ContainerProp,
|
|
14
|
-
ControlledTextProp,
|
|
15
|
-
ElementProp,
|
|
16
|
-
LazyProp,
|
|
17
|
-
ListProp,
|
|
18
|
-
ValueProp,
|
|
19
|
-
} from "./element-props.js";
|
|
20
8
|
export { type LoadedConfig, loadConfig } from "./loader.js";
|
package/src/internal.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { GIR_LIBRARY_PATTERN, isValidApplicationId, LIBRARIES_WILDCARD } from "./config.js";
|
|
1
|
+
export { GIR_LIBRARY_PATTERN, isValidApplicationId, LIBRARIES_WILDCARD, resolveLazyElements } from "./config.js";
|
|
2
2
|
export { type ConfigLoader, createConfigLoader } from "./loader.js";
|
package/src/loader.ts
CHANGED
|
@@ -1,30 +1,32 @@
|
|
|
1
|
+
import { loadConfig as loadConfigFile } from "c12";
|
|
1
2
|
import { existsSync } from "node:fs";
|
|
2
3
|
import { resolve } from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
import { type Config, type ResolvedConfig, resolveConfig, validateConfig } from "./config.js";
|
|
4
|
+
import { type Config, resolveConfig, type ResolvedConfig, validateConfig } from "./config.js";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Result of loading a `gtkx.config.ts` file: the parsed configuration, the
|
|
8
8
|
* resolved config file path (`undefined` when none was found), and the project
|
|
9
9
|
* root it was loaded from.
|
|
10
10
|
*/
|
|
11
|
-
|
|
11
|
+
type LoadedConfig = {
|
|
12
12
|
config: Config;
|
|
13
13
|
configFile: string | undefined;
|
|
14
14
|
root: string;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
type LoadConfigOptions = {
|
|
18
18
|
mode?: string | undefined;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
type ConfigLoader = (cwd: string) => Promise<ResolvedConfig>;
|
|
22
|
+
|
|
21
23
|
/**
|
|
22
24
|
* Loads and validates the `gtkx.config.ts` file for a project, returning the
|
|
23
25
|
* parsed configuration together with the config file path and project root.
|
|
24
26
|
* @param cwd Directory from which to search for the configuration file.
|
|
25
27
|
* @param options Loading options, such as the environment mode.
|
|
26
28
|
*/
|
|
27
|
-
|
|
29
|
+
const loadConfig = async (cwd: string, options: LoadConfigOptions = {}): Promise<LoadedConfig> => {
|
|
28
30
|
const result = await loadConfigFile<Config>({
|
|
29
31
|
name: "gtkx",
|
|
30
32
|
cwd,
|
|
@@ -32,37 +34,44 @@ export const loadConfig = async (cwd: string, options: LoadConfigOptions = {}):
|
|
|
32
34
|
globalRc: false,
|
|
33
35
|
packageJson: false,
|
|
34
36
|
context: { mode: options.mode },
|
|
35
|
-
...(options.mode !== undefined
|
|
37
|
+
...((options.mode !== undefined) && { envName: options.mode }),
|
|
36
38
|
});
|
|
37
39
|
|
|
38
40
|
const config = result.config;
|
|
39
|
-
const
|
|
41
|
+
const isFound = result.configFile !== undefined && existsSync(resolve(cwd, result.configFile));
|
|
40
42
|
|
|
41
|
-
if (
|
|
43
|
+
if (isFound) {
|
|
44
|
+
validateConfig(config);
|
|
45
|
+
}
|
|
42
46
|
|
|
43
47
|
return {
|
|
44
48
|
config,
|
|
45
|
-
configFile:
|
|
49
|
+
configFile: isFound ? result.configFile : undefined,
|
|
46
50
|
root: result.cwd ?? cwd,
|
|
47
51
|
};
|
|
48
52
|
};
|
|
49
53
|
|
|
50
|
-
|
|
54
|
+
const createConfigLoader = (options: LoadConfigOptions = {}): ConfigLoader => {
|
|
55
|
+
const cache: Map<string, Promise<ResolvedConfig>> = new Map();
|
|
51
56
|
|
|
52
|
-
export const createConfigLoader = (options: LoadConfigOptions = {}): ConfigLoader => {
|
|
53
|
-
const cache = new Map<string, Promise<ResolvedConfig>>();
|
|
54
57
|
const loadResolved = async (root: string): Promise<ResolvedConfig> => {
|
|
55
58
|
const { config } = await loadConfig(root, options);
|
|
56
59
|
validateConfig(config);
|
|
57
|
-
|
|
60
|
+
|
|
61
|
+
return resolveConfig(config, root);
|
|
58
62
|
};
|
|
63
|
+
|
|
59
64
|
return (cwd: string): Promise<ResolvedConfig> => {
|
|
60
65
|
const root = resolve(cwd);
|
|
61
66
|
let pending = cache.get(root);
|
|
67
|
+
|
|
62
68
|
if (!pending) {
|
|
63
69
|
pending = loadResolved(root);
|
|
64
70
|
cache.set(root, pending);
|
|
65
71
|
}
|
|
72
|
+
|
|
66
73
|
return pending;
|
|
67
74
|
};
|
|
68
75
|
};
|
|
76
|
+
|
|
77
|
+
export { loadConfig, createConfigLoader, type LoadedConfig, type LoadConfigOptions, type ConfigLoader };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const DEFAULT_USER_EVENT_SIGNALS: Record<string, string[]> = {
|
|
2
|
+
AdwAlertDialog: ["response"],
|
|
3
|
+
AdwCarousel: ["page-changed"],
|
|
4
|
+
AdwDialog: ["closed"],
|
|
5
|
+
AdwNavigationView: ["pushed"],
|
|
6
|
+
AdwTabView: ["close-page", "page-attached", "page-detached", "page-reordered"],
|
|
7
|
+
GObject: ["notify"],
|
|
8
|
+
GtkAdjustment: ["changed", "value-changed"],
|
|
9
|
+
GtkAppChooserButton: ["changed"],
|
|
10
|
+
GtkAppChooserWidget: ["application-selected"],
|
|
11
|
+
GtkCalendar: ["day-selected"],
|
|
12
|
+
GtkCheckButton: ["toggled"],
|
|
13
|
+
GtkComboBox: ["changed"],
|
|
14
|
+
GtkEditable: ["changed", "delete-text", "insert-text"],
|
|
15
|
+
GtkEntryBuffer: ["deleted-text", "inserted-text"],
|
|
16
|
+
GtkFlowBox: ["selected-children-changed"],
|
|
17
|
+
GtkIconView: ["selection-changed"],
|
|
18
|
+
GtkListBox: ["row-selected", "selected-rows-changed"],
|
|
19
|
+
GtkNotebook: ["page-added", "page-removed", "page-reordered", "switch-page"],
|
|
20
|
+
GtkPopover: ["closed"],
|
|
21
|
+
GtkRange: ["value-changed"],
|
|
22
|
+
GtkScaleButton: ["value-changed"],
|
|
23
|
+
GtkScrolledWindow: ["edge-reached"],
|
|
24
|
+
GtkSearchEntry: ["search-changed"],
|
|
25
|
+
GtkSelectionModel: ["selection-changed"],
|
|
26
|
+
GtkSpinButton: ["value-changed"],
|
|
27
|
+
GtkSwitch: ["state-set"],
|
|
28
|
+
GtkTextBuffer: [
|
|
29
|
+
"apply-tag",
|
|
30
|
+
"changed",
|
|
31
|
+
"delete-range",
|
|
32
|
+
"insert-child-anchor",
|
|
33
|
+
"insert-paintable",
|
|
34
|
+
"insert-text",
|
|
35
|
+
"mark-deleted",
|
|
36
|
+
"mark-set",
|
|
37
|
+
"modified-changed",
|
|
38
|
+
"remove-tag",
|
|
39
|
+
],
|
|
40
|
+
GtkToggleButton: ["toggled"],
|
|
41
|
+
GtkTreeSelection: ["changed"],
|
|
42
|
+
GtkTreeView: ["columns-changed", "cursor-changed", "row-collapsed", "row-expanded"],
|
|
43
|
+
GtkWidget: ["state-flags-changed"],
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const resolveUserEventSignals = (overrides: Record<string, string[]> | undefined): Record<string, string[]> => {
|
|
47
|
+
const resolved: Record<string, string[]> = { ...DEFAULT_USER_EVENT_SIGNALS };
|
|
48
|
+
const overrideEntries = Object.entries(overrides ?? {});
|
|
49
|
+
|
|
50
|
+
for (const [typeName, signals] of overrideEntries) {
|
|
51
|
+
resolved[typeName] = [...new Set([...(resolved[typeName] ?? []), ...signals])];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return resolved;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export { DEFAULT_USER_EVENT_SIGNALS, resolveUserEventSignals };
|
package/src/virtual.ts
CHANGED
|
@@ -1,13 +1,36 @@
|
|
|
1
1
|
import type { ResolvedConfig } from "./config.js";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
const GTKX_CONFIG_VIRTUAL_ID = "virtual:gtkx-config";
|
|
4
|
+
const RESOLVED_GTKX_CONFIG_VIRTUAL_ID = `\0${GTKX_CONFIG_VIRTUAL_ID}`;
|
|
5
|
+
const METADATA_SPECIFIER = "@gtkx/jsx/metadata";
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
const lazyElementConfig = (lazyElements: string[]): Record<string, { lazy: true }> =>
|
|
8
|
+
Object.fromEntries(lazyElements.map((type) => [type, { lazy: true }]));
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
/**
|
|
11
|
+
* The runtime element config is the app's static per-element config (currently its lazy flags) merged
|
|
12
|
+
* with its behaviors module, so a single `elements` map is registered.
|
|
13
|
+
*/
|
|
14
|
+
const renderConfigModule = (config: ResolvedConfig): string => {
|
|
15
|
+
const lazyJson = JSON.stringify(lazyElementConfig(config.lazyElements));
|
|
8
16
|
|
|
9
|
-
|
|
10
|
-
|
|
17
|
+
const behaviorImports =
|
|
18
|
+
config.elements === null
|
|
19
|
+
? []
|
|
20
|
+
: [
|
|
21
|
+
"import { mergeElementConfigs } from \"@gtkx/react/config\";",
|
|
22
|
+
`import __elementBehaviors from ${JSON.stringify(config.elements)};`,
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
return [
|
|
26
|
+
...behaviorImports,
|
|
11
27
|
`export * from ${JSON.stringify(METADATA_SPECIFIER)};`,
|
|
12
28
|
`export const applicationId = ${JSON.stringify(config.applicationId)};`,
|
|
29
|
+
`export const userEventSignals = ${JSON.stringify(config.userEventSignals)};`,
|
|
30
|
+
config.elements === null
|
|
31
|
+
? `export const elements = ${lazyJson};`
|
|
32
|
+
: `export const elements = mergeElementConfigs(__elementBehaviors, ${lazyJson});`,
|
|
13
33
|
].join("\n");
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export { GTKX_CONFIG_VIRTUAL_ID, RESOLVED_GTKX_CONFIG_VIRTUAL_ID, renderConfigModule };
|
package/src/vite-plugin.ts
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
import type { Plugin, UserConfig } from "vite";
|
|
2
2
|
import { type ConfigLoader, createConfigLoader } from "./loader.js";
|
|
3
|
-
import { GTKX_CONFIG_VIRTUAL_ID,
|
|
3
|
+
import { GTKX_CONFIG_VIRTUAL_ID, renderConfigModule, RESOLVED_GTKX_CONFIG_VIRTUAL_ID } from "./virtual.js";
|
|
4
|
+
|
|
5
|
+
type PluginState = {
|
|
6
|
+
root: string | undefined;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const resolveVirtualId = (id: string): string | null =>
|
|
10
|
+
id === GTKX_CONFIG_VIRTUAL_ID ? RESOLVED_GTKX_CONFIG_VIRTUAL_ID : null;
|
|
11
|
+
|
|
12
|
+
const loadVirtualModule = async (
|
|
13
|
+
id: string,
|
|
14
|
+
loadConfig: ConfigLoader,
|
|
15
|
+
state: PluginState,
|
|
16
|
+
): Promise<string | undefined> => {
|
|
17
|
+
if (id !== RESOLVED_GTKX_CONFIG_VIRTUAL_ID) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return renderConfigModule(await loadConfig(state.root ?? process.cwd()));
|
|
22
|
+
};
|
|
4
23
|
|
|
5
24
|
/**
|
|
6
25
|
* Creates the Vite plugin that resolves and serves the `virtual:gtkx-config`
|
|
@@ -15,22 +34,17 @@ const createConfigPlugin = (options: {
|
|
|
15
34
|
config?: () => Omit<UserConfig, "plugins">;
|
|
16
35
|
}): Plugin => {
|
|
17
36
|
const loadConfig = options.loadConfig ?? createConfigLoader();
|
|
18
|
-
|
|
37
|
+
const state: PluginState = { root: undefined };
|
|
19
38
|
|
|
20
39
|
return {
|
|
21
40
|
name: options.name,
|
|
22
41
|
config(config: UserConfig) {
|
|
23
|
-
root = config.root ?? root;
|
|
42
|
+
state.root = config.root ?? state.root;
|
|
43
|
+
|
|
24
44
|
return options.config?.();
|
|
25
45
|
},
|
|
26
|
-
resolveId(id: string)
|
|
27
|
-
|
|
28
|
-
return undefined;
|
|
29
|
-
},
|
|
30
|
-
async load(id: string) {
|
|
31
|
-
if (id !== RESOLVED_GTKX_CONFIG_VIRTUAL_ID) return undefined;
|
|
32
|
-
return renderConfigModule(await loadConfig(root ?? process.cwd()));
|
|
33
|
-
},
|
|
46
|
+
resolveId: (id: string) => resolveVirtualId(id),
|
|
47
|
+
load: (id: string) => loadVirtualModule(id, loadConfig, state),
|
|
34
48
|
};
|
|
35
49
|
};
|
|
36
50
|
|