如何让MSBuild目标在输入文件变更时自动运行?
刚接触MSBuild,想实现Blazor项目中*.less文件的编译步骤,之前用WebCompiler但它无法在非Windows机器上运行。
已创建如下Less构建任务(LessCompile.targets):
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <Name>LessCompile</Name> </PropertyGroup> <Target Name="RazorLessCompile" BeforeTargets="ResolveScopedCssInputs" Inputs="@(RazorLessFile)" Outputs="@(RazorLessFile->'%(RelativeDir)%(Filename).css')"> <Exec ContinueOnError="true" IgnoreExitCode="true" Command='npx lessc %(RazorLessFile.Identity) %(RazorLessFile.RelativeDir)%(RazorLessFile.Filename).css --autoprefix="defaults"' /> <!-- This is required for compiled css to be picked up by Razor.ScopedCss.targets Without this, we will get the build error 'Could not find a part of the path '**\Debug\net5.0\scopedcss\projectbundle'.' --> <ItemGroup> <None Remove="%(RazorLessFile.RelativeDir)%(RazorLessFile.Filename).css" /> <None Include="%(RazorLessFile.RelativeDir)%(RazorLessFile.Filename).css" /> </ItemGroup> </Target> <Target Name="LessCompile" BeforeTargets="ResolveStaticWebAssetsConfiguration;ResolveScopedCssInputs" Inputs="@(LessFile)" Outputs="@(LessFile->'%(CompileTo)')"> <Exec ContinueOnError="true" IgnoreExitCode="true" Command='npx lessc %(LessFile.Identity) %(CompileTo) --autoprefix="defaults"' /> </Target> <Target Name="AddStaticAssets" DependsOnTargets="LessCompile" BeforeTargets="ResolveStaticWebAssetsConfiguration;ResolveScopedCssInputs"> <ItemGroup> <Content Remove="wwwroot/**" /> <Content Include="wwwroot/**" /> </ItemGroup> </Target> </Project>
在项目文件中引用的方式:
<ItemGroup> <RazorLessFile Include="**/*.razor.less" /> <LessFile Include="Styles/app.less"> <CompileTo>wwwroot/css/app.css</CompileTo> </LessFile> <LessFile Include="Styles/breezer-overrides.less"> <CompileTo>wwwroot/css/breezer-overrides.css</CompileTo> </LessFile> </ItemGroup> <Import Project="..\..\LessCompile.targets" />
现在遇到的问题:每次修改Less文件后,必须执行重建操作才会重新编译,希望实现输入文件变更时自动触发编译,无需手动执行重建。
要实现MSBuild的增量构建(文件变更时自动触发编译),可以从以下几个方面调整配置:
1. 完善输入输出的关联追踪
当前的编译目标虽指定了Inputs和Outputs,但可以添加Returns属性让MSBuild更清晰地追踪输出文件状态,同时确保每个输入文件对应唯一输出:
<Target Name="RazorLessCompile" BeforeTargets="ResolveScopedCssInputs" Inputs="@(RazorLessFile)" Outputs="@(RazorLessFile->'%(RelativeDir)%(Filename).css')" Returns="@(RazorLessFile->'%(RelativeDir)%(Filename).css')"> <!-- 原编译逻辑不变 --> </Target> <Target Name="LessCompile" BeforeTargets="ResolveStaticWebAssetsConfiguration;ResolveScopedCssInputs" Inputs="@(LessFile)" Outputs="@(LessFile->'%(CompileTo)')" Returns="@(LessFile->'%(CompileTo)')"> <!-- 原编译逻辑不变 --> </Target>
2. 处理Less文件的导入依赖(关键)
如果你的Less文件用@import引用了其他.less文件,MSBuild默认不会识别这些依赖,导致修改导入文件时不会触发编译。需要添加步骤收集依赖:
在LessCompile.targets中新增收集依赖的目标:
<Target Name="CollectLessDependencies" BeforeTargets="RazorLessCompile;LessCompile"> <!-- 收集RazorLessFile的依赖 --> <Exec Command='npx lessc --list-dependencies %(RazorLessFile.Identity)' Outputs="%(RazorLessFile.Identity).deps" WriteLinesToFile="%(RazorLessFile.Identity).deps" Lines="$(_ExecCommandOutput)" /> <ReadLinesFromFile File="%(RazorLessFile.Identity).deps"> <Output TaskParameter="Lines" ItemName="RazorLessDependency" /> </ReadLinesFromFile> <ItemGroup> <RazorLessFile Remove="%(RazorLessFile.Identity)" /> <RazorLessFile Include="%(RazorLessFile.Identity)"> <Dependencies>@(RazorLessDependency)</Dependencies> </RazorLessFile> </ItemGroup> <!-- 收集LessFile的依赖 --> <Exec Command='npx lessc --list-dependencies %(LessFile.Identity)' Outputs="%(LessFile.Identity).deps" WriteLinesToFile="%(LessFile.Identity).deps" Lines="$(_ExecCommandOutput)" /> <ReadLinesFromFile File="%(LessFile.Identity).deps"> <Output TaskParameter="Lines" ItemName="LessDependency" /> </ReadLinesFromFile> <ItemGroup> <LessFile Remove="%(LessFile.Identity)" /> <LessFile Include="%(LessFile.Identity)"> <Dependencies>@(LessDependency)</Dependencies> </LessFile> </ItemGroup> </Target>
然后修改两个编译目标的Inputs,把依赖文件加入监控范围:
<Target Name="RazorLessCompile" BeforeTargets="ResolveScopedCssInputs" Inputs="@(RazorLessFile);@(RazorLessFile->'%(Dependencies)')" Outputs="@(RazorLessFile->'%(RelativeDir)%(Filename).css')"> <!-- 原编译逻辑不变 --> </Target> <Target Name="LessCompile" BeforeTargets="ResolveStaticWebAssetsConfiguration;ResolveScopedCssInputs" Inputs="@(LessFile);@(LessFile->'%(Dependencies)')" Outputs="@(LessFile->'%(CompileTo)')"> <!-- 原编译逻辑不变 --> </Target>
3. 移除错误忽略配置
当前Exec任务设置了ContinueOnError="true"和IgnoreExitCode="true",会导致编译失败时MSBuild仍认为任务成功,无法及时反馈问题。建议移除这两个属性:
<Exec Command='npx lessc %(RazorLessFile.Identity) %(RazorLessFile.RelativeDir)%(RazorLessFile.Filename).css --autoprefix="defaults"' />
4. 优化静态资源添加逻辑
原AddStaticAssets目标每次都会重新包含wwwroot/**,会干扰增量构建。修改为只添加编译生成的CSS文件:
<Target Name="AddStaticAssets" DependsOnTargets="LessCompile;RazorLessCompile" BeforeTargets="ResolveStaticWebAssetsConfiguration;ResolveScopedCssInputs"> <ItemGroup> <!-- 添加编译后的全局CSS --> <Content Include="@(LessFile->'%(CompileTo)')" /> <!-- 添加编译后的Razor作用域CSS --> <Content Include="@(RazorLessFile->'%(RelativeDir)%(Filename).css')" /> </ItemGroup> </Target>
完成以上调整后,修改任意Less文件(包括导入的依赖文件),执行普通构建而非重建,MSBuild会自动触发编译。
内容的提问来源于stack exchange,提问作者Alexander Blyth

