Skip to content

Commit b9e8c5b

Browse files
committed
fix(windows): simplify app initialization with ReactNativeWin32App
1 parent 3cba342 commit b9e8c5b

7 files changed

Lines changed: 82 additions & 180 deletions

File tree

packages/app/test/windows/parseResources.test.mts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,13 @@ describe("parseResources()", () => {
3232
equal(
3333
assetItems,
3434
`<CopyFileToFolders Include="$(ProjectRootDir)\\dist\\assets\\node_modules\\arnold\\portrait.png">
35-
<DestinationFolders>$(OutDir)\\Bundle\\assets\\node_modules\\arnold</DestinationFolders>
35+
<DestinationFolders>$(BundleDir)\\assets\\node_modules\\arnold</DestinationFolders>
3636
</CopyFileToFolders>
3737
<CopyFileToFolders Include="$(ProjectRootDir)\\dist\\assets\\splash.png">
38-
<DestinationFolders>$(OutDir)\\Bundle\\assets</DestinationFolders>
38+
<DestinationFolders>$(BundleDir)\\assets</DestinationFolders>
3939
</CopyFileToFolders>
4040
<CopyFileToFolders Include="$(ProjectRootDir)\\dist\\main.jsbundle">
41-
<DestinationFolders>$(OutDir)\\Bundle</DestinationFolders>
41+
<DestinationFolders>$(BundleDir)</DestinationFolders>
4242
</CopyFileToFolders>`
4343
);
4444
equal(

packages/app/windows/Shared/ReactInstance.cpp

Lines changed: 48 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,18 @@ namespace
4646
winrt::hstring const kUseWebDebugger = L"useWebDebugger";
4747
#endif // USE_WEB_DEBUGGER
4848

49-
std::optional<winrt::hstring> GetBundleName(std::optional<winrt::hstring> const &bundleRoot)
49+
std::filesystem::path GetBundleRootPath()
50+
{
51+
WCHAR modulePath[MAX_PATH];
52+
GetModuleFileNameW(NULL, modulePath, MAX_PATH);
53+
return std::filesystem::path{modulePath}.replace_filename(L"Bundle") / L"";
54+
}
55+
56+
std::optional<winrt::hstring> GetBundleName(std::filesystem::path bundlePath,
57+
std::optional<winrt::hstring> const &bundleRoot)
5058
{
5159
constexpr std::wstring_view const bundleExtension = L".bundle";
5260

53-
std::filesystem::path bundlePath{L"Bundle\\"};
5461
if (bundleRoot.has_value()) {
5562
std::wstring_view root = bundleRoot.value();
5663
for (auto &&ext : {L".windows", L".native", L""}) {
@@ -104,74 +111,31 @@ std::vector<std::wstring_view> const ReactTestApp::JSBundleNames = {
104111
L"main",
105112
};
106113

107-
ReactInstance::ReactInstance()
108-
{
109-
reactNativeHost_.PackageProviders().Append(winrt::make<ReactPackageProvider>());
110-
winrt::Microsoft::ReactNative::RegisterAutolinkedNativeModulePackages(
111-
reactNativeHost_.PackageProviders());
112-
113-
reactNativeHost_.InstanceSettings().InstanceLoaded(
114-
[this](winrt::IInspectable const & /*sender*/, winrt::InstanceLoadedEventArgs const &args) {
115-
context_ = args.Context();
116-
117-
#if __has_include("AppRegistry.h") && __has_include(<JSI/JsiApiContext.h>)
118-
if (!onComponentsRegistered_) {
119-
return;
120-
}
121-
122-
winrt::Microsoft::ReactNative::ExecuteJsi(context_, [this](Runtime &runtime) noexcept {
123-
try {
124-
onComponentsRegistered_(ReactTestApp::GetAppKeys(runtime));
125-
} catch ([[maybe_unused]] std::exception const &e) {
126-
#if defined(_DEBUG) && !defined(DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION)
127-
if (IsDebuggerPresent()) {
128-
__debugbreak();
129-
}
130-
#endif // defined(_DEBUG) && !defined(DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION)
131-
}
132-
});
133-
#endif // __has_include("AppRegistry.h") && __has_include(<JSI/JsiApiContext.h>)
134-
});
135-
}
136-
137-
#if __has_include(<winrt/Microsoft.UI.Composition.h>)
138-
ReactInstance::ReactInstance(HWND hwnd,
139-
winrt::Microsoft::UI::Composition::Compositor const &compositor)
140-
: ReactInstance()
141-
{
142-
winrt::Microsoft::ReactNative::ReactCoreInjection::SetTopLevelWindowId(
143-
reactNativeHost_.InstanceSettings().Properties(), reinterpret_cast<uint64_t>(hwnd));
144-
145-
// By using the MicrosoftCompositionContextHelper here, React Native Windows
146-
// will use Lifted Visuals for its tree.
147-
winrt::Microsoft::ReactNative::Composition::CompositionUIService::SetCompositor(
148-
reactNativeHost_.InstanceSettings(), compositor);
149-
}
150-
#endif // __has_include(<winrt/Microsoft.UI.Composition.h>)
151-
152-
bool ReactInstance::LoadJSBundleFrom(JSBundleSource source)
114+
bool ReactInstance::LoadJSBundleFrom(JSBundleSource source, bool reloadHost)
153115
{
154116
source_ = source;
155117

156118
auto instanceSettings = reactNativeHost_.InstanceSettings();
157119
switch (source) {
158120
case JSBundleSource::DevServer:
159-
instanceSettings.JavaScriptBundleFile(L"index");
121+
instanceSettings.DebugBundlePath(L"index");
160122
break;
161123
case JSBundleSource::Embedded:
162-
auto const &bundleName = GetBundleName(bundleRoot_);
124+
auto const bundleRootPath = GetBundleRootPath();
125+
instanceSettings.BundleRootPath(L"file://" + bundleRootPath.wstring());
126+
auto const &bundleName = GetBundleName(bundleRootPath, bundleRoot_);
163127
if (!bundleName.has_value()) {
164128
return false;
165129
}
166130
instanceSettings.JavaScriptBundleFile(bundleName.value());
167131
break;
168132
}
169133

170-
Reload();
134+
Reload(reloadHost);
171135
return true;
172136
}
173137

174-
void ReactInstance::Reload()
138+
void ReactInstance::Reload(bool reloadHost)
175139
{
176140
auto instanceSettings = reactNativeHost_.InstanceSettings();
177141

@@ -196,7 +160,9 @@ void ReactInstance::Reload()
196160
instanceSettings.SourceBundleHost(host);
197161
instanceSettings.SourceBundlePort(static_cast<uint16_t>(port));
198162

199-
reactNativeHost_.ReloadInstance();
163+
if (reloadHost) {
164+
reactNativeHost_.ReloadInstance();
165+
}
200166
}
201167

202168
bool ReactInstance::BreakOnFirstLine() const
@@ -297,6 +263,35 @@ void ReactInstance::UseWebDebugger(bool useWebDebugger)
297263
#endif // USE_WEB_DEBUGGER
298264
}
299265

266+
void ReactInstance::InitializeHost(winrt::Microsoft::ReactNative::ReactNativeHost host)
267+
{
268+
host.PackageProviders().Append(winrt::make<ReactPackageProvider>());
269+
winrt::Microsoft::ReactNative::RegisterAutolinkedNativeModulePackages(host.PackageProviders());
270+
271+
host.InstanceSettings().InstanceLoaded(
272+
[this](winrt::IInspectable const & /*sender*/, winrt::InstanceLoadedEventArgs const &args) {
273+
context_ = args.Context();
274+
275+
#if __has_include("AppRegistry.h") && __has_include(<JSI/JsiApiContext.h>)
276+
if (!onComponentsRegistered_) {
277+
return;
278+
}
279+
280+
winrt::Microsoft::ReactNative::ExecuteJsi(context_, [this](Runtime &runtime) noexcept {
281+
try {
282+
onComponentsRegistered_(ReactTestApp::GetAppKeys(runtime));
283+
} catch ([[maybe_unused]] std::exception const &e) {
284+
#if defined(_DEBUG) && !defined(DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION)
285+
if (IsDebuggerPresent()) {
286+
__debugbreak();
287+
}
288+
#endif // defined(_DEBUG) && !defined(DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION)
289+
}
290+
});
291+
#endif // __has_include("AppRegistry.h") && __has_include(<JSI/JsiApiContext.h>)
292+
});
293+
}
294+
300295
winrt::IAsyncOperation<bool> ReactTestApp::IsDevServerRunning()
301296
{
302297
winrt::Uri uri(L"http://localhost:8081/status");

packages/app/windows/Shared/ReactInstance.h

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,24 @@ namespace ReactTestApp
3232
public:
3333
static constexpr uint32_t Version = REACT_NATIVE_VERSION;
3434

35-
ReactInstance();
35+
ReactInstance()
36+
{
37+
InitializeHost(reactNativeHost_);
38+
}
3639

37-
#if __has_include(<winrt/Microsoft.UI.Composition.h>)
38-
ReactInstance(HWND hwnd, winrt::Microsoft::UI::Composition::Compositor const &);
39-
#endif // __has_include(<winrt/Microsoft.UI.Composition.h>)
40+
ReactInstance(winrt::Microsoft::ReactNative::ReactNativeHost reactNativeHost)
41+
: reactNativeHost_(reactNativeHost)
42+
{
43+
InitializeHost(reactNativeHost);
44+
}
4045

4146
auto const &ReactHost() const
4247
{
4348
return reactNativeHost_;
4449
}
4550

46-
bool LoadJSBundleFrom(JSBundleSource);
47-
void Reload();
51+
bool LoadJSBundleFrom(JSBundleSource, bool reloadHost = true);
52+
void Reload(bool reloadHost = true);
4853

4954
bool BreakOnFirstLine() const;
5055
void BreakOnFirstLine(bool);
@@ -100,6 +105,8 @@ namespace ReactTestApp
100105
std::optional<winrt::hstring> bundleRoot_;
101106
JSBundleSource source_ = JSBundleSource::DevServer;
102107
OnComponentsRegistered onComponentsRegistered_;
108+
109+
void InitializeHost(winrt::Microsoft::ReactNative::ReactNativeHost);
103110
};
104111

105112
winrt::Windows::Foundation::IAsyncOperation<bool> IsDevServerRunning();

packages/app/windows/UWP/ReactTestApp.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
</PropertyGroup>
1818
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
1919
<PropertyGroup Label="ReactNativeWindowsProps">
20+
<BundleDir Condition="'$(BundleDir)'==''">$(OutDir)\Bundle\</BundleDir>
2021
<ProjectRootDir Condition="'$(ProjectRootDir)'==''">$([MSBuild]::GetDirectoryNameOfFileAbove($(SolutionDir), 'app.json'))</ProjectRootDir>
2122
<ReactAppWinDir Condition="'$(ReactAppWinDir)'==''">$([MSBuild]::GetDirectoryNameOfFileAbove($(SolutionDir), 'node_modules\react-native-test-app\package.json'))\node_modules\react-native-test-app\windows</ReactAppWinDir>
2223
<ReactAppCommonDir Condition="'$(ReactAppCommonDir)'==''">$(ReactAppWinDir)\..\common</ReactAppCommonDir>

packages/app/windows/Win32/Main.cpp

Lines changed: 13 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,7 @@
99
namespace winrt
1010
{
1111
using winrt::Microsoft::ReactNative::IJSValueWriter;
12-
using winrt::Microsoft::ReactNative::LayoutDirection;
13-
using winrt::Microsoft::ReactNative::ReactCoreInjection;
14-
using winrt::Microsoft::ReactNative::ReactNativeIsland;
1512
using winrt::Microsoft::ReactNative::ReactViewOptions;
16-
using winrt::Microsoft::UI::Composition::Compositor;
17-
using winrt::Microsoft::UI::Content::ContentSizePolicy;
18-
using winrt::Microsoft::UI::Content::DesktopChildSiteBridge;
19-
using winrt::Microsoft::UI::Dispatching::DispatcherQueueController;
20-
using winrt::Microsoft::UI::Windowing::AppWindow;
21-
using winrt::Microsoft::UI::Windowing::AppWindowChangedEventArgs;
22-
using winrt::Microsoft::UI::Windowing::OverlappedPresenter;
23-
using winrt::Microsoft::UI::Windowing::OverlappedPresenterState;
24-
using winrt::Windows::Foundation::AsyncStatus;
25-
using winrt::Windows::Foundation::Size;
2613
} // namespace winrt
2714

2815
namespace
@@ -34,30 +21,9 @@ namespace
3421
#endif
3522
constexpr bool kSingleAppMode = static_cast<bool>(ENABLE_SINGLE_APP_MODE);
3623

37-
float ScaleFactor(HWND hwnd) noexcept
24+
void ConfigureReactViewOptions(winrt::ReactViewOptions viewOptions,
25+
ReactApp::Component const &component)
3826
{
39-
return GetDpiForWindow(hwnd) / static_cast<float>(USER_DEFAULT_SCREEN_DPI);
40-
}
41-
42-
void UpdateRootViewSizeToAppWindow(winrt::ReactNativeIsland const &rootView,
43-
winrt::AppWindow const &window)
44-
{
45-
// Do not relayout when minimized
46-
auto windowState = window.Presenter().as<winrt::OverlappedPresenter>().State();
47-
if (windowState == winrt::OverlappedPresenterState::Minimized) {
48-
return;
49-
}
50-
51-
auto hwnd = winrt::Microsoft::UI::GetWindowFromWindowId(window.Id());
52-
auto scaleFactor = ScaleFactor(hwnd);
53-
winrt::Size size{window.ClientSize().Width / scaleFactor,
54-
window.ClientSize().Height / scaleFactor};
55-
rootView.Arrange({size, size, winrt::LayoutDirection::Undefined}, {0, 0});
56-
}
57-
58-
winrt::ReactViewOptions MakeReactViewOptions(ReactApp::Component const &component)
59-
{
60-
winrt::ReactViewOptions viewOptions;
6127
viewOptions.ComponentName(winrt::to_hstring(component.appKey));
6228

6329
auto initialProps = component.initialProperties.value_or<ReactApp::JSONObject>({});
@@ -71,8 +37,6 @@ namespace
7137
}
7238
writer.WriteObjectEnd();
7339
});
74-
75-
return viewOptions;
7640
}
7741
} // namespace
7842

@@ -91,22 +55,8 @@ _Use_decl_annotations_ int CALLBACK WinMain(HINSTANCE /* instance */,
9155
// Enable per monitor DPI scaling
9256
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
9357

94-
// Create a DispatcherQueue for this thread. This is needed for Composition, Content, and
95-
// Input APIs.
96-
auto dispatcherQueueController = winrt::DispatcherQueueController::CreateOnCurrentThread();
97-
98-
// Create a Compositor for all Content on this thread.
99-
auto compositor = winrt::Compositor{};
100-
101-
// Create a top-level window.
102-
auto window = winrt::AppWindow::Create();
103-
window.Title(winrt::to_hstring(manifest.displayName));
104-
window.Resize({600, 800});
105-
window.Show();
106-
auto hwnd = winrt::Microsoft::UI::GetWindowFromWindowId(window.Id());
107-
auto scaleFactor = ScaleFactor(hwnd);
108-
109-
auto instance = ReactTestApp::ReactInstance{hwnd, compositor};
58+
auto app = winrt::Microsoft::ReactNative::ReactNativeAppBuilder().Build();
59+
auto instance = ReactTestApp::ReactInstance{app.ReactNativeHost()};
11060
if (manifest.bundleRoot.has_value()) {
11161
auto &bundleRoot = *manifest.bundleRoot;
11262
instance.BundleRoot(std::make_optional(winrt::to_hstring(bundleRoot)));
@@ -115,81 +65,31 @@ _Use_decl_annotations_ int CALLBACK WinMain(HINSTANCE /* instance */,
11565
// Start the react-native instance, which will create a JavaScript runtime and load the
11666
// applications bundle
11767
if constexpr (kDebug) {
118-
instance.LoadJSBundleFrom(ReactTestApp::JSBundleSource::DevServer);
68+
instance.LoadJSBundleFrom(ReactTestApp::JSBundleSource::DevServer, false);
11969
} else {
120-
instance.LoadJSBundleFrom(ReactTestApp::JSBundleSource::Embedded);
70+
instance.LoadJSBundleFrom(ReactTestApp::JSBundleSource::Embedded, false);
12171
}
12272

123-
// Create a RootView which will present a react-native component
124-
winrt::ReactViewOptions viewOptions;
73+
// Configure ReactViewOptions to load the initial component
12574
if constexpr (kSingleAppMode) {
12675
assert(manifest.singleApp.has_value() ||
12776
!"`ENABLE_SINGLE_APP_MODE` shouldn't have been true");
12877

12978
for (auto &component : *manifest.components) {
13079
if (component.slug == *manifest.singleApp) {
131-
viewOptions = MakeReactViewOptions(component);
80+
ConfigureReactViewOptions(app.ReactViewOptions(), component);
13281
break;
13382
}
13483
}
13584
} else {
13685
// TODO: Implement session restoration
13786
auto &component = (*manifest.components)[0];
138-
viewOptions = MakeReactViewOptions(component);
87+
ConfigureReactViewOptions(app.ReactViewOptions(), component);
13988
}
14089

141-
auto rootView = winrt::ReactNativeIsland{compositor};
142-
rootView.ReactViewHost(
143-
winrt::ReactCoreInjection::MakeViewHost(instance.ReactHost(), viewOptions));
144-
145-
// Update the size of the RootView when the AppWindow changes size
146-
window.Changed(
147-
[wkRootView = winrt::make_weak(rootView)](winrt::AppWindow const &window,
148-
winrt::AppWindowChangedEventArgs const &args) {
149-
if (args.DidSizeChange() || args.DidVisibilityChange()) {
150-
if (auto rootView = wkRootView.get()) {
151-
UpdateRootViewSizeToAppWindow(rootView, window);
152-
}
153-
}
154-
});
155-
156-
// Quit application when main window is closed
157-
window.Destroying([&host = instance.ReactHost()](winrt::AppWindow const & /* window */,
158-
winrt::IInspectable const & /* args */) {
159-
// Before we shutdown the application - unload the ReactNativeHost to give the javascript a
160-
// chance to save any state
161-
auto async = host.UnloadInstance();
162-
async.Completed([host](auto asyncInfo, winrt::AsyncStatus asyncStatus) {
163-
assert(asyncStatus == winrt::AsyncStatus::Completed);
164-
host.InstanceSettings().UIDispatcher().Post([]() { PostQuitMessage(0); });
165-
});
166-
});
167-
168-
// DesktopChildSiteBridge create a ContentSite that can host the RootView ContentIsland
169-
auto bridge = winrt::DesktopChildSiteBridge::Create(compositor, window.Id());
170-
bridge.Connect(rootView.Island());
171-
bridge.ResizePolicy(winrt::ContentSizePolicy::ResizeContentToParentWindow);
172-
173-
auto invScale = 1.0f / scaleFactor;
174-
rootView.RootVisual().Scale({invScale, invScale, invScale});
175-
rootView.ScaleFactor(scaleFactor);
176-
177-
// Set the intialSize of the root view
178-
UpdateRootViewSizeToAppWindow(rootView, window);
179-
180-
bridge.Show();
181-
182-
// Run the main application event loop
183-
dispatcherQueueController.DispatcherQueue().RunEventLoop();
184-
185-
// Rundown the DispatcherQueue. This drains the queue and raises events to let components
186-
// know the message loop has finished.
187-
dispatcherQueueController.ShutdownQueue();
188-
189-
bridge.Close();
190-
bridge = nullptr;
90+
auto window = app.AppWindow();
91+
window.Title(winrt::to_hstring(manifest.displayName));
92+
window.Resize({600, 800});
19193

192-
// Destroy all Composition objects
193-
compositor.Close();
194-
compositor = nullptr;
94+
app.Start();
19595
}

0 commit comments

Comments
 (0)