HOW TO: CSS streaming/injection with Browsersync
injecting style changes without losing page state
TL;DR — When developing CSS, injection of styling changes is going to be better than reloading the whole page and losing application state. With some slight alterations to Browsersync setup, you too can embrace CSS injection! (See gist)
I was notified today about an answer I’d left on StackOverflow quite some time ago being up-voted. The general gist of the question was, “How can I set up SASS/CSS injection with Gulp and Browsersync?” Looking back at my answer, it addressed setting up livereload but didn’t truly set up CSS styling injection. This got me thinking. CSS injection with Browsersync is actually a pretty overlooked feature.
Basic Browsersync setup
A basic Browsersync setup is likely to serve files from a directory and trigger reloads when any files within that directory change.
Something along the lines of the following;
const browsersync = require('browser-sync');const setUp = () => {
const server = browsersync.create();
server.init({
baseDir: 'public/'
});
server.watch('public/**', (event, file) => {
if (event === change) server.reload();
});
}
This would be pretty much identical when using Gulp except we would wrap this in our Gulp syntax like;
const browsersync = require('browser-sync'),
gulp = require('gulp');gulp.task('serve', ['build'], () => {
const server = browsersync.create();
server.init({
baseDir: 'public/'
});
return server.watch('public/**', (event, file) => {
if (event === change) server.reload();
});
});
Note: We add a dependency to our `server` task to run `build` first so that there is content to serve.
This would be fine for Browsersync setup and works perfectly fine. But we can certainly make it better. Especially if we want to make use of streaming goodness.
What’s streaming and CSS injection?
Browsersync offers `streaming` as part of its API.
It’s a powerful feature that allows us to inject changes into the browser without triggering full refreshes of pages.
There isn’t too much in the documentation about using this feature. From what I can tell, it can’t be used with Grunt and there is also no way to make use of it through the CLI. There is however some documentation that shows how to make use of it with Gulp.
Injecting changes into the browser is exceptionally useful when working with styling and CSS. Not triggering a full refresh of the pages that you are working on means that you can make styling alterations without losing page state. This not only saves you the time of having to continually get to a certain state but also makes tweaking and debugging of styles easier.
Consider a scenario where you are working on a particularly stateful single page application. You’ve got to a stage where you are styling a UI component that relies heavily on the state of the application. You’re working on a particular style for the UI component in a particular non-default application state. Using the “reload” API, every time a change was made, the whole page would reload. We would lose the state, and then have to repeat the steps to get to that state in order to continue work on the styling. With the “stream” API, we never lose our page state. Styling is injected without effecting the page state. This will not only save time but also makes life a little easier when working with styles.
Setting up CSS injection
Minimal changes are required to gain the benefits of CSS injection for your development. The Browsersync documentation does detail how to stream changes using Gulp. The main idea being that the output from a compilation task is piped to `Browsersync.stream()`.
An example using SASS with Gulp would be along the following lines;
const gulp = require(‘gulp’),
plumber = require(‘gulp-plumber’),
scss = require(‘gulp-scss’),
browsersync = require(‘browser-sync’);gulp.task(‘styles:compile’, () => {
return gulp.src(‘src/scss/**/*.scss’)
.pipe(plumber())
.pipe(scss())
.pipe(gulp.dest(‘public/css’)
.pipe(browsersync.stream());
});
Note: The last line is responsible for streaming the CSS changes and injecting them into the browser.
It’s a one line change and it will get the job done. However, I’m personally not overly keen on writing some server behaviour into my style compilation task. I feel it makes more sense to keep my CSS injection logic with my Browsersync server setup code.
How do we do this though? It isn’t straightforward from looking at the Browsersync documentation. The documentation only details piping changes using Gulp.
A solution I’ve found is to create our own stream using `vinyl` and changed files. The function earlier did the following;
const setUp = () => {
const server = browsersync.create();
server.init({
baseDir: 'public/'
});
server.watch('public/**', (event, file) => {
if (event === change) server.reload();
});
};
The important part resides in the watch;
server.watch('public/**', (event, file) => {
if (event === change) server.reload();
});
If we check for the file extension of files that change we can determine what behaviour to trigger.
If file is not CSS, do reload page.
If file is CSS, do not reload page, inject file.
We can check for extensions with a simple `indexOf` check on the file.
return server.watch('public/**', (event, file) => {
if (evt === ‘change’ && file.indexOf(‘.css’) === -1)
server.reload();
if (evt === ‘change’ && file.indexOf(‘.css’) !== -1)
server.stream();
});
Just invoking `server.stream()` alone isn’t going to do anything. In order for this to work, we need to make use of `vinyl`. We can create a stream of the content to inject using `vinyl-file`, `vinyl-buffer` and `vinyl-source-stream`. Once created, we can pipe this to `server.stream()`.
vinylFile.readSync(file)
.pipe(vinylSourceStream(file))
.pipe(vinylBuffer())
.pipe(server.stream());
And that’s all there is to it. With some very minimal changes we can reap the benefit of CSS injection when developing for the web. The full source for our Browsersync set up with CSS injection is as follows;
To adapt this for use with Gulp, simply wrap the contents of the `start` function with the usual Gulp task syntax.
Hopefully that can help some people out!
As always, any questions or suggestions, please feel free to leave a response or tweet me 🐦!