Skip to content

Commit 3efd4fa

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 bfc6c5e commit 3efd4fa

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
@@ -9,12 +9,19 @@
99
#include "SpinLock.h"
1010
#include "libplatform/libplatform.h"
1111

12+
#include <functional>
13+
#include <string>
14+
1215
// Declared rather than included: js_native_api_types.h pins NAPI_VERSION for
1316
// the whole translation unit, and Runtime.h reaches nearly all of them.
1417
typedef struct napi_env__* napi_env;
1518

1619
namespace tns {
1720

21+
using ReloadApplicationHook = std::function<bool(const std::string& baseDir)>;
22+
void SetReloadApplicationHook(ReloadApplicationHook hook);
23+
bool InvokeReloadApplicationHook(const std::string& baseDir);
24+
1825
class Runtime {
1926
public:
2027
Runtime();
@@ -107,6 +114,8 @@ class Runtime {
107114
void DefineCollectFunction(v8::Local<v8::Context> context);
108115
void DefineNativeScriptVersion(v8::Isolate* isolate,
109116
v8::Local<v8::ObjectTemplate> globalTemplate);
117+
void DefineNativeScriptRuntime(v8::Isolate* isolate,
118+
v8::Local<v8::ObjectTemplate> globalTemplate);
110119
void DefineTimeMethod(v8::Isolate* isolate,
111120
v8::Local<v8::ObjectTemplate> globalTemplate);
112121
void DefineDrainMicrotaskMethod(v8::Isolate* isolate,

‎NativeScript/runtime/Runtime.mm‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ void DisposeIsolateWhenPossible(Isolate* isolate) {
343343
tns::binding::CreateInternalBindingTemplates(isolate, globalTemplateFunction);
344344
Local<ObjectTemplate> globalTemplate = ObjectTemplate::New(isolate, globalTemplateFunction);
345345
DefineNativeScriptVersion(isolate, globalTemplate);
346+
DefineNativeScriptRuntime(isolate, globalTemplate);
346347

347348
// Worker::Init(isolate, globalTemplate, isWorker);
348349
DefineTimeMethod(isolate, globalTemplate);
@@ -562,6 +563,41 @@ void DisposeIsolateWhenPossible(Isolate* isolate) {
562563
ToV8String(isolate, STRINGIZE_VALUE_OF(NATIVESCRIPT_VERSION)), readOnlyFlags);
563564
}
564565

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

0 commit comments

Comments
 (0)