@gtkx/codegen 1.5.0 → 1.6.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 (175) hide show
  1. package/dist/analysis/descriptor-render.d.ts +1 -0
  2. package/dist/analysis/descriptor-render.d.ts.map +1 -1
  3. package/dist/analysis/descriptor-render.js +56 -28
  4. package/dist/analysis/descriptor-render.js.map +1 -1
  5. package/dist/analysis/descriptor.d.ts +3 -1
  6. package/dist/analysis/descriptor.d.ts.map +1 -1
  7. package/dist/analysis/descriptor.js +10 -9
  8. package/dist/analysis/descriptor.js.map +1 -1
  9. package/dist/compile.d.ts.map +1 -1
  10. package/dist/compile.js +24 -3
  11. package/dist/compile.js.map +1 -1
  12. package/dist/docs/api-reference.d.ts +2 -0
  13. package/dist/docs/api-reference.d.ts.map +1 -1
  14. package/dist/docs/api-reference.js +2 -1
  15. package/dist/docs/api-reference.js.map +1 -1
  16. package/dist/docs/pipeline.d.ts +3 -0
  17. package/dist/docs/pipeline.d.ts.map +1 -1
  18. package/dist/docs/pipeline.js +44 -12
  19. package/dist/docs/pipeline.js.map +1 -1
  20. package/dist/fingerprint.d.ts +3 -0
  21. package/dist/fingerprint.d.ts.map +1 -1
  22. package/dist/fingerprint.js +4 -0
  23. package/dist/fingerprint.js.map +1 -1
  24. package/dist/gi.d.ts +1 -0
  25. package/dist/gi.d.ts.map +1 -1
  26. package/dist/gi.js +4 -1
  27. package/dist/gi.js.map +1 -1
  28. package/dist/gir/libraries.d.ts +12 -5
  29. package/dist/gir/libraries.d.ts.map +1 -1
  30. package/dist/gir/libraries.js +50 -14
  31. package/dist/gir/libraries.js.map +1 -1
  32. package/dist/gir/library.d.ts +5 -0
  33. package/dist/gir/library.d.ts.map +1 -1
  34. package/dist/gir/library.js +6 -0
  35. package/dist/gir/library.js.map +1 -1
  36. package/dist/internal.d.ts +1 -0
  37. package/dist/internal.d.ts.map +1 -1
  38. package/dist/internal.js +1 -0
  39. package/dist/internal.js.map +1 -1
  40. package/dist/jsx.d.ts +1 -0
  41. package/dist/jsx.d.ts.map +1 -1
  42. package/dist/jsx.js +2 -0
  43. package/dist/jsx.js.map +1 -1
  44. package/dist/runner.d.ts +2 -0
  45. package/dist/runner.d.ts.map +1 -1
  46. package/dist/runner.js +3 -1
  47. package/dist/runner.js.map +1 -1
  48. package/dist/store/gi/bootstrap.d.ts +4 -0
  49. package/dist/store/gi/bootstrap.d.ts.map +1 -0
  50. package/dist/store/gi/bootstrap.js +48 -0
  51. package/dist/store/gi/bootstrap.js.map +1 -0
  52. package/dist/store/gi/class.d.ts.map +1 -1
  53. package/dist/store/gi/class.js +75 -28
  54. package/dist/store/gi/class.js.map +1 -1
  55. package/dist/store/gi/constructor-props.d.ts.map +1 -1
  56. package/dist/store/gi/constructor-props.js +2 -1
  57. package/dist/store/gi/constructor-props.js.map +1 -1
  58. package/dist/store/gi/enum.js +3 -3
  59. package/dist/store/gi/enum.js.map +1 -1
  60. package/dist/store/gi/folded.d.ts +14 -0
  61. package/dist/store/gi/folded.d.ts.map +1 -0
  62. package/dist/store/gi/folded.js +35 -0
  63. package/dist/store/gi/folded.js.map +1 -0
  64. package/dist/store/gi/function.d.ts.map +1 -1
  65. package/dist/store/gi/function.js +22 -3
  66. package/dist/store/gi/function.js.map +1 -1
  67. package/dist/store/gi/gtype-binding.d.ts.map +1 -1
  68. package/dist/store/gi/gtype-binding.js +11 -1
  69. package/dist/store/gi/gtype-binding.js.map +1 -1
  70. package/dist/store/gi/interface.d.ts.map +1 -1
  71. package/dist/store/gi/interface.js +43 -4
  72. package/dist/store/gi/interface.js.map +1 -1
  73. package/dist/store/gi/method.d.ts.map +1 -1
  74. package/dist/store/gi/method.js +2 -0
  75. package/dist/store/gi/method.js.map +1 -1
  76. package/dist/store/gi/pipeline.d.ts +5 -1
  77. package/dist/store/gi/pipeline.d.ts.map +1 -1
  78. package/dist/store/gi/pipeline.js +13 -1
  79. package/dist/store/gi/pipeline.js.map +1 -1
  80. package/dist/store/gi/property-accessor.js +1 -1
  81. package/dist/store/gi/property-accessor.js.map +1 -1
  82. package/dist/store/gi/record-field-accessor.d.ts.map +1 -1
  83. package/dist/store/gi/record-field-accessor.js +3 -3
  84. package/dist/store/gi/record-field-accessor.js.map +1 -1
  85. package/dist/store/gi/record.d.ts.map +1 -1
  86. package/dist/store/gi/record.js +44 -12
  87. package/dist/store/gi/record.js.map +1 -1
  88. package/dist/store/gi/registration.js +2 -2
  89. package/dist/store/gi/registration.js.map +1 -1
  90. package/dist/store/gi/return-wrap.js +1 -1
  91. package/dist/store/gi/return-wrap.js.map +1 -1
  92. package/dist/store/gi/signal.d.ts.map +1 -1
  93. package/dist/store/gi/signal.js +7 -3
  94. package/dist/store/gi/signal.js.map +1 -1
  95. package/dist/store/gi/vtable.d.ts.map +1 -1
  96. package/dist/store/gi/vtable.js +1 -1
  97. package/dist/store/gi/vtable.js.map +1 -1
  98. package/dist/store/gi-store.d.ts +1 -0
  99. package/dist/store/gi-store.d.ts.map +1 -1
  100. package/dist/store/gi-store.js +16 -7
  101. package/dist/store/gi-store.js.map +1 -1
  102. package/dist/store/jsx/element-components.d.ts.map +1 -1
  103. package/dist/store/jsx/element-components.js +64 -10
  104. package/dist/store/jsx/element-components.js.map +1 -1
  105. package/dist/store/jsx/element-prop-types.d.ts +2 -0
  106. package/dist/store/jsx/element-prop-types.d.ts.map +1 -1
  107. package/dist/store/jsx/element-prop-types.js +2 -0
  108. package/dist/store/jsx/element-prop-types.js.map +1 -1
  109. package/dist/store/jsx/metadata.d.ts.map +1 -1
  110. package/dist/store/jsx/metadata.js +28 -1
  111. package/dist/store/jsx/metadata.js.map +1 -1
  112. package/dist/store/jsx-store.d.ts +1 -0
  113. package/dist/store/jsx-store.d.ts.map +1 -1
  114. package/dist/store/jsx-store.js +7 -2
  115. package/dist/store/jsx-store.js.map +1 -1
  116. package/dist/store/store-fs.d.ts +2 -1
  117. package/dist/store/store-fs.d.ts.map +1 -1
  118. package/dist/store/store-fs.js +1 -1
  119. package/dist/store/store-fs.js.map +1 -1
  120. package/dist/writer/context.d.ts +17 -0
  121. package/dist/writer/context.d.ts.map +1 -1
  122. package/dist/writer/context.js +83 -11
  123. package/dist/writer/context.js.map +1 -1
  124. package/dist/writer/emit.d.ts +2 -1
  125. package/dist/writer/emit.d.ts.map +1 -1
  126. package/dist/writer/emit.js +2 -1
  127. package/dist/writer/emit.js.map +1 -1
  128. package/dist/writer/imports.d.ts.map +1 -1
  129. package/dist/writer/imports.js +6 -1
  130. package/dist/writer/imports.js.map +1 -1
  131. package/dist/writer/module.d.ts +1 -0
  132. package/dist/writer/module.d.ts.map +1 -1
  133. package/dist/writer/module.js +6 -1
  134. package/dist/writer/module.js.map +1 -1
  135. package/overrides/glib/regex.ts.ejs +2 -0
  136. package/package.json +4 -4
  137. package/src/analysis/descriptor-render.ts +79 -30
  138. package/src/analysis/descriptor.ts +19 -11
  139. package/src/compile.ts +42 -3
  140. package/src/docs/api-reference.ts +4 -1
  141. package/src/docs/pipeline.ts +64 -13
  142. package/src/fingerprint.ts +7 -0
  143. package/src/gi.ts +6 -1
  144. package/src/gir/libraries.ts +64 -16
  145. package/src/gir/library.ts +9 -0
  146. package/src/internal.ts +1 -0
  147. package/src/jsx.ts +3 -0
  148. package/src/runner.ts +5 -1
  149. package/src/store/gi/bootstrap.ts +64 -0
  150. package/src/store/gi/class.ts +100 -29
  151. package/src/store/gi/constructor-props.ts +2 -1
  152. package/src/store/gi/enum.ts +4 -4
  153. package/src/store/gi/folded.ts +57 -0
  154. package/src/store/gi/function.ts +22 -3
  155. package/src/store/gi/gtype-binding.ts +15 -2
  156. package/src/store/gi/interface.ts +64 -7
  157. package/src/store/gi/method.ts +2 -0
  158. package/src/store/gi/pipeline.ts +21 -2
  159. package/src/store/gi/property-accessor.ts +1 -1
  160. package/src/store/gi/record-field-accessor.ts +5 -3
  161. package/src/store/gi/record.ts +61 -14
  162. package/src/store/gi/registration.ts +2 -2
  163. package/src/store/gi/return-wrap.ts +1 -1
  164. package/src/store/gi/signal.ts +8 -3
  165. package/src/store/gi/vtable.ts +1 -0
  166. package/src/store/gi-store.ts +19 -7
  167. package/src/store/jsx/element-components.ts +107 -10
  168. package/src/store/jsx/element-prop-types.ts +4 -0
  169. package/src/store/jsx/metadata.ts +41 -0
  170. package/src/store/jsx-store.ts +10 -4
  171. package/src/store/store-fs.ts +3 -2
  172. package/src/writer/context.ts +109 -16
  173. package/src/writer/emit.ts +3 -1
  174. package/src/writer/imports.ts +9 -1
  175. package/src/writer/module.ts +9 -1
package/src/gi.ts CHANGED
@@ -15,6 +15,7 @@ type GiCodegenOptions = {
15
15
  isValueUnwrapped: boolean;
16
16
  isFinishTrimmed: boolean;
17
17
  isInoutInPlace: boolean;
18
+ isTreeShaken: boolean;
18
19
  };
19
20
 
20
21
  type SplitNamespaces = {
@@ -39,9 +40,12 @@ const splitNamespaces = (library: Library): SplitNamespaces => {
39
40
  continue;
40
41
  }
41
42
 
43
+ const generated = generateNamespaceModule(namespace, library);
44
+
42
45
  namespaces.push({
43
46
  directory: namespaceDirectory(namespace),
44
- rawSource: generateNamespaceModule(namespace, library),
47
+ rawSource: generated.source,
48
+ rawBootstrapSource: generated.bootstrapSource,
45
49
  girFile: namespace.girFile,
46
50
  });
47
51
  }
@@ -62,6 +66,7 @@ const runGiCodegen = (library: Library, options: GiCodegenOptions): number => {
62
66
  isValueUnwrapped,
63
67
  isFinishTrimmed,
64
68
  isInoutInPlace,
69
+ isTreeShaken: options.isTreeShaken,
65
70
  });
66
71
 
67
72
  writeGiStore(gi, namespaces, externalNamespaces, {
@@ -17,36 +17,84 @@ type LibrarySelection = "*" | string[] | undefined;
17
17
  const LIBRARIES_WILDCARD = "*";
18
18
  const GIR_LIBRARY_PATTERN = /^[A-Za-z][A-Za-z0-9]*-\d+(?:\.\d+)*$/;
19
19
  const DEFAULT_LIBRARIES: string[] = ["Gtk-4.0"];
20
+ const ADWAITA_DEFAULT_LIBRARIES: string[] = ["Gtk-4.0", "Adw-1"];
20
21
  const GIR_FILE_SUFFIX = ".gir";
21
22
 
22
23
  /**
23
- * Expands a `libraries` config value into the GIR identifiers to generate from: `"Gtk-4.0"` alone when
24
- * nothing is configured, everything installed for `"*"`, and otherwise the given list with `"Gtk-4.0"`
25
- * prepended unless it already names a Gtk version.
24
+ * Expands a `libraries` config value into the GIR identifiers to generate from: the mandatory set alone
25
+ * when nothing is configured, everything installed for `"*"`, and otherwise the given list with the
26
+ * mandatory set prepended.
27
+ *
28
+ * The mandatory set is `"Gtk-4.0"`, which joins a list that does not already name a Gtk version. Under
29
+ * `isAdwaitaDefault` it is `"Gtk-4.0"` and `"Adw-1"`, and it joins `"*"` as well as an explicit list.
30
+ * Each entry is mandatory by namespace rather than by version, so a list that pins another version of
31
+ * Gtk or Adwaita keeps its pin.
26
32
  *
27
33
  * @throws If `"*"` matched no `.gir` file anywhere on the GIR path.
28
34
  */
29
- const resolveLibraries = (libraries: LibrarySelection, girPath: string[]): string[] => {
35
+ const resolveLibraries = (libraries: LibrarySelection, girPath: string[], isAdwaitaDefault = false): string[] => {
30
36
  if (libraries === undefined) {
31
- return [...DEFAULT_LIBRARIES];
37
+ return [...mandatoryLibraries(isAdwaitaDefault)];
32
38
  }
33
39
 
34
40
  if (libraries === LIBRARIES_WILDCARD) {
35
- const discovered = discoverGirNamespaces(girPath);
41
+ return installedLibraries(girPath, isAdwaitaDefault);
42
+ }
36
43
 
37
- if (discovered.length === 0) {
38
- throw new Error(
39
- `gtkx.config.ts: \`libraries: "*"\` matched no .gir files in [${girPath.join(", ")}]. ` +
40
- "Install gobject-introspection data packages, or list the libraries explicitly.",
41
- );
42
- }
44
+ if (isAdwaitaDefault) {
45
+ return withMandatory(ADWAITA_DEFAULT_LIBRARIES, libraries);
46
+ }
47
+
48
+ return withDefaultGtk(libraries);
49
+ };
50
+
51
+ const redundantLibraries = (libraries: LibrarySelection, isAdwaitaDefault = false): string[] => {
52
+ if (!isAdwaitaDefault || !Array.isArray(libraries)) {
53
+ return [];
54
+ }
55
+
56
+ return libraries.filter((entry) => ADWAITA_DEFAULT_LIBRARIES.includes(entry));
57
+ };
58
+
59
+ const mandatoryLibraries = (isAdwaitaDefault: boolean): string[] =>
60
+ isAdwaitaDefault ? ADWAITA_DEFAULT_LIBRARIES : DEFAULT_LIBRARIES;
43
61
 
44
- return discovered;
62
+ const installedLibraries = (girPath: string[], isAdwaitaDefault: boolean): string[] => {
63
+ const discovered = discoverGirNamespaces(girPath);
64
+
65
+ if (discovered.length === 0) {
66
+ throw new Error(
67
+ `gtkx.config.ts: \`libraries: "*"\` matched no .gir files in [${girPath.join(", ")}]. ` +
68
+ "Install gobject-introspection data packages, or list the libraries explicitly.",
69
+ );
70
+ }
71
+
72
+ if (isAdwaitaDefault) {
73
+ return withMandatory(ADWAITA_DEFAULT_LIBRARIES, discovered);
45
74
  }
46
75
 
47
- const hasGtk = libraries.some((library) => library.startsWith("Gtk-"));
76
+ return discovered;
77
+ };
78
+
79
+ const getNamespace = (library: string): string => {
80
+ const separator = library.indexOf("-");
81
+
82
+ return separator === -1 ? library : library.slice(0, separator);
83
+ };
84
+
85
+ const withMandatory = (mandatory: string[], selected: string[]): string[] => {
86
+ const named: Set<string> = new Set(selected.map((entry) => getNamespace(entry)));
87
+ const missing = mandatory.filter((library) => !named.has(getNamespace(library)));
88
+
89
+ return [...new Set([...missing, ...selected])];
90
+ };
91
+
92
+ const withDefaultGtk = (libraries: string[]): string[] => {
93
+ if (libraries.some((library) => library.startsWith("Gtk-"))) {
94
+ return [...new Set(libraries)];
95
+ }
48
96
 
49
- return [...new Set([...(hasGtk ? [] : DEFAULT_LIBRARIES), ...libraries])];
97
+ return [...new Set([...DEFAULT_LIBRARIES, ...libraries])];
50
98
  };
51
99
 
52
100
  const readDirEntries = (dir: string): string[] => {
@@ -121,4 +169,4 @@ const compareVersions = (a: string, b: string): number => {
121
169
  return 0;
122
170
  };
123
171
 
124
- export { resolveLibraries, discoverGirNamespaces, type LibrarySelection };
172
+ export { resolveLibraries, redundantLibraries, discoverGirNamespaces, LIBRARIES_WILDCARD, type LibrarySelection };
@@ -34,6 +34,8 @@ type FutureBehavior = {
34
34
  isFinishTrimmed?: boolean;
35
35
  /** Whether a handle-passing inout parameter is mutated in place rather than packed into the result. */
36
36
  isInoutInPlace?: boolean;
37
+ /** Whether registrations fold into each class so bundlers can drop unused ones. */
38
+ isTreeShaken?: boolean;
37
39
  };
38
40
 
39
41
  /** A namespace whose header has been read and whose shell is registered, before its body is parsed. */
@@ -105,6 +107,7 @@ class Library {
105
107
  library.isValueUnwrappedValue = future.isValueUnwrapped === true;
106
108
  library.isFinishTrimmedValue = future.isFinishTrimmed === true;
107
109
  library.isInoutInPlaceValue = future.isInoutInPlace === true;
110
+ library.isTreeShakenValue = future.isTreeShaken === true;
108
111
  this.drive(library, libraries, girPath);
109
112
 
110
113
  return library;
@@ -120,6 +123,7 @@ class Library {
120
123
  private isValueUnwrappedValue = false;
121
124
  private isFinishTrimmedValue = false;
122
125
  private isInoutInPlaceValue = false;
126
+ private isTreeShakenValue = false;
123
127
 
124
128
  /** Creates a library with no namespaces loaded; {@link Library.load} builds a populated one. */
125
129
  constructor() {
@@ -369,6 +373,11 @@ class Library {
369
373
  return this.isInoutInPlaceValue;
370
374
  }
371
375
 
376
+ /** Whether registrations fold into each class so bundlers can drop unused ones. */
377
+ public get isTreeShaken(): boolean {
378
+ return this.isTreeShakenValue;
379
+ }
380
+
372
381
  /** Paths of the `.gir` files that were read, in the order they were discovered. */
373
382
  public get girFiles(): string[] {
374
383
  return this.girFilesValue;
package/src/internal.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { writeDocs } from "./docs/pipeline.js";
2
+ export { LIBRARIES_WILDCARD, redundantLibraries } from "./gir/libraries.js";
2
3
  export { runGlCodegen } from "./runner.js";
3
4
  export { getShadowingStorePaths, sweepProjectStaging } from "./store/resolve-store.js";
4
5
  export { ensureStoreLinks } from "./store/store-links.js";
package/src/jsx.ts CHANGED
@@ -24,6 +24,7 @@ type RunJsxCodegenOptions = {
24
24
  userOmittedProps: OmittedProps;
25
25
  isGiRegenerated: boolean;
26
26
  isForced: boolean;
27
+ isTreeShaken: boolean;
27
28
  };
28
29
 
29
30
  type RunJsxCodegenResult = {
@@ -45,6 +46,7 @@ const runJsxCodegen = async (options: RunJsxCodegenOptions): Promise<RunJsxCodeg
45
46
  lazyElements,
46
47
  props,
47
48
  omittedProps,
49
+ isTreeShaken: options.isTreeShaken,
48
50
  };
49
51
 
50
52
  if (!options.isForced && !options.isGiRegenerated) {
@@ -69,6 +71,7 @@ const runJsxCodegen = async (options: RunJsxCodegenOptions): Promise<RunJsxCodeg
69
71
  options: options.jsx,
70
72
  namespaces,
71
73
  metadata,
74
+ isTreeShaken: options.isTreeShaken,
72
75
  externalPackages: storeExternalPackages(library),
73
76
  rawFiles: [
74
77
  {
package/src/runner.ts CHANGED
@@ -48,6 +48,8 @@ type CodegenRunnerOptions = {
48
48
  isFinishTrimmed?: boolean;
49
49
  /** Mutates a handle-passing inout parameter in place instead of packing it into the result; defaults to false. */
50
50
  isInoutInPlace?: boolean;
51
+ /** Folds registrations into each class so bundlers can drop unused ones; defaults to false. */
52
+ isTreeShaken?: boolean;
51
53
  };
52
54
 
53
55
  /** What a `runCodegen` run produced. */
@@ -152,6 +154,7 @@ const emitJsxStore = async (input: {
152
154
  ...jsxUserOptions(options),
153
155
  isGiRegenerated,
154
156
  isForced: options.isForced === true,
157
+ isTreeShaken: options.isTreeShaken === true,
155
158
  });
156
159
 
157
160
  return {
@@ -173,7 +176,8 @@ const emitStoresWithConfig = async (config: {
173
176
  const isValueUnwrapped = options.isValueUnwrapped === true;
174
177
  const isFinishTrimmed = options.isFinishTrimmed === true;
175
178
  const isInoutInPlace = options.isInoutInPlace === true;
176
- const future = { isByteArrayTyped, isValueUnwrapped, isFinishTrimmed, isInoutInPlace };
179
+ const isTreeShaken = options.isTreeShaken === true;
180
+ const future = { isByteArrayTyped, isValueUnwrapped, isFinishTrimmed, isInoutInPlace, isTreeShaken };
177
181
  let library: Library | undefined;
178
182
  const loadLibrary = (): Library => (library ??= Library.load(libraries, girPath, future));
179
183
  const giInputs = { girFiles: [] as string[], libraries, girPath, storeVersion: gi.version, ...future };
@@ -0,0 +1,64 @@
1
+ import { sortStrings, sourceStringLiteral } from "@gtkx/utils";
2
+ import type { ModuleContext } from "../../writer/context.js";
3
+ import { namespaceDirectory } from "../../gir/namespace.js";
4
+
5
+ const PATCHING_OVERRIDES: Record<string, string[]> = {
6
+ glib: ["./overrides/regex.js"],
7
+ gobject: ["./overrides/object.js", "./overrides/param-spec-getters.js", "./overrides/value.js"],
8
+ };
9
+
10
+ const CORE_WRAPPERS: Record<string, string[]> = {
11
+ glib: ["Error", "Variant"],
12
+ gobject: ["ParamSpec", "Value"],
13
+ };
14
+
15
+ const renderDependencyImports = (context: ModuleContext): string[] => [
16
+ ...sortStrings(context.dependencies).map((dependency) => `import "../${dependency}/bootstrap.js";`),
17
+ ...sortStrings(context.externalDependencies).map((packageName) => `import ${sourceStringLiteral(packageName)};`),
18
+ ];
19
+
20
+ const renderOverrideImports = (directory: string): string[] =>
21
+ (PATCHING_OVERRIDES[directory] ?? []).map((override) => `import ${sourceStringLiteral(override)};`);
22
+
23
+ const renderRuntimeImport = (context: ModuleContext, coreWrappers: string[]): string[] => {
24
+ const runtimeImports = new Set(context.bootstrapRuntimeImports);
25
+
26
+ if (coreWrappers.length > 0) {
27
+ runtimeImports.add("retainWrapperClasses");
28
+ }
29
+
30
+ if (runtimeImports.size === 0) {
31
+ return [];
32
+ }
33
+
34
+ return [`import { ${sortStrings(runtimeImports).join(", ")} } from "@gtkx/runtime";`];
35
+ };
36
+
37
+ const renderModuleImports = (context: ModuleContext, directory: string, coreWrappers: string[]): string[] => {
38
+ const moduleImports = sortStrings(new Set([...context.bootstrapModuleExports, ...coreWrappers]));
39
+
40
+ return moduleImports.length === 0 ? [] : [`import { ${moduleImports.join(", ")} } from "./${directory}.js";`];
41
+ };
42
+
43
+ const renderBootstrapModule = (context: ModuleContext): string => {
44
+ const directory = namespaceDirectory(context.namespace);
45
+ const coreWrappers = CORE_WRAPPERS[directory] ?? [];
46
+
47
+ const retention = coreWrappers.length === 0
48
+ ? []
49
+ : [`retainWrapperClasses([${coreWrappers.join(", ")}]);`];
50
+
51
+ const lines = [
52
+ ...renderDependencyImports(context),
53
+ ...renderOverrideImports(directory),
54
+ ...renderRuntimeImport(context, coreWrappers),
55
+ ...renderModuleImports(context, directory, coreWrappers),
56
+ "",
57
+ ...retention,
58
+ ...context.bootstrapCalls,
59
+ ];
60
+
61
+ return `${lines.join("\n")}\n`;
62
+ };
63
+
64
+ export { renderBootstrapModule };
@@ -34,6 +34,7 @@ import {
34
34
  } from "./callables.js";
35
35
  import { renderClassConstructor, renderConstructorPropsInterface } from "./constructor-props.js";
36
36
  import { getDoc } from "./doc-spec.js";
37
+ import { declareFoldedClass, localClassName } from "./folded.js";
37
38
  import { gtypeMemberDeclaration, renderSourceGtype } from "./gtype-binding.js";
38
39
  import { methodExportName } from "./method.js";
39
40
  import { renderPropertyDeclarations } from "./properties.js";
@@ -78,6 +79,14 @@ type AppendInstanceMethodsOptions = {
78
79
  className: string;
79
80
  };
80
81
 
82
+ type TreeShakenClassOptions = {
83
+ klass: GirClass;
84
+ className: string;
85
+ heritage: string;
86
+ body: string;
87
+ implemented: ImplementedRef[];
88
+ };
89
+
81
90
  const generateClass = (context: ModuleContext, klass: GirClass): void => {
82
91
  if (!isEmittableEntity(klass)) {
83
92
  return;
@@ -95,18 +104,25 @@ const generateClass = (context: ModuleContext, klass: GirClass): void => {
95
104
  const parentExpression = resolveParent(context, klass);
96
105
  const extendsClause = renderExtendsClause(context, parentExpression, klass, callables);
97
106
  const implemented = resolveImplementedRefs(context, klass);
98
- const typeRefs = implemented.map((ref) => omittedTypeRef(ref.typeRef, ref.conflicts));
99
- const implementsClause = typeRefs.length === 0 ? "" : ` implements ${typeRefs.join(", ")}`;
107
+ const implementsClause = renderImplementsClause(implemented);
108
+
109
+ if (context.isTreeShaken) {
110
+ context.beginRegistrations();
111
+ }
112
+
100
113
  const { members, accessors } = renderClassMembers(context, klass, callables, parentExpression !== undefined);
101
114
  const body = indentMembers(members);
102
- const doc = getDoc(klass);
103
- const modifier = klass.isAbstract ? "abstract " : "";
104
-
105
- context.declare({
106
- name: className,
107
- code: `${doc}export ${modifier}class ${className}${extendsClause}${implementsClause} {\n${body}\n}`,
108
- owner: klass.name,
109
- });
115
+ const heritage = `${extendsClause}${implementsClause}`;
116
+
117
+ if (context.isTreeShaken) {
118
+ declareTreeShakenClass(context, { klass, className, heritage, body, implemented });
119
+ } else {
120
+ context.declare({
121
+ name: className,
122
+ code: `${getDoc(klass)}export ${classModifier(klass)}class ${className}${heritage} {\n${body}\n}`,
123
+ owner: klass.name,
124
+ });
125
+ }
110
126
 
111
127
  context.declare({
112
128
  name: `${className}ConstructorProps`,
@@ -114,8 +130,37 @@ const generateClass = (context: ModuleContext, klass: GirClass): void => {
114
130
  });
115
131
 
116
132
  appendMemberDeclarations({ context, klass, className, accessors, implemented });
117
- appendInstallMixins(context, className, implemented);
118
- appendClassRegistrations(context, klass, className);
133
+
134
+ if (!context.isTreeShaken) {
135
+ appendInstallMixins(context, className, implemented);
136
+ appendClassRegistrations(context, klass, className);
137
+ }
138
+ };
139
+
140
+ const classModifier = (klass: GirClass): string => (klass.isAbstract ? "abstract " : "");
141
+
142
+ const renderImplementsClause = (implemented: ImplementedRef[]): string => {
143
+ const typeRefs = implemented.map((ref) => omittedTypeRef(ref.typeRef, ref.conflicts));
144
+
145
+ return typeRefs.length === 0 ? "" : ` implements ${typeRefs.join(", ")}`;
146
+ };
147
+
148
+ const declareTreeShakenClass = (context: ModuleContext, options: TreeShakenClassOptions): void => {
149
+ const { klass, className, heritage, body, implemented } = options;
150
+ const localName = localClassName(className);
151
+ appendInstallMixins(context, localName, implemented);
152
+ appendClassRegistrations(context, klass, localName);
153
+ const registrations = context.takeRegistrations();
154
+
155
+ declareFoldedClass({
156
+ context,
157
+ className,
158
+ doc: getDoc(klass),
159
+ owner: klass.name,
160
+ localDeclaration: `${classModifier(klass)}class ${localName}${heritage} {\n${body}\n}`,
161
+ registrations,
162
+ hasInstanceInterface: true,
163
+ });
119
164
  };
120
165
 
121
166
  const appendMemberDeclarations = (options: MemberDeclarationsOptions): void => {
@@ -224,37 +269,56 @@ const appendInstanceMethods = (options: AppendInstanceMethodsOptions): void => {
224
269
  }
225
270
  };
226
271
 
227
- const appendInstallMixins = (context: ModuleContext, className: string, implemented: ImplementedRef[]): void => {
272
+ const appendInstallMixins = (context: ModuleContext, targetName: string, implemented: ImplementedRef[]): void => {
228
273
  if (implemented.length === 0) {
229
274
  return;
230
275
  }
231
276
 
232
- context.addRuntimeImport("installMixins");
233
- const makerRefs = implemented.map((ref) => ref.makerRef);
234
- const localRefs = makerRefs.filter((ref) => !ref.includes("."));
277
+ if (!context.isTreeShaken) {
278
+ context.addRuntimeImport("installMixins");
279
+ const makerRefs = implemented.map((ref) => ref.makerRef);
280
+ const localRefs = makerRefs.filter((ref) => !ref.includes("."));
235
281
 
236
- context.module.appendRegistration(`installMixins(${className}, [${makerRefs.join(", ")}]);`, [
237
- className,
238
- ...localRefs,
239
- ]);
282
+ context.module.appendRegistration(`installMixins(${targetName}, [${makerRefs.join(", ")}]);`, [
283
+ targetName,
284
+ ...localRefs,
285
+ ]);
286
+
287
+ return;
288
+ }
289
+
290
+ const registered = implemented.filter((ref) => ref.interfaceKlass.glibGetType !== undefined);
291
+ const unregistered = implemented.filter((ref) => ref.interfaceKlass.glibGetType === undefined);
292
+
293
+ if (registered.length > 0) {
294
+ context.addRuntimeImport("installInterfaces");
295
+ const refs = registered.map((ref) => ref.typeRef);
296
+ context.collectRegistration(`installInterfaces(${targetName}, [${refs.join(", ")}]);`);
297
+ }
298
+
299
+ if (unregistered.length > 0) {
300
+ context.addRuntimeImport("installMixins");
301
+ const refs = unregistered.map((ref) => ref.makerRef);
302
+ context.collectRegistration(`installMixins(${targetName}, [${refs.join(", ")}]);`);
303
+ }
240
304
  };
241
305
 
242
- const appendClassRegistrations = (context: ModuleContext, klass: GirClass, className: string): void => {
306
+ const appendClassRegistrations = (context: ModuleContext, klass: GirClass, targetName: string): void => {
243
307
  const gtypeExpr = renderSourceGtype(context, klass);
244
308
 
245
309
  appendWrapperClassRegistration(context, {
246
- className,
310
+ className: targetName,
247
311
  gtypeExpr,
248
312
  vfuncs: renderVfuncMetadata(context, klass),
249
313
  });
250
314
 
251
- appendClassStructRegistration(context, klass, className, gtypeExpr);
315
+ appendClassStructRegistration(context, klass, targetName, gtypeExpr);
252
316
  };
253
317
 
254
318
  const appendClassStructRegistration = (
255
319
  context: ModuleContext,
256
320
  klass: GirClass,
257
- className: string,
321
+ targetName: string,
258
322
  gtypeExpr: string | undefined,
259
323
  ): void => {
260
324
  const typeStruct = klass.glibTypeStruct;
@@ -272,8 +336,8 @@ const appendClassStructRegistration = (
272
336
  const structName = sanitizeTypeIdentifier(resolved.value.name);
273
337
  context.addRuntimeImport("registerClassStruct");
274
338
 
275
- context.module.appendRegistration(`registerClassStruct(${className}, ${structName});`, [
276
- className,
339
+ context.collectRegistration(`registerClassStruct(${targetName}, ${structName});`, [
340
+ targetName,
277
341
  structName,
278
342
  ]);
279
343
  };
@@ -445,9 +509,16 @@ const renderExtendsClause = (
445
509
  const resolveParent = (context: ModuleContext, klass: GirClass): string | undefined => {
446
510
  const parent = getParentRef(klass);
447
511
 
448
- return parent === undefined
449
- ? undefined
450
- : context.qualify(parent.namespaceName ?? context.namespace.name, sanitizeTypeIdentifier(parent.typeName));
512
+ if (parent === undefined) {
513
+ return undefined;
514
+ }
515
+
516
+ const qualified = context.qualify(
517
+ parent.namespaceName ?? context.namespace.name,
518
+ sanitizeTypeIdentifier(parent.typeName),
519
+ );
520
+
521
+ return context.isTreeShaken ? context.hoistBaseRef(qualified) : qualified;
451
522
  };
452
523
 
453
524
  export { generateClass };
@@ -96,7 +96,8 @@ const renderTranslatingConstructor = (context: ModuleContext, props: GirProperty
96
96
  context.addRuntimeImport("t");
97
97
  context.addRuntimeImport("registerConstructProperties");
98
98
  const bindings = renderConstructBindings(context, props);
99
- context.module.appendRegistration(`registerConstructProperties(${className}, ${bindings});`, [className]);
99
+ const target = context.isTreeShaken ? `_${className}` : className;
100
+ context.collectRegistration(`registerConstructProperties(${target}, ${bindings});`, [className]);
100
101
 
101
102
  return renderBlock(`constructor(props: ${className}ConstructorProps = {})`, "super(props);");
102
103
  };
@@ -3,7 +3,7 @@ import type { EnumMember, GirEnum } from "../../gir/enum.js";
3
3
  import type { ModuleContext } from "../../writer/context.js";
4
4
  import { hasAnnotations } from "../../gir/annotations.js";
5
5
  import { isEmittableEntity } from "../../gir/emittable.js";
6
- import { indent } from "../../writer/emit.js";
6
+ import { indent, pure } from "../../writer/emit.js";
7
7
  import { getDoc } from "./doc-spec.js";
8
8
 
9
9
  type KeyedMember = EnumMember & { key: string };
@@ -58,11 +58,11 @@ const appendErrorDomain = (
58
58
  owner: enumeration.name,
59
59
  });
60
60
 
61
+ const domain = pure(`createErrorDomain(${quarkExpression}, { ${memberEntries.join(", ")} })`);
62
+
61
63
  context.declare({
62
64
  name,
63
- code:
64
- `${doc}export const ${name}: ErrorDomain<${shape}> = ` +
65
- `createErrorDomain(${quarkExpression}, { ${memberEntries.join(", ")} });`,
65
+ code: `${doc}export const ${name}: ErrorDomain<${shape}> = ${domain};`,
66
66
  });
67
67
  };
68
68
 
@@ -0,0 +1,57 @@
1
+ import { sourceStringLiteral } from "@gtkx/utils";
2
+ import type { ModuleContext } from "../../writer/context.js";
3
+ import { indent } from "../../writer/emit.js";
4
+
5
+ type FoldedClassOptions = {
6
+ context: ModuleContext;
7
+ className: string;
8
+ doc: string;
9
+ owner: string;
10
+ localDeclaration: string;
11
+ registrations: string[];
12
+ hasInstanceInterface: boolean;
13
+ };
14
+
15
+ const localClassName = (className: string): string => `_${className}`;
16
+
17
+ const renderNameStatement = (className: string): string =>
18
+ `globalThis.Object.defineProperty(${localClassName(className)}, "name", ` +
19
+ `{ value: ${sourceStringLiteral(className)}, configurable: true });`;
20
+
21
+ const declareFoldedClass = (options: FoldedClassOptions): void => {
22
+ const { context, className, doc, owner, localDeclaration, registrations, hasInstanceInterface } = options;
23
+ const localName = localClassName(className);
24
+ context.module.appendDeclaration({ name: localName, code: localDeclaration, isLocal: true });
25
+
26
+ const body = [renderNameStatement(className), ...registrations, `return ${localName};`]
27
+ .map((statement) => indent(statement, 1))
28
+ .join("\n");
29
+
30
+ if (!hasInstanceInterface) {
31
+ context.declare({
32
+ name: className,
33
+ code: `${doc}export const ${className}: typeof ${localName} = /* @__PURE__ */ (() => {\n${body}\n})();`,
34
+ owner,
35
+ });
36
+
37
+ return;
38
+ }
39
+
40
+ context.declare({
41
+ name: className,
42
+ code: `export interface ${className} extends ${localName} {}`,
43
+ });
44
+
45
+ context.addRuntimeTypeImport("WrapperClass");
46
+ const constType = `WrapperClass<typeof ${localName}, ${className}>`;
47
+
48
+ context.declare({
49
+ name: className,
50
+ code:
51
+ `${doc}export const ${className}: ${constType} = ` +
52
+ `/* @__PURE__ */ (() => {\n${body}\n})() as ${constType};`,
53
+ owner,
54
+ });
55
+ };
56
+
57
+ export { declareFoldedClass, localClassName };
@@ -176,20 +176,39 @@ const stripLongestPrefix = (input: string, prefixes: string[]): string => {
176
176
  return best.length === 0 ? input : input.slice(best.length);
177
177
  };
178
178
 
179
+ const appendInitBootstrap = (context: ModuleContext, exportName: string): void => {
180
+ if (context.isTreeShaken) {
181
+ context.addBootstrapCall(`${exportName}();`, { moduleExports: [exportName] });
182
+ } else {
183
+ context.module.appendRegistration(`${exportName}();`, [exportName]);
184
+ }
185
+ };
186
+
187
+ const appendFinalizeBootstrap = (context: ModuleContext, exportName: string): void => {
188
+ if (context.isTreeShaken) {
189
+ context.addBootstrapCall(`onExit(${exportName});`, {
190
+ moduleExports: [exportName],
191
+ runtimeImports: ["onExit"],
192
+ });
193
+ } else {
194
+ context.addRuntimeImport("onExit");
195
+ context.module.appendRegistration(`onExit(${exportName});`, [exportName]);
196
+ }
197
+ };
198
+
179
199
  const appendBootstrapRegistration = (context: ModuleContext, fn: GirFunction, exportName: string): void => {
180
200
  if (fn.parameters.length > 0) {
181
201
  return;
182
202
  }
183
203
 
184
204
  if (fn.name === "init") {
185
- context.module.appendRegistration(`${exportName}();`, [exportName]);
205
+ appendInitBootstrap(context, exportName);
186
206
 
187
207
  return;
188
208
  }
189
209
 
190
210
  if (fn.name === "finalize") {
191
- context.addRuntimeImport("onExit");
192
- context.module.appendRegistration(`onExit(${exportName});`, [exportName]);
211
+ appendFinalizeBootstrap(context, exportName);
193
212
  }
194
213
  };
195
214
 
@@ -26,6 +26,14 @@ const renderInternGtype = (context: ModuleContext, typeName: string | undefined)
26
26
 
27
27
  if (context.namespace.name !== "GObject") {
28
28
  context.addRuntimeImport("typeFromName");
29
+
30
+ return `typeFromName(${sourceStringLiteral(typeName)})`;
31
+ }
32
+
33
+ if (context.isTreeShaken) {
34
+ context.module.imports.addNamed("@gtkx/runtime", "typeFromName", false, "runtimeTypeFromName");
35
+
36
+ return `runtimeTypeFromName(${sourceStringLiteral(typeName)})`;
29
37
  }
30
38
 
31
39
  return `typeFromName(${sourceStringLiteral(typeName)})`;
@@ -47,8 +55,13 @@ const renderGtypeExpression = (
47
55
  context: ModuleContext,
48
56
  typeFnName: string,
49
57
  typeName: string | undefined,
50
- ): string | undefined =>
51
- typeFnName === "intern" ? renderInternGtype(context, typeName) : renderResolveGtype(context, typeFnName);
58
+ ): string | undefined => {
59
+ if (typeFnName === "intern") {
60
+ return renderInternGtype(context, typeName);
61
+ }
62
+
63
+ return renderResolveGtype(context, typeFnName);
64
+ };
52
65
 
53
66
  const renderSourceGtype = (context: ModuleContext, source: TypeSource): string | undefined =>
54
67
  source.glibGetType === undefined