Firstly, there are extensions that can compile scss at build time… but every extension installed is another barrier to new team members coming onboard and getting up to speed on a project. I believe the development environment should have as little dependencies as possible (including extensions). That said this approach does rely on Node.JS to compile the SCSS but since Node.JS has wide spread adoption, the chances are current (or potential) team members will have it already.
See the compiling SCSS at build time with visual studio GitHub repo for the full code base.
Initial Setup
To get started, lets create the default MVC application.
Once it is has initialised, create a new folder in the root of the project called Scss. This is where we will store our raw .scss files. For simplicity of this example, we are going to have one file (site.scss). So we should have something that looks like this.
Add the following content to the site.scss file.
$green: #038503;
html h1 {
color: $green;
}
NPM Setup
Open command prompt (note: PATH environment variable must contain npm directory for this to work).
Change directory into the folder where the project is located.
Run npm init
npm init
creates an empty package.json for the project, which will later tell other machines the packages needed for this project to build.
Run npm i node-sass --save-dev
npm i node-sass --save-dev
creates a dev dependency on the node-sass package which is what will be compiling the SCSS into CSS.
Add build-css script to package.json.
{
"name": "compile-scss-on-build",
"version": "1.0.0",
"description": "This package does not do anything useful",
"main": "index.js",
"private": true,
"dependencies": {},
"devDependencies": {
"node-sass": "^4.14.1"
},
"scripts": {
"build-css": "node-sass --include-path src/scss --output-style compressed src/scss/site.scss --output wwwroot/css"
},
"author": "Jacob Dixon",
"license": "MIT"
}
The scripts
section defines commands the can be executed by calling npm run script-name
. The build-css
script executes node-sass while telling it to look in the src/scss folder for any missing files (--include-path src/scss
), that we want the output to be compressed (--output-style compressed
), the file to process (src/scss/site.scss
this could be changed to the whole directory or specific files using glob patterns) and finally the output location (--output wwwroot/css
).
Adding SCSS Compilation To Build
Now we have the npm script configured to build the src/scss folder we need to tell visual studio (or MSBuild more specifically) to run the command before building the project. Add the following lines to the projects .csproj
file
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
<Exec Command="npm run build-css" />
</Target>
The Target
element allows us to extend the build with additional tasks (in the code above the task is Exec
which we will get to shortly). The BeforeTargets
attribute tells MSBuild to run these tasks before running the PreBuildEvent
. Exec tells MSBuild to execute the specified command, in this case we are telling MSBuild to execute our npm script build-css.
The Microsoft Docs contains more information about the Target Element and Exec Task Element.
Adding Fast Build Support
If we run this now before adding fast build support we will see that it doesn’t actually build the scss when only the scss has changed. That is because FastBuild will do prechecks to look for changes and if it doesn’t find any, it will skip the build process. Normally this works great, but since we’ve added custom build steps to compile the scss, FastBuild isn’t aware of this and doesn’t know to check the src/scss folder.
Lets fix that now… add the following lines to the .csproj
file.
<ItemGroup>
<UpToDateCheckInput Include="src/scss/**/*.scss" Set="Css" />
<UpToDateCheckBuilt Include="wwwroot/css/*.css" Set="Css" />
</ItemGroup>
The <UpToDateCheckInput Include="src/scss/**/*.scss" Set="Css" />
tells FastBuild to include any .scss files within the src/scss folder (including subfolders). The Set="Css"
acts a group enabling the UpToDateCheckInput
and UpToDateCheckBuilt
commands to function in isolation to the rest of the FastBuild checks.
The <UpToDateCheckBuilt Include="wwwroot/css/*.css" Set="Css" />
line tells FastBuild to check the modified time of any .css files within the wwwroot/css folder. FastBuild will group the Input and Built checks because they are both within the css set and compare the latest modified time of .scss files against the earliest modified time of .css files. This means it’s important to make sure that only directories and files that will be directly modified as part of the build be included in the checks.
Debugging FastBuild
FastBuild can be tricky to understand what is going on, luckily we can enable logging within Visual Studio 2019.
To enable logging open Options (under Tools) inside Visual Studio 2019
Navigate to Projects and Solutions > SDK-Style Projects
I recommend setting it to Verbose for debugging issues. Verbose logging will output lines like this.
1>FastUpToDate: Comparing timestamps of inputs and outputs in set 'Css': (CompileScssOnBuild)
1>FastUpToDate: Adding UpToDateCheckBuilt outputs in set 'Css': (CompileScssOnBuild)
1>FastUpToDate: 'C:\VS Projects\compile-scss-at-build-time-with-visual-studio\CompileScssOnBuild\wwwroot\css\site.css' (CompileScssOnBuild)
1>FastUpToDate: Adding UpToDateCheckInput inputs in set 'Css': (CompileScssOnBuild)
1>FastUpToDate: 'C:\VS Projects\compile-scss-at-build-time-with-visual-studio\CompileScssOnBuild\src\scss\site.scss' (CompileScssOnBuild)
1>FastUpToDate: Input 'C:\VS Projects\compile-scss-at-build-time-with-visual-studio\CompileScssOnBuild\src\scss\site.scss' is newer (17/10/2020 22:55:58) than earliest output 'C:\VS Projects\compile-scss-at-build-time-with-visual-studio\CompileScssOnBuild\wwwroot\css\site.css' (17/10/2020 21:56:40), not up to date. (CompileScssOnBuild)
1>FastUpToDate: Up to date check completed in 18.5 ms (CompileScssOnBuild)
Summary
MSBuild and FastBuild are incredibly powerful and highly customisable tools that can be used to automate build tasks. Hopefully this example gives you the starting blocks to develop customised builds for your next project.