@gjsify/rolldown-plugin-gjsify 0.41.0 → 0.43.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/lib/app/browser.d.ts +2 -0
- package/lib/app/browser.js +4 -2
- package/lib/app/gjs.d.ts +10 -0
- package/lib/app/gjs.js +17 -4
- package/lib/app/nativescript.d.ts +2 -0
- package/lib/app/nativescript.js +4 -2
- package/lib/app/node.d.ts +2 -0
- package/lib/app/node.js +4 -2
- package/lib/index.d.ts +2 -1
- package/lib/index.js +1 -1
- package/lib/library/lib.d.ts +2 -0
- package/lib/library/lib.js +12 -1
- package/lib/plugin.d.ts +25 -0
- package/lib/plugin.js +1 -0
- package/lib/plugins/gi-runtime-paths.d.ts +27 -5
- package/lib/plugins/gi-runtime-paths.js +74 -27
- package/lib/plugins/intl-segmenter-banner.d.ts +1 -0
- package/lib/plugins/intl-segmenter-banner.js +42 -0
- package/lib/plugins/process-stub.d.ts +7 -0
- package/lib/plugins/process-stub.js +5 -1
- package/lib/plugins/rewrite-node-modules-paths.d.ts +35 -0
- package/lib/plugins/rewrite-node-modules-paths.js +57 -5
- package/lib/plugins/unresolved-workspace-import.d.ts +48 -2
- package/lib/plugins/unresolved-workspace-import.js +71 -7
- package/lib/types/plugin-options.d.ts +12 -0
- package/lib/utils/detect-free-globals.js +29 -7
- package/lib/utils/index.d.ts +2 -0
- package/lib/utils/index.js +1 -0
- package/lib/utils/inline-static-reads.d.ts +0 -7
- package/lib/utils/inline-static-reads.js +79 -16
- package/lib/utils/jsx-survival.d.ts +31 -0
- package/lib/utils/jsx-survival.js +102 -0
- package/package.json +10 -8
package/lib/app/browser.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { RolldownOptions, RolldownPluginOption } from 'rolldown';
|
|
2
2
|
import type { PluginOptions } from '../types/plugin-options.js';
|
|
3
3
|
export interface BrowserBuildConfig {
|
|
4
|
+
/** Transforms that must see the ORIGINAL source; composed before the caller's plugins. */
|
|
5
|
+
prePlugins: RolldownPluginOption[];
|
|
4
6
|
options: RolldownOptions;
|
|
5
7
|
plugins: RolldownPluginOption[];
|
|
6
8
|
}
|
package/lib/app/browser.js
CHANGED
|
@@ -86,11 +86,13 @@ export const setupForBrowser = async (input) => {
|
|
|
86
86
|
// the unresolved-workspace-import guard below so the guard reports the same
|
|
87
87
|
// promise the alias layer made.
|
|
88
88
|
const aliasEntries = flattenAliases(aliasMap);
|
|
89
|
+
// Reflection reads the ORIGINAL TypeScript, so it runs before any user
|
|
90
|
+
// transform can strip the annotations out from under it (see `GjsifyConfig`).
|
|
91
|
+
const prePlugins = [deepkitPlugin({ reflection: input.pluginOptions.reflection })];
|
|
89
92
|
const plugins = [
|
|
90
93
|
gjsImportsEmptyPlugin(),
|
|
91
94
|
aliasPlugin({ entries: aliasEntries }),
|
|
92
95
|
blueprintPlugin(),
|
|
93
|
-
deepkitPlugin({ reflection: input.pluginOptions.reflection }),
|
|
94
96
|
cssAsStringPlugin(),
|
|
95
97
|
// `order: 'post'` — see app/gjs.ts. The browser target's whole job is to
|
|
96
98
|
// replace Node builtins with their `@gjsify/*` browser entries; when one
|
|
@@ -103,7 +105,7 @@ export const setupForBrowser = async (input) => {
|
|
|
103
105
|
isExternal: (id) => external.includes(id),
|
|
104
106
|
}),
|
|
105
107
|
];
|
|
106
|
-
return { options, plugins };
|
|
108
|
+
return { options, prePlugins, plugins };
|
|
107
109
|
};
|
|
108
110
|
function flattenAliases(map) {
|
|
109
111
|
const out = {};
|
package/lib/app/gjs.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { RolldownOptions, RolldownPluginOption } from 'rolldown';
|
|
2
2
|
import type { PluginOptions } from '../types/plugin-options.js';
|
|
3
|
+
import type { GiSystemProbe } from '../plugins/gi-runtime-paths.js';
|
|
3
4
|
/** Resolved Rolldown configuration template + plugins for `--app gjs`. */
|
|
4
5
|
export interface GjsBuildConfig {
|
|
6
|
+
/** Transforms that must see the ORIGINAL source; composed before the caller's plugins. */
|
|
7
|
+
prePlugins: RolldownPluginOption[];
|
|
5
8
|
options: RolldownOptions;
|
|
6
9
|
plugins: RolldownPluginOption[];
|
|
7
10
|
}
|
|
@@ -26,6 +29,13 @@ export interface GjsFactoryInput {
|
|
|
26
29
|
* `"…"` → custom line, supports `${env:NAME[:-default]}` placeholders
|
|
27
30
|
*/
|
|
28
31
|
shebang?: boolean | string;
|
|
32
|
+
/**
|
|
33
|
+
* System GI library dirs the byte-1 prologue tries at RUNTIME — see `GiSystemProbe`.
|
|
34
|
+
* The only half a build can honestly fill: the build machine is not the host that
|
|
35
|
+
* runs the bundle, so what travels is a candidate plus the markers that decide it
|
|
36
|
+
* there. See `GjsifyPluginInput.giSystemProbes` for why the relative half is absent.
|
|
37
|
+
*/
|
|
38
|
+
giSystemProbes?: readonly GiSystemProbe[];
|
|
29
39
|
/** Plugin options forwarded to sub-plugins (deepkit, css, …). */
|
|
30
40
|
pluginOptions: PluginOptions;
|
|
31
41
|
}
|
package/lib/app/gjs.js
CHANGED
|
@@ -158,6 +158,9 @@ export const setupForGjs = async (input) => {
|
|
|
158
158
|
'random-access-file': 'random-access-file/index.js',
|
|
159
159
|
...flattenAliases(aliasMap),
|
|
160
160
|
};
|
|
161
|
+
// Reflection reads the ORIGINAL TypeScript, so it runs before any user
|
|
162
|
+
// transform can strip the annotations out from under it (see `GjsifyConfig`).
|
|
163
|
+
const prePlugins = [deepkitPlugin({ reflection: input.pluginOptions.reflection })];
|
|
161
164
|
const plugins = [
|
|
162
165
|
// Virtual-entry plugin runs FIRST so its resolveId/load match the
|
|
163
166
|
// synthetic input ids that `wrapInputWithSideEffects` produces.
|
|
@@ -184,14 +187,17 @@ export const setupForGjs = async (input) => {
|
|
|
184
187
|
// first.
|
|
185
188
|
externalsPlugin(external, { name: 'gjsify-gjs-externalize' }),
|
|
186
189
|
blueprintPlugin(),
|
|
187
|
-
deepkitPlugin({ reflection: input.pluginOptions.reflection }),
|
|
188
190
|
// GTK4's CSS engine is much older than browser engines — its
|
|
189
191
|
// parser predates nesting + many modern selectors. Targeting
|
|
190
192
|
// `firefox: 60 << 16` makes lightningcss flatten the source
|
|
191
193
|
// into the subset GTK4 understands.
|
|
192
194
|
cssAsStringPlugin({ targets: { firefox: 60 << 16 } }),
|
|
193
195
|
nodeModulesPathRewritePlugin({ bundleDir, runtimeResolve: format === 'esm' }),
|
|
194
|
-
processStubPlugin({
|
|
196
|
+
processStubPlugin({
|
|
197
|
+
userBanner: input.userBanner,
|
|
198
|
+
captureBundleUrl: format === 'esm',
|
|
199
|
+
giSystemProbes: input.giSystemProbes,
|
|
200
|
+
}),
|
|
195
201
|
// resolveShebangLine returns null when disabled, else the resolved line
|
|
196
202
|
// with `${env:…}` expanded.
|
|
197
203
|
(() => {
|
|
@@ -205,9 +211,16 @@ export const setupForGjs = async (input) => {
|
|
|
205
211
|
// Unsupported URI scheme for importing: node`) — the same failure
|
|
206
212
|
// `utils/gjs-bundle-guard.ts` catches at emit time, caught here at its
|
|
207
213
|
// source with the importer and cause named.
|
|
208
|
-
unresolvedWorkspaceImportPlugin({
|
|
214
|
+
unresolvedWorkspaceImportPlugin({
|
|
215
|
+
target: 'gjs',
|
|
216
|
+
aliases: aliasEntries,
|
|
217
|
+
isExternal: external,
|
|
218
|
+
...(input.pluginOptions.toolchainAnchor !== undefined
|
|
219
|
+
? { toolchainAnchor: input.pluginOptions.toolchainAnchor }
|
|
220
|
+
: {}),
|
|
221
|
+
}),
|
|
209
222
|
];
|
|
210
|
-
return { options, plugins };
|
|
223
|
+
return { options, prePlugins, plugins };
|
|
211
224
|
};
|
|
212
225
|
/** Copy the alias map, dropping entries with an empty target. */
|
|
213
226
|
function flattenAliases(map) {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { RolldownOptions, RolldownPluginOption } from 'rolldown';
|
|
2
2
|
import type { PluginOptions } from '../types/plugin-options.js';
|
|
3
3
|
export interface NativescriptBuildConfig {
|
|
4
|
+
/** Transforms that must see the ORIGINAL source; composed before the caller's plugins. */
|
|
5
|
+
prePlugins: RolldownPluginOption[];
|
|
4
6
|
options: RolldownOptions;
|
|
5
7
|
plugins: RolldownPluginOption[];
|
|
6
8
|
}
|
package/lib/app/nativescript.js
CHANGED
|
@@ -124,6 +124,9 @@ export const setupForNativescript = async (input) => {
|
|
|
124
124
|
// the unresolved-workspace-import guard below so the guard reports the same
|
|
125
125
|
// promise the alias layer made.
|
|
126
126
|
const aliasEntries = flattenAliases(aliasMap);
|
|
127
|
+
// Reflection reads the ORIGINAL TypeScript, so it runs before any user
|
|
128
|
+
// transform can strip the annotations out from under it (see `GjsifyConfig`).
|
|
129
|
+
const prePlugins = [deepkitPlugin({ reflection: input.pluginOptions.reflection })];
|
|
127
130
|
const plugins = [
|
|
128
131
|
gjsImportsEmptyPlugin(),
|
|
129
132
|
// Platform-specific source variants (`*.android` / `*.ios` /
|
|
@@ -135,7 +138,6 @@ export const setupForNativescript = async (input) => {
|
|
|
135
138
|
// NO cssAsStringPlugin — NS ships its own CSS pipeline via
|
|
136
139
|
// @nativescript/core; .css imports are handled by the consuming
|
|
137
140
|
// @nativescript/webpack or @nativescript/vite build
|
|
138
|
-
deepkitPlugin({ reflection: input.pluginOptions.reflection }),
|
|
139
141
|
// `order: 'post'` — see app/gjs.ts. Native-bridge identifiers
|
|
140
142
|
// (`java.*`, `NS*`, …) are ambient GLOBALS on this target, never
|
|
141
143
|
// imports, so they never reach this hook; only a `@gjsify/*` edge does,
|
|
@@ -147,7 +149,7 @@ export const setupForNativescript = async (input) => {
|
|
|
147
149
|
isExternal,
|
|
148
150
|
}),
|
|
149
151
|
];
|
|
150
|
-
return { options, plugins };
|
|
152
|
+
return { options, prePlugins, plugins };
|
|
151
153
|
};
|
|
152
154
|
function flattenAliases(map) {
|
|
153
155
|
const out = {};
|
package/lib/app/node.d.ts
CHANGED
|
@@ -54,6 +54,8 @@ export declare function isGjsSourceBuild(options: {
|
|
|
54
54
|
registerInject?: string | undefined;
|
|
55
55
|
}): boolean;
|
|
56
56
|
export interface NodeBuildConfig {
|
|
57
|
+
/** Transforms that must see the ORIGINAL source; composed before the caller's plugins. */
|
|
58
|
+
prePlugins: RolldownPluginOption[];
|
|
57
59
|
options: RolldownOptions;
|
|
58
60
|
plugins: RolldownPluginOption[];
|
|
59
61
|
}
|
package/lib/app/node.js
CHANGED
|
@@ -206,6 +206,9 @@ export const setupForNode = async (input) => {
|
|
|
206
206
|
},
|
|
207
207
|
treeshake: true,
|
|
208
208
|
};
|
|
209
|
+
// Reflection reads the ORIGINAL TypeScript, so it runs before any user
|
|
210
|
+
// transform can strip the annotations out from under it (see `GjsifyConfig`).
|
|
211
|
+
const prePlugins = [deepkitPlugin({ reflection: input.pluginOptions.reflection })];
|
|
209
212
|
const plugins = [
|
|
210
213
|
// Virtual-entry plugin runs FIRST so its resolveId/load match the synthetic
|
|
211
214
|
// `\0gjsify-entry:` ids `wrapInputWithSideEffects` produces (no-op when
|
|
@@ -238,7 +241,6 @@ export const setupForNode = async (input) => {
|
|
|
238
241
|
// so a GJS app entry with composite-template windows must build for
|
|
239
242
|
// `--app node` too. Claims only `.blp` — inert for plain-Node bundles.
|
|
240
243
|
blueprintPlugin(),
|
|
241
|
-
deepkitPlugin({ reflection: input.pluginOptions.reflection }),
|
|
242
244
|
// On a node-gi build (REAL GTK on Node) lower the CSS to GTK 4's subset just
|
|
243
245
|
// as `--app gjs` does, so nesting/`&` is FLATTENED before it reaches
|
|
244
246
|
// `Gtk.CssProvider.load_from_string` — GTK 4's parser otherwise rejects
|
|
@@ -254,7 +256,7 @@ export const setupForNode = async (input) => {
|
|
|
254
256
|
// specifier at runtime.
|
|
255
257
|
unresolvedWorkspaceImportPlugin({ target: 'node', aliases: aliasEntries, isExternal: external }),
|
|
256
258
|
];
|
|
257
|
-
return { options, plugins };
|
|
259
|
+
return { options, prePlugins, plugins };
|
|
258
260
|
};
|
|
259
261
|
function flattenAliases(map) {
|
|
260
262
|
const out = {};
|
package/lib/index.d.ts
CHANGED
|
@@ -2,10 +2,11 @@ export * from './types/index.js';
|
|
|
2
2
|
export * from './utils/index.js';
|
|
3
3
|
export * from './app/index.js';
|
|
4
4
|
export * from './library/index.js';
|
|
5
|
-
export { REWRITE_FILTER, extractPackageSpec, getBundleDirFromOutput, rewriteContents, shouldRewrite, nodeModulesPathRewritePlugin, } from './plugins/rewrite-node-modules-paths.js';
|
|
5
|
+
export { REWRITE_FILTER, extractPackageSpec, getBundleDirFromOutput, rewriteContents, shouldRewrite, shouldInline, nodeModulesPathRewritePlugin, } from './plugins/rewrite-node-modules-paths.js';
|
|
6
6
|
export type { NodeModulesPathRewriteOptions, RewriteResult } from './plugins/rewrite-node-modules-paths.js';
|
|
7
7
|
export { processStubPlugin, GJS_PROCESS_STUB, composeBanner } from './plugins/process-stub.js';
|
|
8
8
|
export { giRuntimePathsStub } from './plugins/gi-runtime-paths.js';
|
|
9
|
+
export type { GiSystemProbe } from './plugins/gi-runtime-paths.js';
|
|
9
10
|
export type { ProcessStubPluginOptions } from './plugins/process-stub.js';
|
|
10
11
|
export { cssAsStringPlugin } from './plugins/css-as-string.js';
|
|
11
12
|
export { textLoaderPlugin } from './plugins/text-loader.js';
|
package/lib/index.js
CHANGED
|
@@ -3,7 +3,7 @@ export * from './types/index.js';
|
|
|
3
3
|
export * from './utils/index.js';
|
|
4
4
|
export * from './app/index.js';
|
|
5
5
|
export * from './library/index.js';
|
|
6
|
-
export { REWRITE_FILTER, extractPackageSpec, getBundleDirFromOutput, rewriteContents, shouldRewrite, nodeModulesPathRewritePlugin, } from './plugins/rewrite-node-modules-paths.js';
|
|
6
|
+
export { REWRITE_FILTER, extractPackageSpec, getBundleDirFromOutput, rewriteContents, shouldRewrite, shouldInline, nodeModulesPathRewritePlugin, } from './plugins/rewrite-node-modules-paths.js';
|
|
7
7
|
export { processStubPlugin, GJS_PROCESS_STUB, composeBanner } from './plugins/process-stub.js';
|
|
8
8
|
export { giRuntimePathsStub } from './plugins/gi-runtime-paths.js';
|
|
9
9
|
export { cssAsStringPlugin } from './plugins/css-as-string.js';
|
package/lib/library/lib.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { RolldownOptions, RolldownPluginOption } from 'rolldown';
|
|
2
2
|
import type { PluginOptions } from '../types/plugin-options.js';
|
|
3
3
|
export interface LibBuildConfig {
|
|
4
|
+
/** Transforms that must see the ORIGINAL source; composed before the caller's plugins. */
|
|
5
|
+
prePlugins: RolldownPluginOption[];
|
|
4
6
|
options: RolldownOptions;
|
|
5
7
|
plugins: RolldownPluginOption[];
|
|
6
8
|
}
|
package/lib/library/lib.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// library config: consumers downstream (downstream apps using `gjsify
|
|
10
10
|
// build --app gjs|node|browser`) re-bundle and apply their own target
|
|
11
11
|
// lowering. Library output stays maximally portable.
|
|
12
|
+
import blueprintPlugin from '@gjsify/vite-plugin-blueprint';
|
|
12
13
|
import { aliasPlugin } from '../plugins/alias.js';
|
|
13
14
|
import { cssAsStringPlugin } from '../plugins/css-as-string.js';
|
|
14
15
|
import { externalsPlugin } from '../plugins/externals.js';
|
|
@@ -90,8 +91,18 @@ export const setupLib = async (input) => {
|
|
|
90
91
|
// tiny JS module re-exporting the CSS source, which preserveModules
|
|
91
92
|
// emits 1:1.
|
|
92
93
|
cssAsStringPlugin(),
|
|
94
|
+
// Same shape as the CSS hook above, and needed for the same reason: without it a `.blp`
|
|
95
|
+
// import reaches rolldown's JS parser, and a Blueprint file's first line — `using Gtk 4.0;`
|
|
96
|
+
// — is VALID JavaScript (a `using` resource declaration missing its initializer), so the
|
|
97
|
+
// build dies on "Using declarations must have an initializer" and nothing points at
|
|
98
|
+
// Blueprint. Only the app factories installed it, which quietly made Blueprint an
|
|
99
|
+
// application-only feature: a shared widget in a library package could not have a
|
|
100
|
+
// template, so its captions had to be assigned from TypeScript, where they carry no
|
|
101
|
+
// `translatable` attribute and no catalogue can ever reach them.
|
|
102
|
+
blueprintPlugin(),
|
|
93
103
|
];
|
|
94
|
-
|
|
104
|
+
// Library mode wires no reflection at all; the empty slot says so out loud.
|
|
105
|
+
return { options, prePlugins: [], plugins };
|
|
95
106
|
};
|
|
96
107
|
/**
|
|
97
108
|
* Whether a specifier should stay external in library mode: a bare /
|
package/lib/plugin.d.ts
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
import type { RolldownOptions, RolldownPluginOption } from 'rolldown';
|
|
2
2
|
import type { PluginOptions } from './types/plugin-options.js';
|
|
3
|
+
import type { GiSystemProbe } from './plugins/gi-runtime-paths.js';
|
|
3
4
|
export interface GjsifyConfig {
|
|
4
5
|
options: RolldownOptions;
|
|
6
|
+
/**
|
|
7
|
+
* Transforms that must see the ORIGINAL module source — composed BEFORE the
|
|
8
|
+
* caller's own plugins, while `plugins` stays after them.
|
|
9
|
+
*
|
|
10
|
+
* The split exists because the two positions answer different questions. User
|
|
11
|
+
* plugins go first so their `resolveId`/`load` wins over gjsify's alias and
|
|
12
|
+
* externals chain. But a user `transform: { order: 'pre' }` that REWRITES the
|
|
13
|
+
* source then also beats gjsify's own pre-transforms, and Deepkit's type
|
|
14
|
+
* compiler is one of them: `@gjsify/rolldown-plugin-solid` runs
|
|
15
|
+
* `@babel/preset-typescript` over every `.tsx`, so Deepkit was handed a file
|
|
16
|
+
* with no type annotations left. MEASURED on one `.tsx` carrying both a
|
|
17
|
+
* `typeOf<T>()` and a JSX tag: `const reflected = typeOf()` in the artifact,
|
|
18
|
+
* exit 0, not one diagnostic, and `Error: No type given` thrown at runtime.
|
|
19
|
+
*/
|
|
20
|
+
prePlugins: RolldownPluginOption[];
|
|
5
21
|
plugins: RolldownPluginOption[];
|
|
6
22
|
}
|
|
7
23
|
export interface GjsifyPluginInput {
|
|
@@ -20,6 +36,15 @@ export interface GjsifyPluginInput {
|
|
|
20
36
|
* `"…"` → custom line, supports `${env:NAME[:-default]}` placeholders
|
|
21
37
|
*/
|
|
22
38
|
shebang?: boolean | string;
|
|
39
|
+
/**
|
|
40
|
+
* `--app gjs` only: system GI library dirs the byte-1 prologue probes on the RUNNING
|
|
41
|
+
* host. The bundle-relative half of that prologue is deliberately NOT plumbed here —
|
|
42
|
+
* `processStubPlugin`'s `giRuntimePaths` still takes it, but nothing in this repo can
|
|
43
|
+
* fill it with a path that is true on the machine a shipped bundle runs on
|
|
44
|
+
* (`packages/infra/cli/src/utils/gi-runtime-paths.ts` records what was measured), and
|
|
45
|
+
* an option no caller fills is the defect this wiring was added to remove.
|
|
46
|
+
*/
|
|
47
|
+
giSystemProbes?: readonly GiSystemProbe[];
|
|
23
48
|
}
|
|
24
49
|
/**
|
|
25
50
|
* Build the Rolldown configuration template + plugin array for the given
|
package/lib/plugin.js
CHANGED
|
@@ -1,10 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* An absolute candidate library dir, the path that proves it IS one, and the path
|
|
3
|
+
* that proves the running host is the PLATFORM the candidate belongs to.
|
|
4
|
+
*
|
|
5
|
+
* Both gates, because they answer different questions and only one of them travels
|
|
6
|
+
* well. `/usr/local/lib` holding a `girepository-1.0/` says "a GI stack is installed
|
|
7
|
+
* here"; it does NOT say "this host's loader needs help finding it" — on Linux
|
|
8
|
+
* `ld.so`'s system-wide cache already resolves those leaves, and a `meson setup
|
|
9
|
+
* --prefix=/usr/local` or jhbuild tree is a normal shape there. Prepending it anyway
|
|
10
|
+
* puts a second GI stack ahead of the distro's for every bundle on that host, which
|
|
11
|
+
* is the two-registries failure ADR 0023 § 4 / #910 describe. The bundle cannot ask
|
|
12
|
+
* `process.platform` (it is platform-neutral JavaScript built on a machine that is
|
|
13
|
+
* not the host), so the platform scope travels as a marker path too.
|
|
14
|
+
*/
|
|
15
|
+
export type GiSystemProbe = readonly [libDir: string, marker: string, hostMarker: string];
|
|
16
|
+
/**
|
|
17
|
+
* The byte-1 prologue that prepends directories to the default repository's typelib
|
|
18
|
+
* search path and library path. Nothing to prepend → `''`: a no-op prologue in every
|
|
19
|
+
* bundle is a guard that can never fire, and a banner that is not there cannot drift.
|
|
20
|
+
*
|
|
21
|
+
* Prepending REVERSES a list, so both loops count down and the emitted order is the
|
|
22
|
+
* order given. `systemProbes` are applied first and therefore end up BEHIND `dirs`:
|
|
23
|
+
* ADR 0023's bundle-before-system precedence, which #910 paid for.
|
|
6
24
|
*
|
|
7
25
|
* @param dirs directories RELATIVE to the program's own directory, `/`-separated —
|
|
8
26
|
* an absolute path baked at build time is the BUILD machine's
|
|
27
|
+
* @param systemProbes absolute directories prepended only where BOTH of a probe's
|
|
28
|
+
* marker paths exist on the RUNNING host; the markers are passed in so the
|
|
29
|
+
* `girepository-1.0` layout rule and the platform scope around it keep their single
|
|
30
|
+
* definition in the caller
|
|
9
31
|
*/
|
|
10
|
-
export declare function giRuntimePathsStub(dirs: readonly string[]): string;
|
|
32
|
+
export declare function giRuntimePathsStub(dirs: readonly string[], systemProbes?: readonly GiSystemProbe[]): string;
|
|
@@ -1,37 +1,84 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// The GI runtime search-path prologue for `--app gjs` bundles: the only repair shape
|
|
3
|
+
// that survives macOS (a typelib names its library by bare leaf, and the link closure
|
|
4
|
+
// never passes through GI, so only `@rpath`/`$ORIGIN` reaches it). Measurements:
|
|
5
|
+
// `status/open-todos.md` § "A globally installed GJS launcher still cannot load a
|
|
6
|
+
// system GTK on macOS", ADR 0023 § 4.
|
|
7
|
+
//
|
|
8
|
+
// WHAT IT REACHES, MEASURED (gjs 1.88.1, linux; pinned by the `gi-runtime-prologue`
|
|
9
|
+
// e2e): a banner is the entry chunk's BODY and ESM evaluates imports first, so a
|
|
10
|
+
// STATIC `import … from 'gi://Ns'` has already loaded its typelib before this runs.
|
|
11
|
+
// What is left is what loads LATER — `await import('gi://Soup')` and the other
|
|
12
|
+
// optional namespaces. Reaching the static ones changes how a bundle acquires GI
|
|
13
|
+
// namespaces at all; that is an ADR, filed in the same entry.
|
|
14
|
+
//
|
|
15
|
+
// TWO KINDS OF DIRECTORY, split by WHOSE FACT each is. `dirs` describe the SHIPPED
|
|
16
|
+
// TREE, so they are baked, relative to the program. `systemProbes` describe the host
|
|
17
|
+
// that will RUN it, which the build machine cannot know — so the candidate travels
|
|
18
|
+
// and is dropped on the host when it names nothing.
|
|
7
19
|
/**
|
|
8
|
-
* The byte-1 prologue that prepends
|
|
9
|
-
* search path and library path.
|
|
10
|
-
* bundle is a guard that can never fire, and a banner that is not there cannot
|
|
11
|
-
*
|
|
20
|
+
* The byte-1 prologue that prepends directories to the default repository's typelib
|
|
21
|
+
* search path and library path. Nothing to prepend → `''`: a no-op prologue in every
|
|
22
|
+
* bundle is a guard that can never fire, and a banner that is not there cannot drift.
|
|
23
|
+
*
|
|
24
|
+
* Prepending REVERSES a list, so both loops count down and the emitted order is the
|
|
25
|
+
* order given. `systemProbes` are applied first and therefore end up BEHIND `dirs`:
|
|
26
|
+
* ADR 0023's bundle-before-system precedence, which #910 paid for.
|
|
12
27
|
*
|
|
13
28
|
* @param dirs directories RELATIVE to the program's own directory, `/`-separated —
|
|
14
29
|
* an absolute path baked at build time is the BUILD machine's
|
|
30
|
+
* @param systemProbes absolute directories prepended only where BOTH of a probe's
|
|
31
|
+
* marker paths exist on the RUNNING host; the markers are passed in so the
|
|
32
|
+
* `girepository-1.0` layout rule and the platform scope around it keep their single
|
|
33
|
+
* definition in the caller
|
|
15
34
|
*/
|
|
16
|
-
export function giRuntimePathsStub(dirs) {
|
|
17
|
-
if (dirs.length === 0)
|
|
35
|
+
export function giRuntimePathsStub(dirs, systemProbes = []) {
|
|
36
|
+
if (dirs.length === 0 && systemProbes.length === 0)
|
|
18
37
|
return '';
|
|
19
38
|
const list = dirs.map((d) => JSON.stringify(d)).join(',');
|
|
39
|
+
const probes = systemProbes.map((probe) => `[${probe.map((v) => JSON.stringify(v)).join(',')}]`).join(',');
|
|
20
40
|
// Four rules, each pinned with its incident by `gi-runtime-paths-banner.spec.ts`:
|
|
21
|
-
// every ambient global through `globalThis.` (a bare `imports` at byte 1 binds to
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
|
|
41
|
+
// every ambient global through `globalThis.` (a bare `imports` at byte 1 binds to a
|
|
42
|
+
// bundled module's own top-level `const imports`, still in its TDZ); capability
|
|
43
|
+
// guards rather than try/catch for every CALL, since none has a throw path in the
|
|
44
|
+
// GIR; one line (the banner precedes source-map-aware machinery); both prepends
|
|
45
|
+
// (search path alone finds the typelib, then fails the dlopen).
|
|
46
|
+
//
|
|
47
|
+
// The ONE try wraps a namespace LOAD, the only operation here that does throw:
|
|
48
|
+
// distributions ship the GIRepository typelib separately from the girepository
|
|
49
|
+
// library GJS links against (Debian's `gir1.2-girepository-3.0` vs
|
|
50
|
+
// `libgirepository-2.0-0`), so a host with only the second raises "Typelib file
|
|
51
|
+
// for namespace 'GIRepository' (any version) not found" — measured, and why
|
|
52
|
+
// `activateNativePrebuilds()` wraps the same access. At byte 1 that throw takes
|
|
53
|
+
// down a program that ran fine while this prologue was the empty string. Not
|
|
54
|
+
// logged, for that function's reason: every outcome leaves the caller where it
|
|
55
|
+
// was before the prologue existed, so no host is made worse.
|
|
56
|
+
const head = `(function(){var i=globalThis.imports;if(!i||!i.gi)return;` +
|
|
57
|
+
`var R;try{R=i.gi.GIRepository}catch(e){return}` +
|
|
58
|
+
`if(!R||!R.Repository||!R.Repository.dup_default)return;` +
|
|
28
59
|
`var r=R.Repository.dup_default();if(!r||!r.prepend_search_path||!r.prepend_library_path)return;` +
|
|
29
|
-
`var
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
60
|
+
`var a=function(f){r.prepend_search_path(f);r.prepend_library_path(f)};`;
|
|
61
|
+
// GLib gets its own try for the same reason, separate so a host that cannot
|
|
62
|
+
// introspect it still gets the relative half.
|
|
63
|
+
//
|
|
64
|
+
// The HOST marker is tested first and short-circuits: on a host the candidate does
|
|
65
|
+
// not belong to, the whole table costs one `stat` per entry and changes nothing.
|
|
66
|
+
const probeHalf = probes.length === 0
|
|
67
|
+
? ''
|
|
68
|
+
: `var G;try{G=i.gi.GLib}catch(e){}` +
|
|
69
|
+
`if(G&&G.file_test&&G.FileTest){var S=[${probes}];` +
|
|
70
|
+
`for(var j=S.length;j--;)` +
|
|
71
|
+
`if(G.file_test(S[j][2],G.FileTest.EXISTS)&&G.file_test(S[j][1],G.FileTest.IS_DIR))a(S[j][0])}`;
|
|
72
|
+
// Guards rather than returns, so a program path with no separator (`gjs -c`) still
|
|
73
|
+
// leaves the system half applied.
|
|
74
|
+
const relativeHalf = list.length === 0
|
|
75
|
+
? ''
|
|
76
|
+
: `var p=(i.system&&(i.system.programPath||i.system.programInvocationName))||'';` +
|
|
77
|
+
// Both separators: the program path is the HOST's, and on win32 a `/`-only
|
|
78
|
+
// cut matches nothing (same rule as `lastPathSeparatorIndex`, #1143).
|
|
79
|
+
`var m=/^(.*)[\\/\\\\][^\\/\\\\]*$/.exec(p);if(m){var b=m[1];var D=[${list}];` +
|
|
80
|
+
`for(var k=D.length;k--;){var d=D[k];` +
|
|
81
|
+
// Absolute stays absolute: joining a system libdir under the program dir names nothing.
|
|
82
|
+
`var q=/^([A-Za-z]:[\\/\\\\]|[\\/\\\\])/.test(d)?d:b+'/'+d;a(q)}}`;
|
|
83
|
+
return `${head}${probeHalf}${relativeHalf}})();`;
|
|
37
84
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const GJS_INTL_SEGMENTER_STUB: string;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// One-line banner that supplies `Intl.Segmenter` on a GJS built without it.
|
|
2
|
+
//
|
|
3
|
+
// THE INCIDENT — measured, not hypothetical. `gjsify ship`'s first CI leg on a
|
|
4
|
+
// bare `ubuntu-latest` (the one that exists to hand a `.deb` to a real `dpkg`)
|
|
5
|
+
// installed the shipped package, ran the binary, and got:
|
|
6
|
+
//
|
|
7
|
+
// JS ERROR: TypeError: Intl.Segmenter is not a constructor
|
|
8
|
+
//
|
|
9
|
+
// Not an old runtime: that host had **gjs 1.88.1**, the same version as the
|
|
10
|
+
// Fedora container where everything passes. The difference is the ICU the two
|
|
11
|
+
// were built against, so a version number does not predict it — and the `.deb`
|
|
12
|
+
// this project ships declares `Depends: gjs >= 1.82`, which apt satisfies on
|
|
13
|
+
// that host. Install succeeds, program does not start: verbatim the failure
|
|
14
|
+
// class `depends.ts` was written to prevent, one layer below where it looks.
|
|
15
|
+
//
|
|
16
|
+
// WHY THE CLI NEEDS IT AT ALL, and why no application code is involved:
|
|
17
|
+
// `@gjsify/cli` → `yargs@18` → `string-width@7` → `get-east-asian-width`, which
|
|
18
|
+
// constructs a `Intl.Segmenter` AT MODULE SCOPE. It is the argument parser, so
|
|
19
|
+
// every command dies, `--help` included — reproduced locally by deleting
|
|
20
|
+
// `Intl.Segmenter` before importing the published bundle.
|
|
21
|
+
//
|
|
22
|
+
// WHAT THIS FALLBACK IS, AND IS NOT. It segments by CODE POINT, not by grapheme
|
|
23
|
+
// cluster, because a correct cluster algorithm is a Unicode table this banner
|
|
24
|
+
// cannot carry. Measured against native, via `string-width`:
|
|
25
|
+
//
|
|
26
|
+
// 'hello' 5→5 'あいう' 6→6 '🇩🇪' 2→2 'café' 4→4 (identical)
|
|
27
|
+
// '👨👩👧👦' 2→8 '👍🏽' 2→4 (over-counted)
|
|
28
|
+
//
|
|
29
|
+
// So ASCII, CJK, flags and combining accents are exact; ZWJ sequences and skin
|
|
30
|
+
// tone modifiers come out too wide. The cost is a misaligned column in terminal
|
|
31
|
+
// output containing those emoji. The cost of not having it is that nothing runs
|
|
32
|
+
// at all. It is installed ONLY when the constructor is missing, so on every GJS
|
|
33
|
+
// with a full ICU — every currently green CI leg — this line does nothing.
|
|
34
|
+
//
|
|
35
|
+
// Kept on a single line, like the sibling banners: the banner runs before any
|
|
36
|
+
// source-map-aware machinery, so a newline here shifts every bundle line by one.
|
|
37
|
+
export const GJS_INTL_SEGMENTER_STUB = 'if(typeof Intl.Segmenter!=="function")Intl.Segmenter=class Segmenter{' +
|
|
38
|
+
'constructor(){}' +
|
|
39
|
+
'resolvedOptions(){return{locale:"en",granularity:"grapheme"}}' +
|
|
40
|
+
'segment(input){const s=String(input);return{input:s,[Symbol.iterator]:function*(){' +
|
|
41
|
+
'let i=0;for(const c of s){yield{segment:c,index:i,input:s};i+=c.length}}}}' +
|
|
42
|
+
'};';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Plugin } from 'rolldown';
|
|
2
|
+
import { type GiSystemProbe } from './gi-runtime-paths.js';
|
|
2
3
|
export declare const GJS_PROCESS_STUB: string;
|
|
3
4
|
/**
|
|
4
5
|
* Compose the GJS process stub with the user-supplied banner so the result is
|
|
@@ -23,6 +24,12 @@ export interface ProcessStubPluginOptions {
|
|
|
23
24
|
* Empty or unset emits nothing.
|
|
24
25
|
*/
|
|
25
26
|
giRuntimePaths?: readonly string[];
|
|
27
|
+
/**
|
|
28
|
+
* Absolute system GI library directories, each paired with the path that proves the
|
|
29
|
+
* host really has one. Prepended BEHIND {@link giRuntimePaths} and only where the
|
|
30
|
+
* marker exists at RUNTIME. Empty or unset emits nothing.
|
|
31
|
+
*/
|
|
32
|
+
giSystemProbes?: readonly GiSystemProbe[];
|
|
26
33
|
}
|
|
27
34
|
/** Inject the byte-1 banner as a chunk banner, after any user `output.banner`. */
|
|
28
35
|
export declare function processStubPlugin(options?: ProcessStubPluginOptions): Plugin;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BUNDLE_URL_BANNER } from './bundle-url-banner.js';
|
|
2
|
+
import { GJS_INTL_SEGMENTER_STUB } from './intl-segmenter-banner.js';
|
|
2
3
|
import { GJS_WELLKNOWN_SYMBOLS_STUB } from './wellknown-symbols-banner.js';
|
|
3
4
|
import { giRuntimePathsStub } from './gi-runtime-paths.js';
|
|
4
5
|
// Every GJS ambient global is reached through `globalThis.` — NEVER as a bare
|
|
@@ -84,8 +85,11 @@ export function processStubPlugin(options = {}) {
|
|
|
84
85
|
// is single-line and idempotent.
|
|
85
86
|
const stub = (options.captureBundleUrl ? BUNDLE_URL_BANNER : '') +
|
|
86
87
|
GJS_WELLKNOWN_SYMBOLS_STUB +
|
|
88
|
+
// Before the process stub and before module init: `get-east-asian-width`
|
|
89
|
+
// constructs a Segmenter at module scope, so a later injection is too late.
|
|
90
|
+
GJS_INTL_SEGMENTER_STUB +
|
|
87
91
|
GJS_PROCESS_STUB +
|
|
88
|
-
giRuntimePathsStub(options.giRuntimePaths ?? []);
|
|
92
|
+
giRuntimePathsStub(options.giRuntimePaths ?? [], options.giSystemProbes ?? []);
|
|
89
93
|
const banner = composeBanner(stub, options.userBanner ?? '');
|
|
90
94
|
return {
|
|
91
95
|
name: 'gjsify-process-stub',
|
|
@@ -2,6 +2,36 @@ import type { Plugin } from 'rolldown';
|
|
|
2
2
|
export declare const REWRITE_FILTER: RegExp;
|
|
3
3
|
/** True when the rewriter wants to look at this path — node_modules + supported ext. */
|
|
4
4
|
export declare function shouldRewrite(path: string): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* True when the static-read INLINER wants to look at this path — any supported
|
|
7
|
+
* source, first-party included.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately wider than {@link shouldRewrite}, because the two answer different
|
|
10
|
+
* questions. The inliner asks *is this read resolvable at build time*, which is a
|
|
11
|
+
* property of the expression and can be true in any file. The rewriter asks *does
|
|
12
|
+
* this file's `import.meta.url` still mean anything once bundled*, which is only
|
|
13
|
+
* worth asking of a file whose original location was `node_modules` — the whole
|
|
14
|
+
* subject of this plugin.
|
|
15
|
+
*
|
|
16
|
+
* They were ONE gate until 2026-08-22, and the cost was silent. Each of
|
|
17
|
+
* `@gjsify/cli`'s own six `readFileSync(new URL(<literal>, import.meta.url))`
|
|
18
|
+
* template loaders shipped into `dist/cli.gjs.mjs` as a LIVE read pointing at a
|
|
19
|
+
* `templates/` directory that exists neither in the repo (the sources are in
|
|
20
|
+
* `src/templates/`; the build copies them to `lib/templates/`) nor in the
|
|
21
|
+
* published tarball (`files` ships `lib`, not a root `templates`). Measured on a
|
|
22
|
+
* fresh build: `gjs -m dist/cli.gjs.mjs ship --stage` died with
|
|
23
|
+
* `ENOENT … packages/infra/cli/templates/app/desktop.tmpl` while the identical
|
|
24
|
+
* command through `lib/index.js` staged the desktop entry. Two of those six files
|
|
25
|
+
* carry a comment asserting the inliner handles them, so reading the code
|
|
26
|
+
* confirmed the belief rather than the behaviour.
|
|
27
|
+
*
|
|
28
|
+
* Consequence for a CONSUMER's build, stated because it is a behaviour change: a
|
|
29
|
+
* statically resolvable read in their own source is baked into their bundle too.
|
|
30
|
+
* That is the promise the inliner exists to make — a bundle that survives leaving
|
|
31
|
+
* its build site — and a read meant to stay dynamic keeps working by not being
|
|
32
|
+
* statically resolvable, since anything the evaluator cannot fold is left alone.
|
|
33
|
+
*/
|
|
34
|
+
export declare function shouldInline(path: string): boolean;
|
|
5
35
|
/** The directory the bundle's outfile lives in, from `output.file` or `output.dir`. */
|
|
6
36
|
export declare function getBundleDirFromOutput(opts: {
|
|
7
37
|
file?: string;
|
|
@@ -48,5 +78,10 @@ export interface NodeModulesPathRewriteOptions {
|
|
|
48
78
|
/**
|
|
49
79
|
* Build a Rolldown plugin that runs the path rewriter as a `transform(code, id)`
|
|
50
80
|
* hook with `order: 'post'`.
|
|
81
|
+
*
|
|
82
|
+
* Two scopes in one transform, not two plugins: a second transform would parse
|
|
83
|
+
* every module a second time, and the inliner's own fast path (skip unless the
|
|
84
|
+
* source mentions `readFileSync`/`readdirSync`/`existsSync`) already makes the
|
|
85
|
+
* wider scope nearly free. See {@link shouldInline} for why the scopes differ.
|
|
51
86
|
*/
|
|
52
87
|
export declare function nodeModulesPathRewritePlugin(options: NodeModulesPathRewriteOptions): Plugin;
|