create-octane-xplat 0.1.0 → 0.4.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 +25 -0
- package/index.mjs +20 -17
- package/package.json +2 -2
- package/template/.gitattributes +1 -0
- package/template/.oxlintrc.json +25 -0
- package/template/App_Resources/Android/app.gradle +25 -0
- package/template/App_Resources/Android/before-plugins.gradle +15 -0
- package/template/App_Resources/Android/src/main/AndroidManifest.xml +43 -0
- package/template/App_Resources/Android/src/main/res/drawable/ic_launcher_foreground.xml +15 -0
- package/template/App_Resources/Android/src/main/res/drawable-hdpi/background.png +0 -0
- package/template/App_Resources/Android/src/main/res/drawable-hdpi/logo.png +0 -0
- package/template/App_Resources/Android/src/main/res/drawable-ldpi/background.png +0 -0
- package/template/App_Resources/Android/src/main/res/drawable-ldpi/logo.png +0 -0
- package/template/App_Resources/Android/src/main/res/drawable-mdpi/background.png +0 -0
- package/template/App_Resources/Android/src/main/res/drawable-mdpi/logo.png +0 -0
- package/template/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml +8 -0
- package/template/App_Resources/Android/src/main/res/drawable-xhdpi/background.png +0 -0
- package/template/App_Resources/Android/src/main/res/drawable-xhdpi/logo.png +0 -0
- package/template/App_Resources/Android/src/main/res/drawable-xxhdpi/background.png +0 -0
- package/template/App_Resources/Android/src/main/res/drawable-xxhdpi/logo.png +0 -0
- package/template/App_Resources/Android/src/main/res/drawable-xxxhdpi/background.png +0 -0
- package/template/App_Resources/Android/src/main/res/drawable-xxxhdpi/logo.png +0 -0
- package/template/App_Resources/Android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +5 -0
- package/template/App_Resources/Android/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/template/App_Resources/Android/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/template/App_Resources/Android/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/template/App_Resources/Android/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/template/App_Resources/Android/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/template/App_Resources/Android/src/main/res/values/colors.xml +14 -0
- package/template/App_Resources/Android/src/main/res/values/ic_launcher_background.xml +4 -0
- package/template/App_Resources/Android/src/main/res/values/styles.xml +42 -0
- package/template/App_Resources/Android/src/main/res/values-v21/colors.xml +5 -0
- package/template/App_Resources/Android/src/main/res/values-v21/styles.xml +34 -0
- package/template/App_Resources/Android/src/main/res/values-v29/styles.xml +18 -0
- package/template/App_Resources/Android/src/main/res/xml/network_security_config.xml +14 -0
- package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json +122 -0
- package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-1024.png +0 -0
- package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-20.png +0 -0
- package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png +0 -0
- package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png +0 -0
- package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png +0 -0
- package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png +0 -0
- package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png +0 -0
- package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png +0 -0
- package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png +0 -0
- package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png +0 -0
- package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png +0 -0
- package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png +0 -0
- package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png +0 -0
- package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png +0 -0
- package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png +0 -0
- package/template/App_Resources/iOS/Assets.xcassets/Contents.json +6 -0
- package/template/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json +23 -0
- package/template/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png +0 -0
- package/template/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png +0 -0
- package/template/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@3x.png +0 -0
- package/template/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json +23 -0
- package/template/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png +0 -0
- package/template/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png +0 -0
- package/template/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@3x.png +0 -0
- package/template/App_Resources/iOS/Assets.xcassets/octane.imageset/Contents.json +8 -0
- package/template/App_Resources/iOS/Assets.xcassets/octane.imageset/octane.png +0 -0
- package/template/App_Resources/iOS/Assets.xcassets/octane.imageset/octane@2x.png +0 -0
- package/template/App_Resources/iOS/Assets.xcassets/octane.imageset/octane@3x.png +0 -0
- package/template/App_Resources/iOS/Info.plist +52 -0
- package/template/App_Resources/iOS/LaunchScreen.storyboard +53 -0
- package/template/App_Resources/iOS/build.xcconfig +7 -0
- package/template/App_Resources/iOS/src/OctaneShaders.metal +128 -0
- package/template/gitignore +2 -0
- package/template/index.html +9 -9
- package/template/nativescript.config.ts +13 -0
- package/template/package.json +40 -18
- package/template/patches/@nativescript-community__vite-octane@0.2.1.patch +41 -0
- package/template/patches/@nativescript__vite@8.0.11.patch +294 -0
- package/template/pnpm-workspace.yaml +25 -3
- package/template/references.d.ts +1 -0
- package/template/src/App.tsrx +5 -3
- package/template/src/index.native.ts +33 -0
- package/template/src/style.css +21 -0
- package/template/tsconfig.json +4 -2
- package/template/tsconfig.native.json +18 -0
- package/template/vite.config.native.mts +8 -0
- package/template/vite.config.ts +14 -7
- /package/template/src/{main.tsrx → main.web.tsrx} +0 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
diff --git a/hmr/server/core-bundle.js b/hmr/server/core-bundle.js
|
|
2
|
+
index 993097b9d861a2a7c477f4e664213efec3eb6293..7a9b95180cbd47e1d9c71180bc4582a450333a7c 100644
|
|
3
|
+
--- a/hmr/server/core-bundle.js
|
|
4
|
+
+++ b/hmr/server/core-bundle.js
|
|
5
|
+
@@ -607,17 +607,32 @@ export function buildCoreMainShimCode() {
|
|
6
|
+
* class/function values. `exportNames` comes from the same disk-based export
|
|
7
|
+
* discovery the per-module bridge relies on (collectStaticExportNamesFromFile).
|
|
8
|
+
*/
|
|
9
|
+
-const RESERVED_EXPORT_NAMES = new Set(['default', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'delete', 'do', 'else', 'enum', 'export', 'extends', 'false', 'finally', 'for', 'function', 'if', 'import', 'in', 'instanceof', 'new', 'null', 'return', 'super', 'switch', 'this', 'throw', 'true', 'try', 'typeof', 'var', 'void', 'while', 'with', 'let', 'static', 'yield', 'await', 'eval', 'arguments', '__ns_core_sub_ns__']);
|
|
10
|
+
+// `default` is emitted separately (below); `__ns_core_sub_ns__` is this shim's
|
|
11
|
+
+// own local — a module exporting that name can't collide with it.
|
|
12
|
+
+const NON_DECLARABLE_EXPORT_NAMES = new Set(['default', '__ns_core_sub_ns__']);
|
|
13
|
+
+// Reserved words can't be declaration bindings (`export const enum` is a
|
|
14
|
+
+// syntax error), but they ARE legal export names — the specifier position in
|
|
15
|
+
+// `export { x as name }` accepts any IdentifierName. Emit those through a
|
|
16
|
+
+// temp + alias so a reserved-word export (zod's `enum`/`catch`/… shapes this
|
|
17
|
+
+// same code path) still reaches the bridged namespace.
|
|
18
|
+
+const RESERVED_EXPORT_NAMES = new Set(['break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'delete', 'do', 'else', 'enum', 'export', 'extends', 'false', 'finally', 'for', 'function', 'if', 'import', 'in', 'instanceof', 'new', 'null', 'return', 'super', 'switch', 'this', 'throw', 'true', 'try', 'typeof', 'var', 'void', 'while', 'with', 'let', 'static', 'yield', 'await', 'eval', 'arguments']);
|
|
19
|
+
export function buildCoreSubShimCode(sub, exportNames, hasOwnDefaultExport = false) {
|
|
20
|
+
const canonical = normalizeCoreSub(sub);
|
|
21
|
+
const registryKey = `@nativescript/core/${canonical}`;
|
|
22
|
+
- const names = Array.from(new Set(exportNames)).filter((n) => /^[A-Za-z_$][\w$]*$/.test(n) && !RESERVED_EXPORT_NAMES.has(n));
|
|
23
|
+
+ const names = Array.from(new Set(exportNames)).filter((n) => /^[A-Za-z_$][\w$]*$/.test(n) && !NON_DECLARABLE_EXPORT_NAMES.has(n));
|
|
24
|
+
const lines = [];
|
|
25
|
+
lines.push(`/* @nativescript/core bridge (bundle mode) — sub: ${canonical} */`);
|
|
26
|
+
lines.push(`import ${JSON.stringify(CORE_BUNDLE_PATH)};`);
|
|
27
|
+
lines.push(`const __ns_core_sub_ns__ = (globalThis.__NS_CORE_MODULES__ || {})[${JSON.stringify(registryKey)}];`);
|
|
28
|
+
lines.push(`if (!__ns_core_sub_ns__) { throw new Error(${JSON.stringify(`[ns-core] core bundle registry missing '${registryKey}' — restart the dev server or set NS_CORE_PER_MODULE=1`)}); }`);
|
|
29
|
+
+ let aliasIdx = 0;
|
|
30
|
+
for (const name of names) {
|
|
31
|
+
+ if (RESERVED_EXPORT_NAMES.has(name)) {
|
|
32
|
+
+ const alias = `__ns_core_alias_${aliasIdx++}`;
|
|
33
|
+
+ lines.push(`const ${alias} = __ns_core_sub_ns__[${JSON.stringify(name)}];`);
|
|
34
|
+
+ lines.push(`export { ${alias} as ${name} };`);
|
|
35
|
+
+ continue;
|
|
36
|
+
+ }
|
|
37
|
+
lines.push(`export const ${name} = __ns_core_sub_ns__.${name};`);
|
|
38
|
+
}
|
|
39
|
+
// Deep-subpath contract (see isDeepCoreSubpath / per-module bridge): a sub
|
|
40
|
+
diff --git a/hmr/server/deps-bundle.js b/hmr/server/deps-bundle.js
|
|
41
|
+
index 50e5abddddf80b35044d45eb108a64dd175bf6e7..799c7257a8a730515633f19f6221ad26ff8678eb 100644
|
|
42
|
+
--- a/hmr/server/deps-bundle.js
|
|
43
|
+
+++ b/hmr/server/deps-bundle.js
|
|
44
|
+
@@ -219,8 +219,37 @@ function isPackageRootSpec(spec) {
|
|
45
|
+
return false;
|
|
46
|
+
return pkgSub.split('/').length === (pkgSub.startsWith('@') ? 2 : 1);
|
|
47
|
+
}
|
|
48
|
+
-function resolveSpecToFile(spec, projectRoot, workspaceRoot, platform) {
|
|
49
|
+
+/**
|
|
50
|
+
+ * `/node_modules/<pkg>/<sub...>` → the exports-mapped file on disk
|
|
51
|
+
+ * (`rouzer/http` → `rouzer/dist/http.js`). Without this, non-root specs fall
|
|
52
|
+
+ * back to `resolveId` and land on Vite's dep-optimizer artifacts under
|
|
53
|
+
+ * `.vite/deps/` — flat bundles whose generated shared chunks
|
|
54
|
+
+ * (`foo-<hash>.js`) this pipeline cannot serve to the device.
|
|
55
|
+
+ */
|
|
56
|
+
+function resolvePackageSubpathFromSpecRoots(spec, roots, platform) {
|
|
57
|
+
+ const pkgSub = spec.replace(/^\/node_modules\//, '');
|
|
58
|
+
+ const parts = pkgSub.split('/');
|
|
59
|
+
+ const nameSegs = parts[0].startsWith('@') ? 2 : 1;
|
|
60
|
+
+ if (parts.length <= nameSegs)
|
|
61
|
+
+ return null;
|
|
62
|
+
+ const subpath = parts.slice(nameSegs).join('/');
|
|
63
|
+
+ for (const root of roots) {
|
|
64
|
+
+ const pkgDir = path.join(root, 'node_modules', ...parts.slice(0, nameSegs));
|
|
65
|
+
+ if (!existsSync(path.join(pkgDir, 'package.json')))
|
|
66
|
+
+ continue;
|
|
67
|
+
+ const resolved = resolvePackageSubpathEntry(pkgDir, subpath, platform);
|
|
68
|
+
+ if (resolved)
|
|
69
|
+
+ return resolved;
|
|
70
|
+
+ }
|
|
71
|
+
+ return null;
|
|
72
|
+
+}
|
|
73
|
+
+export function resolveSpecToFile(spec, projectRoot, workspaceRoot, platform) {
|
|
74
|
+
const roots = [projectRoot, ...(workspaceRoot && path.resolve(workspaceRoot) !== path.resolve(projectRoot) ? [workspaceRoot] : [])];
|
|
75
|
+
+ // `.vite/` is Vite's dep-optimizer store — generated artifacts with
|
|
76
|
+
+ // content-hashed names, not package files. Resolving them here would mint
|
|
77
|
+
+ // deps-bundle entries/shims for files whose names churn per optimization.
|
|
78
|
+
+ if (spec.includes('/.vite/'))
|
|
79
|
+
+ return null;
|
|
80
|
+
// Package roots honor the declared entry (exports/module/main) FIRST —
|
|
81
|
+
// matching esbuild's resolution of bare imports inside the bundle. The
|
|
82
|
+
// index fan-out would otherwise pick stray root files like a shipped
|
|
83
|
+
@@ -236,6 +265,12 @@ function resolveSpecToFile(spec, projectRoot, workspaceRoot, platform) {
|
|
84
|
+
if (abs)
|
|
85
|
+
return abs;
|
|
86
|
+
}
|
|
87
|
+
+ // Non-root specs (`rouzer/http`, `qubu/sqlite`): consult the package's
|
|
88
|
+
+ // exports map so the real dist file wins — the alternative downstream is
|
|
89
|
+
+ // the dep optimizer's `.vite/deps` artifact, whose sibling chunks 404.
|
|
90
|
+
+ const subpathEntry = resolvePackageSubpathFromSpecRoots(spec, roots, platform);
|
|
91
|
+
+ if (subpathEntry)
|
|
92
|
+
+ return subpathEntry;
|
|
93
|
+
return isPackageRootSpec(spec) ? null : resolvePackageRootEntry(spec, roots, platform);
|
|
94
|
+
}
|
|
95
|
+
// Never bundled: `@nativescript/core` belongs to the /ns/core bridge realm,
|
|
96
|
+
@@ -869,7 +904,15 @@ export function collectDepsModuleExportInfo(absPath, platform, seen = new Set())
|
|
97
|
+
names.delete('default');
|
|
98
|
+
return { names: Array.from(names).sort(), hasDefault: hasExistingDefaultExport(code) };
|
|
99
|
+
}
|
|
100
|
+
-const RESERVED_EXPORT_NAMES = new Set(['default', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'delete', 'do', 'else', 'enum', 'export', 'extends', 'false', 'finally', 'for', 'function', 'if', 'import', 'in', 'instanceof', 'new', 'null', 'return', 'super', 'switch', 'this', 'throw', 'true', 'try', 'typeof', 'var', 'void', 'while', 'with', 'let', 'static', 'yield', 'await', 'eval', 'arguments', '__ns_dep_ns__']);
|
|
101
|
+
+// `default` is emitted separately (below); `__ns_dep_ns__` is this shim's own
|
|
102
|
+
+// local — a package exporting that name can't collide with it.
|
|
103
|
+
+const NON_DECLARABLE_EXPORT_NAMES = new Set(['default', '__ns_dep_ns__']);
|
|
104
|
+
+// Reserved words can't be declaration bindings (`export const enum` is a
|
|
105
|
+
+// syntax error), but they ARE legal export names — the specifier position in
|
|
106
|
+
+// `export { x as name }` accepts any IdentifierName. Emit those through a
|
|
107
|
+
+// temp + alias so packages like zod (`enum`, `catch`, `function`, …) keep
|
|
108
|
+
+// their full surface on the bridged namespace.
|
|
109
|
+
+const RESERVED_EXPORT_NAMES = new Set(['break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'delete', 'do', 'else', 'enum', 'export', 'extends', 'false', 'finally', 'for', 'function', 'if', 'import', 'in', 'instanceof', 'new', 'null', 'return', 'super', 'switch', 'this', 'throw', 'true', 'try', 'typeof', 'var', 'void', 'while', 'with', 'let', 'static', 'yield', 'await', 'eval', 'arguments']);
|
|
110
|
+
/**
|
|
111
|
+
* Shim served at `/ns/m/node_modules/...` for a bundled module. The static
|
|
112
|
+
* bundle import guarantees the registry is populated before the body reads it
|
|
113
|
+
@@ -880,13 +923,20 @@ const RESERVED_EXPORT_NAMES = new Set(['default', 'break', 'case', 'catch', 'cla
|
|
114
|
+
export function buildDepsShimCode(key, exportNames, hasOwnDefaultExport) {
|
|
115
|
+
// JS_IDENTIFIER_RE (not ASCII \w): Angular exports `ɵɵdefineInjectable`-style
|
|
116
|
+
// names — dropping them here breaks every `import * as i0` consumer.
|
|
117
|
+
- const names = Array.from(new Set(exportNames)).filter((n) => JS_IDENTIFIER_RE.test(n) && !RESERVED_EXPORT_NAMES.has(n));
|
|
118
|
+
+ const names = Array.from(new Set(exportNames)).filter((n) => JS_IDENTIFIER_RE.test(n) && !NON_DECLARABLE_EXPORT_NAMES.has(n));
|
|
119
|
+
const lines = [];
|
|
120
|
+
lines.push(`/* ns-deps bridge (bundle mode) — ${key} */`);
|
|
121
|
+
lines.push(`import ${JSON.stringify(DEPS_BUNDLE_PATH)};`);
|
|
122
|
+
lines.push(`const __ns_dep_ns__ = (globalThis.__NS_DEPS_MODULES__ || {})[${JSON.stringify(key)}];`);
|
|
123
|
+
lines.push(`if (!__ns_dep_ns__) { throw new Error(${JSON.stringify(`[ns-deps] deps bundle registry missing '${key}' — restart the dev server or set NS_DEPS_PER_MODULE=1`)}); }`);
|
|
124
|
+
+ let aliasIdx = 0;
|
|
125
|
+
for (const name of names) {
|
|
126
|
+
+ if (RESERVED_EXPORT_NAMES.has(name)) {
|
|
127
|
+
+ const alias = `__ns_dep_alias_${aliasIdx++}`;
|
|
128
|
+
+ lines.push(`const ${alias} = __ns_dep_ns__[${JSON.stringify(name)}];`);
|
|
129
|
+
+ lines.push(`export { ${alias} as ${name} };`);
|
|
130
|
+
+ continue;
|
|
131
|
+
+ }
|
|
132
|
+
lines.push(`export const ${name} = __ns_dep_ns__.${name};`);
|
|
133
|
+
}
|
|
134
|
+
if (hasOwnDefaultExport) {
|
|
135
|
+
diff --git a/hmr/server/device-transform-helpers.js b/hmr/server/device-transform-helpers.js
|
|
136
|
+
index 9364ce9942b84fffd0e3a6eb6700d8253fa66e7e..93ee2bf2570303277d7b4350018b00fbbb31e2a4 100644
|
|
137
|
+
--- a/hmr/server/device-transform-helpers.js
|
|
138
|
+
+++ b/hmr/server/device-transform-helpers.js
|
|
139
|
+
@@ -81,12 +81,18 @@ function bareSpecifierFromFlatDepPath(depPath) {
|
|
140
|
+
}
|
|
141
|
+
catch { }
|
|
142
|
+
}
|
|
143
|
+
- const bareSpecifier = resolved || candidates[0];
|
|
144
|
+
+ // Unverified decode → NOT a package: `depPath` is a generated optimizer
|
|
145
|
+
+ // artifact (a hash-named shared chunk like `http-Dm_BQhab.js`, whose
|
|
146
|
+
+ // base64url '_' decodes to a bogus '/'). Returning null lets the caller
|
|
147
|
+
+ // keep the real `.vite/deps` URL instead of emitting a garbage specifier.
|
|
148
|
+
+ if (!resolved) {
|
|
149
|
+
+ return null;
|
|
150
|
+
+ }
|
|
151
|
+
if (!warnedVendorMisses.has(depPath)) {
|
|
152
|
+
warnedVendorMisses.add(depPath);
|
|
153
|
+
- console.warn(`[ns-vite] vendor-manifest miss: prebundled dep '${depPath}' is not in the vendor bundle — serving '${bareSpecifier}' over HTTP via /ns/m/node_modules/${resolved ? '' : ' (could not verify the package on disk; the specifier is a best-effort decode of the flattened id)'}.`);
|
|
154
|
+
+ console.warn(`[ns-vite] vendor-manifest miss: prebundled dep '${depPath}' is not in the vendor bundle — serving '${resolved}' over HTTP via /ns/m/node_modules/${resolved}.`);
|
|
155
|
+
}
|
|
156
|
+
- return bareSpecifier;
|
|
157
|
+
+ return resolved;
|
|
158
|
+
}
|
|
159
|
+
function rewriteVitePrebundleImportsForDevice(code, preserveVendorImports) {
|
|
160
|
+
const imports = collectTopLevelImportRecords(code);
|
|
161
|
+
@@ -111,6 +117,15 @@ function rewriteVitePrebundleImportsForDevice(code, preserveVendorImports) {
|
|
162
|
+
if (bareSpecifier) {
|
|
163
|
+
replacement = imp.text.replace(source, bareSpecifier);
|
|
164
|
+
}
|
|
165
|
+
+ else {
|
|
166
|
+
+ // Generated optimizer chunk (hash-named sibling of an
|
|
167
|
+
+ // optimized entry) — not a package and not decodable to one.
|
|
168
|
+
+ // Route the real artifact URL back through the bridge: the
|
|
169
|
+
+ // /ns/m route serves it from the dep-optimizer store (the
|
|
170
|
+
+ // `?v` stamp rides along as forcedVer) or emits an
|
|
171
|
+
+ // actionable optimizeDeps.exclude error.
|
|
172
|
+
+ replacement = imp.text.replace(source, `/ns/m/node_modules/.vite/deps/${depPath}`);
|
|
173
|
+
+ }
|
|
174
|
+
}
|
|
175
|
+
edits.push({
|
|
176
|
+
start: imp.start,
|
|
177
|
+
diff --git a/hmr/server/websocket-ns-m-request.js b/hmr/server/websocket-ns-m-request.js
|
|
178
|
+
index b54aefcc839e95906d76d02e522c052a706ca76f..d49159de94ba05dcfc372aa7a3c513e0f5c0a0d3 100644
|
|
179
|
+
--- a/hmr/server/websocket-ns-m-request.js
|
|
180
|
+
+++ b/hmr/server/websocket-ns-m-request.js
|
|
181
|
+
@@ -1,6 +1,9 @@
|
|
182
|
+
import * as path from 'path';
|
|
183
|
+
+import { realpathSync } from 'fs';
|
|
184
|
+
import { filterExistingNodeModulesTransformCandidates, getBlockedDeviceNodeModulesReason, resolveCandidateFilePath, tryReadRawExplicitJavaScriptModule } from './websocket-module-specifiers.js';
|
|
185
|
+
import { collapseLegacyNsMTags } from './websocket-ns-m-paths.js';
|
|
186
|
+
+import { resolveSpecToFile } from './deps-bundle.js';
|
|
187
|
+
+import { resolvePlatform } from '../../helpers/cli-flags.js';
|
|
188
|
+
function normalizeAbsoluteFilesystemSpec(spec, serverRoot) {
|
|
189
|
+
const toPosix = (value) => value.replace(/\\/g, '/');
|
|
190
|
+
const rootPosix = toPosix(serverRoot);
|
|
191
|
+
@@ -93,6 +96,29 @@ export function createNsMRequestContext(requestUrl, serverRoot, appVirtualWithSl
|
|
192
|
+
return { kind: 'next' };
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
+/**
|
|
196
|
+
+ * Canonical request URL for a resolved node_modules file. Project-root files
|
|
197
|
+
+ * keep the `/node_modules/...` form so module identity matches the /ns/m
|
|
198
|
+
+ * spec space; files outside the project root (hoisted/workspace) use the
|
|
199
|
+
+ * `/@fs` form Vite can load directly. Symlinked workspace-source packages
|
|
200
|
+
+ * resolve to their realpath so the TS transform applies — same policy as
|
|
201
|
+
+ * filterExistingNodeModulesTransformCandidates.
|
|
202
|
+
+ */
|
|
203
|
+
+function nodeModulesFileToRequestUrl(absPath, serverRoot) {
|
|
204
|
+
+ try {
|
|
205
|
+
+ const real = realpathSync(absPath);
|
|
206
|
+
+ if (real !== absPath && !real.split(path.sep).includes('node_modules')) {
|
|
207
|
+
+ return `/@fs${real.replace(/\\/g, '/')}`;
|
|
208
|
+
+ }
|
|
209
|
+
+ }
|
|
210
|
+
+ catch { }
|
|
211
|
+
+ const rootPosix = path.resolve(serverRoot).replace(/\\/g, '/').replace(/\/+$/, '');
|
|
212
|
+
+ const absPosix = absPath.replace(/\\/g, '/');
|
|
213
|
+
+ if (absPosix.startsWith(`${rootPosix}/`)) {
|
|
214
|
+
+ return absPosix.slice(rootPosix.length);
|
|
215
|
+
+ }
|
|
216
|
+
+ return `/@fs${absPosix}`;
|
|
217
|
+
+}
|
|
218
|
+
async function tryTransformRequest(transformRequest, url, timeoutMs) {
|
|
219
|
+
try {
|
|
220
|
+
return await transformRequest(url, timeoutMs);
|
|
221
|
+
@@ -146,6 +172,38 @@ export async function resolveNsMTransformedModule(options) {
|
|
222
|
+
return { transformed, resolvedCandidate };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
+ // Optimizer-artifact requests (`/node_modules/.vite/deps/<file>`): flat
|
|
226
|
+
+ // entries and hash-named shared chunks live in the dep-optimizer's store
|
|
227
|
+
+ // (often memory-only), keyed by the `?v=<browserHash>` stamp Vite emits —
|
|
228
|
+
+ // captured inbound as `forcedVer`. Replay it so Vite resolves the artifact
|
|
229
|
+
+ // as-is; a miss falls through to the route's transform-miss branch, which
|
|
230
|
+
+ // names optimizeDeps.exclude as the fix.
|
|
231
|
+
+ if (spec.includes('/.vite/deps/')) {
|
|
232
|
+
+ const depUrl = context.forcedVer ? `${spec}?v=${context.forcedVer}` : spec;
|
|
233
|
+
+ transformed = await tryTransformRequest(transformRequest, depUrl, timeoutMs);
|
|
234
|
+
+ if (transformed?.code) {
|
|
235
|
+
+ return { transformed, resolvedCandidate: depUrl };
|
|
236
|
+
+ }
|
|
237
|
+
+ return { transformed: null, resolvedCandidate };
|
|
238
|
+
+ }
|
|
239
|
+
+ // Bare or subpath node_modules specs (`rouzer/http`, `qubu/sqlite`) that
|
|
240
|
+
+ // survive the on-disk candidate fan-out would otherwise hit `resolveId`
|
|
241
|
+
+ // and land on Vite's dep-optimizer output: `/node_modules/.vite/deps/
|
|
242
|
+
+ // <flat>.js` bundles whose generated sibling chunks (`foo-<hash>.js`)
|
|
243
|
+
+ // this pipeline cannot serve — and whose flattened ids decode to bogus
|
|
244
|
+
+ // bare specifiers downstream (`http-Dm/BQhab`). Prefer the real package
|
|
245
|
+
+ // file (exports-map aware) so the device gets the same per-module source
|
|
246
|
+
+ // an `optimizeDeps.exclude` entry produces.
|
|
247
|
+
+ if (spec.includes('/node_modules/')) {
|
|
248
|
+
+ const realFile = resolveSpecToFile(spec.slice(spec.lastIndexOf('/node_modules/')), serverRoot, workspaceRoot, resolvePlatform() || 'ios');
|
|
249
|
+
+ const realUrl = realFile ? nodeModulesFileToRequestUrl(realFile, serverRoot) : null;
|
|
250
|
+
+ if (realUrl) {
|
|
251
|
+
+ transformed = await tryTransformRequest(transformRequest, realUrl, timeoutMs);
|
|
252
|
+
+ if (transformed?.code) {
|
|
253
|
+
+ return { transformed, resolvedCandidate: realUrl };
|
|
254
|
+
+ }
|
|
255
|
+
+ }
|
|
256
|
+
+ }
|
|
257
|
+
const resolvedId = await resolveId(spec);
|
|
258
|
+
if (resolvedId) {
|
|
259
|
+
transformed = await tryTransformRequest(transformRequest, resolvedId, timeoutMs);
|
|
260
|
+
diff --git a/hmr/server/websocket-ns-m.js b/hmr/server/websocket-ns-m.js
|
|
261
|
+
index 8eeb51ab59497e7f775da3117c4207e05f1814c7..346b676fbb67ce6bebe81cc379fdbe0d0aeac876 100644
|
|
262
|
+
--- a/hmr/server/websocket-ns-m.js
|
|
263
|
+
+++ b/hmr/server/websocket-ns-m.js
|
|
264
|
+
@@ -265,6 +265,30 @@ export function registerNsModuleServerRoute(server, options) {
|
|
265
|
+
catch { }
|
|
266
|
+
// If transformRequest failed, handle bare-module vendor shims for 'vue' and 'pinia'
|
|
267
|
+
if (!transformed?.code) {
|
|
268
|
+
+ // Dep-optimizer artifacts (`.vite/deps` entries + generated
|
|
269
|
+
+ // shared chunks) aren't package files — when one reaches the
|
|
270
|
+
+ // bridge unserved, the fix is keeping that dep out of
|
|
271
|
+
+ // optimizeDeps. Name the optimized set so the on-device
|
|
272
|
+
+ // error is actionable instead of a generic transform miss.
|
|
273
|
+
+ if (spec.includes('/.vite/deps/')) {
|
|
274
|
+
+ const optimizedIds = [];
|
|
275
|
+
+ try {
|
|
276
|
+
+ for (const env of Object.values(server.environments || {})) {
|
|
277
|
+
+ const meta = env?.depsOptimizer?.metadata;
|
|
278
|
+
+ for (const record of [meta?.optimized, meta?.discovered]) {
|
|
279
|
+
+ if (record)
|
|
280
|
+
+ optimizedIds.push(...Object.keys(record));
|
|
281
|
+
+ }
|
|
282
|
+
+ }
|
|
283
|
+
+ }
|
|
284
|
+
+ catch { }
|
|
285
|
+
+ const hint = optimizedIds.length
|
|
286
|
+
+ ? ` Currently optimized: ${Array.from(new Set(optimizedIds)).join(', ')} — add the package that pulls in this chunk to optimizeDeps.exclude in the native vite config (or restart with NS_DEPS_PER_MODULE=1).`
|
|
287
|
+
+ : ' Add the importing package to optimizeDeps.exclude in the native vite config.';
|
|
288
|
+
+ res.statusCode = 404;
|
|
289
|
+
+ res.end(`throw new Error(${JSON.stringify(`[ns/m] Vite optimizeDeps artifact '${spec}' is not servable on device — its shared chunks are generated files outside the package tree.${hint}`)});\nexport {};\n`);
|
|
290
|
+
+ return;
|
|
291
|
+
+ }
|
|
292
|
+
const bare = spec.startsWith('/') ? spec.slice(1) : spec;
|
|
293
|
+
const isBare = bare && !bare.includes('/') && !/\.(ts|tsx|js|jsx|mjs|mts|cts|vue)$/i.test(bare);
|
|
294
|
+
if (isBare && (bare === 'vue' || bare === 'nativescript-vue' || bare === 'pinia')) {
|
|
@@ -1,7 +1,29 @@
|
|
|
1
1
|
# pnpm 11 settings live here, not in package.json.
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
2
|
+
#
|
|
3
|
+
# nodeLinker hoisted: Node ESM resolves package imports relative to the
|
|
4
|
+
# symlink path (node_modules/octane/...), not the realpath — so octane's
|
|
5
|
+
# transitive deps (@tsrx/core etc.) must sit at top-level node_modules.
|
|
6
|
+
# NativeScript's bundler wants a flat tree for the same reason.
|
|
7
|
+
nodeLinker: hoisted
|
|
8
|
+
#
|
|
9
|
+
# esbuild + @parcel/watcher need install scripts to place binaries;
|
|
10
|
+
# nativescript's postinstall sets up the local CLI. @nativescript/core's
|
|
11
|
+
# scripts are native toolchain prep the ns CLI does itself.
|
|
5
12
|
allowBuilds:
|
|
6
13
|
esbuild: true
|
|
14
|
+
'@parcel/watcher': true
|
|
15
|
+
nativescript: true
|
|
7
16
|
'@nativescript/core': false
|
|
17
|
+
#
|
|
18
|
+
# vite-octane's octaneServerStrategy early-returns for `.tsrx` files (its
|
|
19
|
+
# SCRIPT_FILE_RE only covers [jt]sx?), so component edits never emit the
|
|
20
|
+
# deferred ns:hmr-delta broadcast — on-device HMR looks dead. The patch also
|
|
21
|
+
# counts open /ns-hmr sockets so `recipients=N` in update logs is real.
|
|
22
|
+
# Drop once a release carries the fix.
|
|
23
|
+
patchedDependencies:
|
|
24
|
+
'@nativescript-community/vite-octane@0.2.1': patches/@nativescript-community__vite-octane@0.2.1.patch
|
|
25
|
+
# Dep/core shims dropped reserved-word named exports (zod's `enum`, `catch`,
|
|
26
|
+
# `function`, …) because `export const enum` is a syntax error — emit them
|
|
27
|
+
# via temp+alias so bridged namespaces keep their full surface. Without it,
|
|
28
|
+
# any app importing such a dep fails module eval on boot/graph-reload.
|
|
29
|
+
'@nativescript/vite@8.0.11': patches/@nativescript__vite@8.0.11.patch
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="@nativescript/types" />
|
package/template/src/App.tsrx
CHANGED
|
@@ -13,10 +13,12 @@ const FEATURES = [
|
|
|
13
13
|
* (.web.tsrx / .native.tsrx), never inside a component. */
|
|
14
14
|
export function App() @{
|
|
15
15
|
const [count, setCount] = useState(0);
|
|
16
|
-
|
|
16
|
+
// null = follow the system; the toggle overrides it for this session.
|
|
17
|
+
const [override, setOverride] = useState<boolean | null>(null);
|
|
17
18
|
const scheme = useColorScheme();
|
|
19
|
+
const dark = override ?? scheme === 'dark';
|
|
18
20
|
|
|
19
|
-
<View className={['app', dark ? 'dark' : '']}>
|
|
21
|
+
<View className={['app', dark ? 'dark' : 'light']}>
|
|
20
22
|
<Text className="title">octane-xplat</Text>
|
|
21
23
|
<Text className="muted">one codebase — web · iOS · android</Text>
|
|
22
24
|
|
|
@@ -28,7 +30,7 @@ export function App() @{
|
|
|
28
30
|
|
|
29
31
|
<List items={FEATURES} renderItem={(f) => <Text className="chip">{f}</Text>} />
|
|
30
32
|
|
|
31
|
-
<Pressable className="link" onPress={() =>
|
|
33
|
+
<Pressable className="link" onPress={() => setOverride(!dark)}>
|
|
32
34
|
<Text>{dark ? 'light' : 'dark'} mode (system: {scheme})</Text>
|
|
33
35
|
</Pressable>
|
|
34
36
|
</View>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Native entry — the iOS/Android twin of main.web.tsrx. Same App.tsrx,
|
|
2
|
+
// different shell: a Frame hosting a Page that renders the universal root.
|
|
3
|
+
import { Application, Frame, Page } from '@nativescript/core'
|
|
4
|
+
import { renderNativeScriptApp } from '@nativescript-community/octane'
|
|
5
|
+
import { App } from './App.tsrx'
|
|
6
|
+
import '@octane-xplat/ui/theme/tokens.css'
|
|
7
|
+
import './style.css'
|
|
8
|
+
|
|
9
|
+
const entry = {
|
|
10
|
+
create: () => {
|
|
11
|
+
// Root is a Frame so pushRoute() can push Pages later — a Frame
|
|
12
|
+
// window root doubles as the 'root' stack with no registration;
|
|
13
|
+
// named stacks come from TabSpec.stack or registerStack. Route
|
|
14
|
+
// pushes also need registerScreens({name: Component}) at boot.
|
|
15
|
+
const frame = new Frame()
|
|
16
|
+
const page = new Page()
|
|
17
|
+
page.actionBarHidden = true
|
|
18
|
+
renderNativeScriptApp(page, App)
|
|
19
|
+
frame.navigate({ create: () => page })
|
|
20
|
+
return frame
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Under the vite dev session the placeholder bootstrap has already run
|
|
25
|
+
// Application.run(), and a module-graph reload re-evaluates this entry.
|
|
26
|
+
// Android's run() throws "Application is already started" on re-entry
|
|
27
|
+
// (iOS core handles the placeholder handoff internally); resetRootView is
|
|
28
|
+
// the documented way to swap the root of a running app.
|
|
29
|
+
if (Application.started) {
|
|
30
|
+
Application.resetRootView(entry)
|
|
31
|
+
} else {
|
|
32
|
+
Application.run(entry)
|
|
33
|
+
}
|
package/template/src/style.css
CHANGED
|
@@ -6,6 +6,25 @@
|
|
|
6
6
|
body {
|
|
7
7
|
margin: 0;
|
|
8
8
|
font-family: system-ui, sans-serif;
|
|
9
|
+
background-color: var(--color-surface);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/* System-dark before JS mounts — no white flash. Same values tokens.css
|
|
13
|
+
* sets under .dark; .app.light below is the manual override back. */
|
|
14
|
+
@media (prefers-color-scheme: dark) {
|
|
15
|
+
:root {
|
|
16
|
+
--color-surface: #0f172a;
|
|
17
|
+
--color-text: #f8fafc;
|
|
18
|
+
}
|
|
19
|
+
body {
|
|
20
|
+
background-color: var(--color-surface);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* Manual light override when the system is dark (JS adds .light). */
|
|
25
|
+
.app.light {
|
|
26
|
+
--color-surface: #ffffff;
|
|
27
|
+
--color-text: #111827;
|
|
9
28
|
}
|
|
10
29
|
|
|
11
30
|
.app {
|
|
@@ -49,6 +68,7 @@ body {
|
|
|
49
68
|
color: var(--color-on-primary);
|
|
50
69
|
padding: 10px 20px;
|
|
51
70
|
border-radius: 10px;
|
|
71
|
+
corner-shape: squircle;
|
|
52
72
|
font-size: 20px;
|
|
53
73
|
}
|
|
54
74
|
|
|
@@ -57,6 +77,7 @@ body {
|
|
|
57
77
|
color: var(--color-on-primary);
|
|
58
78
|
padding: 6px 14px;
|
|
59
79
|
border-radius: 14px;
|
|
80
|
+
corner-shape: squircle;
|
|
60
81
|
font-size: 14px;
|
|
61
82
|
}
|
|
62
83
|
|
package/template/tsconfig.json
CHANGED
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
"customConditions": ["web"],
|
|
11
11
|
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
|
12
12
|
"types": ["vite/client"],
|
|
13
|
-
"noEmit": true
|
|
13
|
+
"noEmit": true,
|
|
14
|
+
"skipLibCheck": true
|
|
14
15
|
},
|
|
15
|
-
"include": ["src/**/*"]
|
|
16
|
+
"include": ["src/**/*"],
|
|
17
|
+
"exclude": ["node_modules", "src/index.native.ts"]
|
|
16
18
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"strict": true,
|
|
4
|
+
"target": "ESNext",
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"jsx": "preserve",
|
|
8
|
+
"jsxImportSource": "@nativescript-community/octane",
|
|
9
|
+
"moduleSuffixes": [".ios", ".android", ".native", ""],
|
|
10
|
+
"customConditions": ["native"],
|
|
11
|
+
"lib": ["ESNext"],
|
|
12
|
+
"types": ["@nativescript/types"],
|
|
13
|
+
"noEmit": true,
|
|
14
|
+
"skipLibCheck": true
|
|
15
|
+
},
|
|
16
|
+
"include": ["src/**/*", "references.d.ts"],
|
|
17
|
+
"exclude": ["node_modules", "platforms", "**/*.web.*"]
|
|
18
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
import { xplatNative } from '@octane-xplat/cli/vite'
|
|
3
|
+
|
|
4
|
+
// The shared preset owns the renderer rules, octane→universal/native alias,
|
|
5
|
+
// suffix extension chain, deps-bundle plugin exclusions, the HMR watchdog,
|
|
6
|
+
// and the px→dip CSS rewrite. App-specific additions go through the second
|
|
7
|
+
// argument's `extra` (or mergeConfig).
|
|
8
|
+
export default defineConfig(({ mode }) => xplatNative(mode))
|
package/template/vite.config.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { defineConfig } from 'vite'
|
|
2
|
-
import { octane } from '@octanejs/vite-plugin'
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
import { octane } from '@octanejs/vite-plugin'
|
|
3
3
|
|
|
4
4
|
// The web build. For native (iOS/Android), the same source runs through
|
|
5
5
|
// @nativescript-community/vite-octane with `conditions: ['native']` and the
|
|
@@ -12,10 +12,17 @@ export default defineConfig({
|
|
|
12
12
|
conditions: ['web'],
|
|
13
13
|
// Suffix chain (first match wins): .web → shared → fallback.
|
|
14
14
|
extensions: [
|
|
15
|
-
'.web.tsrx',
|
|
16
|
-
'.
|
|
17
|
-
'.web.
|
|
18
|
-
'.
|
|
15
|
+
'.web.tsrx',
|
|
16
|
+
'.tsrx',
|
|
17
|
+
'.web.tsx',
|
|
18
|
+
'.tsx',
|
|
19
|
+
'.web.ts',
|
|
20
|
+
'.mjs',
|
|
21
|
+
'.mts',
|
|
22
|
+
'.ts',
|
|
23
|
+
'.jsx',
|
|
24
|
+
'.js',
|
|
25
|
+
'.json',
|
|
19
26
|
],
|
|
20
27
|
},
|
|
21
|
-
})
|
|
28
|
+
})
|
|
File without changes
|