What I Wish I Knew 6 Days Ago — The Missing Guide for Gulp/Webpack-Dev-Server/BrowserSync

Matthew Goo
Points San Francisco
7 min readMar 9, 2017

Recently I spent 6 days scouring the web for answers- specifically on how to create a frontend build process that has the following:

  • Gulp: compiles sass to css
  • Gulp: compiles handlebars to html
  • Webpack: bundles javascript
  • Webpack-dev-server: serves static files, the bundle.js, and hot reloads all javascript changes
  • BrowserSync: updates css without losing state

This is my second time configuring webpack, and it was not any easier. That tool is a monster — it’s like using a chainsaw equipped with sonar, a compass and diamond studded blades for the purpose of mowing your lawn. Well, it’s actually pretty badass once it starts working, but I’m just trying to illustrate my frustration with the tool. So when you add Gulp and hot reloader into the mix, things start to get messy.

version usage:

  • Webpack: v2.2.1
  • Webpack-dev-server: v2.4.1
  • Gulp: v3.9.1
  • BrowserSync: v2.18.8
  • BrowserSyncWebpackPlugin: v1.1.4

WHY???

Why go through all this trouble? Why use all those tools when Webpack and webpack-dev-server do that for you? Why challenge yourself to build something so unnecessary? Well, the fact of the matter is that it was very necessary! Let me explain to you in a few bullets what I am building:

  • 1 React/Redux SPA that has multiple styled themes for different clients
  • Every color, font, icon differs from other clients.
  • Features can be turned off/on from a javascript config

Hope that 1st line really sums it up, but to go into more depth — Webpack really isn’t meant for the type of build I’m describing. There are 2 things about each client that differ: 1) sass config, 2) javascript config. Almost 99% of the code that we write for the app is exactly the same, we just have logic that uses those configs to change the app appropriately.

The problem isn’t necessarily how Webpack bundling works. The problem is that it isn’t smart enough to know that just a few lines are different from another app (or maybe I’m not smart enough to figure out how to configure that). In any case, I originally only used Webpack and webpack-dev-server and I was forced to do some fancy things that ended up not making sense for us. Whenever we deployed it would take up to 10–12 minutes. Webpack would use around 2GB of RAM while bundling everything together, and that would kill our deployer. All because of the 1% code differences from client to client.

Solution

Currently we only have at most 5 clients on this system, and it takes forever. I realized the more clients we add (hopefully one day will be in the 1,000s. Isn’t that always the goal?), the longer it would take and eventually our deployer would run out of memory and not be able to complete a build. So I decided to separate concerns and use Gulp to compile all styles and templates, while having Webpack do what it does best and bundle just the javascript.

Here is the Webpack config (in real life it’s separated into a few files. I’ve also removed some details):

Ordinary Webpack config

All fairly standard stuff. If you you Google for Webpack examples then I’m sure you would see some flavor of what I have shown above (maybe something more elegant).

Then we have the production Gulpfile.js to build the sass:

Gulp Styles tasks for Dev and Prod

Again a very simple gulp-sass implementation. What’s even more boring is my html build and production build. Just to be thorough at a high level the following is my production task:

gulp.task(“build:all”, [
‘setNodeEnvProd’, // NODE_ENV='production'
‘renderProdTemplate’, // creates all index.html for all clients
‘copyAll’, // copies all static assets into /static
‘clean’ // cleans out previous /static folder
]);
// NOTE: /static is where all asset files are being served from

What is most interesting and my motivation for writing this blog is the developer setup in Gulpfile.js and the watch task that runs BrowserSync to update the css.

Gulp development task

I’ve commented the above code to highlight what is/isn’t important for you. The next magic is an inspiration I found from this blog post: https://medium.com/@_jh3y/how-to-css-streaming-injection-with-browsersync-194edc6cd774#.xazgmyb0z, which might also help you. Thanks Jhey Tompkins — you saved me!

Gulp watch task for styles

Hopefully you have stayed with me up until now. If you are as desperate as I was, I understand why. But here’s the magic that lies in front of your eyes:

browserSyncInstance.stream()

This is what took me so long to figure out. This is what reloads the css without losing the state of the app. Clean and simple (if only everything were like that). Of course when you go back to the documentation and read it for the 20th time it makes sense.

Hot Loader

If you found the above helpful, another big pain point I felt was getting hot-loader to work with my new Gulp/webpack-dev-server setup. It worked right out of the box with Webpack and webpack-dev-server. But now you actually have to set somethings up, and actually modify some of your code.

Essentially the only thing that is missing is module.hot code at the bottom of any file(s) that need to be watched and updated. Code looks like:

// index.jsimport Root from './Root';
import ReactDOM from 'react-dom';
// AppContainer is a necessary wrapper component for HMR
import { AppContainer } from 'react-hot-loader';
import { Provider } from 'react-redux';
import configureStore from './reducers/utils/configureStore';
const store = configureStore();const render = (RootContainer) => {
ReactDOM.render(
<AppContainer>
<Provider store={store}>
<RootContainer store={store} />
</Provider>
</AppContainer>,
document.getElementById('app')
);
}
render(Root);if(module.hot) {
module.hot.accept('./Root', () => {
const NewRoot = require('./Root').default;
render(NewRoot);
});
}

module.hot is a global variable that is injected via Webpack when you include the react-hot-loader/patch . You don’t have to worry about production, since module.hot should not be included in your production webpack configuration. What is going on in that chunk of code is after webpack has recompiled the app changes, the callback is called and imports the new <Root /> element and calls render. The only changes that will be picked up will be within that <Root /> element. So if you ever change something in the index.js(above file) you will need to perform a refresh of the page. This is fine as it shouldn’t be that often.

In order to achieve this I had to perform a few changes to my index.js. I had to create a new RootContainer and move around my routes.

You can see above that I actually wrapped the routes in their own component, while before they lived within the <Provider /> element in the index.js file. This approach is actually a lot nicer as it adheres more closely to our component patterns, and we can attach some logic to the lifecycle methods — this makes it much more readable. There is some weirdness where we have to only render the routes once, which explains my setupRoutes method. There are a couple ReactRouter issues: `Warning: [react-router] You cannot change <Router routes>; it will be ignored`. So if you get some error like that you will need to only render those once in the lifetime of the app.

I can’t imagine you would need to put that module.hot anywhere else if you have a hierarchy like the one I have shown above (which I greatly simplified). So you should only need to include it once. However, since I have Redux (you may have Flux or Mobx), you may also need a module.hot there.

// configureStore.jsimport { createStore, applyMiddleware, combineReducers } from 'redux';
import thunk from 'redux-thunk';
import { browserHistory } from 'react-router';
// this is an aggregation of all my reducers in an index.js
import reducer from '../';
import { routerMiddleware } from 'react-router-redux';
export default () => {
const store = createStore(
reducer,
applyMiddleware(thunk, routerMiddleware(browserHistory)));
if(module.hot) {
module.hot.accept('../', () => {
const newReducer = require('../').default;
store.replaceReducer(newReducer);
});
}
return store;
}

Very similar to my above component module.hot if statement. This will pick up any changes you have in your reducers. You may also need one for your action-creators, but I haven’t yet visited that area of the code. I can’t imagine after seeing these two examples that it would be too difficult to figure out.

Conclusion

The development build that I said was taking 10–12 minutes is now taking 1:30–2 minutes. This will increase every time we add a client, but it shouldn’t be by much, and it will only increase the sass compilation (not the js or html, etc). So bottom line: it will always be 1:30–2 minutes flat, which should put you to sleep at night when trying to take over the world.

The power of Webpack is in your hands. You have the responsibility and duty to make it work for you. Although the setup time takes a while, it is all worth it in the end. The development process is lightning fast with hot-loader, and in my experience the best that is out there to date. You can go hours without refreshing a page, losing state, or losing things in your js console (of course you have the preserve log option). I hope this tutorial/rundown gives you the knowledge you need to complete your build setup.

Stay sexy!

Note: Along with all these changes I also did upgrade from webpack 1 to webpack 2. Really not that bad of a migration. You should take the hour to do it!

--

--