@gjsify/rolldown-plugin-gjsify 0.4.35 → 0.4.36

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.
@@ -2,13 +2,16 @@
2
2
  //
3
3
  // Browser builds redirect `@girs/*` and `gi://*` to an empty virtual module
4
4
  // (they appear transitively via `@gjsify/unit` and similar packages with
5
- // GJS-specific code paths). Standard Node.js browser polyfill aliases
6
- // for `process` and `assert` keep `@gjsify/unit`'s top-level imports
7
- // resolvable in a browser bundle.
5
+ // GJS-specific code paths). Bare Node specifiers + their `node:*` prefix
6
+ // variants are routed to `@gjsify/<X>` via the curated
7
+ // `ALIASES_NODE_FOR_BROWSER` table the dynamic per-runtimes-triplet
8
+ // resolver (`getDerivedAliasesSync`) finishes the routing in a second pass
9
+ // (`@gjsify/<X>` → `@gjsify/<X>/globals` / `@gjsify/empty` depending on the
10
+ // slot declaration).
8
11
  import { aliasPlugin } from '../plugins/alias.js';
9
12
  import { deepkitPlugin } from '@gjsify/rolldown-plugin-deepkit';
10
13
  import blueprintPlugin from '@gjsify/vite-plugin-blueprint';
11
- import { getDerivedAliasesSync } from '@gjsify/resolve-npm';
14
+ import { ALIASES_NODE_FOR_BROWSER, getDerivedAliasesSync } from '@gjsify/resolve-npm';
12
15
  import { globToEntryPoints } from '../utils/entry-points.js';
13
16
  import { gjsImportsEmptyPlugin } from '../plugins/gjs-imports-empty.js';
14
17
  import { cssAsStringPlugin } from '../plugins/css-as-string.js';
@@ -17,23 +20,34 @@ export const setupForBrowser = async (input) => {
17
20
  const external = [...userExternal];
18
21
  const exclude = input.pluginOptions.exclude ?? [];
19
22
  const entryPoints = await globToEntryPoints(input.input, exclude);
20
- // `@gjsify/unit` has `await import('process')` inside a try-catch that
21
- // is unreachable in browser (typeof document check comes first), but
22
- // Rolldown still resolves it statically. Map to `@gjsify/empty` so the
23
- // build succeeds. `assert` → `@gjsify/assert` because `@gjsify/unit`
24
- // imports `node:assert` at the top level.
25
- const browserPolyfillAliases = {
26
- process: '@gjsify/empty',
27
- 'node:process': '@gjsify/empty',
28
- assert: '@gjsify/assert',
29
- 'node:assert': '@gjsify/assert',
30
- };
23
+ // Bare Node-builtin + `node:*` prefix aliases both forms route to the
24
+ // same `@gjsify/<X>` target. Generated deterministically from
25
+ // `ALIASES_NODE_FOR_BROWSER` so a single source-of-truth in
26
+ // `@gjsify/resolve-npm` drives every browser-app build (no per-target
27
+ // hand-curation drift). The `process` entry here flips today's
28
+ // accidental `@gjsify/empty` to the polyfill (`@gjsify/process`) —
29
+ // `@gjsify/unit`'s `await import('process')` is unreachable in browser
30
+ // (typeof document check comes first), but Rolldown still resolves it
31
+ // statically; the polyfill is the more honest target.
32
+ const nodePrefixAliases = {};
33
+ for (const [bare, target] of Object.entries(ALIASES_NODE_FOR_BROWSER)) {
34
+ nodePrefixAliases[bare] = target;
35
+ nodePrefixAliases[`node:${bare}`] = target;
36
+ }
37
+ // Legacy overrides — kept as a separate Record so future per-target
38
+ // shims have an explicit landing pad. Today: empty. The `process` /
39
+ // `assert` entries that lived here historically have moved into
40
+ // `ALIASES_NODE_FOR_BROWSER` (assert → `@gjsify/assert`, process →
41
+ // `@gjsify/process`) — pulling them out of this layer means the new
42
+ // tabular values are not silently shadowed by a stale `@gjsify/empty`.
43
+ const browserPolyfillAliases = {};
31
44
  // Derived `@gjsify/<X>` aliases driven by per-package `gjsify.runtimes`
32
- // triplet declarations. Merge order: derived (lowest priority) → hardcoded
33
- // browser polyfillsuser. Hardcoded entries WIN for the curated specifier
34
- // set, preserving 100% backwards-compatible behavior.
45
+ // triplet declarations. Merge order: derived (lowest priority) → curated
46
+ // bare/`node:*` Node-builtin map legacy per-target overrides user.
47
+ // Higher tiers WIN on conflict — the user always retains final say.
35
48
  const aliasMap = {
36
49
  ...getDerivedAliasesSync('browser'),
50
+ ...nodePrefixAliases,
37
51
  ...browserPolyfillAliases,
38
52
  ...input.pluginOptions.aliases,
39
53
  ...input.userAliases,
package/lib/app/gjs.d.ts CHANGED
@@ -30,3 +30,20 @@ export interface GjsFactoryInput {
30
30
  pluginOptions: PluginOptions;
31
31
  }
32
32
  export declare const setupForGjs: (input: GjsFactoryInput) => Promise<GjsBuildConfig>;
33
+ /**
34
+ * Recognize the `/register` and `/register/<feature>` subpath shapes that
35
+ * `--globals auto` injects into the bundle as side-effect imports.
36
+ *
37
+ * Matches every shape that goes through the alias layer + Rolldown
38
+ * resolution chain to a `@gjsify/<pkg>/register*` target:
39
+ * - bare: `<pkg>/register`, `<pkg>/register/<feature>`
40
+ * - fully qualified `@gjsify/<pkg>/register`, `@gjsify/<pkg>/register/<feature>`
41
+ * - resolved disk paths under a real `node_modules/<scope>/<pkg>/lib/esm/register*`
42
+ *
43
+ * Used by the `--app gjs` externals predicate to force-inline these even
44
+ * when the user passes them via `bundler.external`. Exported for direct
45
+ * use by the regression test in `auto-globals.spec.ts`; this is the
46
+ * canonical contract — change-detector status. Keep in sync with the
47
+ * `AGENTS.md` §Tree-shakeable globals subpath convention.
48
+ */
49
+ export declare function isRegisterSubpath(id: string): boolean;
package/lib/app/gjs.js CHANGED
@@ -58,6 +58,24 @@ export const setupForGjs = async (input) => {
58
58
  // string specifiers stay externalised by name.
59
59
  const exactExternal = ['cairo', 'gettext', 'system', ...userExternal];
60
60
  const external = (id) => {
61
+ // `@gjsify/<pkg>/register[/<feature>]` and the bare-`<pkg>/register`
62
+ // form MUST NEVER be externalized for `--app gjs`. These are the
63
+ // side-effect entry points that `--globals auto` injects to wire
64
+ // up `globalThis.{Buffer,fetch,…}`; GJS's native ESM loader has no
65
+ // node_modules walker AND does not follow `package.json#exports`
66
+ // maps for bare specifiers, so an externalized
67
+ // `import '@gjsify/buffer/register/buffer'` at runtime would throw
68
+ // `Module not found` even when `<pkg>/lib/esm/register/buffer.js`
69
+ // is on disk via the exports map.
70
+ //
71
+ // Inlining is the only safe option — the exclusion is by SHAPE
72
+ // (`*/register` or `*/register/*` substring), not by an explicit
73
+ // package list, so the invariant scales to every package added
74
+ // by the tree-shakeable-globals convention. See AGENTS.md
75
+ // §Tree-shakeable globals — /register subpath convention, and
76
+ // §Build — Rolldown, platform plugins for the externals policy.
77
+ if (isRegisterSubpath(id))
78
+ return false;
61
79
  if (id.startsWith('gi://'))
62
80
  return true;
63
81
  if (exactExternal.includes(id))
@@ -294,3 +312,48 @@ function flattenAliases(map) {
294
312
  }
295
313
  return out;
296
314
  }
315
+ /**
316
+ * Recognize the `/register` and `/register/<feature>` subpath shapes that
317
+ * `--globals auto` injects into the bundle as side-effect imports.
318
+ *
319
+ * Matches every shape that goes through the alias layer + Rolldown
320
+ * resolution chain to a `@gjsify/<pkg>/register*` target:
321
+ * - bare: `<pkg>/register`, `<pkg>/register/<feature>`
322
+ * - fully qualified `@gjsify/<pkg>/register`, `@gjsify/<pkg>/register/<feature>`
323
+ * - resolved disk paths under a real `node_modules/<scope>/<pkg>/lib/esm/register*`
324
+ *
325
+ * Used by the `--app gjs` externals predicate to force-inline these even
326
+ * when the user passes them via `bundler.external`. Exported for direct
327
+ * use by the regression test in `auto-globals.spec.ts`; this is the
328
+ * canonical contract — change-detector status. Keep in sync with the
329
+ * `AGENTS.md` §Tree-shakeable globals subpath convention.
330
+ */
331
+ export function isRegisterSubpath(id) {
332
+ // Source-shape: a bare or fully-qualified specifier ending in
333
+ // `/register` or `/register/<feature>`. The leading `/` rules out
334
+ // false positives like `register` (the bare word) or
335
+ // `@scope/unregister`.
336
+ //
337
+ // ✓ `fetch/register`
338
+ // ✓ `@gjsify/buffer/register`
339
+ // ✓ `@gjsify/node-globals/register/buffer`
340
+ // ✗ `register` (no `/` prefix)
341
+ // ✗ `@scope/unregister` (the `/` is followed by `un`, not `register`)
342
+ // ✗ `foo/register.js?query=1` (query-suffix → treat as resolved-path,
343
+ // caught by the second branch only when
344
+ // the file extension is intact)
345
+ if (/\/register(?:\/[^?]*)?$/.test(id)) {
346
+ return true;
347
+ }
348
+ // Resolved disk-path shape — Rolldown sees these after the alias
349
+ // plugin + node_modules resolver run. Matches both ESM build output
350
+ // (`lib/esm/register/<feature>.js`) and any future TS-direct setup
351
+ // that points the export at `src/register/<feature>.ts`. Strictly
352
+ // requires the file extension at the end — a Rolldown synthetic-id
353
+ // suffix like `?query=1` therefore does NOT match (those callers
354
+ // expect to flow through the normal externals path).
355
+ if (/[/\\]register(?:[/\\][^/\\]+)?\.(?:[mc]?js|ts)$/.test(id)) {
356
+ return true;
357
+ }
358
+ return false;
359
+ }
@@ -1,6 +1,8 @@
1
- export { setupForGjs } from './gjs.js';
1
+ export { setupForGjs, isRegisterSubpath } from './gjs.js';
2
2
  export type { GjsBuildConfig, GjsFactoryInput } from './gjs.js';
3
3
  export { setupForNode } from './node.js';
4
4
  export type { NodeBuildConfig, NodeFactoryInput } from './node.js';
5
5
  export { setupForBrowser } from './browser.js';
6
6
  export type { BrowserBuildConfig, BrowserFactoryInput } from './browser.js';
7
+ export { setupForNativescript } from './nativescript.js';
8
+ export type { NativescriptBuildConfig, NativescriptFactoryInput } from './nativescript.js';
package/lib/app/index.js CHANGED
@@ -1,3 +1,4 @@
1
- export { setupForGjs } from './gjs.js';
1
+ export { setupForGjs, isRegisterSubpath } from './gjs.js';
2
2
  export { setupForNode } from './node.js';
3
3
  export { setupForBrowser } from './browser.js';
4
+ export { setupForNativescript } from './nativescript.js';
@@ -0,0 +1,17 @@
1
+ import type { RolldownOptions, RolldownPluginOption } from 'rolldown';
2
+ import type { PluginOptions } from '../types/plugin-options.js';
3
+ export interface NativescriptBuildConfig {
4
+ options: RolldownOptions;
5
+ plugins: RolldownPluginOption[];
6
+ }
7
+ export interface NativescriptFactoryInput {
8
+ input?: RolldownOptions['input'];
9
+ output: {
10
+ file?: string;
11
+ dir?: string;
12
+ };
13
+ userExternal?: string[];
14
+ userAliases?: Record<string, string>;
15
+ pluginOptions: PluginOptions;
16
+ }
17
+ export declare const setupForNativescript: (input: NativescriptFactoryInput) => Promise<NativescriptBuildConfig>;
@@ -0,0 +1,114 @@
1
+ // `--app nativescript` Rolldown configuration factory.
2
+ //
3
+ // NativeScript builds target the V8 engine bundled into the NS Android (JNI)
4
+ // and iOS (Objective-C bridge) runtimes. Both runtimes have aligned V8
5
+ // versions since NS 8.5 (V8 10.3.22+) and ship full ES2024 surface, so the
6
+ // transform target is `esnext`.
7
+ //
8
+ // Native bridges (`java.*`, `android.*`, `androidx.*`, `kotlin.*`, `NS*`,
9
+ // `UI*`, `CG*`, `NSObject`, etc.) are exposed as GLOBAL identifiers by the
10
+ // NativeScript runtime at load time — like GJS's `imports.gi.*`, but ambient
11
+ // rather than module-namespaced. They are NOT aliased and NOT externalized;
12
+ // any value reference resolves at runtime against the host globals.
13
+ //
14
+ // `@girs/*` and `gi://*` imports are silenced via `gjsImportsEmptyPlugin` —
15
+ // they appear transitively through `@gjsify/unit` and similar packages with
16
+ // GJS-specific code paths that never execute on NS.
17
+ //
18
+ // Bare Node specifiers + their `node:*` prefix variants are routed to
19
+ // `@gjsify/<X>` via the curated `ALIASES_NODE_FOR_NATIVESCRIPT` table — the
20
+ // dynamic per-runtimes-triplet resolver (`getDerivedAliasesSync`) finishes
21
+ // the routing in a second pass (`@gjsify/<X>` → `@gjsify/<X>/globals` /
22
+ // `@gjsify/empty` depending on the slot declaration).
23
+ //
24
+ // No `cssAsStringPlugin` (NativeScript ships its own CSS pipeline as part
25
+ // of `@nativescript/core`) and no `blueprintPlugin` (Blueprint is GTK-only).
26
+ import { aliasPlugin } from '../plugins/alias.js';
27
+ import { deepkitPlugin } from '@gjsify/rolldown-plugin-deepkit';
28
+ import { ALIASES_NODE_FOR_NATIVESCRIPT, getDerivedAliasesSync } from '@gjsify/resolve-npm';
29
+ import { globToEntryPoints } from '../utils/entry-points.js';
30
+ import { gjsImportsEmptyPlugin } from '../plugins/gjs-imports-empty.js';
31
+ export const setupForNativescript = async (input) => {
32
+ const userExternal = input.userExternal ?? [];
33
+ const external = [...userExternal];
34
+ const exclude = input.pluginOptions.exclude ?? [];
35
+ const entryPoints = await globToEntryPoints(input.input, exclude);
36
+ // Bare Node-builtin + `node:*` prefix aliases — both forms route to the
37
+ // same `@gjsify/<X>` target. Generated deterministically from
38
+ // `ALIASES_NODE_FOR_NATIVESCRIPT` so a single source-of-truth in
39
+ // `@gjsify/resolve-npm` drives every NS-app build (no per-target
40
+ // hand-curation drift).
41
+ const nodePrefixAliases = {};
42
+ for (const [bare, target] of Object.entries(ALIASES_NODE_FOR_NATIVESCRIPT)) {
43
+ nodePrefixAliases[bare] = target;
44
+ nodePrefixAliases[`node:${bare}`] = target;
45
+ }
46
+ // Legacy / per-target overrides — kept as a separate Record so future
47
+ // mobile-specific shims (e.g. an NS-equivalent of `@gjsify/process`)
48
+ // have an explicit landing pad. Today: empty. New entries SHOULD go
49
+ // into `ALIASES_NODE_FOR_NATIVESCRIPT` so the same wiring is reachable
50
+ // from the Vite-plugin track (`gjsifyNativescript()` preset) too.
51
+ const nativescriptOverrideAliases = {};
52
+ // Derived `@gjsify/<X>` aliases driven by per-package `gjsify.runtimes`
53
+ // triplet declarations. Merge order: derived (lowest priority) → curated
54
+ // bare/`node:*` Node-builtin map → per-target overrides → user.
55
+ // Higher tiers WIN on conflict — the user always retains final say.
56
+ const aliasMap = {
57
+ ...getDerivedAliasesSync('nativescript'),
58
+ ...nodePrefixAliases,
59
+ ...nativescriptOverrideAliases,
60
+ ...input.pluginOptions.aliases,
61
+ ...input.userAliases,
62
+ };
63
+ const options = {
64
+ input: entryPoints,
65
+ // NS's V8 is a "browser-shaped" runtime from Rolldown's perspective:
66
+ // no Node-builtin auto-externals, no globalThis-shaped node-only
67
+ // semantics. `browser` platform is the closest match — the actual
68
+ // host environment is provided by the NS runtime at load time, not
69
+ // by V8 itself.
70
+ platform: 'browser',
71
+ external,
72
+ resolve: {
73
+ mainFields: ['nativescript', 'module', 'main'],
74
+ conditionNames: ['import', 'nativescript'],
75
+ },
76
+ transform: {
77
+ target: 'esnext',
78
+ define: {
79
+ global: 'globalThis',
80
+ // NO `window` define — NativeScript apps don't have a DOM
81
+ // and rely on the canonical absence of `window` to gate
82
+ // their cross-platform branches.
83
+ },
84
+ },
85
+ output: {
86
+ ...input.output,
87
+ format: 'esm',
88
+ sourcemap: false,
89
+ // Single-bundle output. The NS bundler (@nativescript/webpack or
90
+ // @nativescript/vite) consumes the resulting `.mjs` as an entry
91
+ // file and produces the final app bundle from there.
92
+ codeSplitting: false,
93
+ },
94
+ treeshake: true,
95
+ };
96
+ const plugins = [
97
+ gjsImportsEmptyPlugin(),
98
+ aliasPlugin({ entries: flattenAliases(aliasMap) }),
99
+ // NO blueprintPlugin — Blueprint is a GTK-specific UI DSL
100
+ // NO cssAsStringPlugin — NS ships its own CSS pipeline via
101
+ // @nativescript/core; .css imports are handled by the consuming
102
+ // @nativescript/webpack or @nativescript/vite build
103
+ deepkitPlugin({ reflection: input.pluginOptions.reflection }),
104
+ ];
105
+ return { options, plugins };
106
+ };
107
+ function flattenAliases(map) {
108
+ const out = {};
109
+ for (const [from, to] of Object.entries(map)) {
110
+ if (to)
111
+ out[from] = to;
112
+ }
113
+ return out;
114
+ }
package/lib/plugin.js CHANGED
@@ -10,7 +10,7 @@
10
10
  // The CLI consumer (`@gjsify/cli`) calls `gjsifyPlugin(...)` to get back
11
11
  // `{ options, plugins }`, then calls `rolldown({ ...options, plugins:
12
12
  // [...userPlugins, ...plugins] })`.
13
- import { setupForGjs, setupForNode, setupForBrowser } from './app/index.js';
13
+ import { setupForGjs, setupForNode, setupForBrowser, setupForNativescript } from './app/index.js';
14
14
  import { setupLib } from './library/index.js';
15
15
  /**
16
16
  * Build the Rolldown configuration template + plugin array for the given
@@ -60,6 +60,14 @@ export const gjsifyPlugin = async (input, pluginOptions = {}) => {
60
60
  userAliases: input.userAliases,
61
61
  pluginOptions,
62
62
  });
63
+ case 'nativescript':
64
+ return await setupForNativescript({
65
+ input: input.input,
66
+ output: input.output,
67
+ userExternal: input.userExternal,
68
+ userAliases: input.userAliases,
69
+ pluginOptions,
70
+ });
63
71
  default:
64
72
  throw new TypeError('Unknown app platform: ' + app);
65
73
  }
@@ -1 +1 @@
1
- export type App = 'gjs' | 'node' | 'browser';
1
+ export type App = 'gjs' | 'node' | 'browser' | 'nativescript';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/rolldown-plugin-gjsify",
3
- "version": "0.4.35",
3
+ "version": "0.4.36",
4
4
  "description": "Rolldown / Rollup / Vite plugin orchestrator for GJS, Node, and Browser targets",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -48,20 +48,20 @@
48
48
  ],
49
49
  "license": "MIT",
50
50
  "dependencies": {
51
- "@gjsify/console": "^0.4.35",
52
- "@gjsify/resolve-npm": "^0.4.35",
53
- "@gjsify/rolldown-plugin-deepkit": "^0.4.35",
54
- "@gjsify/rolldown-plugin-pnp": "^0.4.35",
55
- "@gjsify/vite-plugin-blueprint": "^0.4.35",
56
- "@rollup/pluginutils": "^5.3.0",
51
+ "@gjsify/console": "^0.4.36",
52
+ "@gjsify/resolve-npm": "^0.4.36",
53
+ "@gjsify/rolldown-plugin-deepkit": "^0.4.36",
54
+ "@gjsify/rolldown-plugin-pnp": "^0.4.36",
55
+ "@gjsify/vite-plugin-blueprint": "^0.4.36",
56
+ "@rollup/pluginutils": "^5.4.0",
57
57
  "acorn": "^8.16.0",
58
58
  "acorn-walk": "^8.3.5",
59
59
  "fast-glob": "^3.3.3",
60
60
  "lightningcss": "^1.32.0"
61
61
  },
62
62
  "peerDependencies": {
63
- "@gjsify/lightningcss-native": "^0.4.35",
64
- "rolldown": "^1.0.0"
63
+ "@gjsify/lightningcss-native": "^0.4.36",
64
+ "rolldown": "^1.0.3"
65
65
  },
66
66
  "peerDependenciesMeta": {
67
67
  "@gjsify/lightningcss-native": {
@@ -73,7 +73,7 @@
73
73
  },
74
74
  "devDependencies": {
75
75
  "@types/node": "^25.9.1",
76
- "rolldown": "^1.0.0",
77
- "typescript": "^6.0.3"
76
+ "rolldown": "^1.0.3",
77
+ "typescript": "^5.9.3"
78
78
  }
79
79
  }