Reference static content from referenced assemblies. Implements #340

This commit is contained in:
Steve Sanderson 2018-04-05 12:15:42 +01:00
parent 05059d9aa3
commit fef5a52183
20 changed files with 393 additions and 67 deletions

View File

@ -93,6 +93,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Performance", "benchmarks\Microsoft.AspNetCore.Blazor.Performance\Microsoft.AspNetCore.Blazor.Performance.csproj", "{50F6820F-D058-4E68-9E15-801F893F514E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorContent.CSharp", "src\Microsoft.AspNetCore.Blazor.Templates\content\BlazorContent.CSharp\BlazorContent.CSharp.csproj", "{3A457B14-D91B-4FFF-A81A-8F350BDB911F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -324,6 +326,10 @@ Global
{50F6820F-D058-4E68-9E15-801F893F514E}.Release|Any CPU.Build.0 = Release|Any CPU
{50F6820F-D058-4E68-9E15-801F893F514E}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
{50F6820F-D058-4E68-9E15-801F893F514E}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
{3A457B14-D91B-4FFF-A81A-8F350BDB911F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3A457B14-D91B-4FFF-A81A-8F350BDB911F}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
{3A457B14-D91B-4FFF-A81A-8F350BDB911F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3A457B14-D91B-4FFF-A81A-8F350BDB911F}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -365,6 +371,7 @@ Global
{9088E4E4-B855-457F-AE9E-D86709A5E1F4} = {F563ABB6-85FB-4CFC-B0D2-1D5130E8246D}
{C57382BC-EE93-49D5-BC40-5C98AF8AA048} = {4AE0D35B-D97A-44D0-8392-C9240377DCCE}
{50F6820F-D058-4E68-9E15-801F893F514E} = {36A7DEB7-5F88-4BFB-B57E-79EEC9950E25}
{3A457B14-D91B-4FFF-A81A-8F350BDB911F} = {E8EBA72C-D555-43AE-BC98-F0B2D05F6A07}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {504DA352-6788-4DC0-8705-82167E72A4D3}

View File

@ -15,15 +15,11 @@ namespace Microsoft.AspNetCore.Blazor.Build.Cli.Commands
CommandOptionType.SingleValue);
var references = command.Option("--reference",
"The path from the _bin folder to a given referenced dll file (Typically just the dll name)",
"The path from the _bin folder to a given referenced dll file (typically just the dll name)",
CommandOptionType.MultipleValue);
var jsReferences = command.Option("--js",
"Adds a <script> tag with the specified 'src' value",
CommandOptionType.MultipleValue);
var cssReferences = command.Option("--css",
"Adds a <link rel=stylesheet> tag with the specified 'href' value",
var embeddedResourcesSources = command.Option("--embedded-resources-source",
"The path to an assembly that may contain embedded resources (typically a referenced assembly in its pre-linked state)",
CommandOptionType.MultipleValue);
var outputPath = command.Option("--output",
@ -52,8 +48,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Cli.Commands
clientPage.Value(),
mainAssemblyPath.Value,
references.Values.ToArray(),
jsReferences.Values.ToArray(),
cssReferences.Values.ToArray(),
embeddedResourcesSources.Values.ToArray(),
linkerEnabledFlag.HasValue(),
outputPath.Value());
return 0;

View File

@ -0,0 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Blazor.Build
{
internal class EmbeddedResourceInfo
{
public EmbeddedResourceKind Kind { get; }
public string RelativePath { get; }
public EmbeddedResourceInfo(EmbeddedResourceKind kind, string relativePath)
{
Kind = kind;
RelativePath = relativePath;
}
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Blazor.Build
{
internal enum EmbeddedResourceKind
{
JavaScript,
Css,
Static
}
}

View File

@ -0,0 +1,137 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Mono.Cecil;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Microsoft.AspNetCore.Blazor.Build
{
internal class EmbeddedResourcesProcessor
{
const string ContentSubdirName = "_content";
private readonly static Dictionary<string, EmbeddedResourceKind> _knownResourceKindsByNamePrefix = new Dictionary<string, EmbeddedResourceKind>
{
{ "blazor:js:", EmbeddedResourceKind.JavaScript },
{ "blazor:css:", EmbeddedResourceKind.Css },
{ "blazor:file:", EmbeddedResourceKind.Static },
};
/// <summary>
/// Finds Blazor-specific embedded resources in the specified assemblies, writes them
/// to disk, and returns a description of those resources in dependency order.
/// </summary>
/// <param name="referencedAssemblyPaths">The paths to assemblies that may contain embedded resources.</param>
/// <param name="outputDir">The path to the directory where output is being written.</param>
/// <returns>A description of the embedded resources that were written to disk.</returns>
public static IReadOnlyList<EmbeddedResourceInfo> ExtractEmbeddedResources(
IEnumerable<string> referencedAssemblyPaths, string outputDir)
{
// Clean away any earlier state
var contentDir = Path.Combine(outputDir, ContentSubdirName);
if (Directory.Exists(contentDir))
{
Directory.Delete(contentDir, recursive: true);
}
// First, get an ordered list of AssemblyDefinition instances
var referencedAssemblyDefinitions = referencedAssemblyPaths
.Where(path => !Path.GetFileName(path).StartsWith("System.", StringComparison.Ordinal)) // Skip System.* because they are never going to contain embedded resources that we want
.Select(path => AssemblyDefinition.ReadAssembly(path))
.ToList();
referencedAssemblyDefinitions.Sort(OrderWithReferenceSubjectFirst);
// Now process them in turn
return referencedAssemblyDefinitions
.SelectMany(def => ExtractEmbeddedResourcesFromSingleAssembly(def, outputDir))
.ToList()
.AsReadOnly();
}
private static IEnumerable<EmbeddedResourceInfo> ExtractEmbeddedResourcesFromSingleAssembly(
AssemblyDefinition assemblyDefinition, string outputDirPath)
{
var assemblyName = assemblyDefinition.Name.Name;
foreach (var res in assemblyDefinition.MainModule.Resources)
{
if (TryExtractEmbeddedResource(assemblyName, res, outputDirPath, out var extractedResourceInfo))
{
yield return extractedResourceInfo;
}
}
}
private static bool TryExtractEmbeddedResource(string assemblyName, Resource resource, string outputDirPath, out EmbeddedResourceInfo extractedResourceInfo)
{
if (resource is EmbeddedResource embeddedResource)
{
if (TryInterpretLogicalName(resource.Name, out var kind, out var name))
{
// Prefix the output path with the assembly name to ensure no clashes
// Also be invariant to the OS on which the package was built
name = Path.Combine(ContentSubdirName, assemblyName, EnsureHasPathSeparators(name, Path.DirectorySeparatorChar));
// Write the file content to disk, ensuring we don't try to write outside the output root
var outputPath = Path.GetFullPath(Path.Combine(outputDirPath, name));
if (!outputPath.StartsWith(outputDirPath))
{
throw new InvalidOperationException($"Cannot write embedded resource from assembly '{assemblyName}' to '{outputPath}' because it is outside the expected directory {outputDirPath}");
}
WriteResourceFile(embeddedResource, outputPath);
// The URLs we write into the index.html file need to use web-style directory separators
extractedResourceInfo = new EmbeddedResourceInfo(kind, EnsureHasPathSeparators(name, '/'));
return true;
}
}
extractedResourceInfo = null;
return false;
}
private static void WriteResourceFile(EmbeddedResource resource, string outputPath)
{
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
using (var outputStream = File.OpenWrite(outputPath))
{
resource.GetResourceStream().CopyTo(outputStream);
}
}
private static string EnsureHasPathSeparators(string name, char desiredSeparatorChar) => name
.Replace('\\', desiredSeparatorChar)
.Replace('/', desiredSeparatorChar);
private static bool TryInterpretLogicalName(string logicalName, out EmbeddedResourceKind kind, out string resolvedName)
{
foreach (var kvp in _knownResourceKindsByNamePrefix)
{
if (logicalName.StartsWith(kvp.Key, StringComparison.Ordinal))
{
kind = kvp.Value;
resolvedName = logicalName.Substring(kvp.Key.Length);
return true;
}
}
kind = default;
resolvedName = default;
return false;
}
// For each assembly B that references A, we want the resources from A to be loaded before
// the references for B (because B's resources might depend on A's resources)
private static int OrderWithReferenceSubjectFirst(AssemblyDefinition a, AssemblyDefinition b)
=> AssemblyHasReference(a, b) ? 1
: AssemblyHasReference(b, a) ? -1
: 0;
private static bool AssemblyHasReference(AssemblyDefinition from, AssemblyDefinition to)
=> from.MainModule.AssemblyReferences
.Select(reference => reference.Name)
.Contains(to.Name.Name, StringComparer.Ordinal);
}
}

View File

@ -19,8 +19,7 @@ namespace Microsoft.AspNetCore.Blazor.Build
string path,
string assemblyPath,
IEnumerable<string> assemblyReferences,
IEnumerable<string> jsReferences,
IEnumerable<string> cssReferences,
IEnumerable<string> embeddedResourcesSources,
bool linkerEnabled,
string outputPath)
{
@ -31,7 +30,9 @@ namespace Microsoft.AspNetCore.Blazor.Build
}
var assemblyName = Path.GetFileNameWithoutExtension(assemblyPath);
var entryPoint = GetAssemblyEntryPoint(assemblyPath);
var updatedContent = GetIndexHtmlContents(template, assemblyName, entryPoint, assemblyReferences, jsReferences, cssReferences, linkerEnabled);
var embeddedContent = EmbeddedResourcesProcessor.ExtractEmbeddedResources(
embeddedResourcesSources, Path.GetDirectoryName(outputPath));
var updatedContent = GetIndexHtmlContents(template, assemblyName, entryPoint, assemblyReferences, embeddedContent, linkerEnabled);
var normalizedOutputPath = Normalize(outputPath);
Console.WriteLine("Writing index to: " + normalizedOutputPath);
File.WriteAllText(normalizedOutputPath, updatedContent);
@ -101,8 +102,7 @@ namespace Microsoft.AspNetCore.Blazor.Build
string assemblyName,
string assemblyEntryPoint,
IEnumerable<string> assemblyReferences,
IEnumerable<string> jsReferences,
IEnumerable<string> cssReferences,
IEnumerable<EmbeddedResourceInfo> embeddedContent,
bool linkerEnabled)
{
var resultBuilder = new StringBuilder();
@ -118,10 +118,11 @@ namespace Microsoft.AspNetCore.Blazor.Build
while (true)
{
var token = tokenizer.Get();
var tokenCharIndex = token.Position.Position - 1;
if (resumeOnNextToken)
{
resumeOnNextToken = false;
currentRangeStartPos = token.Position.Position;
currentRangeStartPos = tokenCharIndex;
}
switch (token.Type)
@ -134,7 +135,7 @@ namespace Microsoft.AspNetCore.Blazor.Build
{
// First, emit the original source text prior to this special tag, since
// we want that to be unchanged
resultBuilder.Append(htmlTemplate, currentRangeStartPos, token.Position.Position - currentRangeStartPos - 1);
resultBuilder.Append(htmlTemplate, currentRangeStartPos, tokenCharIndex - currentRangeStartPos);
// Instead of emitting the source text for this special tag, emit a fully-
// configured Blazor boot script tag
@ -149,11 +150,11 @@ namespace Microsoft.AspNetCore.Blazor.Build
// Emit tags to reference any specified JS/CSS files
AppendReferenceTags(
resultBuilder,
cssReferences,
embeddedContent.Where(c => c.Kind == EmbeddedResourceKind.Css).Select(c => c.RelativePath),
"<link rel=\"stylesheet\" href=\"{0}\" />");
AppendReferenceTags(
resultBuilder,
jsReferences,
embeddedContent.Where(c => c.Kind == EmbeddedResourceKind.JavaScript).Select(c => c.RelativePath),
"<script src=\"{0}\" defer></script>");
// Set a flag so we know not to emit anything else until the special

View File

@ -227,8 +227,11 @@
<!-- Index.html related paths and markers -->
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/ -->
<BlazorIndexHtmlOutputDir>$(BlazorIntermediateOutputPath)</BlazorIndexHtmlOutputDir>
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/index.html -->
<BlazorIndexHtmlOutputPath>$(BlazorIntermediateOutputPath)$(BlazorOutputIndexHtmlName)</BlazorIndexHtmlOutputPath>
<BlazorIndexHtmlOutputPath>$(BlazorIndexHtmlOutputDir)$(BlazorOutputIndexHtmlName)</BlazorIndexHtmlOutputPath>
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/inputs.index.cache -->
<BlazorBuildIndexInputsCache>$(BlazorIntermediateOutputPath)inputs.index.cache</BlazorBuildIndexInputsCache>
@ -568,8 +571,6 @@
<ItemGroup>
<BlazorIndexHtmlInput Include="$(BlazorIndexHtml)" />
<BlazorIndexHtmlInput Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->'%(FullPath)')" />
<BlazorIndexHtmlInput Include="@(BlazorPackageJsRef->'%(FullPath)')" />
<BlazorIndexHtmlInput Include="@(BlazorPackageCssRef->'%(FullPath)')" />
<BlazorIndexHtmlInput Include="@(_BlazorLinkingOption)" />
</ItemGroup>
@ -588,26 +589,30 @@
<Target
Name="_GenerateBlazorIndexHtml"
DependsOnTargets="_ResolveBlazorIndexHtmlInputs"
Inputs="$(BlazorBuildIndexInputsCache);$(BlazorIndexHtml)"
Inputs="$(BlazorBuildIndexInputsCache);$(BlazorIndexHtml);@(_BlazorDependencyInput)"
Outputs="$(BlazorIndexHtmlOutputPath)">
<ItemGroup>
<_UnlinkedAppReferencesPaths Include="@(_BlazorDependencyInput)" />
<_AppReferences Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->WithMetadataValue('PrimaryOutput','')->'%(FileName)%(Extension)')" />
<_JsReferences Include="@(BlazorPackageJsRef->'_content/%(SourcePackage)/%(RecursiveDir)%(FileName)%(Extension)')" />
<_CssReferences Include="@(BlazorPackageCssRef->'_content/%(SourcePackage)/%(RecursiveDir)%(FileName)%(Extension)')" />
</ItemGroup>
<PropertyGroup>
<_LinkerEnabledFlag Condition="'$(_BlazorShouldLinkApplicationAssemblies)' != ''">--linker-enabled</_LinkerEnabledFlag>
</PropertyGroup>
<Exec Command="$(BlazorBuildExe) build @(IntermediateAssembly) --html-page &quot;$(BlazorIndexHtml)&quot; @(_AppReferences->'--reference &quot;%(Identity)&quot;', ' ') @(_JsReferences->'--js &quot;%(Identity)&quot;', ' ') @(_CssReferences->'--css &quot;%(Identity)&quot;', ' ') $(_LinkerEnabledFlag) --output &quot;$(BlazorIndexHtmlOutputPath)&quot;" />
<Exec Command="$(BlazorBuildExe) build @(IntermediateAssembly) --html-page &quot;$(BlazorIndexHtml)&quot; @(_AppReferences->'--reference &quot;%(Identity)&quot;', ' ') @(_UnlinkedAppReferencesPaths->'--embedded-resources-source &quot;%(Identity)&quot;', ' ') $(_LinkerEnabledFlag) --output &quot;$(BlazorIndexHtmlOutputPath)&quot;" />
<ItemGroup Condition="Exists('$(BlazorIndexHtmlOutputPath)')">
<_BlazorIndex Include="$(BlazorIndexHtmlOutputPath)" />
<_BlazorIndexEmbeddedContentFile Include="$(BlazorIndexHtmlOutputDir)_content\**\*.*" />
<BlazorItemOutput Include="@(_BlazorIndex)">
<TargetOutputPath>$(ProjectDir)$(OutputPath)dist/%(FileName)%(Extension)</TargetOutputPath>
<Type>EntryPoint</Type>
</BlazorItemOutput>
<BlazorItemOutput Include="@(_BlazorIndexEmbeddedContentFile)">
<TargetOutputPath>$(ProjectDir)$(OutputPath)dist/_content/%(RecursiveDir)%(FileName)%(Extension)</TargetOutputPath>
</BlazorItemOutput>
<FileWrites Include="$(BlazorIndexHtmlOutputPath)" />
<FileWrites Include="@(_BlazorIndexEmbeddedContentFile)" />
</ItemGroup>
</Target>

View File

@ -0,0 +1,12 @@
{
"$schema": "http://json.schemastore.org/dotnetcli.host",
"symbolInfo": {
"skipRestore": {
"longName": "no-restore",
"shortName": ""
},
"Framework": {
"longName": "framework"
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,70 @@
{
"author": "Microsoft",
"classifications": [
"Web",
"Blazor",
"Content"
],
"groupIdentity": "Microsoft.Web.Blazor.Content",
"identity": "Microsoft.Web.Blazor.Content.CSharp",
"name": "Blazor (content)",
"preferNameDirectory": true,
"primaryOutputs": [
{
"path": "BlazorContent.CSharp.csproj"
}
],
"shortName": "blazorcontent",
"sourceName": "BlazorContent.CSharp",
"sources": [
{
"source": "./",
"target": "./",
"exclude": [
".template.config/**"
]
}
],
"symbols": {
"Framework": {
"type": "parameter",
"description": "The target framework for the project.",
"datatype": "choice",
"choices": [
{
"choice": "netcoreapp2.0",
"description": "Target netcoreapp2.0"
}
],
"replaces": "netcoreapp2.0",
"defaultValue": "netcoreapp2.0"
},
"HostIdentifier": {
"type": "bind",
"binding": "HostIdentifier"
},
"skipRestore": {
"type": "parameter",
"datatype": "bool",
"description": "If specified, skips the automatic restore of the project on create.",
"defaultValue": "false"
}
},
"tags": {
"language": "C#",
"type": "project"
},
"postActions": [
{
"condition": "(!skipRestore)",
"description": "Restore NuGet packages required by this project.",
"manualInstructions": [
{
"text": "Run 'dotnet restore'"
}
],
"actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
"continueOnError": true
}
]
}

View File

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk.Web;Microsoft.NET.Sdk.Razor/2.1.0-preview2-30230">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<OutputType>library</OutputType>
<IsPackable>true</IsPackable>
<BlazorLinkOnBuild>false</BlazorLinkOnBuild>
<!-- This custom package feed is required only when using nightly builds of Blazor -->
<RestoreSources>https://dotnet.myget.org/F/blazor-dev/api/v3/index.json;$(RestoreSources)</RestoreSources>
</PropertyGroup>
<ItemGroup>
<!-- .js/.css files will be referenced via <script>/<link> tags; other content files will just be included in the app's 'dist' directory without any tags referencing them -->
<EmbeddedResource Include="content\**\*.js" LogicalName="blazor:js:%(RecursiveDir)%(Filename)%(Extension)" />
<EmbeddedResource Include="content\**\*.css" LogicalName="blazor:css:%(RecursiveDir)%(Filename)%(Extension)" />
<EmbeddedResource Include="content\**" Exclude="**\*.js;**\*.css" LogicalName="blazor:file:%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.1.0-preview2-30230" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Blazor.Browser" Version="0.2.0-preview1-10168" />
<PackageReference Include="Microsoft.AspNetCore.Blazor.Build" Version="0.2.0-preview1-10168" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,3 @@
<div class="my-component">
This Blazor component is defined in the <strong>BlazorContent.CSharp</strong> package.
</div>

View File

@ -0,0 +1,15 @@
using System;
using Microsoft.AspNetCore.Blazor.Browser.Interop;
namespace BlazorContent.CSharp
{
public class ExampleJsInterop
{
public static string Prompt(string message)
{
return RegisteredFunction.Invoke<string>(
"BlazorContent.CSharp.ExampleJsInterop.Prompt",
message);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 B

View File

@ -0,0 +1,6 @@
// This file is to show how a content package may provide JavaScript interop features
// wrapped in a .NET API
Blazor.registerFunction('BlazorContent.CSharp.ExampleJsInterop.Prompt', function (message) {
return prompt(message, 'Type anything here');
});

View File

@ -0,0 +1,11 @@
/*
This file is to show how CSS and other static resources (such as images) can be
used from a content project/package.
*/
.my-component {
border: 2px dashed red;
padding: 1em;
margin: 1em 0;
background-image: url('background.png');
}

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using AngleSharp.Parser.Html;
using System;
using System.Linq;
using Xunit;
@ -27,15 +28,12 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
<script type='blazor-boot' custom1 custom2=""value"">some text that should be removed</script>
{htmlTemplateSuffix}";
var assemblyReferences = new string[] { "System.Abc.dll", "MyApp.ClassLib.dll", };
var jsReferences = new string[] { "some/file.js", "another.js" };
var cssReferences = new string[] { "my/styles.css" };
var instance = IndexHtmlWriter.GetIndexHtmlContents(
htmlTemplate,
"MyApp.Entrypoint",
"MyNamespace.MyType::MyMethod",
assemblyReferences,
jsReferences,
cssReferences,
Enumerable.Empty<EmbeddedResourceInfo>(),
linkerEnabled: true);
// Act & Assert: Start and end is not modified (including formatting)
@ -57,16 +55,6 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
Assert.Equal(string.Empty, scriptElem.Attributes["custom1"].Value);
Assert.Equal("value", scriptElem.Attributes["custom2"].Value);
Assert.Equal("true", scriptElem.Attributes["linker-enabled"].Value);
// Assert: Also contains script tags referencing JS files
Assert.Equal(
scriptElems.Skip(1).Select(tag => tag.GetAttribute("src")),
jsReferences);
// Assert: Also contains link tags referencing CSS files
Assert.Equal(
linkElems.Select(tag => tag.GetAttribute("href")),
cssReferences);
}
[Fact]
@ -79,10 +67,50 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
var cssReferences = new string[] { "my/styles.css" };
var content = IndexHtmlWriter.GetIndexHtmlContents(
htmlTemplate, "MyApp.Entrypoint", "MyNamespace.MyType::MyMethod", assemblyReferences, jsReferences, cssReferences, linkerEnabled: true);
htmlTemplate, "MyApp.Entrypoint", "MyNamespace.MyType::MyMethod", assemblyReferences, Enumerable.Empty<EmbeddedResourceInfo>(), linkerEnabled: true);
// Assert
Assert.Equal(htmlTemplate, content);
}
[Fact]
public void InjectsAdditionalTagsForEmbeddedContent()
{
// Arrange
var htmlTemplate = "Start <script id='testboot' type='blazor-boot'></script> End";
var embeddedContent = new[]
{
new EmbeddedResourceInfo(EmbeddedResourceKind.Static, "my/static/file"),
new EmbeddedResourceInfo(EmbeddedResourceKind.Css, "css/first.css"),
new EmbeddedResourceInfo(EmbeddedResourceKind.JavaScript, "javascript/first.js"),
new EmbeddedResourceInfo(EmbeddedResourceKind.Css, "css/second.css"),
new EmbeddedResourceInfo(EmbeddedResourceKind.JavaScript, "javascript/second.js"),
};
// Act
var resultHtml = IndexHtmlWriter.GetIndexHtmlContents(
htmlTemplate,
"MyApp.Entrypoint",
"MyNamespace.MyType::MyMethod",
assemblyReferences: new[] { "Something.dll" },
embeddedContent: embeddedContent,
linkerEnabled: true);
// Assert
var parsedHtml = new HtmlParser().Parse(resultHtml);
var blazorBootScript = parsedHtml.GetElementById("testboot");
Assert.NotNull(blazorBootScript);
Assert.Equal(
"Start "
+ blazorBootScript.OuterHtml
// First we insert the CSS file tags in order
+ Environment.NewLine + "<link rel=\"stylesheet\" href=\"css/first.css\" />"
+ Environment.NewLine + "<link rel=\"stylesheet\" href=\"css/second.css\" />"
// Then the JS file tags in order, each with 'defer'
+ Environment.NewLine + "<script src=\"javascript/first.js\" defer></script>"
+ Environment.NewLine + "<script src=\"javascript/second.js\" defer></script>"
+ " End",
resultHtml);
}
}
}

View File

@ -13,11 +13,6 @@
<ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Blazor.Browser\Microsoft.AspNetCore.Blazor.Browser.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Blazor\Microsoft.AspNetCore.Blazor.csproj" />
</ItemGroup>
<!-- Local alternative to <PackageReference> to the content package -->
<Import Project="..\TestContentPackage\build\TestContentPackage.props" />
<ItemGroup>
<ProjectReference Include="..\TestContentPackage\TestContentPackage.csproj" />
</ItemGroup>

View File

@ -7,9 +7,14 @@
</PropertyGroup>
<ItemGroup>
<Content Remove="**" />
<Content Include="build\**" PackagePath="build" />
<Content Include="content\**" PackagePath="content" />
<!-- .js files will be referenced via <script> tags -->
<EmbeddedResource Include="content\**\*.js" LogicalName="blazor:js:%(RecursiveDir)%(Filename)%(Extension)" />
<!-- .css files will be referenced via <link rel='Stylesheet'> tags -->
<EmbeddedResource Include="content\**\*.css" LogicalName="blazor:css:%(RecursiveDir)%(Filename)%(Extension)" />
<!-- Any other files will be included in the 'dist' output but without any tags referencing them -->
<EmbeddedResource Include="content\**" Exclude="**\*.js;**\*.css" LogicalName="blazor:file:%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>
<ItemGroup>

View File

@ -1,19 +0,0 @@
<Project>
<PropertyGroup>
<!-- Update this to match your package ID exactly -->
<_PackageId>TestContentPackage</_PackageId>
<_ContentDir>$(MSBuildThisFileDirectory)..\content\</_ContentDir>
</PropertyGroup>
<ItemGroup>
<!-- All files under "content" will be included with the Blazor app build output -->
<BlazorPackageContentFile Include="$(_ContentDir)**" SourcePackage="$(_PackageId)" />
<!-- We'll generate a <script> tag importing each of the following JavaScript files -->
<!-- Change the "Include" pattern if you don't want to include all .js files. -->
<BlazorPackageJsRef Include="$(_ContentDir)**\*.js" SourcePackage="$(_PackageId)" />
<!-- We'll generate a <link> tag importing each of the following CSS files -->
<!-- Change the "Include" pattern if you don't want to include all .css files. -->
<BlazorPackageCssRef Include="$(_ContentDir)**\*.css" SourcePackage="$(_PackageId)" />
</ItemGroup>
</Project>