-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat(unbundle): Adds unbundle support to react-native-windows #1017
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
{ | ||
static class IntegerHelpers | ||
{ | ||
public static uint LittleEndianToHost(uint value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could we call this InetHelpers (or something similar) to reference the C header whose API we're emulating, and call the static method the same thing? #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me, but it would be nice to see some tests. I love that this is all in Common code! 🥇
Thanks @matthargett, will see what I can do about tests once I get everything done. I still need to add support to the C++/CX ChakraBridge implementation. Once we get unbundles working there, it may make sense to revisit the PR to make that portable to WPF, as I anticipate that the indexed unbundle + memory mapped file may be the best solution in regards to app startup time. |
Hoping that @mattpodwysocki will look at porting this logic to C++/CX ChakraBridge! #Closed |
@@ -1,8 +1,13 @@ | |||
#include "pch.h" | |||
#include <stdint.h> | |||
#include "pch.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pedantic question: is there a prefered style for sorting <> includes "" includes? #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (argumentCount != 2) | ||
{ | ||
return JS_INVALID_REFERENCE; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just double checking, are we sure "no-op" is the correct behavior here? #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#include "pch.h" | ||
#include "FileHelpers.h" | ||
|
||
JsErrorCode FileHelpers::LoadFileContents(const wchar_t* szPath, wchar_t** pszData) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LoadFileContents [](start = 25, length = 16)
Let's just add statics to ChakraHost to reduce the diff for now.
9d77e1a
to
686fc91
Compare
CloseHandle(*hFile); | ||
return JsErrorFatal; | ||
} | ||
*hMap = CreateFileMapping(*hFile, nullptr, PAGE_READONLY, 0, 0, L"ReactNativeMapping"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PAGE_READONLY [](start = 44, length = 13)
Parameterize these readonly changes. #Closed
|
||
// Copy the header data | ||
uint32_t header[3]; | ||
CopyMemory(header, byteBuffer, sizeof(header)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might want to use memcpy_s instead since it can trap error codes. #Closed
// Copy the script | ||
auto script = new char[moduleData.length]; | ||
auto wcScript = new wchar_t[moduleData.length]; | ||
CopyMemory(script, byteBuffer + baseOffset + moduleData.offset, moduleData.length); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
might want to use memcpy_s
here instead which can now return an error code instead of throwing an AVE #WontFix
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Throwing an exception is fine, it turns out that is the expected behavior in react-native for failures in the native callback.
In reply to: 103551729 [](ancestors = 103551729,103547834)
if (fileHandle != NULL) | ||
{ | ||
UnmapViewOfFile(byteBuffer); | ||
CloseHandle(mapHandle); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
might want to trap bad actors of CloseHandle
#WontFix
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just following the lead of https://github.com/ReactWindows/react-native-windows/blob/master/ReactWindows/ChakraBridge/SerializedSourceContext.h#L14-L24
In reply to: 103548082 [](ancestors = 103548082)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since all handles are generated from the same LoadByteCode function, I think the same assumptions apply that if the file handle is not null, then we can close these in the destructor successfully.
In reply to: 103551314 [](ancestors = 103551314,103548082)
{ | ||
if (fileHandle != NULL) | ||
{ | ||
UnmapViewOfFile(byteBuffer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May want to trap if UnmapViewOfFile
returns nonzero #WontFix
uint32_t numberOfTableEntries = header[1]; | ||
moduleTable = ModuleTable(numberOfTableEntries); | ||
uint32_t moduleTableSize = moduleTable.byteLength(); | ||
CopyMemory(moduleTable.modules, byteBuffer + sizeof(header), moduleTableSize); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same concern with CopyMemory
#Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uint32_t startupCodeSize = header[2]; | ||
auto startupCode = new char[startupCodeSize]; | ||
auto wcStartupCode = new wchar_t[startupCodeSize]; | ||
CopyMemory(startupCode, byteBuffer + baseOffset, startupCodeSize); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same again with CopyMemory
#Closed
public: | ||
JsModulesUnbundle(const wchar_t* sourcePath); | ||
~JsModulesUnbundle(); | ||
virtual JsModulesUnbundleModule* GetModule(uint32_t index); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would it be more useful if you had the JsModulesUnbundleModule as an out parameter and returned some error code? #WontFix
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would agree except that these unbundles are being used in the context of a native callback, which is expected to return a JsValueRef. It's not clear how we could effectively transfer a JsErrorCode through the native callback back to the original call to JavaScript from the IJavaScriptExecutor.
In reply to: 103549854 [](ancestors = 103549854,103549241)
JsModulesUnbundle(const wchar_t* sourcePath); | ||
~JsModulesUnbundle(); | ||
virtual JsModulesUnbundleModule* GetModule(uint32_t index); | ||
virtual wchar_t* GetStartupCode(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same thing, would it be more useful to have an error code returned and an out param for the wchar_t
? #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | ||
|
||
|
||
ModuleTable::~ModuleTable() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ModuleTable [](start = 14, length = 11)
Delete unused code. #Closed
} | ||
|
||
auto module = host->unbundle->GetModule((uint32_t)moduleId); | ||
host->EvaluateScript(module->source, module->sourceUrl, nullptr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
source [](start = 30, length = 6)
Ensure module is not null. #Closed
} | ||
|
||
auto module = host->unbundle->GetModule((uint32_t)moduleId); | ||
host->EvaluateScript(module->source, module->sourceUrl, nullptr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
EvaluateScript [](start = 7, length = 14)
Do something with the error code. #Closed
free(contents); | ||
|
||
return status; | ||
} | ||
|
||
JsErrorCode ChakraHost::EvaluateScript(const wchar_t* szScript, const wchar_t* szSourceUri, JsValueRef* result) | ||
{ | ||
return JsRunScript(szScript, currentSourceContext++, szSourceUri, result); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JsRunScript [](start = 8, length = 11)
Add nullptr checks for szScript and szSourceUri #Closed
da5bc7e
to
1eddc04
Compare
return JS_INVALID_REFERENCE; | ||
} | ||
|
||
BOOL IsUnbundle(const wchar_t* sourcePath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can use bool
instead of BOOL
here #Closed
return magicHeader == MagicFileHeader; | ||
} | ||
|
||
BOOL IsIndexedUnbundle(const wchar_t* sourcePath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same, use bool
instead of BOOL
#Closed
} | ||
|
||
uint32_t magicHeader; | ||
fread(&magicHeader, sizeof(uint32_t), 1, file); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
might want to verify the size of the read return value #Closed
@@ -187,7 +257,7 @@ JsErrorCode ChakraHost::RunSerializedScript(const wchar_t* szPath, const wchar_t | |||
} | |||
else | |||
{ | |||
IfFailRet(LoadByteCode(szSerializedPath, &buffer, &hFile, &hMap)); | |||
IfFailRet(LoadByteCode(szSerializedPath, &buffer, &hFile, &hMap, FALSE)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can use false
here #Closed
102ed13
to
ce0c07d
Compare
Closing and reopening to trigger CLA bot |
@rozele, |
To whom it may concern that may will merge this commit: Please use Each one is a separate feature. |
Codecov Report
@@ Coverage Diff @@
## master #1017 +/- ##
==========================================
- Coverage 31.93% 31.78% -0.15%
==========================================
Files 257 258 +1
Lines 18538 18675 +137
Branches 1566 1580 +14
==========================================
+ Hits 5920 5936 +16
- Misses 12470 12582 +112
- Partials 148 157 +9
Continue to review full report at Codecov.
|
Adds unbundle support by adding a check in ChakraJavaScriptExecutor for the UNBUNDLE magic file, and by adding a `nativeRequire` hook to the Chakra runtime to dynamically load files when needed. The intent is that this should reduce app start times by reducing the amount of JavaScript that needs to be initially processed, but it doesn't seem to have much effect on the simple Playground and UIExplorer apps I've tested it on. Remaining TODO: add unbundle support to NativeJavaScriptExecutor Depends on facebook/react-native#12423 Fixes microsoft#952
Indexed unbundle is a single-file approach to loading JavaScript modules. It is based off a single file with a binary header, including a module table with references to the offset and length of each module in the file.
It is desirable to use C++/CX for the unbundle feature because it supports memory mapped files, which will potentially reduce file IO.
Adds unbundle support by adding a check in ChakraJavaScriptExecutor for the UNBUNDLE magic file, and by adding a
nativeRequire
hook to the Chakra runtime to dynamically load files when needed. The intent is that this should reduce app start times by reducing the amount of JavaScript that needs to be initially processed, but it doesn't seem to have much effect on the simple Playground and UIExplorer apps I've tested it on.Remaining TODO: add unbundle support to NativeJavaScriptExecutor
Depends on facebook/react-native#12423
Fixes #952