Skip to content

How do I make Hot Module Replacement work for SASS files? #541

Closed
@x-yuri

Description

@x-yuri

It'd be great to have at least some sort of description for making HMR work. Let me share what I did. Any criticism and suggestions are welcome.

tl;dr

diff --git a/config/webpack/development.js b/config/webpack/development.js
index d98ec5b..53812f7 100644
--- a/config/webpack/development.js
+++ b/config/webpack/development.js
@@ -1,5 +1,6 @@
 // Note: You must restart bin/webpack-watcher for changes to take effect
 
+const webpack = require('webpack')
 const merge = require('webpack-merge')
 const sharedConfig = require('./shared.js')
 
@@ -12,5 +13,9 @@ module.exports = merge(sharedConfig, {
 
   output: {
     pathinfo: true
-  }
+  },
+
+  plugins: [
+    new webpack.HotModuleReplacementPlugin()
+  ],
 })
diff --git a/config/webpack/development.server.js b/config/webpack/development.server.js
index fe840c6..e0d95fd 100644
--- a/config/webpack/development.server.js
+++ b/config/webpack/development.server.js
@@ -12,6 +12,8 @@ module.exports = merge(devConfig, {
     compress: true,
     historyApiFallback: true,
     contentBase: resolve(paths.output, paths.entry),
-    publicPath
+    publicPath,
+    hot: true,
+    headers: {'Access-Control-Allow-Origin': '*'}
   }
 })
diff --git a/config/webpack/loaders/sass.js b/config/webpack/loaders/sass.js
index cdc714f..d5d4764 100644
--- a/config/webpack/loaders/sass.js
+++ b/config/webpack/loaders/sass.js
@@ -2,8 +2,8 @@ const ExtractTextPlugin = require('extract-text-webpack-plugin')
 
 module.exports = {
   test: /\.(scss|sass|css)$/i,
-  use: ExtractTextPlugin.extract({
+  use: ['css-hot-loader'].concat(ExtractTextPlugin.extract({
     fallback: 'style-loader',
     use: ['css-loader', 'postcss-loader', 'sass-loader'],
-  }),
+  })),
 }
diff --git a/config/webpack/shared.js b/config/webpack/shared.js
index ef5fa74..2ea6e62 100644
--- a/config/webpack/shared.js
+++ b/config/webpack/shared.js
@@ -23,7 +23,7 @@ module.exports = {
     }, {}
   ),
 
-  output: { filename: '[name].js', path: resolve(paths.output, paths.entry) },
+  output: { filename: '[name].js', path: resolve(paths.output, paths.entry), publicPath },
 
   module: {
     rules: readdirSync(loadersDir).map(file => (
diff --git a/package.json b/package.json
index dcbdec9..f46a81b 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
     "coffee-script": "^1.12.5",
     "compression-webpack-plugin": "^0.4.0",
     "cookies-js": "^1.2.3",
+    "css-hot-loader": "^1.2.0",
     "css-loader": "^0.28.0",
     "eonasdan-bootstrap-datetimepicker": "^4.17.47",
     "extract-text-webpack-plugin": "^2.1.0",

detailed explanation

According to webpack's documentation I just need to add new webpack.HotModuleReplacementPlugin() to plugins section (development.js) and run webpack-dev-server with --hot switch (development.server.js). But with that you see that HMR doesn't work properly. The whole page gets reloaded (HMR messages in console disappear).

Then you add --hot-only switch (development.server.js) for webpack-dev-server to not fall back to live reload. And see that it can't find hot-update.json file, whatever that may be:

GET http://localhost:3000/40293e2cd0bae6910c2e.hot-update.json 404 (Not Found)

That happens since it's asking for it your rails app (localhost:3000), not webpack-dev-server (localhost:8080). And the reason for that is, output.publicPath option is not set. It's set for webpack-dev-server, but not for webpack itself.

The error gets triggered here. Since $require$.p is empty string. And $require$.p is empty because it's basically output.publicPath option.

So, we add output.publicPath and the next thing you see is webpack-dev-server asking you to allow your rails app access to webpack-dev-server:

XMLHttpRequest cannot load http://localhost:8080/9b1e70204c19ca0472e5.hot-update.json. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access.

You add it to development.server.js (headers: {'Access-Control-Allow-Origin': 'http://localhost:3000'}), and check again. This time your site doesn't see your updates:

[HMR] Checking for updates on the server...
[HMR] Nothing hot updated.

And that has to do with extract-text-webpack-pluging not meant to be used in development. So, you disable it (new ExtractTextPlugin({disable: env.NODE_ENV != 'production', filename: ...). Or this way. And now it sort of works, but you see:

GET http://localhost:8080/application.css

in console. That's because <%= stylesheet_pack_tag 'application' %> in layout still tries to find extracted css file. But we no longer extract it in development env. And thus you end up with:

<%= stylesheet_pack_tag 'application' unless Rails.env.development? %>

The one improvement you can make here is make use of css-hot-loader package. It allows not to disable extract-text-webpack-plugin in development environment.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions