Write out all client app files to disk at build time instead of serving dynamically at runtime

This commit is contained in:
Steve Sanderson 2017-12-14 11:59:55 +00:00
parent 39b64c1bc3
commit aa63da5151
23 changed files with 223 additions and 56 deletions

View File

@ -48,7 +48,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Blazor.Common", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Blazor.Common.Test", "test\Microsoft.Blazor.Common.Test\Microsoft.Blazor.Common.Test.csproj", "{7F0BF3EA-6985-49F6-8070-0BBA41448BB0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Blazor.Server.Test", "test\Microsoft.Blazor.Server.Test\Microsoft.Blazor.Server.Test.csproj", "{0F94DDDD-DAD3-43C6-AFB1-4FA21BABCD3B}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Blazor.Build.Test", "test\Microsoft.Blazor.Build.Test\Microsoft.Blazor.Build.Test.csproj", "{709C7EBE-EB93-4F6D-9491-D714B0D2E898}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -116,10 +116,10 @@ Global
{7F0BF3EA-6985-49F6-8070-0BBA41448BB0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7F0BF3EA-6985-49F6-8070-0BBA41448BB0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7F0BF3EA-6985-49F6-8070-0BBA41448BB0}.Release|Any CPU.Build.0 = Release|Any CPU
{0F94DDDD-DAD3-43C6-AFB1-4FA21BABCD3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0F94DDDD-DAD3-43C6-AFB1-4FA21BABCD3B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0F94DDDD-DAD3-43C6-AFB1-4FA21BABCD3B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0F94DDDD-DAD3-43C6-AFB1-4FA21BABCD3B}.Release|Any CPU.Build.0 = Release|Any CPU
{709C7EBE-EB93-4F6D-9491-D714B0D2E898}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{709C7EBE-EB93-4F6D-9491-D714B0D2E898}.Debug|Any CPU.Build.0 = Debug|Any CPU
{709C7EBE-EB93-4F6D-9491-D714B0D2E898}.Release|Any CPU.ActiveCfg = Release|Any CPU
{709C7EBE-EB93-4F6D-9491-D714B0D2E898}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -143,7 +143,7 @@ Global
{2A076721-6081-4517-8329-B9E5110D6DAC} = {F5FDD4E5-6A52-4A86-BE5E-5E42CB1DC8DA}
{21EF76A4-63CC-455D-907C-F86C9E442CEC} = {B867E038-B3CE-43E3-9292-61568C46CDEB}
{7F0BF3EA-6985-49F6-8070-0BBA41448BB0} = {ADA3AE29-F6DE-49F6-8C7C-B321508CAE8E}
{0F94DDDD-DAD3-43C6-AFB1-4FA21BABCD3B} = {ADA3AE29-F6DE-49F6-8C7C-B321508CAE8E}
{709C7EBE-EB93-4F6D-9491-D714B0D2E898} = {ADA3AE29-F6DE-49F6-8C7C-B321508CAE8E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {504DA352-6788-4DC0-8705-82167E72A4D3}

View File

@ -18,10 +18,8 @@
<ItemGroup>
<ProjectReference Include="..\Microsoft.Blazor.Common\Microsoft.Blazor.Common.csproj" />
</ItemGroup>
<Import Project="..\Microsoft.Blazor.BuildTools\ReferenceFromSource.props" />
<Target Name="EnsureNpmRestored" Condition="!Exists('node_modules')">
<Exec Command="$(BlazorBuildToolsExe) checknodejs -v 8.3.0" />
<Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." />
<Exec Command="npm install" />
</Target>

View File

@ -0,0 +1,43 @@
// 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 Microsoft.Blazor.BuildTools.Core;
using Microsoft.Extensions.CommandLineUtils;
using System;
namespace Microsoft.Blazor.BuildTools.Cli.Commands
{
internal class BuildCommand
{
public static void Command(CommandLineApplication command)
{
var clientAssemblyPath = command.Argument("assembly",
"Specifies the assembly for the Blazor application.");
var webRootPath = command.Option("--webroot",
"Specifies the path to the directory containing static files to be served",
CommandOptionType.SingleValue);
command.OnExecute(() =>
{
if (string.IsNullOrEmpty(clientAssemblyPath.Value))
{
Console.WriteLine($"ERROR: No value specified for required argument '{clientAssemblyPath.Name}'.");
return 1;
}
try
{
Console.WriteLine($"Building Blazor app from {clientAssemblyPath.Value}...");
Build.Execute(clientAssemblyPath.Value, webRootPath.HasValue() ? webRootPath.Value() : null);
return 0;
}
catch (Exception ex)
{
Console.WriteLine($"ERROR: {ex.Message}");
Console.WriteLine(ex.StackTrace);
return 1;
}
});
}
}
}

View File

@ -8,7 +8,7 @@ using System.Text.RegularExpressions;
namespace Microsoft.Blazor.BuildTools.Cli.Commands
{
class CheckNodeJsInstalled
class CheckNodeJsInstalledCommand
{
private static Regex NodeVersionRegex = new Regex(@"^v(\d+\.\d+\.\d+)");

View File

@ -16,7 +16,8 @@ namespace Microsoft.Blazor.BuildTools
};
app.HelpOption("-?|-h|--help");
app.Command("checknodejs", CheckNodeJsInstalled.Command);
app.Command("checknodejs", CheckNodeJsInstalledCommand.Command);
app.Command("build", BuildCommand.Command);
if (args.Length > 0)
{

View File

@ -0,0 +1,25 @@
// 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 System.IO;
namespace Microsoft.Blazor.BuildTools.Core
{
internal static class Build
{
internal static void Execute(string assemblyPath, string webRootPath)
{
var clientFileSystem = ClientFileSystem.Instantiate(assemblyPath, webRootPath);
var distDirPath = Path.Combine(Path.GetDirectoryName(assemblyPath), "dist");
FileUtil.WriteFileProviderToDisk(clientFileSystem, distDirPath, clean: true);
// Temporary hack until ClientFileSystem can mount the subdirs in the correct place
var frameworkPath = Path.Combine(distDirPath, "_framework");
Directory.CreateDirectory(frameworkPath);
Directory.Move(Path.Combine(distDirPath, "_bin"), Path.Combine(frameworkPath, "_bin"));
Directory.Move(Path.Combine(distDirPath, "asmjs"), Path.Combine(frameworkPath, "asmjs"));
Directory.Move(Path.Combine(distDirPath, "wasm"), Path.Combine(frameworkPath, "wasm"));
File.Move(Path.Combine(distDirPath, "blazor.js"), Path.Combine(frameworkPath, "blazor.js"));
}
}
}

View File

@ -0,0 +1,44 @@
// 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 Microsoft.Blazor.BuildTools.Core.FrameworkFiles;
using Microsoft.Blazor.BuildTools.Core.WebRootFiles;
using Microsoft.Extensions.FileProviders;
using System.Collections.Generic;
using System.IO;
namespace Microsoft.Blazor.BuildTools.Core
{
internal static class ClientFileSystem
{
public static IFileProvider Instantiate(string clientAssemblyPath, string webRootPath)
{
var fileProviders = new List<IFileProvider>();
if (!File.Exists(clientAssemblyPath))
{
throw new FileNotFoundException($"Could not find client assembly file at '{clientAssemblyPath}'.", clientAssemblyPath);
}
var frameworkFileProvider = FrameworkFileProvider.Instantiate(
clientAssemblyPath);
fileProviders.Add(frameworkFileProvider);
if (!string.IsNullOrEmpty(webRootPath))
{
if (!Directory.Exists(webRootPath))
{
throw new DirectoryNotFoundException($"Could not find web root directory at '{webRootPath}'.");
}
var webRootFileProvider = WebRootFileProvider.Instantiate(
webRootPath,
Path.GetFileNameWithoutExtension(clientAssemblyPath),
frameworkFileProvider.GetDirectoryContents("/_bin"));
fileProviders.Add(webRootFileProvider);
}
return new CompositeFileProvider(fileProviders);
}
}
}

View File

@ -0,0 +1,60 @@
// 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 Microsoft.Extensions.FileProviders;
using System.IO;
namespace Microsoft.Blazor.BuildTools.Core
{
internal static class FileUtil
{
public static void WriteFileProviderToDisk(IFileProvider fileProvider, string outputDir, bool clean)
{
Directory.CreateDirectory(outputDir);
if (clean)
{
CleanDirectory(outputDir);
}
WriteFileProviderToDisk(fileProvider, string.Empty, outputDir);
}
private static void WriteFileProviderToDisk(IFileProvider fileProvider, string subpath, string outputDir)
{
foreach (var item in fileProvider.GetDirectoryContents(subpath))
{
var itemOutputPath = Path.Combine(outputDir, item.Name);
if (item.IsDirectory)
{
Directory.CreateDirectory(itemOutputPath);
WriteFileProviderToDisk(fileProvider, item.PhysicalPath, itemOutputPath);
}
else
{
using (var stream = item.CreateReadStream())
using (var fileStream = File.Open(itemOutputPath, FileMode.Create))
{
stream.CopyTo(fileStream);
}
}
}
}
private static void CleanDirectory(string path)
{
foreach (var itemName in Directory.GetFileSystemEntries(path))
{
var itemPath = Path.Combine(path, itemName);
if (File.GetAttributes(itemPath).HasFlag(FileAttributes.Directory))
{
Directory.Delete(itemPath, recursive: true);
}
else
{
File.Delete(itemPath);
}
}
}
}
}

View File

@ -6,7 +6,7 @@ using Microsoft.Blazor.Mono;
using Microsoft.Extensions.FileProviders;
using System.IO;
namespace Microsoft.Blazor.Server.FrameworkFiles
namespace Microsoft.Blazor.BuildTools.Core.FrameworkFiles
{
internal static class FrameworkFileProvider
{

View File

@ -7,7 +7,7 @@ using System.Linq;
using Mono.Cecil;
using Microsoft.Blazor.Internal.Common.FileProviders;
namespace Microsoft.Blazor.Server.FrameworkFiles
namespace Microsoft.Blazor.BuildTools.Core.FrameworkFiles
{
internal class ReferencedAssemblyFileProvider : InMemoryFileProvider
{

View File

@ -5,7 +5,7 @@ using Microsoft.Extensions.FileProviders;
using System.IO;
using System.Linq;
namespace Microsoft.Blazor.Server.FrameworkFiles
namespace Microsoft.Blazor.BuildTools.Core.FrameworkFiles
{
internal class ReferencedAssemblyResolver
{

View File

@ -8,7 +8,7 @@ using System.Text;
using Microsoft.Extensions.FileProviders;
using System.Linq;
namespace Microsoft.Blazor.Server.WebRootFiles
namespace Microsoft.Blazor.BuildTools.Core.WebRootFiles
{
internal class IndexHtmlFileProvider : InMemoryFileProvider
{

View File

@ -5,7 +5,7 @@ using Microsoft.Extensions.FileProviders;
using System.Collections.Generic;
using System.IO;
namespace Microsoft.Blazor.Server.WebRootFiles
namespace Microsoft.Blazor.BuildTools.Core.WebRootFiles
{
internal static class WebRootFileProvider
{

View File

@ -15,7 +15,12 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.Blazor.Browser\Microsoft.Blazor.Browser.csproj" />
<ProjectReference Include="..\Microsoft.Blazor.Mono\Microsoft.Blazor.Mono.csproj" />
<PackageReference Include="Microsoft.Extensions.CommandLineUtils" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Composite" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Physical" Version="2.0.0" />
<PackageReference Include="Mono.Cecil" Version="0.10.0-beta7" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.Blazor.Build.Test")]

View File

@ -14,4 +14,14 @@
<ContentWithTargetPath Include="$(BlazorMetadataFilePath)" TargetPath="$(BlazorMetadataFileName)" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Target>
<Target Name="BuildBlazorApp" AfterTargets="Build">
<PropertyGroup>
<WebRootName>wwwroot</WebRootName>
<WebRootPath>$(ProjectDir)$(WebRootName)</WebRootPath>
<WebRootParam Condition="Exists('$(WebRootPath)')">--webroot $(WebRootPath)</WebRootParam>
</PropertyGroup>
<!-- TODO: Find the correct time to run this (right after assemblies were written) -->
<Exec Command="$(BlazorBuildToolsExe) build $(ProjectDir)$(OutDir)$(AssemblyName).dll $(WebRootParam)" />
</Target>
</Project>

View File

@ -3,8 +3,7 @@
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Blazor.Server;
using Microsoft.Blazor.Server.FrameworkFiles;
using Microsoft.Blazor.Server.WebRootFiles;
using Microsoft.Extensions.FileProviders;
using System.Collections.Generic;
using System.IO;
using System.Net.Mime;
@ -21,7 +20,7 @@ namespace Microsoft.AspNetCore.Builder
var clientAssemblyPath = Path.Combine(binDir, $"{clientAssemblyName}.dll");
applicationBuilder.UseBlazorInternal(clientAssemblyPath);
}
// TODO: Change this combination of APIs to make it possible to supply either
// an assembly name (resolved to current bin dir) or full assembly path
internal static void UseBlazorInternal(
@ -29,44 +28,29 @@ namespace Microsoft.AspNetCore.Builder
string clientAssemblyPath)
{
var config = BlazorConfig.Read(clientAssemblyPath);
var frameworkFileProvider = FrameworkFileProvider.Instantiate(
config.SourceOutputAssemblyPath);
var clientAppBinDir = Path.GetDirectoryName(config.SourceOutputAssemblyPath);
var clientAppDistDir = Path.Combine(clientAppBinDir, "dist");
var distFileProvider = new PhysicalFileProvider(clientAppDistDir);
if (config.WebRootPath != null)
applicationBuilder.UseDefaultFiles(new DefaultFilesOptions
{
var webRootFileProvider = WebRootFileProvider.Instantiate(
config.WebRootPath,
Path.GetFileNameWithoutExtension(config.SourceOutputAssemblyPath),
frameworkFileProvider.GetDirectoryContents("/_bin"));
FileProvider = distFileProvider
});
applicationBuilder.UseDefaultFiles(new DefaultFilesOptions
{
FileProvider = webRootFileProvider
});
applicationBuilder.UseStaticFiles(new StaticFileOptions
{
FileProvider = webRootFileProvider
});
}
applicationBuilder.UseStaticFiles(new StaticFileOptions
{
RequestPath = "/_framework",
FileProvider = frameworkFileProvider,
FileProvider = distFileProvider,
ContentTypeProvider = CreateContentTypeProvider(),
});
}
private static IContentTypeProvider CreateContentTypeProvider()
{
return new FileExtensionContentTypeProvider(new Dictionary<string, string>
{
{ ".dll", MediaTypeNames.Application.Octet },
{ ".js", "application/javascript" },
{ ".mem", MediaTypeNames.Application.Octet },
{ ".wasm", MediaTypeNames.Application.Octet },
});
var result = new FileExtensionContentTypeProvider();
result.Mappings.Add(".dll", MediaTypeNames.Application.Octet);
result.Mappings.Add(".mem", MediaTypeNames.Application.Octet);
result.Mappings.Add(".wasm", MediaTypeNames.Application.Octet);
return result;
}
}
}

View File

@ -6,14 +6,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Composite" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Physical" Version="2.0.0" />
<PackageReference Include="Mono.Cecil" Version="0.10.0-beta7" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.Blazor.Browser\Microsoft.Blazor.Browser.csproj" />
<ProjectReference Include="..\Microsoft.Blazor.Mono\Microsoft.Blazor.Mono.csproj" />
</ItemGroup>
</Project>

View File

@ -1,4 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.Blazor.DevHost")]
[assembly: InternalsVisibleTo("Microsoft.Blazor.Server.Test")]

View File

@ -1,13 +1,13 @@
// 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 Microsoft.Blazor.Server.WebRootFiles;
using Microsoft.Extensions.FileProviders;
using System.IO;
using System.Linq;
using Xunit;
using System;
using AngleSharp.Parser.Html;
using Microsoft.Blazor.BuildTools.Core.WebRootFiles;
namespace Microsoft.Blazor.Server.Test
{

View File

@ -15,7 +15,7 @@
<ItemGroup>
<ProjectReference Include="..\..\samples\StandaloneApp\StandaloneApp.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Blazor.Server\Microsoft.Blazor.Server.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Blazor.BuildTools\Microsoft.Blazor.BuildTools.csproj" />
</ItemGroup>
</Project>

View File

@ -1,8 +1,8 @@
// 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 Microsoft.Blazor.BuildTools.Core.FrameworkFiles;
using Microsoft.Blazor.Mono;
using Microsoft.Blazor.Server.FrameworkFiles;
using Mono.Cecil;
using System;
using System.IO;

View File

@ -16,7 +16,9 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\samples\HostedInAspNet.Client\HostedInAspNet.Client.csproj" />
<ProjectReference Include="..\..\samples\HostedInAspNet.Server\HostedInAspNet.Server.csproj" />
<ProjectReference Include="..\..\samples\MonoSanityClient\MonoSanityClient.csproj" />
<ProjectReference Include="..\..\samples\MonoSanity\MonoSanity.csproj" />
<ProjectReference Include="..\..\samples\StandaloneApp\StandaloneApp.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Blazor.DevHost\Microsoft.Blazor.DevHost.csproj" />