ASP.NET MVC5のBundleエラーをなんとかしてみる

ASP.NET MVC5には複数のJavascriptやCSSの内容を圧縮(Minify)して一つのファイルにまとめる(Bundle)機能がある。
これは「App_Start/BundleConfig.cs」のプログラムにて管理できるが、このBundleConfig利用した際に、新しめの演算子などを使ったJavascriptをScriptBundleでバンドルするとSystem.NullReferenceExceptionが発生する。
※デフォルトで作成されるBootstrapの読み込みをBundle(MinifyなしでBundleのみ)からScriptBundleに変更しても同じエラーが発生する。

例:Bootstrapの読み込みをScriptBundleに変更した場合

CSSをバンドルするStyleBundleはExceptionが発生しないため一見問題無さそうに見えるが、実行後にバンドルされたCSSを開発者ツールで見てみると以下のように大量にエラーメッセージが追記されMinifyに失敗しているのが確認できる。

ASP.NET MVC5は現在(2025年)、更新が止まっているため、新しめのスクリプトやスタイルを使うとMinifyに失敗してこのような状態になってしまうようだ。
Webpackなど別の仕組みでMinifyして、Bundleクラスでバンドルすれば、上手いことできそうではあるが、別の仕組みを組み込むのは余り気が進まない。
NUglifyを利用してMinifyするカスタムバンドルクラスを作れば、エラーなくバンドルできそうだったので一応メモを残しておく。
注意:簡単な動作確認しかしていないので上手く動かない可能性あり

動作確認環境

  • Visual Studio 2022
  • ASP.NET MVC 5.3.0(.NET Framework4.8)
  • Nuglify(1.21.15)

カスタムバンドルの作成

NUglifyをNuGetからプロジェクトに追加後、以下のクラスを作成する。
Javascript向け、CSS向けどちらも、既にMinifyされているファイル(拡張子が「.min.XXX」のファイル)以外をNUglifyでMinifyして、1つのファイルにするようにしている。

Javascript向けのBundleクラス

App_Start/Bundles/NUglifyScriptBundle.cs
using NUglify;
using System;
using System.IO;
using System.Linq;
using System.Web.Hosting;
using System.Web.Optimization;

namespace Sample
{
    /// <summary>
    /// NUglifyでMinifyするScriptBundle
    /// </summary>
    public class NUglifyScriptBundle : Bundle
    {
        public NUglifyScriptBundle(string virtualPath)
            : base(virtualPath, new[] { new NUglifyScriptTransform() }) { }

        public NUglifyScriptBundle(string virtualPath, string cdnPath)
            : base(virtualPath, cdnPath, new[] { new NUglifyScriptTransform() }) { }
    }

    /// <summary>
    /// NUglifyでMinifyするScriptTransform
    /// </summary>
    internal class NUglifyScriptTransform : IBundleTransform
    {
        public void Process(BundleContext context, BundleResponse response)
        {
            response.Content = string.Join("\n", response.Files.Select(f =>
            {
                var path = HostingEnvironment.MapPath(f.VirtualFile.VirtualPath);
                var js = File.ReadAllText(path);
                // 拡張子が「.min.js」の場合は既にMinifyされているはずなのでMinifyしない
                return path.EndsWith(".min.js", StringComparison.OrdinalIgnoreCase) ? js : Uglify.Js(js).Code;
            }));
            response.ContentType = "text/javascript";
        }
    }
}

CSS向けのBundleクラス

App_Start/Bundles/NUglifyStyleBundle.cs
using NUglify;
using System;
using System.IO;
using System.Linq;
using System.Web.Hosting;
using System.Web.Optimization;

namespace Sample
{

    /// <summary>
    /// NUglifyでMinifyするStyleBundle
    /// </summary>
    public class NUglifyStyleBundle : Bundle
    {
        public NUglifyStyleBundle(string virtualPath)
            : base(virtualPath, new[] { new NUglifyStyleTransform() }) { }

        public NUglifyStyleBundle(string virtualPath, string cdnPath)
            : base(virtualPath, cdnPath, new[] { new NUglifyStyleTransform() }) { }
    }

    /// <summary>
    /// NUglifyでMinifyするStyleTransform
    /// </summary>
    internal class NUglifyStyleTransform : IBundleTransform
    {
        public void Process(BundleContext context, BundleResponse response)
        {
            response.Content = string.Join("\n", response.Files.Select(f =>
            {
                var path = HostingEnvironment.MapPath(f.VirtualFile.VirtualPath);
                var css = File.ReadAllText(path);
                // 拡張子が「.min.css」の場合は既にMinifyされているはずなのでMinifyしない
                return path.EndsWith(".min.css", StringComparison.OrdinalIgnoreCase) ? css : Uglify.Css(css).Code;
            }));
            response.ContentType = "text/css";
        }
    }
}

カスタムバンドルを利用してバンドルしてみる

作成したカスタムバンドルを利用してバンドルするようBundleConfigを変更する。

App_Start/BundleConfig.cs
using System.Web;
using System.Web.Optimization;

namespace Sample
{
    public class BundleConfig
    {
        // バンドルの詳細については、https://go.microsoft.com/fwlink/?LinkId=301862 を参照してください
        public static void RegisterBundles(BundleCollection bundles)
        {
            bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                        "~/Scripts/jquery-{version}.js"));

            bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
                        "~/Scripts/jquery.validate*"));

            // 開発と学習には、Modernizr の開発バージョンを使用します。次に、実稼働の準備が
            // 運用の準備が完了したら、https://modernizr.com のビルド ツールを使用し、必要なテストのみを選択します。
            bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
                        "~/Scripts/modernizr-*"));

            bundles.Add(new NUglifyScriptBundle("~/bundles/bootstrap").Include(
                     "~/Scripts/bootstrap.js",
                     "~/Scripts/test.js"   // 動確用に追加
                     ));

            bundles.Add(new NUglifyStyleBundle("~/Content/css").Include(
                     "~/Content/bootstrap.css",
                     "~/Content/site.css",
                     "~/Content/test.css"  // 動確用に追加
                     ));

            // 動確用にデバッグ実行でも強制的にバンドルさせる
            BundleTable.EnableOptimizations = true;
        }
    }
}

BundleConfigの変更に合わせ以下の動作確認用スクリプトとスタイルを追加した。

動作確認用Javascript

Scripts/test.js
let test = {
    text = 'Hello World!!'
};
console.log(test?.text);

動作確認用CSS

Content/test.css
:root {
    --test-color: #fff;
}

動作確認結果

開発者ツール内で見ると展開されて見えてしまうので、Javascript、CSSのURLを叩いて直接確認してみるとMinify結果がバンドルされているため、しっかり動作していると思われる。

Javascript

CSS

結論

特別な事情が無い限りは更新の止まっているASP.NET MVC5ではなくASP.NET Core MVCを利用した方が良い。
※ちなみにASP.NET Core MVCにはBundleConfigの仕組みはデフォルトでは用意されておらず、LigerShark.WebOptimizer.Coreを入れればできるっぽい。

コメント