In a project at work , our front end developer was complaining that the changes they made to the styles, when developing an Umbraco site locally, would not update unless they did a rebuild of the solution.

I had experienced this issue too and I wasn't happy with it and knew there would be a way to get it to update without needing to rebuild every time.

So I asked for help in the Umbraco Discord Server and the lovely Mike Chambers came to the rescue. He had already had the same issue a while ago and was helped by a former Umbraco HQ employee who told him how to do it.

This post gives you the final solution we got to after going back and forth and reading other threads etc.

AppSettings

First of all I needed to work out which app settings were needed for dev and production. I settled on this:

Development

"RuntimeMinification": {
    "UseInMemoryCache": true,
    "CacheBuster": "Timestamp"
}

Apparently the timestamp cache refreshes after 5 seconds.

Production

"RuntimeMinification": {
    "UseInMemoryCache": true,
    "CacheBuster": "AppDomain"
}

With AppDomain the cache is refreshed every time the app restarts.

I didn't want to use version number because I am sick of forgetting to increment the version number from the Client Dependency days and I'm using Umbraco Cloud without a devops pipeline to automatically increment the version number.

Create your bundles

Ideally you should do this at the end of the Configure method in the Startup.cs file

app.UseSmidge(bundles =>
{
    bundles.CreateCss("home-css", "~/css/homepage.css"
                            ,"~/css/general-styles.css");

    bundles.CreateJs("home-js", "~/js/maps.js",
                                "~/js/homepage.js");
});

IRuntimeMinifier service

The Umbraco backoffice uses an IRuntimeMinifier service that you can inject into your views.

The advantage of using this rather than the SmidgeHelper is that this checks if you are in debug mode or not and renders the correct script accordingly, otherwise you would need to add a debug attribute to your links and scripts and this saves you from having to do that. So the code in the View is the same when you are working locally and when it gets pushed up to the next environment.

Here I am using the IRuntimeMinifier service to generate the CSS link and I am just using an MVC section so it goes in the correct place in the master view.

@using Umbraco.Cms.Core.WebAssets;
@inject IRuntimeMinifier RuntimeMinifier

@section Head {
    @Html.Raw(await RuntimeMinifier.RenderCssHereAsync("home-css"))
}

That will render out this in the markup in development

<link href="/css/homepage.css?d=1" rel="stylesheet">
<link href="/css/general-styles.css?d=1" rel="stylesheet">

We can do the same with the scripts too

@section ScriptsBottom {
    @Html.Raw(await RuntimeMinifier.RenderJsHereAsync("home-js"))
}

That will render out this in the markup in development

<script src="/js/maps.js?d=1"></script>
<script src="/js/homepage.js?d=1"></script>

That all works perfectly for me and solved my problems.

The only thing left was that I might want to add would be attributes such as defer or async, so I created some extension methods to help with that.

public static string Defer(this string value)
{
    return value.AddAttributes(new Dictionary<string, string>() { { "defer", "" } });
}

public static string Async(this string value)
{
    return value.AddAttributes(new Dictionary<string, string>() { { "async", "" } });
}

public static string PreloadJs(this string value)
{
    return value.AddAttributes(new Dictionary<string, string>() { { "rel", "preload" }, { "as", "script" } });
}

public static string PreloadCss(this string value)
{
    return value.AddAttributes(new Dictionary<string, string>() { { "rel", "preload" }, { "as", "style" } });
}

public static string AddAttributes(this string html, Dictionary<string, string> attributes)
{
    if (string.IsNullOrEmpty(html) || attributes == null || attributes.Count == 0)
    {
        return html;
    }

    var regex = new Regex(@"<\w+[^>]*>");
    var matches = regex.Matches(html);

    if (matches.Count == 0)
    {
        return html;
    }

    foreach (Match match in matches)
    {
        var tag = match.Value;
        var content = tag.Substring(1, tag.Length - 2);

        var parts = content.Split(' ').ToList();
        var element = parts[0];
        var existingAttributes = parts.Skip(1).ToList();

        foreach (var attribute in attributes)
        {
            var name = attribute.Key;
            var value = attribute.Value;

            var index = existingAttributes.FindIndex(a => a.StartsWith(name + "="));

            if (index >= 0)
            {
                existingAttributes[index] = $"{name}=\"{value}\"";
            }
            else
            {
                if (value == "")
                {
                    existingAttributes.Add(name);
                }
                else
                {
                    existingAttributes.Add($"{name}=\"{value}\"");
                }
            }
        }

        var newTag = $"<{element} {string.Join(" ", existingAttributes)}>";

        html = html.Replace(tag, newTag);
    }

    // Return the new html string
    return html;
}

Now I can use them like this in my Views:

@Html.Raw(RuntimeMinifier.RenderJsHereAsync("home-js").Result.Defer())

And it will output the script tags like this:

<script src="/js/maps.js?d=1" defer></script>
<script src="/js/homepage.js?d=1" defer></script>

I hope this helps you out at some point too.

If you want to read the discussion I had in discord you can view it here: https://discord-chats.umbraco.com/t/15696918/optimal-settings-for-smidge-runtimeminification-in-local-dev

Paul Seal

Umbraco MVP and .NET Web Developer from Derby (UK) who specialises in building Content Management System (CMS) websites using MVC with Umbraco as a framework. Paul is passionate about web development and programming as a whole. Apart from when he's with his wife and son, if he's not writing code, he's thinking about it or listening to a podcast about it.

Proudly sponsored by

Moriyama

  • Moriyama build, support and deploy Umbraco, Azure and ASP.NET websites and applications.
AppVeyor

  • CI/CD service for Windows, Linux and macOS
  • Build, test, deploy your apps faster, on any platform.
elmah.io

  • elmah.io is the easy error logging and uptime monitoring service for .NET.
  • Take back control of your errors with support for all .NET web and logging frameworks.
uSync Complete

  • uSync.Complete gives you all the uSync packages, allowing you to completely control how your Umbraco settings, content and media is stored, transferred and managed across all your Umbraco Installations.
uSkinned

  • More than a theme for Umbraco CMS, take full control of your content and design with a feature-rich, award-nominated & content editor focused website platform.
UmbHost

  • Affordable, Geo-Redundant, Umbraco hosting which gives back to the community by sponsoring an Umbraco Open Source Developer with each hosting package sold.