diff --git a/Blazor.sln b/Blazor.sln
index 909b4b516..853eabe2a 100644
--- a/Blazor.sln
+++ b/Blazor.sln
@@ -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
@@ -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
@@ -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
@@ -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}
diff --git a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Boot.ts b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Boot.ts
index 065c71152..7ddd84ff6 100644
--- a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Boot.ts
+++ b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Boot.ts
@@ -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';
@@ -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 ";
+ var assemblyReferences = new string[] { "System.Abc.dll", "MyApp.ClassLib.dll", };
+
+ // Act
+ var fileContents = IndexHtmlWriter.GetIndexHtmlContents(
+ htmlTemplate,
+ "MyApp.Entrypoint",
+ "MyNamespace.MyType::MyMethod",
+ assemblyReferences,
+ /* js references */ new string[] {},
+ /* js references */ new string[] {},
+ /* linkerEnabled */ true,
+ /* reloadUri */ null);
+
+ // Assert
+ var parsedHtml = new HtmlParser().Parse(fileContents);
+ var scriptElem = parsedHtml.QuerySelector("script");
+ Assert.False(scriptElem.HasAttribute("reload"));
+ }
[Fact]
public void SuppliesHtmlTemplateUnchangedIfNoBootScriptPresent()
@@ -79,7 +105,7 @@ public void SuppliesHtmlTemplateUnchangedIfNoBootScriptPresent()
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, jsReferences, cssReferences, linkerEnabled: true, reloadUri: "/my/reload");
// Assert
Assert.Equal(htmlTemplate, content);
diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/ServerFixtures/DevHostServerFixture.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/ServerFixtures/DevHostServerFixture.cs
index 676808f33..08825ecfb 100644
--- a/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/ServerFixtures/DevHostServerFixture.cs
+++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/ServerFixtures/DevHostServerFixture.cs
@@ -2,25 +2,36 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Hosting;
+using System.Collections.Generic;
using DevHostServerProgram = Microsoft.AspNetCore.Blazor.Cli.Server.Program;
namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures
{
public class DevHostServerFixture : WebHostServerFixture
{
+ public string Environment { get; set; }
public string PathBase { get; set; }
+ public string ContentRoot { get; private set; }
protected override IWebHost CreateWebHost()
{
- var sampleSitePath = FindSampleOrTestSitePath(
+ ContentRoot = FindSampleOrTestSitePath(
typeof(TProgram).Assembly.GetName().Name);
- return DevHostServerProgram.BuildWebHost(new string[]
+ var args = new List
{
"--urls", "http://127.0.0.1:0",
- "--contentroot", sampleSitePath,
+ "--contentroot", ContentRoot,
"--pathbase", PathBase
- });
+ };
+
+ if (!string.IsNullOrEmpty(Environment))
+ {
+ args.Add("--environment");
+ args.Add(Environment);
+ }
+
+ return DevHostServerProgram.BuildWebHost(args.ToArray());
}
}
}
diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Microsoft.AspNetCore.Blazor.E2ETest.csproj b/test/Microsoft.AspNetCore.Blazor.E2ETest/Microsoft.AspNetCore.Blazor.E2ETest.csproj
index 7045e97bc..6c681cedf 100644
--- a/test/Microsoft.AspNetCore.Blazor.E2ETest/Microsoft.AspNetCore.Blazor.E2ETest.csproj
+++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Microsoft.AspNetCore.Blazor.E2ETest.csproj
@@ -24,6 +24,7 @@
+
diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/LiveReloadingTest.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/LiveReloadingTest.cs
new file mode 100644
index 000000000..925fdfd33
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/LiveReloadingTest.cs
@@ -0,0 +1,118 @@
+// 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 LiveReloadTestApp;
+using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure;
+using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures;
+using OpenQA.Selenium;
+using OpenQA.Selenium.Support.UI;
+using System;
+using System.Diagnostics;
+using System.IO;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
+{
+ // We need an entirely separate test app for the live reloading tests, because
+ // otherwise it might break other tests that were running parallel (e.g., if we
+ // triggered a reload here while other tests were waiting for something to happen).
+
+ public class LiveReloadingTest
+ : ServerTestBase>
+ {
+ private const string ServerPathBase = "/live/reloading/subdir";
+ private readonly DevHostServerFixture _serverFixture;
+
+ public LiveReloadingTest(BrowserFixture browserFixture, DevHostServerFixture serverFixture)
+ : base(browserFixture, serverFixture)
+ {
+ _serverFixture = serverFixture;
+ serverFixture.Environment = "Development"; // Otherwise the server won't accept live reloading connections
+ serverFixture.PathBase = ServerPathBase;
+ Navigate(ServerPathBase);
+ WaitUntilLoaded();
+ }
+
+ [Fact]
+ public void ReloadsWhenWebRootFilesAreModified()
+ {
+ // Verify we have the expected starting point
+ var jsFileOutputSelector = By.Id("some-js-file-output");
+ Assert.Equal("initial value", Browser.FindElement(jsFileOutputSelector).Text);
+
+ var jsFilePath = Path.Combine(_serverFixture.ContentRoot, "wwwroot", "someJsFile.js");
+ var origContents = File.ReadAllText(jsFilePath);
+ try
+ {
+ // Edit the source file on disk
+ var newContents = origContents.Replace("'initial value'", "'modified value'");
+ File.WriteAllText(jsFilePath, newContents);
+
+ // See that the page reloads and reflects the updated source file
+ new WebDriverWait(Browser, TimeSpan.FromSeconds(30)).Until(
+ driver => driver.FindElement(jsFileOutputSelector).Text == "modified value");
+ WaitUntilLoaded();
+ }
+ finally
+ {
+ // Restore original state
+ File.WriteAllText(jsFilePath, origContents);
+ }
+ }
+
+ [Fact]
+ public void ReloadsWhenBlazorAppRebuilds()
+ {
+ // Verify we have the expected starting point
+ var appElementSelector = By.TagName("app");
+ Assert.Equal("Hello, world!", Browser.FindElement(appElementSelector).Text);
+
+ var cshtmlFilePath = Path.Combine(_serverFixture.ContentRoot, "Home.cshtml");
+ var origContents = File.ReadAllText(cshtmlFilePath);
+ try
+ {
+ // Edit the source file on disk
+ var newContents = origContents.Replace("Hello", "Goodbye");
+ File.WriteAllText(cshtmlFilePath, newContents);
+
+ // Trigger build
+ var buildConfiguration = DetectBuildConfiguration(_serverFixture.ContentRoot);
+ var buildProcess = Process.Start(new ProcessStartInfo
+ {
+ FileName = "dotnet",
+ Arguments = $"build --no-restore --no-dependencies -c {buildConfiguration}",
+ WorkingDirectory = _serverFixture.ContentRoot
+ });
+ Assert.True(buildProcess.WaitForExit(60 * 1000));
+ Assert.Equal(0, buildProcess.ExitCode);
+
+ // See that the page reloads and reflects the updated source file
+ new WebDriverWait(Browser, TimeSpan.FromSeconds(30)).Until(
+ driver => driver.FindElement(appElementSelector).Text == "Goodbye, world!");
+ }
+ finally
+ {
+ // Restore original state
+ File.WriteAllText(cshtmlFilePath, origContents);
+ }
+ }
+
+ private object DetectBuildConfiguration(string contentRoot)
+ {
+ // We want the test to issue the build with the same configuration that
+ // the project was already built with (otherwise there will be errors because
+ // of having multiple directories under /bin, plus it means we don't need
+ // to restore and rebuild all dependencies so it's faster)
+ var binDirInfo = new DirectoryInfo(Path.Combine(contentRoot, "bin"));
+ var configurationDirs = binDirInfo.GetDirectories();
+ Assert.Single(configurationDirs);
+ return configurationDirs[0].Name;
+ }
+
+ private void WaitUntilLoaded()
+ {
+ new WebDriverWait(Browser, TimeSpan.FromSeconds(30)).Until(
+ driver => driver.FindElement(By.TagName("app")).Text != "Loading...");
+ }
+ }
+}
diff --git a/test/testapps/LiveReloadTestApp/Home.cshtml b/test/testapps/LiveReloadTestApp/Home.cshtml
new file mode 100644
index 000000000..9f735a630
--- /dev/null
+++ b/test/testapps/LiveReloadTestApp/Home.cshtml
@@ -0,0 +1 @@
+Hello, world!
diff --git a/test/testapps/LiveReloadTestApp/LiveReloadTestApp.csproj b/test/testapps/LiveReloadTestApp/LiveReloadTestApp.csproj
new file mode 100644
index 000000000..dd97d39d7
--- /dev/null
+++ b/test/testapps/LiveReloadTestApp/LiveReloadTestApp.csproj
@@ -0,0 +1,21 @@
+
+
+
+ netstandard2.0
+
+
+ dotnet
+ run --project ..\..\..\src\Microsoft.AspNetCore.Blazor.Cli serve --pathbase /live/reloading/subdir
+
+
+ true
+
+
+
+
+
+
+
+
+
+
diff --git a/test/testapps/LiveReloadTestApp/Program.cs b/test/testapps/LiveReloadTestApp/Program.cs
new file mode 100644
index 000000000..0aee7832e
--- /dev/null
+++ b/test/testapps/LiveReloadTestApp/Program.cs
@@ -0,0 +1,15 @@
+// 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.Blazor.Browser.Rendering;
+
+namespace LiveReloadTestApp
+{
+ public class Program
+ {
+ static void Main(string[] args)
+ {
+ new BrowserRenderer().AddComponent("app");
+ }
+ }
+}
diff --git a/test/testapps/LiveReloadTestApp/wwwroot/index.html b/test/testapps/LiveReloadTestApp/wwwroot/index.html
new file mode 100644
index 000000000..5c7d816ec
--- /dev/null
+++ b/test/testapps/LiveReloadTestApp/wwwroot/index.html
@@ -0,0 +1,19 @@
+
+
+
+
+ Live reload test app
+
+
+
+ Loading...
+
+
+
+
+
+
diff --git a/test/testapps/LiveReloadTestApp/wwwroot/someJsFile.js b/test/testapps/LiveReloadTestApp/wwwroot/someJsFile.js
new file mode 100644
index 000000000..af61769b2
--- /dev/null
+++ b/test/testapps/LiveReloadTestApp/wwwroot/someJsFile.js
@@ -0,0 +1,3 @@
+// We modify this on disk during E2E tests to verify it causes a reload
+var valueToWrite = 'initial value';
+document.getElementById('some-js-file-output').textContent = valueToWrite;