@gjsify/nativescript-vite 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.
package/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # @gjsify/nativescript-vite
2
+
3
+ Vite 8 / Rolldown compatibility + gjsify transforms for building [NativeScript](https://nativescript.org/) apps.
4
+
5
+ It is a **thin composer** around the upstream `@nativescript/vite` integration. It takes the Vite config that `@nativescript/vite` produces, fixes the two pieces Vite 8 / Rolldown reject, and layers gjsify's NativeScript transforms on top — so a NativeScript app builds under Vite 8 with `gjsify`'s stack.
6
+
7
+ ## Why
8
+
9
+ `@nativescript/vite` targets Vite ≤ 7 (Rollup). Under **Vite 8 / Rolldown** its config uses two constructs the Rust bundler rejects, so a pristine NativeScript app fails to build. This package patches exactly those two pieces on the returned config object — no fork of `@nativescript/vite`, no patched `node_modules` — then adds the same gjsify NativeScript transforms that `gjsify build --app nativescript` applies.
10
+
11
+ **Empirically validated:** with pristine `@nativescript/vite` 2.0.3 + this composer, a real NativeScript app (a `@nativescript/canvas` three.js teapot) builds on **Vite 8.0.16** → 612 modules → `bundle.mjs` (~450 kB), `ns prepare android` succeeds, and the bundle has zero `@nativescript/vite` / `gi://` / `@girs/*` leakage.
12
+
13
+ ## What it fixes
14
+
15
+ 1. **Function-replacement `resolve.alias` entries are dropped.** `@nativescript/vite` registers aliases whose `replacement` is a *function* (platform-`main`, the tsconfig wildcard, `@nativescript/core/.../index` canonicalization). Vite 8's native alias (Rolldown) only accepts string replacements and otherwise fails with `Failed to convert builtin plugin 'ViteAlias' … function replacement into rust type String`. The upstream `nativescript-package-resolver` plugin (a `resolveId` hook, kept) and the string `~/` / `@` aliases (kept) already cover the same resolution, so dropping the function entries is safe.
16
+ 2. **The explicit `@rollup/plugin-commonjs` plugin is removed.** It crashes Rolldown with `Cannot read properties of undefined (reading 'currentLoadingModule')`. Rolldown handles CommonJS (`@nativescript/core`'s modules) natively, so the plugin is not needed.
17
+
18
+ On top of the fixes, it spreads `@gjsify/vite-plugin-gjsify`'s `gjsifyNativescript()` preset: `gi://` → empty module, platform file resolution (`*.android` / `*.ios` / `*.native`), platform defines (`__ANDROID__` / `__IOS__` / `__APPLE__` / `__VISIONOS__` / `__DEV__`), and the node-builtin alias routing (incl. `module` → `@gjsify/module`).
19
+
20
+ ## Install
21
+
22
+ The package's only hard dependency is `@gjsify/vite-plugin-gjsify`. Everything from the NativeScript side is an **optional peer** — install it in your NativeScript app alongside the rest of your NS toolchain:
23
+
24
+ ```bash
25
+ npm install -D @gjsify/nativescript-vite @nativescript/vite vite
26
+ # plus whatever your app already uses, e.g.:
27
+ npm install @nativescript/core @nativescript/canvas @nativescript/canvas-polyfill
28
+ ```
29
+
30
+ Optional peers: `@nativescript/vite`, `@nativescript/core`, `nativescript`, `@nativescript/canvas`, `@nativescript/canvas-polyfill`, and `vite` (`^8.0.14`). They are not installed by this package — your NativeScript app provides them. If `@nativescript/vite` is missing, the config factory throws a clear, actionable error.
31
+
32
+ ## Usage
33
+
34
+ `vite.config.ts` — use the exported factory as your whole config:
35
+
36
+ ```ts
37
+ import { defineNativescriptConfig } from '@gjsify/nativescript-vite';
38
+
39
+ export default defineNativescriptConfig();
40
+ ```
41
+
42
+ It returns an async Vite config **function**, so Vite resolves `mode` and passes it through; the upstream `@nativescript/vite` config is built for the right mode before the fixes + gjsify transforms are applied. The first argument is forwarded to the `gjsifyNativescript()` preset from `@gjsify/vite-plugin-gjsify` (`{ reflection?, aliases?, optimizeDepsExclude? }`); an optional second argument is a Vite config (object or `(env) => config`) that is `mergeConfig`'d in last, so you can compose your own settings on top.
43
+
44
+ `nativescript.config.ts` — select the Vite bundler:
45
+
46
+ ```ts
47
+ import { NativeScriptConfig } from '@nativescript/core';
48
+
49
+ export default {
50
+ id: 'org.example.app',
51
+ appPath: 'src',
52
+ android: { v8Flags: '--expose_gc', markingMode: 'none' },
53
+ bundler: 'vite',
54
+ } satisfies NativeScriptConfig;
55
+ ```
56
+
57
+ Then build as usual:
58
+
59
+ ```bash
60
+ ns prepare android # or: ns run android / ns run ios
61
+ ```
62
+
63
+ ## Audio-context note
64
+
65
+ `@nativescript/canvas` transitively pulls a Web-Audio module that references surface a canvas/WebGL app never executes. If your app only does 2D/WebGL rendering, mark it `external` via the composable second argument so it stays out of the bundle:
66
+
67
+ ```ts
68
+ import { defineNativescriptConfig } from '@gjsify/nativescript-vite';
69
+
70
+ export default defineNativescriptConfig({}, {
71
+ build: { rollupOptions: { external: [/@nativescript\/audio-context/, /@nativescript\/canvas-media/] } },
72
+ });
73
+ ```
74
+
75
+ ## Known limitations
76
+
77
+ - **Production target.** Validated for the production build path (`ns prepare` / `ns build` / `ns run --no-hmr`). The upstream dev-server / HMR plugins are passed through untouched but not separately validated under Vite 8.
78
+ - **Web Worker builds** keep the upstream `worker` config verbatim; gjsify's transforms are not propagated into `worker.plugins`. A worker-using app is an untested shape.
79
+ - **Conditional exports.** `resolve.conditions` keep upstream's `browser` active alongside `nativescript`, so a package that ships *divergent* `browser` vs `nativescript` conditional exports may resolve its `browser` variant. The validated apps do not hit this.
80
+ - **Version coupling.** The two fixes target `@nativescript/vite`'s returned config shape (array-form aliases with function replacements, a plugin named `commonjs`). If a future version renames/wraps those, the composer warns at build time that the CommonJS fix did not apply.
81
+
82
+ ## License
83
+
84
+ MIT — see the [gjsify](https://github.com/gjsify/gjsify) workspace.
package/lib/index.d.ts ADDED
@@ -0,0 +1,40 @@
1
+ import type { ConfigEnv, UserConfig } from 'vite';
2
+ import type { GjsifyNativescriptOptions } from '@gjsify/vite-plugin-gjsify';
3
+ export type GjsifyNativescriptViteOptions = GjsifyNativescriptOptions;
4
+ /** A user-supplied Vite config (or config factory) merged in as the final layer. */
5
+ export type UserConfigInput = UserConfig | ((env: ConfigEnv) => UserConfig | Promise<UserConfig>);
6
+ /**
7
+ * Build a NativeScript Vite config that works under Vite 8 / Rolldown with
8
+ * gjsify's transforms. Use it as your app's `vite.config.ts`:
9
+ *
10
+ * ```ts
11
+ * import { defineNativescriptConfig } from '@gjsify/nativescript-vite';
12
+ * export default defineNativescriptConfig();
13
+ * ```
14
+ *
15
+ * Compose your own Vite config on top (e.g. externalize a canvas/WebGL app's
16
+ * unused `@nativescript/audio-context`) by passing a second argument — it is
17
+ * `mergeConfig`'d in last, so it wins:
18
+ *
19
+ * ```ts
20
+ * export default defineNativescriptConfig({}, {
21
+ * build: { rollupOptions: { external: [/@nativescript\/audio-context/] } },
22
+ * });
23
+ * ```
24
+ *
25
+ * Returns an async Vite config FUNCTION (Vite resolves the `mode` and passes it
26
+ * through), so the upstream `@nativescript/vite` config is built for the right
27
+ * mode before the fixes + gjsify transforms are applied.
28
+ *
29
+ * @param options forwarded to `@gjsify/vite-plugin-gjsify`'s `gjsifyNativescript()` preset.
30
+ * @param userConfig optional final-layer Vite config (object or `(env) => config`).
31
+ */
32
+ export declare function defineNativescriptConfig(options?: GjsifyNativescriptViteOptions, userConfig?: UserConfigInput): (env: ConfigEnv) => Promise<UserConfig>;
33
+ export default defineNativescriptConfig;
34
+ /**
35
+ * Apply the two Vite 8 / Rolldown fixes to a returned `@nativescript/vite`
36
+ * config: drop function-replacement aliases and the explicit
37
+ * `@rollup/plugin-commonjs`. Returns a shallow copy with the fixed `resolve` and
38
+ * `plugins` — the input is not mutated.
39
+ */
40
+ export declare function applyVite8Fixes(config: UserConfig): UserConfig;
package/lib/index.js ADDED
@@ -0,0 +1,184 @@
1
+ // `@gjsify/nativescript-vite` — Vite 8 / Rolldown compatibility + gjsify
2
+ // transforms for building NativeScript apps.
3
+ //
4
+ // `@nativescript/vite` (the upstream NativeScript Vite integration) does not
5
+ // build under Vite 8 / Rolldown: its config uses two constructs Rolldown
6
+ // rejects. This package COMPOSES the upstream config and fixes exactly those
7
+ // two pieces on the returned config object, then layers gjsify's NS transforms
8
+ // on top — so a NativeScript app builds under Vite 8 with `gjsify`'s stack.
9
+ //
10
+ // The two upstream incompatibilities (verified by running real builds):
11
+ // 1. Function-replacement `resolve.alias` entries (platform-`main` + tsconfig
12
+ // wildcard + `@nativescript/core/.../index` canonicalization) →
13
+ // `Failed to convert builtin plugin 'ViteAlias' … function replacement
14
+ // into rust type String`. They are DROPPED — the upstream
15
+ // `nativescript-package-resolver` plugin (a `resolveId` hook, kept) and the
16
+ // string `~/` / `@` aliases (kept) already cover the same resolution.
17
+ // 2. The explicit `@rollup/plugin-commonjs` plugin → `Cannot read properties
18
+ // of undefined (reading 'currentLoadingModule')`. It is DROPPED — Rolldown
19
+ // handles CommonJS (`@nativescript/core`'s modules) natively.
20
+ //
21
+ // On top, it spreads `@gjsify/vite-plugin-gjsify`'s `gjsifyNativescript()`
22
+ // preset (gi:// → empty, platform file resolution, platform defines, the
23
+ // node-builtin alias routing incl. `module` → `@gjsify/module`).
24
+ //
25
+ // `@nativescript/vite`, `@nativescript/core`, the `nativescript` CLI, the
26
+ // optional `@nativescript/canvas*` plugins and `vite` are all OPTIONAL peer
27
+ // dependencies — installed by the consumer only when targeting NativeScript.
28
+ import { mergeConfig } from 'vite';
29
+ import { gjsifyNativescript as gjsifyNativescriptTransforms } from '@gjsify/vite-plugin-gjsify';
30
+ /**
31
+ * Build a NativeScript Vite config that works under Vite 8 / Rolldown with
32
+ * gjsify's transforms. Use it as your app's `vite.config.ts`:
33
+ *
34
+ * ```ts
35
+ * import { defineNativescriptConfig } from '@gjsify/nativescript-vite';
36
+ * export default defineNativescriptConfig();
37
+ * ```
38
+ *
39
+ * Compose your own Vite config on top (e.g. externalize a canvas/WebGL app's
40
+ * unused `@nativescript/audio-context`) by passing a second argument — it is
41
+ * `mergeConfig`'d in last, so it wins:
42
+ *
43
+ * ```ts
44
+ * export default defineNativescriptConfig({}, {
45
+ * build: { rollupOptions: { external: [/@nativescript\/audio-context/] } },
46
+ * });
47
+ * ```
48
+ *
49
+ * Returns an async Vite config FUNCTION (Vite resolves the `mode` and passes it
50
+ * through), so the upstream `@nativescript/vite` config is built for the right
51
+ * mode before the fixes + gjsify transforms are applied.
52
+ *
53
+ * @param options forwarded to `@gjsify/vite-plugin-gjsify`'s `gjsifyNativescript()` preset.
54
+ * @param userConfig optional final-layer Vite config (object or `(env) => config`).
55
+ */
56
+ export function defineNativescriptConfig(options = {}, userConfig) {
57
+ return async (env) => {
58
+ const base = await loadUpstreamConfig(env);
59
+ const fixed = applyVite8Fixes(base);
60
+ // Layer gjsify's NS transforms (a Vite plugin array whose `config()` hook
61
+ // supplies the gi:// → empty redirect, platform resolution, defines, and
62
+ // node-builtin aliases) — the same composition proven to produce a
63
+ // working Vite 8 bundle.
64
+ const withTransforms = mergeConfig(fixed, {
65
+ plugins: [...gjsifyNativescriptTransforms(options)],
66
+ });
67
+ if (userConfig === undefined)
68
+ return withTransforms;
69
+ const resolved = typeof userConfig === 'function' ? await userConfig(env) : userConfig;
70
+ return mergeConfig(withTransforms, resolved);
71
+ };
72
+ }
73
+ export default defineNativescriptConfig;
74
+ /**
75
+ * Resolve `@nativescript/vite`'s TypeScript config factory (an optional peer).
76
+ * Prefers the documented root entry (`@nativescript/vite`) and falls back to the
77
+ * `./typescript` subpath that some published versions expose; throws a clear,
78
+ * actionable error if neither resolves or the export is missing.
79
+ */
80
+ async function loadUpstreamConfig(env) {
81
+ // String-variable specifiers: `@nativescript/vite` is an OPTIONAL peer, not
82
+ // installed in this repo, so a literal `import('@nativescript/vite')` would
83
+ // fail type resolution. Non-literal specifiers resolve at runtime only (the
84
+ // consumer's NS app provides the package). Root is the documented entry
85
+ // (`export { typescriptConfig }`); the subpath is a fallback for versions
86
+ // whose `exports` map exposes `./typescript` but not the root re-export.
87
+ const candidates = ['@nativescript/vite', '@nativescript/vite/typescript'];
88
+ let lastError;
89
+ for (const specifier of candidates) {
90
+ let mod;
91
+ try {
92
+ mod = (await import(specifier));
93
+ }
94
+ catch (cause) {
95
+ lastError = cause;
96
+ continue;
97
+ }
98
+ const { typescriptConfig } = mod;
99
+ if (typeof typescriptConfig === 'function') {
100
+ return typescriptConfig({ mode: env.mode });
101
+ }
102
+ lastError = new Error(`"${specifier}" resolved but did not export a "typescriptConfig" function`);
103
+ }
104
+ throw new Error('@gjsify/nativescript-vite requires the optional peer "@nativescript/vite" exporting `typescriptConfig` ' +
105
+ '(install it alongside @nativescript/core in your NativeScript app).', { cause: lastError });
106
+ }
107
+ /**
108
+ * Apply the two Vite 8 / Rolldown fixes to a returned `@nativescript/vite`
109
+ * config: drop function-replacement aliases and the explicit
110
+ * `@rollup/plugin-commonjs`. Returns a shallow copy with the fixed `resolve` and
111
+ * `plugins` — the input is not mutated.
112
+ */
113
+ export function applyVite8Fixes(config) {
114
+ const out = { ...config };
115
+ if (config.resolve?.alias) {
116
+ out.resolve = { ...config.resolve, alias: dropFunctionAliases(config.resolve.alias) };
117
+ }
118
+ if (Array.isArray(config.plugins)) {
119
+ const before = countPluginByName(config.plugins, 'commonjs');
120
+ out.plugins = stripPluginByName(config.plugins, 'commonjs');
121
+ // Observability: the strip depends on `@rollup/plugin-commonjs` registering
122
+ // exactly `name: 'commonjs'` as a synchronous, top-level (or nested-array)
123
+ // plugin object. If a future `@nativescript/vite` renames it or wraps it in
124
+ // a Promise, the strip silently misses and Rolldown crashes again with the
125
+ // `currentLoadingModule` error this package exists to prevent — so warn loud
126
+ // when the plugin we expect to remove was NOT found.
127
+ if (before === 0) {
128
+ // one-time build-time diagnostic — surfaces a silent upstream rename
129
+ console.warn('[@gjsify/nativescript-vite] no plugin named "commonjs" found in the upstream config — ' +
130
+ 'if your @nativescript/vite version renamed/wrapped @rollup/plugin-commonjs, the Rolldown ' +
131
+ 'CommonJS fix may not have applied.');
132
+ }
133
+ }
134
+ return out;
135
+ }
136
+ /**
137
+ * Keep only `resolve.alias` entries Vite 8 / Rolldown accepts. The array form
138
+ * may carry function `replacement`s (Vite types `Alias.replacement` as `string`,
139
+ * but `@nativescript/vite` sets functions at runtime — so this check is
140
+ * LOAD-BEARING; do not remove it as "always true"). Malformed/holey entries are
141
+ * skipped. The object/record form only ever holds strings, so it passes through.
142
+ */
143
+ function dropFunctionAliases(alias) {
144
+ if (!Array.isArray(alias))
145
+ return alias;
146
+ return alias.filter((entry) => entry != null && typeof entry.replacement !== 'function');
147
+ }
148
+ /**
149
+ * Remove every plugin with the given `name` from a (possibly nested) Vite
150
+ * plugins array, preserving order and nesting of the rest. Assumes plugins are
151
+ * synchronous objects (not `Promise<Plugin>`); `@nativescript/vite` registers
152
+ * `@rollup/plugin-commonjs` synchronously, so this holds for the supported
153
+ * versions.
154
+ */
155
+ function stripPluginByName(plugins, name) {
156
+ if (!Array.isArray(plugins))
157
+ return plugins;
158
+ const out = [];
159
+ for (const entry of plugins) {
160
+ if (Array.isArray(entry)) {
161
+ out.push(stripPluginByName(entry, name));
162
+ }
163
+ else if (entry && typeof entry === 'object' && 'name' in entry && entry.name === name) {
164
+ // dropped
165
+ }
166
+ else {
167
+ out.push(entry);
168
+ }
169
+ }
170
+ return out;
171
+ }
172
+ /** Count plugins with the given `name` across a (possibly nested) plugins array. */
173
+ function countPluginByName(plugins, name) {
174
+ if (!Array.isArray(plugins))
175
+ return 0;
176
+ let n = 0;
177
+ for (const entry of plugins) {
178
+ if (Array.isArray(entry))
179
+ n += countPluginByName(entry, name);
180
+ else if (entry && typeof entry === 'object' && 'name' in entry && entry.name === name)
181
+ n += 1;
182
+ }
183
+ return n;
184
+ }
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@gjsify/nativescript-vite",
3
+ "version": "0.4.36",
4
+ "description": "Vite 8 / Rolldown compatibility + gjsify transforms for building NativeScript apps — composes @nativescript/vite and fixes the pieces Rolldown rejects.",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "module": "lib/index.js",
8
+ "types": "lib/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./lib/index.d.ts",
12
+ "default": "./lib/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "lib"
17
+ ],
18
+ "scripts": {
19
+ "clear": "rm -rf lib tsconfig.tsbuildinfo || exit 0",
20
+ "check": "tsc --noEmit",
21
+ "build": "tsc"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/gjsify/gjsify.git"
26
+ },
27
+ "bugs": {
28
+ "url": "https://github.com/gjsify/gjsify/issues"
29
+ },
30
+ "homepage": "https://github.com/gjsify/gjsify/tree/main/packages/infra/nativescript-vite#readme",
31
+ "keywords": [
32
+ "vite",
33
+ "rolldown",
34
+ "nativescript",
35
+ "android",
36
+ "ios",
37
+ "gjsify"
38
+ ],
39
+ "license": "MIT",
40
+ "dependencies": {
41
+ "@gjsify/vite-plugin-gjsify": "workspace:^"
42
+ },
43
+ "peerDependencies": {
44
+ "@nativescript/canvas": "*",
45
+ "@nativescript/canvas-polyfill": "*",
46
+ "@nativescript/core": "*",
47
+ "@nativescript/vite": "*",
48
+ "nativescript": "*",
49
+ "vite": "^8.0.14"
50
+ },
51
+ "peerDependenciesMeta": {
52
+ "@nativescript/canvas": {
53
+ "optional": true
54
+ },
55
+ "@nativescript/canvas-polyfill": {
56
+ "optional": true
57
+ },
58
+ "@nativescript/core": {
59
+ "optional": true
60
+ },
61
+ "@nativescript/vite": {
62
+ "optional": true
63
+ },
64
+ "nativescript": {
65
+ "optional": true
66
+ },
67
+ "vite": {
68
+ "optional": true
69
+ }
70
+ },
71
+ "devDependencies": {
72
+ "@types/node": "^25.9.1",
73
+ "typescript": "^5.9.3",
74
+ "vite": "^8.0.14"
75
+ }
76
+ }