Back in the days of ASP.NET Core 1.0, all frontend libraries used to be loaded by Bower. This was pretty nice, because it allowed you to keep all the libraries out of the project’s source (by using .gitignore) and simply not commiting them.
But Honza, the default project scaffold contains only 4 tiny libraries…. Yeah, but why should those be in my Git repository? Ideally, those will be loaded from an external CDN anyways. My major issue with this is, that it sets a bad example for developers. You don’t include Nuget packages in your Git repository, do you? Why would you include these then?
When I scaffold a new project, what I always do, is remove the local files, only keep the CDN which I then replace with cdnjs, since using a single CDN across an entire project allows your users to fully benefit from HTTP/2 features and so on.
This project is unique with the fact that I don’t want to use any CDNs at all. Since captive portal works by the access point/gateway restricting your access to outbound IP addresses with the exception of the captive portal itself and some other exceptions, you probably don’t want to whitelist an entire CDNs namespace. That’s why I want to have the libraries served from the server (not in my Git tho).
Choosing the package manager
Since Bower is more or less outdated and after evaluating all the other options available out there, I decided to go with NPM. There are multiple other alternatives out there like Yarn, but that would introduce yet another tool into the build machine (Azure App Service in my case) and onto the dev machine as well.
I expect a standard ASP.NET Core dev computer to contain the .NET Core SDK and Node.js with NPM – simply due to building Single Page Applications, using bundlers, Gulp, Grunt, webpack etc.
Storing NPM packages
Next obvious task was to decide where the NPM packages will be stored in the project structure. The most obvious place would be in the project’s root (/src/WebApplication1/node_modules/) but that didn’t turn out that good. When you decide to use another folder for static files, everything is going to work, except asp-append-version tag (the tiny thingy which makes frontend asset versioning super simple), which I simply wanted there, in case somebody would update the libraries. There are some workarounds, but those are not really ideal I would say.
Unlike with Bower, NPM’s package.json doesn’t offer any option to specify, where the node_modules folder will be stored – by default and in all cases (I didn’t find an option to place it elsewhere), the folder will end up in the same directory. So I put the package.json file into /src/WebApplication1/wwwroot/ folder, gitignored the node_modules folder and added npm install into the build task:
dotnet publish and node_modules
Just to be sure, I decided to clean the wwwroot, built files in D:\home\site\repository and redeploy the application again (basically, we have a policy in our company, that the project has to build on a clean dev machine without any extra steps which then makes it super easy to put into a CI/CD pipeline, just a tip) and the same issue occured. After going through the generated build script (D:\home\site\deployment\tools\build.bat), I decided to run the steps manually.
First, dotnet restore which went fine and then dotnet publish with an output folder specified and Release configuration and whoops, the node_modules weren’t part of my publish result either!
After spending some time on Google and GitHub issues (1, 2, 3), I found out what the issue was – it seems like the .NET SDK is precalculating the files for publishing but at that time, the node_modules folder hasn’t been generated yet!
Then it was time to figure out a fix. I remembered, that the Single Page Application (SPA) templates for ASP.NET Core must work with NPM as well, and that those packages are definitely not included in Git (since the initial project files have about 300MB). So since we live in the opensource era, I found the repo with templates on GitHub and looked at one of the .csproj files. And there it was – the solution. Basically, through that .csproj, the publish process is made aware of the node_modules folder and includes it into the output.
Solving with NPM
So in the end it was all solved, I made few changes to the .csproj myself, so going to share those below, since I think they are useful:
- When running the npm install command, run it with –no-audit parameter – this speeds up the build process.
- On local build, always run npm install. Since multiple people work on the project, it is quite important that everyone has the appropriate packages. This added a slight overhead to the build time, but if all packages are in sync with package.json, it takes only about 0.1 seconds to finish which is kind of unnoticable.
The modifications to the .csproj which I am using are available below:
The real solution
Just a day before this post’s release, I have learned (through the GitHub issue) about a project called Library Manager. It basically allows you to pull frontend libraries into your project – from cdnjs, local or network location. You simply specify a libman.json file, exclude the folder with libraries from Git and you are good to go. The build-time restore is also supported, simply by adding Microsoft.Web.LibraryManager.Build package to your project. An example libman.json file from the project is here:
Update (01SEP2018): Library Manager is now available in Visual Studio 15.8 release! There is also a handy CLI package so you can make use of it in your terminal outside Visual Studio.
Currently, there is an issue #66 which fails during build from CLI (and thus App Service as well). An easy fix is to edit the Microsoft.Web.LibraryManager.Build.targets file in your App Service directly (D:\home.nuget\microsoft.web.librarymanager.build\1.0.20\build) and you are good to go.
So go ahead and give it a try, it’s easy as 1-2-3!