| File: Assets\ImportMapDefinition.cs | Web Access |
| Project: src\aspnetcore\src\Components\Endpoints\src\Microsoft.AspNetCore.Components.Endpoints.csproj (Microsoft.AspNetCore.Components.Endpoints) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Linq; using System.Text.Json; namespace Microsoft.AspNetCore.Components; /// <summary> /// Represents the contents of a <c><script type="importmap"></script></c> element that defines the import map /// for module scripts in the application. /// </summary> /// <remarks> /// The import map is a JSON object that defines the mapping of module import specifiers to URLs. /// <see cref="ImportMapDefinition"/> instances are expensive to create, so it is recommended to cache them if /// you are creating an additional instance. /// </remarks> public sealed class ImportMapDefinition { private Dictionary<string, string>? _imports; private Dictionary<string, IReadOnlyDictionary<string, string>>? _scopes; private Dictionary<string, string>? _integrity; private string? _json; /// <summary> /// Initializes a new instance of <see cref="ImportMapDefinition"/>."/> with the specified imports, scopes, and integrity. /// </summary> /// <param name="imports">The unscoped imports defined in the import map.</param> /// <param name="scopes">The scoped imports defined in the import map.</param> /// <param name="integrity">The integrity for the imports defined in the import map.</param> /// <remarks> /// The <paramref name="imports"/>, <paramref name="scopes"/>, and <paramref name="integrity"/> parameters /// will be copied into the new instance. The original collections will not be modified. /// </remarks> public ImportMapDefinition( IReadOnlyDictionary<string, string>? imports, IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>>? scopes, IReadOnlyDictionary<string, string>? integrity) { _imports = imports?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); _integrity = integrity?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); _scopes = scopes?.ToDictionary( kvp => kvp.Key, kvp => kvp.Value.ToDictionary(scopeKvp => scopeKvp.Key, scopeKvp => scopeKvp.Value) as IReadOnlyDictionary<string, string>); } private ImportMapDefinition() { } /// <summary> /// Creates an import map from a <see cref="ResourceAssetCollection"/>. /// </summary> /// <param name="assets">The collection of assets to create the import map from.</param> /// <returns>The import map.</returns> public static ImportMapDefinition FromResourceCollection(ResourceAssetCollection assets) { var importMap = new ImportMapDefinition(); foreach (var asset in assets) { if (!(asset.Url.EndsWith(".mjs", StringComparison.OrdinalIgnoreCase) || asset.Url.EndsWith(".js", StringComparison.OrdinalIgnoreCase)) || asset.Properties == null) { continue; } var (integrity, label) = GetAssetProperties(asset); if (integrity != null) { importMap._integrity ??= []; importMap._integrity[$"./{asset.Url}"] = integrity; } if (label != null) { importMap._imports ??= []; importMap._imports[$"./{label}"] = $"./{asset.Url}"; } } return importMap; } private static (string? integrity, string? label) GetAssetProperties(ResourceAsset asset) { string? integrity = null; string? label = null; for (var i = 0; i < asset.Properties!.Count; i++) { var property = asset.Properties[i]; if (string.Equals(property.Name, "integrity", StringComparison.OrdinalIgnoreCase)) { integrity = property.Value; } else if (string.Equals(property.Name, "label", StringComparison.OrdinalIgnoreCase)) { label = property.Value; } if (integrity != null && label != null) { return (integrity, label); } } return (integrity, label); } /// <summary> /// Combines one or more import maps into a single import map. /// </summary> /// <param name="sources">The list of import maps to combine.</param> /// <returns> /// A new import map that is the combination of all the input import maps with their /// entries applied in order. /// </returns> public static ImportMapDefinition Combine(params ImportMapDefinition[] sources) { var importMap = new ImportMapDefinition(); foreach (var item in sources) { if (item.Imports != null) { importMap._imports ??= []; foreach (var (key, value) in item.Imports) { importMap._imports[key] = value; } } if (item.Scopes != null) { importMap._scopes ??= []; foreach (var (key, value) in item.Scopes) { if (importMap._scopes.TryGetValue(key, out var existingScope) && existingScope != null) { foreach (var (scopeKey, scopeValue) in value) { ((Dictionary<string, string>)importMap._scopes[key])[scopeKey] = scopeValue; } } else { importMap._scopes[key] = new Dictionary<string, string>(value); } } } if (item.Integrity != null) { importMap._integrity ??= []; foreach (var (key, value) in item.Integrity) { importMap._integrity[key] = value; } } } return importMap; } // Example: // "imports": { // "triangle": "./module/shapes/triangle.js", // "pentagram": "https://example.com/shapes/pentagram.js" // } /// <summary> /// Gets the unscoped imports defined in the import map. /// </summary> public IReadOnlyDictionary<string, string>? Imports { get => _imports; } // Example: // { // "imports": { // "triangle": "./module/shapes/triangle.js" // }, // "scopes": { // "/modules/myshapes/": { // "triangle": "https://example.com/modules/myshapes/triangle.js" // } // } // } /// <summary> /// Gets the scoped imports defined in the import map. /// </summary> public IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>>? Scopes { get => _scopes; } // Example: // <script type="importmap"> // { // "imports": { // "triangle": "./module/shapes/triangle.js" // }, // "integrity": { // "./module/shapes/triangle.js": "sha256-..." // } // } // </script> /// <summary> /// Gets the integrity properties defined in the import map. /// </summary> public IReadOnlyDictionary<string, string>? Integrity { get => _integrity; } internal string ToJson() { _json ??= JsonSerializer.Serialize(this, ImportMapSerializerContext.CustomEncoder.Options); return _json; } /// <inheritdoc /> public override string ToString() => ToJson(); }