Skip to content

Commit 0c78dd9

Browse files
committed
feat: reloadApplication for JS bundle restart without restarting app process
Helpful for programmatic reset of JS isolate for clean restart of JS application as well as OTA (over-the-air) updates without restarting the entire app process.
1 parent d24c897 commit 0c78dd9

4 files changed

Lines changed: 105 additions & 0 deletions

File tree

NativeScript/NativeScript.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,10 @@
2626
- (bool)liveSync;
2727

2828
@end
29+
30+
@interface NativeScriptRuntime : NSObject
31+
32+
+ (BOOL)reloadApplication;
33+
+ (BOOL)reloadApplication:(NSString*)baseDir;
34+
35+
@end

NativeScript/NativeScript.mm

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,21 @@ @implementation Config
2121

2222
@end
2323

24+
static Config* CopyConfig(Config* config) {
25+
Config* copy = [[Config alloc] init];
26+
copy.BaseDir = config.BaseDir;
27+
copy.ApplicationPath = config.ApplicationPath;
28+
copy.MetadataPtr = config.MetadataPtr;
29+
copy.IsDebug = config.IsDebug;
30+
copy.LogToSystemConsole = config.LogToSystemConsole;
31+
copy.ArgumentsCount = config.ArgumentsCount;
32+
copy.Arguments = config.Arguments;
33+
return copy;
34+
}
35+
36+
static NativeScript* currentNativeScript;
37+
static Config* currentConfig;
38+
2439
@implementation NativeScript
2540

2641
extern char defaultStartOfMetadataSection __asm("section$start$__DATA$__TNSMetadata");
@@ -66,6 +81,9 @@ - (void)shutdownRuntime {
6681

6782
- (instancetype)initializeWithConfig:(Config*)config {
6883
if (self = [super init]) {
84+
currentNativeScript = self;
85+
currentConfig = CopyConfig(config);
86+
6987
RuntimeConfig.BaseDir = [config.BaseDir UTF8String];
7088
if (config.ApplicationPath != nil) {
7189
RuntimeConfig.ApplicationPath =
@@ -82,6 +100,15 @@ - (instancetype)initializeWithConfig:(Config*)config {
82100
RuntimeConfig.IsDebug = [config IsDebug];
83101
RuntimeConfig.LogToSystemConsole = [config LogToSystemConsole];
84102

103+
// Connect the JS-exposed `NativeScriptRuntime.reloadApplication(baseDir?)`
104+
// global (registered by the runtime) to the Objective-C implementation below.
105+
tns::SetReloadApplicationHook([](const std::string& baseDir) -> bool {
106+
NSString* dir = baseDir.empty()
107+
? nil
108+
: [NSString stringWithUTF8String:baseDir.c_str()];
109+
return [NativeScriptRuntime reloadApplication:dir] == YES;
110+
});
111+
85112
Runtime::Initialize();
86113
runtime_ = nullptr;
87114
runtime_ = std::make_unique<Runtime>();
@@ -119,3 +146,29 @@ - (void)restartWithConfig:(Config*)config {
119146
}
120147

121148
@end
149+
150+
@implementation NativeScriptRuntime
151+
152+
+ (BOOL)reloadApplication {
153+
return [self reloadApplication:nil];
154+
}
155+
156+
+ (BOOL)reloadApplication:(NSString*)baseDir {
157+
if (currentNativeScript == nil || currentConfig == nil) {
158+
return NO;
159+
}
160+
161+
Config* config = CopyConfig(currentConfig);
162+
if (baseDir != nil && [baseDir length] > 0) {
163+
config.BaseDir = baseDir;
164+
}
165+
166+
dispatch_async(dispatch_get_main_queue(), ^{
167+
[currentNativeScript restartWithConfig:config];
168+
[currentNativeScript runMainApplication];
169+
});
170+
171+
return YES;
172+
}
173+
174+
@end

NativeScript/runtime/Runtime.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,15 @@
88
#include "SpinLock.h"
99
#include "libplatform/libplatform.h"
1010

11+
#include <functional>
12+
#include <string>
13+
1114
namespace tns {
1215

16+
using ReloadApplicationHook = std::function<bool(const std::string& baseDir)>;
17+
void SetReloadApplicationHook(ReloadApplicationHook hook);
18+
bool InvokeReloadApplicationHook(const std::string& baseDir);
19+
1320
class Runtime {
1421
public:
1522
Runtime();
@@ -70,6 +77,8 @@ class Runtime {
7077
void DefineCollectFunction(v8::Local<v8::Context> context);
7178
void DefineNativeScriptVersion(v8::Isolate* isolate,
7279
v8::Local<v8::ObjectTemplate> globalTemplate);
80+
void DefineNativeScriptRuntime(v8::Isolate* isolate,
81+
v8::Local<v8::ObjectTemplate> globalTemplate);
7382
void DefinePerformanceObject(v8::Isolate* isolate,
7483
v8::Local<v8::ObjectTemplate> globalTemplate);
7584
void DefineTimeMethod(v8::Isolate* isolate,

NativeScript/runtime/Runtime.mm

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,7 @@ void DisposeIsolateWhenPossible(Isolate* isolate) {
375375
tns::binding::CreateInternalBindingTemplates(isolate, globalTemplateFunction);
376376
Local<ObjectTemplate> globalTemplate = ObjectTemplate::New(isolate, globalTemplateFunction);
377377
DefineNativeScriptVersion(isolate, globalTemplate);
378+
DefineNativeScriptRuntime(isolate, globalTemplate);
378379

379380
// Worker::Init(isolate, globalTemplate, isWorker);
380381
DefinePerformanceObject(isolate, globalTemplate);
@@ -597,6 +598,41 @@ void DisposeIsolateWhenPossible(Isolate* isolate) {
597598
ToV8String(isolate, STRINGIZE_VALUE_OF(NATIVESCRIPT_VERSION)), readOnlyFlags);
598599
}
599600

601+
static ReloadApplicationHook reloadApplicationHook_;
602+
603+
void SetReloadApplicationHook(ReloadApplicationHook hook) {
604+
reloadApplicationHook_ = std::move(hook);
605+
}
606+
607+
bool InvokeReloadApplicationHook(const std::string& baseDir) {
608+
if (!reloadApplicationHook_) {
609+
return false;
610+
}
611+
return reloadApplicationHook_(baseDir);
612+
}
613+
614+
// API to trigger application reload from JS without restarting the application process.
615+
// Exposes `global.NativeScriptRuntime.reloadApplication(baseDir?)` to JS.
616+
// `NativeScriptRuntime` class is part of the runtime framework and
617+
// is intentionally excluded from metadata generation, so it is not reachable
618+
// from JS on its own.
619+
void Runtime::DefineNativeScriptRuntime(Isolate* isolate, Local<ObjectTemplate> globalTemplate) {
620+
Local<ObjectTemplate> runtimeTemplate = ObjectTemplate::New(isolate);
621+
622+
Local<FunctionTemplate> reloadTemplate =
623+
FunctionTemplate::New(isolate, [](const FunctionCallbackInfo<Value>& info) {
624+
Isolate* isolate = info.GetIsolate();
625+
std::string baseDir;
626+
if (info.Length() > 0 && info[0]->IsString()) {
627+
baseDir = tns::ToString(isolate, info[0]);
628+
}
629+
info.GetReturnValue().Set(tns::InvokeReloadApplicationHook(baseDir));
630+
});
631+
runtimeTemplate->Set(ToV8String(isolate, "reloadApplication"), reloadTemplate);
632+
633+
globalTemplate->Set(ToV8String(isolate, "NativeScriptRuntime"), runtimeTemplate);
634+
}
635+
600636
void Runtime::DefineTimeMethod(v8::Isolate* isolate, v8::Local<v8::ObjectTemplate> globalTemplate) {
601637
Local<FunctionTemplate> timeFunctionTemplate =
602638
FunctionTemplate::New(isolate, [](const FunctionCallbackInfo<Value>& info) {

0 commit comments

Comments
 (0)