@gjsify/rolldown-plugin-gjsify 0.3.14

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 (89) hide show
  1. package/lib/app/browser.d.ts +17 -0
  2. package/lib/app/browser.js +77 -0
  3. package/lib/app/gjs.d.ts +27 -0
  4. package/lib/app/gjs.js +211 -0
  5. package/lib/app/index.d.ts +6 -0
  6. package/lib/app/index.js +3 -0
  7. package/lib/app/node.d.ts +17 -0
  8. package/lib/app/node.js +102 -0
  9. package/lib/globals.d.ts +4 -0
  10. package/lib/globals.js +9 -0
  11. package/lib/index.d.ts +17 -0
  12. package/lib/index.js +15 -0
  13. package/lib/library/index.d.ts +2 -0
  14. package/lib/library/index.js +1 -0
  15. package/lib/library/lib.d.ts +16 -0
  16. package/lib/library/lib.js +118 -0
  17. package/lib/plugin.d.ts +25 -0
  18. package/lib/plugin.js +67 -0
  19. package/lib/plugins/alias.d.ts +5 -0
  20. package/lib/plugins/alias.js +45 -0
  21. package/lib/plugins/css-as-string.d.ts +2 -0
  22. package/lib/plugins/css-as-string.js +34 -0
  23. package/lib/plugins/gjs-imports-empty.d.ts +2 -0
  24. package/lib/plugins/gjs-imports-empty.js +26 -0
  25. package/lib/plugins/process-stub.d.ts +28 -0
  26. package/lib/plugins/process-stub.js +60 -0
  27. package/lib/plugins/rewrite-node-modules-paths.d.ts +38 -0
  28. package/lib/plugins/rewrite-node-modules-paths.js +132 -0
  29. package/lib/plugins/shebang.d.ts +8 -0
  30. package/lib/plugins/shebang.js +26 -0
  31. package/lib/shims/console-gjs.d.ts +24 -0
  32. package/lib/shims/console-gjs.js +24 -0
  33. package/lib/types/app.d.ts +1 -0
  34. package/lib/types/app.js +1 -0
  35. package/lib/types/index.d.ts +3 -0
  36. package/lib/types/index.js +3 -0
  37. package/lib/types/plugin-options.d.ts +46 -0
  38. package/lib/types/plugin-options.js +1 -0
  39. package/lib/types/resolve-alias-options.d.ts +2 -0
  40. package/lib/types/resolve-alias-options.js +1 -0
  41. package/lib/utils/alias.d.ts +12 -0
  42. package/lib/utils/alias.js +29 -0
  43. package/lib/utils/auto-globals.d.ts +72 -0
  44. package/lib/utils/auto-globals.js +193 -0
  45. package/lib/utils/detect-free-globals.d.ts +18 -0
  46. package/lib/utils/detect-free-globals.js +268 -0
  47. package/lib/utils/entry-points.d.ts +2 -0
  48. package/lib/utils/entry-points.js +38 -0
  49. package/lib/utils/extension.d.ts +1 -0
  50. package/lib/utils/extension.js +7 -0
  51. package/lib/utils/index.d.ts +7 -0
  52. package/lib/utils/index.js +7 -0
  53. package/lib/utils/inline-static-reads.d.ts +11 -0
  54. package/lib/utils/inline-static-reads.js +549 -0
  55. package/lib/utils/merge.d.ts +2 -0
  56. package/lib/utils/merge.js +23 -0
  57. package/lib/utils/scan-globals.d.ts +32 -0
  58. package/lib/utils/scan-globals.js +85 -0
  59. package/package.json +68 -0
  60. package/src/app/browser.ts +102 -0
  61. package/src/app/gjs.ts +260 -0
  62. package/src/app/index.ts +6 -0
  63. package/src/app/node.ts +128 -0
  64. package/src/globals.ts +11 -0
  65. package/src/index.ts +32 -0
  66. package/src/library/index.ts +2 -0
  67. package/src/library/lib.ts +142 -0
  68. package/src/plugin.ts +91 -0
  69. package/src/plugins/alias.ts +53 -0
  70. package/src/plugins/css-as-string.ts +37 -0
  71. package/src/plugins/gjs-imports-empty.ts +29 -0
  72. package/src/plugins/process-stub.ts +91 -0
  73. package/src/plugins/rewrite-node-modules-paths.ts +169 -0
  74. package/src/plugins/shebang.ts +33 -0
  75. package/src/shims/console-gjs.ts +25 -0
  76. package/src/types/app.ts +1 -0
  77. package/src/types/index.ts +3 -0
  78. package/src/types/plugin-options.ts +48 -0
  79. package/src/types/resolve-alias-options.ts +1 -0
  80. package/src/utils/alias.ts +46 -0
  81. package/src/utils/auto-globals.ts +283 -0
  82. package/src/utils/detect-free-globals.ts +278 -0
  83. package/src/utils/entry-points.ts +48 -0
  84. package/src/utils/extension.ts +7 -0
  85. package/src/utils/index.ts +7 -0
  86. package/src/utils/inline-static-reads.ts +541 -0
  87. package/src/utils/merge.ts +22 -0
  88. package/src/utils/scan-globals.ts +91 -0
  89. package/tsconfig.json +16 -0
@@ -0,0 +1,278 @@
1
+ // Detect free (unbound) global identifiers in bundled JS output.
2
+ //
3
+ // Used by the `--globals auto` two-pass build: the first esbuild pass
4
+ // produces a minified bundle without globals injection, this module
5
+ // parses it with acorn and finds references to known GJS globals that
6
+ // are not locally declared. The result feeds the second pass's inject
7
+ // stub so only actually-needed globals are registered.
8
+
9
+ import * as acorn from 'acorn';
10
+ import * as walk from 'acorn-walk';
11
+ import { GJS_GLOBALS_MAP } from '@gjsify/resolve-npm/globals-map';
12
+
13
+ const KNOWN_GLOBALS = new Set(Object.keys(GJS_GLOBALS_MAP as Record<string, string>));
14
+
15
+ /**
16
+ * Method markers — `<host>.<method>(…)` patterns that imply a global
17
+ * identifier should be injected even though the identifier itself never
18
+ * appears in the bundle.
19
+ *
20
+ * Example: a project that calls `navigator.getGamepads()` doesn't reference
21
+ * any of the gamepad-related identifiers in the globals map, but it still
22
+ * needs `@gjsify/gamepad/register` to patch `navigator` with the method.
23
+ * This marker maps `navigator.getGamepads` → inject the `GamepadEvent`
24
+ * register path (which is the gamepad package's register entry).
25
+ *
26
+ * Keyed by `host.method` (lowercase host, exact method name). Values are
27
+ * KNOWN_GLOBALS identifiers — the detector adds them as free globals if
28
+ * the corresponding member expression is found in the bundle.
29
+ */
30
+ const METHOD_MARKERS: Record<string, string> = {
31
+ // Gamepad API — navigator.getGamepads is patched on by @gjsify/gamepad/register
32
+ 'navigator.getGamepads': 'GamepadEvent',
33
+ // WebRTC — navigator.mediaDevices is patched on by @gjsify/webrtc/register/media-devices
34
+ 'navigator.mediaDevices': 'MediaDevices',
35
+ // WebAssembly Promise APIs — the runtime stubs throw at first call, so
36
+ // any reference to these methods needs the `@gjsify/webassembly` polyfill.
37
+ // The register entry replaces the stubs with wrappers around the working
38
+ // synchronous `new WebAssembly.{Module,Instance}` constructors.
39
+ 'WebAssembly.compile': 'WebAssembly',
40
+ 'WebAssembly.compileStreaming': 'WebAssembly',
41
+ 'WebAssembly.instantiate': 'WebAssembly',
42
+ 'WebAssembly.instantiateStreaming': 'WebAssembly',
43
+ 'WebAssembly.validate': 'WebAssembly',
44
+ // Note: URL.createObjectURL / URL.revokeObjectURL don't need markers —
45
+ // they are first-class static methods on @gjsify/url's URL class, so the
46
+ // free `URL` identifier (detected directly, maps to
47
+ // @gjsify/node-globals/register/url in GJS_GLOBALS_MAP) already pulls in
48
+ // the correct register module.
49
+ };
50
+
51
+ /**
52
+ * Extract all bound names from a binding pattern
53
+ * (Identifier, ObjectPattern, ArrayPattern, AssignmentPattern, RestElement).
54
+ */
55
+ function extractBindingNames(node: acorn.AnyNode): string[] {
56
+ if (!node) return [];
57
+ switch (node.type) {
58
+ case 'Identifier':
59
+ return [(node as acorn.Identifier).name];
60
+ case 'ObjectPattern':
61
+ return (node as acorn.ObjectPattern).properties.flatMap((p) =>
62
+ p.type === 'RestElement'
63
+ ? extractBindingNames(p.argument)
64
+ : extractBindingNames((p as acorn.Property).value),
65
+ );
66
+ case 'ArrayPattern':
67
+ return (node as acorn.ArrayPattern).elements.flatMap((e) =>
68
+ e
69
+ ? e.type === 'RestElement'
70
+ ? extractBindingNames(e.argument)
71
+ : extractBindingNames(e)
72
+ : [],
73
+ );
74
+ case 'AssignmentPattern':
75
+ return extractBindingNames((node as acorn.AssignmentPattern).left);
76
+ case 'RestElement':
77
+ return extractBindingNames((node as acorn.RestElement).argument);
78
+ default:
79
+ return [];
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Parse bundled JS code and return the set of free (unbound) identifiers
85
+ * that match known GJS globals from `GJS_GLOBALS_MAP`.
86
+ *
87
+ * "Free" means the identifier is referenced but never declared in the
88
+ * module (var/let/const/function/class/import/param/catch).
89
+ *
90
+ * After esbuild bundling + minification, local variables that shadow
91
+ * globals are renamed to short names, so any surviving known-global name
92
+ * in the output is almost certainly a true global reference. The
93
+ * declared-names check is a safety net for edge cases where esbuild
94
+ * keeps the original name.
95
+ *
96
+ * `typeof X` references ARE included — if code guards with
97
+ * `typeof fetch !== 'undefined'`, it intends to use fetch when available
98
+ * and we can provide it.
99
+ */
100
+ export function detectFreeGlobals(code: string): Set<string> {
101
+ const ast = acorn.parse(code, {
102
+ ecmaVersion: 'latest',
103
+ sourceType: 'module',
104
+ });
105
+
106
+ // --- Pass 1: collect all declared names across the entire module ---
107
+ const declaredNames = new Set<string>();
108
+
109
+ walk.simple(ast, {
110
+ VariableDeclarator(node: acorn.VariableDeclarator) {
111
+ for (const name of extractBindingNames(node.id)) {
112
+ declaredNames.add(name);
113
+ }
114
+ },
115
+ FunctionDeclaration(node: acorn.FunctionDeclaration) {
116
+ if (node.id) declaredNames.add(node.id.name);
117
+ for (const param of node.params) {
118
+ for (const name of extractBindingNames(param)) {
119
+ declaredNames.add(name);
120
+ }
121
+ }
122
+ },
123
+ FunctionExpression(node: acorn.FunctionExpression) {
124
+ if (node.id) declaredNames.add(node.id.name);
125
+ for (const param of node.params) {
126
+ for (const name of extractBindingNames(param)) {
127
+ declaredNames.add(name);
128
+ }
129
+ }
130
+ },
131
+ ArrowFunctionExpression(node: acorn.ArrowFunctionExpression) {
132
+ for (const param of node.params) {
133
+ for (const name of extractBindingNames(param)) {
134
+ declaredNames.add(name);
135
+ }
136
+ }
137
+ },
138
+ ClassDeclaration(node: acorn.ClassDeclaration) {
139
+ if (node.id) declaredNames.add(node.id.name);
140
+ },
141
+ ImportSpecifier(node: acorn.ImportSpecifier) {
142
+ declaredNames.add(node.local.name);
143
+ },
144
+ ImportDefaultSpecifier(node: acorn.ImportDefaultSpecifier) {
145
+ declaredNames.add(node.local.name);
146
+ },
147
+ ImportNamespaceSpecifier(node: acorn.ImportNamespaceSpecifier) {
148
+ declaredNames.add(node.local.name);
149
+ },
150
+ CatchClause(node: acorn.CatchClause) {
151
+ if (node.param) {
152
+ for (const name of extractBindingNames(node.param)) {
153
+ declaredNames.add(name);
154
+ }
155
+ }
156
+ },
157
+ });
158
+
159
+ // --- Pass 2: find Identifier nodes in reference position ---
160
+ // Also detects MemberExpressions like `globalThis.X` / `global.X` /
161
+ // `window.X` / `self.X` where X is a known global. esbuild's `define`
162
+ // config replaces `global`/`window` with `globalThis`, but we accept
163
+ // all four host-object names for safety (esbuild also never renames
164
+ // these because they are language keywords / pre-defined globals).
165
+ const freeGlobals = new Set<string>();
166
+ const HOST_OBJECTS = new Set(['globalThis', 'global', 'window', 'self', 'globalObject']);
167
+
168
+ walk.ancestor(ast, {
169
+ MemberExpression(node: acorn.MemberExpression) {
170
+ // Only dot-access — skip computed (bracket) access since the
171
+ // property is then a dynamic Expression, not a known name.
172
+ if (node.computed) return;
173
+ if (node.object.type !== 'Identifier') return;
174
+ if (node.property.type !== 'Identifier') return;
175
+
176
+ const objName = (node.object as acorn.Identifier).name;
177
+ const propName = (node.property as acorn.Identifier).name;
178
+
179
+ // Pattern A: globalThis.X / global.X / window.X / self.X
180
+ // The property is a known global identifier itself.
181
+ if (HOST_OBJECTS.has(objName)) {
182
+ if (KNOWN_GLOBALS.has(propName)) {
183
+ freeGlobals.add(propName);
184
+ }
185
+ return;
186
+ }
187
+
188
+ // Pattern B: known-instance method markers like
189
+ // `navigator.getGamepads` → marker map forwards to a global
190
+ // identifier that triggers the right register path even though
191
+ // the identifier itself never appears in the bundle.
192
+ const markerKey = `${objName}.${propName}`;
193
+ const markerTarget = METHOD_MARKERS[markerKey];
194
+ if (markerTarget && KNOWN_GLOBALS.has(markerTarget)) {
195
+ freeGlobals.add(markerTarget);
196
+ }
197
+ },
198
+ Identifier(node: acorn.Identifier, ancestors: acorn.AnyNode[]) {
199
+ const name = node.name;
200
+
201
+ // Quick filter: only check known globals
202
+ if (!KNOWN_GLOBALS.has(name)) return;
203
+
204
+ // Skip if locally declared
205
+ if (declaredNames.has(name)) return;
206
+
207
+ // Determine if this Identifier is in a reference position
208
+ // by checking the parent node.
209
+ const parent = ancestors[ancestors.length - 2];
210
+ if (!parent) {
211
+ freeGlobals.add(name);
212
+ return;
213
+ }
214
+
215
+ switch (parent.type) {
216
+ // obj.prop — skip if this is the non-computed property
217
+ case 'MemberExpression': {
218
+ const mem = parent as acorn.MemberExpression;
219
+ if (mem.property === (node as acorn.AnyNode) && !mem.computed) return;
220
+ break;
221
+ }
222
+ // { key: value } — skip if this is the non-computed key
223
+ case 'Property': {
224
+ const prop = parent as acorn.Property;
225
+ if (prop.key === (node as acorn.AnyNode) && !prop.computed) return;
226
+ break;
227
+ }
228
+ // Method/property definitions in classes
229
+ case 'MethodDefinition':
230
+ case 'PropertyDefinition': {
231
+ const def = parent as acorn.MethodDefinition | acorn.PropertyDefinition;
232
+ if (def.key === (node as acorn.AnyNode) && !def.computed) return;
233
+ break;
234
+ }
235
+ // label: — skip
236
+ case 'LabeledStatement': {
237
+ const labeled = parent as acorn.LabeledStatement;
238
+ if (labeled.label === (node as acorn.AnyNode)) return;
239
+ break;
240
+ }
241
+ // export { X as Y } — skip the exported name
242
+ case 'ExportSpecifier': {
243
+ const spec = parent as acorn.ExportSpecifier;
244
+ if (spec.exported === (node as acorn.AnyNode)) return;
245
+ break;
246
+ }
247
+ // Declaration ids (function name, class name, variable id)
248
+ // are already in declaredNames, but guard anyway
249
+ case 'FunctionDeclaration':
250
+ case 'FunctionExpression':
251
+ case 'ClassDeclaration':
252
+ case 'ClassExpression': {
253
+ const decl = parent as
254
+ | acorn.FunctionDeclaration
255
+ | acorn.FunctionExpression
256
+ | acorn.ClassDeclaration
257
+ | acorn.ClassExpression;
258
+ if (decl.id === (node as acorn.AnyNode)) return;
259
+ break;
260
+ }
261
+ case 'VariableDeclarator': {
262
+ const vd = parent as acorn.VariableDeclarator;
263
+ if (vd.id === (node as acorn.AnyNode)) return;
264
+ break;
265
+ }
266
+ // import { X } / import X — already in declaredNames
267
+ case 'ImportSpecifier':
268
+ case 'ImportDefaultSpecifier':
269
+ case 'ImportNamespaceSpecifier':
270
+ return;
271
+ }
272
+
273
+ freeGlobals.add(name);
274
+ },
275
+ });
276
+
277
+ return freeGlobals;
278
+ }
@@ -0,0 +1,48 @@
1
+ // Glob expansion for Rolldown entry-point input.
2
+ //
3
+ // Rolldown's `input` accepts:
4
+ // - a single path string
5
+ // - an array of strings
6
+ // - a record mapping output names to input paths
7
+ //
8
+ // `globToEntryPoints` accepts the same shapes and expands any glob patterns
9
+ // against the filesystem via `fast-glob`. Pure-string entries return as-is
10
+ // when they don't contain wildcards (fast-glob handles that gracefully).
11
+ //
12
+ // `.d.ts` files are always excluded — they are type-only declarations,
13
+ // not parseable as runtime modules. esbuild handled this implicitly via
14
+ // its loader table; Rolldown's Oxc parser errors on declaration-only
15
+ // shapes (`get foo(): T;`).
16
+
17
+ import fastGlob from 'fast-glob';
18
+
19
+ export type EntryPoints = string | string[] | Record<string, string>;
20
+
21
+ const DEFAULT_IGNORE = ['**/*.d.ts'];
22
+
23
+ export const globToEntryPoints = async (
24
+ _entryPoints: EntryPoints | undefined,
25
+ ignore: string[] = [],
26
+ ): Promise<EntryPoints | undefined> => {
27
+ if (_entryPoints === undefined) return undefined;
28
+ const fullIgnore = [...DEFAULT_IGNORE, ...ignore];
29
+
30
+ if (typeof _entryPoints === 'string') {
31
+ const expanded = await fastGlob([_entryPoints], { ignore: fullIgnore });
32
+ return expanded;
33
+ }
34
+
35
+ if (Array.isArray(_entryPoints)) {
36
+ return await fastGlob(_entryPoints, { ignore: fullIgnore });
37
+ }
38
+
39
+ const entryPoints: Record<string, string> = {};
40
+ for (const input in _entryPoints) {
41
+ const output = _entryPoints[input];
42
+ const inputs = await fastGlob(input, { ignore: fullIgnore });
43
+ for (const matched of inputs) {
44
+ entryPoints[matched] = output;
45
+ }
46
+ }
47
+ return entryPoints;
48
+ };
@@ -0,0 +1,7 @@
1
+ export const getJsExtensions = (allowExt?: string) => {
2
+ const extensions: Record<string, string> = {'.js': '.js', '.ts': '.js', '.mts': '.js', '.cts': '.js', '.cjs': '.js', '.mjs': '.js'};
3
+ if(allowExt && extensions[allowExt]) {
4
+ delete extensions[allowExt]
5
+ }
6
+ return extensions;
7
+ }
@@ -0,0 +1,7 @@
1
+ export * from './alias.js';
2
+ export * from './entry-points.js';
3
+ export * from './extension.js';
4
+ export * from './merge.js';
5
+ export { detectFreeGlobals } from './detect-free-globals.js';
6
+ export { resolveGlobalsList, writeRegisterInjectFile } from './scan-globals.js';
7
+ export { inlineStaticReads } from './inline-static-reads.js';