loading...

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

seangwright profile image Sean G. Wright ใƒป8 min read

Kentico 12 - Design Patterns (25 Part Series)

1) Kentico 12: Design Patterns Part 1 - Writing Testable Code 2) Kentico 12: Design Patterns Part 2 - Writing Unit Tests 3 ... 23 3) Kentico 12: Design Patterns Part 3 - Tips and Tricks, Application Structure 4) Kentico 12: Design Patterns Part 4 - Adding Dependency Injection to the CMS 5) Kentico 12: Design Patterns Part 5 - Front-End Dependency Management 6) Kentico 12: Design Patterns Part 6 - Rendering Meta Tags in Kentico 12 MVC 7) Kentico 12: Design Patterns Part 7 - Integrating Web API 2 8) Kentico 12: Design Patterns Part 8 - Setting Up Integration Tests 9) Kentico 12: Design Patterns Part 9 - The Different Ways to Store Content in Kentico 12 MVC 10) Kentico 12: Design Patterns Part 10 - MVC Routing with NodeAliasPath 11) Kentico 12: Design Patterns Part 11 - Unit Testing Custom Page Types 12) Kentico 12: Design Patterns Part 12 - Database Query Caching Patterns 13) Kentico 12: Design Patterns Part 13 - Generating Page URLs 14) Kentico 12: Design Patterns Part 14 - DocumentQuery and ObjectQuery Tips 15) Kentico 12: Design Patterns Part 15 - Output Caching and User Context 16) Kentico 12: Design Patterns Part 16 - Integrating Vue.js with MVC 17) Kentico 12: Design Patterns Part 17 - Centralized Cache Management through Decoration 18) Kentico 12: Design Patterns Part 18 - Preparing for Kentico 2020 19) Kentico 12: Design Patterns Part 19 - Protecting An API Against XSRF 20) Kentico 12: Design Patterns Part 20 - Choosing a Solution Architecture 21) Kentico 12: Design Patterns Part 21 - MVC Widget Tips 22) Kentico 12: Design Patterns Part 22 - Improving Our Projects for Developer Experience 23) Kentico 12: Design Patterns Part 23 - Improving Our Projects with Documentation 24) Kentico 12: Design Patterns Part 24 - Improving Our Projects with Configuration 25) Kentico 12: Design Patterns Part 25 - MVC Page Templates

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

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.

Kentico 12 - Design Patterns (25 Part Series)

1) Kentico 12: Design Patterns Part 1 - Writing Testable Code 2) Kentico 12: Design Patterns Part 2 - Writing Unit Tests 3 ... 23 3) Kentico 12: Design Patterns Part 3 - Tips and Tricks, Application Structure 4) Kentico 12: Design Patterns Part 4 - Adding Dependency Injection to the CMS 5) Kentico 12: Design Patterns Part 5 - Front-End Dependency Management 6) Kentico 12: Design Patterns Part 6 - Rendering Meta Tags in Kentico 12 MVC 7) Kentico 12: Design Patterns Part 7 - Integrating Web API 2 8) Kentico 12: Design Patterns Part 8 - Setting Up Integration Tests 9) Kentico 12: Design Patterns Part 9 - The Different Ways to Store Content in Kentico 12 MVC 10) Kentico 12: Design Patterns Part 10 - MVC Routing with NodeAliasPath 11) Kentico 12: Design Patterns Part 11 - Unit Testing Custom Page Types 12) Kentico 12: Design Patterns Part 12 - Database Query Caching Patterns 13) Kentico 12: Design Patterns Part 13 - Generating Page URLs 14) Kentico 12: Design Patterns Part 14 - DocumentQuery and ObjectQuery Tips 15) Kentico 12: Design Patterns Part 15 - Output Caching and User Context 16) Kentico 12: Design Patterns Part 16 - Integrating Vue.js with MVC 17) Kentico 12: Design Patterns Part 17 - Centralized Cache Management through Decoration 18) Kentico 12: Design Patterns Part 18 - Preparing for Kentico 2020 19) Kentico 12: Design Patterns Part 19 - Protecting An API Against XSRF 20) Kentico 12: Design Patterns Part 20 - Choosing a Solution Architecture 21) Kentico 12: Design Patterns Part 21 - MVC Widget Tips 22) Kentico 12: Design Patterns Part 22 - Improving Our Projects for Developer Experience 23) Kentico 12: Design Patterns Part 23 - Improving Our Projects with Documentation 24) Kentico 12: Design Patterns Part 24 - Improving Our Projects with Configuration 25) Kentico 12: Design Patterns Part 25 - MVC Page Templates

Posted on by:

seangwright profile

Sean G. Wright

@seangwright

dev lead @WiredViews, founding partner @craftbrewingbiz. @Kentico Xperience MVP. love to learn / teach web dev & software engineering, collecting vinyl records, mowing my lawn, craft ๐Ÿบ

Discussion

markdown guide