diff --git a/packages/app/scripts/types.ts b/packages/app/scripts/types.ts
index 07f0ecb4b..4e04824dc 100644
--- a/packages/app/scripts/types.ts
+++ b/packages/app/scripts/types.ts
@@ -221,6 +221,7 @@ export type Assets = {
assetItems: string;
assetItemFilters: string;
assetFilters: string;
+ contentItems: string;
};
export type AppManifest = {
@@ -245,6 +246,7 @@ export type AppxBundle = {
assetItems: string;
assetItemFilters: string;
assetFilters: string;
+ contentItems: string;
packageCertificate: string;
singleApp?: string;
};
diff --git a/packages/app/test/windows/getBundleResources.test.mts b/packages/app/test/windows/getBundleResources.test.mts
index b3e382d19..ae4fb8614 100644
--- a/packages/app/test/windows/getBundleResources.test.mts
+++ b/packages/app/test/windows/getBundleResources.test.mts
@@ -5,12 +5,100 @@ import { getBundleResources as getBundleResourcesActual } from "../../windows/pr
import { fs, setMockFiles } from "../fs.mock.mts";
describe("getBundleResources()", () => {
- const getBundleResources: typeof getBundleResourcesActual = (p) =>
- getBundleResourcesActual(p, fs);
+ const getBundleResources: typeof getBundleResourcesActual = (p, opts) =>
+ getBundleResourcesActual(p, opts, fs);
+
+ const legacyOpts = { useFabric: false };
+ const newArchOpts = { useFabric: true };
afterEach(() => setMockFiles());
- it("returns app name and bundle resources", () => {
+ for (const opts of [legacyOpts, newArchOpts]) {
+ const arch = opts.useFabric ? "new" : "old";
+
+ it(`returns package manifest (${arch} arch)`, () => {
+ setMockFiles({
+ "app.json": JSON.stringify({
+ windows: {
+ appxManifest: "windows/Example/Package.appxmanifest",
+ },
+ }),
+ });
+
+ deepEqual(getBundleResources("app.json", opts), {
+ appName: "ReactTestApp",
+ singleApp: undefined,
+ appxManifest: "windows\\Example\\Package.appxmanifest",
+ assetItems: "",
+ assetItemFilters: "",
+ assetFilters: "",
+ contentItems: "",
+ packageCertificate: "",
+ });
+ });
+
+ it(`handles missing manifest (${arch} arch)`, (t) => {
+ const warnMock = t.mock.method(console, "warn", () => null);
+
+ deepEqual(getBundleResources("", opts), {
+ appName: "ReactTestApp",
+ appxManifest: "windows/Package.appxmanifest",
+ assetItems: "",
+ assetItemFilters: "",
+ assetFilters: "",
+ contentItems: "",
+ packageCertificate: "",
+ });
+
+ equal(
+ warnMock.mock.calls[0].arguments[1],
+ "Could not find 'app.json' file."
+ );
+ });
+
+ it(`handles invalid manifest (${arch} arch)`, (t) => {
+ const warnMock = t.mock.method(console, "warn", () => null);
+ setMockFiles({ "app.json": "-" });
+
+ deepEqual(getBundleResources("app.json", opts), {
+ appName: "ReactTestApp",
+ appxManifest: "windows/Package.appxmanifest",
+ assetItems: "",
+ assetItemFilters: "",
+ assetFilters: "",
+ contentItems: "",
+ packageCertificate: "",
+ });
+
+ match(
+ warnMock.mock.calls[0].arguments[1],
+ /^Could not parse 'app.json':\n/
+ );
+ });
+
+ it(`returns package certificate (${arch} arch)`, () => {
+ setMockFiles({
+ "app.json": JSON.stringify({
+ windows: {
+ certificateKeyFile: "windows/ReactTestApp_TemporaryKey.pfx",
+ certificateThumbprint: "thumbprint",
+ certificatePassword: "password",
+ },
+ }),
+ });
+
+ const { packageCertificate } = getBundleResources("app.json", opts);
+ equal(
+ packageCertificate,
+ `true
+ $(ProjectRootDir)\\windows\\ReactTestApp_TemporaryKey.pfx
+ thumbprint
+ password`
+ );
+ });
+ }
+
+ it("returns app name and bundle resources (old arch)", () => {
const assets = path.join("dist", "assets");
const bundle = path.join("dist", "main.bundle");
setMockFiles({
@@ -28,7 +116,8 @@ describe("getBundleResources()", () => {
assetItems,
assetItemFilters,
assetFilters,
- } = getBundleResources("app.json");
+ contentItems,
+ } = getBundleResources("app.json", legacyOpts);
equal(appName, "Example");
equal(appxManifest, "windows\\Package.appxmanifest");
@@ -54,83 +143,45 @@ describe("getBundleResources()", () => {
assetFilters,
/^\s+{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}}<\/UniqueIdentifier>\s+<\/Filter>$/
);
+ equal(contentItems, "");
});
- it("returns package manifest", () => {
+ it("returns app name and bundle resources (new arch)", () => {
+ const assets = path.join("dist", "assets");
+ const bundle = path.join("dist", "main.bundle");
setMockFiles({
"app.json": JSON.stringify({
- windows: {
- appxManifest: "windows/Example/Package.appxmanifest",
- },
+ name: "Example",
+ resources: [assets, bundle],
}),
+ [path.join(assets, "app.json")]: "{}",
+ [bundle]: "'use strict';",
});
- deepEqual(getBundleResources("app.json"), {
- appName: "ReactTestApp",
- singleApp: undefined,
- appxManifest: "windows\\Example\\Package.appxmanifest",
- assetItems: "",
- assetItemFilters: "",
- assetFilters: "",
- packageCertificate: "",
- });
- });
-
- it("handles missing manifest", (t) => {
- const warnMock = t.mock.method(console, "warn", () => null);
-
- deepEqual(getBundleResources(""), {
- appName: "ReactTestApp",
- appxManifest: "windows/Package.appxmanifest",
- assetItems: "",
- assetItemFilters: "",
- assetFilters: "",
- packageCertificate: "",
- });
-
- equal(
- warnMock.mock.calls[0].arguments[1],
- "Could not find 'app.json' file."
- );
- });
-
- it("handles invalid manifest", (t) => {
- const warnMock = t.mock.method(console, "warn", () => null);
- setMockFiles({ "app.json": "-" });
-
- deepEqual(getBundleResources("app.json"), {
- appName: "ReactTestApp",
- appxManifest: "windows/Package.appxmanifest",
- assetItems: "",
- assetItemFilters: "",
- assetFilters: "",
- packageCertificate: "",
- });
-
- match(
- warnMock.mock.calls[0].arguments[1],
- /^Could not parse 'app.json':\n/
- );
- });
-
- it("returns package certificate", () => {
- setMockFiles({
- "app.json": JSON.stringify({
- windows: {
- certificateKeyFile: "windows/ReactTestApp_TemporaryKey.pfx",
- certificateThumbprint: "thumbprint",
- certificatePassword: "password",
- },
- }),
- });
+ const {
+ appName,
+ appxManifest,
+ assetItems,
+ assetItemFilters,
+ assetFilters,
+ contentItems,
+ } = getBundleResources("app.json", newArchOpts);
- const { packageCertificate } = getBundleResources("app.json");
+ equal(appName, "Example");
+ equal(appxManifest, "windows\\Package.appxmanifest");
+ equal(assetItems, "");
+ equal(assetItemFilters, "");
+ equal(assetFilters, "");
equal(
- packageCertificate,
- `true
- $(ProjectRootDir)\\windows\\ReactTestApp_TemporaryKey.pfx
- thumbprint
- password`
+ contentItems,
+ `
+ Bundle\\assets\\app.json
+ PreserveNewest
+
+
+ Bundle\\main.bundle
+ PreserveNewest
+ `
);
});
});
diff --git a/packages/app/test/windows/parseResources.test.mts b/packages/app/test/windows/parseResources.test.mts
index bc1b438f4..96cc91271 100644
--- a/packages/app/test/windows/parseResources.test.mts
+++ b/packages/app/test/windows/parseResources.test.mts
@@ -4,31 +4,59 @@ import { parseResources as parseResourcesActual } from "../../windows/project.mj
import { fs, setMockFiles } from "../fs.mock.mts";
describe("parseResources()", () => {
- const parseResources: typeof parseResourcesActual = (r, p) =>
- parseResourcesActual(r, p, fs);
+ const parseResources: typeof parseResourcesActual = (r, p, opts) =>
+ parseResourcesActual(r, p, opts, fs);
- const empty = { assetFilters: "", assetItemFilters: "", assetItems: "" };
+ const empty = {
+ assetFilters: "",
+ assetItemFilters: "",
+ assetItems: "",
+ contentItems: "",
+ };
+
+ const legacyOpts = { useFabric: false };
+ const newArchOpts = { useFabric: true };
afterEach(() => setMockFiles());
- it("returns empty strings for no resources", () => {
- deepEqual(parseResources(undefined, ""), empty);
- deepEqual(parseResources([], ""), empty);
- deepEqual(parseResources({}, ""), empty);
- deepEqual(parseResources({ windows: [] }, ""), empty);
- });
+ for (const opts of [legacyOpts, newArchOpts]) {
+ const arch = opts.useFabric ? "new" : "old";
+
+ it(`returns empty strings for no resources (${arch} arch)`, () => {
+ deepEqual(parseResources(undefined, "", opts), empty);
+ deepEqual(parseResources([], "", opts), empty);
+ deepEqual(parseResources({}, "", opts), empty);
+ deepEqual(parseResources({ windows: [] }, "", opts), empty);
+ });
+
+ it(`skips missing assets (${arch} arch)`, (t) => {
+ const warnMock = t.mock.method(console, "warn", () => null);
+
+ const resources = ["dist/assets", "dist/main.bundle"];
+
+ deepEqual(parseResources(resources, ".", opts), empty);
+
+ equal(
+ warnMock.mock.calls[0].arguments[1],
+ "Resource not found: dist/assets"
+ );
+ equal(
+ warnMock.mock.calls[1].arguments[1],
+ "Resource not found: dist/main.bundle"
+ );
+ });
+ }
- it("returns references to existing assets", () => {
+ it("returns references to existing assets (old arch)", () => {
setMockFiles({
"dist/assets/node_modules/arnold/portrait.png": "{}",
"dist/assets/splash.png": "{}",
"dist/main.jsbundle": "'use strict';",
});
- const { assetItems, assetItemFilters, assetFilters } = parseResources(
- ["dist/assets", "dist/main.jsbundle"],
- "."
- );
+ const { assetItems, assetItemFilters, assetFilters, contentItems } =
+ parseResources(["dist/assets", "dist/main.jsbundle"], ".", legacyOpts);
+
equal(
assetItems,
`
@@ -57,20 +85,36 @@ describe("parseResources()", () => {
assetFilters,
/^\s+{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}}<\/UniqueIdentifier>\s+<\/Filter>\s+\s+{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}}<\/UniqueIdentifier>\s+<\/Filter>\s+\s+{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}}<\/UniqueIdentifier>\s+<\/Filter>$/
);
+ equal(contentItems, "");
});
- it("skips missing assets", (t) => {
- const warnMock = t.mock.method(console, "warn", () => null);
+ it("returns references to existing assets (new arch)", () => {
+ setMockFiles({
+ "dist/assets/node_modules/arnold/portrait.png": "{}",
+ "dist/assets/splash.png": "{}",
+ "dist/main.jsbundle": "'use strict';",
+ });
- deepEqual(parseResources(["dist/assets", "dist/main.bundle"], "."), empty);
+ const { assetItems, assetItemFilters, assetFilters, contentItems } =
+ parseResources(["dist/assets", "dist/main.jsbundle"], ".", newArchOpts);
+ equal(assetItems, "");
+ equal(assetItemFilters, "");
+ equal(assetFilters, "");
equal(
- warnMock.mock.calls[0].arguments[1],
- "Resource not found: dist/assets"
- );
- equal(
- warnMock.mock.calls[1].arguments[1],
- "Resource not found: dist/main.bundle"
+ contentItems,
+ `
+ Bundle\\assets\\node_modules\\arnold\\portrait.png
+ PreserveNewest
+
+
+ Bundle\\assets\\splash.png
+ PreserveNewest
+
+
+ Bundle\\main.jsbundle
+ PreserveNewest
+ `
);
});
});
diff --git a/packages/app/windows/Shared/ReactInstance.cpp b/packages/app/windows/Shared/ReactInstance.cpp
index bf5fa74ae..60418c4db 100644
--- a/packages/app/windows/Shared/ReactInstance.cpp
+++ b/packages/app/windows/Shared/ReactInstance.cpp
@@ -46,11 +46,23 @@ namespace
winrt::hstring const kUseWebDebugger = L"useWebDebugger";
#endif // USE_WEB_DEBUGGER
- std::optional GetBundleName(std::optional const &bundleRoot)
+ std::filesystem::path GetBundleRootPath()
+ {
+#if USE_FABRIC
+ WCHAR modulePath[MAX_PATH];
+ GetModuleFileNameW(nullptr, modulePath, MAX_PATH);
+ PathCchRemoveFileSpec(modulePath, MAX_PATH);
+ return std::filesystem::path{modulePath}.replace_filename(L"Bundle") / L"";
+#else // USE_FABRIC
+ return std::filesystem::path{L"Bundle\\"};
+#endif // USE_FABRIC
+ }
+
+ std::optional GetBundleName(std::filesystem::path bundlePath,
+ std::optional const &bundleRoot)
{
constexpr std::wstring_view const bundleExtension = L".bundle";
- std::filesystem::path bundlePath{L"Bundle\\"};
if (bundleRoot.has_value()) {
std::wstring_view root = bundleRoot.value();
for (auto &&ext : {L".windows", L".native", L""}) {
@@ -104,77 +116,40 @@ std::vector const ReactTestApp::JSBundleNames = {
L"main",
};
-ReactInstance::ReactInstance()
-{
- reactNativeHost_.PackageProviders().Append(winrt::make());
- winrt::Microsoft::ReactNative::RegisterAutolinkedNativeModulePackages(
- reactNativeHost_.PackageProviders());
-
- reactNativeHost_.InstanceSettings().InstanceLoaded(
- [this](winrt::IInspectable const & /*sender*/, winrt::InstanceLoadedEventArgs const &args) {
- context_ = args.Context();
-
-#if __has_include("AppRegistry.h") && __has_include()
- if (!onComponentsRegistered_) {
- return;
- }
-
- winrt::Microsoft::ReactNative::ExecuteJsi(context_, [this](Runtime &runtime) noexcept {
- try {
- onComponentsRegistered_(ReactTestApp::GetAppKeys(runtime));
- } catch ([[maybe_unused]] std::exception const &e) {
-#if defined(_DEBUG) && !defined(DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION)
- if (IsDebuggerPresent()) {
- __debugbreak();
- }
-#endif // defined(_DEBUG) && !defined(DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION)
- }
- });
-#endif // __has_include("AppRegistry.h") && __has_include()
- });
-}
-
-#if __has_include()
-ReactInstance::ReactInstance(HWND hwnd,
- winrt::Microsoft::UI::Composition::Compositor const &compositor)
- : ReactInstance()
-{
- winrt::Microsoft::ReactNative::ReactCoreInjection::SetTopLevelWindowId(
- reactNativeHost_.InstanceSettings().Properties(), reinterpret_cast(hwnd));
-
- // By using the MicrosoftCompositionContextHelper here, React Native Windows
- // will use Lifted Visuals for its tree.
- winrt::Microsoft::ReactNative::Composition::CompositionUIService::SetCompositor(
- reactNativeHost_.InstanceSettings(), compositor);
-}
-#endif // __has_include()
-
-bool ReactInstance::LoadJSBundleFrom(JSBundleSource source)
+bool ReactInstance::LoadJSBundleFrom(JSBundleSource source, bool reloadHost)
{
source_ = source;
- auto instanceSettings = reactNativeHost_.InstanceSettings();
switch (source) {
case JSBundleSource::DevServer:
- instanceSettings.JavaScriptBundleFile(L"index");
+ // "Fast Refresh" determines whether the bundle is loaded from the
+ // dev server (since at least 0.76)
+ // https://github.com/microsoft/react-native-windows/blob/react-native-windows_v0.76.17/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp#L641
+ UseFastRefresh(true, reloadHost);
break;
case JSBundleSource::Embedded:
- auto const &bundleName = GetBundleName(bundleRoot_);
- if (!bundleName.has_value()) {
- return false;
- }
- instanceSettings.JavaScriptBundleFile(bundleName.value());
+ UseFastRefresh(false, reloadHost);
break;
}
- Reload();
return true;
}
-void ReactInstance::Reload()
+void ReactInstance::Reload(bool reloadHost)
{
auto instanceSettings = reactNativeHost_.InstanceSettings();
+ instanceSettings.DebugBundlePath(L"index");
+
+ auto const bundleRootPath = GetBundleRootPath();
+#if USE_FABRIC
+ instanceSettings.BundleRootPath(L"file://" + bundleRootPath.wstring());
+#endif // USE_FABRIC
+ auto const &bundleName = GetBundleName(bundleRootPath, bundleRoot_);
+ if (bundleName.has_value()) {
+ instanceSettings.JavaScriptBundleFile(bundleName.value());
+ }
+
#if USE_WEB_DEBUGGER
instanceSettings.UseWebDebugger(UseWebDebugger());
#endif // USE_WEB_DEBUGGER
@@ -196,7 +171,9 @@ void ReactInstance::Reload()
instanceSettings.SourceBundleHost(host);
instanceSettings.SourceBundlePort(static_cast(port));
- reactNativeHost_.ReloadInstance();
+ if (reloadHost) {
+ reactNativeHost_.ReloadInstance();
+ }
}
bool ReactInstance::BreakOnFirstLine() const
@@ -270,10 +247,10 @@ bool ReactInstance::UseFastRefresh() const
return IsFastRefreshAvailable() && RetrieveLocalSetting(kUseFastRefresh, true);
}
-void ReactInstance::UseFastRefresh(bool useFastRefresh)
+void ReactInstance::UseFastRefresh(bool useFastRefresh, bool reloadHost)
{
StoreLocalSetting(kUseFastRefresh, useFastRefresh);
- Reload();
+ Reload(reloadHost);
}
bool ReactInstance::UseWebDebugger() const
@@ -297,6 +274,35 @@ void ReactInstance::UseWebDebugger(bool useWebDebugger)
#endif // USE_WEB_DEBUGGER
}
+void ReactInstance::InitializeHost(winrt::Microsoft::ReactNative::ReactNativeHost host)
+{
+ host.PackageProviders().Append(winrt::make());
+ winrt::Microsoft::ReactNative::RegisterAutolinkedNativeModulePackages(host.PackageProviders());
+
+ host.InstanceSettings().InstanceLoaded(
+ [this](winrt::IInspectable const & /*sender*/, winrt::InstanceLoadedEventArgs const &args) {
+ context_ = args.Context();
+
+#if __has_include("AppRegistry.h") && __has_include()
+ if (!onComponentsRegistered_) {
+ return;
+ }
+
+ winrt::Microsoft::ReactNative::ExecuteJsi(context_, [this](Runtime &runtime) noexcept {
+ try {
+ onComponentsRegistered_(ReactTestApp::GetAppKeys(runtime));
+ } catch ([[maybe_unused]] std::exception const &e) {
+#if defined(_DEBUG) && !defined(DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION)
+ if (IsDebuggerPresent()) {
+ __debugbreak();
+ }
+#endif // defined(_DEBUG) && !defined(DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION)
+ }
+ });
+#endif // __has_include("AppRegistry.h") && __has_include()
+ });
+}
+
winrt::IAsyncOperation ReactTestApp::IsDevServerRunning()
{
winrt::Uri uri(L"http://localhost:8081/status");
diff --git a/packages/app/windows/Shared/ReactInstance.h b/packages/app/windows/Shared/ReactInstance.h
index 2353d4759..edbf5d736 100644
--- a/packages/app/windows/Shared/ReactInstance.h
+++ b/packages/app/windows/Shared/ReactInstance.h
@@ -32,19 +32,24 @@ namespace ReactTestApp
public:
static constexpr uint32_t Version = REACT_NATIVE_VERSION;
- ReactInstance();
+ ReactInstance()
+ {
+ InitializeHost(reactNativeHost_);
+ }
-#if __has_include()
- ReactInstance(HWND hwnd, winrt::Microsoft::UI::Composition::Compositor const &);
-#endif // __has_include()
+ ReactInstance(winrt::Microsoft::ReactNative::ReactNativeHost reactNativeHost)
+ : reactNativeHost_(reactNativeHost)
+ {
+ InitializeHost(reactNativeHost);
+ }
auto const &ReactHost() const
{
return reactNativeHost_;
}
- bool LoadJSBundleFrom(JSBundleSource);
- void Reload();
+ bool LoadJSBundleFrom(JSBundleSource, bool reloadHost = true);
+ void Reload(bool reloadHost = true);
bool BreakOnFirstLine() const;
void BreakOnFirstLine(bool);
@@ -89,7 +94,7 @@ namespace ReactTestApp
void UseDirectDebugger(bool);
bool UseFastRefresh() const;
- void UseFastRefresh(bool);
+ void UseFastRefresh(bool, bool reloadHost = true);
bool UseWebDebugger() const;
void UseWebDebugger(bool);
@@ -100,6 +105,8 @@ namespace ReactTestApp
std::optional bundleRoot_;
JSBundleSource source_ = JSBundleSource::DevServer;
OnComponentsRegistered onComponentsRegistered_;
+
+ void InitializeHost(winrt::Microsoft::ReactNative::ReactNativeHost);
};
winrt::Windows::Foundation::IAsyncOperation IsDevServerRunning();
diff --git a/packages/app/windows/Win32/Main.cpp b/packages/app/windows/Win32/Main.cpp
index 91da5f7ea..102d6826c 100644
--- a/packages/app/windows/Win32/Main.cpp
+++ b/packages/app/windows/Win32/Main.cpp
@@ -9,20 +9,7 @@
namespace winrt
{
using winrt::Microsoft::ReactNative::IJSValueWriter;
- using winrt::Microsoft::ReactNative::LayoutDirection;
- using winrt::Microsoft::ReactNative::ReactCoreInjection;
- using winrt::Microsoft::ReactNative::ReactNativeIsland;
using winrt::Microsoft::ReactNative::ReactViewOptions;
- using winrt::Microsoft::UI::Composition::Compositor;
- using winrt::Microsoft::UI::Content::ContentSizePolicy;
- using winrt::Microsoft::UI::Content::DesktopChildSiteBridge;
- using winrt::Microsoft::UI::Dispatching::DispatcherQueueController;
- using winrt::Microsoft::UI::Windowing::AppWindow;
- using winrt::Microsoft::UI::Windowing::AppWindowChangedEventArgs;
- using winrt::Microsoft::UI::Windowing::OverlappedPresenter;
- using winrt::Microsoft::UI::Windowing::OverlappedPresenterState;
- using winrt::Windows::Foundation::AsyncStatus;
- using winrt::Windows::Foundation::Size;
} // namespace winrt
namespace
@@ -34,30 +21,9 @@ namespace
#endif
constexpr bool kSingleAppMode = static_cast(ENABLE_SINGLE_APP_MODE);
- float ScaleFactor(HWND hwnd) noexcept
+ void ConfigureReactViewOptions(winrt::ReactViewOptions viewOptions,
+ ReactApp::Component const &component)
{
- return GetDpiForWindow(hwnd) / static_cast(USER_DEFAULT_SCREEN_DPI);
- }
-
- void UpdateRootViewSizeToAppWindow(winrt::ReactNativeIsland const &rootView,
- winrt::AppWindow const &window)
- {
- // Do not relayout when minimized
- auto windowState = window.Presenter().as().State();
- if (windowState == winrt::OverlappedPresenterState::Minimized) {
- return;
- }
-
- auto hwnd = winrt::Microsoft::UI::GetWindowFromWindowId(window.Id());
- auto scaleFactor = ScaleFactor(hwnd);
- winrt::Size size{window.ClientSize().Width / scaleFactor,
- window.ClientSize().Height / scaleFactor};
- rootView.Arrange({size, size, winrt::LayoutDirection::Undefined}, {0, 0});
- }
-
- winrt::ReactViewOptions MakeReactViewOptions(ReactApp::Component const &component)
- {
- winrt::ReactViewOptions viewOptions;
viewOptions.ComponentName(winrt::to_hstring(component.appKey));
auto initialProps = component.initialProperties.value_or({});
@@ -71,8 +37,6 @@ namespace
}
writer.WriteObjectEnd();
});
-
- return viewOptions;
}
} // namespace
@@ -91,22 +55,8 @@ _Use_decl_annotations_ int CALLBACK WinMain(HINSTANCE /* instance */,
// Enable per monitor DPI scaling
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
- // Create a DispatcherQueue for this thread. This is needed for Composition, Content, and
- // Input APIs.
- auto dispatcherQueueController = winrt::DispatcherQueueController::CreateOnCurrentThread();
-
- // Create a Compositor for all Content on this thread.
- auto compositor = winrt::Compositor{};
-
- // Create a top-level window.
- auto window = winrt::AppWindow::Create();
- window.Title(winrt::to_hstring(manifest.displayName));
- window.Resize({600, 800});
- window.Show();
- auto hwnd = winrt::Microsoft::UI::GetWindowFromWindowId(window.Id());
- auto scaleFactor = ScaleFactor(hwnd);
-
- auto instance = ReactTestApp::ReactInstance{hwnd, compositor};
+ auto app = winrt::Microsoft::ReactNative::ReactNativeAppBuilder().Build();
+ auto instance = ReactTestApp::ReactInstance{app.ReactNativeHost()};
if (manifest.bundleRoot.has_value()) {
auto &bundleRoot = *manifest.bundleRoot;
instance.BundleRoot(std::make_optional(winrt::to_hstring(bundleRoot)));
@@ -115,81 +65,31 @@ _Use_decl_annotations_ int CALLBACK WinMain(HINSTANCE /* instance */,
// Start the react-native instance, which will create a JavaScript runtime and load the
// applications bundle
if constexpr (kDebug) {
- instance.LoadJSBundleFrom(ReactTestApp::JSBundleSource::DevServer);
+ instance.LoadJSBundleFrom(ReactTestApp::JSBundleSource::DevServer, false);
} else {
- instance.LoadJSBundleFrom(ReactTestApp::JSBundleSource::Embedded);
+ instance.LoadJSBundleFrom(ReactTestApp::JSBundleSource::Embedded, false);
}
- // Create a RootView which will present a react-native component
- winrt::ReactViewOptions viewOptions;
+ // Configure ReactViewOptions to load the initial component
if constexpr (kSingleAppMode) {
assert(manifest.singleApp.has_value() ||
!"`ENABLE_SINGLE_APP_MODE` shouldn't have been true");
for (auto &component : *manifest.components) {
if (component.slug == *manifest.singleApp) {
- viewOptions = MakeReactViewOptions(component);
+ ConfigureReactViewOptions(app.ReactViewOptions(), component);
break;
}
}
} else {
// TODO: Implement session restoration
auto &component = (*manifest.components)[0];
- viewOptions = MakeReactViewOptions(component);
+ ConfigureReactViewOptions(app.ReactViewOptions(), component);
}
- auto rootView = winrt::ReactNativeIsland{compositor};
- rootView.ReactViewHost(
- winrt::ReactCoreInjection::MakeViewHost(instance.ReactHost(), viewOptions));
-
- // Update the size of the RootView when the AppWindow changes size
- window.Changed(
- [wkRootView = winrt::make_weak(rootView)](winrt::AppWindow const &window,
- winrt::AppWindowChangedEventArgs const &args) {
- if (args.DidSizeChange() || args.DidVisibilityChange()) {
- if (auto rootView = wkRootView.get()) {
- UpdateRootViewSizeToAppWindow(rootView, window);
- }
- }
- });
-
- // Quit application when main window is closed
- window.Destroying([&host = instance.ReactHost()](winrt::AppWindow const & /* window */,
- winrt::IInspectable const & /* args */) {
- // Before we shutdown the application - unload the ReactNativeHost to give the javascript a
- // chance to save any state
- auto async = host.UnloadInstance();
- async.Completed([host](auto asyncInfo, winrt::AsyncStatus asyncStatus) {
- assert(asyncStatus == winrt::AsyncStatus::Completed);
- host.InstanceSettings().UIDispatcher().Post([]() { PostQuitMessage(0); });
- });
- });
-
- // DesktopChildSiteBridge create a ContentSite that can host the RootView ContentIsland
- auto bridge = winrt::DesktopChildSiteBridge::Create(compositor, window.Id());
- bridge.Connect(rootView.Island());
- bridge.ResizePolicy(winrt::ContentSizePolicy::ResizeContentToParentWindow);
-
- auto invScale = 1.0f / scaleFactor;
- rootView.RootVisual().Scale({invScale, invScale, invScale});
- rootView.ScaleFactor(scaleFactor);
-
- // Set the intialSize of the root view
- UpdateRootViewSizeToAppWindow(rootView, window);
-
- bridge.Show();
-
- // Run the main application event loop
- dispatcherQueueController.DispatcherQueue().RunEventLoop();
-
- // Rundown the DispatcherQueue. This drains the queue and raises events to let components
- // know the message loop has finished.
- dispatcherQueueController.ShutdownQueue();
-
- bridge.Close();
- bridge = nullptr;
+ auto window = app.AppWindow();
+ window.Title(winrt::to_hstring(manifest.displayName));
+ window.Resize({600, 800});
- // Destroy all Composition objects
- compositor.Close();
- compositor = nullptr;
+ app.Start();
}
diff --git a/packages/app/windows/Win32/ReactApp.Package.wapproj b/packages/app/windows/Win32/ReactApp.Package.wapproj
index 02a30f8a5..ea9bc070f 100644
--- a/packages/app/windows/Win32/ReactApp.Package.wapproj
+++ b/packages/app/windows/Win32/ReactApp.Package.wapproj
@@ -25,6 +25,7 @@
-->
$([MSBuild]::MakeRelative($(MSBuildThisFileDirectory), $(SolutionDir)))\$(MSBuildProjectName)\bin\
$(BaseOutputPath)\$(Platform)\$(Configuration)\
+ $([MSBuild]::GetDirectoryNameOfFileAbove($(SolutionDir), 'app.json'))
$(MSBuildExtensionsPath)\Microsoft\DesktopBridge\
@@ -83,6 +84,7 @@
+
diff --git a/packages/app/windows/Win32/ReactApp.vcxproj b/packages/app/windows/Win32/ReactApp.vcxproj
index 12cc2dc8d..c0c267ad5 100644
--- a/packages/app/windows/Win32/ReactApp.vcxproj
+++ b/packages/app/windows/Win32/ReactApp.vcxproj
@@ -135,7 +135,6 @@
-
diff --git a/packages/app/windows/project.mjs b/packages/app/windows/project.mjs
index 6ee2d617b..bc56b0515 100644
--- a/packages/app/windows/project.mjs
+++ b/packages/app/windows/project.mjs
@@ -126,7 +126,7 @@ function warn(message) {
* @param {string} source
* @returns {AssetItems}
*/
-function generateContentItems(
+function generateAssetItems(
resources,
projectPath,
assets = { assetFilters: [], assetItemFilters: [], assetItems: [] },
@@ -159,7 +159,7 @@ function generateContentItems(
const files = fs
.readdirSync(resourcePath)
.map((file) => path.join(resource, file));
- generateContentItems(
+ generateAssetItems(
files,
projectPath,
assets,
@@ -168,7 +168,7 @@ function generateContentItems(
fs
);
} else {
- const assetPath = normalizePath(path.relative(projectPath, resourcePath));
+ const assetPath = normalizePath(resourcePath);
/**
* When a resources folder is included in the manifest, the directory
* structure within the folder must be maintained. For example, given
@@ -199,6 +199,53 @@ function generateContentItems(
return assets;
}
+/**
+ * @param {string[]} resources
+ * @param {string} projectPath
+ * @param {string=} currentDir
+ * @param {string=} destination
+ * @param {string[]=} result
+ * @returns {string[]}
+ */
+function generateContentItems(
+ resources,
+ projectPath,
+ currentDir = ".",
+ destination = "Bundle",
+ result = [],
+ fs = nodefs
+) {
+ for (const res of resources) {
+ const assetPath = path.isAbsolute(res)
+ ? path.relative(projectPath, res)
+ : path.join(currentDir, res);
+ if (!fs.existsSync(assetPath)) {
+ warn(`Resource not found: ${res}`);
+ continue;
+ }
+
+ const link = `${destination}\\${path.basename(assetPath)}`;
+ if (fs.statSync(assetPath).isDirectory()) {
+ generateContentItems(
+ fs.readdirSync(assetPath),
+ projectPath,
+ assetPath,
+ link,
+ result,
+ fs
+ );
+ } else {
+ result.push(
+ ``,
+ ` ${link}`,
+ ` PreserveNewest`,
+ ``
+ );
+ }
+ }
+ return result;
+}
+
/**
* Finds NuGet dependencies.
*
@@ -318,38 +365,60 @@ export function importTargets(refs) {
/**
* @param {string[] | { windows?: string[] } | undefined} resources
* @param {string} projectPath
+ * @param {Pick} options
* @returns {Assets}
*/
-export function parseResources(resources, projectPath, fs = nodefs) {
+export function parseResources(resources, projectPath, options, fs = nodefs) {
if (!Array.isArray(resources)) {
if (resources?.windows) {
- return parseResources(resources.windows, projectPath, fs);
+ return parseResources(resources.windows, projectPath, options, fs);
}
- return { assetItems: "", assetItemFilters: "", assetFilters: "" };
- }
+ return {
+ assetItems: "",
+ assetItemFilters: "",
+ assetFilters: "",
+ contentItems: "",
+ };
+ } else if (!options.useFabric) {
+ const { assetItems, assetItemFilters, assetFilters } = generateAssetItems(
+ resources,
+ projectPath,
+ /* assets */ undefined,
+ /* currentFilter */ undefined,
+ /* source */ undefined,
+ fs
+ );
- const { assetItems, assetItemFilters, assetFilters } = generateContentItems(
- resources,
- projectPath,
- /* assets */ undefined,
- /* currentFilter */ undefined,
- /* source */ undefined,
- fs
- );
+ return {
+ assetItems: assetItems.join("\n "),
+ assetItemFilters: assetItemFilters.join("\n "),
+ assetFilters: assetFilters.join("\n "),
+ contentItems: "",
+ };
+ }
return {
- assetItems: assetItems.join("\n "),
- assetItemFilters: assetItemFilters.join("\n "),
- assetFilters: assetFilters.join("\n "),
+ assetItems: "",
+ assetItemFilters: "",
+ assetFilters: "",
+ contentItems: generateContentItems(
+ resources,
+ projectPath,
+ /** currentDir */ undefined,
+ /** destination */ undefined,
+ /** result */ undefined,
+ fs
+ ).join("\n "),
};
}
/**
* Reads manifest file and and resolves paths to bundle resources.
* @param {string | null} manifestFilePath Path to the closest manifest file.
+ * @param {Pick} options
* @returns {AppxBundle} Application name, and paths to directories and files to include.
*/
-export function getBundleResources(manifestFilePath, fs = nodefs) {
+export function getBundleResources(manifestFilePath, options, fs = nodefs) {
// Default value if manifest or 'name' field don't exist.
const defaultName = "ReactTestApp";
@@ -374,7 +443,7 @@ export function getBundleResources(manifestFilePath, fs = nodefs) {
windows || {},
projectPath
),
- ...parseResources(resources, projectPath, fs),
+ ...parseResources(resources, projectPath, options, fs),
};
} catch (e) {
if (isErrorLike(e)) {
@@ -393,6 +462,7 @@ export function getBundleResources(manifestFilePath, fs = nodefs) {
assetItems: "",
assetItemFilters: "",
assetFilters: "",
+ contentItems: "",
packageCertificate: "",
};
}
@@ -404,21 +474,22 @@ export function getBundleResources(manifestFilePath, fs = nodefs) {
* @returns {Promise}
*/
export async function projectInfo(
- { useFabric, useNuGet },
+ options,
rnWindowsPath,
destPath,
fs = nodefs
) {
const version = getPackageVersion("react-native-windows", rnWindowsPath, fs);
const versionNumber = toVersionNumber(version);
- const newArch = useFabric ?? versionNumber >= v(0, 80, 0);
+ const manifestFilePath = findNearest("app.json", destPath, fs);
+ const useFabric = options.useFabric ?? versionNumber >= v(0, 80, 0);
return {
version,
versionNumber,
- bundle: getBundleResources(findNearest("app.json", destPath, fs), fs),
+ bundle: getBundleResources(manifestFilePath, { useFabric }, fs),
nugetDependencies: await getNuGetDependencies(rnWindowsPath),
- useExperimentalNuGet: newArch || useNuGet,
- useFabric: newArch,
+ useExperimentalNuGet: useFabric || options.useNuGet,
+ useFabric,
};
}
diff --git a/packages/app/windows/win32.mjs b/packages/app/windows/win32.mjs
index 96130083e..b97ac352e 100644
--- a/packages/app/windows/win32.mjs
+++ b/packages/app/windows/win32.mjs
@@ -18,12 +18,16 @@ export function configureForWin32({
["Main.rc"],
["Main.small.ico"],
["Package.appxmanifest"],
- ["ReactApp.Package.wapproj"],
+ [
+ "ReactApp.Package.wapproj",
+ {
+ "": bundle.contentItems,
+ },
+ ],
[
"ReactApp.vcxproj",
{
"REACT_NATIVE_VERSION=1000000000;": `REACT_NATIVE_VERSION=${versionNumber};`,
- "": bundle.assetItems,
"":
importTargets(nugetDependencies),
...(typeof bundle.singleApp === "string"
@@ -31,13 +35,7 @@ export function configureForWin32({
: undefined),
},
],
- [
- "ReactApp.vcxproj.filters",
- {
- "": bundle.assetItemFilters,
- "": bundle.assetFilters,
- },
- ],
+ ["ReactApp.vcxproj.filters"],
["resource.h"],
],
solutionTemplatePath: path.join(