28

How to minify JavaScript inside a view page's script block with minimal effort?

I have some page specific scripts that would like to put on specific view pages. But the ASP.NET MVC4 bundling and minification only works with script files, not script code inside a view page.

UPDATE

I took Sohnee's advice to extract the scripts into files. But I need to use them on specific pages so what I end up doing is:

on layout page, i created an optional section for page specific javascript block:

    @RenderSection("js", required: false)
</body>

then in the view page, let's say Index.cshtml, i render the script section like such:

@section js{    
    @Scripts.Render("~/bundles/js/" + Path.GetFileNameWithoutExtension(this.VirtualPath))
}

as you can see, it assumes the javascript filename (index.js) is the same as the view page name (index.cshtml). then in the bundle config, i have:

var jsFiles = Directory.GetFiles(HttpContext.Current.Server.MapPath("Scripts/Pages"), "*.js");
foreach (var jsFile in jsFiles)
{
    var bundleName = Path.GetFileNameWithoutExtension(jsFile);
    bundles.Add(new ScriptBundle("~/bundles/js/" + bundleName).Include(
    "~/Scripts/pages/" + Path.GetFileName(jsFile)));
}

then, if you are on index page, the HTML output will be:

    <script src="/bundles/js/Index?v=ydlmxiUb9gTRm508o0SaIcc8LJwGpVk-V9iUQwxZGCg1"></script>
</body>

and if you are on products page, the HTML output will be:

    <script src="/bundles/js/Products?v=ydlmxiUb9gTRm508o0SaIcc8LJwGpVk-V9iUQwxZGCg1"></script>
</body>

6 Answers 6

52

You can minify inline scripts using this HTML helper

using Microsoft.Ajax.Utilities;
using System;

namespace System.Web.Mvc
{
    public class HtmlHelperExtensions
    {
        public static MvcHtmlString JsMinify(
            this HtmlHelper helper, Func<object, object> markup)
        {
           string notMinifiedJs =
            markup.Invoke(helper.ViewContext)?.ToString() ?? "";

            var minifier = new Minifier();
            var minifiedJs = minifier.MinifyJavaScript(notMinifiedJs, new CodeSettings
            {
                EvalTreatment = EvalTreatment.MakeImmediateSafe,
                PreserveImportantComments = false
            });
            return new MvcHtmlString(minifiedJs);
        }
    }
}

And inside your Razor View use it like this

 <script type="text/javascript">
           @Html.JsMinify(@<text>
        window.Yk = window.Yk || {};
        Yk.__load = [];
        window.$ = function (f) {
            Yk.__load.push(f);
        }
         </text>)
</script>

If you use System.Web.Optimization than all necessary dlls are already referenced otherwise you can install WebGrease NuGet package.

Some additional details available here: http://www.cleansoft.lv/minify-inline-javascript-in-asp-net-mvc-with-webgrease/

EDIT: Replaced DynamicInvoke() with Invoke(). No need for runtime checks here, Invoke is much faster than DynamicInvoke. Added .? to check for possible null.

Sign up to request clarification or add additional context in comments.

8 Comments

Good to know this one too. +1
this should be the accepted answer as it actually answers the question
Could you please elaborate a little on this line: (markup.DynamicInvoke(helper.ViewContext) ?? "").ToString();
Markup from <text></text> block is passed to helper method as delegate. We execute delegate to get javascript. We pass helper.ViewContext to make sure all calls to ViewBag calls are resolved correctly. We call ToString to get string from delegate result (actually it is IHtmlString).
Good stuff. Inside the method, I'd add a check on Debugger.IsAttached to decide whether to minify or not. Sometimes you don't want your JS minified if you're debugging locally.
|
11

The way to do this with minimal effort is to extract it into a script file. Then you can use bundling and minification just as you want.

If you want to minify it inline, it will be a much greater effort than simply moving the script off-page.

3 Comments

I'd love to not have my script in the view, but I'm curious how you would recommend including @Model elements into an external file?
Typically I would request any data I needed in my script using an AJAX request, but you could drop a little data into an inline script, but only the data - the logic would still be in an external file.
As Steve says, I do something like <script>window.mynamespace.Model = @(Html.Raw(Json.Encode(Model)));</script> at the beginning of my code. This allows reference to the model in other scripts that have been minified. The alternative is to do an AJAX call, but that means additional wait times. Build a proper ViewModel, and this works great.
8

Based on @samfromlv's answer, I created an extension to handle CSS as well. It also takes BundleTable.EnableOptimizations into consideration.

OptimizationExtensions.cs

4 Comments

You can't use this minimalization (neither @samfromlv) when in your script is char "<". For example: a <= 15. Also it destroyed VS js intellisense.
@Gh61 are you sure?
@nmit026 Over 2 years after my comment? No, I'm not sure anymore.
@Gh61 that's because you didn't prepend <text> with @
4

Adding in an answer for ASP.NET MVC Core. The solution I used to minify inline JS and razor generated html was WebMarkupMin.

It ultimately boiled down to adding these two minuscule changes to my project:

public void Configure(IApplicationBuilder app)  
{
    app.UseStaticFiles();

    //added
    app.UseWebMarkupMin();

    app.UseMvc(.....
}

public void ConfigureServices(IServiceCollection services)  
{
    services.AddMvc();

    //added    
    services.AddWebMarkupMin(
        options =>
        {
            //i comment these two lines out after testing locally
            options.AllowMinificationInDevelopmentEnvironment = true;
            options.AllowCompressionInDevelopmentEnvironment = true;
        })
        .AddHttpCompression();
}

There's a great blog post by Andrew Lock (author of ASP.NET Core in Action) about using WebMarkupMin https://andrewlock.net/html-minification-using-webmarkupmin-in-asp-net-core/ WebMarkupMin is highly configurable and Andrew's post goes way more indepth, highly recommended reading it intently before just copying and pasting.

Comments

2

A little late for the party, but for .NET Core you could use a TagHelper to minify the content of a script tag like this:

[HtmlTargetElement("script", Attributes = MinifyAttributeName)]
public class ScriptTagHelper : TagHelper
{
    private const string MinifyAttributeName = "minify";

    [HtmlAttributeName(MinifyAttributeName)]
    public bool ShouldMinify { get; set; }

    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        if (!ShouldMinify)
        {
            await base.ProcessAsync(context, output);
            return;
        }

        var textChildContent = await output.GetChildContentAsync();
        var scriptContent = textChildContent.GetContent();

        // or use any other minifier here
        var minifiedContent = NUglify.Uglify.Js(scriptContent).Code;

        output.Content.SetHtmlContent(minifiedContent);
    }
}

and then use it in your views:

<script minify="true">
  ...
</script>

Comments

0

Fenton had a great answer about this: "rather than minify inline JavaScript code, externalize the inline JavaScript code and then you can minify with any standard JavaScript minifiers / bundlers."

Here is how you externalize the JavaScript: https://webdesign.tutsplus.com/tutorials/how-to-externalize-and-minify-javascript--cms-30718

Here is my direct answer to minify the inline JavaScript code (require a bit of manual work).

  1. Copy the inline JavaScript code snippet and paste them into a separate JavaScript file and save it, e.g. inline.js

  2. Use esbuild to minify the inline code snippet in inline.js, see more details about minification here

    esbuild --minify < inline.js > inline-minified.js
    
  3. Copy the minified JavaScript code snippet in inline-minified.js and paste it back into the original HTML to replace the original code inside of the tag.

  4. Done.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.