diff --git a/.ado/jobs/cli-init-windows.yml b/.ado/jobs/cli-init-windows.yml index 35e9736c8bb..cacf9e74b01 100644 --- a/.ado/jobs/cli-init-windows.yml +++ b/.ado/jobs/cli-init-windows.yml @@ -22,6 +22,16 @@ parameters: configuration: Debug platform: x86 additionalRunArguments: --no-autolink + - Name: FabricLibX64Release + template: cpp-lib + configuration: Release + platform: x64 + additionalRunArguments: + - Name: FabricLibX86Debug + template: cpp-lib + configuration: Debug + platform: x86 + additionalRunArguments: - BuildEnvironment: Continuous Matrix: - Name: FabricX64Debug @@ -44,6 +54,26 @@ parameters: configuration: Release platform: x86 additionalRunArguments: --no-autolink + - Name: FabricLibX64Debug + template: cpp-lib + configuration: Debug + platform: x64 + additionalRunArguments: + - Name: FabricLibX64Release + template: cpp-lib + configuration: ReleaseLib + platform: x64 + additionalRunArguments: + - Name: FabricLibX86Debug + template: cpp-lib + configuration: Debug + platform: x86 + additionalRunArguments: + - Name: FabricLibX86Release + template: cpp-lib + configuration: Release + platform: x86 + additionalRunArguments: jobs: - ${{ each config in parameters.buildMatrix }}: - ${{ if eq(config.BuildEnvironment, parameters.buildEnvironment) }}: diff --git a/.ado/templates/react-native-init-windows.yml b/.ado/templates/react-native-init-windows.yml index 7d977a95082..f48cea9335f 100644 --- a/.ado/templates/react-native-init-windows.yml +++ b/.ado/templates/react-native-init-windows.yml @@ -40,49 +40,70 @@ steps: - ${{ if endsWith(parameters.template, '-app') }}: - script: | npx --yes react-native@$(reactNativeDevDependency) init testcli --template react-native@$(reactNativeDevDependency) - displayName: Init new app project + displayName: Init new app project with react-native init workingDirectory: $(Agent.BuildDirectory) - ${{ if endsWith(parameters.template, '-lib') }}: - script: | - npx --yes create-react-native-module@0.20.2 --package-name "testcli" testcli - displayName: Init new lib project + npx --yes create-react-native-library@latest --slug testcli --description testcli --author-name "React-Native-Windows Bot" --author-email 53619745+rnbot@users.noreply.github.com --author-url http://example.com --repo-url http://example.com --languages java-objc --type module-new --react-native-version $(reactNativeDevDependency) testcli + displayName: Init new lib project with create-react-native-library workingDirectory: $(Agent.BuildDirectory) - - script: | - rmdir /s /q android - displayName: Remove broken android folder # See issue https://github.com/microsoft/react-native-windows/issues/12209 - workingDirectory: $(Agent.BuildDirectory)\testcli - - script: | call yarn install - call yarn upgrade react@$(reactDevDependency) --dev - call yarn upgrade react-native@$(reactNativeDevDependency) --dev - displayName: Update project react and react-native dev versions - workingDirectory: $(Agent.BuildDirectory)\testcli - - - script: | - call yarn add react-native-windows@$(npmVersion) - displayName: yarn add react-native-windows@$(npmVersion) + displayName: yarn install workingDirectory: $(Agent.BuildDirectory)\testcli env: - npm_config_registry: http://localhost:4873 + YARN_ENABLE_IMMUTABLE_INSTALLS: false + + - ${{ if endsWith(parameters.template, '-app') }}: + - script: | + call yarn upgrade react@$(reactDevDependency) --dev + call yarn upgrade react-native@$(reactNativeDevDependency) --dev + displayName: Update project react and react-native dev versions + workingDirectory: $(Agent.BuildDirectory)\testcli + + - script: | + call yarn add react-native-windows@$(npmVersion) + displayName: yarn add react-native-windows@$(npmVersion) + workingDirectory: $(Agent.BuildDirectory)\testcli + env: + npm_config_registry: http://localhost:4873 + + - ${{ if endsWith(parameters.template, '-lib') }}: + - script: | + call yarn config set npmRegistryServer http://localhost:4873 + call yarn config set unsafeHttpWhitelist --json "[\"localhost\"]" + call yarn add react-native-windows@$(npmVersion) --dev + call yarn add react-native-windows@* --peer + displayName: yarn add react-native-windows@$(npmVersion) + workingDirectory: $(Agent.BuildDirectory)\testcli + env: + YARN_ENABLE_IMMUTABLE_INSTALLS: false - script: | call yarn react-native init-windows --template ${{ parameters.template }} --overwrite --logging ${{ parameters.additionalInitArguments }} displayName: Call react-native init-windows workingDirectory: $(Agent.BuildDirectory)\testcli - env: - npm_config_registry: http://localhost:4873 - - ${{ if endsWith(parameters.template, '-app') }}: - - powershell: | - $path = (Get-ChildItem -Filter "Package.appxmanifest" -File -Recurse).FullName; - [xml] $manifest = Get-Content $path - $manifest.Package.Identity.Name = 'ReactNative.InitTest' - $manifest.Save("$path") - displayName: Set AppX package name to "ReactNative.InitTest" + - ${{ if endsWith(parameters.template, '-lib') }}: + - script: | + call yarn install + displayName: yarn install again + workingDirectory: $(Agent.BuildDirectory)\testcli + env: + YARN_ENABLE_IMMUTABLE_INSTALLS: false + + - powershell: | + $path = (Get-ChildItem -Filter "Package.appxmanifest" -File -Recurse).FullName; + [xml] $manifest = Get-Content $path + $manifest.Package.Identity.Name = 'ReactNative.InitTest' + $manifest.Save("$path") + displayName: Set AppX package name to "ReactNative.InitTest" + ${{ if endsWith(parameters.template, '-app') }}: workingDirectory: $(Agent.BuildDirectory)\testcli\windows + ${{ if endsWith(parameters.template, '-lib') }}: + workingDirectory: $(Agent.BuildDirectory)\testcli\example\windows # End npm test server - template: verdaccio-stop.yml @@ -95,7 +116,10 @@ steps: - template: react-native-debug-info.yml parameters: - workingDirectory: $(Agent.BuildDirectory)\testcli + ${{ if endsWith(parameters.template, '-app') }}: + workingDirectory: $(Agent.BuildDirectory)\testcli + ${{ if endsWith(parameters.template, '-lib') }}: + workingDirectory: $(Agent.BuildDirectory)\testcli\example - template: ../templates/run-windows-with-certificates.yml parameters: @@ -105,7 +129,10 @@ steps: buildPlatform: ${{ parameters.platform }} deployOption: ${{ parameters.additionalRunArguments }} buildLogDirectory: $(Build.BinariesDirectory)\${{ parameters.platform }}\${{ parameters.configuration }}\BuildLogs - workingDirectory: $(Agent.BuildDirectory)\testcli + ${{ if endsWith(parameters.template, '-app') }}: + workingDirectory: $(Agent.BuildDirectory)\testcli + ${{ if endsWith(parameters.template, '-lib') }}: + workingDirectory: $(Agent.BuildDirectory)\testcli\example restoreLockedMode: false # Allow new lockfile to be created - template: upload-build-logs.yml diff --git a/change/react-native-windows-f2a1a001-7c49-48af-8a21-a5947fbec507.json b/change/react-native-windows-f2a1a001-7c49-48af-8a21-a5947fbec507.json new file mode 100644 index 00000000000..b15137fc642 --- /dev/null +++ b/change/react-native-windows-f2a1a001-7c49-48af-8a21-a5947fbec507.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Create new arch module template: cpp-lib", + "packageName": "react-native-windows", + "email": "jthysell@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/vnext/PropertySheets/External/Microsoft.ReactNative.Composition.Common.props b/vnext/PropertySheets/External/Microsoft.ReactNative.Composition.Common.props index db2f915b039..3cf960cbc93 100644 --- a/vnext/PropertySheets/External/Microsoft.ReactNative.Composition.Common.props +++ b/vnext/PropertySheets/External/Microsoft.ReactNative.Composition.Common.props @@ -16,5 +16,13 @@ true + + + + + + true + + diff --git a/vnext/PropertySheets/External/Microsoft.ReactNative.Composition.CppLib.props b/vnext/PropertySheets/External/Microsoft.ReactNative.Composition.CppLib.props new file mode 100644 index 00000000000..f9f20164222 --- /dev/null +++ b/vnext/PropertySheets/External/Microsoft.ReactNative.Composition.CppLib.props @@ -0,0 +1,24 @@ + + + + + + true + false + + + + + + + diff --git a/vnext/PropertySheets/External/Microsoft.ReactNative.Composition.CppLib.targets b/vnext/PropertySheets/External/Microsoft.ReactNative.Composition.CppLib.targets new file mode 100644 index 00000000000..aa824160f5f --- /dev/null +++ b/vnext/PropertySheets/External/Microsoft.ReactNative.Composition.CppLib.targets @@ -0,0 +1,20 @@ + + + + + + + + + + + + + diff --git a/vnext/templates/cpp-app/metro.config.js b/vnext/templates/cpp-app/metro.config.js index f5e6fd068c8..686d4a9eda7 100644 --- a/vnext/templates/cpp-app/metro.config.js +++ b/vnext/templates/cpp-app/metro.config.js @@ -48,8 +48,6 @@ const config = { inlineRequires: true, }, }), - // This fixes the 'missing-asset-registry-path` error (see https://github.com/microsoft/react-native-windows/issues/11437) - assetRegistryPath: 'react-native/Libraries/Image/AssetRegistry', }, }; diff --git a/vnext/templates/cpp-app/template.config.js b/vnext/templates/cpp-app/template.config.js index 4627b7c6488..2bf5cb78f35 100644 --- a/vnext/templates/cpp-app/template.config.js +++ b/vnext/templates/cpp-app/template.config.js @@ -20,12 +20,7 @@ const templateUtils = require('../templateUtils'); async function preInstall(config = {}, options = {}) {} async function getFileMappings(config = {}, options = {}) { - const rnwPath = path.dirname( - require.resolve('react-native-windows', [config.root]), - ); - const rnwVersion = require(path.join(rnwPath, 'package.json')).version; - - const devMode = existsSync(path.join(rnwPath, 'src')); + const {rnwVersion, devMode} = templateUtils.getRnwInfo(config, options); const projectName = config?.project?.windows?.project?.projectName ?? options?.name ?? 'MyApp'; @@ -38,6 +33,10 @@ async function getFileMappings(config = {}, options = {}) { const packageGuid = uuid.v4(); const currentUser = username.sync(); // Gets the current username depending on the platform. + const appJsonPath = path.join(config?.root ?? process.cwd(), 'app.json'); + const mainComponentName = + (existsSync(appJsonPath) ? require(appJsonPath).name : null) ?? projectName; + const cppNugetPackages = []; const replacements = { @@ -50,7 +49,7 @@ async function getFileMappings(config = {}, options = {}) { rnwVersion: rnwVersion, - mainComponentName: projectName, // TODO: replace with app.json name + mainComponentName, // Visual Studio is very picky about the casing of the guids for projects, project references and the solution // https://www.bing.com/search?q=visual+studio+project+guid+casing&cvid=311a5ad7f9fc41089507b24600d23ee7&FORM=ANAB01&PC=U531 @@ -61,7 +60,7 @@ async function getFileMappings(config = {}, options = {}) { // packaging and signing variables: packageGuidLower: `{${packageGuid.toLowerCase()}}`, packageGuidUpper: `{${packageGuid.toUpperCase()}}`, - currentUser: currentUser, + currentUser, devMode, diff --git a/vnext/templates/cpp-app/windows/ExperimentalFeatures.props b/vnext/templates/cpp-app/windows/ExperimentalFeatures.props index 6b4e69eb6c1..be73cb5575a 100644 --- a/vnext/templates/cpp-app/windows/ExperimentalFeatures.props +++ b/vnext/templates/cpp-app/windows/ExperimentalFeatures.props @@ -3,7 +3,7 @@ true - false + true true diff --git a/vnext/templates/cpp-app/windows/MyApp/AutolinkedNativeModules.g.cpp b/vnext/templates/cpp-app/windows/MyApp/AutolinkedNativeModules.g.cpp new file mode 100644 index 00000000000..aa6e5db813d --- /dev/null +++ b/vnext/templates/cpp-app/windows/MyApp/AutolinkedNativeModules.g.cpp @@ -0,0 +1,13 @@ +// AutolinkedNativeModules.g.cpp contents generated by "react-native autolink-windows" +// clang-format off +#include "pch.h" +#include "AutolinkedNativeModules.g.h"{{ &autolinkCppIncludes }} + +namespace winrt::Microsoft::ReactNative +{ + +void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector const& packageProviders) +{ {{ &autolinkCppPackageProviders }} +} + +} diff --git a/vnext/templates/cpp-app/windows/MyApp/AutolinkedNativeModules.g.h b/vnext/templates/cpp-app/windows/MyApp/AutolinkedNativeModules.g.h new file mode 100644 index 00000000000..f28bb8be361 --- /dev/null +++ b/vnext/templates/cpp-app/windows/MyApp/AutolinkedNativeModules.g.h @@ -0,0 +1,10 @@ +// AutolinkedNativeModules.g.h contents generated by "react-native autolink-windows" +// clang-format off +#pragma once + +namespace winrt::Microsoft::ReactNative +{ + +void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector const& packageProviders); + +} diff --git a/vnext/templates/cpp-app/windows/MyApp/MyApp.cpp b/vnext/templates/cpp-app/windows/MyApp/MyApp.cpp index 6d3f3e7e012..15ac6aba18a 100644 --- a/vnext/templates/cpp-app/windows/MyApp/MyApp.cpp +++ b/vnext/templates/cpp-app/windows/MyApp/MyApp.cpp @@ -4,18 +4,10 @@ #include "pch.h" #include "{{ name }}.h" -#include -#include - -#include -#include +#include "AutolinkedNativeModules.g.h" #include "NativeModules.h" -#include "ReactPropertyBag.h" - -constexpr size_t MAX_LOADSTRING = 100; -// Have to use TurboModules to override built in modules.. so the standard attributed package provider doesn't work. struct CompReactPackageProvider : winrt::implements { public: // IReactPackageProvider @@ -25,229 +17,146 @@ struct CompReactPackageProvider }; // Global Variables: -WCHAR szTitle[MAX_LOADSTRING]; // The title bar text -WCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name - -winrt::Windows::System::DispatcherQueueController g_dispatcherQueueController{nullptr}; -winrt::Windows::UI::Composition::Compositor g_compositor{nullptr}; - -constexpr auto WindowDataProperty = L"WindowData"; -constexpr PCWSTR c_windowClassName = L"MS_REACTNATIVE_RNTESTER_COMPOSITION"; -constexpr PCWSTR appName = L"{{ mainComponentName }}"; - -// Forward declarations of functions included in this code module: -LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); -int RunRNTester(int showCmd); - -struct WindowData { - static HINSTANCE s_instance; - static constexpr uint16_t defaultDebuggerPort{9229}; - - bool m_windowInited{false}; - winrt::Microsoft::ReactNative::CompositionHwndHost m_CompositionHwndHost{nullptr}; - winrt::Microsoft::ReactNative::ReactNativeHost m_host{nullptr}; - winrt::Microsoft::ReactNative::ReactInstanceSettings m_instanceSettings{nullptr}; - -#if BUNDLE - std::wstring m_bundleFile = L"index.windows"; - bool m_useWebDebugger{false}; - bool m_fastRefreshEnabled{false}; -#else - std::wstring m_bundleFile = L"index"; - bool m_useWebDebugger{false}; - bool m_fastRefreshEnabled{true}; -#endif +constexpr PCWSTR windowTitle = L"{{ mainComponentName }}"; +constexpr PCWSTR mainComponentName = L"{{ mainComponentName }}"; - bool m_useDirectDebugger{false}; - bool m_breakOnNextLine{false}; - uint16_t m_debuggerPort{defaultDebuggerPort}; - xaml::ElementTheme m_theme{xaml::ElementTheme::Default}; +HWND global_hwnd; +winrt::Microsoft::ReactNative::CompositionRootView *global_rootView{nullptr}; - WindowData(const winrt::Microsoft::ReactNative::CompositionHwndHost &compHost) : m_CompositionHwndHost(compHost) { - winrt::Microsoft::ReactNative::Composition::CompositionUIService::SetCompositionContext( - InstanceSettings().Properties(), - winrt::Microsoft::ReactNative::Composition::WindowsCompositionContextHelper::CreateContext(g_compositor)); - } +float ScaleFactor(HWND hwnd) noexcept { + return GetDpiForWindow(hwnd) / static_cast(USER_DEFAULT_SCREEN_DPI); +} - static WindowData *GetFromWindow(HWND hwnd) { - auto data = reinterpret_cast(GetProp(hwnd, WindowDataProperty)); - return data; - } +void UpdateRootViewSizeToAppWindow( + winrt::Microsoft::ReactNative::CompositionRootView const &rootView, + winrt::Microsoft::UI::Windowing::AppWindow const &window) { + auto hwnd = winrt::Microsoft::UI::GetWindowFromWindowId(window.Id()); + auto scaleFactor = ScaleFactor(hwnd); + winrt::Windows::Foundation::Size size{ + window.ClientSize().Width / scaleFactor, window.ClientSize().Height / scaleFactor}; + rootView.Arrange(size); + rootView.Size(size); +} - winrt::Microsoft::ReactNative::ReactNativeHost Host() noexcept { - if (!m_host) { - m_host = winrt::Microsoft::ReactNative::ReactNativeHost(); - m_host.InstanceSettings(InstanceSettings()); - } +// Create and configure the ReactNativeHost +winrt::Microsoft::ReactNative::ReactNativeHost CreateReactNativeHost( + HWND hwnd, + const winrt::Microsoft::UI::Composition::Compositor &compositor) { + WCHAR workingDir[MAX_PATH]; + GetCurrentDirectory(MAX_PATH, workingDir); - return m_host; - } + auto host = winrt::Microsoft::ReactNative::ReactNativeHost(); - winrt::Microsoft::ReactNative::ReactInstanceSettings InstanceSettings() noexcept { - if (!m_instanceSettings) { - m_instanceSettings = winrt::Microsoft::ReactNative::ReactInstanceSettings(); - } + // Include any autolinked modules + RegisterAutolinkedNativeModulePackages(host.PackageProviders()); - return m_instanceSettings; - } + host.PackageProviders().Append(winrt::make()); - LRESULT RenderApp(HWND hwnd) { - WCHAR workingDir[MAX_PATH]; - GetCurrentDirectory(MAX_PATH, workingDir); - - auto host = Host(); - // Disable until we have a 3rd party story for custom components - // RegisterAutolinkedNativeModulePackages(host.PackageProviders()); // Includes any - // autolinked modules - - host.InstanceSettings().JavaScriptBundleFile(m_bundleFile); - - host.InstanceSettings().UseWebDebugger(m_useWebDebugger); - host.InstanceSettings().UseDirectDebugger(m_useDirectDebugger); - host.InstanceSettings().BundleRootPath(std::wstring(L"file:").append(workingDir).append(L"\\Bundle\\").c_str()); - host.InstanceSettings().DebuggerBreakOnNextLine(m_breakOnNextLine); - host.InstanceSettings().UseFastRefresh(m_fastRefreshEnabled); - host.InstanceSettings().DebuggerPort(m_debuggerPort); - host.InstanceSettings().UseDeveloperSupport(true); - - host.PackageProviders().Append(winrt::make()); - winrt::Microsoft::ReactNative::ReactCoreInjection::SetTopLevelWindowId( - host.InstanceSettings().Properties(), reinterpret_cast(hwnd)); - - // Nudge the ReactNativeHost to create the instance and wrapping context - host.ReloadInstance(); - - winrt::Microsoft::ReactNative::ReactViewOptions viewOptions; - viewOptions.ComponentName(appName); - m_CompositionHwndHost.ReactViewHost( - winrt::Microsoft::ReactNative::ReactCoreInjection::MakeViewHost(host, viewOptions)); - - auto windowData = WindowData::GetFromWindow(hwnd); - if (!windowData->m_windowInited) { - m_CompositionHwndHost.Initialize((uint64_t)(hwnd)); - windowData->m_windowInited = true; - } - return 0; - } + host.InstanceSettings().JavaScriptBundleFile(L"index.windows"); + host.InstanceSettings().DebugBundlePath(L"index"); - LRESULT TranslateMessage(UINT message, WPARAM wparam, LPARAM lparam) noexcept { - if (m_CompositionHwndHost) { - return static_cast(m_CompositionHwndHost.TranslateMessage(message, wparam, lparam)); - } - return 0; - } -}; - -extern "C" IMAGE_DOS_HEADER __ImageBase; -HINSTANCE WindowData::s_instance = reinterpret_cast(&__ImageBase); + host.InstanceSettings().BundleRootPath(std::wstring(L"file:").append(workingDir).append(L"\\Bundle\\").c_str()); + host.InstanceSettings().DebuggerBreakOnNextLine(false); +#if _DEBUG + host.InstanceSettings().UseDirectDebugger(true); + host.InstanceSettings().UseFastRefresh(true); +#endif + host.InstanceSettings().UseDeveloperSupport(true); -LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { - auto windowData = WindowData::GetFromWindow(hWnd); - if (windowData) { - auto result = WindowData::GetFromWindow(hWnd)->TranslateMessage(message, wParam, lParam); - if (result) - return result; - } + winrt::Microsoft::ReactNative::ReactCoreInjection::SetTopLevelWindowId( + host.InstanceSettings().Properties(), reinterpret_cast(hwnd)); - switch (message) { - case WM_DESTROY: { - delete WindowData::GetFromWindow(hWnd); - SetProp(hWnd, WindowDataProperty, 0); - PostQuitMessage(0); - return 0; - } - case WM_NCCREATE: { - auto cs = reinterpret_cast(lParam); - auto windowData = static_cast(cs->lpCreateParams); - WINRT_ASSERT(windowData); - SetProp(hWnd, WindowDataProperty, reinterpret_cast(windowData)); - break; - } - case WM_GETOBJECT: { - { - auto windowData = WindowData::GetFromWindow(hWnd); - if (windowData == nullptr || !windowData->m_windowInited) - break; - - auto hwndHost = windowData->m_CompositionHwndHost; - winrt::com_ptr spReps; - if (!hwndHost.UiaProvider().try_as(spReps)) { - break; - } - LRESULT lResult = UiaReturnRawElementProvider(hWnd, wParam, lParam, spReps.get()); - return lResult; - } - } - } + // By using the MicrosoftCompositionContextHelper here, React Native Windows will use Lifted Visuals for its + // tree. + winrt::Microsoft::ReactNative::Composition::CompositionUIService::SetCompositionContext( + host.InstanceSettings().Properties(), + winrt::Microsoft::ReactNative::Composition::MicrosoftCompositionContextHelper::CreateContext(compositor)); - return DefWindowProc(hWnd, message, wParam, lParam); -} - -int RunRNTester(int showCmd) { - auto windowData = std::make_unique(winrt::Microsoft::ReactNative::CompositionHwndHost()); - HWND hwnd = CreateWindow( - c_windowClassName, - appName, - WS_OVERLAPPEDWINDOW, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - nullptr, - nullptr, - WindowData::s_instance, - windowData.get()); - - WINRT_VERIFY(hwnd); - - windowData.release(); - - ShowWindow(hwnd, showCmd); - UpdateWindow(hwnd); - SetFocus(hwnd); - WindowData::GetFromWindow(hwnd)->RenderApp(hwnd); - - HACCEL hAccelTable = LoadAccelerators(WindowData::s_instance, MAKEINTRESOURCE(IDC_RNTESTER_COMPOSITION)); - - MSG msg = {}; - while (GetMessage(&msg, nullptr, 0, 0)) { - if (!TranslateAccelerator(hwnd, hAccelTable, &msg)) { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } - return static_cast(msg.wParam); + return host; } _Use_decl_annotations_ int CALLBACK WinMain(HINSTANCE instance, HINSTANCE, PSTR /* commandLine */, int showCmd) { - WNDCLASSEXW wcex = {}; - wcex.cbSize = sizeof(WNDCLASSEX); - wcex.style = CS_HREDRAW | CS_VREDRAW; - wcex.lpfnWndProc = &WndProc; - wcex.cbClsExtra = DLGWINDOWEXTRA; - wcex.cbWndExtra = sizeof(WindowData *); - wcex.hInstance = WindowData::s_instance; - wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); - wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); - wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_RNTESTER_COMPOSITION); - wcex.lpszClassName = c_windowClassName; - wcex.hIcon = LoadIconW(instance, MAKEINTRESOURCEW(IDI_ICON1)); - ATOM classId = RegisterClassEx(&wcex); - WINRT_VERIFY(classId); - winrt::check_win32(!classId); - - DispatcherQueueOptions options{ - sizeof(DispatcherQueueOptions), /* dwSize */ - DQTYPE_THREAD_CURRENT, /* threadType */ - DQTAT_COM_ASTA /* apartmentType */ - }; - - // Need to have a Dispatcher on the current thread to be able to create a Compositor - winrt::check_hresult(CreateDispatcherQueueController( - options, - reinterpret_cast( - winrt::put_abi(g_dispatcherQueueController)))); - - g_compositor = winrt::Windows::UI::Composition::Compositor(); - return RunRNTester(showCmd); -} + // Initialize WinRT. + winrt::init_apartment(winrt::apartment_type::single_threaded); + + // 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::Microsoft::UI::Dispatching::DispatcherQueueController::CreateOnCurrentThread()}; + + // Create a Compositor for all Content on this thread. + auto compositor{winrt::Microsoft::UI::Composition::Compositor()}; + + // Create a top-level window. + auto window = winrt::Microsoft::UI::Windowing::AppWindow::Create(); + window.Title(windowTitle); + window.Resize({1000, 1000}); + window.Show(); + auto hwnd = winrt::Microsoft::UI::GetWindowFromWindowId(window.Id()); + global_hwnd = hwnd; + auto scaleFactor = ScaleFactor(hwnd); + + auto host = CreateReactNativeHost(hwnd, compositor); + + // Start the react-native instance, which will create a JavaScript runtime and load the applications bundle + host.ReloadInstance(); + + // Create a RootView which will present a react-native component + winrt::Microsoft::ReactNative::ReactViewOptions viewOptions; + viewOptions.ComponentName(mainComponentName); + auto rootView = winrt::Microsoft::ReactNative::CompositionRootView(compositor); + rootView.ReactViewHost(winrt::Microsoft::ReactNative::ReactCoreInjection::MakeViewHost(host, viewOptions)); + + // Update the size of the RootView when the AppWindow changes size + window.Changed([wkRootView = winrt::make_weak(rootView)]( + winrt::Microsoft::UI::Windowing::AppWindow const &window, + winrt::Microsoft::UI::Windowing::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](winrt::Microsoft::UI::Windowing::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::Windows::Foundation::AsyncStatus asyncStatus) { + assert(asyncStatus == winrt::Windows::Foundation::AsyncStatus::Completed); + host.InstanceSettings().UIDispatcher().Post([]() { PostQuitMessage(0); }); + }); + }); + + // DesktopChildSiteBridge create a ContentSite that can host the RootView ContentIsland + auto bridge = winrt::Microsoft::UI::Content::DesktopChildSiteBridge::Create(compositor, window.Id()); + bridge.Connect(rootView.Island()); + bridge.ResizePolicy(winrt::Microsoft::UI::Content::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; + + // Destroy all Composition objects + compositor.Close(); + compositor = nullptr; +} \ No newline at end of file diff --git a/vnext/templates/cpp-app/windows/MyApp/MyApp.vcxproj b/vnext/templates/cpp-app/windows/MyApp/MyApp.vcxproj index 1a019b4e4dc..276e3305cd0 100644 --- a/vnext/templates/cpp-app/windows/MyApp/MyApp.vcxproj +++ b/vnext/templates/cpp-app/windows/MyApp/MyApp.vcxproj @@ -101,11 +101,13 @@ + + Create Create diff --git a/vnext/templates/cpp-app/windows/MyApp/MyApp.vcxproj.filters b/vnext/templates/cpp-app/windows/MyApp/MyApp.vcxproj.filters index bd8163ead6e..a1d9ae1a198 100644 --- a/vnext/templates/cpp-app/windows/MyApp/MyApp.vcxproj.filters +++ b/vnext/templates/cpp-app/windows/MyApp/MyApp.vcxproj.filters @@ -24,6 +24,9 @@ Header Files + + Header Files + Header Files @@ -32,6 +35,9 @@ Source Files + + Source Files + Source Files diff --git a/vnext/templates/cpp-app/windows/MyApp/pch.h b/vnext/templates/cpp-app/windows/MyApp/pch.h index 8512a671430..77dc1aba971 100644 --- a/vnext/templates/cpp-app/windows/MyApp/pch.h +++ b/vnext/templates/cpp-app/windows/MyApp/pch.h @@ -12,17 +12,20 @@ #define WINRT_LEAN_AND_MEAN 1 // Windows Header Files +#include #include - -#pragma push_macro("GetCurrentTime") #undef GetCurrentTime -// Playground pch.h +// WinRT Header Files +#include #include +#include #include -#include -#include -#pragma pop_macro("GetCurrentTime") +#include +#include +#include +#include +#include // C RunTime Header Files #include @@ -30,6 +33,4 @@ #include #include -// reference additional headers your program requires here -#include -#include +// Reference additional headers your project requires here diff --git a/vnext/templates/cpp-lib/example/metro.config.js b/vnext/templates/cpp-lib/example/metro.config.js new file mode 100644 index 00000000000..84f46590d09 --- /dev/null +++ b/vnext/templates/cpp-lib/example/metro.config.js @@ -0,0 +1,74 @@ +const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config'); +const fs = require('fs'); +const path = require('path'); +const escape = require('escape-string-regexp'); +const exclusionList = require('metro-config/src/defaults/exclusionList'); +const pak = require('../package.json'); + +const root = path.resolve(__dirname, '..'); +const modules = Object.keys({ ...pak.peerDependencies }); + +const rnwPath = fs.realpathSync( + path.resolve(require.resolve('react-native-windows/package.json'), '..'), +); + +//{{#devMode}} [devMode +const rnwRootNodeModules = path.resolve(rnwPath, '..', 'node_modules'); +const rnwPackages = path.resolve(rnwPath, '..', 'packages'); +// devMode]{{/devMode}} + +/** + * Metro configuration + * https://facebook.github.io/metro/docs/configuration + * + * @type {import('metro-config').MetroConfig} + */ +const config = { + watchFolders: [root, + //{{#devMode}} [devMode + rnwPath, rnwRootNodeModules, rnwPackages + // devMode]{{/devMode}} + ], + + // We need to make sure that only one version is loaded for peerDependencies + // So we block them at the root, and alias them to the versions in example's node_modules + resolver: { + blacklistRE: exclusionList( + modules.map( + (m) => + new RegExp(`^${escape(path.join(root, 'node_modules', m))}\\/.*$`) + ).concat([ + // This stops "react-native run-windows" from causing the metro server to crash if its already running + new RegExp( + `${path.resolve(__dirname, 'windows').replace(/[/\\]/g, '/')}.*`, + ), + // This prevents "react-native run-windows" from hitting: EBUSY: resource busy or locked, open msbuild.ProjectImports.zip or other files produced by msbuild + new RegExp(`${rnwPath}/build/.*`), + new RegExp(`${rnwPath}/target/.*`), + /.*\.ProjectImports\.zip/, + ]) + ), + + extraNodeModules: modules.reduce((acc, name) => { + acc[name] = path.join(__dirname, 'node_modules', name); + return acc; + }, + { + //{{#devMode}} [devMode + 'react-native-windows': rnwPath, + // devMode]{{/devMode}} + } + ), + }, + + transformer: { + getTransformOptions: async () => ({ + transform: { + experimentalImportSupport: false, + inlineRequires: true, + }, + }), + }, +}; + +module.exports = mergeConfig(getDefaultConfig(__dirname), config); diff --git a/vnext/templates/cpp-lib/template.config.js b/vnext/templates/cpp-lib/template.config.js new file mode 100644 index 00000000000..da150434da0 --- /dev/null +++ b/vnext/templates/cpp-lib/template.config.js @@ -0,0 +1,235 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * + * @ts check + * @format + */ + +const existsSync = require('fs').existsSync; +const path = require('path'); +const username = require('username'); +const uuid = require('uuid'); +const util = require('util'); + +const glob = util.promisify(require('glob')); + +const templateUtils = require('../templateUtils'); + +function resolveArgs(config = {}, options = {}) { + const projectRoot = config?.root ?? process.cwd(); + + const libConfig = {...config}; + const libOptions = {...options}; + + if (libConfig.project?.windows?.project?.projectFile?.startsWith('Error:')) { + libConfig.project.windows = + templateUtils.getWindowsDependencyConfig(projectRoot); + } + + const exampleProjectPath = path.join(projectRoot, 'example'); + + const exExists = existsSync(exampleProjectPath); + const exProjectConfig = exExists + ? templateUtils.getWindowsProjectConfig(exampleProjectPath) + : null; + const exOptions = exExists + ? { + ...options, + template: 'cpp-app', + name: `${options?.name ?? 'MyLib'}Example`, + namespace: `${options?.namespace ?? 'MyLib'}Example`, + } + : null; + + return { + libConfig, + libOptions, + exExists, + exConfig: { + root: exampleProjectPath, + project: { + windows: exProjectConfig, + }, + }, + exOptions, + }; +} + +const exampleTemplateConfig = require('../cpp-app/template.config'); + +async function preInstall(config = {}, options = {}) { + const {exExists, exConfig, exOptions} = resolveArgs(config, options); + + if (exExists) { + if (exOptions?.logging) { + console.log('Running cpp-app template preInstall() for example...'); + } + await exampleTemplateConfig.preInstall(exConfig, exOptions); + } +} + +async function getFileMappings(config = {}, options = {}) { + const {libConfig, libOptions, exExists, exConfig, exOptions} = resolveArgs( + config, + options, + ); + + const {rnwVersion, devMode} = templateUtils.getRnwInfo(libConfig, libOptions); + + const projectName = + libConfig?.project?.windows?.projects[0]?.projectName ?? + libOptions?.name ?? + 'MyLib'; + const namespace = libOptions?.namespace ?? projectName; + const namespaceCpp = namespace.replace(/\./g, '::'); + const projectGuid = + libConfig?.project?.windows?.projects[0]?.projectGuid + ?.replace('{', '') + .replace('}', '') ?? uuid.v4(); + const currentUser = username.sync(); // Gets the current username depending on the platform. + + const cppNugetPackages = []; + + const replacements = { + useMustache: true, + regExpPatternsToRemove: [], + + name: projectName, + pascalName: templateUtils.pascalCase(projectName), + namespace: namespace, + namespaceCpp: namespaceCpp, + + rnwVersion: rnwVersion, + + // Visual Studio is very picky about the casing of the guids for projects, project references and the solution + // https://www.bing.com/search?q=visual+studio+project+guid+casing&cvid=311a5ad7f9fc41089507b24600d23ee7&FORM=ANAB01&PC=U531 + // we therefore have to precariously use the right casing in the right place or risk building in VS breaking. + projectGuidLower: `{${projectGuid.toLowerCase()}}`, + projectGuidUpper: `{${projectGuid.toUpperCase()}}`, + + currentUser, + + devMode, + + cppNugetPackages, + }; + + let fileMappings = []; + + const templateFiles = await glob('**/*', { + cwd: __dirname, + ignore: 'template.config.js', + nodir: true, + }); + + for (const file of templateFiles) { + const fileMapping = { + from: path.resolve(__dirname, path.normalize(file)), + to: path.normalize(file), + replacements, + }; + + // Don't copy example files if there is no example in the destination + if (!exExists && fileMapping.to.startsWith('example')) { + continue; + } + + // Perform simple file renames + const fileName = path.basename(fileMapping.to); + switch (fileName) { + case '_gitignore': + fileMapping.to = path.join(path.dirname(fileMapping.to), '.gitignore'); + break; + case 'NuGet_Config': + fileMapping.to = path.join( + path.dirname(fileMapping.to), + 'NuGet.config', + ); + break; + } + + // Rename files with MyLib in the name + fileMapping.to = fileMapping.to.replace(/MyLib/g, projectName); + + fileMappings.push(fileMapping); + } + + // Add the file mappings from the cpp-app template for the example app + if (exExists) { + const exampleFileMappings = await exampleTemplateConfig.getFileMappings( + exConfig, + exOptions, + ); + + for (const exFileMap of exampleFileMappings) { + exFileMap.to = path.join('example', exFileMap.to); + + // Only add the file map if there isn't a mapping from this cpp-lib template + if (fileMappings.filter(fm => fm.to === exFileMap.to).length === 0) { + fileMappings.push(exFileMap); + } + } + } + + return fileMappings; +} + +async function postInstall(config = {}, options = {}) { + const {libConfig, libOptions, exExists, exConfig, exOptions} = resolveArgs( + config, + options, + ); + + const projectName = + libConfig?.project?.windows?.projects[0]?.projectName ?? + libOptions?.name ?? + 'MyLib'; + const namespace = libOptions?.namespace ?? projectName; + const namespaceCpp = namespace.replace(/\./g, '::'); + + // Update package.json codegen + await templateUtils.updateProjectPackageJson(libConfig, libOptions, { + codegenConfig: { + windows: { + namespace: namespaceCpp + 'Codegen', + outputDirectory: `windows/${projectName}/codegen`, + separateDataTypes: true, + }, + }, + }); + + if (exExists) { + const {rnwVersion} = templateUtils.getRnwInfo(exConfig, exOptions); + + // Update example package.json with new scripts and dependencies + await templateUtils.updateProjectPackageJson(exConfig, exOptions, { + scripts: { + windows: 'react-native run-windows', + 'test:windows': 'jest --config jest.config.windows.js', + }, + dependencies: { + 'react-native-windows': rnwVersion, + }, + devDependencies: { + '@rnx-kit/jest-preset': '^0.1.16', + }, + }); + + // Install recently added dependencies + await templateUtils.runNpmInstall(libConfig, libOptions); + + console.log( + '\nRun the example app on Windows:\n\n > yarn example windows\n', + ); + } +} + +module.exports = { + name: 'React Native Windows Turbo Module (New Arch, C++)', + description: + "[Experimental] A RNW turbo module targeting RN's new architecture.", + preInstall, + getFileMappings, + postInstall, +}; diff --git a/vnext/templates/cpp-lib/windows/ExperimentalFeatures.props b/vnext/templates/cpp-lib/windows/ExperimentalFeatures.props new file mode 100644 index 00000000000..be73cb5575a --- /dev/null +++ b/vnext/templates/cpp-lib/windows/ExperimentalFeatures.props @@ -0,0 +1,11 @@ + + + + + true + true + + true + + + diff --git a/vnext/templates/cpp-lib/windows/MyLib.sln b/vnext/templates/cpp-lib/windows/MyLib.sln new file mode 100644 index 00000000000..7d36d07392c --- /dev/null +++ b/vnext/templates/cpp-lib/windows/MyLib.sln @@ -0,0 +1,156 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32929.385 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "{{ name }}", "{{ name }}\{{ name }}.vcxproj", "{{ projectGuidUpper }}" + ProjectSection(ProjectDependencies) = postProject + {F7D32BD0-2749-483E-9A0D-1635EF7E3136} = {F7D32BD0-2749-483E-9A0D-1635EF7E3136} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Folly", "..\node_modules\react-native-windows\Folly\Folly.vcxproj", "{A990658C-CE31-4BCC-976F-0FC6B1AF693D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fmt", "..\node_modules\react-native-windows\fmt\fmt.vcxproj", "{14B93DC8-FD93-4A6D-81CB-8BC96644501C}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReactCommon", "..\node_modules\react-native-windows\ReactCommon\ReactCommon.vcxproj", "{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}" + ProjectSection(ProjectDependencies) = postProject + {A990658C-CE31-4BCC-976F-0FC6B1AF693D} = {A990658C-CE31-4BCC-976F-0FC6B1AF693D} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Chakra", "..\node_modules\react-native-windows\Chakra\Chakra.vcxitems", "{C38970C0-5FBF-4D69-90D8-CBAC225AE895}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative", "..\node_modules\react-native-windows\Microsoft.ReactNative\Microsoft.ReactNative.vcxproj", "{F7D32BD0-2749-483E-9A0D-1635EF7E3136}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative.Cxx", "..\node_modules\react-native-windows\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems", "{DA8B35B3-DA00-4B02-BDE6-6A397B3FD46B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Common", "..\node_modules\react-native-windows\Common\Common.vcxproj", "{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReactNative", "ReactNative", "{5EA20F54-880A-49F3-99FA-4B3FE54E8AB1}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative.Shared", "..\node_modules\react-native-windows\Shared\Shared.vcxitems", "{2049DBE9-8D13-42C9-AE4B-413AE38FFFD0}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Mso", "..\node_modules\react-native-windows\Mso\Mso.vcxitems", "{84E05BFA-CBAF-4F0D-BFB6-4CE85742A57E}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Include", "..\node_modules\react-native-windows\include\Include.vcxitems", "{EF074BA1-2D54-4D49-A28E-5E040B47CD2E}" +EndProject +Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + ..\node_modules\react-native-windows\Shared\Shared.vcxitems*{2049dbe9-8d13-42c9-ae4b-413ae38fffd0}*SharedItemsImports = 9 + ..\node_modules\react-native-windows\Mso\Mso.vcxitems*{84e05bfa-cbaf-4f0d-bfb6-4ce85742a57e}*SharedItemsImports = 9 + ..\node_modules\react-native-windows\Chakra\Chakra.vcxitems*{c38970c0-5fbf-4d69-90d8-cbac225ae895}*SharedItemsImports = 9 + ..\node_modules\react-native-windows\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{da8b35b3-da00-4b02-bde6-6a397b3fd46b}*SharedItemsImports = 9 + ..\node_modules\react-native-windows\include\Include.vcxitems*{ef074ba1-2d54-4d49-a28e-5e040b47cd2e}*SharedItemsImports = 9 + ..\node_modules\react-native-windows\Chakra\Chakra.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4 + ..\node_modules\react-native-windows\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4 + ..\node_modules\react-native-windows\Mso\Mso.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4 + ..\node_modules\react-native-windows\Shared\Shared.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4 + EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Debug|ARM64 = Debug|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + Release|ARM64 = Release|ARM64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {{ projectGuidUpper }}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {{ projectGuidUpper }}.Debug|ARM64.Build.0 = Debug|ARM64 + {{ projectGuidUpper }}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {{ projectGuidUpper }}.Debug|x64.ActiveCfg = Debug|x64 + {{ projectGuidUpper }}.Debug|x64.Build.0 = Debug|x64 + {{ projectGuidUpper }}.Debug|x64.Deploy.0 = Debug|x64 + {{ projectGuidUpper }}.Debug|x86.ActiveCfg = Debug|Win32 + {{ projectGuidUpper }}.Debug|x86.Build.0 = Debug|Win32 + {{ projectGuidUpper }}.Debug|x86.Deploy.0 = Debug|Win32 + {{ projectGuidUpper }}.Release|ARM64.ActiveCfg = Release|ARM64 + {{ projectGuidUpper }}.Release|ARM64.Build.0 = Release|ARM64 + {{ projectGuidUpper }}.Release|ARM64.Deploy.0 = Release|ARM64 + {{ projectGuidUpper }}.Release|x64.ActiveCfg = Release|x64 + {{ projectGuidUpper }}.Release|x64.Build.0 = Release|x64 + {{ projectGuidUpper }}.Release|x64.Deploy.0 = Release|x64 + {{ projectGuidUpper }}.Release|x86.ActiveCfg = Release|Win32 + {{ projectGuidUpper }}.Release|x86.Build.0 = Release|Win32 + {{ projectGuidUpper }}.Release|x86.Deploy.0 = Release|Win32 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM64.Build.0 = Debug|ARM64 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x64.ActiveCfg = Debug|x64 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x64.Build.0 = Debug|x64 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x86.ActiveCfg = Debug|Win32 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x86.Build.0 = Debug|Win32 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM64.ActiveCfg = Release|ARM64 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM64.Build.0 = Release|ARM64 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x64.ActiveCfg = Release|x64 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x64.Build.0 = Release|x64 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x86.ActiveCfg = Release|Win32 + {A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x86.Build.0 = Release|Win32 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM64.Build.0 = Debug|ARM64 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x64.ActiveCfg = Debug|x64 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x64.Build.0 = Debug|x64 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x86.ActiveCfg = Debug|Win32 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x86.Build.0 = Debug|Win32 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM64.ActiveCfg = Release|ARM64 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM64.Build.0 = Release|ARM64 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x64.ActiveCfg = Release|x64 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x64.Build.0 = Release|x64 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x86.ActiveCfg = Release|Win32 + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x86.Build.0 = Release|Win32 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM64.Build.0 = Debug|ARM64 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x64.ActiveCfg = Debug|x64 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x64.Build.0 = Debug|x64 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x86.ActiveCfg = Debug|Win32 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x86.Build.0 = Debug|Win32 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM64.ActiveCfg = Release|ARM64 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM64.Build.0 = Release|ARM64 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x64.ActiveCfg = Release|x64 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x64.Build.0 = Release|x64 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x86.ActiveCfg = Release|Win32 + {F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x86.Build.0 = Release|Win32 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM64.Build.0 = Debug|ARM64 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x64.ActiveCfg = Debug|x64 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x64.Build.0 = Debug|x64 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x86.ActiveCfg = Debug|Win32 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x86.Build.0 = Debug|Win32 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM64.ActiveCfg = Release|ARM64 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM64.Build.0 = Release|ARM64 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x64.ActiveCfg = Release|x64 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x64.Build.0 = Release|x64 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x86.ActiveCfg = Release|Win32 + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x86.Build.0 = Release|Win32 + {14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Debug|ARM64.Build.0 = Debug|ARM64 + {14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Debug|x64.ActiveCfg = Debug|x64 + {14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Debug|x64.Build.0 = Debug|x64 + {14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Debug|x86.ActiveCfg = Debug|Win32 + {14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Debug|x86.Build.0 = Debug|Win32 + {14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Debug|x86.Deploy.0 = Debug|Win32 + {14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Release|ARM64.ActiveCfg = Release|ARM64 + {14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Release|ARM64.Build.0 = Release|ARM64 + {14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Release|x64.ActiveCfg = Release|x64 + {14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Release|x64.Build.0 = Release|x64 + {14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Release|x86.ActiveCfg = Release|Win32 + {14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Release|x86.Build.0 = Release|Win32 + {14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Release|x86.Deploy.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {A990658C-CE31-4BCC-976F-0FC6B1AF693D} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + {A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + {C38970C0-5FBF-4D69-90D8-CBAC225AE895} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + {F7D32BD0-2749-483E-9A0D-1635EF7E3136} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + {DA8B35B3-DA00-4B02-BDE6-6A397B3FD46B} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + {FCA38F3C-7C73-4C47-BE4E-32F77FA8538D} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + {2049DBE9-8D13-42C9-AE4B-413AE38FFFD0} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + {84E05BFA-CBAF-4F0D-BFB6-4CE85742A57E} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + {EF074BA1-2D54-4D49-A28E-5E040B47CD2E} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + {14B93DC8-FD93-4A6D-81CB-8BC96644501C} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D43FAD39-F619-437D-BB40-04A3982ACB6A} + EndGlobalSection +EndGlobal diff --git a/vnext/templates/cpp-lib/windows/MyLib/MyLib.cpp b/vnext/templates/cpp-lib/windows/MyLib/MyLib.cpp new file mode 100644 index 00000000000..5dd2b28ca5e --- /dev/null +++ b/vnext/templates/cpp-lib/windows/MyLib/MyLib.cpp @@ -0,0 +1,18 @@ +#include "pch.h" + +#include "{{ name }}.h" + +namespace winrt::{{ namespaceCpp }} +{ + +// See https://microsoft.github.io/react-native-windows/docs/native-modules for details on writing native modules + +void {{ pascalName }}::Initialize(React::ReactContext const &reactContext) noexcept { + m_context = reactContext; +} + +double {{ pascalName }}::multiply(double a, double b) noexcept { + return a * b; +} + +} // namespace winrt::{{ namespaceCpp }} \ No newline at end of file diff --git a/vnext/templates/cpp-lib/windows/MyLib/MyLib.def b/vnext/templates/cpp-lib/windows/MyLib/MyLib.def new file mode 100644 index 00000000000..24e7c1235c3 --- /dev/null +++ b/vnext/templates/cpp-lib/windows/MyLib/MyLib.def @@ -0,0 +1,3 @@ +EXPORTS +DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE +DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE diff --git a/vnext/templates/cpp-lib/windows/MyLib/MyLib.h b/vnext/templates/cpp-lib/windows/MyLib/MyLib.h new file mode 100644 index 00000000000..4f89df5b5bb --- /dev/null +++ b/vnext/templates/cpp-lib/windows/MyLib/MyLib.h @@ -0,0 +1,31 @@ +#pragma once + +#include "pch.h" +#include "resource.h" + +#if __has_include("codegen\Native{{ pascalName }}DataTypes.g.h") + #include "codegen\Native{{ pascalName }}DataTypes.g.h" +#endif +#include "codegen\Native{{ pascalName }}Spec.g.h" + +#include "NativeModules.h" + +namespace winrt::{{ namespaceCpp }} +{ + +REACT_MODULE({{ pascalName }}) +struct {{ pascalName }} +{ + using ModuleSpec = {{ namespaceCpp }}Codegen::{{ pascalName }}Spec; + + REACT_INIT(Initialize) + void Initialize(React::ReactContext const &reactContext) noexcept; + + REACT_SYNC_METHOD(multiply) + double multiply(double a, double b) noexcept; + +private: + React::ReactContext m_context; +}; + +} // namespace winrt::{{ namespaceCpp }} \ No newline at end of file diff --git a/vnext/templates/cpp-lib/windows/MyLib/MyLib.rc b/vnext/templates/cpp-lib/windows/MyLib/MyLib.rc new file mode 100644 index 00000000000..3212c105fce Binary files /dev/null and b/vnext/templates/cpp-lib/windows/MyLib/MyLib.rc differ diff --git a/vnext/templates/cpp-lib/windows/MyLib/MyLib.vcxproj b/vnext/templates/cpp-lib/windows/MyLib/MyLib.vcxproj new file mode 100644 index 00000000000..89cb24908df --- /dev/null +++ b/vnext/templates/cpp-lib/windows/MyLib/MyLib.vcxproj @@ -0,0 +1,140 @@ + + + + + + true + true + {{ projectGuidUpper }} + {{ name }} + Win32Proj + {{ namespace }} + 10.0 + en-US + 17.0 + false + + + $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), 'node_modules\react-native-windows\package.json'))\node_modules\react-native-windows\ + false + + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + DynamicLibrary + Unicode + v143 + false + + + true + + + false + true + + + + + + + + + + + + + Use + pch.h + $(IntDir)pch.pch + Level3 + true + %(AdditionalOptions) /bigobj + 4453;28204 + _WINRT_DLL;%(PreprocessorDefinitions) + $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + stdcpp17 + + + shell32.lib;user32.lib;windowsapp.lib;%(AdditionalDependenices) + Console + true + {{ name }}.def + + + + + _DEBUG;%(PreprocessorDefinitions) + + + + + NDEBUG;%(PreprocessorDefinitions) + + + + + USE_FABRIC;%(PreprocessorDefinitions) + + + + + + + ReactPackageProvider.idl + + + + + + + + + Create + + + ReactPackageProvider.idl + + + + + + + + + + + + + + + {{#cppNugetPackages}} + + {{/cppNugetPackages}} + + + + This project references targets in your node_modules\react-native-windows folder. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/vnext/templates/cpp-lib/windows/MyLib/MyLib.vcxproj.filters b/vnext/templates/cpp-lib/windows/MyLib/MyLib.vcxproj.filters new file mode 100644 index 00000000000..0f7dbff1fb5 --- /dev/null +++ b/vnext/templates/cpp-lib/windows/MyLib/MyLib.vcxproj.filters @@ -0,0 +1,44 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/vnext/templates/cpp-lib/windows/MyLib/ReactPackageProvider.cpp b/vnext/templates/cpp-lib/windows/MyLib/ReactPackageProvider.cpp new file mode 100644 index 00000000000..c2386f60205 --- /dev/null +++ b/vnext/templates/cpp-lib/windows/MyLib/ReactPackageProvider.cpp @@ -0,0 +1,20 @@ +#include "pch.h" + +#include "ReactPackageProvider.h" +#if __has_include("ReactPackageProvider.g.cpp") +#include "ReactPackageProvider.g.cpp" +#endif + +#include "{{ name }}.h" + +using namespace winrt::Microsoft::ReactNative; + +namespace winrt::{{ namespaceCpp }}::implementation +{ + +void ReactPackageProvider::CreatePackage(IReactPackageBuilder const &packageBuilder) noexcept +{ + AddAttributedModules(packageBuilder, true); +} + +} // namespace winrt::{{ namespaceCpp }}::implementation diff --git a/vnext/templates/cpp-lib/windows/MyLib/ReactPackageProvider.h b/vnext/templates/cpp-lib/windows/MyLib/ReactPackageProvider.h new file mode 100644 index 00000000000..22532fd6449 --- /dev/null +++ b/vnext/templates/cpp-lib/windows/MyLib/ReactPackageProvider.h @@ -0,0 +1,24 @@ +#pragma once + +#include "ReactPackageProvider.g.h" + +using namespace winrt::Microsoft::ReactNative; + +namespace winrt::{{ namespaceCpp }}::implementation +{ + +struct ReactPackageProvider : ReactPackageProviderT +{ + ReactPackageProvider() = default; + + void CreatePackage(IReactPackageBuilder const &packageBuilder) noexcept; +}; + +} // namespace winrt::{{ namespaceCpp }}::implementation + +namespace winrt::{{ namespaceCpp }}::factory_implementation +{ + +struct ReactPackageProvider : ReactPackageProviderT {}; + +} // namespace winrt::{{ namespaceCpp }}::factory_implementation diff --git a/vnext/templates/cpp-lib/windows/MyLib/ReactPackageProvider.idl b/vnext/templates/cpp-lib/windows/MyLib/ReactPackageProvider.idl new file mode 100644 index 00000000000..1465f2e7184 --- /dev/null +++ b/vnext/templates/cpp-lib/windows/MyLib/ReactPackageProvider.idl @@ -0,0 +1,9 @@ +namespace {{ namespace }} +{ + [webhosthidden] + [default_interface] + runtimeclass ReactPackageProvider : Microsoft.ReactNative.IReactPackageProvider + { + ReactPackageProvider(); + }; +} diff --git a/vnext/templates/cpp-lib/windows/MyLib/_gitignore b/vnext/templates/cpp-lib/windows/MyLib/_gitignore new file mode 100644 index 00000000000..82fabe9662a --- /dev/null +++ b/vnext/templates/cpp-lib/windows/MyLib/_gitignore @@ -0,0 +1 @@ +/Bundle \ No newline at end of file diff --git a/vnext/templates/cpp-lib/windows/MyLib/pch.cpp b/vnext/templates/cpp-lib/windows/MyLib/pch.cpp new file mode 100644 index 00000000000..1d9f38c57d6 --- /dev/null +++ b/vnext/templates/cpp-lib/windows/MyLib/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/vnext/templates/cpp-lib/windows/MyLib/pch.h b/vnext/templates/cpp-lib/windows/MyLib/pch.h new file mode 100644 index 00000000000..b93cec13351 --- /dev/null +++ b/vnext/templates/cpp-lib/windows/MyLib/pch.h @@ -0,0 +1,30 @@ +// pch.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + +#include "targetver.h" + +#define NOMINMAX 1 +#define WIN32_LEAN_AND_MEAN 1 +#define WINRT_LEAN_AND_MEAN 1 + +// Windows Header Files +#include +#include +#undef GetCurrentTime + +// WinRT Header Files +#include +#include +#include + +// C RunTime Header Files +#include +#include +#include +#include + +// Reference additional headers your project requires here diff --git a/vnext/templates/cpp-lib/windows/MyLib/resource.h b/vnext/templates/cpp-lib/windows/MyLib/resource.h new file mode 100644 index 00000000000..964739b0b37 --- /dev/null +++ b/vnext/templates/cpp-lib/windows/MyLib/resource.h @@ -0,0 +1,5 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by {{ name }}.rc + +#pragma once diff --git a/vnext/templates/cpp-lib/windows/MyLib/targetver.h b/vnext/templates/cpp-lib/windows/MyLib/targetver.h new file mode 100644 index 00000000000..87c0086de75 --- /dev/null +++ b/vnext/templates/cpp-lib/windows/MyLib/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include diff --git a/vnext/templates/cpp-lib/windows/_gitignore b/vnext/templates/cpp-lib/windows/_gitignore new file mode 100644 index 00000000000..5bc72a4416d --- /dev/null +++ b/vnext/templates/cpp-lib/windows/_gitignore @@ -0,0 +1,41 @@ +*AppPackages* +*BundleArtifacts* + +#OS junk files +[Tt]humbs.db +*.DS_Store + +#Visual Studio files +*.[Oo]bj +*.user +*.aps +*.pch +*.vspscc +*.vssscc +*_i.c +*_p.c +*.ncb +*.suo +*.tlb +*.tlh +*.bak +*.[Cc]ache +*.ilk +*.log +*.lib +*.sbr +*.sdf +*.opensdf +*.opendb +*.unsuccessfulbuild +ipch/ +[Oo]bj/ +[Bb]in +[Dd]ebug*/ +[Rr]elease*/ +Ankh.NoLoad +.vs/ +# Visual C++ cache files + +#Files generated by the VS build +**/Generated Files/** diff --git a/vnext/templates/templateUtils.js b/vnext/templates/templateUtils.js index e9f2bb7174b..d3c9646d95a 100644 --- a/vnext/templates/templateUtils.js +++ b/vnext/templates/templateUtils.js @@ -7,41 +7,131 @@ */ const existsSync = require('fs').existsSync; +const fs = require('fs').promises; const path = require('path'); const util = require('util'); const exec = util.promisify(require('child_process').exec); +const _ = require('lodash'); + const pkgUtils = require('@react-native-windows/package-utils'); +const projectConfig = require('@react-native-windows/cli').projectConfig; +const dependencyConfig = require('@react-native-windows/cli').dependencyConfig; + +function getRnwInfo(config = {}, options = {}) { + const projectRoot = config?.root ?? process.cwd(); + + const rnwPath = path.dirname( + require.resolve('react-native-windows', [projectRoot]), + ); + + const rnwVersion = require(path.join(rnwPath, 'package.json')).version; + + const devMode = existsSync(path.join(rnwPath, 'src')); + + if (options?.logging) { + console.log( + `Found react-native-windows@${rnwVersion} at ${rnwPath}${ + devMode ? ' (devMode enabled)' : '' + }...`, + ); + } + + return {rnwPath, rnwVersion, devMode}; +} + +function getWindowsProjectConfig(root) { + if (!existsSync(root)) { + return {}; + } + + const userConfigPath = path.join(root, 'react-native.config.js'); + + const userConfig = existsSync(userConfigPath) ? require(userConfigPath) : {}; + + return projectConfig(root, userConfig); +} + +function getWindowsDependencyConfig(root) { + if (!existsSync(root)) { + return {}; + } + + const userConfigPath = path.join(root, 'react-native.config.js'); + + const userConfig = existsSync(userConfigPath) ? require(userConfigPath) : {}; + + return dependencyConfig(root, userConfig); +} + +function pascalCase(str) { + const camelCase = _.camelCase(str); + return camelCase[0].toUpperCase() + camelCase.substr(1); +} + +async function replaceInFile( + config = {}, + options = {}, + relativePath = '', + searchValue = '', + replaceValue = '', +) { + const filePath = path.join(config?.root ?? process.cwd(), relativePath); + if (existsSync(filePath)) { + const contents = await fs.readFile(filePath, {encoding: 'utf-8'}); + const newContents = contents.replace(searchValue, replaceValue); + if (contents !== newContents) { + if (options?.logging) { + console.log(`Modifying ${filePath}...`); + } + await fs.writeFile(filePath, newContents, {encoding: 'utf-8'}); + } + } +} + async function runNpmInstall(config = {}, options = {}) { - const projectPath = config?.root ?? process.cwd(); + const projectRoot = config?.root ?? process.cwd(); if (options?.logging) { - console.log('Installing dependencies...'); + console.log(`Installing dependencies for ${projectRoot}...`); + } + const isYarn = existsSync(path.join(projectRoot, 'yarn.lock')); + const cmd = isYarn ? 'yarn' : 'npm i'; + + try { + await exec(cmd, options?.logging ? {stdio: 'inherit'} : {}); + } catch (ex) { + console.log( + `Failed to install dependencies for ${projectRoot}. Please run '${cmd}' manually to update the dependencies.`, + ); } - const isYarn = existsSync(path.join(projectPath, 'yarn.lock')); - await exec( - isYarn ? 'yarn' : 'npm i', - options?.logging ? {stdio: 'inherit'} : {}, - ); } async function updateProjectPackageJson(config = {}, options = {}, props = {}) { - const projectPath = config?.root ?? process.cwd(); + const projectRoot = config?.root ?? process.cwd(); const projectPackage = await pkgUtils.WritableNpmPackage.fromPath( - projectPath, + projectRoot, ); if (!projectPackage) { throw new Error( - `The directory '${projectPath}' is not the root of an npm package`, + `The directory '${projectRoot}' is not the root of an npm package`, ); } if (options?.logging) { - console.log('Modifying project package.json...'); + console.log(`Modifying ${path.join(projectRoot, 'package.json')}...`); } await projectPackage.mergeProps(props); } -module.exports = {runNpmInstall, updateProjectPackageJson}; +module.exports = { + getRnwInfo, + getWindowsProjectConfig, + getWindowsDependencyConfig, + pascalCase, + replaceInFile, + runNpmInstall, + updateProjectPackageJson, +};