diff --git a/samples/StandaloneApp/Index.cshtml b/samples/StandaloneApp/Index.cshtml index f731b5af5a..92491f81c2 100644 --- a/samples/StandaloneApp/Index.cshtml +++ b/samples/StandaloneApp/Index.cshtml @@ -1,3 +1,2 @@ -@inherits Microsoft.Blazor.Components.BlazorComponent -

Hello, world!

+

Hello, world!

Hello from the Razor component. diff --git a/src/Microsoft.Blazor.Build/Core/RazorCompilation/Engine/BlazorCodeDocItems.cs b/src/Microsoft.Blazor.Build/Core/RazorCompilation/Engine/BlazorCodeDocItems.cs new file mode 100644 index 0000000000..1eeb4b59ee --- /dev/null +++ b/src/Microsoft.Blazor.Build/Core/RazorCompilation/Engine/BlazorCodeDocItems.cs @@ -0,0 +1,11 @@ +// 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.Blazor.Build.Core.RazorCompilation.Engine +{ + internal static class BlazorCodeDocItems + { + public static object ClassName = new object(); + public static object Namespace = new object(); + } +} diff --git a/src/Microsoft.Blazor.Build/Core/RazorCompilation/Engine/BlazorCodeTarget.cs b/src/Microsoft.Blazor.Build/Core/RazorCompilation/Engine/BlazorCodeTarget.cs new file mode 100644 index 0000000000..cc3a49d8ba --- /dev/null +++ b/src/Microsoft.Blazor.Build/Core/RazorCompilation/Engine/BlazorCodeTarget.cs @@ -0,0 +1,23 @@ +// 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.AspNetCore.Razor.Language.CodeGeneration; +using System; + +namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine +{ + /// + /// Directs a to use . + /// + internal class BlazorCodeTarget : CodeTarget + { + public override IntermediateNodeWriter CreateNodeWriter() + => new BlazorIntermediateNodeWriter(); + + public override TExtension GetExtension() + => throw new NotImplementedException(); + + public override bool HasExtension() + => throw new NotImplementedException(); + } +} diff --git a/src/Microsoft.Blazor.Build/Core/RazorCompilation/Engine/BlazorIntermediateNodeWriter.cs b/src/Microsoft.Blazor.Build/Core/RazorCompilation/Engine/BlazorIntermediateNodeWriter.cs new file mode 100644 index 0000000000..d8c84b8c22 --- /dev/null +++ b/src/Microsoft.Blazor.Build/Core/RazorCompilation/Engine/BlazorIntermediateNodeWriter.cs @@ -0,0 +1,64 @@ +// 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.AspNetCore.Razor.Language.CodeGeneration; +using Microsoft.AspNetCore.Razor.Language.Intermediate; + +namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine +{ + /// + /// Generates the C# code corresponding to Razor source document contents. + /// + internal class BlazorIntermediateNodeWriter : IntermediateNodeWriter + { + public override void BeginWriterScope(CodeRenderingContext context, string writer) + { + throw new System.NotImplementedException(nameof(BeginWriterScope)); + } + + public override void EndWriterScope(CodeRenderingContext context) + { + throw new System.NotImplementedException(nameof(EndWriterScope)); + } + + public override void WriteCSharpCode(CodeRenderingContext context, CSharpCodeIntermediateNode node) + { + throw new System.NotImplementedException(nameof(WriteCSharpCode)); + } + + public override void WriteCSharpCodeAttributeValue(CodeRenderingContext context, CSharpCodeAttributeValueIntermediateNode node) + { + throw new System.NotImplementedException(nameof(WriteCSharpCodeAttributeValue)); + } + + public override void WriteCSharpExpression(CodeRenderingContext context, CSharpExpressionIntermediateNode node) + { + throw new System.NotImplementedException(nameof(WriteCSharpExpression)); + } + + public override void WriteCSharpExpressionAttributeValue(CodeRenderingContext context, CSharpExpressionAttributeValueIntermediateNode node) + { + throw new System.NotImplementedException(nameof(WriteCSharpExpressionAttributeValue)); + } + + public override void WriteHtmlAttribute(CodeRenderingContext context, HtmlAttributeIntermediateNode node) + { + throw new System.NotImplementedException(nameof(WriteHtmlAttribute)); + } + + public override void WriteHtmlAttributeValue(CodeRenderingContext context, HtmlAttributeValueIntermediateNode node) + { + throw new System.NotImplementedException(nameof(WriteHtmlAttributeValue)); + } + + public override void WriteHtmlContent(CodeRenderingContext context, HtmlContentIntermediateNode node) + { + context.CodeWriter.Write("/* HTML content */"); + } + + public override void WriteUsingDirective(CodeRenderingContext context, UsingDirectiveIntermediateNode node) + { + throw new System.NotImplementedException(nameof(WriteUsingDirective)); + } + } +} diff --git a/src/Microsoft.Blazor.Build/Core/RazorCompilation/Engine/BlazorLoweringPhase.cs b/src/Microsoft.Blazor.Build/Core/RazorCompilation/Engine/BlazorLoweringPhase.cs new file mode 100644 index 0000000000..4d2d44f8f8 --- /dev/null +++ b/src/Microsoft.Blazor.Build/Core/RazorCompilation/Engine/BlazorLoweringPhase.cs @@ -0,0 +1,52 @@ +// 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.AspNetCore.Razor.Language; +using Microsoft.AspNetCore.Razor.Language.CodeGeneration; +using Microsoft.AspNetCore.Razor.Language.Intermediate; +using System; + +namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine +{ + /// + /// A phase that builds the C# document corresponding to + /// a for a Blazor component. + /// + internal class BlazorLoweringPhase : IRazorCSharpLoweringPhase + { + private readonly RazorCodeGenerationOptions _codegenOptions; + + public BlazorLoweringPhase(RazorCodeGenerationOptions codegenOptions) + { + _codegenOptions = codegenOptions + ?? throw new ArgumentNullException(nameof(codegenOptions)); + } + + public RazorEngine Engine { get; set; } + + public void Execute(RazorCodeDocument codeDocument) + { + var writer = BlazorComponentDocumentWriter.Create(_codegenOptions); + var documentNode = codeDocument.GetDocumentIntermediateNode(); + var csharpDoc = writer.WriteDocument(codeDocument, documentNode); + codeDocument.SetCSharpDocument(csharpDoc); + } + + /// + /// Creates instances that are configured to use + /// . + /// + private class BlazorComponentDocumentWriter : DocumentWriter + { + public static DocumentWriter Create(RazorCodeGenerationOptions options) + => Instance.Create(new BlazorCodeTarget(), options); + + private static BlazorComponentDocumentWriter Instance + = new BlazorComponentDocumentWriter(); + + public override RazorCSharpDocument WriteDocument( + RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) + => throw new NotImplementedException(); + } + } +} diff --git a/src/Microsoft.Blazor.Build/Core/RazorCompilation/Engine/BlazorRazorEngine.cs b/src/Microsoft.Blazor.Build/Core/RazorCompilation/Engine/BlazorRazorEngine.cs new file mode 100644 index 0000000000..8fa4b4facb --- /dev/null +++ b/src/Microsoft.Blazor.Build/Core/RazorCompilation/Engine/BlazorRazorEngine.cs @@ -0,0 +1,41 @@ +// 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.AspNetCore.Razor.Language; +using Microsoft.Blazor.Components; +using System.Linq; + +namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine +{ + /// + /// Wraps , configuring it to compile Blazor components. + /// + internal class BlazorRazorEngine + { + private readonly RazorEngine _engine; + private readonly RazorCodeGenerationOptions _codegenOptions; + + public BlazorRazorEngine() + { + _codegenOptions = RazorCodeGenerationOptions.CreateDefault(); + + _engine = RazorEngine.Create(configure => + { + configure.SetBaseType(typeof(BlazorComponent).FullName); + + configure.Phases.Remove( + configure.Phases.OfType().Single()); + configure.Phases.Add(new BlazorLoweringPhase(_codegenOptions)); + + configure.ConfigureClass((codeDoc, classNode) => + { + configure.SetNamespace((string)codeDoc.Items[BlazorCodeDocItems.Namespace]); + classNode.ClassName = (string)codeDoc.Items[BlazorCodeDocItems.ClassName]; + }); + }); + } + + public void Process(RazorCodeDocument document) + => _engine.Process(document); + } +} diff --git a/src/Microsoft.Blazor.Build/Core/RazorCompilation/RazorCompiler.cs b/src/Microsoft.Blazor.Build/Core/RazorCompilation/RazorCompiler.cs index 6d59fdab34..346b4a77ab 100644 --- a/src/Microsoft.Blazor.Build/Core/RazorCompilation/RazorCompiler.cs +++ b/src/Microsoft.Blazor.Build/Core/RazorCompilation/RazorCompiler.cs @@ -1,6 +1,10 @@ // 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.AspNetCore.Razor.Language; +using Microsoft.Blazor.Build.Core.RazorCompilation.Engine; +using Microsoft.Blazor.Components; +using Microsoft.Blazor.RenderTree; using System; using System.Collections.Generic; using System.IO; @@ -35,7 +39,7 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation TextWriter verboseOutput) => inputPaths.SelectMany(path => { - using (var reader = File.OpenText(path)) + using (var reader = File.OpenRead(path)) { return CompileSingleFile(inputRootPath, path, reader, baseNamespace, resultOutput, verboseOutput); } @@ -53,14 +57,14 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation public IEnumerable CompileSingleFile( string inputRootPath, string inputFilePath, - TextReader inputFileReader, + Stream inputFileContents, string baseNamespace, TextWriter resultOutput, TextWriter verboseOutput) { - if (inputFileReader == null) + if (inputFileContents == null) { - throw new ArgumentNullException(nameof(inputFileReader)); + throw new ArgumentNullException(nameof(inputFileContents)); } if (resultOutput == null) @@ -86,12 +90,20 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation ? baseNamespace : $"{baseNamespace}.{itemNamespace}"; - resultOutput.WriteLine($"namespace {combinedNamespace} {{"); - resultOutput.WriteLine($"public class {itemClassName}"); - resultOutput.WriteLine("{"); - resultOutput.WriteLine("}"); - resultOutput.WriteLine("}"); - resultOutput.WriteLine(); + var engine = new BlazorRazorEngine(); + + var sourceDoc = RazorSourceDocument.ReadFrom(inputFileContents, inputFilePath); + var codeDoc = RazorCodeDocument.Create(sourceDoc); + codeDoc.Items[BlazorCodeDocItems.Namespace] = combinedNamespace; + codeDoc.Items[BlazorCodeDocItems.ClassName] = itemClassName; + engine.Process(codeDoc); + var csharpDocument = codeDoc.GetCSharpDocument(); + var generatedCode = csharpDocument.GeneratedCode; + generatedCode = generatedCode.Replace( + "public async override global::System.Threading.Tasks.Task ExecuteAsync()", + $"public override void {nameof(BlazorComponent.BuildRenderTree)}({typeof(RenderTreeBuilder).FullName} builder)"); + + resultOutput.WriteLine(generatedCode); return Enumerable.Empty(); } diff --git a/src/Microsoft.Blazor.Build/Microsoft.Blazor.Build.csproj b/src/Microsoft.Blazor.Build/Microsoft.Blazor.Build.csproj index 47c9859584..5a5c65e271 100644 --- a/src/Microsoft.Blazor.Build/Microsoft.Blazor.Build.csproj +++ b/src/Microsoft.Blazor.Build/Microsoft.Blazor.Build.csproj @@ -22,6 +22,9 @@ + + + diff --git a/test/Microsoft.Blazor.Build.Test/RazorCompilerTest.cs b/test/Microsoft.Blazor.Build.Test/RazorCompilerTest.cs index 9e50e3fdd3..8273b396e3 100644 --- a/test/Microsoft.Blazor.Build.Test/RazorCompilerTest.cs +++ b/test/Microsoft.Blazor.Build.Test/RazorCompilerTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.Blazor.Build.Core.RazorCompilation; +using Microsoft.Blazor.Components; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using System; @@ -95,10 +96,17 @@ namespace Microsoft.Blazor.Build.Test { CSharpSyntaxTree.ParseText(csharpResult.Code) }; - var references = new[] + var referenceAssembliesContainingTypes = new[] { - MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location) + typeof(System.Runtime.AssemblyTargetedPatchBandAttribute), // System.Runtime + typeof(BlazorComponent) }; + var references = referenceAssembliesContainingTypes + .SelectMany(type => type.Assembly.GetReferencedAssemblies().Concat(new[] { type.Assembly.GetName() })) + .Distinct() + .Select(Assembly.Load) + .Select(assembly => MetadataReference.CreateFromFile(assembly.Location)) + .ToList(); var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); var assemblyName = "TestAssembly" + Guid.NewGuid().ToString("N"); var compilation = CSharpCompilation.Create(assemblyName, @@ -126,12 +134,12 @@ namespace Microsoft.Blazor.Build.Test using (var resultWriter = new StreamWriter(resultStream)) using (var verboseLogStream = new MemoryStream()) using (var verboseWriter = new StreamWriter(verboseLogStream)) - using (var inputReader = new StringReader(cshtmlContent)) + using (var inputContents = new MemoryStream(Encoding.UTF8.GetBytes(cshtmlContent))) { var diagnostics = new RazorCompiler().CompileSingleFile( cshtmlRootPath, cshtmlRelativePath, - inputReader, + inputContents, outputNamespace, resultWriter, verboseWriter);