Skip to content

Commit 1d7f9d2

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 debf7f8 commit 1d7f9d2

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
@@ -24,6 +24,21 @@ @implementation Config
2424

2525
@end
2626

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

2944
extern char defaultStartOfMetadataSection __asm("section$start$__DATA$__TNSMetadata");
@@ -82,6 +97,9 @@ - (void)shutdownRuntime {
8297

8398
- (instancetype)initializeWithConfig:(Config*)config {
8499
if (self = [super init]) {
100+
currentNativeScript = self;
101+
currentConfig = CopyConfig(config);
102+
85103
RuntimeConfig.BaseDir = [config.BaseDir UTF8String];
86104
if (config.ApplicationPath != nil) {
87105
RuntimeConfig.ApplicationPath =
@@ -98,6 +116,15 @@ - (instancetype)initializeWithConfig:(Config*)config {
98116
RuntimeConfig.IsDebug = [config IsDebug];
99117
RuntimeConfig.LogToSystemConsole = [config LogToSystemConsole];
100118

119+
// Connect the JS-exposed `NativeScriptRuntime.reloadApplication(baseDir?)`
120+
// global (registered by the runtime) to the Objective-C implementation below.
121+
tns::SetReloadApplicationHook([](const std::string& baseDir) -> bool {
122+
NSString* dir = baseDir.empty()
123+
? nil
124+
: [NSString stringWithUTF8String:baseDir.c_str()];
125+
return [NativeScriptRuntime reloadApplication:dir] == YES;
126+
});
127+
101128
Runtime::Initialize();
102129
runtime_ = nullptr;
103130
runtime_ = std::make_unique<Runtime>();
@@ -135,3 +162,29 @@ - (void)restartWithConfig:(Config*)config {
135162
}
136163

137164
@end
165+
166+
@implementation NativeScriptRuntime
167+
168+
+ (BOOL)reloadApplication {
169+
return [self reloadApplication:nil];
170+
}
171+
172+
+ (BOOL)reloadApplication:(NSString*)baseDir {
173+
if (currentNativeScript == nil || currentConfig == nil) {
174+
return NO;
175+
}
176+
177+
Config* config = CopyConfig(currentConfig);
178+
if (baseDir != nil && [baseDir length] > 0) {
179+
config.BaseDir = baseDir;
180+
}
181+
182+
dispatch_async(dispatch_get_main_queue(), ^{
183+
[currentNativeScript restartWithConfig:config];
184+
[currentNativeScript runMainApplication];
185+
});
186+
187+
return YES;
188+
}
189+
190+
@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();
@@ -67,6 +74,8 @@ class Runtime {
6774
void DefineCollectFunction(v8::Local<v8::Context> context);
6875
void DefineNativeScriptVersion(v8::Isolate* isolate,
6976
v8::Local<v8::ObjectTemplate> globalTemplate);
77+
void DefineNativeScriptRuntime(v8::Isolate* isolate,
78+
v8::Local<v8::ObjectTemplate> globalTemplate);
7079
void DefinePerformanceObject(v8::Isolate* isolate,
7180
v8::Local<v8::ObjectTemplate> globalTemplate);
7281
void DefineTimeMethod(v8::Isolate* isolate,

NativeScript/runtime/Runtime.mm

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ void DisposeIsolateWhenPossible(Isolate* isolate) {
292292
tns::binding::CreateInternalBindingTemplates(isolate, globalTemplateFunction);
293293
Local<ObjectTemplate> globalTemplate = ObjectTemplate::New(isolate, globalTemplateFunction);
294294
DefineNativeScriptVersion(isolate, globalTemplate);
295+
DefineNativeScriptRuntime(isolate, globalTemplate);
295296

296297
// Worker::Init(isolate, globalTemplate, isWorker);
297298
DefinePerformanceObject(isolate, globalTemplate);
@@ -573,6 +574,41 @@ void DisposeIsolateWhenPossible(Isolate* isolate) {
573574
ToV8String(isolate, STRINGIZE_VALUE_OF(NATIVESCRIPT_VERSION)), readOnlyFlags);
574575
}
575576

577+
static ReloadApplicationHook reloadApplicationHook_;
578+
579+
void SetReloadApplicationHook(ReloadApplicationHook hook) {
580+
reloadApplicationHook_ = std::move(hook);
581+
}
582+
583+
bool InvokeReloadApplicationHook(const std::string& baseDir) {
584+
if (!reloadApplicationHook_) {
585+
return false;
586+
}
587+
return reloadApplicationHook_(baseDir);
588+
}
589+
590+
// API to trigger application reload from JS without restarting the application process.
591+
// Exposes `global.NativeScriptRuntime.reloadApplication(baseDir?)` to JS.
592+
// `NativeScriptRuntime` class is part of the runtime framework and
593+
// is intentionally excluded from metadata generation, so it is not reachable
594+
// from JS on its own.
595+
void Runtime::DefineNativeScriptRuntime(Isolate* isolate, Local<ObjectTemplate> globalTemplate) {
596+
Local<ObjectTemplate> runtimeTemplate = ObjectTemplate::New(isolate);
597+
598+
Local<FunctionTemplate> reloadTemplate =
599+
FunctionTemplate::New(isolate, [](const FunctionCallbackInfo<Value>& info) {
600+
Isolate* isolate = info.GetIsolate();
601+
std::string baseDir;
602+
if (info.Length() > 0 && info[0]->IsString()) {
603+
baseDir = tns::ToString(isolate, info[0]);
604+
}
605+
info.GetReturnValue().Set(tns::InvokeReloadApplicationHook(baseDir));
606+
});
607+
runtimeTemplate->Set(ToV8String(isolate, "reloadApplication"), reloadTemplate);
608+
609+
globalTemplate->Set(ToV8String(isolate, "NativeScriptRuntime"), runtimeTemplate);
610+
}
611+
576612
void Runtime::DefineTimeMethod(v8::Isolate* isolate, v8::Local<v8::ObjectTemplate> globalTemplate) {
577613
Local<FunctionTemplate> timeFunctionTemplate =
578614
FunctionTemplate::New(isolate, [](const FunctionCallbackInfo<Value>& info) {

0 commit comments

Comments
 (0)