@gjsify/rolldown-plugin-gjsify 0.4.35 → 0.4.37

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,130 @@
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
+ import { platformResolvePlugin, detectNativescriptPlatform, nativescriptPlatformDefines, } from '../plugins/platform-resolve.js';
32
+ export const setupForNativescript = async (input) => {
33
+ const userExternal = input.userExternal ?? [];
34
+ const external = [...userExternal];
35
+ const exclude = input.pluginOptions.exclude ?? [];
36
+ const entryPoints = await globToEntryPoints(input.input, exclude);
37
+ // Target platform — discovered from the env NS' CLI sets when it spawns a
38
+ // bundler (else `undefined` → `.native`-only resolution + neutral defines).
39
+ // `gjsify build` is a production bundler, so `__DEV__` is `false` here.
40
+ const platform = detectNativescriptPlatform();
41
+ // Bare Node-builtin + `node:*` prefix aliases — both forms route to the
42
+ // same `@gjsify/<X>` target. Generated deterministically from
43
+ // `ALIASES_NODE_FOR_NATIVESCRIPT` so a single source-of-truth in
44
+ // `@gjsify/resolve-npm` drives every NS-app build (no per-target
45
+ // hand-curation drift).
46
+ const nodePrefixAliases = {};
47
+ for (const [bare, target] of Object.entries(ALIASES_NODE_FOR_NATIVESCRIPT)) {
48
+ nodePrefixAliases[bare] = target;
49
+ nodePrefixAliases[`node:${bare}`] = target;
50
+ }
51
+ // Legacy / per-target overrides — kept as a separate Record so future
52
+ // mobile-specific shims (e.g. an NS-equivalent of `@gjsify/process`)
53
+ // have an explicit landing pad. Today: empty. New entries SHOULD go
54
+ // into `ALIASES_NODE_FOR_NATIVESCRIPT` so the same wiring is reachable
55
+ // from the Vite-plugin track (`gjsifyNativescript()` preset) too.
56
+ const nativescriptOverrideAliases = {};
57
+ // Derived `@gjsify/<X>` aliases driven by per-package `gjsify.runtimes`
58
+ // triplet declarations. Merge order: derived (lowest priority) → curated
59
+ // bare/`node:*` Node-builtin map → per-target overrides → user.
60
+ // Higher tiers WIN on conflict — the user always retains final say.
61
+ const aliasMap = {
62
+ ...getDerivedAliasesSync('nativescript'),
63
+ ...nodePrefixAliases,
64
+ ...nativescriptOverrideAliases,
65
+ ...input.pluginOptions.aliases,
66
+ ...input.userAliases,
67
+ };
68
+ const options = {
69
+ input: entryPoints,
70
+ // NS's V8 is a "browser-shaped" runtime from Rolldown's perspective:
71
+ // no Node-builtin auto-externals, no globalThis-shaped node-only
72
+ // semantics. `browser` platform is the closest match — the actual
73
+ // host environment is provided by the NS runtime at load time, not
74
+ // by V8 itself.
75
+ platform: 'browser',
76
+ external,
77
+ resolve: {
78
+ mainFields: ['nativescript', 'module', 'main'],
79
+ conditionNames: ['import', 'nativescript'],
80
+ },
81
+ transform: {
82
+ target: 'esnext',
83
+ define: {
84
+ global: 'globalThis',
85
+ // NO `window` define — NativeScript apps don't have a DOM
86
+ // and rely on the canonical absence of `window` to gate
87
+ // their cross-platform branches.
88
+ //
89
+ // Standard NS compile-time platform flags (`__ANDROID__` /
90
+ // `__IOS__` / `__APPLE__` / `__VISIONOS__` / `__DEV__`) so app
91
+ // code branching on them is statically resolved + dead-code
92
+ // eliminated per target — matching the globals
93
+ // `@nativescript/vite` seeds in its main entry.
94
+ ...nativescriptPlatformDefines(platform, { dev: false }),
95
+ },
96
+ },
97
+ output: {
98
+ ...input.output,
99
+ format: 'esm',
100
+ sourcemap: false,
101
+ // Single-bundle output. The NS bundler (@nativescript/webpack or
102
+ // @nativescript/vite) consumes the resulting `.mjs` as an entry
103
+ // file and produces the final app bundle from there.
104
+ codeSplitting: false,
105
+ },
106
+ treeshake: true,
107
+ };
108
+ const plugins = [
109
+ gjsImportsEmptyPlugin(),
110
+ // Platform-specific source variants (`*.android` / `*.ios` /
111
+ // `*.native`) win over the base file — resolved BEFORE the Node-builtin
112
+ // alias routing so a platform fork of a portable module is honored.
113
+ platformResolvePlugin({ platform }),
114
+ aliasPlugin({ entries: flattenAliases(aliasMap) }),
115
+ // NO blueprintPlugin — Blueprint is a GTK-specific UI DSL
116
+ // NO cssAsStringPlugin — NS ships its own CSS pipeline via
117
+ // @nativescript/core; .css imports are handled by the consuming
118
+ // @nativescript/webpack or @nativescript/vite build
119
+ deepkitPlugin({ reflection: input.pluginOptions.reflection }),
120
+ ];
121
+ return { options, plugins };
122
+ };
123
+ function flattenAliases(map) {
124
+ const out = {};
125
+ for (const [from, to] of Object.entries(map)) {
126
+ if (to)
127
+ out[from] = to;
128
+ }
129
+ return out;
130
+ }
package/lib/index.d.ts CHANGED
@@ -12,6 +12,8 @@ export type { TextLoaderPluginOptions, LoaderKind } from './plugins/text-loader.
12
12
  export { shebangPlugin, GJS_SHEBANG, NODE_SHEBANG, expandEnvTemplate, resolveShebangLine } from './plugins/shebang.js';
13
13
  export type { ShebangPluginOptions } from './plugins/shebang.js';
14
14
  export { gjsImportsEmptyPlugin } from './plugins/gjs-imports-empty.js';
15
+ export { platformResolvePlugin, detectNativescriptPlatform, nativescriptPlatformDefines, } from './plugins/platform-resolve.js';
16
+ export type { PlatformResolvePluginOptions, NativescriptPlatform } from './plugins/platform-resolve.js';
15
17
  export * from './plugin.js';
16
18
  import { gjsifyPlugin } from './plugin.js';
17
19
  export { gjsifyPlugin };
package/lib/index.js CHANGED
@@ -9,6 +9,7 @@ export { cssAsStringPlugin } from './plugins/css-as-string.js';
9
9
  export { textLoaderPlugin } from './plugins/text-loader.js';
10
10
  export { shebangPlugin, GJS_SHEBANG, NODE_SHEBANG, expandEnvTemplate, resolveShebangLine } from './plugins/shebang.js';
11
11
  export { gjsImportsEmptyPlugin } from './plugins/gjs-imports-empty.js';
12
+ export { platformResolvePlugin, detectNativescriptPlatform, nativescriptPlatformDefines, } from './plugins/platform-resolve.js';
12
13
  export * from './plugin.js';
13
14
  import { gjsifyPlugin } from './plugin.js';
14
15
  export { gjsifyPlugin };
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
  }
@@ -0,0 +1,34 @@
1
+ import type { Plugin } from 'rolldown';
2
+ export type NativescriptPlatform = 'android' | 'ios' | 'visionos';
3
+ export interface PlatformResolvePluginOptions {
4
+ /**
5
+ * Target platform. When omitted, only `.native.*` variants are tried
6
+ * (the platform-specific `.android.*` / `.ios.*` lookups are skipped).
7
+ */
8
+ platform?: NativescriptPlatform;
9
+ }
10
+ /**
11
+ * Resolve platform-specific source-file variants (`*.android` / `*.ios` /
12
+ * `*.native`) ahead of the base file. Relative imports only — bare/package
13
+ * specifiers are left to the normal resolver chain (package platform-`main`
14
+ * fields are a separate, rarer concern handled by the alias layer).
15
+ */
16
+ export declare function platformResolvePlugin(options?: PlatformResolvePluginOptions): Plugin;
17
+ /**
18
+ * Best-effort detection of the NativeScript target platform from the
19
+ * environment NS' CLI sets when it spawns a bundler (`NATIVESCRIPT_BUNDLER_ENV`
20
+ * / `NATIVESCRIPT_WEBPACK_ENV` carry the platform; `NATIVESCRIPT_PLATFORM` is
21
+ * an explicit override). Returns `undefined` when no platform is discernible —
22
+ * callers then fall back to `.native`-only resolution + neutral defines.
23
+ */
24
+ export declare function detectNativescriptPlatform(env?: Record<string, string | undefined>): NativescriptPlatform | undefined;
25
+ /**
26
+ * The standard NativeScript compile-time platform flags, matching the globals
27
+ * `@nativescript/vite` seeds in its main entry (`__ANDROID__` / `__IOS__` /
28
+ * `__APPLE__` / `__VISIONOS__` / `__DEV__`). Fed into the bundler's
29
+ * `transform.define` (Rolldown) / `define` (Vite) so NS app code branching on
30
+ * these constants is statically resolved + dead-code-eliminated per target.
31
+ */
32
+ export declare function nativescriptPlatformDefines(platform: NativescriptPlatform | undefined, opts?: {
33
+ dev?: boolean;
34
+ }): Record<string, string>;
@@ -0,0 +1,135 @@
1
+ // NativeScript / React-Native-style platform file resolution.
2
+ //
3
+ // Lets a shared codebase fork a single module per target platform by file
4
+ // name — `foo.android.ts` / `foo.ios.ts` (platform-specific) or `foo.native.ts`
5
+ // (any native platform, as opposed to the browser's `foo.ts`). An
6
+ // `import './foo'` (or `import './foo.js'`) resolves to the most specific
7
+ // variant that exists, in priority order:
8
+ //
9
+ // ./foo.<platform>.<ext> (e.g. ./foo.android.ts) — when a platform is known
10
+ // ./foo.native.<ext> — native-but-not-browser
11
+ // ./foo.<ext> — the base file (default resolver fallthrough)
12
+ //
13
+ // WHY THIS LIVES IN GJSIFY (not just a dependency on @nativescript/vite):
14
+ // `@nativescript/vite` implements the same lookup as a Vite `resolve.alias`
15
+ // whose `replacement` is a FUNCTION. Vite 8 / Rolldown rejects function
16
+ // replacements on the native alias path (`Failed to convert builtin plugin
17
+ // 'ViteAlias' … function replacement into rust type String`), which breaks the
18
+ // NS production build under Vite 8. Implemented here as a proper `resolveId`
19
+ // plugin HOOK (not a `resolve.alias`), it works identically under Rolldown
20
+ // (the `gjsify build --app nativescript` CLI) AND under Vite 7/8 (the
21
+ // `gjsifyNativescript()` preset) — the function-alias limitation only affects
22
+ // the `resolve.alias` config shorthand, not plugin `resolveId` hooks.
23
+ //
24
+ // Reference: @nativescript/vite `helpers/package-platform-aliases.js`. Original
25
+ // Copyright (c) NativeScript contributors, Apache-2.0. Reimplemented for the
26
+ // gjsify Rolldown/Vite plugin pipeline.
27
+ const PLATFORMS = ['android', 'ios', 'visionos'];
28
+ // JS/TS extensions a source specifier may carry. Stripped so the platform
29
+ // suffix lands BEFORE the extension (`./foo.js` → `./foo.android`).
30
+ const KNOWN_EXT_RE = /\.(tsx?|jsx?|mts|cts|mjs|cjs)$/;
31
+ function isPlatform(value) {
32
+ return PLATFORMS.includes(value);
33
+ }
34
+ /**
35
+ * Resolve platform-specific source-file variants (`*.android` / `*.ios` /
36
+ * `*.native`) ahead of the base file. Relative imports only — bare/package
37
+ * specifiers are left to the normal resolver chain (package platform-`main`
38
+ * fields are a separate, rarer concern handled by the alias layer).
39
+ */
40
+ export function platformResolvePlugin(options = {}) {
41
+ const platform = options.platform;
42
+ // Priority: platform-specific suffix first, then the platform-agnostic
43
+ // `native` suffix. Without a known platform, only `native` applies.
44
+ const suffixes = platform ? [platform, 'native'] : ['native'];
45
+ return {
46
+ name: 'gjsify-nativescript-platform-resolve',
47
+ resolveId: {
48
+ order: 'pre',
49
+ async handler(source, importer, extraOptions) {
50
+ // Only relative source imports get platform variants.
51
+ if (!importer)
52
+ return null;
53
+ if (!source.startsWith('./') && !source.startsWith('../'))
54
+ return null;
55
+ const extMatch = KNOWN_EXT_RE.exec(source);
56
+ const origExt = extMatch ? extMatch[0] : '';
57
+ const base = origExt ? source.slice(0, -origExt.length) : source;
58
+ for (const suffix of suffixes) {
59
+ // Try the bare form first (the resolver extension-probes,
60
+ // so `./foo.android` finds `./foo.android.ts`), then the
61
+ // extension-preserving form as a fallback.
62
+ const candidates = origExt && `${base}.${suffix}` !== `${base}.${suffix}${origExt}`
63
+ ? [`${base}.${suffix}`, `${base}.${suffix}${origExt}`]
64
+ : [`${base}.${suffix}`];
65
+ for (const candidate of candidates) {
66
+ if (candidate === source)
67
+ continue;
68
+ // `skipSelf: true` re-runs the resolver chain WITHOUT
69
+ // this plugin → no recursion on the nested resolve.
70
+ const resolved = await this.resolve(candidate, importer, {
71
+ skipSelf: true,
72
+ ...(extraOptions?.kind ? { kind: extraOptions.kind } : {}),
73
+ });
74
+ if (resolved && !resolved.external)
75
+ return resolved;
76
+ }
77
+ }
78
+ // No variant on disk → let the default chain resolve the base.
79
+ return null;
80
+ },
81
+ },
82
+ };
83
+ }
84
+ /**
85
+ * Best-effort detection of the NativeScript target platform from the
86
+ * environment NS' CLI sets when it spawns a bundler (`NATIVESCRIPT_BUNDLER_ENV`
87
+ * / `NATIVESCRIPT_WEBPACK_ENV` carry the platform; `NATIVESCRIPT_PLATFORM` is
88
+ * an explicit override). Returns `undefined` when no platform is discernible —
89
+ * callers then fall back to `.native`-only resolution + neutral defines.
90
+ */
91
+ export function detectNativescriptPlatform(env = process.env) {
92
+ const direct = env.NATIVESCRIPT_PLATFORM ?? env.NS_PLATFORM;
93
+ if (direct && isPlatform(direct))
94
+ return direct;
95
+ const raw = env.NATIVESCRIPT_BUNDLER_ENV ?? env.NATIVESCRIPT_WEBPACK_ENV;
96
+ if (raw) {
97
+ try {
98
+ const parsed = JSON.parse(raw);
99
+ // NS' webpack/vite env historically uses `{ android: true }` /
100
+ // `{ ios: true }` booleans; newer paths may pass `platform`.
101
+ if (parsed.android === true)
102
+ return 'android';
103
+ if (parsed.ios === true)
104
+ return 'ios';
105
+ if (parsed.visionos === true)
106
+ return 'visionos';
107
+ if (typeof parsed.platform === 'string' && isPlatform(parsed.platform)) {
108
+ return parsed.platform;
109
+ }
110
+ }
111
+ catch {
112
+ // Malformed env JSON → undefined (native-only fallback).
113
+ }
114
+ }
115
+ return undefined;
116
+ }
117
+ /**
118
+ * The standard NativeScript compile-time platform flags, matching the globals
119
+ * `@nativescript/vite` seeds in its main entry (`__ANDROID__` / `__IOS__` /
120
+ * `__APPLE__` / `__VISIONOS__` / `__DEV__`). Fed into the bundler's
121
+ * `transform.define` (Rolldown) / `define` (Vite) so NS app code branching on
122
+ * these constants is statically resolved + dead-code-eliminated per target.
123
+ */
124
+ export function nativescriptPlatformDefines(platform, opts = {}) {
125
+ const isAndroid = platform === 'android';
126
+ const isIos = platform === 'ios';
127
+ const isVisionOs = platform === 'visionos';
128
+ return {
129
+ __ANDROID__: String(isAndroid),
130
+ __IOS__: String(isIos),
131
+ __VISIONOS__: String(isVisionOs),
132
+ __APPLE__: String(isIos || isVisionOs),
133
+ __DEV__: String(opts.dev ?? false),
134
+ };
135
+ }
@@ -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.37",
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.37",
52
+ "@gjsify/resolve-npm": "^0.4.37",
53
+ "@gjsify/rolldown-plugin-deepkit": "^0.4.37",
54
+ "@gjsify/rolldown-plugin-pnp": "^0.4.37",
55
+ "@gjsify/vite-plugin-blueprint": "^0.4.37",
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.37",
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
  }