@gjsify/rolldown-plugin-gjsify 0.52.0 → 0.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/lib/app/browser.js +1 -0
- package/lib/app/gjs.js +14 -2
- package/lib/app/nativescript.js +1 -1
- package/lib/app/node.d.ts +1 -1
- package/lib/app/node.js +26 -10
- package/lib/index.d.ts +3 -1
- package/lib/index.js +3 -1
- package/lib/plugins/console-assign.d.ts +13 -0
- package/lib/plugins/console-assign.js +125 -0
- package/lib/plugins/css-as-string.js +126 -44
- package/lib/plugins/gi-renderer.d.ts +1 -1
- package/lib/plugins/gi-renderer.js +1 -1
- package/lib/plugins/gi-runtime-paths.js +5 -2
- package/lib/plugins/napi-node-addon.d.ts +48 -4
- package/lib/plugins/napi-node-addon.js +261 -85
- package/lib/plugins/node-native-external.d.ts +20 -0
- package/lib/plugins/node-native-external.js +145 -0
- package/lib/plugins/rewrite-node-modules-paths.d.ts +18 -1
- package/lib/plugins/rewrite-node-modules-paths.js +181 -25
- package/lib/plugins/unresolved-workspace-import.js +89 -68
- package/lib/shims/addon-resolve.d.ts +19 -0
- package/lib/shims/addon-resolve.js +193 -0
- package/lib/utils/addon-platform.d.ts +23 -0
- package/lib/utils/addon-platform.js +50 -0
- package/lib/utils/auto-globals.d.ts +6 -0
- package/lib/utils/auto-globals.js +13 -10
- package/lib/utils/declare-build-input.d.ts +13 -0
- package/lib/utils/declare-build-input.js +37 -0
- package/lib/utils/detect-free-globals.d.ts +3 -0
- package/lib/utils/detect-free-globals.js +1 -1
- package/lib/utils/entry-wrapper.js +6 -1
- package/lib/utils/index.d.ts +1 -0
- package/lib/utils/index.js +1 -0
- package/lib/utils/inline-static-reads.d.ts +15 -1
- package/lib/utils/inline-static-reads.js +16 -8
- package/lib/utils/scan-globals.d.ts +2 -2
- package/lib/utils/scan-globals.js +20 -14
- package/lib/utils/zip-path.d.ts +9 -0
- package/lib/utils/zip-path.js +12 -0
- package/package.json +13 -9
package/README.md
CHANGED
|
@@ -40,6 +40,17 @@ import {
|
|
|
40
40
|
} from '@gjsify/rolldown-plugin-gjsify';
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
+
## Native addons (`--app gjs`)
|
|
44
|
+
|
|
45
|
+
A Node-API addon package (`bufferutil`, `better-sqlite3`, `lightningcss`, `@signalapp/libsignal-client`, …) is rewritten to `loadAddon()` from `@gjsify/napi`, which GJS cannot load by itself. On `--app node` the same packages stay external and keep resolving from `node_modules`.
|
|
46
|
+
|
|
47
|
+
For a GJS build the bundle records the addon's **package identity**, not a path — see [ADR 0084](../../docs/adr/0084-an-addon-is-found-by-package-identity.md). Every `.node` the package ships is enumerated at build time, keyed by platform (`linux-x64`, `linux-x64-musl`, `darwin-arm64`, …), and the bundle picks the entry for the host it finds itself on when it loads. Consequences worth knowing:
|
|
48
|
+
|
|
49
|
+
- The bundle is **relocatable** — move the output tree and it still loads its addon, because nothing names the machine that built it.
|
|
50
|
+
- The addon **package** must be installed somewhere the bundle can reach (its own `node_modules`, or an `addons/<package>/` next to the bundle that no gjsify step fills yet). A single copied `.node` file is not enough; a `.node` that `dlopen`s a sibling library or reads a data file beside itself needs its directory.
|
|
51
|
+
- A cross-build works when the target's binary is installed on the builder: a multi-platform npm package, or `gjsify install`, which materialises every platform package.
|
|
52
|
+
- `runtimeResolve` (set from `format === 'esm'` by `gjsify build`) gates the whole mechanism: without the bundle-URL banner there is nothing to anchor on, so the rewrite is declined with a warning rather than emitting a path that would only be right on the build machine.
|
|
53
|
+
|
|
43
54
|
## License
|
|
44
55
|
|
|
45
56
|
MIT
|
package/lib/app/browser.js
CHANGED
package/lib/app/gjs.js
CHANGED
|
@@ -19,6 +19,7 @@ import { globToEntryPoints } from '../utils/entry-points.js';
|
|
|
19
19
|
import { nodeModulesPathRewritePlugin, getBundleDirFromOutput } from '../plugins/rewrite-node-modules-paths.js';
|
|
20
20
|
import { processStubPlugin } from '../plugins/process-stub.js';
|
|
21
21
|
import { cssAsStringPlugin } from '../plugins/css-as-string.js';
|
|
22
|
+
import { consoleAssignPlugin } from '../plugins/console-assign.js';
|
|
22
23
|
import { shebangPlugin, resolveShebangLine, inputShebangStripPlugin } from '../plugins/shebang.js';
|
|
23
24
|
import { wrapInputWithSideEffects } from '../utils/entry-wrapper.js';
|
|
24
25
|
const _shimDir = dirname(fileURLToPath(import.meta.url));
|
|
@@ -132,8 +133,13 @@ export const setupForGjs = async (input) => {
|
|
|
132
133
|
target: 'firefox140',
|
|
133
134
|
define: {
|
|
134
135
|
global: 'globalThis',
|
|
136
|
+
// An identity, unlike on `--app node` (ADR 0079): GJS itself defines `window`
|
|
137
|
+
// as a non-configurable alias of the global, so `typeof window` is 'object'
|
|
138
|
+
// with or without it. What a browser branch then calls on it comes from
|
|
139
|
+
// `dom-events/register/global-event-target` (`addEventListener` & co.).
|
|
135
140
|
window: 'globalThis',
|
|
136
141
|
'process.env.READABLE_STREAM': '"disable"',
|
|
142
|
+
'process.env.NODE_ENV': '"production"',
|
|
137
143
|
},
|
|
138
144
|
// Rewrite bare `console` to a named import from our shim. Rolldown's
|
|
139
145
|
// `inject` (not a register-style global write) because GJS defines
|
|
@@ -182,6 +188,9 @@ export const setupForGjs = async (input) => {
|
|
|
182
188
|
// banner, and acorn (the auto-globals detector) rejects the `#` byte.
|
|
183
189
|
// The final-output shebang is composed by shebangPlugin's renderChunk.
|
|
184
190
|
inputShebangStripPlugin(),
|
|
191
|
+
// A module assigning the global `console` gets a local binding, or the inject
|
|
192
|
+
// below turns its assignment into `ASSIGN_TO_IMPORT` and fails the build.
|
|
193
|
+
...(consoleShimPath ? [consoleAssignPlugin()] : []),
|
|
185
194
|
// Platform-file forks for the desktop, ADR 0032 § 9: `.gtk` → `.<os>` →
|
|
186
195
|
// `.desktop` → base. BEFORE the alias layer, so a platform fork of a
|
|
187
196
|
// module that also has a Node-builtin substitution wins over the
|
|
@@ -208,8 +217,11 @@ export const setupForGjs = async (input) => {
|
|
|
208
217
|
// from `@gjsify/napi`, which is BUNDLED, not external. Order matters:
|
|
209
218
|
// after aliasPlugin so a user alias pinning an addon's native entry wins,
|
|
210
219
|
// before externalsPlugin so the acquisition is claimed first. Inert
|
|
211
|
-
// otherwise; `--app gjs` only.
|
|
212
|
-
|
|
220
|
+
// otherwise; `--app gjs` only. `runtimeResolve` is the SAME `format ===
|
|
221
|
+
// 'esm'` gate the path rewriter below carries: the shim resolves the
|
|
222
|
+
// addon from the bundle's own URL, which only the ESM output's
|
|
223
|
+
// bundle-URL banner records (ADR 0084).
|
|
224
|
+
napiNodeAddonPlugin({ runtimeResolve: format === 'esm' }),
|
|
213
225
|
// Externals policy as a resolveId hook — the only form BOTH engines
|
|
214
226
|
// honour. Runs after the alias plugin's `pre` resolveId so aliases apply
|
|
215
227
|
// first.
|
package/lib/app/nativescript.js
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
// "this package IS the native implementation" sense and therefore promises a
|
|
27
27
|
// `globals.mjs` it does not ship. The slot vocabulary's `native` means "the
|
|
28
28
|
// RUNTIME provides this, use its value", so the bridge packages are
|
|
29
|
-
// mis-declared; that is tracked in status/open-todos.md and must be settled
|
|
29
|
+
// mis-declared; that is tracked in status/open-todos/README.md and must be settled
|
|
30
30
|
// before this table is composed.
|
|
31
31
|
//
|
|
32
32
|
// Derived `@gjsify/<X>` entries are merged into the map below, so an import by
|
package/lib/app/node.d.ts
CHANGED
|
@@ -43,7 +43,7 @@ export declare function enableGjsRegistersForNode(baseAliases: Record<string, st
|
|
|
43
43
|
* emptying both stay, so a cross-platform package's node bundle keeps loading on
|
|
44
44
|
* plain Node without node-gi installed.
|
|
45
45
|
*
|
|
46
|
-
* KNOWN NARROWNESS (tracked in status/open-todos.md): a genuine GJS source whose
|
|
46
|
+
* KNOWN NARROWNESS (tracked in status/open-todos/README.md): a genuine GJS source whose
|
|
47
47
|
* ONLY platform reach is `gi://` (no ambient global, no bare built-in) is not
|
|
48
48
|
* recognised — its `@girs/*` and its registers are both emptied. A surviving
|
|
49
49
|
* `gi://` import cannot simply become a third signal: its shim loads node-gi
|
package/lib/app/node.js
CHANGED
|
@@ -11,6 +11,7 @@ import { cssAsStringPlugin } from '../plugins/css-as-string.js';
|
|
|
11
11
|
import { gjsImportsEmptyPlugin } from '../plugins/gjs-imports-empty.js';
|
|
12
12
|
import { gjsGiNodePlugin, gjsBuiltinModulesNodePlugin } from '../plugins/gjs-gi-node.js';
|
|
13
13
|
import { unresolvedWorkspaceImportPlugin } from '../plugins/unresolved-workspace-import.js';
|
|
14
|
+
import { nodeNativeExternalPlugin } from '../plugins/node-native-external.js';
|
|
14
15
|
import { platformResolvePlugin, desktopSuffixChain, desktopOsSuffix, DESKTOP_REFUSED_SUFFIXES, } from '../plugins/platform-resolve.js';
|
|
15
16
|
import { reactNativeAliasPlugin } from '../plugins/react-native-alias.js';
|
|
16
17
|
import { reactNativeSupportGatePlugin } from '../plugins/react-native-gate.js';
|
|
@@ -178,7 +179,7 @@ export function enableGjsRegistersForNode(baseAliases) {
|
|
|
178
179
|
* emptying both stay, so a cross-platform package's node bundle keeps loading on
|
|
179
180
|
* plain Node without node-gi installed.
|
|
180
181
|
*
|
|
181
|
-
* KNOWN NARROWNESS (tracked in status/open-todos.md): a genuine GJS source whose
|
|
182
|
+
* KNOWN NARROWNESS (tracked in status/open-todos/README.md): a genuine GJS source whose
|
|
182
183
|
* ONLY platform reach is `gi://` (no ambient global, no bare built-in) is not
|
|
183
184
|
* recognised — its `@girs/*` and its registers are both emptied. A surviving
|
|
184
185
|
* `gi://` import cannot simply become a third signal: its shim loads node-gi
|
|
@@ -192,9 +193,9 @@ export function isGjsSourceBuild(options) {
|
|
|
192
193
|
}
|
|
193
194
|
export const setupForNode = async (input) => {
|
|
194
195
|
const userExternal = input.userExternal ?? [];
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
//
|
|
196
|
+
// `@gjsify/node-gi` stays external by name: the injected globals shim and the
|
|
197
|
+
// bare-module specifiers below name it before any package is resolved. Every
|
|
198
|
+
// other native addon package is found by `nodeNativeExternalPlugin`.
|
|
198
199
|
//
|
|
199
200
|
// GJS-specific specifiers (`gi://*`, `@girs/*`) are deliberately NOT
|
|
200
201
|
// externalised: `gjsGiNodePlugin` rewrites `gi://` to `requireGi`, and
|
|
@@ -204,7 +205,6 @@ export const setupForNode = async (input) => {
|
|
|
204
205
|
// `ERR_UNSUPPORTED_ESM_URL_SCHEME`.
|
|
205
206
|
const exactExternal = [
|
|
206
207
|
...EXTERNALS_NODE,
|
|
207
|
-
'node-datachannel',
|
|
208
208
|
'@gjsify/node-gi',
|
|
209
209
|
'@gjsify/node-gi/gi',
|
|
210
210
|
NODE_GI_GLOBALS_SPECIFIER,
|
|
@@ -275,16 +275,28 @@ export const setupForNode = async (input) => {
|
|
|
275
275
|
external: exactExternal,
|
|
276
276
|
resolve: {
|
|
277
277
|
mainFields: format === 'esm' ? ['module', 'main', 'browser'] : ['main', 'module', 'browser'],
|
|
278
|
-
//
|
|
279
|
-
//
|
|
280
|
-
//
|
|
281
|
-
|
|
278
|
+
// Neither 'import' nor 'require' on the esm build: rolldown adds the one
|
|
279
|
+
// matching each call site's kind, as Node's resolver does. Listing 'require'
|
|
280
|
+
// made every `import` take the CJS build wherever a package declares
|
|
281
|
+
// `require` first — `import basex from 'base-x'` then bound
|
|
282
|
+
// `module.exports` as the default and threw "(0, x.default) is not a
|
|
283
|
+
// function" at load, while the same source ran on Node. `--app gjs` lost
|
|
284
|
+
// the pinned 'import' for the mirror bug (#1130). 'module' is not a
|
|
285
|
+
// condition Node knows, and a require() would take it wherever a package
|
|
286
|
+
// lists it before 'require'.
|
|
287
|
+
conditionNames: format === 'esm' ? ['node'] : ['require'],
|
|
282
288
|
},
|
|
283
289
|
transform: {
|
|
284
290
|
target: 'node24',
|
|
285
291
|
define: {
|
|
286
292
|
global: 'globalThis',
|
|
287
|
-
window:
|
|
293
|
+
// NO `window` define (ADR 0079): Node has none, and a bundle must see the same
|
|
294
|
+
// `typeof window` as its source run. A reverse-bridge DOM app gets `window`
|
|
295
|
+
// from @gjsify/dom-elements/register/document, which defines it at runtime.
|
|
296
|
+
// NO `process.env.NODE_ENV` define either, unlike gjs/browser (#1355): Node
|
|
297
|
+
// has a real `process.env`, so a Node bundle reads NODE_ENV at run time like
|
|
298
|
+
// its source does. Baking "production" in selected React's production build,
|
|
299
|
+
// which has no `act`, in every `--app node` test bundle.
|
|
288
300
|
},
|
|
289
301
|
},
|
|
290
302
|
output: {
|
|
@@ -352,6 +364,10 @@ export const setupForNode = async (input) => {
|
|
|
352
364
|
// externals policy — a redirect after `externalsPlugin` would find the
|
|
353
365
|
// specifier already externalised.
|
|
354
366
|
...(input.pluginOptions.dialect === 'react-native' ? [reactNativeAliasPlugin()] : []),
|
|
367
|
+
// Node-API addon packages stay external so Node loads their `.node` from
|
|
368
|
+
// `node_modules` (see the plugin header). Resolves THROUGH the alias layer
|
|
369
|
+
// (`this.resolve`), so an aliased specifier is judged by where it lands.
|
|
370
|
+
nodeNativeExternalPlugin(),
|
|
355
371
|
aliasPlugin({ entries: aliasEntries }),
|
|
356
372
|
// Blueprint (.blp → XML string): the reverse bridge runs REAL GTK on Node,
|
|
357
373
|
// so a GJS app entry with composite-template windows must build for
|
package/lib/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { REWRITE_FILTER, extractPackageSpec, getBundleDirFromOutput, rewriteCont
|
|
|
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 { bindConsoleLocally, consoleAssignPlugin, freeConsoleAssignmentInsertion, CONSOLE_LOCAL_BINDING, } from './plugins/console-assign.js';
|
|
9
10
|
export type { GiSystemProbe } from './plugins/gi-runtime-paths.js';
|
|
10
11
|
export type { ProcessStubPluginOptions } from './plugins/process-stub.js';
|
|
11
12
|
export { cssAsStringPlugin } from './plugins/css-as-string.js';
|
|
@@ -20,8 +21,9 @@ export { externalsPlugin } from './plugins/externals.js';
|
|
|
20
21
|
export type { ExternalsPredicate, ExternalsPluginOptions } from './plugins/externals.js';
|
|
21
22
|
export { unresolvedWorkspaceImportPlugin, classifyImport, isWorkspaceSpecifier, formatUnresolvedWorkspaceImport, buildReverseAliasIndex, UnresolvedWorkspaceImportError, } from './plugins/unresolved-workspace-import.js';
|
|
22
23
|
export type { WorkspaceImportGuardOptions, WorkspaceImportGuardTarget, ImportVerdict, ClassifyImportInput, UnresolvedWorkspaceImportDetails, } from './plugins/unresolved-workspace-import.js';
|
|
23
|
-
export { napiNodeAddonPlugin, resolveAddonPath, nearestPackageRoot, classifySpecifier, directNodeShim, nodeGypBuildShim, bindingsShim, napiRsShim, ADDON_FILTER_RE, isNapiRsPackageJson, isNapiRsSibling, isGjsifyNativeBridge, detectNapiRsEntry, hostNapiRsTriple, AddonNotBuiltError, } from './plugins/napi-node-addon.js';
|
|
24
|
+
export { napiNodeAddonPlugin, resolveAddonPath, enumerateAddonTargets, nearestPackageRoot, classifySpecifier, directNodeShim, nodeGypBuildShim, bindingsShim, napiRsShim, ADDON_FILTER_RE, isNapiRsPackageJson, isNapiRsSibling, isGjsifyNativeBridge, detectNapiRsEntry, hostNapiRsTriple, AddonNotBuiltError, } from './plugins/napi-node-addon.js';
|
|
24
25
|
export type { NapiNodeAddonPluginOptions, AddonPackageJson } from './plugins/napi-node-addon.js';
|
|
26
|
+
export { nodeNativeExternalPlugin, isNodeAddonPackage, packageNameOf } from './plugins/node-native-external.js';
|
|
25
27
|
export { platformResolvePlugin, nativescriptSuffixChain, desktopSuffixChain, browserSuffixChain, desktopOsSuffix, DESKTOP_OS_SUFFIXES, DESKTOP_REFUSED_SUFFIXES, BROWSER_REFUSED_SUFFIXES, PlatformVariantExternalError, detectNativescriptPlatform, nativescriptPlatformDefines, } from './plugins/platform-resolve.js';
|
|
26
28
|
export type { PlatformResolvePluginOptions, NativescriptPlatform } from './plugins/platform-resolve.js';
|
|
27
29
|
export { rnRouteManifestPlugin, renderRouteManifest, walkRoutes, RouteManifestError, RN_ROUTES_MODULE_ID, MAX_ROUTE_DEPTH, } from './plugins/rn-route-manifest.js';
|
package/lib/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export * from './library/index.js';
|
|
|
6
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
|
+
export { bindConsoleLocally, consoleAssignPlugin, freeConsoleAssignmentInsertion, CONSOLE_LOCAL_BINDING, } from './plugins/console-assign.js';
|
|
9
10
|
export { cssAsStringPlugin } from './plugins/css-as-string.js';
|
|
10
11
|
export { textLoaderPlugin } from './plugins/text-loader.js';
|
|
11
12
|
export { shebangPlugin, GJS_SHEBANG, NODE_SHEBANG, expandEnvTemplate, resolveShebangLine } from './plugins/shebang.js';
|
|
@@ -13,7 +14,8 @@ export { aliasPlugin } from './plugins/alias.js';
|
|
|
13
14
|
export { gjsImportsEmptyPlugin } from './plugins/gjs-imports-empty.js';
|
|
14
15
|
export { externalsPlugin } from './plugins/externals.js';
|
|
15
16
|
export { unresolvedWorkspaceImportPlugin, classifyImport, isWorkspaceSpecifier, formatUnresolvedWorkspaceImport, buildReverseAliasIndex, UnresolvedWorkspaceImportError, } from './plugins/unresolved-workspace-import.js';
|
|
16
|
-
export { napiNodeAddonPlugin, resolveAddonPath, nearestPackageRoot, classifySpecifier, directNodeShim, nodeGypBuildShim, bindingsShim, napiRsShim, ADDON_FILTER_RE, isNapiRsPackageJson, isNapiRsSibling, isGjsifyNativeBridge, detectNapiRsEntry, hostNapiRsTriple, AddonNotBuiltError, } from './plugins/napi-node-addon.js';
|
|
17
|
+
export { napiNodeAddonPlugin, resolveAddonPath, enumerateAddonTargets, nearestPackageRoot, classifySpecifier, directNodeShim, nodeGypBuildShim, bindingsShim, napiRsShim, ADDON_FILTER_RE, isNapiRsPackageJson, isNapiRsSibling, isGjsifyNativeBridge, detectNapiRsEntry, hostNapiRsTriple, AddonNotBuiltError, } from './plugins/napi-node-addon.js';
|
|
18
|
+
export { nodeNativeExternalPlugin, isNodeAddonPackage, packageNameOf } from './plugins/node-native-external.js';
|
|
17
19
|
export { platformResolvePlugin, nativescriptSuffixChain, desktopSuffixChain, browserSuffixChain, desktopOsSuffix, DESKTOP_OS_SUFFIXES, DESKTOP_REFUSED_SUFFIXES, BROWSER_REFUSED_SUFFIXES, PlatformVariantExternalError, detectNativescriptPlatform, nativescriptPlatformDefines, } from './plugins/platform-resolve.js';
|
|
18
20
|
export { rnRouteManifestPlugin, renderRouteManifest, walkRoutes, RouteManifestError, RN_ROUTES_MODULE_ID, MAX_ROUTE_DEPTH, } from './plugins/rn-route-manifest.js';
|
|
19
21
|
export { reactNativeAliasPlugin, classifyReactNativeSpecifier, couldBeSurfaceSpecifier, loadLayer, FALLBACK_SURFACES, REACT_NATIVE_ALIAS_TARGET, REACT_NATIVE_SPECIFIER, ReactNativeDeepImportError, ReactNativeAliasTargetMissingError, SURFACE_MENTION, SURFACE_NAME_PREFIXES, } from './plugins/react-native-alias.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Plugin } from 'rolldown';
|
|
2
|
+
export declare const CONSOLE_LOCAL_BINDING = "var console = globalThis.console;";
|
|
3
|
+
/**
|
|
4
|
+
* Where to insert the local binding in `code`, or null when the module does not assign
|
|
5
|
+
* the GLOBAL `console`. Null as well when the module declares a `console` anywhere: its
|
|
6
|
+
* assignments may target that binding, and declaring a second one would not parse.
|
|
7
|
+
* The offset is after the directive prologue, so a `'use strict'` stays a directive.
|
|
8
|
+
*/
|
|
9
|
+
export declare function freeConsoleAssignmentInsertion(code: string): number | null;
|
|
10
|
+
/** `code` with the local `console` binding inserted, or null when it needs none. */
|
|
11
|
+
export declare function bindConsoleLocally(code: string): string | null;
|
|
12
|
+
/** Composed by `--app gjs` whenever the console shim is injected. */
|
|
13
|
+
export declare function consoleAssignPlugin(): Plugin;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// Give a module that ASSIGNS the global `console` a local binding of its own, so the
|
|
2
|
+
// `--app gjs` console `inject` leaves it alone.
|
|
3
|
+
//
|
|
4
|
+
// `--app gjs` rewrites every free `console` into an import from `shims/console-gjs`
|
|
5
|
+
// (rolldown `transform.inject`), because GJS defines `globalThis.console` non-writable
|
|
6
|
+
// and non-configurable. An import binding cannot be assigned, so ONE sloppy-mode
|
|
7
|
+
// `console = { log() {} }` anywhere in the dependency graph failed the whole build with
|
|
8
|
+
// `ASSIGN_TO_IMPORT: Cannot assign to import 'console'`. Measured: `node-forge`'s
|
|
9
|
+
// `lib/log.js` does exactly that on its browser-without-console branch, and it reaches
|
|
10
|
+
// `wxt` through `web-ext`, so `gjsify exec wxt` could not be rebuilt at all.
|
|
11
|
+
//
|
|
12
|
+
// The binding is `var console = globalThis.console`: the module keeps working with the
|
|
13
|
+
// runtime's console, and its assignment lands on its own variable — which is all a
|
|
14
|
+
// sloppy-mode write to a non-writable global ever did under GJS. Only such a module
|
|
15
|
+
// gives up the shim's print()-based output; every other module is untouched.
|
|
16
|
+
//
|
|
17
|
+
// Detection is an AST question (a parameter or a `const console` is a binding, not an
|
|
18
|
+
// assignment to the global), asked only of sources the cheap pattern admits.
|
|
19
|
+
import * as acorn from 'acorn';
|
|
20
|
+
import * as walk from 'acorn-walk';
|
|
21
|
+
import { extractBindingNames } from '../utils/detect-free-globals.js';
|
|
22
|
+
const CANDIDATE_RE = /(?:^|[^.\w$])console\s*=(?![=>])/;
|
|
23
|
+
const SOURCE_RE = /\.(?:m|c)?jsx?$/;
|
|
24
|
+
export const CONSOLE_LOCAL_BINDING = 'var console = globalThis.console;';
|
|
25
|
+
function parse(code) {
|
|
26
|
+
const base = { ecmaVersion: 'latest', allowHashBang: true, allowAwaitOutsideFunction: true };
|
|
27
|
+
try {
|
|
28
|
+
return acorn.parse(code, { ...base, sourceType: 'module' });
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// Sloppy CJS (the node-forge case: `with`, legacy octals, top-level `return`)
|
|
32
|
+
// is not a valid module; the script grammar is the one it was written in.
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
return acorn.parse(code, { ...base, sourceType: 'script', allowReturnOutsideFunction: true });
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// Not JavaScript acorn can read (a syntax newer than acorn): leave it to the
|
|
39
|
+
// bundler, which reports an assignment it cannot bundle loudly on its own.
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Where to insert the local binding in `code`, or null when the module does not assign
|
|
45
|
+
* the GLOBAL `console`. Null as well when the module declares a `console` anywhere: its
|
|
46
|
+
* assignments may target that binding, and declaring a second one would not parse.
|
|
47
|
+
* The offset is after the directive prologue, so a `'use strict'` stays a directive.
|
|
48
|
+
*/
|
|
49
|
+
export function freeConsoleAssignmentInsertion(code) {
|
|
50
|
+
if (!CANDIDATE_RE.test(code))
|
|
51
|
+
return null;
|
|
52
|
+
const ast = parse(code);
|
|
53
|
+
if (!ast)
|
|
54
|
+
return null;
|
|
55
|
+
let declares = false;
|
|
56
|
+
let assigns = false;
|
|
57
|
+
const bindsConsole = (node) => !!node && extractBindingNames(node).includes('console');
|
|
58
|
+
walk.full(ast, (node) => {
|
|
59
|
+
switch (node.type) {
|
|
60
|
+
case 'VariableDeclarator':
|
|
61
|
+
if (bindsConsole(node.id))
|
|
62
|
+
declares = true;
|
|
63
|
+
break;
|
|
64
|
+
case 'FunctionDeclaration':
|
|
65
|
+
case 'FunctionExpression':
|
|
66
|
+
case 'ArrowFunctionExpression':
|
|
67
|
+
if (bindsConsole(node.id) || node.params.some((p) => bindsConsole(p)))
|
|
68
|
+
declares = true;
|
|
69
|
+
break;
|
|
70
|
+
case 'ClassDeclaration':
|
|
71
|
+
case 'ClassExpression':
|
|
72
|
+
if (bindsConsole(node.id))
|
|
73
|
+
declares = true;
|
|
74
|
+
break;
|
|
75
|
+
case 'CatchClause':
|
|
76
|
+
if (bindsConsole(node.param))
|
|
77
|
+
declares = true;
|
|
78
|
+
break;
|
|
79
|
+
case 'ImportSpecifier':
|
|
80
|
+
case 'ImportDefaultSpecifier':
|
|
81
|
+
case 'ImportNamespaceSpecifier':
|
|
82
|
+
if (node.local.name === 'console')
|
|
83
|
+
declares = true;
|
|
84
|
+
break;
|
|
85
|
+
case 'AssignmentExpression':
|
|
86
|
+
if (bindsConsole(node.left))
|
|
87
|
+
assigns = true;
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
if (!assigns || declares)
|
|
92
|
+
return null;
|
|
93
|
+
let offset = 0;
|
|
94
|
+
for (const statement of ast.body) {
|
|
95
|
+
if (statement.type !== 'ExpressionStatement' || !('directive' in statement))
|
|
96
|
+
break;
|
|
97
|
+
offset = statement.end;
|
|
98
|
+
}
|
|
99
|
+
return offset;
|
|
100
|
+
}
|
|
101
|
+
/** `code` with the local `console` binding inserted, or null when it needs none. */
|
|
102
|
+
export function bindConsoleLocally(code) {
|
|
103
|
+
const at = freeConsoleAssignmentInsertion(code);
|
|
104
|
+
if (at === null)
|
|
105
|
+
return null;
|
|
106
|
+
// No newline, so every later line keeps its number; after a directive the `;` ends
|
|
107
|
+
// it even when the source relied on ASI (`'use strict'\n`).
|
|
108
|
+
const sep = at === 0 ? '' : ';';
|
|
109
|
+
return `${code.slice(0, at)}${sep}${CONSOLE_LOCAL_BINDING}${code.slice(at)}`;
|
|
110
|
+
}
|
|
111
|
+
/** Composed by `--app gjs` whenever the console shim is injected. */
|
|
112
|
+
export function consoleAssignPlugin() {
|
|
113
|
+
return {
|
|
114
|
+
name: 'gjsify-console-assign',
|
|
115
|
+
transform: {
|
|
116
|
+
filter: { id: SOURCE_RE },
|
|
117
|
+
handler(code, id) {
|
|
118
|
+
if (!SOURCE_RE.test(id))
|
|
119
|
+
return null;
|
|
120
|
+
const out = bindConsoleLocally(code);
|
|
121
|
+
return out === null ? null : { code: out, map: null };
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|