Kentico 12: Design Patterns Part 5 - Front-End Dependency Management
Sean G. Wright
Posted on June 17, 2019
Since Kentico CMS 12 was released, and ASP.NET MVC became the recommended framework for building web sites and applications based on Kentico, we have new ways of accomplishing many of our development goals.
As .NET developers, we have traditionally managed our library dependencies through NuGet packages.
What are the ways that we can manage our front-end dependencies? What are the pros and cons of the available options? ๐ค
In this post I discuss the two main options I see available to developers building a Kentico 12 MVC site, and describe why I think one of them is clearly better than the other.
Using System.Web.Optimization
When creating a new Kentico 12 MVC project we are given several configuration classes in the App_Start
folder. One of these is found in BundleConfig.cs
.
This BundleConfig
class adds ScriptBundle
and StyleBundle
instances to the BundleCollection
provided by BundleTable.Bundles
.
private static void RegisterJqueryBundle(BundleCollection bundles)
{
var bundle = new ScriptBundle("~/bundles/jquery")
{
CdnPath = "https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.3.1.min.js",
CdnFallbackExpression = "window.jQuery"
};
bundle.Include("~/Scripts/jquery-{version}.js");
bundles.Add(bundle);
}
These bundles can then be referenced in Views, often in the _Layout.cshtml
, by the identifiers used to register them.
<body>
<!-- begin content -->
<div class="container">
@RenderBody()
</div>
<!-- end content -->
@Scripts.Render("~/bundles/jquery")
</body>
All of these types can be found in the System.Web.Optimization
namespace and you can find the source code on GitHub. ๐ค
You can read more about how to use
BundleCollection
for bundling and minification in the Microsoft Docs.
The primary goal of System.Web.Optimization
and BundleTable.Bundles
in ASP.NET is to give developers an easy way of bundling and minifying sets of JavaScript and CSS files.
These framework features, provided for us out of the box, "just work". ๐
However, these tools were created back when managing client-side dependencies was difficult, the community hadn't yet established consistency or best practices, and the dependencies being managed were much simpler.
The Problems With System.Web.Optimization
All of this bundling technology has been re-vamped for ASP.NET Core as a new tool integrated into Visual Studio called LibMan.
There is a helpful explanation provided in the description of LibMan that puts it (and ASP.NET's "bundling" approach) into perspective, given all the tools available for building modern web applications:
If youโre happily using npm/yarn/(or something else), we encourage you to continue doing so. LibMan was not developed as a replacement for these tools.
The docs also mentioned that LibMan is for simple use-cases and requires no additional tools:
If your project does not require additional tools (like Node, npm, Gulp, Grunt, WebPack, etc) and you simply want to acquire a couple of files, then LibMan might be for you.
Due to the way ASP.NET tries to simplify client-side dependency management, it leads to some design patterns that I don't agree with:
- ๐๐ผ Treating client-side dependencies as a small bucket of scripts and styles
- ๐๐ผ Managing library versions by manually downloading files from the internet
- ๐๐ผ Committing libraries to source control and including them in the ASP.NET project (usually under a
\Scripts
or\Styles
folder) - ๐๐ผ Not tree-shaking client-side dependencies
- ๐๐ผ Not using modern CSS tooling (Sass, PostCSS, stylelint)
- ๐๐ผ Not using modern JavaScript features (transpiling, ES Modules for dependency management, ES2015+ language enhancements)
The world of 2019 web development is very different from 2009 when ASP.NET MVC first came out - let's embrace the world we live in! ๐
There are some tools that enhance ASP.NET's bundling functionality, providing dependency management, and more of a feature-folder based approach, but they don't deal with all the issues I mention above.
Using Client-Side Tools
So, what will we use instead of System.Web.Optimization
?
I believe that we should be using modern client-side development tools to manage our client-side dependencies.
- โ npm for package and version management
- โ Sass for creating our stylesheets
- โ Webpack, GulpJs, ParcelJs, or a SPA CLI for bundling & minification
- โ VS Code for the best editor + tooling experience
Requirements
We will need the following tools installed to have the best client-side development experience:
The reasons for installing and using VS Code will be clearer in my next post
Removing System.Web.Optimization
First, we will need to delete all the existing bundling code. ๐ซ๐ค ๐ฃ
Delete App_Start\BundleConfig.cs
and the reference to it in Global.asax.cs
.
Next, delete the calls to @Scripts.Render()
and @Styles.Render()
in Shared\_Layout.cshtml
.
We will also delete the \Scripts
and \Styles
directories as all of our client-side libraries will be managed by npm
and our CSS files will be generated from our Sass files.
Using npm
First, open the terminal and navigate to the MVC project directory.
Assuming you installed VS Code, you should be able to open your current folder in Code by typing the following command:
code .
Next, initialize the project with the npm
CLI and accept all the defaults (you can change them later):
npm init -y
Now, start installing the packages for the libraries you would like to use! In this example we'll install jquery
:
npm install jquery
We want to ensure that the libraries installed from npm are not committed to source control. npm stores all of its packages in a
\node_modules
folder, which should be added to our.gitignore
file.
Creating Client-Side Code
To use jQuery
in our application we need to write some modern JavaScript and use it. ๐
Create a \src
folder, which is where we will keep the entry points to our client-side source files.
In the next post you will see how we take a "feature folder" based approach to client-side development.
The first file we will create, \src\styles.scss
, will be the entry point for all of our Sass code. Add the following (not very amazing) content:
// Yup, we're using Kentico's theme!
$background-color: #f14b00;
body {
background-color: $background-color;
}
Now, create \src\app.js
with the following content:
/*
* We use this non-standard import
* to ensure our Sass is part of the build process
*/
import './styles.scss';
import $ from 'jquery';
const PIE = '๐ฐ';
$(() => console.log(`Document loaded! It's easy as ${PIE}`));
To learn more about what
import
,const
,() =>
and${}
means, there are many wonderful free resources online like Exploring ES6 by Dr. Axel Rauschmayer, Learn ES2015 from Babel, and Learn ES6 on Egghead
ParcelJs
If we use a tool like ParcelJs for building and bundling, we can get running very quickly, but with limitations on how far we can customize our build pipeline for client-side dependencies.
ParcelJs is a great tool to start with and we will explore other options in my next post.
To use it, we will need to install ParcelJs as a development dependency (using the -D
option):
npm i parcel-bundler -D
We will also need to define commands we will run with npm that use ParcelJs, so replace the scripts
block in your package.json
with the following:
"scripts": {
"start": "parcel watch src/app.js",
"dev": "parcel build src/app.js --no-minify",
"prod": "parcel build src/app.js"
},
When we run npm start
at the command line we can see that our JavaScript and Sass is transpiled, with sourcemaps to help with debugging in browser developer tools, into a \dist
directory. ๐
ParcelJs will continue to watch for changes to the source files and produce new output automatically anytime we save those changes. ๐
To stop this "watch" mode type
ctrl+c
Running npm run dev
will create the same files as npm start
but the command will exit once compilation is completed.
If we run npm run prod
, we will produce a "production" ready version of our code.
With any client-side build process we will end up with "compiled"/"transpiled" output, which we do not want to commit to source control. These files or folders should be added to our
.gitignore
file.
Using Client-Side Build Ouptut
To use this build output we need to add references to it in our Shared\_Layout.cshtml
.
Where we were previously referencing the jquery
and CSS bundles we can now reference the output of the ParcelJs build:
<head>
<!-- various meta -->
<link href="/dist/app.css" rel="stylesheet" />
</head>
<body>
<!-- body content -->
<script src="/dist/app.js"></script>
</body>
End-To-End Build Coordination
To ensure our client side assets get created when we build our ASP.NET project in Visual Studio we can use MSBuild configuration in our MVC project's .csproj
file.
We need it to perform the following steps:
- โ Install npm packages
- โ Run the correct npm command based on the build (Debug/Release)
- โ Finish with the normal .NET build
There is a clever solution on StackOverflow that uses file modification dates to ensure we don't install packages if we already have everything installed. ๐คฏ
The following MSBuild XML added to our .csproj
will serve our purposes:
<PropertyGroup>
<!-- File with mtime of last successful npm install -->
<NpmInstallStampFile>node_modules/.install-stamp</NpmInstallStampFile>
</PropertyGroup>
<ItemGroup>
<JSFile Include="src\**\*.js" />
<SCSSFile Include="src\**\*.scss" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<NpmCommand>npm run dev</NpmCommand>
<NpmOutput>dist\app.js</NpmOutput>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' != 'Debug|AnyCPU' ">
<NpmCommand>npm run prod</NpmCommand>
<NpmOutput>dist\app.js</NpmOutput>
</PropertyGroup>
<Target Name="NpmInstall"
BeforeTargets="NpmBuildClientAssets"
Inputs="package.json"
Outputs="$(NpmInstallStampFile)">
<Exec Command="npm install" />
<Touch Files="$(NpmInstallStampFile)" AlwaysCreate="true" />
</Target>
<Target Name="NpmBuildClientAssets"
BeforeTargets="BeforeBuild"
Inputs="@(JSFile);@(SCSSFile)"
Outputs="$(NpmOutput)">
<Exec Command="$(NpmCommand)" />
</Target>
One convenience of having your project open in VS Code is that it's a lot easier to edit the
.csproj
file ๐
Now when we build our project in Visual Studio we are guaranteed to have the client-side build assets in the \dist
directory before the site ever starts running. ๐๐ฝ
So What Did We Accomplish?
Before we look to where we can go from here, let's remember where are are!
We realized that while the classes ASP.NET provides to us in System.Web.Optimization
had great APIs and tooling when they first came out, the web, and front-end development, has changed significantly. ๐ค
There are some software development patterns we would like to avoid, like committing libraries to source control, that this older approach encourages. ๐
Using client-side tools for client-side development actually works pretty well! ๐
We can also integrate the client-side development process into our .NET development process to have a great end-to-end solution. ๐ช
What's Next?
Now that we've set up the foundational pieces we can start to explore all the wonderful front-end tools and libraries that can improve our development experience.
In my next post I'm going to discuss those tools and libraries, how to integrate them into VS Code, and what a "best practices" setup might look like. ๐ฎ
If you are looking for additional Kentico content, checkout the Kentico tag here on DEV:
or my Kentico 12: Design Patterns series.
Posted on June 17, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.