67
Using npm workspaces with ReactJS(Typescript) and .NET
This article explains how to leverage the existing .NET SPA template to work with npm workspaces. explanation on what npm workspaces are is not addressed in this article. for any one who is new to npm workspaces its recommended to check npm official documentation. npm workspaces is a nice way of organizing code but at the time being in order to use workspaces in .NET some customization are required, which will be explained in the following sections of this article.
.NET project with react can be created by running the following command
dotnet new react -n SampleApp
Once the
SampleApp
project is created by default it will contain ClientApp
directory, which is where the SPA(in this case React App) resides. as the default SPA template doesn't fit the required scenario delete everything inside ClientApp
directory. To setup workspaces open terminal inside the
ClientApp
directory first run the following commandnpm init -y
Running this command will generate
package.json
file which will contain the workspace information. for this example I want to create four workspaces namedThe
ClientApp
will now look like the following
{
"name": "@clientapp/root",
"version": "1.0.0",
"private": true,
"scripts": {
"start:table": "npm run start -w @clientapp/table",
"start:card": "npm run start -w @clientapp/card",
"build:table": "npm run build -w @clientapp/table",
"build:card": "npm run build -w @clientapp/card"
},
"workspaces": [
"workspaces/*/**"
]
}
To create the two applications inside
ClientApp\workspaces\apps
directory run the following commands consecutivelynpx create-react-app table --template typescript
updated
name
field inside ClientApp\workspaces\apps\table\package.json
to"name": "@clientapp/table"
npx create-react-app card --template typescript
updated
name
field inside ClientApp\workspaces\apps\card\package.json
to"name": "@clientapp/card"
By default in both
@clientapp/table
& @clientapp/card
we will not be able to use the typescript libraries from other workspaces. in order to support typescript I will use craco
instead of react-scripts
. the changes in this section must be applied in both @clientapp/table
& @clientapp/card
.Install
craco
as dev dependencynpm install craco --save-dev
Create file name
craco.config.js
const path = require("path");
const { getLoader, loaderByName } = require("craco");
const packages = [];
/**
* add the typescript workspaces this project is dependent up on
*/
packages.push(path.join(__dirname, "../../libs/core"));
module.exports = {
webpack: {
configure: (webpackConfig, { env, paths }) => {
/**
* Overriding the output directory of build to fit with default configuration of .NET wrapper
*/
paths.appBuild = webpackConfig.output.path = path.resolve('../../../build');
const { isFound, match } = getLoader(webpackConfig, loaderByName("babel-loader"));
if (isFound) {
const include = Array.isArray(match.loader.include)
? match.loader.include
: [match.loader.include];
match.loader.include = include.concat(packages);
}
return webpackConfig;
},
},
};
Update the
scrpts
section inside package.json
of both @clientapp/table
& @clientapp/card
as shown below:{
...
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "craco eject"
},
...
}
From
ClientApp\workspaces\libs
open terminal and run the following commandnpx create-react-app core --template typescript
updated
name
field inside ClientApp\workspaces\apps\card\package.json
to"name": "@clientapp/core"
Since @clientapp/core is not dependent on another workspace there is no need to configure
craco
.From all application delete node_modules
directory
To install the
@clientapp/core
workspace into @clientapp/table
& @clientapp/card
run the following commands from ClientApp
directorynpm install @clientapp/core -w @clientapp/table
npm install @clientapp/core -w @clientapp/card
To install the dependency packages run
npm install
from ClientApp
directory.At this point the SPA workspace configuration is completed & can be tested by running either of the following commands
npm run start:table
or
npm run start:card
For development update
Configure
method inside Startup.cs
by replacingspa.UseReactDevelopmentServer(npmScript: "start");
By
spa.UseReactDevelopmentServer(npmScript: "run start:table");
To start @clientapp/table. & replace it by
spa.UseReactDevelopmentServer(npmScript: "run start:card");
To start @clientapp/card
For publish update
SampleApp.csproj
by replacing<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build" />
<!-- Include the newly-built files in the publish output -->
<ItemGroup>
<DistFiles Include="$(SpaRoot)build\**" />
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
<RelativePath>%(DistFiles.Identity)</RelativePath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</ResolvedFileToPublish>
</ItemGroup>
</Target>
By
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
<Error Condition="'$(SpaBuildScript)' == ''" Text="Spa build script is not specified." />
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
<Exec WorkingDirectory="$(SpaRoot)" Command="$(SpaBuildScript)" />
<!-- Include the newly-built files in the publish output -->
<ItemGroup>
<DistFiles Include="$(SpaRoot)build\**" />
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
<RelativePath>%(DistFiles.Identity)</RelativePath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</ResolvedFileToPublish>
</ItemGroup>
</Target>
Add Two publish profiles one for @clientapp/card & one for @clientapp/table
CardAppProfile.pubxml
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<DeleteExistingFiles>False</DeleteExistingFiles>
<ExcludeApp_Data>False</ExcludeApp_Data>
<LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<PublishProvider>FileSystem</PublishProvider>
<PublishUrl>bin\Release\net5.0\publish\</PublishUrl>
<WebPublishMethod>FileSystem</WebPublishMethod>
<SpaBuildScript>npm run build:card</SpaBuildScript>
</PropertyGroup>
</Project>
TableAppProfile.pubxml
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<DeleteExistingFiles>False</DeleteExistingFiles>
<ExcludeApp_Data>False</ExcludeApp_Data>
<LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<PublishProvider>FileSystem</PublishProvider>
<PublishUrl>bin\Release\net5.0\publish\</PublishUrl>
<WebPublishMethod>FileSystem</WebPublishMethod>
<SpaBuildScript>npm run build:table</SpaBuildScript>
</PropertyGroup>
</Project>
After adding these publish profiles, @cilentapp/table can be published by running the following command for
dotnet pubilsh /p:PublishProfile="Properties\PublishProfiles\TableAppProfile.pubxml"
And for @cilentapp/card
dotnet pubilsh /p:PublishProfile="Properties\PublishProfiles\CardAppProfile.pubxml"
That is one way of using npm workspaces with .NET, full source code can be found on GitHub.
Thanks for reading, Happy coding!
67