[Blazor] CSS isolation follow-ups (#25565)
This change includes several improvements to CSS isolation that we have gathered from validation and usage feedback. We have switched from producing a single scoped CSS bundle file for the entire application with all the scoped css files from the current project, referenced projects and package projects to producing one bundle per referenced project/package and to include those bundles into an "application" bundle throught CSS @import statements. We have cleaned up the bundle names to make them more unique by including the project name on them and we have also cleaned up the bundle extensions. We have decided to put the individual bundles generated for the project scoped css assets into the static web assets base path of the project, so that when developers reference assets from their scoped css files (like using the CSS url function) the path they use matches what they have inside their library wwwroot folder. We have decided to put the application bundle on the root path of the application provided that the developer has not overriden the default StaticWebAssetsBasePath. This is so that the bundle location is consistent across templates, and can be found at ProjectName.styles.css independent of whether the app is a blazor webassembly app or a server side blazor app. For cases where the default StaticWebAssetBasePath has been overriden, the value is respected and the bundle is placed at $(StaticWebAssetBasePath)/ProjectName.styles.css. Packaged razor class libraries with scoped css files now package a "project" bundle instead of the individual files.
This commit is contained in:
parent
bbc7fd8192
commit
3d38d397ae
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// 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;
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "System.Text.Json.dll"); // Verify dependencies are part of the output.
|
||||
|
||||
// Verify scoped css
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "scoped.styles.css");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "blazorwasm.styles.css");
|
||||
|
||||
// Verify referenced static web assets
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js");
|
||||
|
|
@ -663,7 +663,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "System.Text.Json.dll"); // Verify dependencies are part of the output.
|
||||
|
||||
// Verify scoped css
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "scoped.styles.css");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "blazorwasm.styles.css");
|
||||
|
||||
// Verify static assets are in the publish directory
|
||||
Assert.FileExists(result, blazorPublishDirectory, "index.html");
|
||||
|
|
|
|||
|
|
@ -266,11 +266,18 @@ Copyright (c) .NET Foundation. All rights reserved.
|
|||
|
||||
<GetCurrentProjectStaticWebAssetsDependsOn>
|
||||
$(GetCurrentProjectStaticWebAssetsDependsOn);
|
||||
AddScopedCssBundle;
|
||||
_BlazorWasmReplaceBundle;
|
||||
_BlazorWasmPrepareForRun;
|
||||
</GetCurrentProjectStaticWebAssetsDependsOn>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="_BlazorWasmReplaceBundle">
|
||||
<ItemGroup>
|
||||
<StaticWebAsset Include="@(_AppBundleStaticWebAsset)" />
|
||||
<Staticwebasset Remove="@(_ProjectBundleStaticWebAsset)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="_BlazorWasmPrepareForRun" DependsOnTargets="_ProcessBlazorWasmOutputs" BeforeTargets="_RazorPrepareForRun" AfterTargets="GetCurrentProjectStaticWebAssets">
|
||||
<PropertyGroup>
|
||||
<_BlazorBuildBootJsonPath>$(IntermediateOutputPath)blazor.boot.json</_BlazorBuildBootJsonPath>
|
||||
|
|
|
|||
|
|
@ -344,6 +344,10 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
var specialStyleDiv = appElement.FindElement(By.ClassName("special-style"));
|
||||
Assert.Equal("50px", specialStyleDiv.GetCssValue("padding"));
|
||||
|
||||
// This style is isolated to the component and comes from the bundle that gets generated for BasicTestApp
|
||||
// and that includes the @import for the TestContentPackage.bundle.scp.css file
|
||||
Assert.Equal("20px", specialStyleDiv.GetCssValue("font-size"));
|
||||
|
||||
// The external components are fully functional, not just static HTML
|
||||
var externalComponentButton = specialStyleDiv.FindElement(By.TagName("button"));
|
||||
Assert.Equal("Click me", externalComponentButton.Text);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@
|
|||
|
||||
<!-- Used by ExternalContentPackage -->
|
||||
<link href="_content/TestContentPackage/styles.css" rel="stylesheet" />
|
||||
|
||||
<!-- App bundle that contains a reference to the scoped css bundle created by TestContentPackage -->
|
||||
<link href="BasicTestApp.styles.css" rel="stylesheet" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
div {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
|
@ -1,10 +1,9 @@
|
|||
.special-style {
|
||||
.special-style {
|
||||
background-image: url('./face.png');
|
||||
padding: 50px;
|
||||
background-repeat: repeat-x;
|
||||
border: 5px dashed red;
|
||||
font-family: "Comic Sans MS";
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
animation: hideous-rainbow 1s infinite;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
|
||||
|
|
@ -36,19 +36,4 @@
|
|||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="CopyClientAssetsForTest" BeforeTargets="Build"
|
||||
Inputs="..\BasicTestApp\wwwroot\js\jsinteroptests.js;
|
||||
..\BasicTestApp\wwwroot\js\webComponentPerformingJsInterop.js;
|
||||
..\BasicTestApp\wwwroot\NotAComponent.html;
|
||||
..\BasicTestApp\wwwroot\style.css"
|
||||
Outputs="wwwroot\js\jsinteroptests.js;
|
||||
wwwroot\js\webComponentPerformingJsInterop.js;
|
||||
wwwroot\NotAComponent.html;
|
||||
wwwroot\style.css">
|
||||
|
||||
<MakeDir Directories="wwwroot" />
|
||||
|
||||
<Copy SourceFiles="..\BasicTestApp\wwwroot\js\jsinteroptests.js;..\BasicTestApp\wwwroot\js\webComponentPerformingJsInterop.js;..\BasicTestApp\wwwroot\NotAComponent.html;..\BasicTestApp\wwwroot\style.css"
|
||||
DestinationFiles="wwwroot\js\jsinteroptests.js;wwwroot\js\webComponentPerformingJsInterop.js;wwwroot\NotAComponent.html;wwwroot\style.css" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
<!-- Used by ExternalContentPackage -->
|
||||
<link href="_content/TestContentPackage/styles.css" rel="stylesheet" />
|
||||
<link href="Components.TestServer.styles.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<root><component type="typeof(BasicTestApp.Index)" render-mode="Server" /></root>
|
||||
|
|
@ -41,8 +42,6 @@
|
|||
|
||||
<script src="_content/Microsoft.AspNetCore.Components.Web.Extensions/headManager.js"></script>
|
||||
|
||||
<script src="_content/Microsoft.AspNetCore.Components.Web.Extensions/inputFile.js"></script>
|
||||
|
||||
<!-- Used by ExternalContentPackage -->
|
||||
<script src="_content/TestContentPackage/prompt.js"></script>
|
||||
<script>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
<base href="~/" />
|
||||
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
|
||||
<link href="css/site.css" rel="stylesheet" />
|
||||
<link href="_content/BlazorServerWeb-CSharp/_framework/scoped.styles.css" rel="stylesheet" />
|
||||
<link href="BlazorServerWeb-CSharp.styles.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<component type="typeof(App)" render-mode="ServerPrerendered" />
|
||||
|
|
|
|||
|
|
@ -8,7 +8,11 @@
|
|||
<base href="/" />
|
||||
<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
|
||||
<link href="css/app.css" rel="stylesheet" />
|
||||
<link href="_framework/scoped.styles.css" rel="stylesheet" />
|
||||
<!--#if (Hosted) -->
|
||||
<link href="ComponentsWebAssembly-CSharp.Client.styles.css" rel="stylesheet" />
|
||||
<!--#else -->
|
||||
<link href="ComponentsWebAssembly-CSharp.styles.css" rel="stylesheet" />
|
||||
<!--#endif -->
|
||||
<!--#if PWA -->
|
||||
<link href="manifest.json" rel="manifest" />
|
||||
<link rel="apple-touch-icon" sizes="512x512" href="icon-512.png" />
|
||||
|
|
|
|||
|
|
@ -180,6 +180,30 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
Path.Combine("lib", TFM, "ClassLibrary.Views.dll"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[InitializeTestProject("PackageLibraryDirectDependency", additionalProjects: new[] { "PackageLibraryTransitiveDependency" })]
|
||||
public async Task Pack_FailsWhenStaticWebAssetsHaveConflictingPaths()
|
||||
{
|
||||
Project.AddProjectFileContent(@"
|
||||
<ItemGroup>
|
||||
<StaticWebAsset Include=""bundle\js\pkg-direct-dep.js"">
|
||||
<SourceType></SourceType>
|
||||
<SourceId>PackageLibraryDirectDependency</SourceId>
|
||||
<ContentRoot>$([MSBuild]::NormalizeDirectory('$(MSBuildProjectDirectory)\bundle\'))</ContentRoot>
|
||||
<BasePath>_content/PackageLibraryDirectDependency</BasePath>
|
||||
<RelativePath>js/pkg-direct-dep.js</RelativePath>
|
||||
</StaticWebAsset>
|
||||
</ItemGroup>");
|
||||
|
||||
Directory.CreateDirectory(Path.Combine(Project.DirectoryPath, "bundle", "js"));
|
||||
File.WriteAllText(Path.Combine(Project.DirectoryPath, "bundle", "js", "pkg-direct-dep.js"), "console.log('bundle');");
|
||||
|
||||
var result = await DotnetMSBuild("Pack");
|
||||
|
||||
Assert.BuildFailed(result);
|
||||
}
|
||||
|
||||
// If you modify this test, make sure you also modify the test below this one to assert that things are not included as content.
|
||||
[Fact]
|
||||
[InitializeTestProject("PackageLibraryDirectDependency", additionalProjects: new[] { "PackageLibraryTransitiveDependency" })]
|
||||
public async Task Pack_IncludesStaticWebAssets()
|
||||
|
|
@ -197,6 +221,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
{
|
||||
Path.Combine("staticwebassets", "js", "pkg-direct-dep.js"),
|
||||
Path.Combine("staticwebassets", "css", "site.css"),
|
||||
Path.Combine("staticwebassets", "PackageLibraryDirectDependency.bundle.scp.css"),
|
||||
Path.Combine("build", "Microsoft.AspNetCore.StaticWebAssets.props"),
|
||||
Path.Combine("build", "PackageLibraryDirectDependency.props"),
|
||||
Path.Combine("buildMultiTargeting", "PackageLibraryDirectDependency.props"),
|
||||
|
|
@ -204,6 +229,55 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[InitializeTestProject("PackageLibraryDirectDependency", additionalProjects: new[] { "PackageLibraryTransitiveDependency" })]
|
||||
public async Task Pack_DoesNotInclude_TransitiveBundleOrScopedCssAsStaticWebAsset()
|
||||
{
|
||||
var result = await DotnetMSBuild("Pack");
|
||||
|
||||
Assert.BuildPassed(result, allowWarnings: true);
|
||||
|
||||
Assert.FileExists(result, OutputPath, "PackageLibraryDirectDependency.dll");
|
||||
|
||||
Assert.NupkgDoesNotContain(
|
||||
result,
|
||||
Path.Combine("..", "TestPackageRestoreSource", "PackageLibraryDirectDependency.1.0.0.nupkg"),
|
||||
filePaths: new[]
|
||||
{
|
||||
// This is to make sure we don't include the scoped css files on the package when bundling is enabled.
|
||||
Path.Combine("staticwebassets", "Components", "App.razor.rz.scp.css"),
|
||||
Path.Combine("staticwebassets", "PackageLibraryDirectDependency.styles.css"),
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[InitializeTestProject("PackageLibraryDirectDependency", additionalProjects: new[] { "PackageLibraryTransitiveDependency" })]
|
||||
public async Task Pack_DoesNotIncludeStaticWebAssetsAsContent()
|
||||
{
|
||||
var result = await DotnetMSBuild("Pack");
|
||||
|
||||
Assert.BuildPassed(result, allowWarnings: true);
|
||||
|
||||
Assert.FileExists(result, OutputPath, "PackageLibraryDirectDependency.dll");
|
||||
|
||||
Assert.NupkgDoesNotContain(
|
||||
result,
|
||||
Path.Combine("..", "TestPackageRestoreSource", "PackageLibraryDirectDependency.1.0.0.nupkg"),
|
||||
filePaths: new[]
|
||||
{
|
||||
Path.Combine("content", "js", "pkg-direct-dep.js"),
|
||||
Path.Combine("content", "css", "site.css"),
|
||||
Path.Combine("content", "Components", "App.razor.css"),
|
||||
// This is to make sure we don't include the unscoped css file on the package.
|
||||
Path.Combine("content", "Components", "App.razor.css"),
|
||||
Path.Combine("content", "Components", "App.razor.rz.scp.css"),
|
||||
Path.Combine("contentFiles", "js", "pkg-direct-dep.js"),
|
||||
Path.Combine("contentFiles", "css", "site.css"),
|
||||
Path.Combine("contentFiles", "Components", "App.razor.css"),
|
||||
Path.Combine("contentFiles", "Components", "App.razor.rz.scp.css"),
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[InitializeTestProject("PackageLibraryDirectDependency", additionalProjects: new[] { "PackageLibraryTransitiveDependency" })]
|
||||
public async Task Pack_StaticWebAssetsEnabledFalse_DoesNotPackAnyStaticWebAssets()
|
||||
|
|
@ -246,7 +320,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
filePaths: new[]
|
||||
{
|
||||
Path.Combine("staticwebassets", "js", "pkg-direct-dep.js"),
|
||||
Path.Combine("staticwebassets", "Components", "App.razor.rz.scp.css"),
|
||||
Path.Combine("staticwebassets", "PackageLibraryDirectDependency.bundle.scp.css"),
|
||||
Path.Combine("staticwebassets", "css", "site.css"),
|
||||
Path.Combine("build", "Microsoft.AspNetCore.StaticWebAssets.props"),
|
||||
Path.Combine("build", "PackageLibraryDirectDependency.props"),
|
||||
|
|
|
|||
|
|
@ -22,6 +22,58 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
|
||||
public ITestOutputHelper Output { get; private set; }
|
||||
|
||||
[Fact]
|
||||
[InitializeTestProject("ComponentApp", language: "C#")]
|
||||
public async Task Build_NoOps_WhenScopedCssIsDisabled()
|
||||
{
|
||||
var result = await DotnetMSBuild("Build", "/p:ScopedCssEnabled=false");
|
||||
Assert.BuildPassed(result);
|
||||
|
||||
Assert.FileDoesNotExist(result, IntermediateOutputPath, "scopedcss", "Components", "Pages", "Counter.razor.rz.scp.css");
|
||||
Assert.FileDoesNotExist(result, IntermediateOutputPath, "scopedcss", "Components", "Pages", "Index.razor.rz.scp.css");
|
||||
Assert.FileDoesNotExist(result, IntermediateOutputPath, "scopedcss", "ComponentApp.styles.css");
|
||||
Assert.FileDoesNotExist(result, IntermediateOutputPath, "scopedcss", "Components", "Pages", "FetchData.razor.rz.scp.css");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[InitializeTestProject("ComponentApp", language: "C#")]
|
||||
public async Task CanDisableDefaultDiscoveryConvention()
|
||||
{
|
||||
var result = await DotnetMSBuild("Build", "/p:EnableDefaultScopedCssItems=false");
|
||||
Assert.BuildPassed(result);
|
||||
|
||||
Assert.FileDoesNotExist(result, IntermediateOutputPath, "scopedcss", "Components", "Pages", "Counter.razor.rz.scp.css");
|
||||
Assert.FileDoesNotExist(result, IntermediateOutputPath, "scopedcss", "Components", "Pages", "Index.razor.rz.scp.css");
|
||||
Assert.FileDoesNotExist(result, IntermediateOutputPath, "scopedcss", "ComponentApp.styles.css");
|
||||
Assert.FileDoesNotExist(result, IntermediateOutputPath, "scopedcss", "Components", "Pages", "FetchData.razor.rz.scp.css");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[InitializeTestProject("ComponentApp", language: "C#")]
|
||||
public async Task CanOverrideScopeIdentifiers()
|
||||
{
|
||||
var stylesFolder = Path.Combine(Project.DirectoryPath, "Styles", "Pages");
|
||||
Directory.CreateDirectory(stylesFolder);
|
||||
var styles = Path.Combine(stylesFolder, "Counter.css");
|
||||
File.Move(Path.Combine(Project.DirectoryPath, "Components", "Pages", "Counter.razor.css"), styles);
|
||||
Project.AddProjectFileContent(@"
|
||||
<ItemGroup>
|
||||
<ScopedCssInput Include=""Styles\Pages\Counter.css"">
|
||||
<RazorComponent>Components\Pages\Counter.razor</RazorComponent>
|
||||
<CssScope>b-overriden</CssScope>
|
||||
</ScopedCssInput>
|
||||
</ItemGroup>
|
||||
");
|
||||
var result = await DotnetMSBuild("Build", "/p:EnableDefaultScopedCssItems=false");
|
||||
Assert.BuildPassed(result);
|
||||
|
||||
var scoped = Assert.FileExists(result, IntermediateOutputPath, "scopedcss", "Styles", "Pages", "Counter.rz.scp.css");
|
||||
Assert.FileContains(result, scoped, "b-overriden");
|
||||
var generated = Assert.FileExists(result, IntermediateOutputPath, "Razor", "Components", "Pages", "Counter.razor.g.cs");
|
||||
Assert.FileContains(result, generated, "b-overriden");
|
||||
Assert.FileDoesNotExist(result, IntermediateOutputPath, "scopedcss", "Components", "Pages", "Index.razor.rz.scp.css");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[InitializeTestProject("ComponentApp", language: "C#")]
|
||||
public async Task Build_GeneratesTransformedFilesAndBundle_ForComponentsWithScopedCss()
|
||||
|
|
@ -31,7 +83,8 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
|
||||
Assert.FileExists(result, IntermediateOutputPath, "scopedcss", "Components", "Pages", "Counter.razor.rz.scp.css");
|
||||
Assert.FileExists(result, IntermediateOutputPath, "scopedcss", "Components", "Pages", "Index.razor.rz.scp.css");
|
||||
Assert.FileExists(result, IntermediateOutputPath, "scopedcss", "_framework", "scoped.styles.css");
|
||||
Assert.FileExists(result, IntermediateOutputPath, "scopedcss", "bundle", "ComponentApp.styles.css");
|
||||
Assert.FileExists(result, IntermediateOutputPath, "scopedcss", "projectbundle", "ComponentApp.bundle.scp.css");
|
||||
Assert.FileDoesNotExist(result, IntermediateOutputPath, "scopedcss", "Components", "Pages", "FetchData.razor.rz.scp.css");
|
||||
}
|
||||
|
||||
|
|
@ -65,7 +118,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
var result = await DotnetMSBuild("Publish");
|
||||
Assert.BuildPassed(result);
|
||||
|
||||
Assert.FileExists(result, PublishOutputPath, "wwwroot", "_content", "ComponentApp", "_framework", "scoped.styles.css");
|
||||
Assert.FileExists(result, PublishOutputPath, "wwwroot", "ComponentApp.styles.css");
|
||||
Assert.FileDoesNotExist(result, PublishOutputPath, "wwwroot", "_content", "ComponentApp", "Components", "Pages", "Index.razor.rz.scp.css");
|
||||
Assert.FileDoesNotExist(result, PublishOutputPath, "wwwroot", "_content", "ComponentApp", "Components", "Pages", "Counter.razor.rz.scp.css");
|
||||
}
|
||||
|
|
@ -80,7 +133,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
result = await DotnetMSBuild("Publish", "/p:NoBuild=true");
|
||||
Assert.BuildPassed(result);
|
||||
|
||||
Assert.FileExists(result, PublishOutputPath, "wwwroot", "_content", "ComponentApp", "_framework", "scoped.styles.css");
|
||||
Assert.FileExists(result, PublishOutputPath, "wwwroot", "ComponentApp.styles.css");
|
||||
Assert.FileDoesNotExist(result, PublishOutputPath, "wwwroot", "_content", "ComponentApp", "Components", "Pages", "Index.razor.rz.scp.css");
|
||||
Assert.FileDoesNotExist(result, PublishOutputPath, "wwwroot", "_content", "ComponentApp", "Components", "Pages", "Counter.razor.rz.scp.css");
|
||||
}
|
||||
|
|
@ -98,6 +151,20 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
Assert.FileDoesNotExist(result, PublishOutputPath, "wwwroot", "_content", "ComponentApp", "_framework", "scoped.styles.css");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[InitializeTestProject("ComponentApp", language: "C#")]
|
||||
public async Task Publish_Publishes_IndividualScopedCssFiles_WhenNoBundlingIsEnabled()
|
||||
{
|
||||
var result = await DotnetMSBuild("Publish", args: "/p:DisableScopedCssBundling=true");
|
||||
Assert.BuildPassed(result);
|
||||
|
||||
Assert.FileDoesNotExist(result, PublishOutputPath, "wwwroot", "_content", "ComponentApp", "ComponentApp.styles.css");
|
||||
|
||||
Assert.FileExists(result, PublishOutputPath, "wwwroot", "_content", "ComponentApp", "Components", "Pages", "Index.razor.rz.scp.css");
|
||||
Assert.FileExists(result, PublishOutputPath, "wwwroot", "_content", "ComponentApp", "Components", "Pages", "Counter.razor.rz.scp.css");
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
[InitializeTestProject("ComponentApp", language: "C#")]
|
||||
public async Task Build_GeneratedComponentContainsScope()
|
||||
|
|
@ -125,7 +192,8 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
Assert.BuildPassed(result);
|
||||
|
||||
Assert.FileExists(result, IntermediateOutputPath, "scopedcss", "Components", "Pages", "Counter.razor.rz.scp.css");
|
||||
var generatedBundle = Assert.FileExists(result, IntermediateOutputPath, "scopedcss", "_framework", "scoped.styles.css");
|
||||
var generatedBundle = Assert.FileExists(result, IntermediateOutputPath, "scopedcss", "bundle", "ComponentApp.styles.css");
|
||||
var generatedProjectBundle = Assert.FileExists(result, IntermediateOutputPath, "scopedcss", "projectbundle", "ComponentApp.bundle.scp.css");
|
||||
var generatedCounter = Assert.FileExists(result, IntermediateOutputPath, "Razor", "Components", "Pages", "Counter.razor.g.cs");
|
||||
|
||||
var componentThumbprint = GetThumbPrint(generatedCounter);
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
|
||||
Assert.BuildPassed(result);
|
||||
|
||||
Assert.FileExists(result, PublishOutputPath, Path.Combine("wwwroot", "_content", "ClassLibrary", "ClassLibrary.bundle.scp.css"));
|
||||
Assert.FileExists(result, PublishOutputPath, Path.Combine("wwwroot", "_content", "ClassLibrary", "js", "project-transitive-dep.js"));
|
||||
Assert.FileExists(result, PublishOutputPath, Path.Combine("wwwroot", "_content", "ClassLibrary", "js", "project-transitive-dep.v4.js"));
|
||||
Assert.FileExists(result, PublishOutputPath, Path.Combine("wwwroot", "_content", "ClassLibrary2", "css", "site.css"));
|
||||
|
|
@ -72,7 +73,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
Assert.FileExists(result, PublishOutputPath, Path.Combine("wwwroot", "_content", "PackageLibraryDirectDependency", "css", "site.css"));
|
||||
Assert.FileExists(result, PublishOutputPath, Path.Combine("wwwroot", "_content", "PackageLibraryDirectDependency", "js", "pkg-direct-dep.js"));
|
||||
Assert.FileExists(result, PublishOutputPath, Path.Combine("wwwroot", "_content", "PackageLibraryTransitiveDependency", "js", "pkg-transitive-dep.js"));
|
||||
Assert.FileExists(result, PublishOutputPath, Path.Combine("wwwroot", "_content", "AppWithPackageAndP2PReference", "_framework", "scoped.styles.css"));
|
||||
Assert.FileExists(result, PublishOutputPath, Path.Combine("wwwroot", "AppWithPackageAndP2PReference.styles.css"));
|
||||
|
||||
// Validate that static web assets don't get published as content too on their regular path
|
||||
Assert.FileDoesNotExist(result, PublishOutputPath, Path.Combine("wwwroot", "js", "project-transitive-dep.js"));
|
||||
|
|
@ -91,22 +92,23 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
var result = await DotnetMSBuild("Publish", $"/restore /p:PublishSingleFile=true /p:ReferenceLocallyBuiltPackages=true");
|
||||
|
||||
Assert.BuildPassed(result);
|
||||
var publishOutputPath = GetRidSpecificPublishOutputPath("win-x64");
|
||||
Assert.FileExists(result, publishOutputPath, Path.Combine("wwwroot", "_content", "ClassLibrary", "js", "project-transitive-dep.js"));
|
||||
Assert.FileExists(result, publishOutputPath, Path.Combine("wwwroot", "_content", "ClassLibrary", "js", "project-transitive-dep.v4.js"));
|
||||
Assert.FileExists(result, publishOutputPath, Path.Combine("wwwroot", "_content", "ClassLibrary2", "css", "site.css"));
|
||||
Assert.FileExists(result, publishOutputPath, Path.Combine("wwwroot", "_content", "ClassLibrary2", "js", "project-direct-dep.js"));
|
||||
Assert.FileExists(result, publishOutputPath, Path.Combine("wwwroot", "_content", "PackageLibraryDirectDependency", "css", "site.css"));
|
||||
Assert.FileExists(result, publishOutputPath, Path.Combine("wwwroot", "_content", "PackageLibraryDirectDependency", "js", "pkg-direct-dep.js"));
|
||||
Assert.FileExists(result, publishOutputPath, Path.Combine("wwwroot", "_content", "PackageLibraryTransitiveDependency", "js", "pkg-transitive-dep.js"));
|
||||
Assert.FileExists(result, publishOutputPath, Path.Combine("wwwroot", "_content", "AppWithPackageAndP2PReferenceAndRID", "_framework", "scoped.styles.css"));
|
||||
var publishOutputPathWithRID = GetRidSpecificPublishOutputPath("win-x64");
|
||||
Assert.FileExists(result, publishOutputPathWithRID, Path.Combine("wwwroot", "_content", "ClassLibrary", "ClassLibrary.bundle.scp.css"));
|
||||
Assert.FileExists(result, publishOutputPathWithRID, Path.Combine("wwwroot", "_content", "ClassLibrary", "js", "project-transitive-dep.js"));
|
||||
Assert.FileExists(result, publishOutputPathWithRID, Path.Combine("wwwroot", "_content", "ClassLibrary", "js", "project-transitive-dep.v4.js"));
|
||||
Assert.FileExists(result, publishOutputPathWithRID, Path.Combine("wwwroot", "_content", "ClassLibrary2", "css", "site.css"));
|
||||
Assert.FileExists(result, publishOutputPathWithRID, Path.Combine("wwwroot", "_content", "ClassLibrary2", "js", "project-direct-dep.js"));
|
||||
Assert.FileExists(result, publishOutputPathWithRID, Path.Combine("wwwroot", "_content", "PackageLibraryDirectDependency", "css", "site.css"));
|
||||
Assert.FileExists(result, publishOutputPathWithRID, Path.Combine("wwwroot", "_content", "PackageLibraryDirectDependency", "js", "pkg-direct-dep.js"));
|
||||
Assert.FileExists(result, publishOutputPathWithRID, Path.Combine("wwwroot", "_content", "PackageLibraryTransitiveDependency", "js", "pkg-transitive-dep.js"));
|
||||
Assert.FileExists(result, publishOutputPathWithRID, Path.Combine("wwwroot", "AppWithPackageAndP2PReferenceAndRID.styles.css"));
|
||||
|
||||
// Validate that static web assets don't get published as content too on their regular path
|
||||
Assert.FileDoesNotExist(result, publishOutputPath, Path.Combine("wwwroot", "js", "project-transitive-dep.js"));
|
||||
Assert.FileDoesNotExist(result, publishOutputPath, Path.Combine("wwwroot", "js", "project-transitive-dep.v4.js"));
|
||||
Assert.FileDoesNotExist(result, publishOutputPathWithRID, Path.Combine("wwwroot", "js", "project-transitive-dep.js"));
|
||||
Assert.FileDoesNotExist(result, publishOutputPathWithRID, Path.Combine("wwwroot", "js", "project-transitive-dep.v4.js"));
|
||||
|
||||
// Validate that the manifest never gets copied
|
||||
Assert.FileDoesNotExist(result, publishOutputPath, "AppWithPackageAndP2PReference.StaticWebAssets.xml");
|
||||
Assert.FileDoesNotExist(result, publishOutputPathWithRID, "AppWithPackageAndP2PReference.StaticWebAssets.xml");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -122,6 +124,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
|
||||
Assert.BuildPassed(publish);
|
||||
|
||||
Assert.FileExists(publish, PublishOutputPath, Path.Combine("wwwroot", "_content", "ClassLibrary", "ClassLibrary.bundle.scp.css"));
|
||||
Assert.FileExists(publish, PublishOutputPath, Path.Combine("wwwroot", "_content", "ClassLibrary", "js", "project-transitive-dep.js"));
|
||||
Assert.FileExists(publish, PublishOutputPath, Path.Combine("wwwroot", "_content", "ClassLibrary", "js", "project-transitive-dep.v4.js"));
|
||||
Assert.FileExists(publish, PublishOutputPath, Path.Combine("wwwroot", "_content", "ClassLibrary2", "css", "site.css"));
|
||||
|
|
@ -129,7 +132,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
Assert.FileExists(publish, PublishOutputPath, Path.Combine("wwwroot", "_content", "PackageLibraryDirectDependency", "css", "site.css"));
|
||||
Assert.FileExists(publish, PublishOutputPath, Path.Combine("wwwroot", "_content", "PackageLibraryDirectDependency", "js", "pkg-direct-dep.js"));
|
||||
Assert.FileExists(publish, PublishOutputPath, Path.Combine("wwwroot", "_content", "PackageLibraryTransitiveDependency", "js", "pkg-transitive-dep.js"));
|
||||
Assert.FileExists(publish, PublishOutputPath, Path.Combine("wwwroot", "_content", "AppWithPackageAndP2PReference", "_framework", "scoped.styles.css"));
|
||||
Assert.FileExists(publish, PublishOutputPath, Path.Combine("wwwroot", "AppWithPackageAndP2PReference.styles.css"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -145,6 +148,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
|
||||
Assert.BuildPassed(publish);
|
||||
|
||||
Assert.FileExists(publish, PublishOutputPath, Path.Combine("wwwroot", "_content", "ClassLibrary", "ClassLibrary.bundle.scp.css"));
|
||||
Assert.FileExists(publish, PublishOutputPath, Path.Combine("wwwroot", "_content", "ClassLibrary", "js", "project-transitive-dep.js"));
|
||||
Assert.FileExists(publish, PublishOutputPath, Path.Combine("wwwroot", "_content", "ClassLibrary", "js", "project-transitive-dep.v4.js"));
|
||||
Assert.FileExists(publish, PublishOutputPath, Path.Combine("wwwroot", "_content", "ClassLibrary2", "css", "site.css"));
|
||||
|
|
@ -152,7 +156,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
Assert.FileExists(publish, PublishOutputPath, Path.Combine("wwwroot", "_content", "PackageLibraryDirectDependency", "css", "site.css"));
|
||||
Assert.FileExists(publish, PublishOutputPath, Path.Combine("wwwroot", "_content", "PackageLibraryDirectDependency", "js", "pkg-direct-dep.js"));
|
||||
Assert.FileExists(publish, PublishOutputPath, Path.Combine("wwwroot", "_content", "PackageLibraryTransitiveDependency", "js", "pkg-transitive-dep.js"));
|
||||
Assert.FileExists(publish, PublishOutputPath, Path.Combine("wwwroot", "_content", "AppWithPackageAndP2PReference", "_framework", "scoped.styles.css"));
|
||||
Assert.FileExists(publish, PublishOutputPath, Path.Combine("wwwroot", "AppWithPackageAndP2PReference.styles.css"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -171,6 +175,18 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
var path = Assert.FileExists(result, OutputPath, "SimpleMvc.dll");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[InitializeTestProject("AppWithPackageAndP2PReference", language: "C#", additionalProjects: new[] { "ClassLibrary", "ClassLibrary2" })]
|
||||
public async Task Build_Fails_WhenConflictingAssetsFoundBetweenAStaticWebAssetAndAFileInTheWebRootFolder()
|
||||
{
|
||||
Directory.CreateDirectory(Path.Combine(Project.DirectoryPath, "wwwroot", "_content", "ClassLibrary", "js"));
|
||||
File.WriteAllText(Path.Combine(Project.DirectoryPath, "wwwroot", "_content", "ClassLibrary", "js", "project-transitive-dep.js"), "console.log('transitive-dep');");
|
||||
|
||||
var result = await DotnetMSBuild("Build");
|
||||
|
||||
Assert.BuildFailed(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/23756")]
|
||||
[InitializeTestProject("AppWithPackageAndP2PReference", language: "C#", additionalProjects: new[] { "ClassLibrary", "ClassLibrary2" })]
|
||||
|
|
@ -301,15 +317,17 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
Path.Combine(restorePath, "packagelibrarydirectdependency", "1.0.0", "build", "..", "staticwebassets") + Path.DirectorySeparatorChar,
|
||||
Path.GetFullPath(Path.Combine(source, "ClassLibrary2", "wwwroot")) + Path.DirectorySeparatorChar,
|
||||
Path.GetFullPath(Path.Combine(source, "ClassLibrary", "wwwroot")) + Path.DirectorySeparatorChar,
|
||||
Path.GetFullPath(Path.Combine(source, "AppWithPackageAndP2PReference", IntermediateOutputPath, "scopedcss")) + Path.DirectorySeparatorChar,
|
||||
Path.GetFullPath(Path.Combine(source, "ClassLibrary", IntermediateOutputPath, "scopedcss", "projectbundle")) + Path.DirectorySeparatorChar,
|
||||
Path.GetFullPath(Path.Combine(source, "AppWithPackageAndP2PReference", IntermediateOutputPath, "scopedcss", "bundle")) + Path.DirectorySeparatorChar,
|
||||
};
|
||||
|
||||
return $@"<StaticWebAssets Version=""1.0"">
|
||||
<ContentRoot BasePath=""_content/AppWithPackageAndP2PReference"" Path=""{projects[4]}"" />
|
||||
<ContentRoot BasePath=""_content/ClassLibrary"" Path=""{projects[4]}"" />
|
||||
<ContentRoot BasePath=""_content/ClassLibrary"" Path=""{projects[3]}"" />
|
||||
<ContentRoot BasePath=""_content/ClassLibrary2"" Path=""{projects[2]}"" />
|
||||
<ContentRoot BasePath=""_content/PackageLibraryDirectDependency"" Path=""{projects[1]}"" />
|
||||
<ContentRoot BasePath=""_content/PackageLibraryTransitiveDependency"" Path=""{projects[0]}"" />
|
||||
<ContentRoot BasePath=""/"" Path=""{projects[5]}"" />
|
||||
</StaticWebAssets>";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Build.Framework;
|
||||
|
|
@ -69,8 +68,10 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
|||
|
||||
if (scopeFiles.Count > 1)
|
||||
{
|
||||
Log.LogError($"More than one scoped css files were found for the razor component '{component}'. Each razor component must have at most" +
|
||||
" a single associated scoped css file." + Environment.NewLine + string.Join(Environment.NewLine, scopeFiles.Select(f => f.ItemSpec)));
|
||||
Log.LogError(null, "BLAZOR101", "", component, 0, 0, 0, 0, $"More than one scoped css files were found for the razor component '{component}'. " +
|
||||
$"Each razor component must have at most a single associated scoped css file." +
|
||||
Environment.NewLine +
|
||||
string.Join(Environment.NewLine, scopeFiles.Select(f => f.ItemSpec)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -84,7 +85,7 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
|||
// can update the Content item for the .razor.css file with Scoped=false and we will not consider it.
|
||||
foreach (var unmatched in unmatchedScopedCss)
|
||||
{
|
||||
Log.LogError($"The scoped css file '{unmatched.ItemSpec}' was defined but no associated razor component was found for it.");
|
||||
Log.LogError(null, "BLAZOR102", "", unmatched.ItemSpec, 0, 0, 0, 0, $"The scoped css file '{unmatched.ItemSpec}' was defined but no associated razor component was found for it.");
|
||||
}
|
||||
|
||||
RazorComponentsWithScopes = razorComponentsWithScopes.ToArray();
|
||||
|
|
|
|||
|
|
@ -28,8 +28,7 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
|||
for (var i = 0; i < ScopedCssInput.Length; i++)
|
||||
{
|
||||
var input = ScopedCssInput[i];
|
||||
// Todo: Normalize path to forward slashes and lowercase before computing the hash
|
||||
var relativePath = input.ItemSpec.Replace("\\","//");
|
||||
var relativePath = input.ItemSpec.ToLowerInvariant().Replace("\\","//");
|
||||
var scope = input.GetMetadata("CssScope");
|
||||
scope = !string.IsNullOrEmpty(scope) ? scope : GenerateScope(TargetName, relativePath);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// 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.Linq;
|
||||
using System.Security.Cryptography;
|
||||
|
|
@ -13,21 +14,72 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
|||
{
|
||||
public class ConcatenateCssFiles : Task
|
||||
{
|
||||
private static readonly IComparer<ITaskItem> _fullPathComparer =
|
||||
Comparer<ITaskItem>.Create((x, y) => StringComparer.OrdinalIgnoreCase.Compare(x.GetMetadata("FullPath"), y.GetMetadata("FullPath")));
|
||||
|
||||
[Required]
|
||||
public ITaskItem[] FilesToProcess { get; set; }
|
||||
public ITaskItem[] ScopedCssFiles { get; set; }
|
||||
|
||||
[Required]
|
||||
public ITaskItem[] ProjectBundles { get; set; }
|
||||
|
||||
[Required]
|
||||
public string ScopedCssBundleBasePath { get; set; }
|
||||
|
||||
[Required]
|
||||
public string OutputFile { get; set; }
|
||||
|
||||
public override bool Execute()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
var orderedFiles = FilesToProcess.OrderBy(f => f.GetMetadata("FullPath")).ToArray();
|
||||
for (var i = 0; i < orderedFiles.Length; i++)
|
||||
if (ProjectBundles.Length > 0)
|
||||
{
|
||||
var current = orderedFiles[i];
|
||||
builder.AppendLine($"/* {current.GetMetadata("BasePath").Replace("\\","/")}{current.GetMetadata("RelativePath").Replace("\\","/")} */");
|
||||
foreach (var line in File.ReadLines(FilesToProcess[i].GetMetadata("FullPath")))
|
||||
Array.Sort(ProjectBundles, _fullPathComparer);
|
||||
}
|
||||
Array.Sort(ScopedCssFiles, _fullPathComparer);
|
||||
|
||||
var builder = new StringBuilder();
|
||||
if (ProjectBundles.Length > 0)
|
||||
{
|
||||
// We are importing bundles from other class libraries and packages, in that case we need to compute the
|
||||
// import path relative to the position of where the final bundle will be.
|
||||
// Our final bundle will always be at "<<CurrentBasePath>>/scoped.styles.css"
|
||||
// Other bundles will be at "<<BundleBasePath>>/bundle.bdl.scp.css"
|
||||
// The base and relative paths can be modified by the user, so we do a normalization process to ensure they
|
||||
// are in the shape we expect them before we use them.
|
||||
// We normalize path separators to '\' from '/' which is what we expect on a url. The separator can come as
|
||||
// '\' as a result of user input or another MSBuild path normalization operation. We always want '/' since that
|
||||
// is what is valid on the url.
|
||||
// We remove leading and trailing '/' on all paths to ensure we can combine them properly. Users might specify their
|
||||
// base path with or without forward and trailing slashes and we always need to make sure we combine them appropriately.
|
||||
// These links need to be relative to the final bundle to be independent of the path where the main app is being served.
|
||||
// For example:
|
||||
// An app is served from the "subdir" path base, the main bundle path on disk is "MyApp/scoped.styles.css" and it uses a
|
||||
// library with scoped components that is placed on "_content/library/bundle.bdl.scp.css".
|
||||
// The resulting import would be "import '../_content/library/bundle.bdl.scp.css'".
|
||||
// If we were to produce "/_content/library/bundle.bdl.scp.css" it would fail to accoutn for "subdir"
|
||||
// We could produce shorter paths if we detected common segments between the final bundle base path and the imported bundle
|
||||
// base paths, but its more work and it will not have a significant impact on the bundle size size.
|
||||
var normalizedBasePath = NormalizePath(ScopedCssBundleBasePath);
|
||||
var currentBasePathSegments = normalizedBasePath.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var prefix = string.Join("/", Enumerable.Repeat("..", currentBasePathSegments.Length));
|
||||
for (var i = 0; i < ProjectBundles.Length; i++)
|
||||
{
|
||||
var bundle = ProjectBundles[i];
|
||||
var bundleBasePath = NormalizePath(bundle.GetMetadata("BasePath"));
|
||||
var relativePath = NormalizePath(bundle.GetMetadata("RelativePath"));
|
||||
var importPath = NormalizePath(Path.Combine(prefix, bundleBasePath, relativePath));
|
||||
|
||||
builder.AppendLine($"@import '{importPath}';");
|
||||
}
|
||||
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
for (var i = 0; i < ScopedCssFiles.Length; i++)
|
||||
{
|
||||
var current = ScopedCssFiles[i];
|
||||
builder.AppendLine($"/* {NormalizePath(current.GetMetadata("BasePath"))}/{NormalizePath(current.GetMetadata("RelativePath"))} */");
|
||||
foreach (var line in File.ReadLines(current.GetMetadata("FullPath")))
|
||||
{
|
||||
builder.AppendLine(line);
|
||||
}
|
||||
|
|
@ -45,6 +97,8 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
|||
return !Log.HasLoggedErrors;
|
||||
}
|
||||
|
||||
private string NormalizePath(string path) => path.Replace("\\", "/").Trim('/');
|
||||
|
||||
private bool SameContent(string content, string outputFilePath)
|
||||
{
|
||||
var contentHash = GetContentHash(content);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
|||
{
|
||||
private const string ContentRoot = "ContentRoot";
|
||||
private const string BasePath = "BasePath";
|
||||
private const string NodePath = "Path";
|
||||
private const string SourceId = "SourceId";
|
||||
|
||||
[Required]
|
||||
|
|
@ -82,8 +83,7 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
|||
// single trailing separator.
|
||||
var normalizedContentRoot = $"{contentRoot.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)}{Path.DirectorySeparatorChar}";
|
||||
|
||||
// At this point we already know that there are no elements with different base paths and same content roots
|
||||
// or viceversa. Here we simply skip additional items that have the same base path and same content root.
|
||||
// Here we simply skip additional items that have the same base path and same content root.
|
||||
if (!nodes.Exists(e => e.Attribute("BasePath").Value.Equals(normalizedBasePath, StringComparison.OrdinalIgnoreCase) &&
|
||||
e.Attribute("Path").Value.Equals(normalizedContentRoot, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
|
|
@ -94,7 +94,7 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
|||
}
|
||||
|
||||
// Its important that we order the nodes here to produce a manifest deterministically.
|
||||
return nodes.OrderBy(e=>e.Attribute(BasePath).Value);
|
||||
return nodes.OrderBy(e=>e.Attribute(BasePath).Value).ThenBy(e => e.Attribute(NodePath).Value);
|
||||
}
|
||||
|
||||
private XmlWriter GetXmlWriter(XmlWriterSettings settings)
|
||||
|
|
@ -116,78 +116,6 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
|||
}
|
||||
}
|
||||
|
||||
// We want to validate that there are no different item groups that share either the same base path
|
||||
// but different content roots or that share the same content root but different base paths.
|
||||
// We pass in all the static web assets that we discovered to this task without making any distinction for
|
||||
// duplicates, so here we skip elements for which we are already tracking an element with the same
|
||||
// content root path and same base path.
|
||||
|
||||
// Case-sensitivity depends on the underlying OS so we are not going to do anything to enforce it here.
|
||||
// Any two items that match base path and content root in a case-insensitive way won't produce an error.
|
||||
// Any other two items will produce an error even if there is only a casing difference between either the
|
||||
// base paths or the content roots.
|
||||
var basePaths = new Dictionary<string, ITaskItem>(StringComparer.OrdinalIgnoreCase);
|
||||
var contentRootPaths = new Dictionary<string, ITaskItem>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
for (var i = 0; i < ContentRootDefinitions.Length; i++)
|
||||
{
|
||||
var contentRootDefinition = ContentRootDefinitions[i];
|
||||
var basePath = contentRootDefinition.GetMetadata(BasePath);
|
||||
var contentRoot = contentRootDefinition.GetMetadata(ContentRoot);
|
||||
var sourceId = contentRootDefinition.GetMetadata(SourceId);
|
||||
|
||||
if (basePaths.TryGetValue(basePath, out var existingBasePath))
|
||||
{
|
||||
var existingBasePathContentRoot = existingBasePath.GetMetadata(ContentRoot);
|
||||
var existingSourceId = existingBasePath.GetMetadata(SourceId);
|
||||
if (!string.Equals(contentRoot, existingBasePathContentRoot, StringComparison.OrdinalIgnoreCase) &&
|
||||
// We want to check this case to allow for client-side blazor projects to have multiple different content
|
||||
// root sources exposed under the same base path while still requiring unique base paths/content roots across
|
||||
// project/package boundaries.
|
||||
!string.Equals(sourceId, existingSourceId, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Case:
|
||||
// Item2: /_content/Library, project:/project/aspnetContent2
|
||||
// Item1: /_content/Library, package:/package/aspnetContent1
|
||||
Log.LogError($"Duplicate base paths '{basePath}' for content root paths '{contentRoot}' and '{existingBasePathContentRoot}'. " +
|
||||
$"('{contentRootDefinition.ItemSpec}', '{existingBasePath.ItemSpec}')");
|
||||
return false;
|
||||
}
|
||||
|
||||
// It was a duplicate, so we skip it.
|
||||
// Case:
|
||||
// Item1: /_content/Library, project:/project/aspnetContent
|
||||
// Item2: /_content/Library, project:/project/aspnetContent
|
||||
|
||||
// It was a separate content root exposed from the same project/package, so we skip it.
|
||||
// Case:
|
||||
// Item1: /_content/Library, project:/project/aspnetContent/bin/debug/netstandard2.1/dist
|
||||
// Item2: /_content/Library, project:/project/wwwroot
|
||||
}
|
||||
else
|
||||
{
|
||||
if (contentRootPaths.TryGetValue(contentRoot, out var existingContentRoot))
|
||||
{
|
||||
// Case:
|
||||
// Item1: /_content/Library1, /package/aspnetContent
|
||||
// Item2: /_content/Library2, /package/aspnetContent
|
||||
Log.LogError($"Duplicate content root paths '{contentRoot}' for base paths '{basePath}' and '{existingContentRoot.GetMetadata(BasePath)}' " +
|
||||
$"('{contentRootDefinition.ItemSpec}', '{existingContentRoot.ItemSpec}')");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!basePaths.ContainsKey(basePath))
|
||||
{
|
||||
basePaths.Add(basePath, contentRootDefinition);
|
||||
}
|
||||
|
||||
if (!contentRootPaths.ContainsKey(contentRoot))
|
||||
{
|
||||
contentRootPaths.Add(contentRoot, contentRootDefinition);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -203,4 +131,4 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
|||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
|
|
@ -42,19 +43,22 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
|||
return !Log.HasLoggedErrors;
|
||||
}
|
||||
|
||||
var template = StaticWebAssets[0];
|
||||
var itemGroup = new XElement("ItemGroup");
|
||||
var orderedAssets = StaticWebAssets.OrderBy(e => e.GetMetadata(BasePath), StringComparer.OrdinalIgnoreCase)
|
||||
.ThenBy(e => e.GetMetadata(RelativePath), StringComparer.OrdinalIgnoreCase);
|
||||
foreach(var element in orderedAssets)
|
||||
{
|
||||
itemGroup.Add(new XElement("StaticWebAsset",
|
||||
new XAttribute("Include", @$"$(MSBuildThisFileDirectory)..\staticwebassets\{Normalize(element.GetMetadata(RelativePath))}"),
|
||||
new XElement(SourceType, "Package"),
|
||||
new XElement(SourceId, element.GetMetadata(SourceId)),
|
||||
new XElement(ContentRoot, @"$(MSBuildThisFileDirectory)..\staticwebassets\"),
|
||||
new XElement(BasePath, element.GetMetadata(BasePath)),
|
||||
new XElement(RelativePath, element.GetMetadata(RelativePath))));
|
||||
}
|
||||
|
||||
var document = new XDocument(new XDeclaration("1.0", "utf-8", "yes"));
|
||||
var root = new XElement(
|
||||
"Project",
|
||||
new XElement("ItemGroup",
|
||||
new XElement("StaticWebAsset",
|
||||
new XAttribute("Include", @"$(MSBuildThisFileDirectory)..\staticwebassets\**"),
|
||||
new XElement(SourceType, "Package"),
|
||||
new XElement(SourceId, template.GetMetadata(SourceId)),
|
||||
new XElement(ContentRoot, @"$(MSBuildThisFileDirectory)..\staticwebassets\"),
|
||||
new XElement(BasePath, template.GetMetadata(BasePath)),
|
||||
new XElement(RelativePath, "%(RecursiveDir)%(FileName)%(Extension)"))));
|
||||
var root = new XElement("Project", itemGroup);
|
||||
|
||||
document.Add(root);
|
||||
|
||||
|
|
@ -74,6 +78,8 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
|||
}
|
||||
|
||||
return !Log.HasLoggedErrors;
|
||||
|
||||
static string Normalize(string relativePath) => relativePath.Replace("/", "\\").TrimStart('\\');
|
||||
}
|
||||
|
||||
private XmlWriter GetXmlWriter(XmlWriterSettings settings)
|
||||
|
|
@ -105,12 +111,7 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
|||
}
|
||||
|
||||
if (!ValidateMetadataMatches(firstAsset, webAsset, SourceId) ||
|
||||
!ValidateMetadataMatches(firstAsset, webAsset, SourceType) ||
|
||||
// Now that we support generated assets we need to be able to support multiple content roots.
|
||||
// We need to change this check for one that ensures that no two files end up in the same final destination
|
||||
//!ValidateMetadataMatches(firstAsset, webAsset, ContentRoot) ||
|
||||
// See https://github.com/dotnet/aspnetcore/issues/24257
|
||||
!ValidateMetadataMatches(firstAsset, webAsset, BasePath))
|
||||
!ValidateMetadataMatches(firstAsset, webAsset, SourceType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
@ -123,7 +124,7 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
|||
{
|
||||
var referenceMetadata = reference.GetMetadata(metadata);
|
||||
var candidateMetadata = candidate.GetMetadata(metadata);
|
||||
if (!string.Equals(referenceMetadata, candidateMetadata, System.StringComparison.Ordinal))
|
||||
if (!string.Equals(referenceMetadata, candidateMetadata, StringComparison.Ordinal))
|
||||
{
|
||||
Log.LogError($"Static web assets have different '{metadata}' metadata values '{referenceMetadata}' and '{candidateMetadata}' for '{reference.ItemSpec}' and '{candidate.ItemSpec}'.");
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -16,21 +16,30 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
|||
[Output]
|
||||
public ITaskItem[] ScopedCssAssets { get; set; }
|
||||
|
||||
[Output]
|
||||
public ITaskItem[] ScopedCssProjectBundles { get; set; }
|
||||
|
||||
public override bool Execute()
|
||||
{
|
||||
var scopedCssAssets = new List<ITaskItem>();
|
||||
var scopedCssProjectBundles = new List<ITaskItem>();
|
||||
|
||||
for (var i = 0; i < StaticWebAssets.Length; i++)
|
||||
{
|
||||
var swa = StaticWebAssets[i];
|
||||
var fullPath = swa.GetMetadata("RelativePath");
|
||||
if (fullPath.EndsWith(".rz.scp.css", StringComparison.OrdinalIgnoreCase))
|
||||
var path = swa.GetMetadata("RelativePath");
|
||||
if (path.EndsWith(".rz.scp.css", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
scopedCssAssets.Add(swa);
|
||||
}
|
||||
else if (path.EndsWith(".bundle.scp.css", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
scopedCssProjectBundles.Add(swa);
|
||||
}
|
||||
}
|
||||
|
||||
ScopedCssAssets = scopedCssAssets.ToArray();
|
||||
ScopedCssProjectBundles = scopedCssProjectBundles.ToArray();
|
||||
|
||||
return !Log.HasLoggedErrors;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// 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;
|
||||
|
|
@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
|||
}
|
||||
else
|
||||
{
|
||||
var webRootPath = GetWebRootPath(
|
||||
var webRootPath = GetWebRootPath("/wwwroot",
|
||||
contentRootDefinition.GetMetadata(BasePath),
|
||||
contentRootDefinition.GetMetadata(RelativePath));
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
|||
{
|
||||
var webRootFile = WebRootFiles[i];
|
||||
var relativePath = webRootFile.GetMetadata(TargetPath);
|
||||
var webRootFileWebRootPath = GetWebRootPath("/", relativePath);
|
||||
var webRootFileWebRootPath = GetWebRootPath("", "/", relativePath);
|
||||
if (assetsByWebRootPaths.TryGetValue(webRootFileWebRootPath, out var existingAsset))
|
||||
{
|
||||
Log.LogError($"The static web asset '{existingAsset.ItemSpec}' has a conflicting web root path '{webRootFileWebRootPath}' with the project file '{webRootFile.ItemSpec}'.");
|
||||
|
|
@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
|||
}
|
||||
|
||||
// Normalizes /base/relative \base\relative\ base\relative and so on to /base/relative
|
||||
private string GetWebRootPath(string basePath, string relativePath) => $"/{Path.Combine(basePath, relativePath.TrimStart('.').TrimStart('/')).Replace("\\", "/").Trim('/')}";
|
||||
private string GetWebRootPath(string webRoot, string basePath, string relativePath) => $"{webRoot}/{Path.Combine(basePath, relativePath.TrimStart('.').TrimStart('/')).Replace("\\", "/").Trim('/')}";
|
||||
|
||||
private bool EnsureRequiredMetadata(ITaskItem item, string metadataName)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ Copyright (c) .NET Foundation. All rights reserved.
|
|||
* Computing the scopes will happen very early on the pipeline and it will generate all the input that the compiler needs to do its job
|
||||
independently.
|
||||
* For web applications (Blazor webassembly and Blazor server) the main project is responsible for producing the final CSS bundle and making
|
||||
it available during development and production behind _framework/scoped.styles.css
|
||||
it available during development and production behind $(PackageId).styles.css
|
||||
* For razor class libraries we will add the list of ScopedCss to the list of available static web assets imported by the project, the main project
|
||||
will then discover these assets and add them to the ScopedCss files to process in the final bundle.
|
||||
* For packing in razor class libraries, the ScopedCss files will get processed and added as static web assets to the pack.
|
||||
|
|
@ -51,16 +51,62 @@ Integration with static web assets:
|
|||
<UsingTask TaskName="Microsoft.AspNetCore.Razor.Tasks.ConcatenateCssFiles" AssemblyFile="$(RazorSdkBuildTasksAssembly)" />
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
<PrepareForRunDependsOn>
|
||||
_PrepareForScopedCss;
|
||||
$(PrepareForRunDependsOn)
|
||||
</PrepareForRunDependsOn>
|
||||
|
||||
<_PrepareForScopedCssDependsOn Condition="'$(DisableScopedCssBundling)' != 'true'">
|
||||
_PrepareForBundling;
|
||||
ResolveStaticWebAssetsInputs;
|
||||
_CollectAllScopedCssAssets;
|
||||
BundleScopedCssFiles;
|
||||
$(_PrepareForScopedCssDependsOn)
|
||||
</_PrepareForScopedCssDependsOn>
|
||||
<!-- Order between this definition and the above one is important to make sure we don't create a circular reference loop. -->
|
||||
<_PrepareForScopedCssDependsOn>
|
||||
_GenerateScopedCssFiles;
|
||||
$(_PrepareForScopedCssDependsOn)
|
||||
</_PrepareForScopedCssDependsOn>
|
||||
|
||||
<ResolveCurrentProjectStaticWebAssetsInputsDependsOn>
|
||||
$(ResolveCurrentProjectStaticWebAssetsInputsDependsOn);
|
||||
_AddGeneratedScopedCssFiles;
|
||||
</ResolveCurrentProjectStaticWebAssetsInputsDependsOn>
|
||||
|
||||
<!-- We want to include the bundle as part of the list of static web assets when bundling is enabled and remove all the individual scoped
|
||||
css files.
|
||||
-->
|
||||
<ResolveStaticWebAssetsInputsDependsOn Condition="'$(DisableScopedCssBundling)' != 'true'">
|
||||
$(ResolveStaticWebAssetsInputsDependsOn);
|
||||
_AddScopedCssBundles;
|
||||
</ResolveStaticWebAssetsInputsDependsOn>
|
||||
|
||||
<!-- When used as a reference, the app will have been built before and as a result the list of static web assets will include the bundle
|
||||
instead of the individual files. We need to correct that in GetCopyToOuputDirectoryItems -->
|
||||
|
||||
<GetCurrentProjectStaticWebAssetsDependsOn Condition="'$(DisableScopedCssBundling)' != 'true'">
|
||||
$(GetCurrentProjectStaticWebAssetsDependsOn);
|
||||
_AddScopedCssBundles;
|
||||
_AddGeneratedScopedCssFilesForReference;
|
||||
</GetCurrentProjectStaticWebAssetsDependsOn>
|
||||
|
||||
<GenerateStaticWebAssetsPackTargetsDependsOn>
|
||||
$(GenerateStaticWebAssetsPackTargetsDependsOn);
|
||||
_AddScopedCssBundles;
|
||||
_AddGeneratedScopedCssFilesForReference;
|
||||
</GenerateStaticWebAssetsPackTargetsDependsOn>
|
||||
|
||||
<!-- We are going to use .rz.scp.css as the extension to mark scoped css files that come from packages or that have been pre-procesed by
|
||||
referenced class libraries. This way, we can use that information to adjust the build pipeline without having to rely on external
|
||||
sources like an additional itemgroup or metadata.
|
||||
-->
|
||||
<_ScopedCssExtension>.rz.scp.css</_ScopedCssExtension>
|
||||
<ResolveStaticWebAssetsInputsDependsOn>$(ResolveStaticWebAssetsInputsDependsOn);_CollectAllScopedCssAssets;AddScopedCssBundle</ResolveStaticWebAssetsInputsDependsOn>
|
||||
<ResolveCurrentProjectStaticWebAssetsInputsDependsOn>$(ResolveCurrentProjectStaticWebAssetsInputsDependsOn);_AddGeneratedScopedCssFiles</ResolveCurrentProjectStaticWebAssetsInputsDependsOn>
|
||||
<GetCurrentProjectStaticWebAssetsDependsOn Condition="'$(UsingMicrosoftNETSdkBlazorWebAssembly)' == 'true'">$(GetCurrentProjectStaticWebAssetsDependsOn);IncludeScopedCssBundle;</GetCurrentProjectStaticWebAssetsDependsOn>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="_PrepareForScopedCss" DependsOnTargets="$(_PrepareForScopedCssDependsOn)" />
|
||||
|
||||
<Target Name="ResolveScopedCssInputs">
|
||||
<!--
|
||||
Gathers input source files for Razor component generation. This is a separate target so that we can avoid
|
||||
|
|
@ -73,13 +119,13 @@ Integration with static web assets:
|
|||
<Output TaskParameter="DiscoveredScopedCssInputs" ItemName="_DiscoveredScopedCssInputs" />
|
||||
</DiscoverDefaultScopedCssItems>
|
||||
|
||||
<ItemGroup Condition="'$(EnableDefaultScopedCssItems)'=='true'">
|
||||
<ItemGroup Condition="'$(EnableDefaultScopedCssItems)' == 'true'">
|
||||
<ScopedCssInput Include="@(_DiscoveredScopedCssInputs)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Remove="@(ScopedCssInput)" />
|
||||
<Content Include="@(ScopedCssInput)" CopyToPublishDirectory="Never" />
|
||||
<Content Include="@(ScopedCssInput)" Pack="false" CopyToPublishDirectory="Never" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
|
|
@ -116,6 +162,13 @@ Integration with static web assets:
|
|||
</_ScopedCss>
|
||||
<_ScopedCssOutputs Include="%(_ScopedCss.OutputFile)" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- https://github.com/dotnet/project-system/blob/master/docs/up-to-date-check.md -->
|
||||
<ItemGroup>
|
||||
<UpToDateCheckInput Include="%(_ScopedCss.Identity)" />
|
||||
<UpToDateCheckBuilt Include="%(_ScopedCss.OutputFile)" Original="%(_ScopedCss.Identity)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Target>
|
||||
|
||||
<!-- Transforms the original scoped CSS files into their scoped versions on their designated output paths -->
|
||||
|
|
@ -139,7 +192,7 @@ Integration with static web assets:
|
|||
In the hosted blazor webassembly case, we want to include the bundle within the assets returned to the host, so we wire up this task
|
||||
to `GetCurrentProjectStaticWebAssetsDependsOn` so that contents are replaced and shared with the host application.
|
||||
|
||||
Normally, _CollectAllScopedCssAssets will find all the scoped css files from referenced packages, class libraries and the current project. When AddScopedCssBundle
|
||||
Normally, _CollectAllScopedCssAssets will find all the scoped css files from referenced packages, class libraries and the current project. When _AddScopedCssBundles
|
||||
runs, it will remove all those static web assets and add the bundle asset.
|
||||
When _CollectAllScopedCssAssets runs as part of a hosted blazor webassembly app, only the current project and package assets are removed from the list of
|
||||
static web assets. If the host also decides to generate a bundle, there will be a bundle for the razor client app and another bundle for the host and they will
|
||||
|
|
@ -154,118 +207,176 @@ Integration with static web assets:
|
|||
If one single bundle is desired, bundling can be disabled in the Blazor application and the host will create a single big bundle file.
|
||||
|
||||
-->
|
||||
<Target Name="AddScopedCssBundle" Condition="'$(ScopedCssDisableBundling)' != 'true'" DependsOnTargets="_CollectAllScopedCssAssets">
|
||||
|
||||
<Target Name="_PrepareForBundling" DependsOnTargets="ResolveStaticWebAssetsConfiguration">
|
||||
<PropertyGroup>
|
||||
<_ScopedCssOutputPath>$(_ScopedCssIntermediatePath)_framework\scoped.styles.css</_ScopedCssOutputPath>
|
||||
<_ScopedCssOutputFullPath>$([System.IO.Path]::Combine('$(MSBuildProjectFileDirectory)', '$(_ScopedCssIntermediatePath)_framework\scoped.styles.css'))</_ScopedCssOutputFullPath>
|
||||
<!-- This bundle represents the bundle for the entire application dependency graph which includes the application scoped css files and all the scoped css files from
|
||||
projects and packages that this app references -->
|
||||
<_ScopedCssBundleContentRoot>$(_ScopedCssIntermediatePath)bundle\</_ScopedCssBundleContentRoot>
|
||||
<_ScopedCssOutputPath>$(_ScopedCssIntermediatePath)bundle\$(PackageId).styles.css</_ScopedCssOutputPath>
|
||||
<_ScopedCssOutputFullPath>$([System.IO.Path]::Combine('$(MSBuildProjectFileDirectory)', '$(_ScopedCssIntermediatePath)bundle\$(PackageId).styles.css'))</_ScopedCssOutputFullPath>
|
||||
|
||||
<!-- This bundle represents the bundle for the scoped css files in this project, without references to other projects or package scoped css files. This bundle is used by projects
|
||||
referencing this project that import it through an import rule into their app bundle -->
|
||||
<_ScopedCssProjectBundleContentRoot>$(_ScopedCssIntermediatePath)projectbundle\</_ScopedCssProjectBundleContentRoot>
|
||||
<_ScopedCssProjectOutputPath>$(_ScopedCssIntermediatePath)projectbundle\$(PackageId).bundle.scp.css</_ScopedCssProjectOutputPath>
|
||||
<_ScopedCssProjectOutputFullPath>$([System.IO.Path]::Combine('$(MSBuildProjectFileDirectory)', '$(_ScopedCssIntermediatePath)projectbundle\$(PackageId).bundle.scp.css'))</_ScopedCssProjectOutputFullPath>
|
||||
<!-- We want the scoped css bundle path to always point to the root path of the app, overriding the default base path unless it is not explicitly overriden
|
||||
by the user. This is so that when you are developing a server-side application or in the future potentially an ASP.NET application using css isolation,
|
||||
you don't have to make the urls in your files relative to "_content/$(PackageId).styles.css".
|
||||
If the user chooses to override the base path explicitly, we place the bundle at the root of the defined base path, this allows Blazor WebAssembly applications to be hosted
|
||||
on different paths other than the root path and for the bundle to behave as expected
|
||||
-->
|
||||
<_ScopedCssBundleBasePath>/</_ScopedCssBundleBasePath>
|
||||
<_ScopedCssBundleBasePath Condition="'$(StaticWebAssetBasePath)' != '_content/$(PackageId)'">$(StaticWebAssetBasePath)</_ScopedCssBundleBasePath>
|
||||
</PropertyGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="_ComputeCssBundles" DependsOnTargets="_PrepareForBundling;_CollectAllScopedCssAssets">
|
||||
<ItemGroup>
|
||||
<!-- When bundling is enabled we want to remove all identified generated scoped css files from the list of static web assets so that
|
||||
they are not copied to the output folder. -->
|
||||
<StaticWebAsset Remove="@(_AllScopedCss)" Condition="'$(ScopedCssDisableBundling)' != 'true'" />
|
||||
<!-- https://github.com/dotnet/aspnetcore/issues/24245 -->
|
||||
<StaticWebAsset Include="$(_ScopedCssOutputPath)" Condition="@(_AllScopedCss) != ''">
|
||||
<_AppBundleStaticWebAsset Include="$(_ScopedCssOutputPath)" Condition="@(_AllScopedCss) != ''">
|
||||
<SourceType></SourceType>
|
||||
<SourceId>$(PackageId)</SourceId>
|
||||
<ContentRoot>$(_ScopedCssIntermediatePath)</ContentRoot>
|
||||
<BasePath>$(StaticWebAssetBasePath)</BasePath>
|
||||
<RelativePath>_framework/scoped.styles.css</RelativePath>
|
||||
</StaticWebAsset>
|
||||
<_ExternalStaticWebAsset Include="$(_ScopedCssOutputPath)" Condition="@(_AllScopedCss) != ''">
|
||||
<SourceType>generated</SourceType>
|
||||
<ContentRoot>$(_ScopedCssBundleContentRoot)</ContentRoot>
|
||||
<BasePath>$(_ScopedCssBundleBasePath)</BasePath>
|
||||
<RelativePath>$(PackageId).styles.css</RelativePath>
|
||||
</_AppBundleStaticWebAsset>
|
||||
|
||||
<!-- We include the project bundle as an SWA too so that other targets can be aware of the file and take it into account when making decissions.
|
||||
The application bundle is the only one that gets a special treatment and is allowed to be on the "/" by default when the SWA default path hasn't
|
||||
changed.
|
||||
-->
|
||||
<_ProjectBundleStaticWebAsset Include="$(_ScopedCssProjectOutputPath)" Condition="@(_ScopedCss) != ''">
|
||||
<SourceType></SourceType>
|
||||
<SourceId>$(PackageId)</SourceId>
|
||||
<ContentRoot>$(_ScopedCssIntermediatePath)</ContentRoot>
|
||||
<ContentRoot>$(_ScopedCssProjectBundleContentRoot)</ContentRoot>
|
||||
<BasePath>$(StaticWebAssetBasePath)</BasePath>
|
||||
<RelativePath>_framework/scoped.styles.css</RelativePath>
|
||||
</_ExternalStaticWebAsset>
|
||||
<RelativePath>$(PackageId).bundle.scp.css</RelativePath>
|
||||
</_ProjectBundleStaticWebAsset>
|
||||
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="_AddScopedCssBundles" Condition="'$(DisableScopedCssBundling)' != 'true'" DependsOnTargets="_ComputeCssBundles">
|
||||
<ItemGroup>
|
||||
<!-- When bundling is enabled we want to remove all identified generated scoped css files from the list of static web assets so that
|
||||
they are not copied to the output folder. -->
|
||||
<StaticWebAsset Remove="@(_DiscoveredScopedCssFiles)" />
|
||||
<!-- https://github.com/dotnet/aspnetcore/issues/24245 -->
|
||||
<StaticWebAsset Include="@(_AppBundleStaticWebAsset)" />
|
||||
<!-- We include the project bundle as an SWA too so that other targets can be aware of the file and take it into account when making decissions.
|
||||
The application bundle is the only one that gets a special treatment and is allowed to be on the "/" by default when the SWA default path hasn't
|
||||
changed.
|
||||
-->
|
||||
<StaticWebAsset Include="@(_ProjectBundleStaticWebAsset)" />
|
||||
|
||||
<_ExternalStaticWebAsset Include="@(_AppBundleStaticWebAsset)">
|
||||
<SourceType>generated</SourceType>
|
||||
</_ExternalStaticWebAsset>
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
</Target>
|
||||
|
||||
<!-- This target runs as part of ResolveStaticWebAssetInputs and collects all the generated scoped css files. When bundling is enabled
|
||||
these files are removed from the list of static web assets by '_AddScopedCssBundle' -->
|
||||
these files are removed from the list of static web assets by '_AddScopedCssBundles' -->
|
||||
|
||||
<Target Name="_CollectAllScopedCssAssets">
|
||||
<ResolveAllScopedCssAssets StaticWebAssets="@(StaticWebAsset)">
|
||||
<Output TaskParameter="ScopedCssAssets" ItemName="_AllScopedCss" />
|
||||
<Output TaskParameter="ScopedCssAssets" ItemName="_DiscoveredScopedCssFiles" />
|
||||
<Output TaskParameter="ScopedCssProjectBundles" ItemName="_ScopedCssProjectBundles" />
|
||||
</ResolveAllScopedCssAssets>
|
||||
<ItemGroup>
|
||||
<_AllScopedCss Include="@(_ScopedCssProjectBundles);@(_DiscoveredScopedCssFiles)" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- https://github.com/dotnet/project-system/blob/master/docs/up-to-date-check.md -->
|
||||
<ItemGroup>
|
||||
<UpToDateCheckInput Include="@(_AllScopedCss)" />
|
||||
<UpToDateCheckBuilt Include="$(_ScopedCssOutputFullPath)" />
|
||||
<UpToDateCheckBuilt Include="$(_ScopedCssProjectOutputFullPath)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Target>
|
||||
|
||||
<!-- This target is only called as part of GetCurrentProjectStaticWebAssets which is only invoked on referenced projects to get the list
|
||||
of their assets. We return the list of css outputs we will produce and let the main app do the final bundling. -->
|
||||
|
||||
<Target Name="_AddGeneratedScopedCssFiles" DependsOnTargets="_ResolveScopedCssOutputs">
|
||||
<PropertyGroup>
|
||||
<StaticWebAssetBasePath Condition="$(StaticWebAssetBasePath) == ''">_content/$(PackageId)</StaticWebAssetBasePath>
|
||||
</PropertyGroup>
|
||||
<Target Name="_AddGeneratedScopedCssFiles" DependsOnTargets="_ResolveScopedCssOutputs;ResolveStaticWebAssetsConfiguration">
|
||||
<!-- We do this in two steps because we will be modifying the list of static web assets if we bundle the files and these assets need to be
|
||||
available when called from GetCurrentProjectStaticWebAssets -->
|
||||
<ItemGroup>
|
||||
<StaticWebAsset Include="%(_ScopedCss.OutputFile)" Condition="@(_ScopedCss) != ''">
|
||||
<_ScopedCssStaticWebAsset Include="%(_ScopedCss.OutputFile)" Condition="@(_ScopedCss) != ''">
|
||||
<SourceType></SourceType>
|
||||
<SourceId>$(PackageId)</SourceId>
|
||||
<ContentRoot>$(IntermediateOutputPath)scopedcss\</ContentRoot>
|
||||
<BasePath>$(StaticWebAssetBasePath)</BasePath>
|
||||
<RelativePath>$([MSBuild]::MakeRelative('$(_ScopedCssIntermediatePath)','%(_ScopedCss.OutputFile)'))</RelativePath>
|
||||
</StaticWebAsset>
|
||||
</_ScopedCssStaticWebAsset>
|
||||
<StaticWebAsset Include="@(_ScopedCssStaticWebAsset)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="IncludeScopedCssBundle" Condition="'$(ScopedCssDisableBundling)' != 'true'" DependsOnTargets="_CollectAllScopedCssAssets;_AddGeneratedScopedCssFiles">
|
||||
<PropertyGroup>
|
||||
<_ScopedCssOutputPath>$(_ScopedCssIntermediatePath)_framework\scoped.styles.css</_ScopedCssOutputPath>
|
||||
<_ScopedCssOutputFullPath>$([System.IO.Path]::Combine('$(MSBuildProjectFileDirectory)', '$(_ScopedCssIntermediatePath)_framework\scoped.styles.css'))</_ScopedCssOutputFullPath>
|
||||
</PropertyGroup>
|
||||
<Target Name="_AddGeneratedScopedCssFilesForReference" Condition="'$(DisableScopedCssBundling)' != 'true'" DependsOnTargets="_PrepareForBundling;_AddGeneratedScopedCssFiles">
|
||||
<ItemGroup>
|
||||
<!-- When bundling is enabled we want to remove all identified generated scoped css files from the list of static web assets so that
|
||||
they are not copied to the output folder. -->
|
||||
<StaticWebAsset Remove="@(_AllScopedCss)" Condition="'$(ScopedCssDisableBundling)' != 'true'" />
|
||||
<!-- https://github.com/dotnet/aspnetcore/issues/24245 -->
|
||||
<StaticWebAsset Include="$(_ScopedCssOutputPath)" Condition="@(_AllScopedCss) != ''">
|
||||
<SourceType></SourceType>
|
||||
<SourceId>$(PackageId)</SourceId>
|
||||
<ContentRoot>$(_ScopedCssIntermediatePath)</ContentRoot>
|
||||
<BasePath>$(StaticWebAssetBasePath)</BasePath>
|
||||
<RelativePath>_framework/scoped.styles.css</RelativePath>
|
||||
</StaticWebAsset>
|
||||
<_ExternalStaticWebAsset Include="$(_ScopedCssOutputPath)" Condition="@(_AllScopedCss) != ''">
|
||||
<SourceType>generated</SourceType>
|
||||
<SourceId>$(PackageId)</SourceId>
|
||||
<ContentRoot>$(_ScopedCssIntermediatePath)</ContentRoot>
|
||||
<BasePath>$(StaticWebAssetBasePath)</BasePath>
|
||||
<RelativePath>_framework/scoped.styles.css</RelativePath>
|
||||
</_ExternalStaticWebAsset>
|
||||
<StaticWebAsset Remove="@(_ScopedCssStaticWebAsset)" />
|
||||
<Staticwebasset Remove="$(_ScopedCssOutputPath)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="BundleScopedCssFiles" Condition="'$(ScopedCssDisableBundling)' != 'true' and '@(_AllScopedCss)' != ''" BeforeTargets="GetCopyToOutputDirectoryItems;_StaticWebAssetsComputeFilesToPublish" DependsOnTargets="_GenerateScopedCssFiles">
|
||||
<Target Name="BundleScopedCssFiles" Condition="'$(DisableScopedCssBundling)' != 'true'">
|
||||
<!-- Incrementalism is built into the task itself. -->
|
||||
<ConcatenateCssFiles FilesToProcess="@(_AllScopedCss)" OutputFile="$(_ScopedCssOutputPath)" />
|
||||
</Target>
|
||||
|
||||
<Target Name="_RemoveBundleFromOutput" BeforeTargets="GetCopyToOutputDirectoryItems" DependsOnTargets="BundleScopedCssFiles">
|
||||
<ItemGroup>
|
||||
<StaticWebAsset Remove="$(_ScopedCssOutputFullPath)" />
|
||||
<StaticWebAsset Include="@(_AllScopedCss)" Condition="'%(SourceType)' == ''" />
|
||||
<_CurrentProjectDiscoveredScopedCssFiles Include="@(_DiscoveredScopedCssFiles)" Condition="'%(SourceType)' == ''" />
|
||||
</ItemGroup>
|
||||
<!-- This is the bundle for the app, we will always generate it when there are scoped css files for the current project or
|
||||
we detected existing bundles available. If some other project/package didn't bundle their assets, we will not be including
|
||||
them in this bundle. -->
|
||||
<ConcatenateCssFiles
|
||||
Condition="'@(_ScopedCssProjectBundles)' != '' or '@(_ScopedCss)' != ''"
|
||||
ScopedCssFiles="@(_CurrentProjectDiscoveredScopedCssFiles)"
|
||||
ProjectBundles="@(_ScopedCssProjectBundles)"
|
||||
ScopedCssBundleBasePath="$(_ScopedCssBundleBasePath)"
|
||||
OutputFile="$(_ScopedCssOutputPath)" />
|
||||
<!-- This is the project bundle, we will only generate it when there are scoped files defined in the project. This bundle will be used
|
||||
when the project is referenced from another project or packed as a package (Razor Class Library). If some other project/package
|
||||
didn't bundle their assets, we will not be including them in this bundle. -->
|
||||
<ConcatenateCssFiles
|
||||
Condition="'@(_ScopedCss)' != ''"
|
||||
ScopedCssFiles="@(_CurrentProjectDiscoveredScopedCssFiles)"
|
||||
ProjectBundles="@()"
|
||||
ScopedCssBundleBasePath="$(_ScopedCssBundleBasePath)"
|
||||
OutputFile="$(_ScopedCssProjectOutputPath)" />
|
||||
|
||||
<ItemGroup>
|
||||
<FileWrites Include="$(_ScopedCssOutputPath)" />
|
||||
<FileWrites Condition="'@(_ScopedCss)' != ''" Include="$(_ScopedCssProjectOutputPath)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="_AddBundleToStaticWebAssetsPublishedFile" Condition="'$(ScopedCssDisableBundling)' != 'true' and '@(_AllScopedCss)' != ''" BeforeTargets="_StaticWebAssetsComputeFilesToPublish" DependsOnTargets="_CollectAllScopedCssAssets">
|
||||
<Target Name="_AddAppBundleToStaticWebAssetsPublishedFiles" BeforeTargets="_StaticWebAssetsComputeFilesToPublish" Condition="'$(DisableScopedCssBundling)' != 'true' and '@(_AllScopedCss)' != ''" DependsOnTargets="_CollectAllScopedCssAssets">
|
||||
<ItemGroup>
|
||||
<!-- Manually add the file to the publish flow. See https://github.com/dotnet/aspnetcore/issues/24245 -->
|
||||
<_ExternalPublishStaticWebAsset Include="$(_ScopedCssOutputFullPath)" ExcludeFromSingleFile="true">
|
||||
<SourceType>generated</SourceType>
|
||||
<SourceId>$(PackageId)</SourceId>
|
||||
<ContentRoot>$(_ScopedCssIntermediatePath)</ContentRoot>
|
||||
<BasePath>$(StaticWebAssetBasePath)</BasePath>
|
||||
<ContentRoot>$(_ScopedCssBundleContentRoot)</ContentRoot>
|
||||
<BasePath>$(_ScopedCssBundleBasePath)</BasePath>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
<RelativePath>$([MSBuild]::MakeRelative('$(MSBuildProjectDirectory)',$([MSBuild]::NormalizePath('wwwroot/$(StaticWebAssetBasePath)/_framework/scoped.styles.css'))))</RelativePath>
|
||||
<RelativePath>$([MSBuild]::MakeRelative('$(MSBuildProjectDirectory)',$([MSBuild]::NormalizePath('wwwroot/$(_ScopedCssBundleBasePath)/$(PackageId).styles.css'))))</RelativePath>
|
||||
</_ExternalPublishStaticWebAsset>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="_AdjustIsolatedCssPackageContents" BeforeTargets="_RemoveWebRootContentFromPackaging;_CreateStaticWebAssetsCustomPropsCacheFile" DependsOnTargets="_CollectAllScopedCssAssets">
|
||||
<Target Name="_AddScopedCssFilesToStaticWebAssetsPublishedFiles" BeforeTargets="_StaticWebAssetsComputeFilesToPublish" Condition="'$(DisableScopedCssBundling)' == 'true' and '@(_ScopedCss)' != ''" DependsOnTargets="_CollectAllScopedCssAssets">
|
||||
<ItemGroup>
|
||||
<_CurrentProjectStaticWebAsset Remove="$(_ScopedCssOutputFullPath)" />
|
||||
<StaticWebAsset Remove="$(_ScopedCssOutputFullPath)" />
|
||||
<StaticWebAsset Include="@(_AllScopedCss)" Condition="'%(SourceType)' == ''" />
|
||||
<!-- Manually add the scoped css files to the publish flow. See https://github.com/dotnet/aspnetcore/issues/24245 -->
|
||||
<_ExternalPublishStaticWebAsset Include="@(_ScopedCssStaticWebAsset)" ExcludeFromSingleFile="true">
|
||||
<SourceType>generated</SourceType>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
<RelativePath>$([MSBuild]::MakeRelative('$(MSBuildProjectDirectory)','$([MSBuild]::NormalizePath('wwwroot\%(BasePath)\%(RelativePath)'))'))</RelativePath>
|
||||
</_ExternalPublishStaticWebAsset>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
|
|
|
|||
|
|
@ -50,8 +50,8 @@ Copyright (c) .NET Foundation. All rights reserved.
|
|||
<PropertyGroup>
|
||||
<GenerateStaticWebAssetsManifestDependsOn>
|
||||
ResolveStaticWebAssetsInputs;
|
||||
_CreateStaticWebAssetsInputsCacheFile;
|
||||
$(GenerateStaticWebAssetsManifestDependsOn)
|
||||
_CreateStaticWebAssetsInputsCacheFile;
|
||||
</GenerateStaticWebAssetsManifestDependsOn>
|
||||
|
||||
<GetCurrentProjectStaticWebAssetsDependsOn>
|
||||
|
|
@ -59,11 +59,21 @@ Copyright (c) .NET Foundation. All rights reserved.
|
|||
$(GetCurrentProjectStaticWebAssetsDependsOn)
|
||||
</GetCurrentProjectStaticWebAssetsDependsOn>
|
||||
|
||||
<GetCopyToOutputDirectoryItemsDependsOn>
|
||||
$(GetCopyToOutputDirectoryItemsDependsOn);
|
||||
<PrepareForRunDependsOn>
|
||||
GenerateStaticWebAssetsManifest;
|
||||
$(PrepareForRunDependsOn);
|
||||
</PrepareForRunDependsOn>
|
||||
|
||||
<GetCopyToOutputDirectoryItemsDependsOn>
|
||||
_IncludeGeneratedStaticWebAssetsManifest;
|
||||
$(GetCopyToOutputDirectoryItemsDependsOn);
|
||||
</GetCopyToOutputDirectoryItemsDependsOn>
|
||||
|
||||
<ResolveCurrentProjectStaticWebAssetsInputsDependsOn>
|
||||
ResolveStaticWebAssetsConfiguration;
|
||||
$(ResolveCurrentProjectStaticWebAssetsInputsDependsOn)
|
||||
</ResolveCurrentProjectStaticWebAssetsInputsDependsOn>
|
||||
|
||||
<ResolveStaticWebAssetsInputsDependsOn>
|
||||
ResolveCurrentProjectStaticWebAssetsInputs;
|
||||
ResolveReferencedProjectsStaticWebAssets;
|
||||
|
|
@ -76,8 +86,9 @@ Copyright (c) .NET Foundation. All rights reserved.
|
|||
</ResolveReferencedProjectsStaticWebAssetsDependsOn>
|
||||
|
||||
<GenerateStaticWebAssetsPackTargetsDependsOn>
|
||||
ResolveStaticWebAssetsInputs;
|
||||
$(GenerateStaticWebAssetsPackTargetsDependsOn);
|
||||
_CreateStaticWebAssetsCustomPropsCacheFile;
|
||||
$(GenerateStaticWebAssetsPackTargetsDependsOn)
|
||||
</GenerateStaticWebAssetsPackTargetsDependsOn>
|
||||
|
||||
<TargetsForTfmSpecificContentInPackage>
|
||||
|
|
@ -124,6 +135,12 @@ Copyright (c) .NET Foundation. All rights reserved.
|
|||
Condition="!Exists('$(_StaticWebAssetsIntermediateOutputPath)')" />
|
||||
</Target>
|
||||
|
||||
<Target Name="ResolveStaticWebAssetsConfiguration">
|
||||
<PropertyGroup>
|
||||
<StaticWebAssetBasePath Condition="'$(StaticWebAssetBasePath)' == ''">_content/$(PackageId)</StaticWebAssetBasePath>
|
||||
</PropertyGroup>
|
||||
</Target>
|
||||
|
||||
<!--
|
||||
============================================================
|
||||
Static web assets development manifest generation targets
|
||||
|
|
@ -196,22 +213,25 @@ Copyright (c) .NET Foundation. All rights reserved.
|
|||
ContentRootDefinitions="@(_ExternalStaticWebAsset)"
|
||||
TargetManifestPath="$(_GeneratedStaticWebAssetsDevelopmentManifest)" />
|
||||
|
||||
<ItemGroup>
|
||||
<FileWrites Include="$(_GeneratedStaticWebAssetsDevelopmentManifest)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Target>
|
||||
|
||||
<Target Name="_IncludeGeneratedStaticWebAssetsManifest">
|
||||
<!-- This is the list of inputs that will be used for generating the manifest used during development. -->
|
||||
<ItemGroup>
|
||||
<ContentWithTargetPath
|
||||
Include="$(_GeneratedStaticWebAssetsDevelopmentManifest)"
|
||||
Condition="'@(_ExternalStaticWebAsset->Count())' != '0'">
|
||||
|
||||
<Pack>false</Pack>
|
||||
<TargetPath>$(TargetName).StaticWebAssets.xml</TargetPath>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
|
||||
</ContentWithTargetPath>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FileWrites Include="$(_GeneratedStaticWebAssetsDevelopmentManifest)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Target>
|
||||
|
||||
<!--
|
||||
|
|
@ -275,11 +295,6 @@ Copyright (c) .NET Foundation. All rights reserved.
|
|||
</Target>
|
||||
|
||||
<Target Name="ResolveCurrentProjectStaticWebAssetsInputs" DependsOnTargets="$(ResolveCurrentProjectStaticWebAssetsInputsDependsOn)">
|
||||
|
||||
<PropertyGroup>
|
||||
<StaticWebAssetBasePath Condition="$(StaticWebAssetBasePath) == ''">_content/$(PackageId)</StaticWebAssetBasePath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
<_ThisProjectStaticWebAsset
|
||||
|
|
@ -356,7 +371,7 @@ Copyright (c) .NET Foundation. All rights reserved.
|
|||
|
||||
<Target
|
||||
Name="_CreateStaticWebAssetsCustomPropsCacheFile"
|
||||
DependsOnTargets="ResolveStaticWebAssetsInputs;_PrepareForStaticWebAssets">
|
||||
DependsOnTargets="_PrepareForStaticWebAssets">
|
||||
|
||||
<ItemGroup>
|
||||
<!-- This is the list of inputs that will be used for generating the props file that will be packed with
|
||||
|
|
@ -409,6 +424,11 @@ Copyright (c) .NET Foundation. All rights reserved.
|
|||
<_CurrentProjectHasStaticWebAssets Condition="'@(_CurrentProjectStaticWebAsset->Count())' != '0'">true</_CurrentProjectHasStaticWebAssets>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Validate that there are no path conflicts between the assets we are about to pack. -->
|
||||
<ValidateStaticWebAssetsUniquePaths Condition="'$(_CurrentProjectHasStaticWebAssets)' == 'true'"
|
||||
StaticWebAssets="@(_CurrentProjectStaticWebAsset)"
|
||||
WebRootFiles="" />
|
||||
|
||||
<!-- Generates a props file that goes in build\Microsoft.AspNetCore.StaticWebAssets.props
|
||||
and that describes the static web assets for the package.
|
||||
-->
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ Copyright (c) .NET Foundation. All rights reserved.
|
|||
<StaticWebAssetsEnabled Condition="'$(StaticWebAssetsEnabled)' == ''">$(_Targeting30OrNewerRazorLangVersion)</StaticWebAssetsEnabled>
|
||||
|
||||
<!-- Controls whether or not the scoped css feature is enabled. By default is enabled for net5.0 applications and RazorLangVersion 5 or above -->
|
||||
<ScopedCssEnabled Condition="'$(ScopedCssEnabled)' == ''">$(_Targeting30OrNewerRazorLangVersion)</ScopedCssEnabled>
|
||||
<ScopedCssEnabled Condition="'$(ScopedCssEnabled)' == '' and '$(StaticWebAssetsEnabled)' == 'true'">$(_Targeting30OrNewerRazorLangVersion)</ScopedCssEnabled>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
|
|
@ -352,10 +352,10 @@ Copyright (c) .NET Foundation. All rights reserved.
|
|||
|
||||
<Import Project="Microsoft.NET.Sdk.Razor.Component.targets" Condition="'$(_Targeting30OrNewerRazorLangVersion)' == 'true'" />
|
||||
|
||||
<Import Project="Microsoft.NET.Sdk.Razor.StaticWebAssets.targets" Condition="'$(StaticWebAssetsEnabled)' == 'true'" />
|
||||
|
||||
<Import Project="Microsoft.NET.Sdk.Razor.ScopedCss.targets" Condition="'$(ScopedCssEnabled)' == 'true'" />
|
||||
|
||||
<Import Project="Microsoft.NET.Sdk.Razor.StaticWebAssets.targets" Condition="'$(StaticWebAssetsEnabled)' == 'true'" />
|
||||
|
||||
<Import Project="Microsoft.NET.Sdk.Razor.GenerateAssemblyInfo.targets" />
|
||||
|
||||
<Import Project="Microsoft.NET.Sdk.Razor.MvcApplicationPartsDiscovery.targets" Condition="'$(_TargetingNETCoreApp30OrLater)' == 'true'" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,160 @@
|
|||
// 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.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Razor.Tasks;
|
||||
using Microsoft.Build.Framework;
|
||||
using Microsoft.Build.Utilities;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.NET.Sdk.Razor.Test
|
||||
{
|
||||
public class ApplyAllCssScopesTest
|
||||
{
|
||||
[Fact]
|
||||
public void ApplyAllCssScopes_AppliesScopesToRazorFiles()
|
||||
{
|
||||
// Arrange
|
||||
var taskInstance = new ApplyCssScopes()
|
||||
{
|
||||
RazorComponents = new[]
|
||||
{
|
||||
new TaskItem("TestFiles/Pages/Counter.razor"),
|
||||
new TaskItem("TestFiles/Pages/Index.razor"),
|
||||
},
|
||||
ScopedCss = new[]
|
||||
{
|
||||
new TaskItem("TestFiles/Pages/Index.razor.css", new Dictionary<string, string> { ["CssScope"] = "index-scope" }),
|
||||
new TaskItem("TestFiles/Pages/Counter.razor.css", new Dictionary<string, string> { ["CssScope"] = "counter-scope" }),
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = taskInstance.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.Equal(2, taskInstance.RazorComponentsWithScopes.Length);
|
||||
Assert.Single(taskInstance.RazorComponentsWithScopes, rcws => rcws.ItemSpec == "TestFiles/Pages/Index.razor" && rcws.GetMetadata("CssScope") == "index-scope");
|
||||
Assert.Single(taskInstance.RazorComponentsWithScopes, rcws => rcws.ItemSpec == "TestFiles/Pages/Counter.razor" && rcws.GetMetadata("CssScope") == "counter-scope");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DoesNotApplyCssScopes_ToRazorComponentsWithoutAssociatedFiles()
|
||||
{
|
||||
// Arrange
|
||||
var taskInstance = new ApplyCssScopes()
|
||||
{
|
||||
RazorComponents = new[]
|
||||
{
|
||||
new TaskItem("TestFiles/Pages/Counter.razor"),
|
||||
new TaskItem("TestFiles/Pages/Index.razor"),
|
||||
new TaskItem("TestFiles/Pages/FetchData.razor"),
|
||||
},
|
||||
ScopedCss = new[]
|
||||
{
|
||||
new TaskItem("TestFiles/Pages/Index.razor.css", new Dictionary<string, string> { ["CssScope"] = "index-scope" }),
|
||||
new TaskItem("TestFiles/Pages/Counter.razor.css", new Dictionary<string, string> { ["CssScope"] = "counter-scope" })
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = taskInstance.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.DoesNotContain(taskInstance.RazorComponentsWithScopes, rcws => rcws.ItemSpec == "TestFiles/Pages/Fetchdata.razor");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApplyAllCssScopes_FailsWhenTheScopedCss_DoesNotMatchTheRazorComponent()
|
||||
{
|
||||
// Arrange
|
||||
var taskInstance = new ApplyCssScopes()
|
||||
{
|
||||
RazorComponents = new[]
|
||||
{
|
||||
new TaskItem("TestFiles/Pages/Counter.razor"),
|
||||
new TaskItem("TestFiles/Pages/Index.razor"),
|
||||
},
|
||||
ScopedCss = new[]
|
||||
{
|
||||
new TaskItem("TestFiles/Pages/Index.razor.css", new Dictionary<string, string> { ["CssScope"] = "index-scope" }),
|
||||
new TaskItem("TestFiles/Pages/Counter.razor.css", new Dictionary<string, string> { ["CssScope"] = "counter-scope" }),
|
||||
new TaskItem("TestFiles/Pages/Profile.razor.css", new Dictionary<string, string> { ["CssScope"] = "profile-scope" }),
|
||||
}
|
||||
};
|
||||
|
||||
taskInstance.BuildEngine = Mock.Of<IBuildEngine>();
|
||||
|
||||
// Act
|
||||
var result = taskInstance.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScopedCssCanDefineAssociatedRazorComponentFile()
|
||||
{
|
||||
// Arrange
|
||||
var taskInstance = new ApplyCssScopes()
|
||||
{
|
||||
RazorComponents = new[]
|
||||
{
|
||||
new TaskItem("TestFiles/Pages/FetchData.razor")
|
||||
},
|
||||
ScopedCss = new[]
|
||||
{
|
||||
new TaskItem("TestFiles/Pages/Profile.razor.css", new Dictionary<string, string>
|
||||
{
|
||||
["CssScope"] = "fetchdata-scope",
|
||||
["RazorComponent"] = "TestFiles/Pages/FetchData.razor"
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = taskInstance.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
var rcws = Assert.Single(taskInstance.RazorComponentsWithScopes);
|
||||
Assert.Equal("TestFiles/Pages/FetchData.razor", rcws.ItemSpec);
|
||||
Assert.Equal("fetchdata-scope", rcws.GetMetadata("CssScope"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApplyAllCssScopes_FailsWhenMultipleScopedCssFiles_MatchTheSameRazorComponent()
|
||||
{
|
||||
// Arrange
|
||||
var taskInstance = new ApplyCssScopes()
|
||||
{
|
||||
RazorComponents = new[]
|
||||
{
|
||||
new TaskItem("TestFiles/Pages/Counter.razor"),
|
||||
new TaskItem("TestFiles/Pages/Index.razor"),
|
||||
},
|
||||
ScopedCss = new[]
|
||||
{
|
||||
new TaskItem("TestFiles/Pages/Index.razor.css", new Dictionary<string, string> { ["CssScope"] = "index-scope" }),
|
||||
new TaskItem("TestFiles/Pages/Counter.razor.css", new Dictionary<string, string> { ["CssScope"] = "counter-scope" }),
|
||||
new TaskItem("TestFiles/Pages/Profile.razor.css", new Dictionary<string, string>
|
||||
{
|
||||
["CssScope"] = "conflict-scope",
|
||||
["RazorComponent"] = "TestFiles/Pages/Index.razor"
|
||||
}),
|
||||
}
|
||||
};
|
||||
|
||||
taskInstance.BuildEngine = Mock.Of<IBuildEngine>();
|
||||
|
||||
// Act
|
||||
var result = taskInstance.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Build.Utilities;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Tasks
|
||||
{
|
||||
public class ComputeCssScopesTests
|
||||
{
|
||||
[Fact]
|
||||
public void ComputesScopes_ComputesUniqueScopes_ForCssFiles()
|
||||
{
|
||||
// Arrange
|
||||
var taskInstance = new ComputeCssScope()
|
||||
{
|
||||
ScopedCssInput = new[]
|
||||
{
|
||||
new TaskItem("TestFiles/Pages/Counter.razor.css"),
|
||||
new TaskItem("TestFiles/Pages/Index.razor.css"),
|
||||
new TaskItem("TestFiles/Pages/Profile.razor.css"),
|
||||
},
|
||||
TargetName = "Test"
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = taskInstance.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.Equal(3, taskInstance.ScopedCss.Length);
|
||||
Assert.All(taskInstance.ScopedCss, item =>
|
||||
{
|
||||
var scope = item.GetMetadata("CssScope");
|
||||
Assert.NotEmpty(scope);
|
||||
Assert.Matches("b-[a-z0-9]+", scope);
|
||||
});
|
||||
|
||||
Assert.Equal(3, new HashSet<string>(taskInstance.ScopedCss.Select(s => s.GetMetadata("CssScope"))).Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputesScopes_ScopeVariesByTargetName()
|
||||
{
|
||||
// Arrange
|
||||
var taskInstance = new ComputeCssScope()
|
||||
{
|
||||
ScopedCssInput = new[]
|
||||
{
|
||||
new TaskItem("TestFiles/Pages/Counter.razor.css"),
|
||||
new TaskItem("TestFiles/Pages/Index.razor.css"),
|
||||
new TaskItem("TestFiles/Pages/Profile.razor.css"),
|
||||
},
|
||||
TargetName = "Test"
|
||||
};
|
||||
|
||||
// Act
|
||||
taskInstance.Execute();
|
||||
var existing = taskInstance.ScopedCss.Select(s => s.GetMetadata("CssScope")).ToArray();
|
||||
|
||||
taskInstance.TargetName = "AnotherLibrary";
|
||||
var result = taskInstance.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.All(taskInstance.ScopedCss, newScoped => Assert.DoesNotContain(newScoped.GetMetadata("ScopedCss"), existing));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputesScopes_IsDeterministic()
|
||||
{
|
||||
// Arrange
|
||||
var taskInstance = new ComputeCssScope()
|
||||
{
|
||||
ScopedCssInput = new[]
|
||||
{
|
||||
new TaskItem("TestFiles/Pages/Counter.razor.css"),
|
||||
new TaskItem("TestFiles/Pages/Index.razor.css"),
|
||||
new TaskItem("TestFiles/Pages/Profile.razor.css"),
|
||||
},
|
||||
TargetName = "Test"
|
||||
};
|
||||
|
||||
// Act
|
||||
taskInstance.Execute();
|
||||
var existing = taskInstance.ScopedCss.Select(s => s.GetMetadata("CssScope")).OrderBy(id => id).ToArray();
|
||||
|
||||
var result = taskInstance.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(existing, taskInstance.ScopedCss.Select(newScoped => newScoped.GetMetadata("CssScope")).OrderBy(id => id).ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputesScopes_VariesByPath()
|
||||
{
|
||||
// Arrange
|
||||
var taskInstance = new ComputeCssScope()
|
||||
{
|
||||
ScopedCssInput = new[]
|
||||
{
|
||||
new TaskItem("TestFiles/Pages/Index.razor.css"),
|
||||
new TaskItem("TestFiles/Index.razor.css"),
|
||||
},
|
||||
TargetName = "Test"
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = taskInstance.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.Equal(2, taskInstance.ScopedCss.Length);
|
||||
Assert.NotEqual(taskInstance.ScopedCss[0].GetMetadata("CssScope"), taskInstance.ScopedCss[1].GetMetadata("CssScope"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputesScopes_PreservesUserDefinedScopes()
|
||||
{
|
||||
// Arrange
|
||||
var taskInstance = new ComputeCssScope()
|
||||
{
|
||||
ScopedCssInput = new[]
|
||||
{
|
||||
new TaskItem("TestFiles/Pages/Index.razor.css", new Dictionary<string,string>{ ["CssScope"] = "b-predefined" }), },
|
||||
TargetName = "Test"
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = taskInstance.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
var scopedCss = Assert.Single(taskInstance.ScopedCss);
|
||||
Assert.Equal("b-predefined", scopedCss.GetMetadata("CssScope"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,394 @@
|
|||
// 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 Microsoft.Build.Framework;
|
||||
using Microsoft.Build.Utilities;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Tasks
|
||||
{
|
||||
public class ConcatenateCssFilesTest
|
||||
{
|
||||
private static readonly string BundleContent =
|
||||
@"/* _content/Test/TestFiles/Generated/Counter.razor.rz.scp.css */
|
||||
.counter {
|
||||
font-size: 2rem;
|
||||
}
|
||||
/* _content/Test/TestFiles/Generated/Index.razor.rz.scp.css */
|
||||
.index {
|
||||
font-weight: bold;
|
||||
}
|
||||
";
|
||||
|
||||
private static readonly string BundleWithImportsContent =
|
||||
@"@import '_content/Test/TestFiles/Generated/lib.bundle.scp.css';
|
||||
@import 'TestFiles/Generated/package.bundle.scp.css';
|
||||
|
||||
/* _content/Test/TestFiles/Generated/Counter.razor.rz.scp.css */
|
||||
.counter {
|
||||
font-size: 2rem;
|
||||
}
|
||||
/* _content/Test/TestFiles/Generated/Index.razor.rz.scp.css */
|
||||
.index {
|
||||
font-weight: bold;
|
||||
}
|
||||
";
|
||||
|
||||
private static readonly string UpdatedBundleContent =
|
||||
@"/* _content/Test/TestFiles/Generated/Counter.razor.rz.scp.css */
|
||||
.counter {
|
||||
font-size: 2rem;
|
||||
}
|
||||
/* _content/Test/TestFiles/Generated/FetchData.razor.rz.scp.css */
|
||||
.fetchData {
|
||||
font-family: Helvetica;
|
||||
}
|
||||
/* _content/Test/TestFiles/Generated/Index.razor.rz.scp.css */
|
||||
.index {
|
||||
font-weight: bold;
|
||||
}
|
||||
";
|
||||
|
||||
[Fact]
|
||||
public void BundlesScopedCssFiles_ProducesEmpyBundleIfNoFilesAvailable()
|
||||
{
|
||||
// Arrange
|
||||
var expectedFile = Path.Combine(Directory.GetCurrentDirectory(), $"{Guid.NewGuid():N}.css");
|
||||
var taskInstance = new ConcatenateCssFiles()
|
||||
{
|
||||
ScopedCssFiles = Array.Empty<ITaskItem>(),
|
||||
ProjectBundles = Array.Empty<ITaskItem>(),
|
||||
OutputFile = expectedFile
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = taskInstance.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.True(File.Exists(expectedFile));
|
||||
|
||||
Assert.Empty(File.ReadAllText(expectedFile));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BundlesScopedCssFiles_ProducesBundle()
|
||||
{
|
||||
// Arrange
|
||||
var expectedFile = Path.Combine(Directory.GetCurrentDirectory(), $"{Guid.NewGuid():N}.css");
|
||||
var taskInstance = new ConcatenateCssFiles()
|
||||
{
|
||||
ScopedCssFiles = new[]
|
||||
{
|
||||
new TaskItem(
|
||||
"TestFiles/Generated/Counter.razor.rz.scp.css",
|
||||
new Dictionary<string,string>
|
||||
{
|
||||
["BasePath"] = "_content/Test/",
|
||||
["RelativePath"] = "TestFiles/Generated/Counter.razor.rz.scp.css",
|
||||
}),
|
||||
new TaskItem(
|
||||
"TestFiles/Generated/Index.razor.rz.scp.css",
|
||||
new Dictionary<string,string>
|
||||
{
|
||||
["BasePath"] = "_content/Test/",
|
||||
["RelativePath"] = "TestFiles/Generated/Index.razor.rz.scp.css",
|
||||
}),
|
||||
},
|
||||
ProjectBundles = Array.Empty<ITaskItem>(),
|
||||
OutputFile = expectedFile
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = taskInstance.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.True(File.Exists(expectedFile));
|
||||
|
||||
var actualContents = File.ReadAllText(expectedFile);
|
||||
Assert.Equal(BundleContent, actualContents, ignoreLineEndingDifferences: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BundlesScopedCssFiles_IncludesOtherBundles()
|
||||
{
|
||||
// Arrange
|
||||
var expectedFile = Path.Combine(Directory.GetCurrentDirectory(), $"{Guid.NewGuid():N}.css");
|
||||
var taskInstance = new ConcatenateCssFiles()
|
||||
{
|
||||
ScopedCssFiles = new[]
|
||||
{
|
||||
new TaskItem(
|
||||
"TestFiles/Generated/Counter.razor.rz.scp.css",
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
["BasePath"] = "_content/Test/",
|
||||
["RelativePath"] = "TestFiles/Generated/Counter.razor.rz.scp.css",
|
||||
}),
|
||||
new TaskItem(
|
||||
"TestFiles/Generated/Index.razor.rz.scp.css",
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
["BasePath"] = "_content/Test/",
|
||||
["RelativePath"] = "TestFiles/Generated/Index.razor.rz.scp.css",
|
||||
}),
|
||||
},
|
||||
ProjectBundles = new[]
|
||||
{
|
||||
new TaskItem(
|
||||
"TestFiles/Generated/lib.bundle.scp.css",
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
["BasePath"] = "_content/Test/",
|
||||
["RelativePath"] = "TestFiles/Generated/lib.bundle.scp.css",
|
||||
}),
|
||||
new TaskItem(
|
||||
"TestFiles/Generated/package.bundle.scp.css",
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
["BasePath"] = "",
|
||||
["RelativePath"] = "TestFiles/Generated/package.bundle.scp.css",
|
||||
}),
|
||||
},
|
||||
ScopedCssBundleBasePath = "/",
|
||||
OutputFile = expectedFile
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = taskInstance.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.True(File.Exists(expectedFile));
|
||||
|
||||
var actualContents = File.ReadAllText(expectedFile);
|
||||
Assert.Equal(BundleWithImportsContent, actualContents, ignoreLineEndingDifferences: true);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("", "", "TestFiles/Generated/lib.bundle.scp.css")]
|
||||
[InlineData("/", "/", "TestFiles/Generated/lib.bundle.scp.css")]
|
||||
[InlineData("app", "_content", "../_content/TestFiles/Generated/lib.bundle.scp.css")]
|
||||
[InlineData("app", "/_content", "../_content/TestFiles/Generated/lib.bundle.scp.css")]
|
||||
[InlineData("app", "/_content/", "../_content/TestFiles/Generated/lib.bundle.scp.css")]
|
||||
[InlineData("/app", "_content", "../_content/TestFiles/Generated/lib.bundle.scp.css")]
|
||||
[InlineData("/app", "/_content", "../_content/TestFiles/Generated/lib.bundle.scp.css")]
|
||||
[InlineData("/app", "/_content/", "../_content/TestFiles/Generated/lib.bundle.scp.css")]
|
||||
[InlineData("app/", "_content", "../_content/TestFiles/Generated/lib.bundle.scp.css")]
|
||||
[InlineData("app/", "/_content", "../_content/TestFiles/Generated/lib.bundle.scp.css")]
|
||||
[InlineData("app/", "/_content/", "../_content/TestFiles/Generated/lib.bundle.scp.css")]
|
||||
[InlineData("/company/app/", "_content", "../../_content/TestFiles/Generated/lib.bundle.scp.css")]
|
||||
[InlineData("/company/app/", "/_content", "../../_content/TestFiles/Generated/lib.bundle.scp.css")]
|
||||
[InlineData("/company/app/", "/_content/", "../../_content/TestFiles/Generated/lib.bundle.scp.css")]
|
||||
|
||||
public void BundlesScopedCssFiles_HandlesBasePathCombinationsCorrectly(string finalBasePath, string libraryBasePath, string expectedImport)
|
||||
{
|
||||
// Arrange
|
||||
var expectedContent = BundleWithImportsContent
|
||||
.Replace("_content/Test/TestFiles/Generated/lib.bundle.scp.css", expectedImport)
|
||||
.Replace("@import 'TestFiles/Generated/package.bundle.scp.css';", "")
|
||||
.Replace("\r\n", "\n")
|
||||
.Replace("\n\n", "\n");
|
||||
|
||||
var expectedFile = Path.Combine(Directory.GetCurrentDirectory(), $"{Guid.NewGuid():N}.css");
|
||||
var taskInstance = new ConcatenateCssFiles()
|
||||
{
|
||||
ScopedCssFiles = new[]
|
||||
{
|
||||
new TaskItem(
|
||||
"TestFiles/Generated/Counter.razor.rz.scp.css",
|
||||
new Dictionary<string,string>
|
||||
{
|
||||
["BasePath"] = "_content/Test/",
|
||||
["RelativePath"] = "TestFiles/Generated/Counter.razor.rz.scp.css",
|
||||
}),
|
||||
new TaskItem(
|
||||
"TestFiles/Generated/Index.razor.rz.scp.css",
|
||||
new Dictionary<string,string>
|
||||
{
|
||||
["BasePath"] = "_content/Test/",
|
||||
["RelativePath"] = "TestFiles/Generated/Index.razor.rz.scp.css",
|
||||
})
|
||||
},
|
||||
ProjectBundles = new[]
|
||||
{
|
||||
new TaskItem(
|
||||
"TestFiles/Generated/lib.bundle.scp.css",
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
["BasePath"] = libraryBasePath,
|
||||
["RelativePath"] = "TestFiles/Generated/lib.bundle.scp.css",
|
||||
}),
|
||||
},
|
||||
ScopedCssBundleBasePath = finalBasePath,
|
||||
OutputFile = expectedFile
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = taskInstance.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.True(File.Exists(expectedFile));
|
||||
|
||||
var actualContents = File.ReadAllText(expectedFile);
|
||||
Assert.Equal(expectedContent, actualContents, ignoreLineEndingDifferences: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BundlesScopedCssFiles_BundlesFilesInOrder()
|
||||
{
|
||||
// Arrange
|
||||
var expectedFile = Path.Combine(Directory.GetCurrentDirectory(), $"{Guid.NewGuid():N}.css");
|
||||
var taskInstance = new ConcatenateCssFiles()
|
||||
{
|
||||
ScopedCssFiles = new[]
|
||||
{
|
||||
new TaskItem(
|
||||
"TestFiles/Generated/Index.razor.rz.scp.css",
|
||||
new Dictionary<string,string>
|
||||
{
|
||||
["BasePath"] = "_content/Test/",
|
||||
["RelativePath"] = "TestFiles/Generated/Index.razor.rz.scp.css",
|
||||
}),
|
||||
new TaskItem(
|
||||
"TestFiles/Generated/Counter.razor.rz.scp.css",
|
||||
new Dictionary<string,string>
|
||||
{
|
||||
["BasePath"] = "_content/Test/",
|
||||
["RelativePath"] = "TestFiles/Generated/Counter.razor.rz.scp.css",
|
||||
}),
|
||||
},
|
||||
ProjectBundles = Array.Empty<ITaskItem>(),
|
||||
OutputFile = expectedFile
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = taskInstance.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.True(File.Exists(expectedFile));
|
||||
|
||||
var actualContents = File.ReadAllText(expectedFile);
|
||||
Assert.Equal(BundleContent, actualContents, ignoreLineEndingDifferences: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BundlesScopedCssFiles_DoesNotOverrideBundleForSameContents()
|
||||
{
|
||||
// Arrange
|
||||
var expectedFile = Path.Combine(Directory.GetCurrentDirectory(), $"{Guid.NewGuid():N}.css");
|
||||
var taskInstance = new ConcatenateCssFiles()
|
||||
{
|
||||
ScopedCssFiles = new[]
|
||||
{
|
||||
new TaskItem(
|
||||
"TestFiles/Generated/Index.razor.rz.scp.css",
|
||||
new Dictionary<string,string>
|
||||
{
|
||||
["BasePath"] = "_content/Test/",
|
||||
["RelativePath"] = "TestFiles/Generated/Index.razor.rz.scp.css",
|
||||
}),
|
||||
new TaskItem(
|
||||
"TestFiles/Generated/Counter.razor.rz.scp.css",
|
||||
new Dictionary<string,string>
|
||||
{
|
||||
["BasePath"] = "_content/Test/",
|
||||
["RelativePath"] = "TestFiles/Generated/Counter.razor.rz.scp.css",
|
||||
}),
|
||||
},
|
||||
ProjectBundles = Array.Empty<ITaskItem>(),
|
||||
OutputFile = expectedFile
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = taskInstance.Execute();
|
||||
|
||||
var lastModified = File.GetLastWriteTimeUtc(expectedFile);
|
||||
|
||||
taskInstance.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.True(File.Exists(expectedFile));
|
||||
var actualContents = File.ReadAllText(expectedFile);
|
||||
Assert.Equal(BundleContent, actualContents, ignoreLineEndingDifferences: true);
|
||||
|
||||
Assert.Equal(lastModified, File.GetLastWriteTimeUtc(expectedFile));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BundlesScopedCssFiles_UpdatesBundleWhenContentsChange()
|
||||
{
|
||||
// Arrange
|
||||
var expectedFile = Path.Combine(Directory.GetCurrentDirectory(), $"{Guid.NewGuid():N}.css");
|
||||
var taskInstance = new ConcatenateCssFiles()
|
||||
{
|
||||
ScopedCssFiles = new[]
|
||||
{
|
||||
new TaskItem(
|
||||
"TestFiles/Generated/Index.razor.rz.scp.css",
|
||||
new Dictionary<string,string>
|
||||
{
|
||||
["BasePath"] = "_content/Test/",
|
||||
["RelativePath"] = "TestFiles/Generated/Index.razor.rz.scp.css",
|
||||
}),
|
||||
new TaskItem(
|
||||
"TestFiles/Generated/Counter.razor.rz.scp.css",
|
||||
new Dictionary<string,string>
|
||||
{
|
||||
["BasePath"] = "_content/Test/",
|
||||
["RelativePath"] = "TestFiles/Generated/Counter.razor.rz.scp.css",
|
||||
}),
|
||||
},
|
||||
ProjectBundles = Array.Empty<ITaskItem>(),
|
||||
OutputFile = expectedFile
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = taskInstance.Execute();
|
||||
|
||||
var lastModified = File.GetLastWriteTimeUtc(expectedFile);
|
||||
|
||||
taskInstance.ScopedCssFiles = new[]
|
||||
{
|
||||
new TaskItem(
|
||||
"TestFiles/Generated/Index.razor.rz.scp.css",
|
||||
new Dictionary<string,string>
|
||||
{
|
||||
["BasePath"] = "_content/Test/",
|
||||
["RelativePath"] = "TestFiles/Generated/Index.razor.rz.scp.css",
|
||||
}),
|
||||
new TaskItem(
|
||||
"TestFiles/Generated/Counter.razor.rz.scp.css",
|
||||
new Dictionary<string,string>
|
||||
{
|
||||
["BasePath"] = "_content/Test/",
|
||||
["RelativePath"] = "TestFiles/Generated/Counter.razor.rz.scp.css",
|
||||
}),
|
||||
new TaskItem(
|
||||
"TestFiles/Generated/FetchData.razor.rz.scp.css",
|
||||
new Dictionary<string,string>
|
||||
{
|
||||
["BasePath"] = "_content/Test/",
|
||||
["RelativePath"] = "TestFiles/Generated/FetchData.razor.rz.scp.css",
|
||||
}),
|
||||
};
|
||||
|
||||
taskInstance.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.True(File.Exists(expectedFile));
|
||||
var actualContents = File.ReadAllText(expectedFile);
|
||||
Assert.Equal(UpdatedBundleContent, actualContents, ignoreLineEndingDifferences: true);
|
||||
|
||||
Assert.NotEqual(lastModified, File.GetLastWriteTimeUtc(expectedFile));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
// 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.Collections.Generic;
|
||||
using Microsoft.Build.Utilities;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Tasks
|
||||
{
|
||||
public class DiscoverDefaultScopedCssItemsTests
|
||||
{
|
||||
[Fact]
|
||||
public void DiscoversScopedCssFiles_BasedOnTheirExtension()
|
||||
{
|
||||
// Arrange
|
||||
var taskInstance = new DiscoverDefaultScopedCssItems()
|
||||
{
|
||||
Content = new[]
|
||||
{
|
||||
new TaskItem("TestFiles/Pages/Counter.razor.css"),
|
||||
new TaskItem("TestFiles/Pages/Index.razor.css"),
|
||||
new TaskItem("TestFiles/Pages/Profile.razor.css"),
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = taskInstance.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.Equal(3, taskInstance.DiscoveredScopedCssInputs.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DiscoversScopedCssFiles_SkipsFilesWithScopedAttributeWithAFalseValue()
|
||||
{
|
||||
// Arrange
|
||||
var taskInstance = new DiscoverDefaultScopedCssItems()
|
||||
{
|
||||
Content = new[]
|
||||
{
|
||||
new TaskItem("TestFiles/Pages/Counter.razor.css"),
|
||||
new TaskItem("TestFiles/Pages/Index.razor.css"),
|
||||
new TaskItem("TestFiles/Pages/Profile.razor.css", new Dictionary<string,string>{ ["Scoped"] = "false" }),
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = taskInstance.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.Equal(2, taskInstance.DiscoveredScopedCssInputs.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// 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.Collections.Generic;
|
||||
|
|
@ -72,55 +72,14 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
|||
Assert.Equal($"Missing required metadata 'ContentRoot' for '{Path.Combine("wwwroot", "sample.js")}'.", message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReturnsError_ForDuplicateBasePaths()
|
||||
{
|
||||
// Arrange
|
||||
var errorMessages = new List<string>();
|
||||
var buildEngine = new Mock<IBuildEngine>();
|
||||
buildEngine.Setup(e => e.LogErrorEvent(It.IsAny<BuildErrorEventArgs>()))
|
||||
.Callback<BuildErrorEventArgs>(args => errorMessages.Add(args.Message));
|
||||
|
||||
var task = new GenerateStaticWebAssetsManifest
|
||||
{
|
||||
BuildEngine = buildEngine.Object,
|
||||
ContentRootDefinitions = new TaskItem[]
|
||||
{
|
||||
CreateItem(Path.Combine("wwwroot","sample.js"), new Dictionary<string,string>
|
||||
{
|
||||
["BasePath"] = "MyLibrary",
|
||||
["ContentRoot"] = Path.Combine("nuget", "MyLibrary"),
|
||||
["SourceId"] = "MyLibrary"
|
||||
}),
|
||||
CreateItem(Path.Combine("wwwroot", "otherLib.js"), new Dictionary<string,string>
|
||||
{
|
||||
["BasePath"] = "MyLibrary",
|
||||
["ContentRoot"] = Path.Combine("nuget", "MyOtherLibrary"),
|
||||
["SourceId"] = "MyOtherLibrary"
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = task.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
var message = Assert.Single(errorMessages);
|
||||
Assert.Equal(
|
||||
$"Duplicate base paths 'MyLibrary' for content root paths '{Path.Combine("nuget", "MyOtherLibrary")}' and '{Path.Combine("nuget", "MyLibrary")}'. " +
|
||||
$"('{Path.Combine("wwwroot", "otherLib.js")}', '{Path.Combine("wwwroot", "sample.js")}')",
|
||||
message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllowsMultipleContentRootsWithSameBasePath_ForTheSameSourceId()
|
||||
{
|
||||
// Arrange
|
||||
var file = Path.GetTempFileName();
|
||||
var expectedDocument = $@"<StaticWebAssets Version=""1.0"">
|
||||
<ContentRoot BasePath=""Blazor.Client"" Path=""{Path.Combine(".", "nuget", $"Blazor.Client{Path.DirectorySeparatorChar}")}"" />
|
||||
<ContentRoot BasePath=""Blazor.Client"" Path=""{Path.Combine(".", "nuget", "bin", "debug", $"netstandard2.1{Path.DirectorySeparatorChar}")}"" />
|
||||
<ContentRoot BasePath=""Blazor.Client"" Path=""{Path.Combine(".", "nuget", $"Blazor.Client{Path.DirectorySeparatorChar}")}"" />
|
||||
</StaticWebAssets>";
|
||||
|
||||
var buildEngine = new Mock<IBuildEngine>();
|
||||
|
|
@ -165,47 +124,6 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReturnsError_ForDuplicateContentRoots()
|
||||
{
|
||||
// Arrange
|
||||
var errorMessages = new List<string>();
|
||||
var buildEngine = new Mock<IBuildEngine>();
|
||||
buildEngine.Setup(e => e.LogErrorEvent(It.IsAny<BuildErrorEventArgs>()))
|
||||
.Callback<BuildErrorEventArgs>(args => errorMessages.Add(args.Message));
|
||||
|
||||
var task = new GenerateStaticWebAssetsManifest
|
||||
{
|
||||
BuildEngine = buildEngine.Object,
|
||||
ContentRootDefinitions = new TaskItem[]
|
||||
{
|
||||
CreateItem(Path.Combine("wwwroot","sample.js"), new Dictionary<string,string>
|
||||
{
|
||||
["BasePath"] = "MyLibrary",
|
||||
["SourceId"] = "MyLibrary",
|
||||
["ContentRoot"] = Path.Combine(".", "MyLibrary")
|
||||
}),
|
||||
CreateItem(Path.Combine("wwwroot", "otherLib.js"), new Dictionary<string,string>
|
||||
{
|
||||
["BasePath"] = "MyOtherLibrary",
|
||||
["SourceId"] = "MyOtherLibrary",
|
||||
["ContentRoot"] = Path.Combine(".", "MyLibrary")
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = task.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
var message = Assert.Single(errorMessages);
|
||||
Assert.Equal(
|
||||
$"Duplicate content root paths '{Path.Combine(".", "MyLibrary")}' for base paths 'MyOtherLibrary' and 'MyLibrary' " +
|
||||
$"('{Path.Combine("wwwroot", "otherLib.js")}', '{Path.Combine("wwwroot", "sample.js")}')",
|
||||
message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Generates_EmptyManifest_WhenNoItems_Passed()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -315,52 +315,6 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
|||
Assert.Equal(expectedError, message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Fails_WhenStaticWebAsset_HaveDifferentBasePath()
|
||||
{
|
||||
// Arrange
|
||||
var expectedError = "Static web assets have different 'BasePath' metadata values " +
|
||||
"'_content/mylibrary' and '_content/mylibrary2' " +
|
||||
$"for '{Path.Combine("wwwroot", "js", "sample.js")}' and '{Path.Combine("wwwroot", "css", "site.css")}'.";
|
||||
|
||||
var errorMessages = new List<string>();
|
||||
var buildEngine = new Mock<IBuildEngine>();
|
||||
buildEngine.Setup(e => e.LogErrorEvent(It.IsAny<BuildErrorEventArgs>()))
|
||||
.Callback<BuildErrorEventArgs>(args => errorMessages.Add(args.Message));
|
||||
|
||||
var task = new GenerateStaticWebAsssetsPropsFile
|
||||
{
|
||||
BuildEngine = buildEngine.Object,
|
||||
StaticWebAssets = new TaskItem[]
|
||||
{
|
||||
CreateItem(Path.Combine("wwwroot","js","sample.js"), new Dictionary<string,string>
|
||||
{
|
||||
["SourceType"] = "",
|
||||
["SourceId"] = "MyLibrary",
|
||||
["ContentRoot"] = @"$(MSBuildThisFileDirectory)..\staticwebassets",
|
||||
["BasePath"] = "_content/mylibrary",
|
||||
["RelativePath"] = Path.Combine("js", "sample.js"),
|
||||
}),
|
||||
CreateItem(Path.Combine("wwwroot","css","site.css"), new Dictionary<string,string>
|
||||
{
|
||||
["SourceType"] = "",
|
||||
["SourceId"] = "MyLibrary",
|
||||
["ContentRoot"] = @"$(MSBuildThisFileDirectory)..\staticwebassets",
|
||||
["BasePath"] = "_content/mylibrary2",
|
||||
["RelativePath"] = Path.Combine("css", "site.css"),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = task.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
var message = Assert.Single(errorMessages);
|
||||
Assert.Equal(expectedError, message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WritesPropsFile_WhenThereIsAtLeastOneStaticAsset()
|
||||
{
|
||||
|
|
@ -368,12 +322,12 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
|||
var file = Path.GetTempFileName();
|
||||
var expectedDocument = @"<Project>
|
||||
<ItemGroup>
|
||||
<StaticWebAsset Include=""$(MSBuildThisFileDirectory)..\staticwebassets\**"">
|
||||
<StaticWebAsset Include=""$(MSBuildThisFileDirectory)..\staticwebassets\js\sample.js"">
|
||||
<SourceType>Package</SourceType>
|
||||
<SourceId>MyLibrary</SourceId>
|
||||
<ContentRoot>$(MSBuildThisFileDirectory)..\staticwebassets\</ContentRoot>
|
||||
<BasePath>_content/mylibrary</BasePath>
|
||||
<RelativePath>%(RecursiveDir)%(FileName)%(Extension)</RelativePath>
|
||||
<RelativePath>js/sample.js</RelativePath>
|
||||
</StaticWebAsset>
|
||||
</ItemGroup>
|
||||
</Project>";
|
||||
|
|
@ -388,14 +342,84 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
|||
TargetPropsFilePath = file,
|
||||
StaticWebAssets = new TaskItem[]
|
||||
{
|
||||
CreateItem(Path.Combine("wwwroot","js","sample.js"), new Dictionary<string,string>
|
||||
CreateItem(Path.Combine("wwwroot","js","sample.js"), new Dictionary<string,string>
|
||||
{
|
||||
["SourceType"] = "",
|
||||
["SourceId"] = "MyLibrary",
|
||||
["ContentRoot"] = @"$(MSBuildThisFileDirectory)..\staticwebassets",
|
||||
["BasePath"] = "_content/mylibrary",
|
||||
["RelativePath"] = Path.Combine("js", "sample.js").Replace("\\","/"),
|
||||
}),
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = task.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
var document = File.ReadAllText(file);
|
||||
Assert.Equal(expectedDocument, document, ignoreLineEndingDifferences: true);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(file))
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WritesIndividualItems_WithTheirRespectiveBaseAndRelativePaths()
|
||||
{
|
||||
// Arrange
|
||||
var file = Path.GetTempFileName();
|
||||
var expectedDocument = @"<Project>
|
||||
<ItemGroup>
|
||||
<StaticWebAsset Include=""$(MSBuildThisFileDirectory)..\staticwebassets\App.styles.css"">
|
||||
<SourceType>Package</SourceType>
|
||||
<SourceId>MyLibrary</SourceId>
|
||||
<ContentRoot>$(MSBuildThisFileDirectory)..\staticwebassets\</ContentRoot>
|
||||
<BasePath>/</BasePath>
|
||||
<RelativePath>App.styles.css</RelativePath>
|
||||
</StaticWebAsset>
|
||||
<StaticWebAsset Include=""$(MSBuildThisFileDirectory)..\staticwebassets\js\sample.js"">
|
||||
<SourceType>Package</SourceType>
|
||||
<SourceId>MyLibrary</SourceId>
|
||||
<ContentRoot>$(MSBuildThisFileDirectory)..\staticwebassets\</ContentRoot>
|
||||
<BasePath>_content/mylibrary</BasePath>
|
||||
<RelativePath>js/sample.js</RelativePath>
|
||||
</StaticWebAsset>
|
||||
</ItemGroup>
|
||||
</Project>";
|
||||
|
||||
try
|
||||
{
|
||||
var buildEngine = new Mock<IBuildEngine>();
|
||||
|
||||
var task = new GenerateStaticWebAsssetsPropsFile
|
||||
{
|
||||
BuildEngine = buildEngine.Object,
|
||||
TargetPropsFilePath = file,
|
||||
StaticWebAssets = new TaskItem[]
|
||||
{
|
||||
["SourceType"] = "",
|
||||
["SourceId"] = "MyLibrary",
|
||||
["ContentRoot"] = @"$(MSBuildThisFileDirectory)..\staticwebassets",
|
||||
["BasePath"] = "_content/mylibrary",
|
||||
["RelativePath"] = Path.Combine("js", "sample.js"),
|
||||
}),
|
||||
CreateItem(Path.Combine("wwwroot","js","sample.js"), new Dictionary<string,string>
|
||||
{
|
||||
["SourceType"] = "",
|
||||
["SourceId"] = "MyLibrary",
|
||||
["ContentRoot"] = @"$(MSBuildThisFileDirectory)..\staticwebassets",
|
||||
["BasePath"] = "_content/mylibrary",
|
||||
["RelativePath"] = Path.Combine("js", "sample.js").Replace("\\","/"),
|
||||
}),
|
||||
CreateItem(Path.Combine("wwwroot","App.styles.css"), new Dictionary<string,string>
|
||||
{
|
||||
["SourceType"] = "",
|
||||
["SourceId"] = "MyLibrary",
|
||||
["ContentRoot"] = @"$(MSBuildThisFileDirectory)..\staticwebassets",
|
||||
["BasePath"] = "/",
|
||||
["RelativePath"] = "App.styles.css",
|
||||
}),
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
|
||||
|
|
@ -13,4 +13,13 @@
|
|||
<Reference Include="Microsoft.NET.Sdk.Razor" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="TestFiles\Generated\*.rz.scp.css">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TestFiles\Generated\*.bundle.scp.css">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,124 @@
|
|||
// 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.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Razor.Tasks;
|
||||
using Microsoft.Build.Utilities;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.NET.Sdk.Razor.Test
|
||||
{
|
||||
public class ResolveAllScopedCssAssetsTest
|
||||
{
|
||||
[Fact]
|
||||
public void ResolveAllScopedCssAssets_IgnoresRegularCssFiles()
|
||||
{
|
||||
// Arrange
|
||||
var taskInstance = new ResolveAllScopedCssAssets()
|
||||
{
|
||||
StaticWebAssets = new[]
|
||||
{
|
||||
new TaskItem("TestFiles/Pages/Counter.razor.rz.scp.css", new Dictionary<string,string>
|
||||
{
|
||||
["RelativePath"] = "Pages/Counter.razor.rz.scp.css"
|
||||
}),
|
||||
new TaskItem("site.css", new Dictionary<string,string>
|
||||
{
|
||||
["RelativePath"] = "site.css"
|
||||
}),
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = taskInstance.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
var scopedCssAsset = Assert.Single(taskInstance.ScopedCssAssets);
|
||||
Assert.NotEqual("site.css", scopedCssAsset.ItemSpec);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveAllScopedCssAssets_DetectsScopedCssFiles()
|
||||
{
|
||||
// Arrange
|
||||
var taskInstance = new ResolveAllScopedCssAssets()
|
||||
{
|
||||
StaticWebAssets = new[]
|
||||
{
|
||||
new TaskItem("TestFiles/Pages/Counter.razor.rz.scp.css", new Dictionary<string,string>
|
||||
{
|
||||
["RelativePath"] = "Pages/Counter.razor.rz.scp.css"
|
||||
}),
|
||||
new TaskItem("site.css", new Dictionary<string,string>
|
||||
{
|
||||
["RelativePath"] = "site.css"
|
||||
}),
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = taskInstance.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
var scopedCssAsset = Assert.Single(taskInstance.ScopedCssAssets);
|
||||
Assert.Equal("TestFiles/Pages/Counter.razor.rz.scp.css", scopedCssAsset.ItemSpec);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveAllScopedCssAssets_DetectsScopedCssProjectBundleFiles()
|
||||
{
|
||||
// Arrange
|
||||
var taskInstance = new ResolveAllScopedCssAssets()
|
||||
{
|
||||
StaticWebAssets = new[]
|
||||
{
|
||||
new TaskItem("Folder/Project.bundle.scp.css", new Dictionary<string,string>
|
||||
{
|
||||
["RelativePath"] = "Project.bundle.scp.css"
|
||||
}),
|
||||
new TaskItem("site.css", new Dictionary<string,string>
|
||||
{
|
||||
["RelativePath"] = "site.css"
|
||||
}),
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = taskInstance.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
var scopedCssBundle = Assert.Single(taskInstance.ScopedCssProjectBundles);
|
||||
Assert.Equal("Folder/Project.bundle.scp.css", scopedCssBundle.ItemSpec);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveAllScopedCssAssets_IgnoresScopedCssApplicationBundleFiles()
|
||||
{
|
||||
// Arrange
|
||||
var taskInstance = new ResolveAllScopedCssAssets()
|
||||
{
|
||||
StaticWebAssets = new[]
|
||||
{
|
||||
new TaskItem("Folder/Project.styles.css", new Dictionary<string,string>
|
||||
{
|
||||
["RelativePath"] = "Project.styles.css"
|
||||
}),
|
||||
new TaskItem("site.css", new Dictionary<string,string>
|
||||
{
|
||||
["RelativePath"] = "site.css"
|
||||
}),
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = taskInstance.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.Empty(taskInstance.ScopedCssProjectBundles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.counter {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.fetchData {
|
||||
font-family: Helvetica;
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.index {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.counter {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.counter {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// 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 Microsoft.Build.Framework;
|
||||
|
|
@ -26,28 +27,103 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
|||
BuildEngine = buildEngine.Object,
|
||||
StaticWebAssets = new TaskItem[]
|
||||
{
|
||||
CreateItem(Path.Combine(".", "Library", "wwwroot", "sample.js"), new Dictionary<string,string>
|
||||
CreateItem(Path.Combine("wwroot", "js", "project-transitive-dep.js"), new Dictionary<string,string>
|
||||
{
|
||||
["BasePath"] = "/",
|
||||
["RelativePath"] = "/sample.js",
|
||||
})
|
||||
["BasePath"] = "_content/ClassLibrary",
|
||||
["RelativePath"] = "js/project-transitive-dep.js",
|
||||
["SourceId"] = "ClassLibrary",
|
||||
["SourceType"] = "Project",
|
||||
}),
|
||||
},
|
||||
WebRootFiles = new TaskItem[]
|
||||
{
|
||||
CreateItem(Path.Combine(".", "App", "wwwroot", "sample.js"), new Dictionary<string,string>
|
||||
CreateItem(Path.Combine("wwwroot", "_content", "ClassLibrary", "js", "project-transitive-dep.js"), new Dictionary<string,string>
|
||||
{
|
||||
["TargetPath"] = "/SAMPLE.js",
|
||||
["CopyToPublishDirectory"] = "PreserveNewest",
|
||||
["ExcludeFromSingleFile"] = "true",
|
||||
["OriginalItemSpec"] = Path.Combine("wwwroot", "_content", "ClassLibrary", "js", "project-transitive-dep.js"),
|
||||
["TargetPath"] = Path.Combine("wwwroot", "_content", "ClassLibrary", "js", "project-transitive-dep.js"),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
var expectedMessage = $"The static web asset '{Path.Combine("wwroot", "js", "project-transitive-dep.js")}' " +
|
||||
"has a conflicting web root path '/wwwroot/_content/ClassLibrary/js/project-transitive-dep.js' with the " +
|
||||
$"project file '{Path.Combine("wwwroot", "_content", "ClassLibrary", "js", "project-transitive-dep.js")}'.";
|
||||
|
||||
// Act
|
||||
var result = task.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
var message = Assert.Single(errorMessages);
|
||||
Assert.Equal($"The static web asset '{Path.Combine(".", "Library", "wwwroot", "sample.js")}' has a conflicting web root path '/SAMPLE.js' with the project file '{Path.Combine(".", "App", "wwwroot", "sample.js")}'.", message);
|
||||
Assert.Equal(expectedMessage, message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllowsAssetsHavingTheSameBasePathAcrossDifferentSources_WhenTheirFinalDestinationPathIsDifferent()
|
||||
{
|
||||
// Arrange
|
||||
var task = new ValidateStaticWebAssetsUniquePaths
|
||||
{
|
||||
StaticWebAssets = new TaskItem[]
|
||||
{
|
||||
CreateItem(Path.Combine("wwwroot","sample.js"), new Dictionary<string,string>
|
||||
{
|
||||
["BasePath"] = "MyLibrary",
|
||||
["ContentRoot"] = Path.Combine("nuget", "MyLibrary"),
|
||||
["RelativePath"] = "sample.js",
|
||||
["SourceId"] = "MyLibrary"
|
||||
}),
|
||||
CreateItem(Path.Combine("wwwroot", "otherLib.js"), new Dictionary<string,string>
|
||||
{
|
||||
["BasePath"] = "MyLibrary",
|
||||
["ContentRoot"] = Path.Combine("nuget", "MyOtherLibrary"),
|
||||
["RelativePath"] = "otherLib.js",
|
||||
["SourceId"] = "MyOtherLibrary"
|
||||
})
|
||||
},
|
||||
WebRootFiles = Array.Empty<TaskItem>()
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = task.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllowsAssetsHavingTheSameContentRootAndDifferentBasePathsAcrossDifferentSources_WhenTheirFinalDestinationPathIsDifferent()
|
||||
{
|
||||
// Arrange
|
||||
var task = new ValidateStaticWebAssetsUniquePaths
|
||||
{
|
||||
StaticWebAssets = new TaskItem[]
|
||||
{
|
||||
CreateItem(Path.Combine("wwwroot","sample.js"), new Dictionary<string,string>
|
||||
{
|
||||
["BasePath"] = "MyLibrary",
|
||||
["SourceId"] = "MyLibrary",
|
||||
["RelativePath"] = "sample.js",
|
||||
["ContentRoot"] = Path.Combine(".", "MyLibrary")
|
||||
}),
|
||||
CreateItem(Path.Combine("wwwroot", "otherLib.js"), new Dictionary<string,string>
|
||||
{
|
||||
["BasePath"] = "MyOtherLibrary",
|
||||
["SourceId"] = "MyOtherLibrary",
|
||||
["RelativePath"] = "otherlib.js",
|
||||
["ContentRoot"] = Path.Combine(".", "MyLibrary")
|
||||
})
|
||||
},
|
||||
WebRootFiles = Array.Empty<TaskItem>()
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = task.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -83,7 +159,7 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
|||
// Assert
|
||||
Assert.False(result);
|
||||
var message = Assert.Single(errorMessages);
|
||||
Assert.Equal($"Conflicting assets with the same path '/sample.js' for content root paths '{Path.Combine(".", "Library", "bin", "dist", "sample.js")}' and '{Path.Combine(".", "Library", "wwwroot", "sample.js")}'.", message);
|
||||
Assert.Equal($"Conflicting assets with the same path '/wwwroot/sample.js' for content root paths '{Path.Combine(".", "Library", "bin", "dist", "sample.js")}' and '{Path.Combine(".", "Library", "wwwroot", "sample.js")}'.", message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
Loading…
Reference in New Issue