-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Precompile razor views #1178
Precompile razor views #1178
Changes from all commits
130e1f9
1d49844
b990650
8287ac3
97166c6
b1fd168
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,34 +3,110 @@ | |
|
|
||
| using System; | ||
| using System.Collections.Concurrent; | ||
| using Microsoft.AspNet.FileSystems; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Reflection; | ||
|
|
||
| namespace Microsoft.AspNet.Mvc.Razor | ||
| { | ||
| public class CompilerCache | ||
| { | ||
| private readonly ConcurrentDictionary<string, Type> _cache; | ||
| private readonly ConcurrentDictionary<string, CompilerCacheEntry> _cache; | ||
| private static readonly Type[] EmptyType = new Type[0]; | ||
|
|
||
| public CompilerCache() | ||
| public CompilerCache([NotNull] IEnumerable<Assembly> assemblies) | ||
| : this(GetFileInfos(assemblies)) | ||
| { | ||
| _cache = new ConcurrentDictionary<string, Type>(StringComparer.OrdinalIgnoreCase); | ||
| } | ||
|
|
||
| public CompilationResult GetOrAdd(IFileInfo file, Func<CompilationResult> compile) | ||
| internal CompilerCache(IEnumerable<RazorFileInfoCollection> viewCollections) : this() | ||
| { | ||
| // Generate a content id | ||
| var contentId = file.PhysicalPath + '|' + file.LastModified.Ticks; | ||
| foreach (var viewCollection in viewCollections) | ||
| { | ||
| foreach (var fileInfo in viewCollection.FileInfos) | ||
| { | ||
| var containingAssembly = viewCollection.GetType().GetTypeInfo().Assembly; | ||
| var viewType = containingAssembly.GetType(fileInfo.FullTypeName); | ||
| var cacheEntry = new CompilerCacheEntry(fileInfo, viewType); | ||
|
|
||
| // There shouldn't be any duplicates and if there are any the first will win. | ||
| // If the result doesn't match the one on disk its going to recompile anyways. | ||
| _cache.TryAdd(fileInfo.RelativePath, cacheEntry); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| internal CompilerCache() | ||
| { | ||
| _cache = new ConcurrentDictionary<string, CompilerCacheEntry>(StringComparer.OrdinalIgnoreCase); | ||
| } | ||
|
|
||
| Type compiledType; | ||
| if (!_cache.TryGetValue(contentId, out compiledType)) | ||
| internal static IEnumerable<RazorFileInfoCollection> | ||
| GetFileInfos(IEnumerable<Assembly> assemblies) | ||
| { | ||
| return assemblies.SelectMany(a => a.ExportedTypes) | ||
| .Where(Match) | ||
| .Select(c => (RazorFileInfoCollection)Activator.CreateInstance(c)); | ||
| } | ||
|
|
||
| private static bool Match(Type t) | ||
| { | ||
| var inAssemblyType = typeof(RazorFileInfoCollection); | ||
| if (inAssemblyType.IsAssignableFrom(t)) | ||
| { | ||
| var result = compile(); | ||
| _cache.TryAdd(contentId, result.CompiledType); | ||
| var hasParameterlessConstructor = t.GetConstructor(EmptyType) != null; | ||
|
|
||
| return result; | ||
| return hasParameterlessConstructor | ||
| && !t.GetTypeInfo().IsAbstract | ||
| && !t.GetTypeInfo().ContainsGenericParameters; | ||
| } | ||
|
|
||
| return CompilationResult.Successful(compiledType); | ||
| return false; | ||
| } | ||
|
|
||
| public CompilationResult GetOrAdd(RelativeFileInfo fileInfo, Func<CompilationResult> compile) | ||
| { | ||
| if (!_cache.TryGetValue(fileInfo.RelativePath, out var cacheEntry)) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💯 |
||
| { | ||
| return OnCacheMiss(fileInfo, compile); | ||
| } | ||
| else | ||
| { | ||
| if (cacheEntry.Length != fileInfo.FileInfo.Length) | ||
| { | ||
| // it's not a match, recompile | ||
| return OnCacheMiss(fileInfo, compile); | ||
| } | ||
|
|
||
| if (cacheEntry.LastModified == fileInfo.FileInfo.LastModified) | ||
| { | ||
| // Match, not update needed | ||
| return CompilationResult.Successful(cacheEntry.ViewType); | ||
| } | ||
|
|
||
| var hash = RazorFileHash.GetHash(fileInfo.FileInfo); | ||
|
|
||
| // Timestamp doesn't match but it might be because of deployment, compare the hash. | ||
| if (cacheEntry.IsPreCompiled && | ||
| string.Equals(cacheEntry.Hash, hash, StringComparison.Ordinal)) | ||
| { | ||
| // Cache hit, but we need to update the entry | ||
| return OnCacheMiss(fileInfo, () => CompilationResult.Successful(cacheEntry.ViewType)); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Create another
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. meh, this is not a hot path by any means, will happen (maybe) once per view, I'm not concerned with this and it makes the code uglier. |
||
| } | ||
|
|
||
| // it's not a match, recompile | ||
| return OnCacheMiss(fileInfo, compile); | ||
| } | ||
| } | ||
|
|
||
| private CompilationResult OnCacheMiss(RelativeFileInfo file, Func<CompilationResult> compile) | ||
| { | ||
| var result = compile(); | ||
|
|
||
| var cacheEntry = new CompilerCacheEntry(file, result.CompiledType); | ||
| _cache.AddOrUpdate(file.RelativePath, cacheEntry, (a, b) => cacheEntry); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nope, this needs to replace |
||
|
|
||
| return result; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
|
||
| using System; | ||
|
|
||
| namespace Microsoft.AspNet.Mvc.Razor | ||
| { | ||
| public class CompilerCacheEntry | ||
| { | ||
| public CompilerCacheEntry([NotNull] RazorFileInfo info, [NotNull] Type viewType) | ||
| { | ||
| ViewType = viewType; | ||
| RelativePath = info.RelativePath; | ||
| Length = info.Length; | ||
| LastModified = info.LastModified; | ||
| Hash = info.Hash; | ||
| } | ||
|
|
||
| public CompilerCacheEntry([NotNull] RelativeFileInfo info, [NotNull] Type viewType) | ||
| { | ||
| ViewType = viewType; | ||
| RelativePath = info.RelativePath; | ||
| Length = info.FileInfo.Length; | ||
| LastModified = info.FileInfo.LastModified; | ||
| } | ||
|
|
||
| public Type ViewType { get; set; } | ||
| public string RelativePath { get; set; } | ||
| public long Length { get; set; } | ||
| public DateTime LastModified { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The file hash, should only be available for pre compiled files. | ||
| /// </summary> | ||
| public string Hash { get; set; } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be called
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Its really fingerprint :) potatos/potatos going to leave as is unless you have strong opinion on this |
||
|
|
||
| public bool IsPreCompiled { get { return Hash != null; } } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
|
||
| using System.Text; | ||
| using Microsoft.CodeAnalysis; | ||
| using Microsoft.CodeAnalysis.CSharp; | ||
| using Microsoft.CodeAnalysis.Text; | ||
|
|
||
| namespace Microsoft.AspNet.Mvc.Razor | ||
| { | ||
| public static class SyntaxTreeGenerator | ||
| { | ||
| private static CSharpParseOptions DefaultOptions | ||
| { | ||
| get | ||
| { | ||
| return CSharpParseOptions.Default | ||
| .WithLanguageVersion(LanguageVersion.CSharp6); | ||
| } | ||
| } | ||
|
|
||
| public static SyntaxTree Generate([NotNull] string text, [NotNull] string path) | ||
| { | ||
| return GenerateCore(text, path, DefaultOptions); | ||
| } | ||
|
|
||
| public static SyntaxTree Generate([NotNull] string text, | ||
| [NotNull] string path, | ||
| [NotNull] CSharpParseOptions options) | ||
| { | ||
| return GenerateCore(text, path, options); | ||
| } | ||
|
|
||
| public static SyntaxTree GenerateCore([NotNull] string text, | ||
| [NotNull] string path, | ||
| [NotNull] CSharpParseOptions options) | ||
| { | ||
| var sourceText = SourceText.From(text, Encoding.UTF8); | ||
| var syntaxTree = CSharpSyntaxTree.ParseText(sourceText, | ||
| path: path, | ||
| options: options); | ||
|
|
||
| return syntaxTree; | ||
| } | ||
|
|
||
| public static CSharpParseOptions GetParseOptions(CSharpCompilation compilation) | ||
| { | ||
| return CSharpParseOptions.Default | ||
| .WithLanguageVersion(compilation.LanguageVersion); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,10 @@ | ||
| // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
|
||
| using Microsoft.AspNet.FileSystems; | ||
|
|
||
| namespace Microsoft.AspNet.Mvc.Razor | ||
| { | ||
| public interface IRazorCompilationService | ||
| { | ||
| CompilationResult Compile(IFileInfo fileInfo); | ||
| CompilationResult Compile(RelativeFileInfo fileInfo); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use
Type.EmptyTypesThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope - Not supported in core clr