DEV Community

Sean G. Wright
Sean G. Wright

Posted on

Kentico 12: Design Patterns Part 5 - Front-End Dependency Management

Photo by chuttersnap on Unsplash

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);
}

Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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 .
Enter fullscreen mode Exit fullscreen mode

Next, initialize the project with the npm CLI and accept all the defaults (you can change them later):

npm init -y
Enter fullscreen mode Exit fullscreen mode

Now, start installing the packages for the libraries you would like to use! In this example we'll install jquery:

npm install jquery
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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}`));
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
  },
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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:

#kentico

or my Kentico 12: Design Patterns series.

Top comments (0)