create-octane-xplat 0.6.0 → 0.7.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 +37 -17
- package/package.json +6 -1
- package/template/.agents/skills/xplat/SKILL.md +7 -3
- package/template/.agents/skills/xplat/references/components.md +13 -8
- package/template/.agents/skills/xplat/references/navigation.md +8 -5
- package/template/.agents/skills/xplat/references/platform.md +21 -21
- package/template/App_Resources/iOS/src/ShaderEffectRegistration.swift +31 -0
- package/template/package.json +7 -8
- package/template/patches/@nativescript-community__octane@0.2.1.patch +16 -0
- package/template/patches/@nativescript__core@9.1.2.patch +752 -0
- package/template/patches/@nativescript__core@9.1.3-next.2.patch +428 -0
- package/template/patches/@nativescript__vite@8.0.11.patch +531 -261
- package/template/patches/@nativescript__vite@8.0.13.patch +233 -0
- package/template/patches/css@3.0.0.patch +52 -0
- package/template/patches/octane@0.6.3.patch +1149 -0
- package/template/pnpm-workspace.yaml +54 -12
- package/template/src/App.tsrx +2 -2
- package/template/tsconfig.json +1 -1
- package/template/tsconfig.native.json +1 -1
- package/template/vite.config.ts +3 -3
- /package/template/src/{index.native.ts → index.ts} +0 -0
|
@@ -1,294 +1,564 @@
|
|
|
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
1
|
diff --git a/hmr/server/deps-bundle.js b/hmr/server/deps-bundle.js
|
|
41
|
-
index 50e5abddddf80b35044d45eb108a64dd175bf6e7..
|
|
2
|
+
index 50e5abddddf80b35044d45eb108a64dd175bf6e7..4f16f129b4c4271173169871f42645d16d3cfa0c 100644
|
|
42
3
|
--- a/hmr/server/deps-bundle.js
|
|
43
4
|
+++ b/hmr/server/deps-bundle.js
|
|
44
|
-
@@ -
|
|
45
|
-
|
|
46
|
-
|
|
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;
|
|
47
101
|
}
|
|
48
|
-
|
|
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;
|
|
49
159
|
+/**
|
|
50
|
-
+ *
|
|
51
|
-
+ *
|
|
52
|
-
+ *
|
|
53
|
-
+ *
|
|
54
|
-
+ *
|
|
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.
|
|
55
171
|
+ */
|
|
56
|
-
+function
|
|
57
|
-
+
|
|
58
|
-
+
|
|
59
|
-
+
|
|
60
|
-
+
|
|
61
|
-
+
|
|
62
|
-
+
|
|
63
|
-
+
|
|
64
|
-
+ const
|
|
65
|
-
+
|
|
66
|
-
+
|
|
67
|
-
+
|
|
68
|
-
+
|
|
69
|
-
+
|
|
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
|
+
+ }
|
|
70
224
|
+ }
|
|
71
|
-
+ return
|
|
225
|
+
+ return chars.join('');
|
|
72
226
|
+}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
+
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
+
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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('.')) {
|
|
130
338
|
+ continue;
|
|
131
339
|
+ }
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
+
+ }
|
|
140
384
|
}
|
|
141
|
-
catch { }
|
|
142
385
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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}.`);
|
|
386
|
+
return null;
|
|
387
|
+
@@ -794,16 +873,24 @@ export function resolveVendorRouting(nodeModulesSpec, projectRoot) {
|
|
388
|
+
if (rootPackageName && pkgName !== rootPackageName) {
|
|
389
|
+
return { route: 'http' };
|
|
155
390
|
}
|
|
156
|
-
-
|
|
157
|
-
+
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
+
|
|
166
|
-
+
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
+
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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 };
|
|
174
409
|
}
|
|
175
|
-
|
|
176
|
-
|
|
410
|
+
}
|
|
411
|
+
- const manifest = getVendorManifest();
|
|
412
|
+
if (!manifest?.modules?.[pkgName]) {
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
177
415
|
diff --git a/hmr/server/websocket-ns-m-request.js b/hmr/server/websocket-ns-m-request.js
|
|
178
|
-
index b54aefcc839e95906d76d02e522c052a706ca76f..
|
|
416
|
+
index b54aefcc839e95906d76d02e522c052a706ca76f..a9c38c32b8752765044cd8f2c1eac558a30052dd 100644
|
|
179
417
|
--- a/hmr/server/websocket-ns-m-request.js
|
|
180
418
|
+++ b/hmr/server/websocket-ns-m-request.js
|
|
181
|
-
@@ -1,6 +1,
|
|
419
|
+
@@ -1,6 +1,41 @@
|
|
182
420
|
import * as path from 'path';
|
|
183
|
-
+import { realpathSync } from 'fs';
|
|
184
421
|
import { filterExistingNodeModulesTransformCandidates, getBlockedDeviceNodeModulesReason, resolveCandidateFilePath, tryReadRawExplicitJavaScriptModule } from './websocket-module-specifiers.js';
|
|
185
422
|
import { collapseLegacyNsMTags } from './websocket-ns-m-paths.js';
|
|
186
|
-
+import { resolveSpecToFile } from './deps-bundle.js';
|
|
187
423
|
+import { resolvePlatform } from '../../helpers/cli-flags.js';
|
|
188
|
-
|
|
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
|
-
}
|
|
424
|
+
+let cachedPlatformSuffixes = undefined;
|
|
195
425
|
+/**
|
|
196
|
-
+ *
|
|
197
|
-
+ *
|
|
198
|
-
+ *
|
|
199
|
-
+ *
|
|
200
|
-
+ *
|
|
201
|
-
+ *
|
|
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.
|
|
202
434
|
+ */
|
|
203
|
-
+function
|
|
204
|
-
+
|
|
205
|
-
+ const
|
|
206
|
-
+
|
|
207
|
-
+
|
|
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);
|
|
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)];
|
|
215
440
|
+ }
|
|
216
|
-
+ return
|
|
441
|
+
+ return cachedPlatformSuffixes;
|
|
217
442
|
+}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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 };
|
|
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}`);
|
|
254
450
|
+ }
|
|
255
451
|
+ }
|
|
452
|
+
+ for (const ext of CANDIDATE_EXTS) {
|
|
453
|
+
+ candidates.push(`${base}.${ext}`);
|
|
454
|
+
+ }
|
|
256
455
|
+ }
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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',
|
|
260
485
|
diff --git a/hmr/server/websocket-ns-m.js b/hmr/server/websocket-ns-m.js
|
|
261
|
-
index 8eeb51ab59497e7f775da3117c4207e05f1814c7..
|
|
486
|
+
index 8eeb51ab59497e7f775da3117c4207e05f1814c7..407c2a2484c843bc538ce6ef9cf7af0b9b883ad3 100644
|
|
262
487
|
--- a/hmr/server/websocket-ns-m.js
|
|
263
488
|
+++ b/hmr/server/websocket-ns-m.js
|
|
264
|
-
@@ -
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
+
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
+
|
|
278
|
-
+
|
|
279
|
-
+
|
|
280
|
-
+
|
|
281
|
-
+
|
|
489
|
+
@@ -6,6 +6,7 @@ import { isRuntimeGraphExcludedPath } from './runtime-graph-filter.js';
|
|
490
|
+
import { buildPiniaVendorShim, buildVueVendorShim } from './vendor-bare-module-shims.js';
|
|
491
|
+
import { collapseLegacyNsMTags } from './websocket-ns-m-paths.js';
|
|
492
|
+
import { createNsMRequestContext, resolveNsMTransformedModule } from './websocket-ns-m-request.js';
|
|
493
|
+
+import { resolveCandidateFilePath } from './websocket-module-specifiers.js';
|
|
494
|
+
import { setDeviceModuleHeaders } from './route-helpers.js';
|
|
495
|
+
import { CSS_MODULE_RE, buildCssRegisterSnippetFromVar, normalizeCssForDevice } from './css-device-module.js';
|
|
496
|
+
import { assertNoOptimizedArtifacts, buildBootProgressSnippet, canonicalizeRtImports, dedupeRtNamedImportsAgainstDestructures, deduplicateLinkerImports, ensureDestructureCoreImports, ensureGuardPlainDynamicImports, ensureVariableDynamicImportHelper, expandStarExports, hoistTopLevelStaticImports, MODULE_IMPORT_ANALYSIS_PLUGINS, wrapCommonJsModuleForDevice, ensureWorkerEntryGlobalsImport, classifyServedRequest } from './websocket-served-module-helpers.js';
|
|
497
|
+
@@ -154,7 +155,22 @@ export function registerNsModuleServerRoute(server, options) {
|
|
498
|
+
// string for `import css from './x.css'`. `app.css` has its own path.
|
|
499
|
+
if (CSS_MODULE_RE.test(spec)) {
|
|
500
|
+
try {
|
|
501
|
+
- const inlineRes = await sharedTransformRequest(`${spec}?inline`);
|
|
502
|
+
+ // `spec` is root-relative to the app package — workspace
|
|
503
|
+
+ // css (e.g. `packages/ui/theme/tokens.css` in a monorepo)
|
|
504
|
+
+ // resolves under the workspace root, so a bare `${spec}
|
|
505
|
+
+ // ?inline` transform misses there. Anchor it to the real
|
|
506
|
+
+ // file via /@fs/ first, like the module pipeline does.
|
|
507
|
+
+ let inlineSpec = spec;
|
|
508
|
+
+ const cssFsRoots = [serverRoot, ...(monorepoWorkspaceRoot && path.resolve(monorepoWorkspaceRoot) !== path.resolve(serverRoot) ? [monorepoWorkspaceRoot] : [])];
|
|
509
|
+
+ for (const root of cssFsRoots) {
|
|
510
|
+
+ const fsId = `/@fs${String(root).replace(/\\/g, '/').replace(/\/$/, '')}${spec.startsWith('/') ? '' : '/'}${spec}`;
|
|
511
|
+
+ const abs = resolveCandidateFilePath(fsId, serverRoot, monorepoWorkspaceRoot);
|
|
512
|
+
+ if (abs) {
|
|
513
|
+
+ inlineSpec = `/@fs${abs.replace(/\\/g, '/')}`;
|
|
514
|
+
+ break;
|
|
282
515
|
+ }
|
|
283
516
|
+ }
|
|
284
|
-
+
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
517
|
+
+ const inlineRes = await sharedTransformRequest(`${inlineSpec}?inline`);
|
|
518
|
+
const inlineCode = inlineRes?.code || '';
|
|
519
|
+
// `?inline` output is `export default "<compiled css>"`.
|
|
520
|
+
const m = inlineCode.match(/export\s+default\s+("(?:[^"\\]|\\.)*")\s*;?/s);
|
|
521
|
+
diff --git a/hmr/server/websocket-served-module-helpers.js b/hmr/server/websocket-served-module-helpers.js
|
|
522
|
+
index e65b1abaaf223cca4b11b65b8428c85c484f125d..c738f974ccdef53166365cf3f34d45136a4684f6 100644
|
|
523
|
+
--- a/hmr/server/websocket-served-module-helpers.js
|
|
524
|
+
+++ b/hmr/server/websocket-served-module-helpers.js
|
|
525
|
+
@@ -6,7 +6,7 @@ import * as PAT from './constants.js';
|
|
526
|
+
import { isDeepCoreSubpath } from './core-sanitize.js';
|
|
527
|
+
import { getCjsNamedExports } from '../helpers/cjs-named-exports.js';
|
|
528
|
+
import { getMonorepoWorkspaceRoot } from '../../helpers/project.js';
|
|
529
|
+
-import { extractDirectExportedNames, parseExportSpecList } from './websocket-core-bridge.js';
|
|
530
|
+
+import { extractDirectExportedNames, maskJsComments, parseExportSpecList } from './websocket-core-bridge.js';
|
|
531
|
+
import { resolveCandidateFilePath } from './websocket-module-specifiers.js';
|
|
532
|
+
let cachedWorkspaceCoreRoot;
|
|
533
|
+
/**
|
|
534
|
+
@@ -105,8 +105,8 @@ export function classifyServedRequest(spec, resolvedId) {
|
|
535
|
+
return classifyServedModule(spec);
|
|
536
|
+
}
|
|
537
|
+
export const MODULE_IMPORT_ANALYSIS_PLUGINS = ['typescript', 'jsx', 'importMeta', 'topLevelAwait', 'classProperties', 'classPrivateProperties', 'classPrivateMethods', 'decorators-legacy'];
|
|
538
|
+
-export function collectTopLevelImportRecords(code) {
|
|
539
|
+
- if (!code || typeof code !== 'string' || !/\bimport\b/.test(code)) {
|
|
540
|
+
+export function collectTopLevelImportRecords(code, includeReExports = false) {
|
|
541
|
+
+ if (!code || typeof code !== 'string' || !(includeReExports ? /\b(?:import|export)\b/ : /\bimport\b/).test(code)) {
|
|
542
|
+
return [];
|
|
543
|
+
}
|
|
544
|
+
try {
|
|
545
|
+
@@ -119,7 +119,7 @@ export function collectTopLevelImportRecords(code) {
|
|
546
|
+
return [];
|
|
547
|
+
}
|
|
548
|
+
return body
|
|
549
|
+
- .filter((node) => t.isImportDeclaration(node) && typeof node.start === 'number' && typeof node.end === 'number' && typeof node.source?.value === 'string')
|
|
550
|
+
+ .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')
|
|
551
|
+
.map((node) => ({
|
|
552
|
+
start: node.start,
|
|
553
|
+
end: node.end,
|
|
554
|
+
@@ -366,6 +366,10 @@ function normalizeStarExportTargetPath(spec, parentVitePath) {
|
|
555
|
+
// - `extractExportMetadata` (below): does a module have a default export
|
|
556
|
+
// and which named ones — shape-only, for SFC route metadata.
|
|
557
|
+
function scanModuleExportSurface(code) {
|
|
558
|
+
+ // Mask comments up front: the unanchored `export * as`/`export {} from`
|
|
559
|
+
+ // regexes below would otherwise claim names from commented-out lines and
|
|
560
|
+
+ // shadow the same name's real star-export source.
|
|
561
|
+
+ code = maskJsComments(code);
|
|
562
|
+
const ownNames = new Set(extractDirectExportedNames(code));
|
|
563
|
+
for (const m of code.matchAll(/\bexport\s+\*\s+as\s+([A-Za-z_$][\w$]*)\s+from\s*["'][^"']+["']/g)) {
|
|
564
|
+
ownNames.add(m[1]);
|