Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Hot Reload Sass Using Webpack 2?

I'm working on setting up a React application that uses Webpack2, webpack-dev-middleware and HMR for development. Whenever I make a change on a React component, it updates in the browser as intended. The issue I am running into is that when I modify my .scss files, the browser does not update. What happens instead, is that in the console it gives me the following:

[HMR] bundle rebuilding
client.js:207 [HMR] bundle rebuilt in 1567ms
process-update.js:27 [HMR] Checking for updates on the server...
process-update.js:98 [HMR] Nothing hot updated.
process-update.js:107 [HMR] App is up to date.

After this, when I refresh the page, my style changes appear. I'm not entirely sure what's going on or where the issue stems from but would like some help and clarification.Below is my setup:

Webpack.config.js

var webpack = require('webpack');
var path = require('path');
var autoprefixer = require('autoprefixer');
var DashboardPlugin = require('webpack-dashboard/plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var argv = require('yargs').argv;

const config = {};

// This configured production
if (argv.p) {
    config.entry = [
      './src/client/scripts/index',
      './src/client/scripts/utils/index',
      './src/client/styles/index.scss'
    ]
    config.plugins = [
      new DashboardPlugin(),
      new ExtractTextPlugin({
        filename: 'bundle.css',
        allChunks: true
      }),
    ]
}
else {
  config.entry = [
    'react-hot-loader/patch',
    'webpack-hot-middleware/client',
    './src/client/scripts/index',
    './src/client/scripts/utils/index',
    './src/client/styles/index.scss'
  ]
  config.plugins = [
    new DashboardPlugin(),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoEmitOnErrorsPlugin(),
    new webpack.NamedModulesPlugin(),
    new ExtractTextPlugin({
      filename: 'bundle.css',
      allChunks: true
    })
  ]
}

module.exports = {
  entry: config.entry,
  output: {
    path: path.join(__dirname, 'src', 'client', 'static'),
    filename: 'bundle.js',
    publicPath: '/static/'
  },
  devtool: 'inline-source-map',
  devServer: {
    hot: true,
    contentBase: path.resolve(__dirname, 'src', 'client', 'static'),
    publicPath: (__dirname, 'src', 'client', 'static')
  },
  plugins: config.plugins,
  module: {
    rules: [
      {
        test: /\.js?$/,
        exclude: /(node_modules|bower_components)/,
        include: path.join(__dirname, 'src'),
        use: [
          {
            loader: 'babel-loader',
            query: {
              presets: ['react', ['es2015', { 'modules': false }], 'stage-0'],
              plugins: ['react-hot-loader/babel', 'react-html-attrs', 'transform-class-properties', 'transform-decorators-legacy'],
            }
          }
        ]
      },
      {
        test: /\.(png|woff|woff2|eot|ttf|svg)$/,
        use: [
          {
            loader: 'url-loader?limit=100000'
          }
        ],
      },
      {
        test: /\.scss$/,
        use: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: ['css-loader', 'sass-loader']
        })
      }
    ]
  }
};

Server.js using webpack-dev-middleware

const router = Router();
const clientDir = resolve(`${__dirname}/../../client`);

if (isDev()) {
  const webpackDevMiddleware = require('webpack-dev-middleware')
  const webpack = require('webpack')
  const webpackConfig = require('../../../webpack.config')
  const webpackHotMiddleware = require('webpack-hot-middleware')

  const compiler = webpack(webpackConfig)

  // This compiles our app using webpack
  router.use(webpackDevMiddleware(compiler, {
    publicPath: webpackConfig.output.publicPath,
    noInfo: true
  }))

  // This connects our app to HMR using the middleware
  router.use(webpackHotMiddleware(compiler))
}

router.use(express.static(clientDir));

export default router

index.js on client side

import React from 'react'
import ReactDOM from 'react-dom'
import { AppContainer } from 'react-hot-loader'
import App from './App'

const root = document.querySelector('.root');

// Wraps our App in AppContainer
const render = (Component) => {
  ReactDOM.render(
    <AppContainer>
      <Component/>
    </AppContainer>,
    root
  );
};

// Renders our application
render(App);

// This checks if a component has been updated
// It then accepts the changes and replaced the module.
// It's only checking if JS has been changed...
// @TODO - it only works for JS not CSS.
// I think this is an issue with webpack not
// recognizing bundle.css as a dependency?
if (module.hot) {
  module.hot.accept();
}
like image 210
u111937 Avatar asked Apr 07 '17 20:04

u111937


1 Answers

You're using extract-text-webpack-plugin and after webpack rebuilds the bundle the webpack-dev-middleware thinks that nothing changed, because the corresponding module in your bundle representing the CSS is empty as its content has been extracted.

You need to disable extract-text-webpack-plugin in development to get HMR. You can use the disable option and it will fallback to the style-loader, which injects the <style> tags.

new ExtractTextPlugin({
  filename: 'bundle.css',
  allChunks: true,
  disable: true
})

Instead of having to define two versions of the plugin you can use environment variables like NODE_ENV=production and use it in the plugin:

new ExtractTextPlugin({
  filename: 'bundle.css',
  allChunks: true,
  disable: process.env.NODE_ENV !== 'production'
})
like image 162
Michael Jungo Avatar answered Sep 22 '22 09:09

Michael Jungo