@nativescript/vite 8.0.0-alpha.10 → 8.0.0-alpha.12
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/configuration/angular.d.ts +34 -1
- package/configuration/angular.js +165 -2
- package/configuration/angular.js.map +1 -1
- package/configuration/solid.js +20 -0
- package/configuration/solid.js.map +1 -1
- package/helpers/angular/inject-hmr-vite-ignore.d.ts +75 -0
- package/helpers/angular/inject-hmr-vite-ignore.js +288 -0
- package/helpers/angular/inject-hmr-vite-ignore.js.map +1 -0
- package/helpers/main-entry.js +10 -0
- package/helpers/main-entry.js.map +1 -1
- package/helpers/resolver.js +9 -1
- package/helpers/resolver.js.map +1 -1
- package/hmr/client/index.js +275 -7
- package/hmr/client/index.js.map +1 -1
- package/hmr/client/utils.js +113 -33
- package/hmr/client/utils.js.map +1 -1
- package/hmr/server/websocket.js +553 -13
- package/hmr/server/websocket.js.map +1 -1
- package/package.json +1 -1
package/hmr/client/index.js
CHANGED
|
@@ -101,6 +101,62 @@ function hideConnectionOverlay() {
|
|
|
101
101
|
}
|
|
102
102
|
catch { }
|
|
103
103
|
}
|
|
104
|
+
function setUpdateOverlayStage(stage, info) {
|
|
105
|
+
try {
|
|
106
|
+
const api = getHmrOverlayApi();
|
|
107
|
+
if (api && typeof api.setUpdateStage === 'function') {
|
|
108
|
+
api.setUpdateStage(stage, info);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch { }
|
|
112
|
+
}
|
|
113
|
+
// Store the listener registry on globalThis (rather than in a module-private
|
|
114
|
+
// closure) because in NativeScript the HMR client module and the user app
|
|
115
|
+
// modules can resolve to different module instances depending on how the
|
|
116
|
+
// dev runtime loads them (HTTP client URL vs. the bundled vendor realm).
|
|
117
|
+
// A module-local Set would not be shared across instances; the global one
|
|
118
|
+
// is.
|
|
119
|
+
function getNsSolidHmrListenerSet() {
|
|
120
|
+
const g = globalThis;
|
|
121
|
+
let set = g.__ns_solid_hmr_listener_set;
|
|
122
|
+
if (!set) {
|
|
123
|
+
set = new Set();
|
|
124
|
+
g.__ns_solid_hmr_listener_set = set;
|
|
125
|
+
}
|
|
126
|
+
return set;
|
|
127
|
+
}
|
|
128
|
+
function nsSolidHmrSubscribe(fn) {
|
|
129
|
+
const listeners = getNsSolidHmrListenerSet();
|
|
130
|
+
listeners.add(fn);
|
|
131
|
+
if (VERBOSE)
|
|
132
|
+
console.log('[hmr][solid] subscribe — listeners=', listeners.size);
|
|
133
|
+
return () => listeners.delete(fn);
|
|
134
|
+
}
|
|
135
|
+
function nsSolidHmrEmit(ev) {
|
|
136
|
+
const listeners = getNsSolidHmrListenerSet();
|
|
137
|
+
if (VERBOSE)
|
|
138
|
+
console.log('[hmr][solid] emit listeners=', listeners.size, 'changedFiles=', ev.changedFiles);
|
|
139
|
+
for (const fn of Array.from(listeners)) {
|
|
140
|
+
try {
|
|
141
|
+
fn(ev);
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
if (VERBOSE)
|
|
145
|
+
console.warn('[hmr][solid] listener threw', err);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
const g = globalThis;
|
|
151
|
+
g.__ns_solid_hmr_subscribe = nsSolidHmrSubscribe;
|
|
152
|
+
// Eagerly create the listener set so the global exists at module load time.
|
|
153
|
+
getNsSolidHmrListenerSet();
|
|
154
|
+
if (VERBOSE)
|
|
155
|
+
console.log('[hmr][solid] HMR client loaded. global set=', typeof g.__ns_solid_hmr_subscribe, 'listenerSet=', typeof g.__ns_solid_hmr_listener_set);
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
console.warn('[hmr][solid] could not install global __ns_solid_hmr_subscribe', err);
|
|
159
|
+
}
|
|
104
160
|
// Eagerly drive the HMR-applying overlay's 'received' frame as soon
|
|
105
161
|
// as the server emits `ns:hmr-pending`, BEFORE the framework-specific
|
|
106
162
|
// (`ns:angular-update` / `ns:css-updates`) payload arrives. The
|
|
@@ -827,6 +883,12 @@ async function processQueue() {
|
|
|
827
883
|
return;
|
|
828
884
|
if (VERBOSE)
|
|
829
885
|
console.log('[hmr][queue] processing changed ids', drained);
|
|
886
|
+
// Track wall-clock so the 'complete' frame can show a meaningful
|
|
887
|
+
// total. Only the Solid + TypeScript flavors drive the overlay
|
|
888
|
+
// from here; Angular has its own flow inside
|
|
889
|
+
// `frameworks/angular/client/index.ts`.
|
|
890
|
+
const tQueueStart = Date.now();
|
|
891
|
+
const driveSolidOverlay = TARGET_FLAVOR === 'solid';
|
|
830
892
|
// Explicit eviction step.
|
|
831
893
|
//
|
|
832
894
|
// On modern runtimes the URL canonicalizer collapses any
|
|
@@ -842,11 +904,21 @@ async function processQueue() {
|
|
|
842
904
|
// legacy `/ns/m/__ns_hmr__/v<N>/` URL versioning path in that
|
|
843
905
|
// case. node_modules and virtual specs are filtered out by
|
|
844
906
|
// `buildEvictionUrls` so vendor modules stay hot.
|
|
907
|
+
if (driveSolidOverlay) {
|
|
908
|
+
setUpdateOverlayStage('evicting', {
|
|
909
|
+
detail: drained.length === 1 ? `Invalidating ${drained[0]}` : `Invalidating ${drained.length} modules`,
|
|
910
|
+
});
|
|
911
|
+
}
|
|
845
912
|
const evictUrls = buildEvictionUrls(drained);
|
|
846
913
|
const evicted = invalidateModulesByUrls(evictUrls);
|
|
847
914
|
if (VERBOSE)
|
|
848
915
|
console.log(`[hmr][queue] eviction count=${evictUrls.length} ok=${evicted}`);
|
|
849
916
|
// Evaluate changed modules best-effort; failures shouldn't completely break HMR.
|
|
917
|
+
if (driveSolidOverlay) {
|
|
918
|
+
setUpdateOverlayStage('reimporting', {
|
|
919
|
+
detail: drained.length === 1 ? `Re-importing ${drained[0]}` : `Re-importing ${drained.length} modules`,
|
|
920
|
+
});
|
|
921
|
+
}
|
|
850
922
|
for (const id of drained) {
|
|
851
923
|
try {
|
|
852
924
|
const spec = normalizeSpec(id);
|
|
@@ -868,6 +940,13 @@ async function processQueue() {
|
|
|
868
940
|
// Vue SFCs are handled via the registry update path; nothing to do here.
|
|
869
941
|
break;
|
|
870
942
|
case 'solid': {
|
|
943
|
+
// Boundaries discovered in this HMR cycle (tsx files reachable
|
|
944
|
+
// via the reverse import graph from any changed file, plus route
|
|
945
|
+
// files reachable from any tsx start point). Declared at the top
|
|
946
|
+
// of the case block so the emit step below can include the
|
|
947
|
+
// complete set in the listener event — framework integrations
|
|
948
|
+
// use it to map route boundaries → fresh component references.
|
|
949
|
+
const boundaries = new Set();
|
|
871
950
|
// Solid .tsx components are self-accepting via solid-refresh's inline
|
|
872
951
|
// patchRegistry — re-importing them is sufficient. For non-component
|
|
873
952
|
// .ts utility modules, we must propagate up the import graph to find
|
|
@@ -886,8 +965,10 @@ async function processQueue() {
|
|
|
886
965
|
arr.push(id);
|
|
887
966
|
}
|
|
888
967
|
}
|
|
889
|
-
// BFS from each non-tsx changed module up to tsx/jsx
|
|
890
|
-
|
|
968
|
+
// Pass 1: BFS from each non-tsx changed module up to tsx/jsx
|
|
969
|
+
// boundaries. These get re-imported below so solid-refresh's
|
|
970
|
+
// inline patchRegistry runs and (best-effort) swaps the proxy
|
|
971
|
+
// signals for any components defined in those tsx boundaries.
|
|
891
972
|
for (const id of drained) {
|
|
892
973
|
if (/\.(tsx|jsx)$/i.test(id))
|
|
893
974
|
continue; // already self-accepting
|
|
@@ -911,6 +992,51 @@ async function processQueue() {
|
|
|
911
992
|
}
|
|
912
993
|
}
|
|
913
994
|
}
|
|
995
|
+
// Pass 2: walk further from any tsx starting point (a tsx file
|
|
996
|
+
// in `drained` OR a tsx boundary discovered in pass 1) to find
|
|
997
|
+
// route files (`/src/routes/*.{tsx,jsx}`) that transitively
|
|
998
|
+
// import them. Re-importing a route file refreshes its
|
|
999
|
+
// `Route.options.component` to the freshly-imported reference
|
|
1000
|
+
// and the existing boundary loop below patches the live router
|
|
1001
|
+
// with that fresh reference.
|
|
1002
|
+
//
|
|
1003
|
+
// This is the key fix for "edit home.tsx → save → no visual
|
|
1004
|
+
// update": the old BFS skipped tsx files in `drained` (assuming
|
|
1005
|
+
// solid-refresh's in-place proxy patch was sufficient), but in
|
|
1006
|
+
// the universal-renderer + nested-context configuration that
|
|
1007
|
+
// patch does not always propagate to the visible page tree.
|
|
1008
|
+
// Adding the route file as a boundary lets us patch
|
|
1009
|
+
// `route.options.component` directly to a fresh module export,
|
|
1010
|
+
// which the framework subscriber then passes through to the
|
|
1011
|
+
// page remount — making the cycle robust to the proxy patch
|
|
1012
|
+
// silently failing.
|
|
1013
|
+
const tsxStarts = new Set();
|
|
1014
|
+
for (const id of drained) {
|
|
1015
|
+
if (/\.(tsx|jsx)$/i.test(id))
|
|
1016
|
+
tsxStarts.add(id);
|
|
1017
|
+
}
|
|
1018
|
+
for (const b of boundaries)
|
|
1019
|
+
tsxStarts.add(b);
|
|
1020
|
+
const ROUTE_FILE_RE = /\/src\/routes\/.+\.(tsx|jsx)$/i;
|
|
1021
|
+
for (const start of tsxStarts) {
|
|
1022
|
+
const visited = new Set();
|
|
1023
|
+
const queue = [start];
|
|
1024
|
+
while (queue.length) {
|
|
1025
|
+
const cur = queue.shift();
|
|
1026
|
+
if (visited.has(cur))
|
|
1027
|
+
continue;
|
|
1028
|
+
visited.add(cur);
|
|
1029
|
+
if (cur !== start && ROUTE_FILE_RE.test(cur)) {
|
|
1030
|
+
boundaries.add(cur);
|
|
1031
|
+
}
|
|
1032
|
+
const importers = reverseIndex.get(cur);
|
|
1033
|
+
if (!importers)
|
|
1034
|
+
continue;
|
|
1035
|
+
for (const imp of importers) {
|
|
1036
|
+
queue.push(imp);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
914
1040
|
// Re-import each boundary so solid-refresh patchRegistry fires.
|
|
915
1041
|
// For route files (TanStack Router), capture the new Route export
|
|
916
1042
|
// and patch the router's existing route with the fresh loader.
|
|
@@ -980,22 +1106,28 @@ async function processQueue() {
|
|
|
980
1106
|
if (VERBOSE)
|
|
981
1107
|
console.log('[hmr][solid] propagated to boundary', { id, url });
|
|
982
1108
|
const mod = await import(/* @vite-ignore */ url);
|
|
983
|
-
// Patch TanStack Router route
|
|
1109
|
+
// Patch TanStack Router route options for any module
|
|
1110
|
+
// that exports a `Route`. We patch BOTH the component
|
|
1111
|
+
// and the loader (when present); components-only routes
|
|
1112
|
+
// were previously skipped because the gate required a
|
|
1113
|
+
// loader, which left their `options.component` pointing
|
|
1114
|
+
// at the stale module's exports after HMR.
|
|
984
1115
|
try {
|
|
985
1116
|
const newRoute = mod?.Route;
|
|
986
|
-
if (newRoute?.options
|
|
1117
|
+
if (newRoute?.options) {
|
|
987
1118
|
const router = findRouter();
|
|
988
1119
|
const fullPath = boundaryToFullPath(id);
|
|
989
1120
|
if (VERBOSE)
|
|
990
|
-
console.log('[hmr][solid][diag] route patch attempt', { id, fullPath, hasRouter: !!router,
|
|
1121
|
+
console.log('[hmr][solid][diag] route patch attempt', { id, fullPath, hasRouter: !!router, hasLoader: !!newRoute.options.loader, hasComponent: !!newRoute.options.component });
|
|
991
1122
|
const existingRoute = fullPath && router ? findRouteByFullPath(router, fullPath) : null;
|
|
992
1123
|
if (existingRoute?.options) {
|
|
993
|
-
|
|
1124
|
+
if (newRoute.options.loader)
|
|
1125
|
+
existingRoute.options.loader = newRoute.options.loader;
|
|
994
1126
|
if (newRoute.options.component)
|
|
995
1127
|
existingRoute.options.component = newRoute.options.component;
|
|
996
1128
|
routesPatchCount++;
|
|
997
1129
|
if (VERBOSE)
|
|
998
|
-
console.log('[hmr][solid] patched route
|
|
1130
|
+
console.log('[hmr][solid] patched route', existingRoute.id, 'fullPath=', fullPath);
|
|
999
1131
|
}
|
|
1000
1132
|
else if (VERBOSE) {
|
|
1001
1133
|
console.log('[hmr][solid] no matching route for fullPath', fullPath);
|
|
@@ -1031,6 +1163,44 @@ async function processQueue() {
|
|
|
1031
1163
|
if (VERBOSE)
|
|
1032
1164
|
console.warn('[hmr][solid] propagation failed', e);
|
|
1033
1165
|
}
|
|
1166
|
+
// Notify any framework integrations (e.g.
|
|
1167
|
+
// `@nativescript/tanstack-router`) that a Solid HMR
|
|
1168
|
+
// cycle has completed. They use this signal to perform
|
|
1169
|
+
// framework-specific UI refresh (e.g. remount the active
|
|
1170
|
+
// router page) when solid-refresh's own reactive
|
|
1171
|
+
// propagation does not reach the visible tree under
|
|
1172
|
+
// the current renderer/context configuration.
|
|
1173
|
+
//
|
|
1174
|
+
// Boundaries include both the directly-changed tsx files
|
|
1175
|
+
// AND every tsx ancestor reachable via the reverse import
|
|
1176
|
+
// graph (route files in particular). The framework
|
|
1177
|
+
// listener uses the route-file boundaries to look up the
|
|
1178
|
+
// freshly-patched `route.options.component` and pass it
|
|
1179
|
+
// through to the page remount.
|
|
1180
|
+
try {
|
|
1181
|
+
const tsxChangedInDrained = drained.filter((id) => /\.(tsx|jsx)$/i.test(id));
|
|
1182
|
+
const allBoundaries = Array.from(new Set([...tsxChangedInDrained, ...boundaries]));
|
|
1183
|
+
nsSolidHmrEmit({
|
|
1184
|
+
kind: 'solid',
|
|
1185
|
+
changedFiles: drained.slice(),
|
|
1186
|
+
boundaries: allBoundaries,
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
catch (err) {
|
|
1190
|
+
if (VERBOSE)
|
|
1191
|
+
console.warn('[hmr][solid] emit failed', err);
|
|
1192
|
+
}
|
|
1193
|
+
// Tell the overlay the cycle is done. solid-refresh's
|
|
1194
|
+
// inline patchRegistry has already flushed the new
|
|
1195
|
+
// component bodies into the live tree (the `case
|
|
1196
|
+
// 'solid'` block above re-imports each .tsx
|
|
1197
|
+
// boundary), so by the time we get here the user is
|
|
1198
|
+
// already looking at the new render. The 'complete'
|
|
1199
|
+
// frame surfaces the wall-clock total and triggers
|
|
1200
|
+
// the overlay's auto-hide.
|
|
1201
|
+
setUpdateOverlayStage('complete', {
|
|
1202
|
+
detail: `Total ${Math.max(0, Date.now() - tQueueStart)}ms`,
|
|
1203
|
+
});
|
|
1034
1204
|
break;
|
|
1035
1205
|
}
|
|
1036
1206
|
case 'typescript': {
|
|
@@ -1547,6 +1717,104 @@ async function handleHmrMessage(ev) {
|
|
|
1547
1717
|
return;
|
|
1548
1718
|
}
|
|
1549
1719
|
else {
|
|
1720
|
+
// Vite custom-event dispatch.
|
|
1721
|
+
//
|
|
1722
|
+
// `server.ws.send('event-name', payload)` from any Vite plugin lands
|
|
1723
|
+
// on the wire as `{ type: 'custom', event: 'event-name', data: payload }`.
|
|
1724
|
+
// On the web, Vite's stock client owns a `customListenersMap` that
|
|
1725
|
+
// fires every `import.meta.hot.on('event-name', cb)` callback. We
|
|
1726
|
+
// don't run Vite's stock client on device — the iOS runtime owns
|
|
1727
|
+
// the listener registry via `__NS_DISPATCH_HOT_EVENT__` (the
|
|
1728
|
+
// counterpart to `import.meta.hot.on` populated by user code +
|
|
1729
|
+
// compiled Angular components). Forwarding `type: 'custom'` here
|
|
1730
|
+
// is the only thing standing between server-emitted events and
|
|
1731
|
+
// the listeners they were meant for.
|
|
1732
|
+
//
|
|
1733
|
+
// `angular:component-update` is the canonical example. Analog's
|
|
1734
|
+
// plugin sends it on `.html` / component-style edits; the
|
|
1735
|
+
// compiled component `.mjs` registered a listener that
|
|
1736
|
+
// dynamic-imports `/@ng/component?c=<id>&t=<ts>` and calls
|
|
1737
|
+
// `ɵɵreplaceMetadata` on the live class — swapping the template
|
|
1738
|
+
// definition AND walking live `LView`s to recreate matching views
|
|
1739
|
+
// in-place. The page stays mounted and only the changed bits
|
|
1740
|
+
// re-render. We MUST `return` after dispatch so the reboot path
|
|
1741
|
+
// (`handleAngularHotUpdateMessage` → `__reboot_ng_modules__`)
|
|
1742
|
+
// never runs for these updates — that's the whole point of the
|
|
1743
|
+
// component-replacement pipeline.
|
|
1744
|
+
//
|
|
1745
|
+
// All other custom events are forwarded but NOT short-circuited
|
|
1746
|
+
// (Vite spec: custom events are additive — they don't replace
|
|
1747
|
+
// any framework-specific handling). The reboot path falls through
|
|
1748
|
+
// for `ns:angular-update` (the legacy/`.ts`-edit broadcast) and
|
|
1749
|
+
// for any framework not yet using the in-place replacement path.
|
|
1750
|
+
if (msg.type === 'custom' && typeof msg.event === 'string') {
|
|
1751
|
+
// Dispatch every Vite "custom" event through the runtime's
|
|
1752
|
+
// `__NS_DISPATCH_HOT_EVENT__` bridge so `import.meta.hot.on(event, cb)`
|
|
1753
|
+
// callbacks fire on the device. Critical contract: this is the
|
|
1754
|
+
// ONLY route by which Analog's `angular:component-update` reaches
|
|
1755
|
+
// the compiled component's `(d) => d.id === id && Component_HmrLoad(...)`
|
|
1756
|
+
// listener — without it, server-side broadcasts log green
|
|
1757
|
+
// (`(client) hmr update`) while the device sees nothing happen.
|
|
1758
|
+
//
|
|
1759
|
+
// Diagnostic policy: log "no dispatcher" loud (boot-time rt-bridge
|
|
1760
|
+
// failure), and listener exceptions loud (compiled HmrLoad
|
|
1761
|
+
// fetch/parse error). Successful dispatches are silent — the
|
|
1762
|
+
// runtime's `[import.meta.hot] dispatch summary` line carries
|
|
1763
|
+
// the per-event match-count diagnostic.
|
|
1764
|
+
try {
|
|
1765
|
+
const dispatch = globalThis.__NS_DISPATCH_HOT_EVENT__;
|
|
1766
|
+
if (typeof dispatch === 'function') {
|
|
1767
|
+
dispatch(msg.event, msg.data);
|
|
1768
|
+
}
|
|
1769
|
+
else {
|
|
1770
|
+
console.warn(`[hmr-client][custom] no __NS_DISPATCH_HOT_EVENT__ available for '${msg.event}'`);
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
catch (err) {
|
|
1774
|
+
console.warn('[hmr-client][custom] dispatch threw for', msg.event, err);
|
|
1775
|
+
}
|
|
1776
|
+
if (msg.event === 'angular:component-update') {
|
|
1777
|
+
if (VERBOSE)
|
|
1778
|
+
console.log('[hmr-client][custom] dispatched angular:component-update — skipping reboot path');
|
|
1779
|
+
// Walk the apply-progress overlay through its
|
|
1780
|
+
// remaining stages for the in-place template-swap
|
|
1781
|
+
// path. The full reboot path
|
|
1782
|
+
// (`handleAngularHotUpdateMessage`) drives the
|
|
1783
|
+
// overlay itself ('received' → 'evicting' →
|
|
1784
|
+
// 'reimporting' → 'rebooting' → 'complete'); the
|
|
1785
|
+
// in-place path bypasses that handler entirely
|
|
1786
|
+
// because the work happens inside Angular's
|
|
1787
|
+
// `ɵɵreplaceMetadata` after the runtime forwards the
|
|
1788
|
+
// `angular:component-update` event to the compiled
|
|
1789
|
+
// component's listener. Without this update the
|
|
1790
|
+
// overlay would freeze at 5% ('received') even
|
|
1791
|
+
// though the visual swap completes a few frames
|
|
1792
|
+
// later — exactly the "Preparing update (5%)" stuck
|
|
1793
|
+
// frame we have been chasing.
|
|
1794
|
+
//
|
|
1795
|
+
// We transition straight to 'reimporting' to
|
|
1796
|
+
// communicate that metadata is being fetched (the
|
|
1797
|
+
// runtime listener fires `__ns_import('/@ng/component?c=...&t=...')`),
|
|
1798
|
+
// then schedule 'complete' on the next macrotask so
|
|
1799
|
+
// the auto-hide timer kicks in. The actual
|
|
1800
|
+
// template swap is fire-and-forget from this point;
|
|
1801
|
+
// the user sees the overlay close at the same time
|
|
1802
|
+
// as Angular re-renders the bound text/structure.
|
|
1803
|
+
try {
|
|
1804
|
+
const filePath = typeof msg.data?.id === 'string' ? decodeURIComponent(msg.data.id).split('@')[0] : undefined;
|
|
1805
|
+
const detail = filePath ? `Applying template update to ${filePath}` : 'Applying template update';
|
|
1806
|
+
setUpdateOverlayStage('reimporting', { detail });
|
|
1807
|
+
setTimeout(() => {
|
|
1808
|
+
try {
|
|
1809
|
+
setUpdateOverlayStage('complete', { detail: filePath ? `Updated ${filePath}` : 'Update applied' });
|
|
1810
|
+
}
|
|
1811
|
+
catch { }
|
|
1812
|
+
}, 16);
|
|
1813
|
+
}
|
|
1814
|
+
catch { }
|
|
1815
|
+
return;
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1550
1818
|
if (msg.type === 'ns:angular-update' && typeof msg.version === 'number') {
|
|
1551
1819
|
setGraphVersion(Number(msg.version || getGraphVersion() || 0));
|
|
1552
1820
|
}
|