@gjsify/rolldown-plugin-gjsify 0.4.36 → 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.
@@ -28,11 +28,16 @@ import { deepkitPlugin } from '@gjsify/rolldown-plugin-deepkit';
28
28
  import { ALIASES_NODE_FOR_NATIVESCRIPT, getDerivedAliasesSync } from '@gjsify/resolve-npm';
29
29
  import { globToEntryPoints } from '../utils/entry-points.js';
30
30
  import { gjsImportsEmptyPlugin } from '../plugins/gjs-imports-empty.js';
31
+ import { platformResolvePlugin, detectNativescriptPlatform, nativescriptPlatformDefines, } from '../plugins/platform-resolve.js';
31
32
  export const setupForNativescript = async (input) => {
32
33
  const userExternal = input.userExternal ?? [];
33
34
  const external = [...userExternal];
34
35
  const exclude = input.pluginOptions.exclude ?? [];
35
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();
36
41
  // Bare Node-builtin + `node:*` prefix aliases — both forms route to the
37
42
  // same `@gjsify/<X>` target. Generated deterministically from
38
43
  // `ALIASES_NODE_FOR_NATIVESCRIPT` so a single source-of-truth in
@@ -80,6 +85,13 @@ export const setupForNativescript = async (input) => {
80
85
  // NO `window` define — NativeScript apps don't have a DOM
81
86
  // and rely on the canonical absence of `window` to gate
82
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 }),
83
95
  },
84
96
  },
85
97
  output: {
@@ -95,6 +107,10 @@ export const setupForNativescript = async (input) => {
95
107
  };
96
108
  const plugins = [
97
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 }),
98
114
  aliasPlugin({ entries: flattenAliases(aliasMap) }),
99
115
  // NO blueprintPlugin — Blueprint is a GTK-specific UI DSL
100
116
  // NO cssAsStringPlugin — NS ships its own CSS pipeline via
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 };
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/rolldown-plugin-gjsify",
3
- "version": "0.4.36",
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,11 +48,11 @@
48
48
  ],
49
49
  "license": "MIT",
50
50
  "dependencies": {
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",
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
56
  "@rollup/pluginutils": "^5.4.0",
57
57
  "acorn": "^8.16.0",
58
58
  "acorn-walk": "^8.3.5",
@@ -60,7 +60,7 @@
60
60
  "lightningcss": "^1.32.0"
61
61
  },
62
62
  "peerDependencies": {
63
- "@gjsify/lightningcss-native": "^0.4.36",
63
+ "@gjsify/lightningcss-native": "^0.4.37",
64
64
  "rolldown": "^1.0.3"
65
65
  },
66
66
  "peerDependenciesMeta": {