Skip to content

Commit 86a42f0

Browse files
committed
feat: support for meta.hot.on, meta.hot.send and custom event listeners
1 parent 7dff8ee commit 86a42f0

File tree

3 files changed

+133
-0
lines changed

3 files changed

+133
-0
lines changed

NativeScript/runtime/HMRSupport.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ template <class T> class Local;
1111
class Object;
1212
class Function;
1313
class Context;
14+
class Value;
1415
}
1516

1617
namespace tns {
@@ -67,4 +68,21 @@ std::string CanonicalizeHttpUrlKey(const std::string& url);
6768
// - status: HTTP status code
6869
bool HttpFetchText(const std::string& url, std::string& out, std::string& contentType, int& status);
6970

71+
// ─────────────────────────────────────────────────────────────
72+
// Custom HMR event support
73+
74+
// Register a custom event listener (called by import.meta.hot.on())
75+
void RegisterHotEventListener(v8::Isolate* isolate, const std::string& event, v8::Local<v8::Function> cb);
76+
77+
// Get all listeners for a custom event
78+
std::vector<v8::Local<v8::Function>> GetHotEventListeners(v8::Isolate* isolate, const std::string& event);
79+
80+
// Dispatch a custom event to all registered listeners
81+
// This should be called when the HMR WebSocket receives framework-specific events
82+
void DispatchHotEvent(v8::Isolate* isolate, v8::Local<v8::Context> context, const std::string& event, v8::Local<v8::Value> data);
83+
84+
// Initialize the global event dispatcher function (__NS_DISPATCH_HOT_EVENT__)
85+
// This exposes a JavaScript-callable function that the HMR client can use to dispatch events
86+
void InitializeHotEventDispatcher(v8::Isolate* isolate, v8::Local<v8::Context> context);
87+
7088
} // namespace tns

NativeScript/runtime/HMRSupport.mm

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ static inline bool EndsWith(const std::string& s, const char* suffix) {
2929
static std::unordered_map<std::string, std::vector<v8::Global<v8::Function>>> g_hotAccept;
3030
static std::unordered_map<std::string, std::vector<v8::Global<v8::Function>>> g_hotDispose;
3131

32+
// Custom event listeners
33+
// Keyed by event name (global, not per-module)
34+
static std::unordered_map<std::string, std::vector<v8::Global<v8::Function>>> g_hotEventListeners;
35+
3236
v8::Local<v8::Object> GetOrCreateHotData(v8::Isolate* isolate, const std::string& key) {
3337
auto it = g_hotData.find(key);
3438
if (it != g_hotData.end()) {
@@ -73,6 +77,76 @@ void RegisterHotDispose(v8::Isolate* isolate, const std::string& key, v8::Local<
7377
return out;
7478
}
7579

80+
void RegisterHotEventListener(v8::Isolate* isolate, const std::string& event, v8::Local<v8::Function> cb) {
81+
if (cb.IsEmpty()) return;
82+
g_hotEventListeners[event].emplace_back(v8::Global<v8::Function>(isolate, cb));
83+
}
84+
85+
std::vector<v8::Local<v8::Function>> GetHotEventListeners(v8::Isolate* isolate, const std::string& event) {
86+
std::vector<v8::Local<v8::Function>> out;
87+
auto it = g_hotEventListeners.find(event);
88+
if (it != g_hotEventListeners.end()) {
89+
for (auto& gfn : it->second) {
90+
if (!gfn.IsEmpty()) out.push_back(gfn.Get(isolate));
91+
}
92+
}
93+
return out;
94+
}
95+
96+
void DispatchHotEvent(v8::Isolate* isolate, v8::Local<v8::Context> context, const std::string& event, v8::Local<v8::Value> data) {
97+
auto callbacks = GetHotEventListeners(isolate, event);
98+
for (auto& cb : callbacks) {
99+
v8::TryCatch tryCatch(isolate);
100+
v8::Local<v8::Value> args[] = { data };
101+
v8::MaybeLocal<v8::Value> result = cb->Call(context, v8::Undefined(isolate), 1, args);
102+
(void)result; // Suppress unused result warning
103+
if (tryCatch.HasCaught()) {
104+
// Log error but continue to other listeners
105+
if (tns::IsScriptLoadingLogEnabled()) {
106+
Log(@"[import.meta.hot] Error in event listener for '%s'", event.c_str());
107+
}
108+
}
109+
}
110+
}
111+
112+
void InitializeHotEventDispatcher(v8::Isolate* isolate, v8::Local<v8::Context> context) {
113+
using v8::FunctionCallbackInfo;
114+
using v8::Local;
115+
using v8::Value;
116+
117+
// Create a global function __NS_DISPATCH_HOT_EVENT__(event, data)
118+
// that the HMR client can call to dispatch events to registered listeners
119+
auto dispatchCb = [](const FunctionCallbackInfo<Value>& info) {
120+
v8::Isolate* iso = info.GetIsolate();
121+
v8::Local<v8::Context> ctx = iso->GetCurrentContext();
122+
123+
if (info.Length() < 1 || !info[0]->IsString()) {
124+
info.GetReturnValue().Set(v8::Boolean::New(iso, false));
125+
return;
126+
}
127+
128+
v8::String::Utf8Value eventName(iso, info[0]);
129+
std::string event = *eventName ? *eventName : "";
130+
if (event.empty()) {
131+
info.GetReturnValue().Set(v8::Boolean::New(iso, false));
132+
return;
133+
}
134+
135+
v8::Local<Value> data = info.Length() > 1 ? info[1] : v8::Undefined(iso).As<Value>();
136+
137+
if (tns::IsScriptLoadingLogEnabled()) {
138+
Log(@"[import.meta.hot] Dispatching event '%s'", event.c_str());
139+
}
140+
141+
DispatchHotEvent(iso, ctx, event, data);
142+
info.GetReturnValue().Set(v8::Boolean::New(iso, true));
143+
};
144+
145+
v8::Local<v8::Object> global = context->Global();
146+
v8::Local<v8::Function> dispatchFn = v8::Function::New(context, dispatchCb).ToLocalChecked();
147+
global->CreateDataProperty(context, tns::ToV8String(isolate, "__NS_DISPATCH_HOT_EVENT__"), dispatchFn).Check();
148+
}
149+
76150
void InitializeImportMetaHot(v8::Isolate* isolate,
77151
v8::Local<v8::Context> context,
78152
v8::Local<v8::Object> importMeta,
@@ -194,6 +268,31 @@ void InitializeImportMetaHot(v8::Isolate* isolate,
194268
info.GetReturnValue().Set(v8::Undefined(info.GetIsolate()));
195269
};
196270

271+
// on(event, cb) — register custom event listener
272+
auto onCb = [](const FunctionCallbackInfo<Value>& info) {
273+
v8::Isolate* iso = info.GetIsolate();
274+
if (info.Length() < 2) {
275+
info.GetReturnValue().Set(v8::Undefined(iso));
276+
return;
277+
}
278+
if (!info[0]->IsString() || !info[1]->IsFunction()) {
279+
info.GetReturnValue().Set(v8::Undefined(iso));
280+
return;
281+
}
282+
v8::String::Utf8Value eventName(iso, info[0]);
283+
std::string event = *eventName ? *eventName : "";
284+
if (!event.empty()) {
285+
RegisterHotEventListener(iso, event, info[1].As<v8::Function>());
286+
}
287+
info.GetReturnValue().Set(v8::Undefined(iso));
288+
};
289+
290+
// send(event, data) — send event to server (no-op on client, could be wired to WebSocket)
291+
auto sendCb = [](const FunctionCallbackInfo<Value>& info) {
292+
// No-op for now - could be wired to WebSocket for client->server events
293+
info.GetReturnValue().Set(v8::Undefined(info.GetIsolate()));
294+
};
295+
197296
Local<Object> hot = Object::New(isolate);
198297
// Stable flags
199298
hot->CreateDataProperty(context, tns::ToV8String(isolate, "data"),
@@ -213,6 +312,12 @@ void InitializeImportMetaHot(v8::Isolate* isolate,
213312
hot->CreateDataProperty(
214313
context, tns::ToV8String(isolate, "invalidate"),
215314
v8::Function::New(context, invalidateCb, makeKeyData(key)).ToLocalChecked()).Check();
315+
hot->CreateDataProperty(
316+
context, tns::ToV8String(isolate, "on"),
317+
v8::Function::New(context, onCb, makeKeyData(key)).ToLocalChecked()).Check();
318+
hot->CreateDataProperty(
319+
context, tns::ToV8String(isolate, "send"),
320+
v8::Function::New(context, sendCb, makeKeyData(key)).ToLocalChecked()).Check();
216321

217322
// Attach to import.meta
218323
importMeta->CreateDataProperty(

NativeScript/runtime/Runtime.mm

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,16 @@ void DisposeIsolateWhenPossible(Isolate* isolate) {
339339
PromiseProxy::Init(context);
340340
Console::Init(context);
341341
WeakRef::Init(context);
342+
343+
// Initialize HMR event dispatcher for dev mode
344+
// This provides __NS_DISPATCH_HOT_EVENT__ global for the HMR client
345+
if (RuntimeConfig.IsDebug) {
346+
try {
347+
tns::InitializeHotEventDispatcher(isolate, context);
348+
} catch (...) {
349+
// Don't crash if HMR setup fails
350+
}
351+
}
342352

343353
// Implement Blob per the File API spec (https://w3c.github.io/FileAPI/#blob-section)
344354
// This provides a complete Blob implementation with:

0 commit comments

Comments
 (0)