React Native for Windows and native modules: how to add CI/CD to your project

This post has been republished via RSS; it originally appeared at: New blog articles in Microsoft Tech Community.

In the previous post we have learned how to create a CI/CD pipeline on Azure DevOps to automatically build and deploy a Windows application built with React Native. Everything worked fine but, in that scenario, I used as a sample the basic React Native template, which doesn't leverage any native module. Especially when you're building a real application, however, it's a very uncommon scenario. At some point, you will have to interact with any of the native features offered by the platform, even if it's a simple one like the file system. For this reason, in another blog post I wrote, we have learned how we can build native modules for Windows, so that your React Native application can access to native Windows features using JavaScript.

 

However, as soon as you try to setup a CI/CD pipeline following the guidance of the previous post for your React Native project which uses native modules for Windows, you hit a blocker. The Visual Studio build task will fail with a series of errors like the following ones:

 

[error]node_modules\react-native-windows\Microsoft.ReactNative.SharedManaged\AttributedViewManager.cs(447,21): Error CS8107: Feature 'default literal' is not available in C# 7.0. Please use language version 7.1 or greater.

[error]node_modules\react-native-windows\Microsoft.ReactNative.SharedManaged\JSValue.cs(105,36): Error CS8107: Feature 'readonly references' is not available in C# 7.0. Please use language version 7.2 or greater.

The reason is that, when you build a native module in C#, you reference a project included in the React Native for Windows implementation called Microsoft.ReactNative.SharedManaged, which provides various attributes to expose C# classes and methods to JavaScript. This project leverages many features of the C# language that has been added after the 7.0 release, which aren't supported by Visual Studio 2017, that is the version installed on our hosted agent. We need to switch to Visual Studio 2019 but we can't just do that: the React Native for Windows implementation leverages many C++ projects which target the v141 C++ platform toolset, which is installed by default in Visual Studio 2017 but not in Visual Studio 2019.

 

Shall we give up in our plan of using Azure DevOps to build a CI/CD pipeline for this scenario? Absolutely not! Self-hosted agents to the rescue =)

 

Setting up the self-hosted agent

For scenarios like this, where the available hosted agents don't meet our requirements, Azure DevOps supports the concept of self-hosted agents. A self-hosted agent is nothing more than a regular machine, where you deploy an agent which is able to receive tasks from Azure DevOps and execute them. The agent is an application that can run in interactive mode or simply be installed as a service, so that it can performs all the operations in background. The advantage of a self-hosted agent is its flexibility: being a regular machine, you can install everything you need to satisfy your requirements: development tools, SDKs, frameworks, etc. This helps also to reduce the execution times of a pipeline. Sometimes, some of our requirements can be satisfied also on hosted agents with installation tasks: for example, in the previous post we have learned how to use Chocolatey to install the Windows 10 1903 SDK, which is missing on the vs2017-win2016 agent. However, all these tasks must be added on top of the compilation time, making the whole execution longer.

 

The downside of a self-hosted agent is that you need to maintain it. You need to keep it up & running, you need to patch it, you need to pay for it, etc. Hosted agents, instead, are maintained directly by Microsoft. The difference between a hosted agent and a self-hosted agent is not very different between choosing a IaaS (Infrastructure as a Service) or PaaS (Platform as a Service) approach for running an application in the cloud.

 

Setting up a self-hosted agent is a quite straightforward operation. First, you need to have a machine that you want to use as an agent. It can be any kind of machine: physical, virtual, in the cloud. As long as it's connected to Internet, you're good to go. Of course, the best approach is to use a dedicated virtual machine, where you're going to install only the tools you need to perform the compilation. In my case, I opted for creating a dedicated VM on Azure. If you have a Visual Studio subscription linked to your Azure account, you can choose one of the available Windows 10 Pro images (in my case, I chose the most recent version, Windows 10 1909). Otherwise, you can choose a Windows Server 2019 image as well.

 

Windows101909.png

As size, keep in mind that disk speed is very important. React Native for Windows is built on top of C++ and, as such, the compilation times are quite long. If you choose a cheap storage (like a regular HDD instead of SSD), the compilation time will be very long. My suggestion is to go, at least, with a DS2_V2, which is the same size leveraged by the Microsoft hosted agents.

 

VMsize.png

 

Once your VM is up & running, regardless if you have created it locally or in the cloud, you will need to setup the agent.

 

Configuring the agent

I won't go into all the details on how to setup a self-hosted agent, since my colleague Freist has explained it in a great way in the following blog post. Also the official documentation does a great job in explaining all the required steps.

 

The process is really easy. You just need to download a package from the Azure DevOps website, copy it over on your machine and run a script to configure it. The script will ask you a few questions, like the security token to connect your Azure DevOps instance, the work folder, if you want to install the agent as a Windows Service, etc. Once the operation is complete, you're all set. The agent will take care of communicating with Azure DevOps via HTTPS, to exchange information: it will receive the tasks to perform and it will send back the produced artifacts.

 

Once the agent is up & running, you can start installing all the tools you need to compile our React Native for Windows project. Let's see them.

 

Visual Studio 2019

Any version, including the Community one, will be fine. Just make sure to install the workloads and components required by React Native, which are described in the official documentation:

  • Workloads
    • Universal Windows Platform development
      • Enable the optional C++ (v141) Universal Windows Platform tools
    • Desktop development with C++
  • Individual Components
    • Compilers, build tools and runtimes
      • VC v141 - VS 2017 C++ x64/x86 build tools (v14.16)
      • MSVC v141 - VS 2017 C++ ARM build tools (v14.16)

 

Chocolatey

We have already talked about Chocolatey in the previous post. It's a popular package manager for Windows and it's the easiest way to install software on a machine (especially a build agent), since everything can be done via command-line, without requiring any user interaction. To install it, you just need to open an administrative PowerShell prompt on the VM (the easiest way is to right click on the Start button and choose Windows PowerShell (Admin)) and launch the following command:

 

Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))

Once the tool is installed, you'll be able to install applications just by launching choco install followed by the unique identifier of the package. You can explore all the available packages on the official website.

 

Node.js

You will need Node.js installed on the machine, since it's leveraged by React Native to handle all the modules. Now that we have Chocolatey on the machine, installing Node.js is really easy. Open a PowerShell prompt with administrative rights and run the following command:

 

choco install nodejs.install --version=12.9.1

Make sure to specify the --version property, since 12.9.1 is the Node.js version which works best with React Native at the time of writing. Different versions can lead to errors during the bundle generation.

 

Yarn

When you commit your React Native project to your repository, by default the node_modules folder is ignored. These are libraries and tools which haven't been developed by us, so it wouldn't make sense to waste space on our repository for stuff that can be easily downloaded. As such, you will have to restore all the dependencies before running the compilation. The best way to do is to leverage Yarn, a popular package manager for Node. Also Yarn is available on Chocolatey, so you can just run the following command from a PowerShell prompt with administrative rights:

 

choco install yarn

 

React Native CLI

During the compilation of the package, at some point Visual Studio will launch the react-native bundle command, which bundles all the JavaScript files together so that you don't need the Metro packager up & running to launch the application. In order to achieve this task, you will need to install first the react-native-cli package globally. However, there's a catch, which we have learned about also in the previous post. The React Native CLI must be available to the user which runs the build agent service, otherwise Visual Studio won't be able to find it. In case of a hosted agent, we learned that the user that runs this service is called VssAdministrator. What about self-hosted agents? If you have installed the agent as a service using the default options, the service will be running using the Network Service user or the Local System account, which are special users that doesn't have a dedicated account. As such, you have two options:

  1. You can create on the machine a dedicated user with administrator rights, which will be dedicated to the agent service.
  2. You can use your current default user to run the agent service.

Regardless of your choice, to change this behavior open the Start menu and type:

 

services.msc

You will open a window that will list all the services installed in the system. Look for the agent service, which will be called Azure Pipelines Agent, followed by the a string composed by:

  • The name of your Azure DevOps account
  • The name of the pool the agent belongs to
  • The name of the machine

For example, in my case it's called Azure Pipelines Agent (mpagani-ms.Default.VSPreviewBuild).

 

AgentName.png

 

Now double click on it to open its properties and move to the Log on section. Select the This account option and, with the Browse button, search for the user on the machine you have decide to use to run the service. In my case, I chose to leverage my current user, which is called qmatteoq and he has administrator rights. You will need to provide also the password of the account.

 

ServiceUser.png

 

Once you have setup the user who runs the agent, you're ready to install the React Native CLI. Open an administrative prompt and run first the following command:

 

npm config set prefix C:\Users\qmatteoq\AppData\Roaming\npm

Make sure to replace qmatteoq with the name of the user you have chosen to run the service. This command will set the default folder which will be used as cache for NPM packages. Now you can install the CLI with the following command:

 

npm install -g react-native-cli

That's it. This was the last requirement to install on the machine. Now we can go back to the Azure DevOps portal and create our pipeline.

 

Setup the CI/CD pipeline

The process of creating a CI/CD pipeline isn't very different from the one we did in the previous post. In your Azure DevOps project move to the Pipelines section, create a new pipeline, choose where your source code is hosted and, in the end, choose Universal Windows Platform as last template. We will need, also in this case, to make a few tweaks, but we will have to perform fewer steps. In the previous post, in fact, we were leveraging a hosted agent, so we needed to install every time the missing dependencies, like the Windows 10 1903 SDK or the React Native CLI. In our scenario, instead, we have already installed all the dependencies on the machine, so we don't need to install them every time we perform a new build.

Here is how your YAML file should look like:

 

# Universal Windows Platform
# Build a Universal Windows Platform project using Visual Studio.
# Add steps that test and distribute an app, save build artifacts, and more:
# https://aka.ms/yaml

trigger:
- master 

pool: Default

variables:
  solution: 'windows/*.sln'
  buildPlatform: 'x64'
  buildConfiguration: 'Release'
  appxPackageDir: '$(build.artifactStagingDirectory)\AppxPackages\\'

name: $(date:yyyy).$(Month)$(rev:.r).0

steps:

- task: VersionAPPX@2
  displayName: 'Version MSIX'
  inputs:
    Path: '$(Build.SourcesDirectory)'
    VersionNumber: '$(Build.BuildNumber)'
    InjectVersion: true

- script: yarn install

- task: NuGetCommand@2
  inputs:
    command: 'restore'
    restoreSolution: 'windows/*.sln'
    feedsToUse: 'select'

- task: VSBuild@1
  inputs:
    solution: '$(solution)'
    msbuildArgs: '/p:AppxBundlePlatforms="$(buildPlatform)" /p:AppxPackageDir="$(appxPackageDir)" /p:AppxBundle=Never /p:UapAppxPackageBuildMode=SideloadOnly /p:AppxPackageSigningEnabled=false'
    platform: '$(buildPlatform)'
    configuration: '$(buildConfiguration)'

- task: PublishBuildArtifacts@1
  inputs:
    PathtoPublish: '$(appxPackageDir)'
    ArtifactName: 'drop'

Let's see which are the major differences compared to the one we have created in the previous post:

  • We have set the pool property to Default. If you have setup the agent on the machine using the default settings, it will belong to the Default pool. With this setting we're telling to Azure DevOps that we don't want to use a hosted agent, but our custom one.
  • We don't need anymore to install Yarn, the Windows 1903 SDK and the React Native CLI at every build. We have already installed them when we have configured the machine.

The rest of the steps are the same:

  1. We change the build number, so that it's compliant with the rules required by a MSIX manifest to define a version number (x.y.z.0). Then, using the VersionAPPX task, we inject it in the manifest of your application.
  2. We restore all the Node dependencies with the yarn install command.
  3. We restore the NuGet packages required by React Native for Windows.
  4. We build the project using Visual Studio.
  5. We publish the build artifacts on Azure DevOps, so that other pipelines (like a release pipeline) can pick it to perform additional tasks, like performing a deployment.

 

That's all folks!

That's it! Now we have a CI pipeline that can automatically build a new MSIX package every time we commit some code to the repository of our React Native project which uses one or more native modules for Windows. Now we can build a release pipeline, which will take care of deploying the MSIX package we have just created to our users: we can upload it on the Microsoft Store, we can upload it on a website or cloud storage to make it available through sideloading, etc. You can learn more about this in the last chapter of my latest e-book titled MSIX Succinctly, which has been published and released for free by Syncfusion, or in Exercise 6 of the Windows application modernization workshop built by my team.

 

Happy coding!

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.