@nativescript/vite 0.0.1-alpha.3 → 0.0.1-alpha.4

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.
@@ -1,7 +1,7 @@
1
1
  import { knownFolders, File, Page, Application, Label, Folder, Frame, } from "@nativescript/core";
2
2
  import { NSVRoot, createVNode, createApp, resolveComponent, createBlock, createTextVNode, createElementVNode, createElementBlock, ref, computed, onMounted, onUnmounted, $navigateTo, $navigateBack, } from "nativescript-vue";
3
3
  // Import the core Vue functions for the ones not wrapped
4
- import { openBlock, withCtx } from "@vue/runtime-core";
4
+ import { openBlock, withCtx, renderList, renderSlot, toDisplayString, createCommentVNode, Fragment, unref } from "@vue/runtime-core";
5
5
  // Wire in standard Vite 'update' handling (CSS etc.)
6
6
  import { handleCssUpdates } from "./css-handler";
7
7
  // Simple, robust HMR client for NativeScript + Vue 3.
@@ -164,6 +164,28 @@ async function importModuleFromContent(content, msg) {
164
164
  globalThis._createTextVNode = createTextVNode;
165
165
  globalThis._createElementVNode = createElementVNode || createVNode;
166
166
  globalThis._createElementBlock = createElementBlock || createBlock;
167
+ // Additional helpers (underscore + plain) used by compiled templates
168
+ globalThis._renderList = globalThis._renderList || renderList;
169
+ globalThis._renderSlot = globalThis._renderSlot || renderSlot;
170
+ globalThis._toDisplayString = globalThis._toDisplayString || toDisplayString;
171
+ globalThis._createCommentVNode = globalThis._createCommentVNode || createCommentVNode;
172
+ globalThis._Fragment = globalThis._Fragment || Fragment;
173
+ globalThis.renderList = globalThis.renderList || renderList;
174
+ globalThis.renderSlot = globalThis.renderSlot || renderSlot;
175
+ globalThis.toDisplayString = globalThis.toDisplayString || toDisplayString;
176
+ globalThis.createCommentVNode = globalThis.createCommentVNode || createCommentVNode;
177
+ globalThis.Fragment = globalThis.Fragment || Fragment;
178
+ // Provide normalizeClass/normalizeStyle fallbacks (no-ops if not present)
179
+ if (!globalThis.normalizeClass) {
180
+ globalThis.normalizeClass = (v) => v;
181
+ }
182
+ if (!globalThis.normalizeStyle) {
183
+ globalThis.normalizeStyle = (v) => v;
184
+ }
185
+ // Provide unref using ref utilities if not already available (avoid direct import to limit surface)
186
+ if (!globalThis.unref) {
187
+ globalThis.unref = unref || ((r) => (r && typeof r === 'object' && 'value' in r ? r.value : r));
188
+ }
167
189
  // Set up Vue reactivity functions - CRITICAL for reactive bindings
168
190
  globalThis.ref = ref;
169
191
  globalThis.computed = computed;
@@ -311,18 +311,13 @@ const _defineComponent = globalThis._defineComponent || globalThis.defineCompone
311
311
  });
312
312
  // Remove imports of core runtime helpers (we'll map to globals) and inject underscore aliases
313
313
  const coreFns = [
314
- "resolveComponent",
315
- "createVNode",
316
- "createTextVNode",
317
- "withCtx",
318
- "openBlock",
319
- "createBlock",
320
- "createElementVNode",
321
- "createElementBlock",
314
+ "resolveComponent", "createVNode", "createTextVNode", "withCtx", "openBlock", "createBlock", "createElementVNode", "createElementBlock",
315
+ // extended helpers needed by compiled templates
316
+ "Fragment", "createCommentVNode", "renderList", "renderSlot", "toDisplayString", "normalizeClass", "normalizeStyle", "unref"
322
317
  ];
323
318
  enhanced = removeNamedImports(enhanced, coreFns);
324
- const usesUnderscoreHelpers = /\b_(resolveComponent|createVNode|createTextVNode|withCtx|openBlock|createBlock|createElementVNode|createElementBlock)\b/.test(enhanced);
325
- if (usesUnderscoreHelpers) {
319
+ const needsGlobals = /\b_(resolveComponent|createVNode|createTextVNode|withCtx|openBlock|createBlock|createElementVNode|createElementBlock)\b/.test(enhanced) || /\b(Fragment|createCommentVNode|renderList|renderSlot|toDisplayString|normalizeClass|normalizeStyle|unref)\b/.test(enhanced);
320
+ if (needsGlobals) {
326
321
  const globalsPrelude = [
327
322
  "const _resolveComponent = globalThis._resolveComponent || globalThis.resolveComponent;",
328
323
  "const _createVNode = globalThis._createVNode || globalThis.createVNode;",
@@ -332,6 +327,19 @@ const _defineComponent = globalThis._defineComponent || globalThis.defineCompone
332
327
  "const _createBlock = globalThis._createBlock || globalThis.createBlock;",
333
328
  "const _createElementVNode = globalThis._createElementVNode || globalThis.createElementVNode || globalThis.createVNode;",
334
329
  "const _createElementBlock = globalThis._createElementBlock || globalThis.createElementBlock || globalThis.createBlock;",
330
+ "const Fragment = globalThis.Fragment || globalThis._Fragment;",
331
+ "const _Fragment = globalThis._Fragment || globalThis.Fragment;",
332
+ "const createCommentVNode = globalThis.createCommentVNode || globalThis._createCommentVNode;",
333
+ "const _createCommentVNode = globalThis._createCommentVNode || globalThis.createCommentVNode;",
334
+ "const renderList = globalThis.renderList;",
335
+ "const _renderList = globalThis._renderList || globalThis.renderList;",
336
+ "const renderSlot = globalThis.renderSlot;",
337
+ "const _renderSlot = globalThis._renderSlot || globalThis.renderSlot;",
338
+ "const toDisplayString = globalThis.toDisplayString || globalThis._toDisplayString;",
339
+ "const _toDisplayString = globalThis._toDisplayString || globalThis.toDisplayString;",
340
+ "const normalizeClass = globalThis.normalizeClass;",
341
+ "const normalizeStyle = globalThis.normalizeStyle;",
342
+ "const unref = globalThis.unref || globalThis._unref;",
335
343
  ].join("\n");
336
344
  enhanced = globalsPrelude + "\n" + enhanced;
337
345
  }
@@ -434,17 +442,11 @@ const _defineComponent = globalThis._defineComponent || globalThis.defineCompone
434
442
  }
435
443
  // Remove helper imports and inject globals like registry path
436
444
  const coreFns = [
437
- "resolveComponent",
438
- "createVNode",
439
- "createTextVNode",
440
- "withCtx",
441
- "openBlock",
442
- "createBlock",
443
- "createElementVNode",
444
- "createElementBlock",
445
+ "resolveComponent", "createVNode", "createTextVNode", "withCtx", "openBlock", "createBlock", "createElementVNode", "createElementBlock",
446
+ "Fragment", "createCommentVNode", "renderList", "renderSlot", "toDisplayString", "normalizeClass", "normalizeStyle", "unref"
445
447
  ];
446
448
  depCode = removeNamedImports(depCode, coreFns);
447
- if (/\b_(resolveComponent|createVNode|createTextVNode|withCtx|openBlock|createBlock|createElementVNode|createElementBlock)\b/.test(depCode)) {
449
+ if (/\b_(resolveComponent|createVNode|createTextVNode|withCtx|openBlock|createBlock|createElementVNode|createElementBlock)\b/.test(depCode) || /\b(Fragment|createCommentVNode|renderList|renderSlot|toDisplayString|normalizeClass|normalizeStyle|unref)\b/.test(depCode)) {
448
450
  const globalsPrelude = [
449
451
  "const _resolveComponent = globalThis._resolveComponent || globalThis.resolveComponent;",
450
452
  "const _createVNode = globalThis._createVNode || globalThis.createVNode;",
@@ -454,6 +456,19 @@ const _defineComponent = globalThis._defineComponent || globalThis.defineCompone
454
456
  "const _createBlock = globalThis._createBlock || globalThis.createBlock;",
455
457
  "const _createElementVNode = globalThis._createElementVNode || globalThis.createElementVNode || globalThis.createVNode;",
456
458
  "const _createElementBlock = globalThis._createElementBlock || globalThis.createElementBlock || globalThis.createBlock;",
459
+ "const Fragment = globalThis.Fragment || globalThis._Fragment;",
460
+ "const _Fragment = globalThis._Fragment || globalThis.Fragment;",
461
+ "const createCommentVNode = globalThis.createCommentVNode || globalThis._createCommentVNode;",
462
+ "const _createCommentVNode = globalThis._createCommentVNode || globalThis.createCommentVNode;",
463
+ "const renderList = globalThis.renderList;",
464
+ "const _renderList = globalThis._renderList || globalThis.renderList;",
465
+ "const renderSlot = globalThis.renderSlot;",
466
+ "const _renderSlot = globalThis._renderSlot || globalThis.renderSlot;",
467
+ "const toDisplayString = globalThis.toDisplayString || globalThis._toDisplayString;",
468
+ "const _toDisplayString = globalThis._toDisplayString || globalThis.toDisplayString;",
469
+ "const normalizeClass = globalThis.normalizeClass;",
470
+ "const normalizeStyle = globalThis.normalizeStyle;",
471
+ "const unref = globalThis.unref || globalThis._unref;",
457
472
  ].join("\n");
458
473
  depCode = globalsPrelude + "\n" + depCode;
459
474
  }
@@ -606,17 +621,11 @@ const _defineComponent = globalThis._defineComponent || globalThis.defineCompone
606
621
  }
607
622
  // Remove core runtime helper imports and map to globals
608
623
  const coreFns = [
609
- "resolveComponent",
610
- "createVNode",
611
- "createTextVNode",
612
- "withCtx",
613
- "openBlock",
614
- "createBlock",
615
- "createElementVNode",
616
- "createElementBlock",
624
+ "resolveComponent", "createVNode", "createTextVNode", "withCtx", "openBlock", "createBlock", "createElementVNode", "createElementBlock",
625
+ "Fragment", "createCommentVNode", "renderList", "renderSlot", "toDisplayString", "normalizeClass", "normalizeStyle", "unref"
617
626
  ];
618
627
  sfcCode = removeNamedImports(sfcCode, coreFns);
619
- if (/\b_(resolveComponent|createVNode|createTextVNode|withCtx|openBlock|createBlock|createElementVNode|createElementBlock)\b/.test(sfcCode)) {
628
+ if (/\b_(resolveComponent|createVNode|createTextVNode|withCtx|openBlock|createBlock|createElementVNode|createElementBlock)\b/.test(sfcCode) || /\b(Fragment|createCommentVNode|renderList|renderSlot|toDisplayString|normalizeClass|normalizeStyle|unref)\b/.test(sfcCode)) {
620
629
  const globalsPrelude = [
621
630
  "const _resolveComponent = globalThis._resolveComponent || globalThis.resolveComponent;",
622
631
  "const _createVNode = globalThis._createVNode || globalThis.createVNode;",
@@ -626,6 +635,19 @@ const _defineComponent = globalThis._defineComponent || globalThis.defineCompone
626
635
  "const _createBlock = globalThis._createBlock || globalThis.createBlock;",
627
636
  "const _createElementVNode = globalThis._createElementVNode || globalThis.createElementVNode || globalThis.createVNode;",
628
637
  "const _createElementBlock = globalThis._createElementBlock || globalThis.createElementBlock || globalThis.createBlock;",
638
+ "const Fragment = globalThis.Fragment || globalThis._Fragment;",
639
+ "const _Fragment = globalThis._Fragment || globalThis.Fragment;",
640
+ "const createCommentVNode = globalThis.createCommentVNode || globalThis._createCommentVNode;",
641
+ "const _createCommentVNode = globalThis._createCommentVNode || globalThis.createCommentVNode;",
642
+ "const renderList = globalThis.renderList;",
643
+ "const _renderList = globalThis._renderList || globalThis.renderList;",
644
+ "const renderSlot = globalThis.renderSlot;",
645
+ "const _renderSlot = globalThis._renderSlot || globalThis.renderSlot;",
646
+ "const toDisplayString = globalThis.toDisplayString || globalThis._toDisplayString;",
647
+ "const _toDisplayString = globalThis._toDisplayString || globalThis.toDisplayString;",
648
+ "const normalizeClass = globalThis.normalizeClass;",
649
+ "const normalizeStyle = globalThis.normalizeStyle;",
650
+ "const unref = globalThis.unref || globalThis._unref;",
629
651
  ].join("\n");
630
652
  sfcCode = globalsPrelude + "\n" + sfcCode;
631
653
  }
@@ -808,17 +830,11 @@ async function buildAndSendSfcRegistry(server, sfcFileMap, wss) {
808
830
  }
809
831
  // Remove imports for core runtime helper functions and map to globals
810
832
  const coreFns = [
811
- "resolveComponent",
812
- "createVNode",
813
- "createTextVNode",
814
- "withCtx",
815
- "openBlock",
816
- "createBlock",
817
- "createElementVNode",
818
- "createElementBlock",
833
+ "resolveComponent", "createVNode", "createTextVNode", "withCtx", "openBlock", "createBlock", "createElementVNode", "createElementBlock",
834
+ "Fragment", "createCommentVNode", "renderList", "renderSlot", "toDisplayString", "normalizeClass", "normalizeStyle", "unref"
819
835
  ];
820
836
  code = removeNamedImports(code, coreFns);
821
- if (/\b_(resolveComponent|createVNode|createTextVNode|withCtx|openBlock|createBlock|createElementVNode|createElementBlock)\b/.test(code)) {
837
+ if (/\b_(resolveComponent|createVNode|createTextVNode|withCtx|openBlock|createBlock|createElementVNode|createElementBlock)\b/.test(code) || /\b(Fragment|createCommentVNode|renderList|renderSlot|toDisplayString|normalizeClass|normalizeStyle|unref)\b/.test(code)) {
822
838
  const globalsPrelude = [
823
839
  "const _resolveComponent = globalThis._resolveComponent || globalThis.resolveComponent;",
824
840
  "const _createVNode = globalThis._createVNode || globalThis.createVNode;",
@@ -828,6 +844,19 @@ async function buildAndSendSfcRegistry(server, sfcFileMap, wss) {
828
844
  "const _createBlock = globalThis._createBlock || globalThis.createBlock;",
829
845
  "const _createElementVNode = globalThis._createElementVNode || globalThis.createElementVNode || globalThis.createVNode;",
830
846
  "const _createElementBlock = globalThis._createElementBlock || globalThis.createElementBlock || globalThis.createBlock;",
847
+ "const Fragment = globalThis.Fragment || globalThis._Fragment;",
848
+ "const _Fragment = globalThis._Fragment || globalThis.Fragment;",
849
+ "const createCommentVNode = globalThis.createCommentVNode || globalThis._createCommentVNode;",
850
+ "const _createCommentVNode = globalThis._createCommentVNode || globalThis.createCommentVNode;",
851
+ "const renderList = globalThis.renderList;",
852
+ "const _renderList = globalThis._renderList || globalThis.renderList;",
853
+ "const renderSlot = globalThis.renderSlot;",
854
+ "const _renderSlot = globalThis._renderSlot || globalThis.renderSlot;",
855
+ "const toDisplayString = globalThis.toDisplayString || globalThis._toDisplayString;",
856
+ "const _toDisplayString = globalThis._toDisplayString || globalThis.toDisplayString;",
857
+ "const normalizeClass = globalThis.normalizeClass;",
858
+ "const normalizeStyle = globalThis.normalizeStyle;",
859
+ "const unref = globalThis.unref || globalThis._unref;",
831
860
  ].join("\n");
832
861
  code = globalsPrelude + "\n" + code;
833
862
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nativescript/vite",
3
- "version": "0.0.1-alpha.3",
3
+ "version": "0.0.1-alpha.4",
4
4
  "description": "Vite for NativeScript",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -1,2 +0,0 @@
1
- import type { Plugin } from "vite";
2
- export declare function hmrPluginVue(platform: string): Plugin;
@@ -1,38 +0,0 @@
1
- import { createRequire } from "node:module";
2
- const require = createRequire(import.meta.url);
3
- const VIRTUAL_ID = "virtual:pure-vue-hmr";
4
- const RESOLVED_ID = "\0" + VIRTUAL_ID;
5
- // TODO: make switch statement on flavors to load different flavor clients
6
- export function hmrPluginVue(platform) {
7
- let config;
8
- return {
9
- name: "nativescript-pure-vue-hmr",
10
- configResolved(c) {
11
- config = c;
12
- },
13
- resolveId(id) {
14
- if (id === VIRTUAL_ID)
15
- return RESOLVED_ID;
16
- return null;
17
- },
18
- load(id) {
19
- if (id !== RESOLVED_ID)
20
- return null;
21
- const clientPath = require.resolve("@nativescript/vite/dist/hmr/client-vue.js");
22
- // Build ws url from Vite server info
23
- const host = process.env.NS_HMR_HOST ||
24
- config?.server?.host ||
25
- (platform === 'android' ? "10.0.2.2" : "localhost");
26
- const port = Number(config?.server?.port || 5173);
27
- const secure = !!config?.server?.https;
28
- const protocol = secure ? "wss" : "ws";
29
- const wsUrl = `${protocol}://${host}:${port}/ns-hmr`;
30
- // Import client and start it with explicit ws URL
31
- return `
32
- import startViteHMR from "${clientPath}";
33
- console.log('[hmr-plugin-vue] starting client -> ${wsUrl}');
34
- startViteHMR({ wsUrl: ${JSON.stringify(wsUrl)} });
35
- `;
36
- },
37
- };
38
- }
@@ -1,2 +0,0 @@
1
- import type { Plugin } from "vite";
2
- export declare function hmrWebSocketVue(): Plugin;
@@ -1,875 +0,0 @@
1
- import { WebSocketServer } from "ws";
2
- import path from "path";
3
- import { readFileSync, readdirSync, statSync } from "fs";
4
- import { createHash } from "node:crypto";
5
- // Helper: remove balanced `if (import.meta.hot) { ... }` blocks to avoid orphan fragments
6
- function stripImportMetaHotBlocks(input) {
7
- let out = input;
8
- const re = /if\s*\(\s*import\.meta\.hot\s*\)\s*\{/g;
9
- let m;
10
- while ((m = re.exec(out)) !== null) {
11
- let start = m.index;
12
- let i = start + m[0].length;
13
- let depth = 1;
14
- while (i < out.length && depth > 0) {
15
- const ch = out[i++];
16
- if (ch === "{")
17
- depth++;
18
- else if (ch === "}")
19
- depth--;
20
- }
21
- // Replace the whole balanced block with a comment
22
- out =
23
- out.slice(0, start) + "// Removed import.meta.hot block\n" + out.slice(i);
24
- re.lastIndex = start; // reset search after modification
25
- }
26
- return out;
27
- }
28
- // Helper: remove common Vue HMR runtime statements and orphan else blocks
29
- function stripVueHmrRuntimeNoise(input) {
30
- let out = input;
31
- // Remove standalone createRecord/reload/rerender calls
32
- out = out.replace(/__VUE_HMR_RUNTIME__\.(?:createRecord|reload|rerender)\s*\([\s\S]*?\);\s*/g, "");
33
- // Remove patterns like `} else { __VUE_HMR_RUNTIME__.reload(...); }`
34
- out = out.replace(/\}\s*else\s*\{\s*__VUE_HMR_RUNTIME__\.(?:reload|rerender)\s*\([\s\S]*?\);\s*\}\s*/g, "");
35
- // Remove orphan `else { __VUE_HMR_RUNTIME__... }` if the preceding `if (import.meta.hot) {}` was stripped
36
- out = out.replace(/(?:^|\n)\s*else\s*\{\s*__VUE_HMR_RUNTIME__\.(?:reload|rerender)\s*\([\s\S]*?\);\s*\}\s*/g, "\n");
37
- // Collapse any now-empty `else {}` blocks
38
- out = out.replace(/\}\s*else\s*\{\s*\}\s*/g, "}\n");
39
- return out;
40
- }
41
- // Helper: Strip anything between the compiled HMR id assignment and the export default
42
- // This nukes any remaining HMR scaffolding the compiler injects and avoids syntax drift
43
- function stripVueHmrTail(input) {
44
- let out = input;
45
- // Match from a line containing `.__hmrId = "...";` up to but not including the `export default` line
46
- out = out.replace(/\n[^\n]*__hmrId\s*=\s*['\"][^'\"]+['\"];[\s\S]*?(?=\n\s*export\s+default)/g, (match) => {
47
- // Keep just a single newline to preserve spacing before export
48
- return "\n";
49
- });
50
- // Also guard against any stray `typeof __VUE_HMR_RUNTIME__ !== "undefined" && ...` lines left over
51
- out = out.replace(/^[^\n]*typeof\s+__VUE_HMR_RUNTIME__[^\n]*$/gm, "");
52
- return out;
53
- }
54
- // Helper: remove named specifiers from import curly lists; drop import if empty
55
- function removeNamedImports(code, namesToRemove) {
56
- const re = /^(\s*import\s*\{)([^}]*)(\}\s*from\s*['"][^'"]+['"];?)/gm;
57
- return code.replace(re, (_m, p1, specList, p3) => {
58
- const remaining = specList
59
- .split(",")
60
- .map((s) => s.trim())
61
- .filter(Boolean)
62
- .filter((entry) => {
63
- // entry like: name OR name as alias
64
- const base = entry.split(/\s+as\s+/i)[0].trim();
65
- return !namesToRemove.includes(base);
66
- });
67
- if (!remaining.length)
68
- return "";
69
- return `${p1} ${remaining.join(", ")} ${p3}`;
70
- });
71
- }
72
- // Helper: inject const bindings from globals for the given names
73
- function injectGlobalsBindings(code, names) {
74
- if (!names.length)
75
- return code;
76
- const lines = names.map((n) => `const ${n} = globalThis.${n};`);
77
- return lines.join("\n") + "\n" + code;
78
- }
79
- export function hmrWebSocketVue() {
80
- let wss = null;
81
- // Map of project-relative vue path -> device-local hashed filename (e.g., sfc-<hash>.mjs)
82
- const sfcFileMap = new Map();
83
- let registrySent = false;
84
- return {
85
- name: "pure-vue-websocket",
86
- apply: "serve",
87
- configureServer(server) {
88
- const httpServer = server.httpServer;
89
- if (!httpServer)
90
- return;
91
- wss = new WebSocketServer({ noServer: true, path: "/ns-hmr" });
92
- httpServer.on("upgrade", (request, socket, head) => {
93
- const pathname = new URL(request.url || "", "http://localhost")
94
- .pathname;
95
- if (pathname === "/ns-hmr") {
96
- wss?.handleUpgrade(request, socket, head, (ws) => {
97
- wss?.emit("connection", ws, request);
98
- });
99
- }
100
- });
101
- wss.on("connection", async (ws) => {
102
- console.log("[pure-vue-ws] Client connected to /ns-hmr");
103
- ws.on("close", () => console.log("[pure-vue-ws] Client disconnected"));
104
- // Send SFC registry once on first client connection
105
- try {
106
- if (!registrySent) {
107
- await buildAndSendSfcRegistry(server, sfcFileMap, wss);
108
- registrySent = true;
109
- }
110
- }
111
- catch (e) {
112
- console.warn("[pure-vue-ws] Failed to send SFC registry:", e?.message || e);
113
- }
114
- });
115
- // console.log("[pure-vue-ws] WebSocket server configured on /ns-hmr");
116
- },
117
- async handleHotUpdate(ctx) {
118
- const { file, server } = ctx;
119
- if (!wss)
120
- return;
121
- // Fast-path: handle plain CSS updates via our single socket with a dedicated message
122
- if (file.endsWith(".css")) {
123
- try {
124
- const root = server.config.root || process.cwd();
125
- let rel = path.posix.join("/", path.posix
126
- .normalize(path.relative(root, file))
127
- .split(path.sep)
128
- .join("/"));
129
- if (!rel.startsWith("/"))
130
- rel = "/" + rel;
131
- const ts = Date.now();
132
- const isHttps = !!server.config.server?.https;
133
- let origin = null;
134
- const urls = server.resolvedUrls;
135
- if (urls?.local?.length) {
136
- origin = String(urls.local[0]).replace(/\/$/, "");
137
- }
138
- else {
139
- const httpServer = server.httpServer;
140
- const addr = httpServer?.address?.();
141
- const port = Number(server.config.server?.port || addr?.port || 5173);
142
- const hostCfg = server.config.server?.host;
143
- const host = typeof hostCfg === "string" && hostCfg !== "0.0.0.0" ? hostCfg : "127.0.0.1";
144
- origin = `${isHttps ? "https" : "http"}://${host}:${port}`;
145
- }
146
- const update = { type: "css-update", path: rel, acceptedPath: rel, timestamp: ts };
147
- const cssMsg = { type: "ns:css-updates", origin: origin || undefined, updates: [update] };
148
- wss.clients.forEach((client) => {
149
- if (client.readyState === client.OPEN)
150
- client.send(JSON.stringify(cssMsg));
151
- });
152
- return;
153
- }
154
- catch (e) {
155
- console.warn("[pure-vue-ws] Failed to process CSS HMR for", file, e?.message || e);
156
- return;
157
- }
158
- }
159
- if (!file.endsWith(".vue"))
160
- return;
161
- try {
162
- // Build a project-root relative path that matches Vite dev served path
163
- const root = server.config.root || process.cwd();
164
- let rel = path.posix.join("/", path.posix
165
- .normalize(path.relative(root, file))
166
- .split(path.sep)
167
- .join("/"));
168
- // Ensure leading slash
169
- if (!rel.startsWith("/"))
170
- rel = "/" + rel;
171
- // Construct template path the client can request from the dev server
172
- const templatePath = `${rel}?vue&type=template`;
173
- const id = path
174
- .basename(file)
175
- .replace(/\.vue$/i, "")
176
- .toLowerCase();
177
- // Compute a stable hashed HMR id similar to webpack/vue plugin behavior
178
- const hmrId = createHash("md5").update(rel).digest("hex").slice(0, 8);
179
- // Get absolute file path for reference (simulator can't access but useful for debugging)
180
- const absolutePath = `file://${path.resolve(file)}`;
181
- const ts = Date.now(); // Compute absolute origin for the dev server
182
- const isHttps = !!server.config.server?.https;
183
- // Prefer resolvedUrls from Vite when available
184
- let origin = null;
185
- const urls = server.resolvedUrls;
186
- if (urls?.local?.length) {
187
- // A local URL is like http://127.0.0.1:5173/
188
- origin = String(urls.local[0]).replace(/\/$/, "");
189
- }
190
- else {
191
- const httpServer = server.httpServer;
192
- const addr = httpServer?.address?.();
193
- const port = Number(server.config.server?.port || addr?.port || 5173);
194
- const hostCfg = server.config.server?.host;
195
- // Default to 127.0.0.1 for iOS simulator reliability
196
- const host = typeof hostCfg === "string" && hostCfg !== "0.0.0.0"
197
- ? hostCfg
198
- : "127.0.0.1";
199
- origin = `${isHttps ? "https" : "http"}://${host}:${port}`;
200
- }
201
- // Try to get transformed code to avoid HTTP fetch when possible
202
- let templateCode;
203
- try {
204
- const transformed = await server.transformRequest(templatePath);
205
- if (transformed?.code)
206
- templateCode = transformed.code;
207
- }
208
- catch (e) {
209
- // non-fatal
210
- }
211
- // Helper to embed minimal HMR runtime polyfills and rewrite imports to on-device vendor.mjs
212
- // Also rewrites any `.vue` sub-imports to local hashed SFC module filenames (./sfc-<hash>.mjs)
213
- function embedHmrRuntimeInModule(code, importerRel) {
214
- const vendorMjs = "~/vendor.mjs"; // NativeScript app-root alias
215
- const hmrPrelude = `
216
- // Embedded HMR Runtime for NativeScript Simulator (minimal)
217
- const createHotContext = (id) => ({
218
- on: (event, handler) => {
219
- if (!globalThis.__NS_HMR_HANDLERS__) globalThis.__NS_HMR_HANDLERS__ = new Map();
220
- if (!globalThis.__NS_HMR_HANDLERS__.has(id)) globalThis.__NS_HMR_HANDLERS__.set(id, []);
221
- globalThis.__NS_HMR_HANDLERS__.get(id).push({ event, handler });
222
- },
223
- accept: (handler) => {
224
- if (!globalThis.__NS_HMR_ACCEPTS__) globalThis.__NS_HMR_ACCEPTS__ = new Map();
225
- globalThis.__NS_HMR_ACCEPTS__.set(id, handler);
226
- }
227
- });
228
-
229
- if (typeof import.meta === 'undefined') {
230
- // Basic meta polyfill; hot is not used directly after rewrite
231
- // @ts-ignore
232
- globalThis.importMeta = { hot: null };
233
- } else if (!import.meta.hot) {
234
- // @ts-ignore
235
- import.meta.hot = null;
236
- }
237
-
238
- // Expose Vite-like hot context factory
239
- // eslint-disable-next-line @typescript-eslint/naming-convention
240
- const __vite__createHotContext = createHotContext;
241
-
242
- // Ensure __VUE_HMR_RUNTIME__ exists (client installs a real one)
243
- if (typeof __VUE_HMR_RUNTIME__ === 'undefined') {
244
- // @ts-ignore
245
- globalThis.__VUE_HMR_RUNTIME__ = {
246
- createRecord: () => true,
247
- reload: () => {},
248
- rerender: () => {},
249
- };
250
- }
251
-
252
- // Local copy of _export_sfc helper used by Vue SFCs
253
- function _export_sfc(sfc, props) {
254
- if (props && Array.isArray(props)) {
255
- for (const [key, value] of props) {
256
- if (key === 'render') sfc.render = value;
257
- else sfc[key] = value;
258
- }
259
- }
260
- return sfc;
261
- }
262
-
263
- // Ensure _defineComponent is available by binding from globalThis (set by the HMR client)
264
- // Fallback to identity to keep runtime working even if not provided
265
- const _defineComponent = globalThis._defineComponent || globalThis.defineComponent || ((o) => o);
266
- `;
267
- let enhanced = String(code);
268
- // Remove any @vite/client imports
269
- enhanced = enhanced.replace(/^.*import.*@vite\/client.*$/gm, "// Removed @vite/client import for simulator compatibility");
270
- // Remove import.meta.hot assignments and handlers (we supply our own minimal polyfill)
271
- enhanced = enhanced.replace(/import\.meta\.hot\s*=\s*__vite__createHotContext\([^)]*\);?/g, "// Removed import.meta.hot assignment - using embedded polyfill");
272
- // Remove entire if (import.meta.hot) { ... } blocks to avoid trailing braces (balanced)
273
- enhanced = stripImportMetaHotBlocks(enhanced);
274
- // Remove direct handler registrations like: import.meta.hot.on(..., () => { ... }) and accept((mod) => {...})
275
- // Robustly strip the entire call including nested arrow function bodies until the matching ');'
276
- enhanced = enhanced.replace(/import\.meta\.hot\.(?:on|accept)\s*\([\s\S]*?\);\s*/g, "");
277
- // Remove common Vue HMR runtime statements and orphan else tails
278
- enhanced = stripVueHmrRuntimeNoise(enhanced);
279
- // Aggressively strip compiler HMR tail between __hmrId and export default
280
- enhanced = stripVueHmrTail(enhanced);
281
- // Drop virtual sub-imports (template/style blocks)
282
- enhanced = enhanced.replace(/import\s+[^"']*\s*from\s*["'][^"']*[?&]vue&type=[^"']*["'];?/g, "// Removed virtual vue sub-import for simulator compatibility");
283
- enhanced = enhanced.replace(/import\s*["'][^"']*[?&]vue&type=style[^"']*["'];?/g, "// Removed CSS virtual import");
284
- // Use the already-loaded on-device vendor bundle for framework imports
285
- enhanced = enhanced.replace(/from\s*["']@vue\/runtime-core["']/g, `from "${vendorMjs}"`);
286
- enhanced = enhanced.replace(/from\s*["']vue["']/g, `from "${vendorMjs}"`);
287
- enhanced = enhanced.replace(/from\s*["']nativescript-vue["']/g, `from "${vendorMjs}"`);
288
- // Also handle Vite-deps resolved paths just in case
289
- enhanced = enhanced.replace(/from\s*["']\/node_modules\/\.vite\/deps\/nativescript-vue.*?["']/g, `from "${vendorMjs}"`);
290
- // Remove export helper import (we provide _export_sfc above)
291
- enhanced = enhanced.replace(/^.*import\s+\{\s*?_export_sfc\s*?\}.*$/gm, "// Removed _export_sfc import - using embedded helper");
292
- // Remove remaining virtual id imports
293
- enhanced = enhanced.replace(/^.*import.*\/@id\/__x00__.*$/gm, "// Removed virtual import id");
294
- // Rewrite .vue sub-component imports to device-local hashed SFC files (strip query if present)
295
- enhanced = enhanced.replace(/(import\s+[^;]*?from\s*["'])([^"'?]*\.vue)(?:\?[^"']*)?(["'])/g, (_m, p1, spec, p3) => {
296
- const importerDir = path.posix.dirname(importerRel);
297
- let key = spec;
298
- if (!key.startsWith("/")) {
299
- // Resolve relative spec to project-absolute (leading slash)
300
- key = path.posix.normalize(path.posix.join(importerDir, spec));
301
- if (!key.startsWith("/"))
302
- key = "/" + key;
303
- }
304
- const fileName = sfcFileMap.get(key);
305
- if (!fileName)
306
- return `${p1}${spec}${p3}`;
307
- return `${p1}./${fileName}${p3}`;
308
- });
309
- // Remove imports of core runtime helpers (we'll map to globals) and inject underscore aliases
310
- const coreFns = [
311
- "resolveComponent",
312
- "createVNode",
313
- "createTextVNode",
314
- "withCtx",
315
- "openBlock",
316
- "createBlock",
317
- "createElementVNode",
318
- "createElementBlock",
319
- ];
320
- enhanced = removeNamedImports(enhanced, coreFns);
321
- const usesUnderscoreHelpers = /\b_(resolveComponent|createVNode|createTextVNode|withCtx|openBlock|createBlock|createElementVNode|createElementBlock)\b/.test(enhanced);
322
- if (usesUnderscoreHelpers) {
323
- const globalsPrelude = [
324
- "const _resolveComponent = globalThis._resolveComponent || globalThis.resolveComponent;",
325
- "const _createVNode = globalThis._createVNode || globalThis.createVNode;",
326
- "const _createTextVNode = globalThis._createTextVNode || globalThis.createTextVNode;",
327
- "const _withCtx = globalThis._withCtx || globalThis.withCtx;",
328
- "const _openBlock = globalThis._openBlock || globalThis.openBlock;",
329
- "const _createBlock = globalThis._createBlock || globalThis.createBlock;",
330
- "const _createElementVNode = globalThis._createElementVNode || globalThis.createElementVNode || globalThis.createVNode;",
331
- "const _createElementBlock = globalThis._createElementBlock || globalThis.createElementBlock || globalThis.createBlock;",
332
- ].join("\n");
333
- enhanced = globalsPrelude + "\n" + enhanced;
334
- }
335
- // Remove lingering imports of reactive helpers and bind from globals if used
336
- const reactiveFns = ["ref", "computed", "onMounted", "onUnmounted"];
337
- enhanced = removeNamedImports(enhanced, reactiveFns);
338
- // Remove any residual import lines for these names
339
- enhanced = enhanced.replace(/^.*import\s*\{[^}]*\b(ref|computed|onMounted|onUnmounted)\b[^}]*\}\s*from\s*["'][^"']+["'];?\s*$/gm, "");
340
- const reactiveToBind = [];
341
- if (/\bref\b/.test(enhanced))
342
- reactiveToBind.push("ref");
343
- if (/\bcomputed\b/.test(enhanced))
344
- reactiveToBind.push("computed");
345
- if (/\bonMounted\b/.test(enhanced))
346
- reactiveToBind.push("onMounted");
347
- if (/\bonUnmounted\b/.test(enhanced))
348
- reactiveToBind.push("onUnmounted");
349
- if (reactiveToBind.length) {
350
- enhanced = injectGlobalsBindings(enhanced, reactiveToBind);
351
- }
352
- // If code uses $navigate helpers, import only missing ones from vendor to avoid duplicates
353
- const usesNavTo = /\$navigateTo\b/.test(enhanced);
354
- const usesNavBack = /\$navigateBack\b/.test(enhanced);
355
- // Remove any named imports of these helpers from any source to avoid missing exports
356
- if (usesNavTo || usesNavBack) {
357
- const names = [];
358
- if (usesNavTo)
359
- names.push("$navigateTo");
360
- if (usesNavBack)
361
- names.push("$navigateBack");
362
- enhanced = removeNamedImports(enhanced, names);
363
- // Inject globals-based bindings
364
- enhanced = injectGlobalsBindings(enhanced, names);
365
- }
366
- // Finally, prepend the minimal HMR prelude
367
- return hmrPrelude + "\n" + enhanced;
368
- }
369
- const msg = {
370
- type: "ns:vue-template-url",
371
- id,
372
- hmrId,
373
- path: rel,
374
- absolutePath,
375
- templatePath,
376
- templateUrl: origin ? `${origin}${templatePath}` : undefined,
377
- origin: origin || undefined,
378
- code: templateCode,
379
- ts,
380
- };
381
- // APPROACH: Generate new .mjs module for dynamic import (preserves context, uses proper module system)
382
- try {
383
- const source = readFileSync(file, "utf8");
384
- // Transform the entire Vue SFC to a module via Vite's pipeline
385
- const transformResult = await server.transformRequest(rel);
386
- if (transformResult?.code) {
387
- // Before sending dynamic module, ensure any newly imported .vue deps are registered
388
- try {
389
- const importerDir = path.posix.dirname(rel);
390
- const depSpecs = [];
391
- const depRe = /(import\s+[^;]*?from\s*["'])([^"'?]*\.vue)(?:\?[^"']*)?["']/g;
392
- let mm;
393
- while ((mm = depRe.exec(transformResult.code)) !== null) {
394
- const spec = mm[2];
395
- let key = spec;
396
- if (!key.startsWith("/")) {
397
- key = path.posix.normalize(path.posix.join(importerDir, spec));
398
- if (!key.startsWith("/"))
399
- key = "/" + key;
400
- }
401
- depSpecs.push(key);
402
- }
403
- for (const depRel of depSpecs) {
404
- if (!sfcFileMap.has(depRel)) {
405
- const depResult = await server.transformRequest(depRel);
406
- if (depResult?.code) {
407
- const sfcHash = createHash("md5").update(depRel).digest("hex").slice(0, 8);
408
- const depFileName = `sfc-${sfcHash}.mjs`;
409
- sfcFileMap.set(depRel, depFileName);
410
- // Clean and rewrite similar to registry path
411
- let depCode = depResult.code
412
- .replace(/^.*import.*@vite\/client.*$/gm, "")
413
- .replace(/import\.meta\.hot\s*=\s*__vite__createHotContext\([^)]*\);?/g, "");
414
- depCode = stripImportMetaHotBlocks(depCode);
415
- depCode = depCode.replace(/import\.meta\.hot\.(?:on|accept)\s*\([\s\S]*?\);\s*/g, "");
416
- depCode = depCode
417
- .replace(/import\s+[^"']*\s*from\s*["'][^"']*[?&]vue&type=[^"']*["'];?/g, "")
418
- .replace(/import\s*["'][^"']*[?&]vue&type=style[^"']*["'];?/g, "")
419
- .replace(/from\s*["']@vue\/runtime-core["']/g, 'from "~/vendor.mjs"')
420
- .replace(/from\s*["']vue["']/g, 'from "~/vendor.mjs"')
421
- .replace(/from\s*["']nativescript-vue["']/g, 'from "~/vendor.mjs"')
422
- .replace(/from\s*["']\/node_modules\/\.vite\/deps\/nativescript-vue.*?["']/g, 'from "~/vendor.mjs"')
423
- .replace(/^.*import\s+\{\s*?_export_sfc\s*?\}.*$/gm, "")
424
- .replace(/^.*import.*\/@id\/__x00__.*$/gm, "");
425
- depCode = stripVueHmrRuntimeNoise(depCode);
426
- depCode = stripVueHmrTail(depCode);
427
- // Export helper and _defineComponent binding
428
- const exportHelperPrelude = `function _export_sfc(sfc, props){if(props&&Array.isArray(props)){for(const [k,v] of props){if(k==='render')sfc.render=v;else sfc[k]=v;}}return sfc;}`;
429
- if (/\b_defineComponent\b/.test(depCode) && !/\bconst\s+_defineComponent\b/.test(depCode)) {
430
- depCode = `const _defineComponent = globalThis._defineComponent || globalThis.defineComponent || ((o) => o);\n` + depCode;
431
- }
432
- // Remove helper imports and inject globals like registry path
433
- const coreFns = [
434
- "resolveComponent",
435
- "createVNode",
436
- "createTextVNode",
437
- "withCtx",
438
- "openBlock",
439
- "createBlock",
440
- "createElementVNode",
441
- "createElementBlock",
442
- ];
443
- depCode = removeNamedImports(depCode, coreFns);
444
- if (/\b_(resolveComponent|createVNode|createTextVNode|withCtx|openBlock|createBlock|createElementVNode|createElementBlock)\b/.test(depCode)) {
445
- const globalsPrelude = [
446
- "const _resolveComponent = globalThis._resolveComponent || globalThis.resolveComponent;",
447
- "const _createVNode = globalThis._createVNode || globalThis.createVNode;",
448
- "const _createTextVNode = globalThis._createTextVNode || globalThis.createTextVNode;",
449
- "const _withCtx = globalThis._withCtx || globalThis.withCtx;",
450
- "const _openBlock = globalThis._openBlock || globalThis.openBlock;",
451
- "const _createBlock = globalThis._createBlock || globalThis.createBlock;",
452
- "const _createElementVNode = globalThis._createElementVNode || globalThis.createElementVNode || globalThis.createVNode;",
453
- "const _createElementBlock = globalThis._createElementBlock || globalThis.createElementBlock || globalThis.createBlock;",
454
- ].join("\n");
455
- depCode = globalsPrelude + "\n" + depCode;
456
- }
457
- const reactiveFns = ["ref", "computed", "onMounted", "onUnmounted"];
458
- depCode = removeNamedImports(depCode, reactiveFns);
459
- depCode = depCode.replace(/^.*import\s*\{[^}]*\b(ref|computed|onMounted|onUnmounted)\b[^}]*\}\s*from\s*["'][^"']+["'];?\s*$/gm, "");
460
- const reactiveToBind = [];
461
- if (/\bref\b/.test(depCode))
462
- reactiveToBind.push("ref");
463
- if (/\bcomputed\b/.test(depCode))
464
- reactiveToBind.push("computed");
465
- if (/\bonMounted\b/.test(depCode))
466
- reactiveToBind.push("onMounted");
467
- if (/\bonUnmounted\b/.test(depCode))
468
- reactiveToBind.push("onUnmounted");
469
- if (reactiveToBind.length)
470
- depCode = injectGlobalsBindings(depCode, reactiveToBind);
471
- // Nav helpers
472
- if (/\$navigate(To|Back)\b/.test(depCode)) {
473
- depCode = removeNamedImports(depCode, ["$navigateTo", "$navigateBack"]);
474
- depCode = injectGlobalsBindings(depCode, ["$navigateTo", "$navigateBack"]);
475
- }
476
- depCode = exportHelperPrelude + "\n" + depCode;
477
- const depUpdateMsg = {
478
- type: "ns:vue-sfc-registry-update",
479
- path: depRel,
480
- fileName: depFileName,
481
- code: depCode,
482
- ts,
483
- };
484
- wss.clients.forEach((client) => {
485
- if (client.readyState === client.OPEN)
486
- client.send(JSON.stringify(depUpdateMsg));
487
- });
488
- console.log(`[pure-vue-websocket] Registered new SFC dep ${depRel} -> ${depFileName}`);
489
- }
490
- }
491
- }
492
- }
493
- catch (e) {
494
- console.warn("[pure-vue-websocket] Failed ensuring SFC deps:", e?.message || e);
495
- }
496
- // Generate unique module name for this HMR update
497
- const moduleId = `hmr-${id}-${ts}`;
498
- const modulePath = `/${rel}?hmr=${ts}`;
499
- // Send dynamic module reload instruction with embedded HMR runtime
500
- const moduleMsg = {
501
- type: "ns:vue-dynamic-module",
502
- id,
503
- hmrId,
504
- path: rel,
505
- absolutePath,
506
- moduleId,
507
- modulePath,
508
- moduleCode: embedHmrRuntimeInModule(transformResult.code, rel), // Apply HMR runtime embedding here!
509
- originalSource: source,
510
- ts,
511
- };
512
- wss.clients.forEach((client) => {
513
- if (client.readyState === client.OPEN)
514
- client.send(JSON.stringify(moduleMsg));
515
- });
516
- console.log(`[pure-vue-websocket] Sent dynamic module for ${rel}: ${modulePath}`);
517
- // Additionally, if app-level CSS exists (e.g., /src/app.css), emit CSS update so Tailwind class changes apply
518
- try {
519
- const appCssFs = path.resolve(root, "src/app.css");
520
- let cssExists = false;
521
- try {
522
- const st = statSync(appCssFs);
523
- cssExists = st?.isFile?.() || false;
524
- }
525
- catch { }
526
- if (cssExists) {
527
- const cssRel = "/src/app.css";
528
- const update = {
529
- type: "css-update",
530
- path: cssRel,
531
- acceptedPath: cssRel,
532
- timestamp: ts,
533
- };
534
- const cssMsg = {
535
- type: "ns:css-updates",
536
- origin: origin || undefined,
537
- updates: [update],
538
- };
539
- wss.clients.forEach((client) => {
540
- if (client.readyState === client.OPEN)
541
- client.send(JSON.stringify(cssMsg));
542
- });
543
- }
544
- }
545
- catch (e) {
546
- console.warn("[pure-vue-websocket] Failed emitting Tailwind CSS update:", e?.message || e);
547
- }
548
- // Also set the transformed code for template URL fallback
549
- templateCode = transformResult.code;
550
- // Additionally, update (or add) the corresponding registry SFC module on device
551
- try {
552
- const sfcHash = createHash("md5")
553
- .update(rel)
554
- .digest("hex")
555
- .slice(0, 8);
556
- const fileName = sfcFileMap.get(rel) || `sfc-${sfcHash}.mjs`;
557
- sfcFileMap.set(rel, fileName);
558
- // Apply similar transforms as in buildAndSendSfcRegistry
559
- let sfcCode = transformResult.code
560
- // Remove @vite/client
561
- .replace(/^.*import.*@vite\/client.*$/gm, "")
562
- // Remove import.meta.hot assignment
563
- .replace(/import\.meta\.hot\s*=\s*__vite__createHotContext\([^)]*\);?/g, "");
564
- sfcCode = stripImportMetaHotBlocks(sfcCode);
565
- sfcCode = sfcCode.replace(/import\.meta\.hot\.(?:on|accept)\s*\([\s\S]*?\);\s*/g, "");
566
- // Remove virtual blocks
567
- sfcCode = sfcCode
568
- .replace(/import\s+[^"']*\s*from\s*["'][^"']*[?&]vue&type=[^"']*["'];?/g, "")
569
- .replace(/import\s*["'][^"']*[?&]vue&type=style[^"']*["'];?/g, "")
570
- // Vendor rewrites
571
- .replace(/from\s*["']@vue\/runtime-core["']/g, 'from "~/vendor.mjs"')
572
- .replace(/from\s*["']vue["']/g, 'from "~/vendor.mjs"')
573
- .replace(/from\s*["']nativescript-vue["']/g, 'from "~/vendor.mjs"')
574
- .replace(/from\s*["']\/node_modules\/\.vite\/deps\/nativescript-vue.*?["']/g, 'from "~/vendor.mjs"')
575
- // Remove export helper import (we inject our own)
576
- .replace(/^.*import\s+\{\s*?_export_sfc\s*?\}.*$/gm, "")
577
- // Remove remaining virtual id imports
578
- .replace(/^.*import.*\/@id\/__x00__.*$/gm, "");
579
- // Remove Vue HMR runtime noise and orphan else
580
- sfcCode = stripVueHmrRuntimeNoise(sfcCode);
581
- sfcCode = stripVueHmrTail(sfcCode);
582
- // Rewrite internal .vue imports to hashed files (strip any query)
583
- const importerDir = path.posix.dirname(rel);
584
- sfcCode = sfcCode.replace(/(import\s+[^;]*?from\s*["'])([^"'?]*\.vue)(?:\?[^"']*)?(["'])/g, (_m, p1, spec, p3) => {
585
- let key = spec;
586
- if (!key.startsWith("/")) {
587
- key = path.posix.normalize(path.posix.join(importerDir, spec));
588
- if (!key.startsWith("/"))
589
- key = "/" + key;
590
- }
591
- const depFile = sfcFileMap.get(key);
592
- if (!depFile)
593
- return `${p1}${spec}${p3}`;
594
- return `${p1}./${depFile}${p3}`;
595
- });
596
- const exportHelperPrelude = `function _export_sfc(sfc, props){if(props&&Array.isArray(props)){for(const [k,v] of props){if(k==='render')sfc.render=v;else sfc[k]=v;}}return sfc;}`;
597
- // Ensure _defineComponent available via globals
598
- if (/\b_defineComponent\b/.test(sfcCode) &&
599
- !/\bconst\s+_defineComponent\b/.test(sfcCode)) {
600
- sfcCode =
601
- `const _defineComponent = globalThis._defineComponent || globalThis.defineComponent || ((o) => o);\n` +
602
- sfcCode;
603
- }
604
- // Remove core runtime helper imports and map to globals
605
- const coreFns = [
606
- "resolveComponent",
607
- "createVNode",
608
- "createTextVNode",
609
- "withCtx",
610
- "openBlock",
611
- "createBlock",
612
- "createElementVNode",
613
- "createElementBlock",
614
- ];
615
- sfcCode = removeNamedImports(sfcCode, coreFns);
616
- if (/\b_(resolveComponent|createVNode|createTextVNode|withCtx|openBlock|createBlock|createElementVNode|createElementBlock)\b/.test(sfcCode)) {
617
- const globalsPrelude = [
618
- "const _resolveComponent = globalThis._resolveComponent || globalThis.resolveComponent;",
619
- "const _createVNode = globalThis._createVNode || globalThis.createVNode;",
620
- "const _createTextVNode = globalThis._createTextVNode || globalThis.createTextVNode;",
621
- "const _withCtx = globalThis._withCtx || globalThis.withCtx;",
622
- "const _openBlock = globalThis._openBlock || globalThis.openBlock;",
623
- "const _createBlock = globalThis._createBlock || globalThis.createBlock;",
624
- "const _createElementVNode = globalThis._createElementVNode || globalThis.createElementVNode || globalThis.createVNode;",
625
- "const _createElementBlock = globalThis._createElementBlock || globalThis.createElementBlock || globalThis.createBlock;",
626
- ].join("\n");
627
- sfcCode = globalsPrelude + "\n" + sfcCode;
628
- }
629
- // Remove lingering reactive helper imports and bind from globals if used
630
- const reactiveFns = ["ref", "computed", "onMounted", "onUnmounted"];
631
- sfcCode = removeNamedImports(sfcCode, reactiveFns);
632
- sfcCode = sfcCode.replace(/^.*import\s*\{[^}]*\b(ref|computed|onMounted|onUnmounted)\b[^}]*\}\s*from\s*["'][^"']+["'];?\s*$/gm, "");
633
- const reactiveToBind = [];
634
- if (/\bref\b/.test(sfcCode))
635
- reactiveToBind.push("ref");
636
- if (/\bcomputed\b/.test(sfcCode))
637
- reactiveToBind.push("computed");
638
- if (/\bonMounted\b/.test(sfcCode))
639
- reactiveToBind.push("onMounted");
640
- if (/\bonUnmounted\b/.test(sfcCode))
641
- reactiveToBind.push("onUnmounted");
642
- if (reactiveToBind.length) {
643
- sfcCode = injectGlobalsBindings(sfcCode, reactiveToBind);
644
- }
645
- // Handle navigation helpers via globals
646
- const usesNavTo = /\$navigateTo\b/.test(sfcCode);
647
- const usesNavBack = /\$navigateBack\b/.test(sfcCode);
648
- if (usesNavTo || usesNavBack) {
649
- const names = [];
650
- if (usesNavTo)
651
- names.push("$navigateTo");
652
- if (usesNavBack)
653
- names.push("$navigateBack");
654
- sfcCode = removeNamedImports(sfcCode, names);
655
- sfcCode = injectGlobalsBindings(sfcCode, names);
656
- }
657
- sfcCode = exportHelperPrelude + "\n" + sfcCode;
658
- const updateMsg = {
659
- type: "ns:vue-sfc-registry-update",
660
- path: rel,
661
- fileName,
662
- code: sfcCode,
663
- ts,
664
- };
665
- wss.clients.forEach((client) => {
666
- if (client.readyState === client.OPEN)
667
- client.send(JSON.stringify(updateMsg));
668
- });
669
- }
670
- catch (e) {
671
- console.warn("[pure-vue-websocket] Failed to send SFC registry update for", rel, e);
672
- }
673
- }
674
- // FALLBACK: Send raw SFC source for on-device compilation if needed
675
- const sfcMsg = {
676
- type: "ns:vue-sfc-source",
677
- id,
678
- hmrId,
679
- path: rel,
680
- absolutePath,
681
- source,
682
- ts,
683
- };
684
- wss.clients.forEach((client) => {
685
- if (client.readyState === client.OPEN)
686
- client.send(JSON.stringify(sfcMsg));
687
- });
688
- }
689
- catch (e) {
690
- console.warn("[pure-vue-websocket] Failed to generate dynamic module:", e);
691
- }
692
- // SECOND: Send transformed template as fallback
693
- console.log("[pure-vue-ws] template-url (fallback)", {
694
- path: rel,
695
- hmrId,
696
- templateUrl: msg.templateUrl,
697
- hasCode: !!templateCode,
698
- });
699
- wss.clients.forEach((client) => {
700
- if (client.readyState === client.OPEN)
701
- client.send(JSON.stringify(msg));
702
- });
703
- // Note: keeping template-url as fallback for debugging/alternative paths
704
- }
705
- catch (e) {
706
- console.warn("[pure-vue-ws] Failed to process HMR for", file, e?.message || e);
707
- }
708
- return ctx.modules;
709
- },
710
- };
711
- }
712
- // Recursively walk directories to find .vue files (excluding common large/irrelevant folders)
713
- function walkVueFiles(dir, acc, root) {
714
- for (const name of readdirSync(dir)) {
715
- const full = path.join(dir, name);
716
- const rel = path.relative(root, full);
717
- // skip
718
- if (name === "node_modules" ||
719
- name === ".git" ||
720
- name === "dist" ||
721
- name === "platforms" ||
722
- name === "App_Resources") {
723
- continue;
724
- }
725
- const st = statSync(full);
726
- if (st.isDirectory()) {
727
- walkVueFiles(full, acc, root);
728
- }
729
- else if (st.isFile() && name.endsWith(".vue")) {
730
- acc.push("/" + rel.split(path.sep).join("/"));
731
- }
732
- }
733
- }
734
- async function buildAndSendSfcRegistry(server, sfcFileMap, wss) {
735
- const root = server.config.root || process.cwd();
736
- const vueFiles = [];
737
- walkVueFiles(root, vueFiles, root);
738
- if (!vueFiles.length)
739
- return;
740
- // Prepare entries
741
- const entries = [];
742
- for (const rel of vueFiles) {
743
- // Generate stable hashed filename for device-local storage
744
- const hash = createHash("md5").update(rel).digest("hex").slice(0, 8);
745
- const fileName = `sfc-${hash}.mjs`;
746
- sfcFileMap.set(rel, fileName);
747
- }
748
- // Transform all and rewrite their own .vue imports to hashed names and vendor path
749
- for (const rel of vueFiles) {
750
- try {
751
- const result = await server.transformRequest(rel);
752
- if (!result?.code)
753
- continue;
754
- let code = result.code;
755
- // Apply same vendor/virtual/remove-hot rewrites as dynamic modules, but no HMR prelude
756
- code = code
757
- // Remove @vite/client
758
- .replace(/^.*import.*@vite\/client.*$/gm, "// Removed @vite/client import")
759
- // Remove import.meta.hot assignment and blocks
760
- .replace(/import\.meta\.hot\s*=\s*__vite__createHotContext\([^)]*\);?/g, "");
761
- code = stripImportMetaHotBlocks(code);
762
- code = code.replace(/import\.meta\.hot\.(?:on|accept)\s*\([\s\S]*?\);\s*/g, "");
763
- code = code
764
- // Remove virtual blocks
765
- .replace(/import\s+[^"']*\s*from\s*["'][^"']*[?&]vue&type=[^"']*["'];?/g, "// Removed virtual vue import")
766
- .replace(/import\s*["'][^"']*[?&]vue&type=style[^"']*["'];?/g, "// Removed CSS virtual import")
767
- // Vendor rewrites
768
- .replace(/from\s*["']@vue\/runtime-core["']/g, 'from "~/vendor.mjs"')
769
- .replace(/from\s*["']vue["']/g, 'from "~/vendor.mjs"')
770
- .replace(/from\s*["']nativescript-vue["']/g, 'from "~/vendor.mjs"')
771
- .replace(/from\s*["']\/node_modules\/\.vite\/deps\/nativescript-vue.*?["']/g, 'from "~/vendor.mjs"')
772
- // Remove export helper import (we provide one in registry output below)
773
- .replace(/^.*import\s+\{\s*?_export_sfc\s*?\}.*$/gm, "")
774
- // Remove remaining virtual id imports
775
- .replace(/^.*import.*\/@id\/__x00__.*$/gm, "");
776
- code = stripVueHmrRuntimeNoise(code);
777
- code = stripVueHmrTail(code);
778
- // Inject a minimal _export_sfc helper at top to satisfy compiled SFC default export pattern
779
- const exportHelperPrelude = `function _export_sfc(sfc, props){if(props&&Array.isArray(props)){for(const [k,v] of props){if(k==='render')sfc.render=v;else sfc[k]=v;}}return sfc;}`;
780
- code = exportHelperPrelude + "\n" + code;
781
- // Rewrite its own .vue imports to hashed SFC local files, resolving relative paths against importer (strip any query)
782
- const importerDir = path.posix.dirname(rel);
783
- code = code.replace(/(import\s+[^;]*?from\s*["'])([^"'?]*\.vue)(?:\?[^"']*)?(["'])/g, (_m, p1, importPath, p3) => {
784
- let key = importPath;
785
- if (!key.startsWith("/")) {
786
- key = path.posix.normalize(path.posix.join(importerDir, importPath));
787
- if (!key.startsWith("/"))
788
- key = "/" + key;
789
- }
790
- const fileName = sfcFileMap.get(key);
791
- if (!fileName)
792
- return `${p1}${importPath}${p3}`;
793
- return `${p1}./${fileName}${p3}`;
794
- });
795
- // Ensure _defineComponent is available via globals if referenced
796
- if (/\b_defineComponent\b/.test(code) &&
797
- !/\bconst\s+_defineComponent\b/.test(code)) {
798
- code =
799
- `const _defineComponent = globalThis._defineComponent || globalThis.defineComponent || ((o) => o);\n` +
800
- code;
801
- }
802
- // Remove imports for core runtime helper functions and map to globals
803
- const coreFns = [
804
- "resolveComponent",
805
- "createVNode",
806
- "createTextVNode",
807
- "withCtx",
808
- "openBlock",
809
- "createBlock",
810
- "createElementVNode",
811
- "createElementBlock",
812
- ];
813
- code = removeNamedImports(code, coreFns);
814
- if (/\b_(resolveComponent|createVNode|createTextVNode|withCtx|openBlock|createBlock|createElementVNode|createElementBlock)\b/.test(code)) {
815
- const globalsPrelude = [
816
- "const _resolveComponent = globalThis._resolveComponent || globalThis.resolveComponent;",
817
- "const _createVNode = globalThis._createVNode || globalThis.createVNode;",
818
- "const _createTextVNode = globalThis._createTextVNode || globalThis.createTextVNode;",
819
- "const _withCtx = globalThis._withCtx || globalThis.withCtx;",
820
- "const _openBlock = globalThis._openBlock || globalThis.openBlock;",
821
- "const _createBlock = globalThis._createBlock || globalThis.createBlock;",
822
- "const _createElementVNode = globalThis._createElementVNode || globalThis.createElementVNode || globalThis.createVNode;",
823
- "const _createElementBlock = globalThis._createElementBlock || globalThis.createElementBlock || globalThis.createBlock;",
824
- ].join("\n");
825
- code = globalsPrelude + "\n" + code;
826
- }
827
- // Remove lingering imports of reactive helpers and bind from globals if used
828
- const reactiveFns = ["ref", "computed", "onMounted", "onUnmounted"];
829
- code = removeNamedImports(code, reactiveFns);
830
- code = code.replace(/^.*import\s*\{[^}]*\b(ref|computed|onMounted|onUnmounted)\b[^}]*\}\s*from\s*["'][^"']+["'];?\s*$/gm, "");
831
- const reactiveToBind = [];
832
- if (/\bref\b/.test(code))
833
- reactiveToBind.push("ref");
834
- if (/\bcomputed\b/.test(code))
835
- reactiveToBind.push("computed");
836
- if (/\bonMounted\b/.test(code))
837
- reactiveToBind.push("onMounted");
838
- if (/\bonUnmounted\b/.test(code))
839
- reactiveToBind.push("onUnmounted");
840
- if (reactiveToBind.length) {
841
- code = injectGlobalsBindings(code, reactiveToBind);
842
- }
843
- // Handle navigation helpers using globals instead of vendor exports
844
- const usesNavTo = /\$navigateTo\b/.test(code);
845
- const usesNavBack = /\$navigateBack\b/.test(code);
846
- if (usesNavTo || usesNavBack) {
847
- const names = [];
848
- if (usesNavTo)
849
- names.push("$navigateTo");
850
- if (usesNavBack)
851
- names.push("$navigateBack");
852
- code = removeNamedImports(code, names);
853
- code = injectGlobalsBindings(code, names);
854
- }
855
- const hmrId = createHash("md5").update(rel).digest("hex").slice(0, 8);
856
- const fileName = sfcFileMap.get(rel);
857
- entries.push({ path: rel, fileName, hmrId, code });
858
- }
859
- catch (e) {
860
- console.warn("[pure-vue-ws] Failed transforming", rel, e?.message || e);
861
- }
862
- }
863
- if (!entries.length)
864
- return;
865
- const msg = {
866
- type: "ns:vue-sfc-registry",
867
- entries,
868
- ts: Date.now(),
869
- };
870
- wss.clients.forEach((client) => {
871
- if (client.readyState === client.OPEN)
872
- client.send(JSON.stringify(msg));
873
- });
874
- console.log(`[pure-vue-websocket] Sent SFC registry with ${entries.length} entries`);
875
- }