Skip to content

Commit

Permalink
Live reloading (currently enabled only for command-line builds - will
Browse files Browse the repository at this point in the history
add VS support next)
  • Loading branch information
SteveSandersonMS committed Apr 2, 2018
1 parent 512a15e commit 0b3b84f
Show file tree
Hide file tree
Showing 23 changed files with 539 additions and 20 deletions.
12 changes: 12 additions & 0 deletions Blazor.sln
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.VisualStudio.Blaz
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestContentPackage", "test\testapps\TestContentPackage\TestContentPackage.csproj", "{C57382BC-EE93-49D5-BC40-5C98AF8AA048}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LiveReloadTestApp", "test\testapps\LiveReloadTestApp\LiveReloadTestApp.csproj", "{0246AA77-1A27-4A67-874B-6EF6F99E414E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -281,6 +283,7 @@ Global
{F3E02B21-1127-431A-B832-0E53CB72097B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F3E02B21-1127-431A-B832-0E53CB72097B}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
{F3E02B21-1127-431A-B832-0E53CB72097B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F3E02B21-1127-431A-B832-0E53CB72097B}.Release|Any CPU.Build.0 = Release|Any CPU
{F3E02B21-1127-431A-B832-0E53CB72097B}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
{FF25111E-5A3E-48A3-96D8-08A2C5A2A91C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FF25111E-5A3E-48A3-96D8-08A2C5A2A91C}.Debug|Any CPU.Build.0 = Debug|Any CPU
Expand Down Expand Up @@ -312,6 +315,14 @@ Global
{C57382BC-EE93-49D5-BC40-5C98AF8AA048}.Release|Any CPU.Build.0 = Release|Any CPU
{C57382BC-EE93-49D5-BC40-5C98AF8AA048}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
{C57382BC-EE93-49D5-BC40-5C98AF8AA048}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
{0246AA77-1A27-4A67-874B-6EF6F99E414E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0246AA77-1A27-4A67-874B-6EF6F99E414E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0246AA77-1A27-4A67-874B-6EF6F99E414E}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
{0246AA77-1A27-4A67-874B-6EF6F99E414E}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
{0246AA77-1A27-4A67-874B-6EF6F99E414E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0246AA77-1A27-4A67-874B-6EF6F99E414E}.Release|Any CPU.Build.0 = Release|Any CPU
{0246AA77-1A27-4A67-874B-6EF6F99E414E}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
{0246AA77-1A27-4A67-874B-6EF6F99E414E}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -352,6 +363,7 @@ Global
{43E39257-7DC1-46BD-9BD9-2319A1313D07} = {F563ABB6-85FB-4CFC-B0D2-1D5130E8246D}
{9088E4E4-B855-457F-AE9E-D86709A5E1F4} = {F563ABB6-85FB-4CFC-B0D2-1D5130E8246D}
{C57382BC-EE93-49D5-BC40-5C98AF8AA048} = {4AE0D35B-D97A-44D0-8392-C9240377DCCE}
{0246AA77-1A27-4A67-874B-6EF6F99E414E} = {4AE0D35B-D97A-44D0-8392-C9240377DCCE}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {504DA352-6788-4DC0-8705-82167E72A4D3}
Expand Down
8 changes: 8 additions & 0 deletions src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Boot.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { platform } from './Environment';
import { getAssemblyNameFromUrl } from './Platform/DotNet';
import { enableLiveReloading } from './LiveReloading';
import './Rendering/Renderer';
import './Services/Http';
import './Services/UriHelper';
Expand Down Expand Up @@ -36,6 +37,13 @@ async function boot() {

// Start up the application
platform.callEntryPoint(entryPointAssemblyName, entryPointMethod, []);

// Enable live reloading only if there's a "reload" attribute on the <script> tag.
// In production, this should not be the case.
const reloadUri = thisScriptElem.getAttribute('reload');
if (reloadUri) {
enableLiveReloading(reloadUri);
}
}

function getRequiredBootScriptAttribute(elem: HTMLScriptElement, attributeName: string): string {
Expand Down
48 changes: 48 additions & 0 deletions src/Microsoft.AspNetCore.Blazor.Browser.JS/src/LiveReloading.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
export function enableLiveReloading(endpointUri: string) {
listenForReloadEvent(endpointUri);
}

function listenForReloadEvent(endpointUri: string) {
if (!WebSocket) {
console.log('Browser does not support WebSocket, so live reloading will be disabled.');
return;
}

// First, connect to the endpoint
const websocketUri = toAbsoluteWebSocketUri(endpointUri);
const source = new WebSocket(websocketUri);
let allowConnectionFailedErrorReporting = true;

source.onopen = e => {
allowConnectionFailedErrorReporting = false;
};

source.onerror = e => {
if (allowConnectionFailedErrorReporting) {
allowConnectionFailedErrorReporting = false;
console.error(`The client app was compiled with live reloading enabled, but could not open `
+ ` a WebSocket connection to the server at ${websocketUri}\n`
+ `To fix this inconsistency, either run the server in development mode, or compile the `
+ `client app in Release configuration.`);
}
};

// If we're notified that we should reload, then do so
source.onmessage = e => {
if (e.data === 'reload') {
location.reload();
}
};
}

function toAbsoluteWebSocketUri(uri: string) {
const baseUri = document.baseURI;
if (baseUri) {
const lastSlashPos = baseUri.lastIndexOf('/');
const prefix = baseUri.substr(0, lastSlashPos);
uri = prefix + uri;
}

// Scheme must be ws: or wss:
return uri.replace(/^http/, 'ws');
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ public static void Command(CommandLineApplication command)
"Adds a <link rel=stylesheet> tag with the specified 'href' value",
CommandOptionType.MultipleValue);

var reloadUri = command.Option("--reload-uri",
"If specified, enables live reloading and specifies the URI of the notification endpoint.",
CommandOptionType.SingleValue);

var outputPath = command.Option("--output",
"Path to the output file",
CommandOptionType.SingleValue);
Expand Down Expand Up @@ -55,6 +59,7 @@ public static void Command(CommandLineApplication command)
jsReferences.Values.ToArray(),
cssReferences.Values.ToArray(),
linkerEnabledFlag.HasValue(),
reloadUri.Value(),
outputPath.Value());
return 0;
}
Expand Down
23 changes: 18 additions & 5 deletions src/Microsoft.AspNetCore.Blazor.Build/Core/IndexHtmlWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public static void UpdateIndex(
IEnumerable<string> jsReferences,
IEnumerable<string> cssReferences,
bool linkerEnabled,
string reloadUri,
string outputPath)
{
var template = GetTemplate(path);
Expand All @@ -31,7 +32,7 @@ public static void UpdateIndex(
}
var assemblyName = Path.GetFileNameWithoutExtension(assemblyPath);
var entryPoint = GetAssemblyEntryPoint(assemblyPath);
var updatedContent = GetIndexHtmlContents(template, assemblyName, entryPoint, assemblyReferences, jsReferences, cssReferences, linkerEnabled);
var updatedContent = GetIndexHtmlContents(template, assemblyName, entryPoint, assemblyReferences, jsReferences, cssReferences, linkerEnabled, reloadUri);
var normalizedOutputPath = Normalize(outputPath);
Console.WriteLine("Writing index to: " + normalizedOutputPath);
File.WriteAllText(normalizedOutputPath, updatedContent);
Expand Down Expand Up @@ -103,7 +104,8 @@ public static string GetIndexHtmlContents(
IEnumerable<string> assemblyReferences,
IEnumerable<string> jsReferences,
IEnumerable<string> cssReferences,
bool linkerEnabled)
bool linkerEnabled,
string reloadUri)
{
var resultBuilder = new StringBuilder();

Expand Down Expand Up @@ -144,7 +146,8 @@ public static string GetIndexHtmlContents(
assemblyEntryPoint,
assemblyReferences,
linkerEnabled,
tag.Attributes);
tag.Attributes,
reloadUri);

// Emit tags to reference any specified JS/CSS files
AppendReferenceTags(
Expand Down Expand Up @@ -175,7 +178,11 @@ public static string GetIndexHtmlContents(

case HtmlTokenType.EndOfFile:
// Finally, emit any remaining text from the original source file
resultBuilder.Append(htmlTemplate, currentRangeStartPos, htmlTemplate.Length - currentRangeStartPos);
var remainingLength = htmlTemplate.Length - currentRangeStartPos;
if (remainingLength > 0)
{
resultBuilder.Append(htmlTemplate, currentRangeStartPos, remainingLength);
}
return resultBuilder.ToString();
}
}
Expand All @@ -202,7 +209,8 @@ private static void AppendScriptTagWithBootConfig(
string assemblyEntryPoint,
IEnumerable<string> binFiles,
bool linkerEnabled,
List<KeyValuePair<string, string>> attributes)
List<KeyValuePair<string, string>> attributes,
string reloadUri)
{
var assemblyNameWithExtension = $"{assemblyName}.dll";

Expand All @@ -224,6 +232,11 @@ private static void AppendScriptTagWithBootConfig(
attributesDict.Remove("linker-enabled");
}

if (!string.IsNullOrEmpty(reloadUri))
{
attributesDict["reload"] = reloadUri;
}

resultBuilder.Append("<script");
foreach (var attributePair in attributesDict)
{
Expand Down
9 changes: 9 additions & 0 deletions src/Microsoft.AspNetCore.Blazor.Build/targets/All.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,16 @@
<PropertyGroup>
<DefaultWebContentItemExcludes>$(DefaultWebContentItemExcludes);wwwroot\**</DefaultWebContentItemExcludes>

<!-- By default, live reloading is on for debug builds -->
<UseBlazorLiveReloading Condition="'$(Configuration)' == 'Debug'">true</UseBlazorLiveReloading>
<BlazorLiveReloadUri>/_reload</BlazorLiveReloadUri>

<!-- We can remove this after updating to newer Razor tooling, where it's enabled by default -->
<UseRazorBuildServer>true</UseRazorBuildServer>
</PropertyGroup>

<ItemGroup>
<!-- In case you're using 'dotnet watch', enable reloading when editing .cshtml files -->
<Watch Include="$(ProjectDir)**\*.cshtml" />
</ItemGroup>
</Project>
3 changes: 2 additions & 1 deletion src/Microsoft.AspNetCore.Blazor.Build/targets/All.targets
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<PropertyGroup>
<BlazorBuildExe>dotnet &quot;$(MSBuildThisFileDirectory)../tools/Microsoft.AspNetCore.Blazor.Build.dll&quot;</BlazorBuildExe>

<!-- The Blazor build code can only find your referenced assemblies if they are in the output directory -->
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
Expand All @@ -23,6 +23,7 @@
</PropertyGroup>
<WriteLinesToFile File="$(BlazorMetadataFilePath)" Lines="$(MSBuildProjectFullPath)" Overwrite="true" Encoding="Unicode"/>
<WriteLinesToFile File="$(BlazorMetadataFilePath)" Lines="$(OutDir)$(AssemblyName).dll" Overwrite="false" Encoding="Unicode"/>
<WriteLinesToFile File="$(BlazorMetadataFilePath)" Condition="'$(UseBlazorLiveReloading)'=='true'" Lines="reload:$(BlazorLiveReloadUri)" Overwrite="false" Encoding="Unicode"/>
<ItemGroup>
<ContentWithTargetPath Include="$(BlazorMetadataFilePath)" TargetPath="$(BlazorMetadataFileName)" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@

<PropertyGroup Label="Blazor build outputs">
<AdditionalMonoLinkerOptions>-c link -u link -t --verbose </AdditionalMonoLinkerOptions>
<BaseBlazorPackageContentOutputPath>dist/_content/</BaseBlazorPackageContentOutputPath>
<BaseBlazorRuntimeOutputPath>dist/_framework/</BaseBlazorRuntimeOutputPath>
<BaseBlazorDistPath>dist/</BaseBlazorDistPath>
<BaseBlazorPackageContentOutputPath>$(BaseBlazorDistPath)_content/</BaseBlazorPackageContentOutputPath>
<BaseBlazorRuntimeOutputPath>$(BaseBlazorDistPath)_framework/</BaseBlazorRuntimeOutputPath>
<BaseBlazorRuntimeBinOutputPath>$(BaseBlazorRuntimeOutputPath)_bin/</BaseBlazorRuntimeBinOutputPath>
<BaseBlazorRuntimeAsmjsOutputPath>$(BaseBlazorRuntimeOutputPath)asmjs/</BaseBlazorRuntimeAsmjsOutputPath>
<BaseBlazorRuntimeWasmOutputPath>$(BaseBlazorRuntimeOutputPath)wasm/</BaseBlazorRuntimeWasmOutputPath>
Expand All @@ -21,6 +22,7 @@
<BlazorWebRootName>wwwroot/</BlazorWebRootName>
<BlazorIndexHtmlName>Index.html</BlazorIndexHtmlName>
<BlazorOutputIndexHtmlName>$(BlazorIndexHtmlName.ToLowerInvariant())</BlazorOutputIndexHtmlName>
<BlazorBuildCompletedSignalPath>$(BaseBlazorDistPath)__blazorBuildCompleted</BlazorBuildCompletedSignalPath>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
<ItemGroup>
<FileWrites Include="@(BlazorItemOutput->'%(TargetOutputPath)')" />
</ItemGroup>
<PropertyGroup>
<_BlazorDidCopyFilesToOutputDirectory>true</_BlazorDidCopyFilesToOutputDirectory>
</PropertyGroup>
</Target>

<Target Name="_BlazorTrackResolveReferencesDidRun" AfterTargets="ResolveReferences">
Expand All @@ -47,6 +50,21 @@
<Message Importance="$(_BlazorStatisticsReportImportance)" Text="%(_BlazorStatisticsOutput.Identity)" />
</Target>

<!--
We only issue the reload notification from here if you're *not* building inside VS.
If you are building inside VS, then it's possible you're building an arbitrary collection
of projects of which this is just one, and it's important to wait until all projects in
the build are completed before reloading, so the notification is instead triggered by the
VS extension that can see when a complete build process is finished.
-->
<Target Name="_BlazorIssueLiveReloadNotification"
AfterTargets="Build"
Condition="'$(UseBlazorLiveReloading)'=='true' AND '$(_BlazorDidCopyFilesToOutputDirectory)'=='true' AND '$(BuildingInsideVisualStudio)'!='true'">
<!-- Touch the signal file to trigger a reload -->
<WriteLinesToFile File="$(ProjectDir)$(OutputPath)$(BlazorBuildCompletedSignalPath)" Lines="_" />
<Delete Files="$(ProjectDir)$(OutputPath)$(BlazorBuildCompletedSignalPath)" />
</Target>

<!-- Preparing blazor files for output:
PrepareBlazorOutputs
_PrepareBlazorOutputConfiguration
Expand All @@ -65,6 +83,7 @@
_TouchBlazorApplicationAssemblies
_GenerateBlazorIndexHtml
_BlazorCopyFilesToOutputDirectory
_BlazorIssueLiveReloadNotification
The process for doing builds goes as follows:
Produce a hash file with the Hash SDK task and write that hash to a marker file.
Expand Down Expand Up @@ -571,6 +590,7 @@
<BlazorIndexHtmlInput Include="@(BlazorPackageJsRef->'%(FullPath)')" />
<BlazorIndexHtmlInput Include="@(BlazorPackageCssRef->'%(FullPath)')" />
<BlazorIndexHtmlInput Include="@(_BlazorLinkingOption)" />
<BlazorIndexHtmlInput Include="$(BlazorLiveReloadUri)" Condition="'$(UseBlazorLiveReloading)'=='true'" />
</ItemGroup>

<WriteLinesToFile
Expand All @@ -597,9 +617,10 @@
</ItemGroup>
<PropertyGroup>
<_LinkerEnabledFlag Condition="'$(_BlazorShouldLinkApplicationAssemblies)' != ''">--linker-enabled</_LinkerEnabledFlag>
<_LiveReloadArg Condition="'$(UseBlazorLiveReloading)' == 'true' AND '$(BlazorLiveReloadUri)' != ''">--reload-uri "$(BlazorLiveReloadUri)"</_LiveReloadArg>
</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;', ' ') @(_JsReferences->'--js &quot;%(Identity)&quot;', ' ') @(_CssReferences->'--css &quot;%(Identity)&quot;', ' ') $(_LinkerEnabledFlag) $(_LiveReloadArg) --output &quot;$(BlazorIndexHtmlOutputPath)&quot;" />

<ItemGroup Condition="Exists('$(BlazorIndexHtmlOutputPath)')">
<_BlazorIndex Include="$(BlazorIndexHtmlOutputPath)" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,9 @@ public static void UseBlazor(
// hence all the path manipulation here. We shouldn't be hardcoding 'dist' here either.
var env = (IHostingEnvironment)applicationBuilder.ApplicationServices.GetService(typeof(IHostingEnvironment));
var config = BlazorConfig.Read(options.ClientAssemblyPath);
var clientAppBinDir = Path.GetDirectoryName(config.SourceOutputAssemblyPath);
var clientAppDistDir = Path.Combine(
env.ContentRootPath,
Path.Combine(clientAppBinDir, "dist"));
var distDirStaticFiles = new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(clientAppDistDir),
FileProvider = new PhysicalFileProvider(config.DistPath),
ContentTypeProvider = CreateContentTypeProvider(),
};

Expand All @@ -68,6 +64,15 @@ public static void UseBlazor(
});
}

// Definitely don't open a listener for live reloading in production, even if the
// client app was compiled with live reloading enabled
if (env.IsDevelopment())
{
// Whether or not live reloading is actually enabled depends on the client config
// For release builds, it won't be (by default)
applicationBuilder.UseBlazorLiveReloading(config);
}

// Finally, use SPA fallback routing (serve default page for anything else,
// excluding /_framework/*)
applicationBuilder.MapWhen(IsNotFrameworkDir, childAppBuilder =>
Expand Down
18 changes: 18 additions & 0 deletions src/Microsoft.AspNetCore.Blazor.Server/BlazorConfig.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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;
using System.IO;
using System.Linq;

Expand All @@ -11,12 +12,19 @@ internal class BlazorConfig
public string SourceMSBuildPath { get; }
public string SourceOutputAssemblyPath { get; }
public string WebRootPath { get; }
public string ReloadUri { get; }
public string DistPath
=> Path.Combine(Path.GetDirectoryName(SourceOutputAssemblyPath), "dist");

public static BlazorConfig Read(string assemblyPath)
=> new BlazorConfig(assemblyPath);

private BlazorConfig(string assemblyPath)
{
// TODO: Instead of assuming the lines are in a specific order, either JSON-encode
// the whole thing, or at least give the lines key prefixes (e.g., "reload:<someuri>")
// so we're not dependent on order and all lines being present.

var configFilePath = Path.ChangeExtension(assemblyPath, ".blazor.config");
var configLines = File.ReadLines(configFilePath).ToList();
SourceMSBuildPath = configLines[0];
Expand All @@ -29,6 +37,16 @@ private BlazorConfig(string assemblyPath)
{
WebRootPath = webRootPath;
}

const string reloadMarker = "reload:";
var reloadUri = configLines
.Where(line => line.StartsWith(reloadMarker, StringComparison.Ordinal))
.Select(line => line.Substring(reloadMarker.Length))
.FirstOrDefault();
if (!string.IsNullOrEmpty(reloadUri))
{
ReloadUri = reloadUri;
}
}
}
}
Loading

0 comments on commit 0b3b84f

Please sign in to comment.