アイキャッチ画像

Razor PagesでESMのカスタムタグヘルパーを作成する方法について

Razor PagesのJavaScriptモジュールと言えば「WebOptimizer」でできますが、ネイティブのJavaScriptにもJavaScriptモジュールが存在しています。

当記事では、ESM(ネイティブの JavaScript モジュール)のカスタムタグヘルパーを紹介していきます。また、ブラウザキャッシュの対策もされていますので、サンプルソースをコピーして利用してみてください。

対象環境

  • .Net 6 ※.Net Core 3.1以降
  • Razor Pages
  • MVC

タグヘルパーの紹介

ESMのタグヘルパーになります。ブラウザキャッシュの対策もされています。

using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using System.Text;
using System.Text.Encodings.Web;

namespace SamplePages.TagHelpers.ESM
{
    [HtmlTargetElement("esm")]
    public class ESMTagHelper : UrlResolutionTagHelper
    {
        /// <summary>
        /// JavaScriptの一覧
        /// </summary>
        [HtmlAttributeName("scripts")]
        public List<ScriptItem> Scripts { get; set; } = new List<ScriptItem>();

        public IFileVersionProvider _fileVersionProvider;

        public ESMTagHelper(
            IUrlHelperFactory urlHelperFactory,
            HtmlEncoder htmlEncoder)
            : base(urlHelperFactory, htmlEncoder)
        {
        }

        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            ArgumentNullException.ThrowIfNull(context);
            ArgumentNullException.ThrowIfNull(output);

            EnsureFileVersionProvider();

            output.TagName = "script";
            output.Attributes.Add("type ", "module");
            StringBuilder stringBuilder = new StringBuilder();
            foreach (ScriptItem scriptItem in Scripts)
            {
                // nameの指定が無ければ、重複防止のためにGUIDを利用
                string name = !string.IsNullOrEmpty(scriptItem.Name) ? scriptItem.Name : $"esm_{Guid.NewGuid():N}";
                TryResolveUrl(scriptItem.Src ?? "", out string? moduleName);
                // ブラウザキャッシュ対策(asp-append-versionと同一の動作)
                if (scriptItem.AspAppendVersion)
                {
                    moduleName = _fileVersionProvider.AddFileVersionToPath(ViewContext.HttpContext.Request.PathBase, moduleName);
                }
                stringBuilder.AppendLine(@$"import * as {name} from ""{moduleName}"";");
            }

            output.Content.SetHtmlContent(stringBuilder.ToString());
            base.Process(context, output);
        }

        private void EnsureFileVersionProvider()
        {
            if (_fileVersionProvider == null)
            {
                _fileVersionProvider = ViewContext.HttpContext.RequestServices.GetRequiredService<IFileVersionProvider>();
            }
        }
    }
}

JavaScriptを管理するクラスになります。Srcは「~」を利用することができるようになっています。

namespace SamplePages.TagHelpers.ESM
{
    public class ScriptItem
    {
        /// <summary>
        /// scriptタグのsrc属性
        /// </summary>
        public string Src { get; set; }

        /// <summary>
        /// ブラウザキャッシュ対策
        /// </summary>
        public bool AspAppendVersion { get; set; }

        /// <summary>
        /// importのname ※重複禁止
        /// </summary>
        public string? Name { get; set; }

        public ScriptItem(string src, bool aspAppendVersion = true, string? name = null)
        {
            Src = src;
            AspAppendVersion = aspAppendVersion;
            Name = name;
        }
    }
}

使い方

  1. タグヘルパークラスを配置します。
タグヘルパークラスの配置場所
  1. JavaScriptを管理するクラスを作成します。
using SamplePages.TagHelpers.ESM;

namespace SamplePages
{
    /// <summary>
    /// 例
    /// </summary>
    public static class EsmContents
    {
        public static List<ScriptItem> Common =
            new List<ScriptItem>
            {
                        new ScriptItem("~/lib/jquery/dist/jquery.min.js"),
                        new ScriptItem("~/lib/bootstrap/dist/js/bootstrap.min.js"),
                        new ScriptItem("~/lib/jquery-validation/dist/jquery.validate.min.js"),
                        new ScriptItem("~/lib/jquery-validation/dist/additional-methods.min.js"),
            };

        public static List<ScriptItem> Index =
            new List<ScriptItem>
            {
                new ScriptItem("~/js/common.js"),
                new ScriptItem("~/js/index/sample.js"),
            };
    }
}
  1. 今回作成したタグヘルパーを利用してJavaScriptを読み込みます。「scripts」に2.で作成した内容を指定すれば完成です。
<esm scripts="EsmContents.Common"></esm>
@*ESMの内容*@
@*<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.min.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation/dist/additional-methods.min.js"></script>*@

<esm scripts="EsmContents.Index"></esm>
@*ESMの内容*@
@*<script src="~/js/common.js"></script>
<script src="~/js/index/sample.js"></script>*@

実行結果

実行して作成されるHTMLは以下のようになります。

<!-- <esm scripts="EsmContents.Common"></esm> -->
<script type="module">import * as esm_dadea85b39dd4bba8eb5259ff19e7b49 from "/lib/jquery/dist/jquery.min.js?v=_xUj-3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej_m4";
import * as esm_b7099655e017446397bb214b1d3a521f from "/lib/bootstrap/dist/js/bootstrap.min.js?v=c4Ll6eSIg6Eothk8pCWAF8aE923EvtU11pqjBy-NjNM";
import * as esm_a11174ae03ac4753bd06b87dd73cbc3a from "/lib/jquery-validation/dist/jquery.validate.min.js?v=F6h55Qw6sweK-t7SiOJX-2bpSAa3b_fnlrVCJvmEj1A";
import * as esm_418377f5faba4043a4f52e5aa9871954 from "/lib/jquery-validation/dist/additional-methods.min.js?v=2F_T6dcoSumcuA_fcU4W36VpSKPtq4nQf_0_vNFsC-w";
</script>
<!-- <esm scripts="EsmContents.Index"></esm> -->
<script type="module">import * as esm_978308985b074efa9d2d8dcfc7147c85 from "/js/common.js?v=Kc7vJdPMUXz_QwNxzAb3jj_-tyh6D6OPlTWQxeKlANA";
import * as esm_6663f0d0c3124e99b1bac9fa43a566f6 from "/js/index/sample.js?v=oH22PIMxdBK_GQVqEIVuuqoHbHeGV_nnQnt2G1XlgmE";
</script>

※ESMの部分だけ抜粋しています。

※コメントはわかりやすくするためです。実際には出力されません。