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;
}
}
}
使い方
- タグヘルパークラスを配置します。
- 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"),
};
}
}
- 今回作成したタグヘルパーを利用して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の部分だけ抜粋しています。
※コメントはわかりやすくするためです。実際には出力されません。