-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Add support for static files to dotnet-watch #15405
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
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 |
|---|---|---|
|
|
@@ -7,17 +7,84 @@ setTimeout(function () { | |
| console.debug(ex); | ||
| return; | ||
| } | ||
|
|
||
| let waiting = false; | ||
|
|
||
| connection.onmessage = function (message) { | ||
| if (message.data === 'Reload') { | ||
| console.debug('Server is ready. Reloading...'); | ||
| location.reload(); | ||
| } else if (message.data === 'Wait') { | ||
| if (waiting) { | ||
| return; | ||
| } | ||
| waiting = true; | ||
| console.debug('File changes detected. Waiting for application to rebuild.'); | ||
| const t = document.title; const r = ['☱', '☲', '☴']; let i = 0; | ||
| setInterval(function () { document.title = r[i++ % r.length] + ' ' + t; }, 240); | ||
| const glyphs = ['☱', '☲', '☴']; | ||
| const title = document.title; | ||
| let i = 0; | ||
| setInterval(function () { document.title = glyphs[i++ % glyphs.length] + ' ' + title; }, 240); | ||
| } else { | ||
| const parsed = JSON.parse(message.data); | ||
| if (parsed.type == 'UpdateStaticFile') { | ||
| const path = parsed.path; | ||
| if (path && path.endsWith('.css')) { | ||
| updateCssByPath(path); | ||
| } else { | ||
| console.debug(`File change detected to css file ${path}. Reloading page...`); | ||
| location.reload(); | ||
| return; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| connection.onerror = function (event) { console.debug('dotnet-watch reload socket error.', event) } | ||
| connection.onclose = function () { console.debug('dotnet-watch reload socket closed.') } | ||
| connection.onopen = function () { console.debug('dotnet-watch reload socket connected.') } | ||
|
|
||
| function updateCssByPath(path) { | ||
| const styleElement = document.querySelector(`link[href^="${path}"]`) || | ||
| document.querySelector(`link[href^="${document.baseURI}${path}"]`); | ||
|
|
||
| if (!styleElement || !styleElement.parentNode) { | ||
| console.debug('Unable to find a stylesheet to update. Updating all css.'); | ||
| updateAllLocalCss(); | ||
| } | ||
|
|
||
| updateCssElement(styleElement); | ||
| } | ||
|
|
||
| function updateAllLocalCss() { | ||
| [...document.querySelectorAll('link')] | ||
| .filter(l => l.baseURI === document.baseURI) | ||
| .forEach(e => updateCssElement(e)); | ||
| } | ||
|
|
||
| function updateCssElement(styleElement) { | ||
| if (styleElement.loading) { | ||
| // A file change notification may be triggered for the same file before the browser | ||
| // finishes processing a previous update. In this case, it's easiest to ignore later updates | ||
| return; | ||
| } | ||
|
|
||
| const newElement = styleElement.cloneNode(); | ||
| const href = styleElement.href; | ||
| newElement.href = href.split('?', 1)[0] + `?nonce=${Date.now()}`; | ||
|
Member
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. Is there a chance |
||
|
|
||
| styleElement.loading = true; | ||
| newElement.loading = true; | ||
| newElement.addEventListener('load', function () { | ||
| newElement.loading = false; | ||
| styleElement.remove(); | ||
| }); | ||
|
|
||
| styleElement.parentNode.insertBefore(newElement, styleElement.nextSibling); | ||
| } | ||
|
|
||
| function updateScopedCss() { | ||
| [...document.querySelectorAll('link')] | ||
| .filter(l => l.baseURI === document.baseURI && l.href && l.href.indexOf('.styles.css') !== -1) | ||
| .forEach(e => updateCssElement(e)); | ||
| } | ||
| }, 500); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <TargetFramework>netstandard2.0</TargetFramework> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Microsoft.Build.Framework" Version="$(MicrosoftBuildFrameworkVersion)" ExcludeAssets="Runtime" /> | ||
| <PackageReference Include="Microsoft.Build.Utilities.Core" Version="$(MicrosoftBuildUtilitiesCoreVersion)" ExcludeAssets="Runtime" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <Compile Include="..\dotnet-watch\Internal\MSBuildFileSetResult.cs" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| // 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.Collections.Generic; | ||
| using System.IO; | ||
| using System.Runtime.Serialization.Json; | ||
| using System.Text; | ||
| using Microsoft.Build.Framework; | ||
| using Microsoft.Build.Utilities; | ||
| using Microsoft.DotNet.Watcher.Internal; | ||
|
|
||
| namespace DotNetWatchTasks | ||
| { | ||
| public class FileSetSerializer : Task | ||
| { | ||
| public ITaskItem[] WatchFiles { get; set; } | ||
|
|
||
| public bool IsNetCoreApp31OrNewer { 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. Reminder where this is used again?
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. We use it to determine if we can inject the browser refresh middleware. It compiles against netcoreapp3.1 so we have to make sure it's at least that |
||
|
|
||
| public ITaskItem OutputPath { get; set; } | ||
|
|
||
| public string[] PackageIds { get; set; } | ||
pranavkm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| public override bool Execute() | ||
| { | ||
| var projectItems = new Dictionary<string, ProjectItems>(StringComparer.OrdinalIgnoreCase); | ||
| var fileSetResult = new MSBuildFileSetResult | ||
| { | ||
| IsNetCoreApp31OrNewer = IsNetCoreApp31OrNewer, | ||
| Projects = projectItems | ||
| }; | ||
|
|
||
| foreach (var item in WatchFiles) | ||
| { | ||
| var fullPath = item.GetMetadata("FullPath"); | ||
| var staticWebAssetPath = item.GetMetadata("StaticWebAssetPath"); | ||
| var projectFullPath = item.GetMetadata("ProjectFullPath"); | ||
|
|
||
| if (!projectItems.TryGetValue(projectFullPath, out var project)) | ||
| { | ||
| projectItems[projectFullPath] = project = new ProjectItems(); | ||
| } | ||
|
|
||
| if (string.IsNullOrEmpty(staticWebAssetPath)) | ||
| { | ||
| project.Files.Add(fullPath); | ||
| } | ||
| else | ||
| { | ||
| project.StaticFiles.Add(new StaticFileItem | ||
| { | ||
| FilePath = fullPath, | ||
| StaticWebAssetPath = staticWebAssetPath, | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| var serializer = new DataContractJsonSerializer(fileSetResult.GetType(), new DataContractJsonSerializerSettings | ||
| { | ||
| UseSimpleDictionaryFormat = true, | ||
| }); | ||
|
|
||
| using var fileStream = File.Create(OutputPath.ItemSpec); | ||
| using var writer = JsonReaderWriterFactory.CreateJsonWriter(fileStream, Encoding.UTF8, ownsStream: false, indent: true); | ||
| serializer.WriteObject(writer, fileSetResult); | ||
|
|
||
| return !Log.HasLoggedErrors; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,14 +11,16 @@ public class DotNetWatchContext | |
|
|
||
| public ProcessSpec ProcessSpec { get; set; } | ||
|
|
||
| public IFileSet FileSet { get; set; } | ||
| public FileSet FileSet { get; set; } | ||
|
Member
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. Why do we want to use the concrete type instead of the interface here?
Member
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. Nevermind. I see that the interface was removed.
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. It's a POCO type, there's really no need for there to be an interface and a concrete implementation. Kinda gets in the way since you have to update two places every time you make a change to the contract here. |
||
|
|
||
| public int Iteration { get; set; } | ||
|
|
||
| public string ChangedFile { get; set; } | ||
| public FileItem? ChangedFile { get; set; } | ||
|
|
||
| public bool RequiresMSBuildRevaluation { get; set; } | ||
|
|
||
| public bool SuppressMSBuildIncrementalism { get; set; } | ||
|
|
||
| public BrowserRefreshServer BrowserRefreshServer { get; set; } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| // 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; | ||
|
|
||
| namespace Microsoft.DotNet.Watcher | ||
| { | ||
| public record DotNetWatchOptions( | ||
| bool SuppressHandlingStaticContentFiles, | ||
| bool SuppressMSBuildIncrementalism, | ||
| bool SuppressLaunchBrowser, | ||
| bool SuppressBrowserRefresh, | ||
| bool RunningAsTest) | ||
| { | ||
| public static DotNetWatchOptions Default { get; } = new DotNetWatchOptions | ||
| ( | ||
| SuppressHandlingStaticContentFiles: IsEnvironmentSet("DOTNET_WATCH_SUPPRESS_STATIC_FILE_HANDLING"), | ||
| SuppressMSBuildIncrementalism: IsEnvironmentSet("DOTNET_WATCH_SUPPRESS_MSBUILD_INCREMENTALISM"), | ||
| SuppressLaunchBrowser: IsEnvironmentSet("DOTNET_WATCH_SUPPRESS_LAUNCH_BROWSER"), | ||
| SuppressBrowserRefresh: IsEnvironmentSet("DOTNET_WATCH_SUPPRESS_BROWSER_REFRESH"), | ||
| RunningAsTest: IsEnvironmentSet("__DOTNET_WATCH_RUNNING_AS_TEST") | ||
| ); | ||
|
|
||
| private static bool IsEnvironmentSet(string key) | ||
| { | ||
| var envValue = Environment.GetEnvironmentVariable(key); | ||
| return envValue == "1" || envValue == "true"; | ||
| } | ||
| } | ||
| } |
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.
Super nit: We might want to filter by
rel='stylesheet'to avoid selecting links to non-stylesheet assets.Probably an edge case so feel free to ignore.