How To Use SCSS With Blazor

Intro

In this tutorial I will give instruction on one way how to, in conjunction with npm and gulp, automatically compile scss to css and refresh page when developing a blazor client application. This will also work in asp.net mcv project.

About Blazor

Blazor is a framework built using c# and .net core architecture to develop browser applications using aforementioned technologies. In essence, blazor allows developer to write c# and get browser interaction. The idea itself is not particularly new. There has been multiple attempts to bring other languages to browser like Silverlight and Java applets. Neither technology however were widely enough adopted by the developer community to become standardized. Blazor is the latest attempt at this and only time will tell whether it becomes a success.

Scss

Scss and sass are essentially expansions to css, the browsers primary method of providing styling to websites. Scss provides multiple improvements to ordinary css most prominent (imho) being variables and ability to nest style clauses. The end result will still be css since browsers can't natively parse the scss syntax.

Putting the two together

When a developer starts out a blazor client project, they are greeted with the following file structure:

Blazorwasm default file structure

What we are interested lies in wwwroot/css folder. In there you will see a plain app.css file that contains some boilerplate css rules. Very soon we are going to overwrite that file with css compiled from scss.

Installing packages

In order to compile scss, we need some npm packages to do our work. Run the following command:

npm init

Fill it out with whatever details you want. This will create a package.json file that will hold information about the npm packages this app needs to compile scss.

Then install required packages:

npm install --save-dev browser-sync concurrently gulp gulp-sass node-sass

Let's explain what these packages will do.

Creating source files

This is the simplest step in this tutorial. Create a folder calles 'Styles' at the project root alongside wwwroot, Pages, Properties etc. Inside that folder, create a file app.scss.

Gulpfile.js

In order for gulp to know what to do and when, it needs a configuration file. This is where gulpfile comes into play. Create a new file gulpfile.js and fill it with this:

'use strict';

/*global require*/
/*global process*/

const gulp = require('gulp');
const sass = require('gulp-sass');
const browserSync = require('browser-sync').create();

const dirs = {
    scss: {
        src: 'Styles',
        dest: 'wwwroot/css'
    }
};

const production = process.env.NODE_ENV === 'production';

const stylesTask = function stylesTask(done) {
    gulp.src(`${dirs.scss.src}/*.scss`)
        .pipe(sass().on('error', sass.logError))
        .pipe(gulp.dest(dirs.scss.dest));

    if(!production) {
        browserSync.reload();
        done();
    }
};

const watchTask = function watchTask() {
    browserSync.init({
        proxy: "http://localhost:5000"
    });

    gulp.watch(`${dirs.scss.src}/**/*.scss`, gulp.series(stylesTask));
};

const buildTask = function buildTask() {
    return new Promise(function (resolve) {
        gulp.task('styles')();
        resolve();
    });
};
gulp.task('styles', stylesTask);
gulp.task('watch', watchTask);
gulp.task('build', buildTask);

Gulp is built with the concept of pipes. Source file is passed from one process to another. For example stylesTask has two pipes: First it will read any file ending with .scss and passes that on to sass (this is where gulp-sass and node-sass come into play). Then the output of that is passed to another pipe, gulp.dest, which copies the output to a designated file. In this case wwroot/css folder. The source file name is used in the output so the file names match. Once the file is saved, command browser to reload.

Another task that bears some explanation is the watch task. What we do before setting up the watcher for .scss files, we proxy the source url of the blazorwasm app to browserSync url (which by default is http://localhost:3000) essentially using localhost:3000 to "mask" localhost:5000. The purpose for this proxy is that while browsersync is showing the browser, it doesn't actually host the files necessary to display css from wwwroot. Dotnet is responsible for this and it runs in a different url. Changing dotnet to localhost:3000 won't work since browsersync would then attempt to use some other port (3001 or 3002) since the 3000 port is already under perview of the dotnet process.

Setting up scripts

In order to make development process easier and more uniform, we use npm scripts to give specific commands. Attach the following section to your package.json:

"scripts": {
    "watch": "NODE_ENV='development' npx gulp watch",
    "start": "concurrently --kill-others \"dotnet watch run\" \"npm run watch\"",
    "prod": "NODE_ENV='production' npx gulp build"
  },

Here's what they do

Time for a test run

Test this little combination by running the start command.

npm start

What you should see is some text and a browser opening. Since dotnet takes some time to compile everything you may have to do some manual refreshing and navigation to localhost:3000. After you see the counter app displayed, go to yout app.scss file, make an edit and save. What you should see is your browser window refreshing and your changes displayed in the browser window.