create-octane-xplat 0.7.3 → 0.10.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 +28 -8
- package/index.mjs +112 -14
- package/package.json +15 -8
- package/scaffold.mjs +531 -0
- package/template/.agents/skills/xplat/SKILL.md +109 -40
- package/template/.agents/skills/xplat/references/components.md +149 -96
- package/template/.agents/skills/xplat/references/navigation.md +81 -31
- package/template/.agents/skills/xplat/references/platform.md +74 -35
- package/template/.agents/skills/xplat/references/styling.md +80 -27
- package/template/AGENTS.md +1 -1
- package/template/package.json +11 -9
- package/template/pnpm-workspace.yaml +66 -33
- package/template/src/App.tsrx +17 -8
- package/template/src/process.d.ts +7 -0
- package/template/src/style.css +24 -6
- package/template/tsconfig.json +1 -1
- package/template/vite.config.native.mts +3 -2
- package/template/vite.config.ts +5 -3
- package/template/App_Resources/iOS/src/OctaneShaders.metal +0 -128
- package/template/App_Resources/iOS/src/ShaderEffectRegistration.swift +0 -31
- package/template/patches/@nativescript-community__octane@0.2.1.patch +0 -16
- package/template/patches/@nativescript-community__vite-octane@0.2.1.patch +0 -41
- package/template/patches/@nativescript__core@9.1.2.patch +0 -794
- package/template/patches/@nativescript__core@9.1.3-next.2.patch +0 -428
- package/template/patches/@nativescript__vite@8.0.11.patch +0 -569
- package/template/patches/@nativescript__vite@8.0.13.patch +0 -233
- package/template/patches/@tsrx__typescript-plugin@0.4.11.patch +0 -11
- package/template/patches/css@3.0.0.patch +0 -52
- package/template/patches/octane@0.6.3.patch +0 -1149
- /package/template/src/{index.ts → main.ts} +0 -0
|
@@ -1,569 +0,0 @@
|
|
|
1
|
-
diff --git a/hmr/server/deps-bundle.js b/hmr/server/deps-bundle.js
|
|
2
|
-
index 50e5abddddf80b35044d45eb108a64dd175bf6e7..4f16f129b4c4271173169871f42645d16d3cfa0c 100644
|
|
3
|
-
--- a/hmr/server/deps-bundle.js
|
|
4
|
-
+++ b/hmr/server/deps-bundle.js
|
|
5
|
-
@@ -53,6 +53,7 @@
|
|
6
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
7
|
-
import * as path from 'node:path';
|
|
8
|
-
import { createHash } from 'node:crypto';
|
|
9
|
-
+import { createRequire } from 'node:module';
|
|
10
|
-
import * as esbuild from 'esbuild';
|
|
11
|
-
import { resolvePlatform } from '../../helpers/cli-flags.js';
|
|
12
|
-
import { getGlobalDefines, getUserDefineEntries } from '../../helpers/global-defines.js';
|
|
13
|
-
@@ -669,6 +670,29 @@ export async function generateDepsBundle(options) {
|
|
14
|
-
// the device cannot resolve.
|
|
15
|
-
const buildPlugins = () => [
|
|
16
|
-
createWebpackLoaderStubEsbuildPlugin(),
|
|
17
|
-
+ // Bare `octane` is the DOM entry — a dep importing it (published
|
|
18
|
-
+ // @octane-xplat/* dist files kept `from "octane"` through 0.6.0)
|
|
19
|
-
+ // bundles a second runtime next to the universal one, and hooks read
|
|
20
|
-
+ // an empty CURRENT_SCOPE ("Cannot read properties of null (reading
|
|
21
|
-
+ // 'hooks')"). Vite resolve.alias never reaches these esbuild passes;
|
|
22
|
-
+ // route exact `octane` imports to the host-neutral entry here.
|
|
23
|
-
+ {
|
|
24
|
-
+ name: 'ns-deps-bundle-octane-universal-alias',
|
|
25
|
-
+ setup(build) {
|
|
26
|
-
+ build.onResolve({ filter: /^octane$/ }, (args) => {
|
|
27
|
-
+ for (const dir of [args.importer && path.dirname(args.importer), args.resolveDir, projectRoot]) {
|
|
28
|
-
+ if (!dir)
|
|
29
|
-
+ continue;
|
|
30
|
-
+ try {
|
|
31
|
-
+ return { path: createRequire(path.join(dir, 'noop.js')).resolve('octane/universal/native') };
|
|
32
|
-
+ }
|
|
33
|
-
+ catch {
|
|
34
|
-
+ }
|
|
35
|
-
+ }
|
|
36
|
-
+ return null;
|
|
37
|
-
+ });
|
|
38
|
-
+ },
|
|
39
|
-
+ },
|
|
40
|
-
createNodeBuiltinPolyfillEsbuildPlugin(projectRoot, NODE_BUILTINS),
|
|
41
|
-
createNativeClassEsbuildPlugin(platform),
|
|
42
|
-
createVendorEsbuildPlugin(projectRoot),
|
|
43
|
-
diff --git a/helpers/ts-config-paths.js b/helpers/ts-config-paths.js
|
|
44
|
-
index 27013b5c8f78a72b6e6df903ac36f72fc0d75c1a..53a2e7d489ab91593cbec47ee6fb640fc56c786f 100644
|
|
45
|
-
--- a/helpers/ts-config-paths.js
|
|
46
|
-
+++ b/helpers/ts-config-paths.js
|
|
47
|
-
@@ -113,15 +113,20 @@ function resolveTsConfigPath(fullPath, platform, verbose, debugId) {
|
|
48
|
-
if (verbose && debugId) {
|
|
49
|
-
console.log(`📁 TypeScript path candidate: ${debugId} -> ${fullPath}`);
|
|
50
|
-
}
|
|
51
|
-
+ // `.mobile` is the shared ios/android suffix tier — it sits between the os
|
|
52
|
-
+ // qualifier and the unsuffixed default. Probing it per extension keeps
|
|
53
|
-
+ // paths-mapped imports consistent with the bundler's extension chain.
|
|
54
|
-
+ const suffixes = platform === 'ios' || platform === 'android' ? [platform, 'mobile'] : platform ? [platform] : [];
|
|
55
|
-
if (fs.existsSync(fullPath) && fs.statSync(fullPath).isDirectory()) {
|
|
56
|
-
- const platformIndexPatterns = [`index.${platform}.ts`, `index.${platform}.js`, `index.${platform}.mjs`];
|
|
57
|
-
- for (const indexFile of platformIndexPatterns) {
|
|
58
|
-
- const indexPath = path.join(fullPath, indexFile);
|
|
59
|
-
- if (fs.existsSync(indexPath)) {
|
|
60
|
-
- if (verbose) {
|
|
61
|
-
- console.log(`📁 Found platform-specific directory index: ${indexPath}`);
|
|
62
|
-
+ for (const suffix of suffixes) {
|
|
63
|
-
+ for (const ext of ['.ts', '.js', '.mjs']) {
|
|
64
|
-
+ const indexPath = path.join(fullPath, `index.${suffix}${ext}`);
|
|
65
|
-
+ if (fs.existsSync(indexPath)) {
|
|
66
|
-
+ if (verbose) {
|
|
67
|
-
+ console.log(`📁 Found platform-specific directory index: ${indexPath}`);
|
|
68
|
-
+ }
|
|
69
|
-
+ return indexPath;
|
|
70
|
-
}
|
|
71
|
-
- return indexPath;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
for (const indexFile of ['index.ts', 'index.js', 'index.mjs']) {
|
|
75
|
-
@@ -135,12 +140,14 @@ function resolveTsConfigPath(fullPath, platform, verbose, debugId) {
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
for (const ext of ['.ts', '.js', '.mjs']) {
|
|
79
|
-
- const platformPath = `${fullPath}.${platform}${ext}`;
|
|
80
|
-
- if (fs.existsSync(platformPath)) {
|
|
81
|
-
- if (verbose) {
|
|
82
|
-
- console.log(`📁 Found platform-specific file: ${platformPath}`);
|
|
83
|
-
+ for (const suffix of suffixes) {
|
|
84
|
-
+ const platformPath = `${fullPath}.${suffix}${ext}`;
|
|
85
|
-
+ if (fs.existsSync(platformPath)) {
|
|
86
|
-
+ if (verbose) {
|
|
87
|
-
+ console.log(`📁 Found platform-specific file: ${platformPath}`);
|
|
88
|
-
+ }
|
|
89
|
-
+ return platformPath;
|
|
90
|
-
}
|
|
91
|
-
- return platformPath;
|
|
92
|
-
}
|
|
93
|
-
const basePath = `${fullPath}${ext}`;
|
|
94
|
-
if (fs.existsSync(basePath)) {
|
|
95
|
-
diff --git a/hmr/server/device-transform-helpers.js b/hmr/server/device-transform-helpers.js
|
|
96
|
-
index 9364ce9942b84fffd0e3a6eb6700d8253fa66e7e..0ae5a89f0359afc3c700b43b2c80b176536100a6 100644
|
|
97
|
-
--- a/hmr/server/device-transform-helpers.js
|
|
98
|
-
+++ b/hmr/server/device-transform-helpers.js
|
|
99
|
-
@@ -89,7 +89,11 @@ function bareSpecifierFromFlatDepPath(depPath) {
|
|
100
|
-
return bareSpecifier;
|
|
101
|
-
}
|
|
102
|
-
function rewriteVitePrebundleImportsForDevice(code, preserveVendorImports) {
|
|
103
|
-
- const imports = collectTopLevelImportRecords(code);
|
|
104
|
-
+ // Re-export statements (`export { x } from`/`export * from`) carry specifiers
|
|
105
|
-
+ // too — vite's importAnalysis rewrites them to `.vite/deps?v=` URLs the same
|
|
106
|
-
+ // as `import` declarations, so they need the same decode. Barrel modules
|
|
107
|
-
+ // (e.g. a linked framework package's index) are nothing but re-exports.
|
|
108
|
-
+ const imports = collectTopLevelImportRecords(code, true);
|
|
109
|
-
if (!imports.length) {
|
|
110
|
-
return code;
|
|
111
|
-
}
|
|
112
|
-
diff --git a/hmr/server/rewrite-imports.js b/hmr/server/rewrite-imports.js
|
|
113
|
-
index 4a6575cb00a6aeecbbb7ac5ccac3268cb10d7f54..dc70055007e26117b76fbb9d2f4f17fabfd8db00 100644
|
|
114
|
-
--- a/hmr/server/rewrite-imports.js
|
|
115
|
-
+++ b/hmr/server/rewrite-imports.js
|
|
116
|
-
@@ -253,11 +253,29 @@ export function rewriteImports(code, importerPath, sfcFileMap, depFileMap, proje
|
|
117
|
-
}
|
|
118
|
-
return `${prefix}${rewritten}${suffix}`;
|
|
119
|
-
}
|
|
120
|
-
- // Path resolves outside both roots — leave Vite's URL alone as a
|
|
121
|
-
- // last resort. The original behaviour was to fall through here
|
|
122
|
-
- // and let downstream branches (e.g. `normalizeNodeModulesSpecifier`)
|
|
123
|
-
- // handle paths whose abs form happens to contain `/node_modules/`,
|
|
124
|
-
- // so preserve that for the unrewritable case below.
|
|
125
|
-
+ // Path resolves outside both roots — the `link:`/`workspace:` case,
|
|
126
|
-
+ // where a package's realpath lives in another checkout entirely.
|
|
127
|
-
+ // A raw /@fs URL makes the device fetch vite's standard middleware
|
|
128
|
-
+ // response, bypassing the device transform: optimizer
|
|
129
|
-
+ // `.vite/deps?v=` specifiers inside reach the runtime unrewritten
|
|
130
|
-
+ // and 504 once the optimizer re-commits its browserHash. The /ns/m
|
|
131
|
-
+ // route already resolves an absolute `/@fs` spec through the full
|
|
132
|
-
+ // device pipeline, so route it there. Paths whose realpath still
|
|
133
|
-
+ // contains /node_modules/ keep falling through to the bare-package
|
|
134
|
-
+ // branch below (upstream behavior).
|
|
135
|
-
+ if (!spec.includes('/node_modules/')) {
|
|
136
|
-
+ const out = `/ns/m${spec}`;
|
|
137
|
-
+ if (httpOriginSafe) {
|
|
138
|
-
+ return `${prefix}${httpOriginSafe}${out}${suffix}`;
|
|
139
|
-
+ }
|
|
140
|
-
+ return `${prefix}${out}${suffix}`;
|
|
141
|
-
+ }
|
|
142
|
-
+ // Path resolves outside both roots but still names a node_modules
|
|
143
|
-
+ // file — leave Vite's URL alone as a last resort. The original
|
|
144
|
-
+ // behaviour was to fall through here and let downstream branches
|
|
145
|
-
+ // (e.g. `normalizeNodeModulesSpecifier`) handle paths whose abs
|
|
146
|
-
+ // form happens to contain `/node_modules/`, so preserve that for
|
|
147
|
-
+ // the unrewritable case below.
|
|
148
|
-
}
|
|
149
|
-
// Vite 8 oxc runtime helpers (decorators etc.) — emitted as
|
|
150
|
-
// `/@id/__x00__@oxc-project+runtime@<ver>/helpers/<name>.js`. These are
|
|
151
|
-
diff --git a/hmr/server/websocket-core-bridge.js b/hmr/server/websocket-core-bridge.js
|
|
152
|
-
index 444ea4474645a10ce4f9ca3b81d96ec9c017c0cb..caa3724caeeb04b4d69d04a15a5899f835fec5ff 100644
|
|
153
|
-
--- a/hmr/server/websocket-core-bridge.js
|
|
154
|
-
+++ b/hmr/server/websocket-core-bridge.js
|
|
155
|
-
@@ -8,7 +8,76 @@ import { getGlobalScope } from '../shared/runtime/global-scope.js';
|
|
156
|
-
* an ASCII filter silently drops every such export from generated shims.
|
|
157
|
-
*/
|
|
158
|
-
export const JS_IDENTIFIER_RE = /^[$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*$/u;
|
|
159
|
-
+/**
|
|
160
|
-
+ * Blank out `//` and `/* ... *\/` comment bodies (positions and length
|
|
161
|
-
+ * preserved — every matched index stays valid). Export-name scanners are
|
|
162
|
-
+ * regex-based, so a commented-out declaration like ui-image's
|
|
163
|
-
+ * `// export const roundRadiusProperty = ...` would otherwise register a
|
|
164
|
-
+ * phantom export and the emitted explicit re-export list then fails to link
|
|
165
|
-
+ * on device ("does not provide an export named ...").
|
|
166
|
-
+ *
|
|
167
|
-
+ * Strings and template literals are skipped so `//` inside quotes isn't
|
|
168
|
-
+ * treated as a comment start. Regex literals are intentionally not tracked —
|
|
169
|
-
+ * an unescaped `//` inside one (e.g. `[//]` char classes) still masks the
|
|
170
|
-
+ * rest of the line; that is rare enough to accept in a dev-server path.
|
|
171
|
-
+ */
|
|
172
|
-
+export function maskJsComments(code) {
|
|
173
|
-
+ if (!code || (code.indexOf('//') === -1 && code.indexOf('/*') === -1)) {
|
|
174
|
-
+ return code;
|
|
175
|
-
+ }
|
|
176
|
-
+ const chars = code.split('');
|
|
177
|
-
+ const n = code.length;
|
|
178
|
-
+ let i = 0;
|
|
179
|
-
+ while (i < n) {
|
|
180
|
-
+ const c = code[i];
|
|
181
|
-
+ const next = code[i + 1];
|
|
182
|
-
+ if (c === '/' && next === '/') {
|
|
183
|
-
+ chars[i] = ' ';
|
|
184
|
-
+ chars[i + 1] = ' ';
|
|
185
|
-
+ i += 2;
|
|
186
|
-
+ while (i < n && code[i] !== '\n') {
|
|
187
|
-
+ chars[i] = ' ';
|
|
188
|
-
+ i++;
|
|
189
|
-
+ }
|
|
190
|
-
+ }
|
|
191
|
-
+ else if (c === '/' && next === '*') {
|
|
192
|
-
+ chars[i] = ' ';
|
|
193
|
-
+ chars[i + 1] = ' ';
|
|
194
|
-
+ i += 2;
|
|
195
|
-
+ while (i < n && !(code[i] === '*' && code[i + 1] === '/')) {
|
|
196
|
-
+ if (code[i] !== '\n')
|
|
197
|
-
+ chars[i] = ' ';
|
|
198
|
-
+ i++;
|
|
199
|
-
+ }
|
|
200
|
-
+ if (i < n) {
|
|
201
|
-
+ chars[i] = ' ';
|
|
202
|
-
+ chars[i + 1] = ' ';
|
|
203
|
-
+ i += 2;
|
|
204
|
-
+ }
|
|
205
|
-
+ }
|
|
206
|
-
+ else if (c === "'" || c === '"' || c === '`') {
|
|
207
|
-
+ const quote = c;
|
|
208
|
-
+ i++;
|
|
209
|
-
+ while (i < n) {
|
|
210
|
-
+ if (code[i] === '\\') {
|
|
211
|
-
+ i += 2;
|
|
212
|
-
+ continue;
|
|
213
|
-
+ }
|
|
214
|
-
+ if (code[i] === quote) {
|
|
215
|
-
+ i++;
|
|
216
|
-
+ break;
|
|
217
|
-
+ }
|
|
218
|
-
+ i++;
|
|
219
|
-
+ }
|
|
220
|
-
+ }
|
|
221
|
-
+ else {
|
|
222
|
-
+ i++;
|
|
223
|
-
+ }
|
|
224
|
-
+ }
|
|
225
|
-
+ return chars.join('');
|
|
226
|
-
+}
|
|
227
|
-
export function extractDirectExportedNames(code) {
|
|
228
|
-
+ code = maskJsComments(code);
|
|
229
|
-
const names = new Set();
|
|
230
|
-
const declRe = /\bexport\s+(?:async\s+)?(?:function|class)\s+([$_\p{ID_Start}][$\p{ID_Continue}]*)/gu;
|
|
231
|
-
let match;
|
|
232
|
-
diff --git a/hmr/server/websocket-module-bindings.js b/hmr/server/websocket-module-bindings.js
|
|
233
|
-
index 54b8fba2def7ed0128e8e1dd4b2549d25559de82..fd276ae860d0805533be0bff4399247b38ebac01 100644
|
|
234
|
-
--- a/hmr/server/websocket-module-bindings.js
|
|
235
|
-
+++ b/hmr/server/websocket-module-bindings.js
|
|
236
|
-
@@ -7,6 +7,7 @@ import { getProjectRootPath } from '../../helpers/project.js';
|
|
237
|
-
import { extractRootPackageName } from '../shared/package-classifier.js';
|
|
238
|
-
import { isEsmFrameworkPackageSpecifier, isLikelyNativeScriptPluginSpecifier, isLikelyNativeScriptRuntimePluginSpecifier, normalizeNodeModulesSpecifier, resolveInternalRuntimePluginBareSpecifier, resolveNodeModulesPackageBoundary, resolveVendorFromCandidate, shouldPreserveBareRuntimePluginSubpathImport } from './websocket-module-specifiers.js';
|
|
239
|
-
import { MODULE_IMPORT_ANALYSIS_PLUGINS } from './websocket-served-module-helpers.js';
|
|
240
|
-
+import { getVendorManifest, resolveVendorSpecifier } from '../shared/vendor/registry.js';
|
|
241
|
-
const sourceImportIntentCache = new Map();
|
|
242
|
-
const EXPLICIT_RUNTIME_PLUGIN_SCRIPT_EXT_RE = /(?:\.(?:ios|android|visionos))?\.(?:ts|tsx|js|jsx|mjs|mts|cts)$/i;
|
|
243
|
-
function hasExplicitRuntimePluginScriptExtension(segment) {
|
|
244
|
-
@@ -109,6 +110,9 @@ export function ensureNativeScriptModuleBindings(code, options) {
|
|
245
|
-
}
|
|
246
|
-
const runtimePluginBareSpecifier = resolveInternalRuntimePluginBareSpecifier(specifier, getProjectRootPath());
|
|
247
|
-
let canonical = runtimePluginBareSpecifier || resolveVendorFromCandidate(specifier);
|
|
248
|
-
+ if (canonical && getVendorManifest() && !resolveVendorSpecifier(canonical)) {
|
|
249
|
-
+ canonical = null;
|
|
250
|
-
+ }
|
|
251
|
-
const runtimePluginSpecifier = isLikelyNativeScriptRuntimePluginSpecifier(canonical || specifier, getProjectRootPath());
|
|
252
|
-
// Preserving a bare vendor id is only correct where the IMPORT MAP
|
|
253
|
-
// exists to route it (main realm). Worker realms have no import map —
|
|
254
|
-
@@ -120,7 +124,7 @@ export function ensureNativeScriptModuleBindings(code, options) {
|
|
255
|
-
preservedImports.push(original);
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
- if (!canonical && isLikelyNativeScriptPluginSpecifier(specifier, getProjectRootPath())) {
|
|
259
|
-
+ if (!canonical && isLikelyNativeScriptPluginSpecifier(specifier, getProjectRootPath()) && (!getVendorManifest() || resolveVendorSpecifier(specifier))) {
|
|
260
|
-
canonical = specifier;
|
|
261
|
-
}
|
|
262
|
-
if (canonical && /^@nativescript\/core(\b|\/)/i.test(canonical)) {
|
|
263
|
-
@@ -163,6 +167,9 @@ export function ensureNativeScriptModuleBindings(code, options) {
|
|
264
|
-
}
|
|
265
|
-
const runtimePluginBareSpecifier = resolveInternalRuntimePluginBareSpecifier(specifier, getProjectRootPath());
|
|
266
|
-
let canonical = runtimePluginBareSpecifier || resolveVendorFromCandidate(specifier);
|
|
267
|
-
+ if (canonical && getVendorManifest() && !resolveVendorSpecifier(canonical)) {
|
|
268
|
-
+ canonical = null;
|
|
269
|
-
+ }
|
|
270
|
-
const runtimePluginSpecifier = isLikelyNativeScriptRuntimePluginSpecifier(canonical || specifier, getProjectRootPath());
|
|
271
|
-
// See the side-effect branch above: bare preservation relies on the main
|
|
272
|
-
// realm's import map; in a worker serve (vendorImportsAsHttp) it must not
|
|
273
|
-
@@ -171,7 +178,7 @@ export function ensureNativeScriptModuleBindings(code, options) {
|
|
274
|
-
preservedImports.push(original);
|
|
275
|
-
continue;
|
|
276
|
-
}
|
|
277
|
-
- if (!canonical && isLikelyNativeScriptPluginSpecifier(specifier, getProjectRootPath())) {
|
|
278
|
-
+ if (!canonical && isLikelyNativeScriptPluginSpecifier(specifier, getProjectRootPath()) && (!getVendorManifest() || resolveVendorSpecifier(specifier))) {
|
|
279
|
-
canonical = specifier;
|
|
280
|
-
}
|
|
281
|
-
if (canonical && /^@nativescript\/core(\b|\/)/i.test(canonical)) {
|
|
282
|
-
diff --git a/hmr/server/websocket-module-specifiers.js b/hmr/server/websocket-module-specifiers.js
|
|
283
|
-
index 2f1dd5d5d688076c5fc0ec8c5c888f9e834e7831..68cdd051144c1d69d8d20271558c8f8e79532d73 100644
|
|
284
|
-
--- a/hmr/server/websocket-module-specifiers.js
|
|
285
|
-
+++ b/hmr/server/websocket-module-specifiers.js
|
|
286
|
-
@@ -1,4 +1,4 @@
|
|
287
|
-
-import { existsSync, readFileSync, realpathSync } from 'fs';
|
|
288
|
-
+import { existsSync, readdirSync, readFileSync, realpathSync } from 'fs';
|
|
289
|
-
import * as path from 'path';
|
|
290
|
-
import * as PAT from './constants.js';
|
|
291
|
-
import { getVendorManifest, resolveVendorSpecifier } from '../shared/vendor/registry.js';
|
|
292
|
-
@@ -331,6 +331,68 @@ export function resolveVendorFromCandidate(specifier) {
|
|
293
|
-
* request to the dev server's bridge fails with a "transform miss"
|
|
294
|
-
* 404 even though the file is right there at the workspace root.
|
|
295
|
-
*/
|
|
296
|
-
+const workspacePackageNodeModulesDirsCache = new Map();
|
|
297
|
-
+/**
|
|
298
|
-
+ * Enumerate `node_modules` directories that belong to workspace packages
|
|
299
|
-
+ * (e.g. `<root>/packages/<pkg>/node_modules`, `<root>/apps/<app>/node_modules`)
|
|
300
|
-
+ * plus the workspace root's own. pnpm's isolated linker gives each workspace
|
|
301
|
-
+ * package a private `node_modules` — a dependency declared by a workspace
|
|
302
|
-
+ * package (say a NativeScript plugin inside a library) is invisible from the
|
|
303
|
-
+ * app root, so `/ns/m/node_modules/<pkg>/...` requests for transitive deps 404
|
|
304
|
-
+ * without this fallback.
|
|
305
|
-
+ *
|
|
306
|
-
+ * Bounded: direct children of the root plus one level down
|
|
307
|
-
+ * (`<root>/<group>/<pkg>/node_modules`) covers standard workspace layouts
|
|
308
|
-
+ * without walking arbitrary depth. Cached for the dev-server lifetime.
|
|
309
|
-
+ */
|
|
310
|
-
+function enumerateWorkspacePackageNodeModulesDirs(workspaceRoot) {
|
|
311
|
-
+ const resolved = path.resolve(workspaceRoot);
|
|
312
|
-
+ const cached = workspacePackageNodeModulesDirsCache.get(resolved);
|
|
313
|
-
+ if (cached) {
|
|
314
|
-
+ return cached;
|
|
315
|
-
+ }
|
|
316
|
-
+ const dirs = [];
|
|
317
|
-
+ const pushIfPresent = (dir) => {
|
|
318
|
-
+ const nm = path.join(dir, 'node_modules');
|
|
319
|
-
+ try {
|
|
320
|
-
+ if (existsSync(nm)) {
|
|
321
|
-
+ dirs.push(nm);
|
|
322
|
-
+ }
|
|
323
|
-
+ }
|
|
324
|
-
+ catch {
|
|
325
|
-
+ // unreadable dirs are skipped — this is a best-effort fallback
|
|
326
|
-
+ }
|
|
327
|
-
+ };
|
|
328
|
-
+ pushIfPresent(resolved);
|
|
329
|
-
+ let groups = [];
|
|
330
|
-
+ try {
|
|
331
|
-
+ groups = readdirSync(resolved, { withFileTypes: true });
|
|
332
|
-
+ }
|
|
333
|
-
+ catch {
|
|
334
|
-
+ groups = [];
|
|
335
|
-
+ }
|
|
336
|
-
+ for (const group of groups) {
|
|
337
|
-
+ if (!group.isDirectory() || group.name === 'node_modules' || group.name.startsWith('.')) {
|
|
338
|
-
+ continue;
|
|
339
|
-
+ }
|
|
340
|
-
+ const groupDir = path.join(resolved, group.name);
|
|
341
|
-
+ pushIfPresent(groupDir);
|
|
342
|
-
+ let packages = [];
|
|
343
|
-
+ try {
|
|
344
|
-
+ packages = readdirSync(groupDir, { withFileTypes: true });
|
|
345
|
-
+ }
|
|
346
|
-
+ catch {
|
|
347
|
-
+ packages = [];
|
|
348
|
-
+ }
|
|
349
|
-
+ for (const pkg of packages) {
|
|
350
|
-
+ if (pkg.isDirectory() && !pkg.name.startsWith('.')) {
|
|
351
|
-
+ pushIfPresent(path.join(groupDir, pkg.name));
|
|
352
|
-
+ }
|
|
353
|
-
+ }
|
|
354
|
-
+ }
|
|
355
|
-
+ workspacePackageNodeModulesDirsCache.set(resolved, dirs);
|
|
356
|
-
+ return dirs;
|
|
357
|
-
+}
|
|
358
|
-
export function resolveCandidateFilePath(candidate, projectRoot, workspaceRoot) {
|
|
359
|
-
const cleaned = candidate.replace(PAT.QUERY_PATTERN, '');
|
|
360
|
-
if (!cleaned)
|
|
361
|
-
@@ -363,7 +425,24 @@ export function resolveCandidateFilePath(candidate, projectRoot, workspaceRoot)
|
|
362
|
-
const resolvedProject = path.resolve(projectRoot);
|
|
363
|
-
const resolvedWorkspace = path.resolve(workspaceRoot);
|
|
364
|
-
if (resolvedWorkspace !== resolvedProject) {
|
|
365
|
-
- return tryUnderRoot(resolvedWorkspace);
|
|
366
|
-
+ const fromWorkspace = tryUnderRoot(resolvedWorkspace);
|
|
367
|
-
+ if (fromWorkspace) {
|
|
368
|
-
+ return fromWorkspace;
|
|
369
|
-
+ }
|
|
370
|
-
+ }
|
|
371
|
-
+ // Transitive-dep fallback: the `/ns/m/node_modules/<pkg>/<sub>` URL
|
|
372
|
-
+ // form drops the importer, so a package that only exists under a
|
|
373
|
-
+ // workspace package's private node_modules (pnpm isolated linker)
|
|
374
|
-
+ // can't be anchored to a root. Probe every workspace package's
|
|
375
|
-
+ // node_modules for `<pkg>/<sub>` and serve the first hit.
|
|
376
|
-
+ if (cleaned.includes('/node_modules/')) {
|
|
377
|
-
+ const afterNodeModules = cleaned.slice(cleaned.lastIndexOf('/node_modules/') + '/node_modules/'.length);
|
|
378
|
-
+ for (const nmDir of enumerateWorkspacePackageNodeModulesDirs(resolvedWorkspace)) {
|
|
379
|
-
+ const absPath = path.join(nmDir, afterNodeModules);
|
|
380
|
-
+ if (existsSync(absPath)) {
|
|
381
|
-
+ return absPath;
|
|
382
|
-
+ }
|
|
383
|
-
+ }
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
return null;
|
|
387
|
-
@@ -794,16 +873,24 @@ export function resolveVendorRouting(nodeModulesSpec, projectRoot) {
|
|
388
|
-
if (rootPackageName && pkgName !== rootPackageName) {
|
|
389
|
-
return { route: 'http' };
|
|
390
|
-
}
|
|
391
|
-
- if (isLikelyNativeScriptRuntimePluginSpecifier(pkgName, projectRoot) && (!subpath || isRootLevelMainEntry)) {
|
|
392
|
-
+ // A plugin-patterned package absent from an active vendor manifest (a
|
|
393
|
-
+ // transitive dep under pnpm's isolated linker, an NS_VENDOR_EXCLUDE/
|
|
394
|
-
+ // flavor-excluded package) must not take the vendor route — the registry
|
|
395
|
-
+ // has no entry and the sync require resolves to the missing-module stub.
|
|
396
|
-
+ // Fall through to null so callers emit the per-module /ns/m HTTP form,
|
|
397
|
-
+ // which resolves transitive deps through Vite's importer-aware resolution.
|
|
398
|
-
+ // A null manifest (early boot) keeps the legacy behavior.
|
|
399
|
-
+ const manifest = getVendorManifest();
|
|
400
|
-
+ const pluginVendored = !manifest || !!manifest.modules?.[pkgName];
|
|
401
|
-
+ if (isLikelyNativeScriptRuntimePluginSpecifier(pkgName, projectRoot) && pluginVendored && (!subpath || isRootLevelMainEntry)) {
|
|
402
|
-
return { route: 'vendor', bareSpec: pkgName };
|
|
403
|
-
}
|
|
404
|
-
- if (isLikelyNativeScriptRuntimePluginSpecifier(pkgName, projectRoot) && subpath.includes('/')) {
|
|
405
|
-
+ if (isLikelyNativeScriptRuntimePluginSpecifier(pkgName, projectRoot) && pluginVendored && subpath.includes('/')) {
|
|
406
|
-
const exactBareSpecifier = resolveInternalRuntimePluginBareSpecifier(nodeModulesSpec, projectRoot);
|
|
407
|
-
if (exactBareSpecifier) {
|
|
408
|
-
return { route: 'vendor', bareSpec: exactBareSpecifier };
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
- const manifest = getVendorManifest();
|
|
412
|
-
if (!manifest?.modules?.[pkgName]) {
|
|
413
|
-
return null;
|
|
414
|
-
}
|
|
415
|
-
diff --git a/hmr/server/websocket-ns-m-request.js b/hmr/server/websocket-ns-m-request.js
|
|
416
|
-
index b54aefcc839e95906d76d02e522c052a706ca76f..a9c38c32b8752765044cd8f2c1eac558a30052dd 100644
|
|
417
|
-
--- a/hmr/server/websocket-ns-m-request.js
|
|
418
|
-
+++ b/hmr/server/websocket-ns-m-request.js
|
|
419
|
-
@@ -1,6 +1,41 @@
|
|
420
|
-
import * as path from 'path';
|
|
421
|
-
import { filterExistingNodeModulesTransformCandidates, getBlockedDeviceNodeModulesReason, resolveCandidateFilePath, tryReadRawExplicitJavaScriptModule } from './websocket-module-specifiers.js';
|
|
422
|
-
import { collapseLegacyNsMTags } from './websocket-ns-m-paths.js';
|
|
423
|
-
+import { resolvePlatform } from '../../helpers/cli-flags.js';
|
|
424
|
-
+let cachedPlatformSuffixes = undefined;
|
|
425
|
-
+/**
|
|
426
|
-
+ * Platform-suffixed resolution tags (`ios`, `android`, `visionos`, plus the
|
|
427
|
-
+ * shared `native` tag used by split-entry packages). Packages shipped with
|
|
428
|
-
+ * platform-split entries — e.g. `@nativescript-community/*` plugins carrying
|
|
429
|
-
+ * `index.ios.js`/`index.android.js` and a `main` like `./index` — 404 on the
|
|
430
|
-
+ * device bridge when only unsuffixed candidates are probed. Candidates are
|
|
431
|
-
+ * existence-filtered later, so probing every suffix is safe; the active
|
|
432
|
-
+ * platform's variants go first so a package shipping several platforms picks
|
|
433
|
-
+ * the right one.
|
|
434
|
-
+ */
|
|
435
|
-
+function platformSuffixes() {
|
|
436
|
-
+ if (cachedPlatformSuffixes === undefined) {
|
|
437
|
-
+ const platform = resolvePlatform();
|
|
438
|
-
+ const list = [platform, 'ios', 'android', 'visionos', 'native'].filter(Boolean);
|
|
439
|
-
+ cachedPlatformSuffixes = [...new Set(list)];
|
|
440
|
-
+ }
|
|
441
|
-
+ return cachedPlatformSuffixes;
|
|
442
|
-
+}
|
|
443
|
-
+const CANDIDATE_EXTS = ['ts', 'tsx', 'js', 'jsx', 'mjs', 'mts', 'cts', 'vue'];
|
|
444
|
-
+function expandModuleCandidates(spec, hasExt, baseNoExt) {
|
|
445
|
-
+ const candidates = [...(hasExt ? [spec] : [])];
|
|
446
|
-
+ for (const base of [baseNoExt, `${baseNoExt}/index`]) {
|
|
447
|
-
+ for (const suffix of platformSuffixes()) {
|
|
448
|
-
+ for (const ext of CANDIDATE_EXTS) {
|
|
449
|
-
+ candidates.push(`${base}.${suffix}.${ext}`);
|
|
450
|
-
+ }
|
|
451
|
-
+ }
|
|
452
|
-
+ for (const ext of CANDIDATE_EXTS) {
|
|
453
|
-
+ candidates.push(`${base}.${ext}`);
|
|
454
|
-
+ }
|
|
455
|
-
+ }
|
|
456
|
-
+ return candidates;
|
|
457
|
-
+}
|
|
458
|
-
function normalizeAbsoluteFilesystemSpec(spec, serverRoot) {
|
|
459
|
-
const toPosix = (value) => value.replace(/\\/g, '/');
|
|
460
|
-
const rootPosix = toPosix(serverRoot);
|
|
461
|
-
@@ -49,6 +84,14 @@ export function createNsMRequestContext(requestUrl, serverRoot, appVirtualWithSl
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
spec = spec.replace(/[?#].*$/, '');
|
|
465
|
-
+ // urlObj.pathname keeps percent-encoding — device fetches encode
|
|
466
|
-
+ // characters like `[`/`]` in file-based route names, so decode the
|
|
467
|
-
+ // spec before resolving it to a filesystem path. Malformed escapes
|
|
468
|
-
+ // keep the raw spelling rather than throwing.
|
|
469
|
-
+ try {
|
|
470
|
-
+ spec = decodeURIComponent(spec);
|
|
471
|
-
+ }
|
|
472
|
-
+ catch { }
|
|
473
|
-
const decorated = collapseLegacyNsMTags(spec, 'inbound-request-spec');
|
|
474
|
-
spec = decorated.cleanedSpec;
|
|
475
|
-
bootTaggedRequest = decorated.bootTaggedRequest;
|
|
476
|
-
@@ -73,7 +116,7 @@ export function createNsMRequestContext(requestUrl, serverRoot, appVirtualWithSl
|
|
477
|
-
}
|
|
478
|
-
const hasExt = /\.(ts|tsx|js|jsx|mjs|mts|cts|vue)$/i.test(spec);
|
|
479
|
-
const baseNoExt = hasExt ? spec.replace(/\.(ts|tsx|js|jsx|mjs|mts|cts)$/i, '') : spec;
|
|
480
|
-
- const candidates = [...(hasExt ? [spec] : []), `${baseNoExt}.ts`, `${baseNoExt}.js`, `${baseNoExt}.tsx`, `${baseNoExt}.jsx`, `${baseNoExt}.mjs`, `${baseNoExt}.mts`, `${baseNoExt}.cts`, `${baseNoExt}.vue`, `${baseNoExt}/index.ts`, `${baseNoExt}/index.js`, `${baseNoExt}/index.tsx`, `${baseNoExt}/index.jsx`, `${baseNoExt}/index.mjs`];
|
|
481
|
-
+ const candidates = expandModuleCandidates(spec, hasExt, baseNoExt);
|
|
482
|
-
const transformCandidates = filterExistingNodeModulesTransformCandidates(spec, candidates, serverRoot, workspaceRoot);
|
|
483
|
-
return {
|
|
484
|
-
kind: 'context',
|
|
485
|
-
diff --git a/hmr/server/websocket-ns-m.js b/hmr/server/websocket-ns-m.js
|
|
486
|
-
index 8eeb51ab59497e7f775da3117c4207e05f1814c7..ad91d9bab28605d06b16d1ef7fdb5d6c49678d9b 100644
|
|
487
|
-
--- a/hmr/server/websocket-ns-m.js
|
|
488
|
-
+++ b/hmr/server/websocket-ns-m.js
|
|
489
|
-
@@ -1,3 +1,4 @@
|
|
490
|
-
+import * as path from 'node:path';
|
|
491
|
-
import { parse as babelParse } from '@babel/parser';
|
|
492
|
-
import traverse from '@babel/traverse';
|
|
493
|
-
import { sanitizeStrayCoreReferences } from './core-sanitize.js';
|
|
494
|
-
@@ -6,6 +7,7 @@ import { isRuntimeGraphExcludedPath } from './runtime-graph-filter.js';
|
|
495
|
-
import { buildPiniaVendorShim, buildVueVendorShim } from './vendor-bare-module-shims.js';
|
|
496
|
-
import { collapseLegacyNsMTags } from './websocket-ns-m-paths.js';
|
|
497
|
-
import { createNsMRequestContext, resolveNsMTransformedModule } from './websocket-ns-m-request.js';
|
|
498
|
-
+import { resolveCandidateFilePath } from './websocket-module-specifiers.js';
|
|
499
|
-
import { setDeviceModuleHeaders } from './route-helpers.js';
|
|
500
|
-
import { CSS_MODULE_RE, buildCssRegisterSnippetFromVar, normalizeCssForDevice } from './css-device-module.js';
|
|
501
|
-
import { assertNoOptimizedArtifacts, buildBootProgressSnippet, canonicalizeRtImports, dedupeRtNamedImportsAgainstDestructures, deduplicateLinkerImports, ensureDestructureCoreImports, ensureGuardPlainDynamicImports, ensureVariableDynamicImportHelper, expandStarExports, hoistTopLevelStaticImports, MODULE_IMPORT_ANALYSIS_PLUGINS, wrapCommonJsModuleForDevice, ensureWorkerEntryGlobalsImport, classifyServedRequest } from './websocket-served-module-helpers.js';
|
|
502
|
-
@@ -154,7 +155,22 @@ export function registerNsModuleServerRoute(server, options) {
|
|
503
|
-
// string for `import css from './x.css'`. `app.css` has its own path.
|
|
504
|
-
if (CSS_MODULE_RE.test(spec)) {
|
|
505
|
-
try {
|
|
506
|
-
- const inlineRes = await sharedTransformRequest(`${spec}?inline`);
|
|
507
|
-
+ // `spec` is root-relative to the app package — workspace
|
|
508
|
-
+ // css (e.g. `packages/ui/theme/tokens.css` in a monorepo)
|
|
509
|
-
+ // resolves under the workspace root, so a bare `${spec}
|
|
510
|
-
+ // ?inline` transform misses there. Anchor it to the real
|
|
511
|
-
+ // file via /@fs/ first, like the module pipeline does.
|
|
512
|
-
+ let inlineSpec = spec;
|
|
513
|
-
+ const cssFsRoots = [serverRoot, ...(monorepoWorkspaceRoot && path.resolve(monorepoWorkspaceRoot) !== path.resolve(serverRoot) ? [monorepoWorkspaceRoot] : [])];
|
|
514
|
-
+ for (const root of cssFsRoots) {
|
|
515
|
-
+ const fsId = `/@fs${String(root).replace(/\\/g, '/').replace(/\/$/, '')}${spec.startsWith('/') ? '' : '/'}${spec}`;
|
|
516
|
-
+ const abs = resolveCandidateFilePath(fsId, serverRoot, monorepoWorkspaceRoot);
|
|
517
|
-
+ if (abs) {
|
|
518
|
-
+ inlineSpec = `/@fs${abs.replace(/\\/g, '/')}`;
|
|
519
|
-
+ break;
|
|
520
|
-
+ }
|
|
521
|
-
+ }
|
|
522
|
-
+ const inlineRes = await sharedTransformRequest(`${inlineSpec}?inline`);
|
|
523
|
-
const inlineCode = inlineRes?.code || '';
|
|
524
|
-
// `?inline` output is `export default "<compiled css>"`.
|
|
525
|
-
const m = inlineCode.match(/export\s+default\s+("(?:[^"\\]|\\.)*")\s*;?/s);
|
|
526
|
-
diff --git a/hmr/server/websocket-served-module-helpers.js b/hmr/server/websocket-served-module-helpers.js
|
|
527
|
-
index e65b1abaaf223cca4b11b65b8428c85c484f125d..c738f974ccdef53166365cf3f34d45136a4684f6 100644
|
|
528
|
-
--- a/hmr/server/websocket-served-module-helpers.js
|
|
529
|
-
+++ b/hmr/server/websocket-served-module-helpers.js
|
|
530
|
-
@@ -6,7 +6,7 @@ import * as PAT from './constants.js';
|
|
531
|
-
import { isDeepCoreSubpath } from './core-sanitize.js';
|
|
532
|
-
import { getCjsNamedExports } from '../helpers/cjs-named-exports.js';
|
|
533
|
-
import { getMonorepoWorkspaceRoot } from '../../helpers/project.js';
|
|
534
|
-
-import { extractDirectExportedNames, parseExportSpecList } from './websocket-core-bridge.js';
|
|
535
|
-
+import { extractDirectExportedNames, maskJsComments, parseExportSpecList } from './websocket-core-bridge.js';
|
|
536
|
-
import { resolveCandidateFilePath } from './websocket-module-specifiers.js';
|
|
537
|
-
let cachedWorkspaceCoreRoot;
|
|
538
|
-
/**
|
|
539
|
-
@@ -105,8 +105,8 @@ export function classifyServedRequest(spec, resolvedId) {
|
|
540
|
-
return classifyServedModule(spec);
|
|
541
|
-
}
|
|
542
|
-
export const MODULE_IMPORT_ANALYSIS_PLUGINS = ['typescript', 'jsx', 'importMeta', 'topLevelAwait', 'classProperties', 'classPrivateProperties', 'classPrivateMethods', 'decorators-legacy'];
|
|
543
|
-
-export function collectTopLevelImportRecords(code) {
|
|
544
|
-
- if (!code || typeof code !== 'string' || !/\bimport\b/.test(code)) {
|
|
545
|
-
+export function collectTopLevelImportRecords(code, includeReExports = false) {
|
|
546
|
-
+ if (!code || typeof code !== 'string' || !(includeReExports ? /\b(?:import|export)\b/ : /\bimport\b/).test(code)) {
|
|
547
|
-
return [];
|
|
548
|
-
}
|
|
549
|
-
try {
|
|
550
|
-
@@ -119,7 +119,7 @@ export function collectTopLevelImportRecords(code) {
|
|
551
|
-
return [];
|
|
552
|
-
}
|
|
553
|
-
return body
|
|
554
|
-
- .filter((node) => t.isImportDeclaration(node) && typeof node.start === 'number' && typeof node.end === 'number' && typeof node.source?.value === 'string')
|
|
555
|
-
+ .filter((node) => (t.isImportDeclaration(node) || (includeReExports && (t.isExportNamedDeclaration(node) || t.isExportAllDeclaration(node)))) && typeof node.start === 'number' && typeof node.end === 'number' && typeof node.source?.value === 'string')
|
|
556
|
-
.map((node) => ({
|
|
557
|
-
start: node.start,
|
|
558
|
-
end: node.end,
|
|
559
|
-
@@ -366,6 +366,10 @@ function normalizeStarExportTargetPath(spec, parentVitePath) {
|
|
560
|
-
// - `extractExportMetadata` (below): does a module have a default export
|
|
561
|
-
// and which named ones — shape-only, for SFC route metadata.
|
|
562
|
-
function scanModuleExportSurface(code) {
|
|
563
|
-
+ // Mask comments up front: the unanchored `export * as`/`export {} from`
|
|
564
|
-
+ // regexes below would otherwise claim names from commented-out lines and
|
|
565
|
-
+ // shadow the same name's real star-export source.
|
|
566
|
-
+ code = maskJsComments(code);
|
|
567
|
-
const ownNames = new Set(extractDirectExportedNames(code));
|
|
568
|
-
for (const m of code.matchAll(/\bexport\s+\*\s+as\s+([A-Za-z_$][\w$]*)\s+from\s*["'][^"']+["']/g)) {
|
|
569
|
-
ownNames.add(m[1]);
|