New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reloading extracted css with hot module replacement #30

Closed
nickdima opened this Issue Oct 7, 2014 · 95 comments

Comments

Projects
None yet
@nickdima

nickdima commented Oct 7, 2014

Is it possible to have hot module replacement for an extracted css file that I load via a css link tag in my html's head tag?
I have HMR working for my javascript but not sure how to make it work for extracted css.
This is my css related config:

entry:
    styles: ['webpack-dev-server/client?http://localhost:3001', 'webpack/hot/dev-server', './app.scss']
loaders: [
  {
    test: /\.scss$/
    loader: ExtractTextPlugin.extract "style-loader", "css-loader!sass-loader?" + JSON.stringify
      outputStyle: 'expanded'
      includePaths: [
        path.resolve __dirname, './app'
        path.resolve __dirname, './bower_components'
        require('node-bourbon').includePaths
        path.resolve __dirname, './vendor/css'
      ]
  }
plugins: [new ExtractTextPlugin "css/#{nameBundle}.css"]
@andreypopp

This comment has been minimized.

Show comment
Hide comment
@andreypopp

andreypopp Oct 7, 2014

Contributor

Why use extract-text-webpack-plugin in development when you can use just css-loader + style-loader which can do HMR?

Contributor

andreypopp commented Oct 7, 2014

Why use extract-text-webpack-plugin in development when you can use just css-loader + style-loader which can do HMR?

@nickdima

This comment has been minimized.

Show comment
Hide comment
@nickdima

nickdima Oct 7, 2014

Good point! :)
Thanks, I'll give it a try.

nickdima commented Oct 7, 2014

Good point! :)
Thanks, I'll give it a try.

@nickdima nickdima closed this Oct 7, 2014

@nickdima nickdima reopened this Oct 7, 2014

@nickdima

This comment has been minimized.

Show comment
Hide comment
@nickdima

nickdima Oct 7, 2014

@andreypopp how do you handle dev/production configuration changes? Just curious how other people do it, I'm using the NODE_ENV variable.

nickdima commented Oct 7, 2014

@andreypopp how do you handle dev/production configuration changes? Just curious how other people do it, I'm using the NODE_ENV variable.

@andreypopp

This comment has been minimized.

Show comment
Hide comment
@andreypopp

andreypopp Oct 7, 2014

Contributor

@nickdima just if (process.env.NODE_ENV === 'production') { ... }

Contributor

andreypopp commented Oct 7, 2014

@nickdima just if (process.env.NODE_ENV === 'production') { ... }

@nickdima

This comment has been minimized.

Show comment
Hide comment
@nickdima

nickdima Oct 7, 2014

@andreypopp it seems that if I require my css from my main js entry point the HMR works, but not if I want to have it as a separate bundle and load it via a script tag.
Any ideas?

nickdima commented Oct 7, 2014

@andreypopp it seems that if I require my css from my main js entry point the HMR works, but not if I want to have it as a separate bundle and load it via a script tag.
Any ideas?

@nickdima

This comment has been minimized.

Show comment
Hide comment
@nickdima

nickdima Oct 7, 2014

These are my entries:

entry:
  common: ['webpack-dev-server/client?http://localhost:3001', 'webpack/hot/dev-server', './client.coffee']
  styles: ['webpack-dev-server/client?http://localhost:3001', 'webpack/hot/dev-server','./app.scss']

If I leave only each one of them HMR works for any of them, but if I put them both HMR works only for common.
This is how the log from the browser's consoles looks like when making a css change:

screen shot 2014-10-07 at 15 54 39

nickdima commented Oct 7, 2014

These are my entries:

entry:
  common: ['webpack-dev-server/client?http://localhost:3001', 'webpack/hot/dev-server', './client.coffee']
  styles: ['webpack-dev-server/client?http://localhost:3001', 'webpack/hot/dev-server','./app.scss']

If I leave only each one of them HMR works for any of them, but if I put them both HMR works only for common.
This is how the log from the browser's consoles looks like when making a css change:

screen shot 2014-10-07 at 15 54 39

@sokra

This comment has been minimized.

Show comment
Hide comment
@sokra

sokra Oct 7, 2014

Member

We need to change this https://github.com/webpack/webpack/blob/master/hot/dev-server.js#L39

window.onmessage = function(event) {

to something like addEventListener...

Do you want to send a PR?

Member

sokra commented Oct 7, 2014

We need to change this https://github.com/webpack/webpack/blob/master/hot/dev-server.js#L39

window.onmessage = function(event) {

to something like addEventListener...

Do you want to send a PR?

@nickdima

This comment has been minimized.

Show comment
Hide comment
@nickdima

nickdima Oct 7, 2014

I'll take a look. I'm a bit in the dark there, just starting out with HMR :)

nickdima commented Oct 7, 2014

I'll take a look. I'm a bit in the dark there, just starting out with HMR :)

@sokra

This comment has been minimized.

Show comment
Hide comment
@sokra

sokra Oct 7, 2014

Member

as workaround you can do:

entry:
  common: ['webpack-dev-server/client?http://localhost:3001', 'webpack/hot/dev-server', 
             './app.scss', './client.coffee']

Best is to use only one entry per HTML page...

Member

sokra commented Oct 7, 2014

as workaround you can do:

entry:
  common: ['webpack-dev-server/client?http://localhost:3001', 'webpack/hot/dev-server', 
             './app.scss', './client.coffee']

Best is to use only one entry per HTML page...

@nickdima

This comment has been minimized.

Show comment
Hide comment
@nickdima

nickdima Oct 7, 2014

My goal is to have the css as a separate bundle so I can load it in the head. My html is generated server side and the javascript loaded just before closing the body tag.
I could just load everything in the head in development, but I don't want to change things to much between dev and prod. There would be just too many places to check for the env variable, so I was looking for a cleaner solution to my scenario.

nickdima commented Oct 7, 2014

My goal is to have the css as a separate bundle so I can load it in the head. My html is generated server side and the javascript loaded just before closing the body tag.
I could just load everything in the head in development, but I don't want to change things to much between dev and prod. There would be just too many places to check for the env variable, so I was looking for a cleaner solution to my scenario.

@nickdima

This comment has been minimized.

Show comment
Hide comment
@nickdima

nickdima Oct 7, 2014

@sokra I investigated the problem and now I see what you're saying about window.onmessage, it gets overwritten by the last loaded bundle.
I've made a test with addEventListener and it seems to work. I'll prepare a PR.

nickdima commented Oct 7, 2014

@sokra I investigated the problem and now I see what you're saying about window.onmessage, it gets overwritten by the last loaded bundle.
I've made a test with addEventListener and it seems to work. I'll prepare a PR.

@mysterycommand

This comment has been minimized.

Show comment
Hide comment
@mysterycommand

mysterycommand Jan 24, 2015

@nickdima Sorry if I'm not following something, but did you get HMR with the extract plugin working? Can you provide an example config?

mysterycommand commented Jan 24, 2015

@nickdima Sorry if I'm not following something, but did you get HMR with the extract plugin working? Can you provide an example config?

@nickdima

This comment has been minimized.

Show comment
Hide comment
@nickdima

nickdima Jan 25, 2015

No, I just use the script loaded css in dev and extracted only in production.

nickdima commented Jan 25, 2015

No, I just use the script loaded css in dev and extracted only in production.

@mmahalwy

This comment has been minimized.

Show comment
Hide comment
@mmahalwy

mmahalwy May 30, 2015

@nickdima any luck with this yet with Extract plugin? Shouldn't the CSS just reload and the browser pick up the changes or link tags don't work that :)

mmahalwy commented May 30, 2015

@nickdima any luck with this yet with Extract plugin? Shouldn't the CSS just reload and the browser pick up the changes or link tags don't work that :)

@sokra

This comment has been minimized.

Show comment
Hide comment
@sokra

sokra May 31, 2015

Member

@mmahalwy This doesn't work. You shoudn't use the extract-text-webpack-plugin in development. Better thread the extract-text-webpack-plugin as production optimiation for the style-loader.

andreypopp: Why use extract-text-webpack-plugin in development when you can use just css-loader + style-loader which can do HMR?

Member

sokra commented May 31, 2015

@mmahalwy This doesn't work. You shoudn't use the extract-text-webpack-plugin in development. Better thread the extract-text-webpack-plugin as production optimiation for the style-loader.

andreypopp: Why use extract-text-webpack-plugin in development when you can use just css-loader + style-loader which can do HMR?

@rxtphan

This comment has been minimized.

Show comment
Hide comment
@rxtphan

rxtphan Jul 17, 2015

@nickdima i'm trying to get something similar working. Would you mind linking your webpack.config.js?

rxtphan commented Jul 17, 2015

@nickdima i'm trying to get something similar working. Would you mind linking your webpack.config.js?

@nickdima

This comment has been minimized.

Show comment
Hide comment
@nickdima

nickdima Jul 17, 2015

@rxtphan it depends on you setup, what we did is we have an entry point for all our css (we use sass imports) that uses the style-loader and css-loader.
In development we just load the generated js file that adds the css at runtime while for the production build we wrap the loaders with the extract-text-webpack-plugin so that it creates a separate css file with all our styles. Basically in your html's head you need to load one or the either based on the env.

nickdima commented Jul 17, 2015

@rxtphan it depends on you setup, what we did is we have an entry point for all our css (we use sass imports) that uses the style-loader and css-loader.
In development we just load the generated js file that adds the css at runtime while for the production build we wrap the loaders with the extract-text-webpack-plugin so that it creates a separate css file with all our styles. Basically in your html's head you need to load one or the either based on the env.

@choonkending choonkending referenced this issue Jul 18, 2015

Merged

React Hot Loader #37

4 of 4 tasks complete
@ptahdunbar

This comment has been minimized.

Show comment
Hide comment
@ptahdunbar

ptahdunbar Jul 28, 2015

@rxtphan @mmahalwy @mysterycommand et all,

I ran into this "bug" too, wondering why my styles weren't reloading. Here's a solution that works:

// webpack.config.js
var DEBUG = process.env.NODE_ENV !== 'production' ? true : false;
var styles = 'css!csslint';

// add to loaders
{
    test: /\.css$/,
    loader: DEBUG ? 'style!' + styles : ExtractTextPlugin.extract(styles)
}

ptahdunbar commented Jul 28, 2015

@rxtphan @mmahalwy @mysterycommand et all,

I ran into this "bug" too, wondering why my styles weren't reloading. Here's a solution that works:

// webpack.config.js
var DEBUG = process.env.NODE_ENV !== 'production' ? true : false;
var styles = 'css!csslint';

// add to loaders
{
    test: /\.css$/,
    loader: DEBUG ? 'style!' + styles : ExtractTextPlugin.extract(styles)
}
@WishCow

This comment has been minimized.

Show comment
Hide comment
@WishCow

WishCow Aug 16, 2015

After a while of googling, I came to the realization that having live-reload (or hmr), with sass, with source maps, is currently not possible with webpack. Can anyone please confirm this, or point me in the right direction on how to get this working?

If I don't use extract-text-webpack-plugin, I can get hmr working just fine, but since that just puts the CSS inline in a <style> tag, there is no way to get sourcemaps. The sass-loader readme specifies that if you need sourcemaps, you have to use this plugin. Ok, I start using this plugin. Sourcemaps are working great, but there is no way to get hmr/livereload working, since the recommendation by @sokra here is to not use this in development.

WishCow commented Aug 16, 2015

After a while of googling, I came to the realization that having live-reload (or hmr), with sass, with source maps, is currently not possible with webpack. Can anyone please confirm this, or point me in the right direction on how to get this working?

If I don't use extract-text-webpack-plugin, I can get hmr working just fine, but since that just puts the CSS inline in a <style> tag, there is no way to get sourcemaps. The sass-loader readme specifies that if you need sourcemaps, you have to use this plugin. Ok, I start using this plugin. Sourcemaps are working great, but there is no way to get hmr/livereload working, since the recommendation by @sokra here is to not use this in development.

@luismreis

This comment has been minimized.

Show comment
Hide comment
@luismreis

luismreis Aug 17, 2015

@WishCow Just realized the exact same thing. Bonus bogus points for part of the documentation refering to devtool: 'source-map', and some other to devtool: 'sourcemap', (this is addressed by pull-request #84)). Seems like we are chasing ghosts at this time.

luismreis commented Aug 17, 2015

@WishCow Just realized the exact same thing. Bonus bogus points for part of the documentation refering to devtool: 'source-map', and some other to devtool: 'sourcemap', (this is addressed by pull-request #84)). Seems like we are chasing ghosts at this time.

@sokra

This comment has been minimized.

Show comment
Hide comment
@sokra

sokra Aug 17, 2015

Member

Doesn't style-loader!css-loader?sourceMap work?

Member

sokra commented Aug 17, 2015

Doesn't style-loader!css-loader?sourceMap work?

@WishCow

This comment has been minimized.

Show comment
Hide comment
@WishCow

WishCow Aug 17, 2015

@sokra that does not work, however messing around a bit with it, it turns out that adding sourceMap to both sass, and css loader works:

{
  test: /\.scss$/,
  loaders: [ 'style', 'css?sourceMap', 'sass?sourceMap' ]
}

It does produce a bit of a weird output in Chrome about which file the rules belong to, but clicking on the line does reveal the correct scss file (with the correct line number).

chrome sourcemaps

I'll open an issue on sass-loader, so that it has the correct information on how to enable source maps, so people don't get misled here.

Thanks a lot!

WishCow commented Aug 17, 2015

@sokra that does not work, however messing around a bit with it, it turns out that adding sourceMap to both sass, and css loader works:

{
  test: /\.scss$/,
  loaders: [ 'style', 'css?sourceMap', 'sass?sourceMap' ]
}

It does produce a bit of a weird output in Chrome about which file the rules belong to, but clicking on the line does reveal the correct scss file (with the correct line number).

chrome sourcemaps

I'll open an issue on sass-loader, so that it has the correct information on how to enable source maps, so people don't get misled here.

Thanks a lot!

@yantakus

This comment has been minimized.

Show comment
Hide comment
@yantakus

yantakus Aug 26, 2015

When I use autoprefixer-loader it produces even more weird source path:

zsfjq6z

Here is my webpack config:

var AUTOPREFIXER_BROWSERS = '"ie >= 10","ie_mob >= 10","ff >= 30","chrome >= 34","safari >= 7","opera >= 23","ios >= 7","android >= 4.4","bb >= 10"';

loaders: [
  "style-loader",
  "css-loader?sourceMap",
  "autoprefixer-loader?{browsers:[" + AUTOPREFIXER_BROWSERS + "]}",
  "sass-loader?sourceMap"
]

yantakus commented Aug 26, 2015

When I use autoprefixer-loader it produces even more weird source path:

zsfjq6z

Here is my webpack config:

var AUTOPREFIXER_BROWSERS = '"ie >= 10","ie_mob >= 10","ff >= 30","chrome >= 34","safari >= 7","opera >= 23","ios >= 7","android >= 4.4","bb >= 10"';

loaders: [
  "style-loader",
  "css-loader?sourceMap",
  "autoprefixer-loader?{browsers:[" + AUTOPREFIXER_BROWSERS + "]}",
  "sass-loader?sourceMap"
]
@tsheaff

This comment has been minimized.

Show comment
Hide comment
@tsheaff

tsheaff Oct 21, 2015

@sokra seems like saying "don't use extract-text in development" isn't a great solution for a few reasons:

(1) There are big debugging benefits to having separate CSS files that I want on dev
(2) I'd like to test my load times in development as close to production as possible
(3) more generally, the further your dev and prod become the more likely you are to have bugs

Would be great to get Hot Module Reload working from a stylesheet link tag.

@nickdima looks like you solved the multiple hot server entry points, was there another reason you weren't able to get this working?

tsheaff commented Oct 21, 2015

@sokra seems like saying "don't use extract-text in development" isn't a great solution for a few reasons:

(1) There are big debugging benefits to having separate CSS files that I want on dev
(2) I'd like to test my load times in development as close to production as possible
(3) more generally, the further your dev and prod become the more likely you are to have bugs

Would be great to get Hot Module Reload working from a stylesheet link tag.

@nickdima looks like you solved the multiple hot server entry points, was there another reason you weren't able to get this working?

@Slumber86

This comment has been minimized.

Show comment
Hide comment
@Slumber86

Slumber86 Apr 6, 2017

I found easier to do something like this:

webpackConfig.plugins = [
...
  new ExtractTextPlugin({
    disable: process.env.NODE_ENV === "development",
    filename  : '[name].[contenthash].css',
    allChunks : true
})
]

...

webpackConfig.module.rules.push({
  test : /\.scss$/,
  use: ExtractTextPlugin.extract({
      fallback: 'style-loader',
      use: [{
          loader: 'css-loader',
          options: {
              sourceMap: false,
              import: false,
              url: false
          }
      }, {
          loader: 'sass-loader',
          options: {
              sourceMap: true,
              outputStyle: 'expanded',
              includePaths: [...project.paths.client('styles'), './node_modules',]
          }
      }]
    })
})

For me, it works...

Slumber86 commented Apr 6, 2017

I found easier to do something like this:

webpackConfig.plugins = [
...
  new ExtractTextPlugin({
    disable: process.env.NODE_ENV === "development",
    filename  : '[name].[contenthash].css',
    allChunks : true
})
]

...

webpackConfig.module.rules.push({
  test : /\.scss$/,
  use: ExtractTextPlugin.extract({
      fallback: 'style-loader',
      use: [{
          loader: 'css-loader',
          options: {
              sourceMap: false,
              import: false,
              url: false
          }
      }, {
          loader: 'sass-loader',
          options: {
              sourceMap: true,
              outputStyle: 'expanded',
              includePaths: [...project.paths.client('styles'), './node_modules',]
          }
      }]
    })
})

For me, it works...

@nathanboktae

This comment has been minimized.

Show comment
Hide comment
@nathanboktae

nathanboktae Apr 17, 2017

None of these last few suggestions worked for me mainly due to webpack 2 not seeing that my style files were true dependencies of any javascript that required them. webpack-hot-middleware didn't either. But it's possible to subscribe to all changes via webpack/hot/emitter 's webpackHotUpdate event:

if (module.hot) {
  var hotEmitter = require("webpack/hot/emitter");
  hotEmitter.on("webpackHotUpdate", function(currentHash) {
    document.querySelectorAll('link[href][rel=stylesheet]').forEach((link) => {
      const nextStyleHref = link.href.replace(/(\?\d+)?$/, `?${Date.now()}`)
      link.href = nextStyleHref
    })
  })
}

nathanboktae commented Apr 17, 2017

None of these last few suggestions worked for me mainly due to webpack 2 not seeing that my style files were true dependencies of any javascript that required them. webpack-hot-middleware didn't either. But it's possible to subscribe to all changes via webpack/hot/emitter 's webpackHotUpdate event:

if (module.hot) {
  var hotEmitter = require("webpack/hot/emitter");
  hotEmitter.on("webpackHotUpdate", function(currentHash) {
    document.querySelectorAll('link[href][rel=stylesheet]').forEach((link) => {
      const nextStyleHref = link.href.replace(/(\?\d+)?$/, `?${Date.now()}`)
      link.href = nextStyleHref
    })
  })
}
@mieszko4

This comment has been minimized.

Show comment
Hide comment
@mieszko4

mieszko4 Apr 18, 2017

@nathanboktae it works well for me, except it refreshes the styles each time I make a change regardless if it is a change in javascript or css. I am wondering if there is a way to check if change was made in css.

Also, in chrome every time I make a change I see an annoying style flash. In firefox there is no such flash - it is all good.

mieszko4 commented Apr 18, 2017

@nathanboktae it works well for me, except it refreshes the styles each time I make a change regardless if it is a change in javascript or css. I am wondering if there is a way to check if change was made in css.

Also, in chrome every time I make a change I see an annoying style flash. In firefox there is no such flash - it is all good.

@helly0d

This comment has been minimized.

Show comment
Hide comment
@helly0d

helly0d Apr 18, 2017

@mieszko4 In order to avoid that annoying style flash ( aka FOUT ). You could combine the solution I gave above with the event based solution provided by @nathanboktae like this:

if (module.hot) {
  const hotEmitter = require("webpack/hot/emitter");
  const DEAD_CSS_TIMEOUT = 2000;

  hotEmitter.on("webpackHotUpdate", function(currentHash) {
    document.querySelectorAll("link[href][rel=stylesheet]").forEach((link) => {
      const nextStyleHref = link.href.replace(/(\?\d+)?$/, `?${Date.now()}`);
      const newLink = link.cloneNode();
      newLink.href = nextStyleHref;

      link.parentNode.appendChild(newLink);
      setTimeout(() => {
        link.parentNode.removeChild(link);
      }, DEAD_CSS_TIMEOUT);
    });
  })
}

Basically what this does, is to insert new links with the updated timestamp in the query, and remove the old link tags after 2 seconds. This way you will avoid that moment of FOUT because the new link tags will overwrite the rules from the old ones, once they have loaded their srcs. The removal of the old links is for clean-up purpose.

helly0d commented Apr 18, 2017

@mieszko4 In order to avoid that annoying style flash ( aka FOUT ). You could combine the solution I gave above with the event based solution provided by @nathanboktae like this:

if (module.hot) {
  const hotEmitter = require("webpack/hot/emitter");
  const DEAD_CSS_TIMEOUT = 2000;

  hotEmitter.on("webpackHotUpdate", function(currentHash) {
    document.querySelectorAll("link[href][rel=stylesheet]").forEach((link) => {
      const nextStyleHref = link.href.replace(/(\?\d+)?$/, `?${Date.now()}`);
      const newLink = link.cloneNode();
      newLink.href = nextStyleHref;

      link.parentNode.appendChild(newLink);
      setTimeout(() => {
        link.parentNode.removeChild(link);
      }, DEAD_CSS_TIMEOUT);
    });
  })
}

Basically what this does, is to insert new links with the updated timestamp in the query, and remove the old link tags after 2 seconds. This way you will avoid that moment of FOUT because the new link tags will overwrite the rules from the old ones, once they have loaded their srcs. The removal of the old links is for clean-up purpose.

@mieszko4

This comment has been minimized.

Show comment
Hide comment
@mieszko4

mieszko4 Apr 18, 2017

Thanx @helly0d! Your solution works well if there are no custom fonts in my case.

I looked more into the problem and I realized that the flash is actually caused by redownloading custom fonts that I have defined in my scss.
After each change in my scss or js, chrome redownloads the same font (the same filename).
I will post more if I figure out how to resolve that problem.

mieszko4 commented Apr 18, 2017

Thanx @helly0d! Your solution works well if there are no custom fonts in my case.

I looked more into the problem and I realized that the flash is actually caused by redownloading custom fonts that I have defined in my scss.
After each change in my scss or js, chrome redownloads the same font (the same filename).
I will post more if I figure out how to resolve that problem.

@mieszko4

This comment has been minimized.

Show comment
Hide comment
@mieszko4

mieszko4 Apr 18, 2017

It seems that chrome does not redownload fonts if styles are defined in <style />.
I do not have enough knowledge to figure it out. It seems it would be best but also very hard (if possible at all?) if it was solved with <style /> patches instead of entire file refresh since the source map file maps to multiple source files in the project.

mieszko4 commented Apr 18, 2017

It seems that chrome does not redownload fonts if styles are defined in <style />.
I do not have enough knowledge to figure it out. It seems it would be best but also very hard (if possible at all?) if it was solved with <style /> patches instead of entire file refresh since the source map file maps to multiple source files in the project.

@nathanboktae

This comment has been minimized.

Show comment
Hide comment
@nathanboktae

nathanboktae Apr 18, 2017

Great @helly0d I was about to merge our solutions too 👏

@milworm that article's solution is is 2x the lines, adds a totally unnecessary AJAX call, and keeps a 2nd copy of your stylesheets in memory in JS. This one is much better.

nathanboktae commented Apr 18, 2017

Great @helly0d I was about to merge our solutions too 👏

@milworm that article's solution is is 2x the lines, adds a totally unnecessary AJAX call, and keeps a 2nd copy of your stylesheets in memory in JS. This one is much better.

@nathanboktae

This comment has been minimized.

Show comment
Hide comment
@nathanboktae

nathanboktae Apr 18, 2017

For the maintainers (@bebraw, @TheLarkInn, etc) the principle of keeping development and production as close as possible is extremely important, and a 2.5 year old issue with 85 comments figuring out how to do it is a testament to that. I would be great to have first class HMR support in extract-text-webpack-plugin, if not at least an official, documented solution.

nathanboktae commented Apr 18, 2017

For the maintainers (@bebraw, @TheLarkInn, etc) the principle of keeping development and production as close as possible is extremely important, and a 2.5 year old issue with 85 comments figuring out how to do it is a testament to that. I would be great to have first class HMR support in extract-text-webpack-plugin, if not at least an official, documented solution.

@mieszko4

This comment has been minimized.

Show comment
Hide comment
@mieszko4

mieszko4 Apr 18, 2017

@nathanboktae, @bebraw, @TheLarkInn It is not only about keeping developement and production as close as possible.
With extract-text-webpack-plugin I have source files under webpack:// in my chrome developer tools which is really awesome as they follow the tree structure of my project.
Without extract-text-webpack-plugin in my chrome developer tools they appear flat under ☁️ (no-domain and if I include resolve-url-loader source maps do not work (issue with resolve-url-loader).

mieszko4 commented Apr 18, 2017

@nathanboktae, @bebraw, @TheLarkInn It is not only about keeping developement and production as close as possible.
With extract-text-webpack-plugin I have source files under webpack:// in my chrome developer tools which is really awesome as they follow the tree structure of my project.
Without extract-text-webpack-plugin in my chrome developer tools they appear flat under ☁️ (no-domain and if I include resolve-url-loader source maps do not work (issue with resolve-url-loader).

@milworm

This comment has been minimized.

Show comment
Hide comment
@milworm

milworm Apr 18, 2017

@nathanboktae , well, you are right about second copy, but I don't think it's something bad. I wouldn't say that it's a bad solution. At least it works and doesn't have any issues mentioned in this thread.

milworm commented Apr 18, 2017

@nathanboktae , well, you are right about second copy, but I don't think it's something bad. I wouldn't say that it's a bad solution. At least it works and doesn't have any issues mentioned in this thread.

@nathanboktae

This comment has been minimized.

Show comment
Hide comment
@nathanboktae

nathanboktae Apr 18, 2017

@milworm No, You still have flickering as you're just setting href directly. You are polluting the network console with an XHR request (including when only JS changes). That also causes another delay.

nathanboktae commented Apr 18, 2017

@milworm No, You still have flickering as you're just setting href directly. You are polluting the network console with an XHR request (including when only JS changes). That also causes another delay.

@TheLarkInn

This comment has been minimized.

Show comment
Hide comment
@TheLarkInn

TheLarkInn Apr 18, 2017

@nathanboktae @mieszko4 PR's are welcomed! 😄 ❤️ If you think you can have a fool proof non-breaking solution why not?

The thing is that the team itself cannot and will not (currently) invest the time in tackling this vs the laundry list of user voted features that will take priority. However, it sounds that there are quite a few people here who believe that they would like this feature so I would love to see some collaboration and I'm happy to answer internal api questions, and foster any learning needed.

TheLarkInn commented Apr 18, 2017

@nathanboktae @mieszko4 PR's are welcomed! 😄 ❤️ If you think you can have a fool proof non-breaking solution why not?

The thing is that the team itself cannot and will not (currently) invest the time in tackling this vs the laundry list of user voted features that will take priority. However, it sounds that there are quite a few people here who believe that they would like this feature so I would love to see some collaboration and I'm happy to answer internal api questions, and foster any learning needed.

@mieszko4

This comment has been minimized.

Show comment
Hide comment
@mieszko4

mieszko4 Apr 18, 2017

I think this pr is great for starters: #457

mieszko4 commented Apr 18, 2017

I think this pr is great for starters: #457

@nathanboktae

This comment has been minimized.

Show comment
Hide comment
@nathanboktae

nathanboktae Apr 18, 2017

@TheLarkInn thanks for recognizing the need, and of course the hardwork on WebPack. 👏

nathanboktae commented Apr 18, 2017

@TheLarkInn thanks for recognizing the need, and of course the hardwork on WebPack. 👏

@milworm

This comment has been minimized.

Show comment
Hide comment
@milworm

milworm Apr 19, 2017

@nathanboktae well, the ajax-call takes around 15ms on macbook, so it doesn't seem to be a lot.

milworm commented Apr 19, 2017

@nathanboktae well, the ajax-call takes around 15ms on macbook, so it doesn't seem to be a lot.

christopher4lis added a commit to christopher4lis/webpack.js.org that referenced this issue Jun 8, 2017

Add section for HMR and external stylesheets
There's no way built into webpack core to reload external stylesheets automatically with HMR. There is a work around however, involving the automatic insertion and removal of link tags within a  `hotEmitter.on("webpackHotUpdate"...` function. Many people have looked for a solution for this, but the only answer is located in an issue thread near the very bottom: webpack-contrib/extract-text-webpack-plugin#30

It would be much more efficient and helpful for developers to see the solution directly on the docs page until a workaround is implemented into Webpack core.
@IAMtheIAM

This comment has been minimized.

Show comment
Hide comment
@IAMtheIAM

IAMtheIAM Aug 10, 2017

Here's my updated loader config

This configuration allows for Hot Module replacement with SCSS files using webpack dev server, and extracttextplugin for production mode to emit actual .css files. Dev server mode simulated using extracted CSS because it loads the files in the when ?sourcemap=true on a previous loader

I also don't use the stylesUrl property, I import the .scss file outside of the @component decorator so that the styles load in the global context, rather than scoped by component.
Here's my working config

{
        test: /\.(scss)$/,
        use:
          isDevServer ? [
              {
                loader: 'style-loader',
              },            
              {
                loader: 'css-loader',
                options: { sourceMap: true }
              },
              {
                loader: 'postcss-loader',
                options: { postcss: [AutoPrefixer(autoPrefixerOptions)], sourceMap: true }
              },
              {
                loader: 'sass-loader',
                options: { sourceMap: true }
              },
              {
                loader: 'sass-resources-loader',
                options: {
                  resources: [
                    './src/assets/styles/variables.scss',
                    './src/assets/styles/mixins.scss']
                }
              }, 
              /**
               * The sass-vars-loader will convert the 'vars' property or any module.exports of 
               * a .JS or .JSON file into valid SASS and append to the beginning of each 
               * .scss file loaded.
               *
               * See: https://github.com/epegzz/sass-vars-loader
               */
              {
                loader: '@epegzz/sass-vars-loader?',
                options: querystring.stringify({
                  vars: JSON.stringify({
                    susyIsDevServer: susyIsDevServer
                  })
                })
              }] : // dev mode
          ExtractTextPlugin.extract({
            fallback: "css-loader",
            use: [
              {
                loader: 'css-loader',
                options: { sourceMap: true }
              },
              {
                loader: 'postcss-loader',
                options: { postcss: [AutoPrefixer(autoPrefixerOptions)], sourceMap: true }
              },
              {
                loader: 'sass-loader',
                options: { sourceMap: true }
              },
              {
                loader: 'sass-resources-loader',
                options: {
                  resources: [
                    './src/assets/styles/variables.scss',
                    './src/assets/styles/mixins.scss']
                }
              }, {
                loader: '@epegzz/sass-vars-loader?',
                options: querystring.stringify({
                  vars: JSON.stringify({
                    susyIsDevServer: susyIsDevServer
                  })
                  // // Or use 'files" object to specify vars in an external .js or .json file
                  // files: [
                  //    path.resolve(helpers.paths.appRoot + '/assets/styles/sass-js-variables.js')
                  // ],
                })
              }],
            publicPath: '/' // 'string' override the publicPath setting for this loader
          })
      },

app.component.css

import './app.style.scss'

/**
 * AppComponent Component
 * Top Level Component
 */
@Component({
   selector: 'body',
   encapsulation: ViewEncapsulation.None,
   host: { '[class.authenticated]': 'appState.state.isAuthenticated' },
   templateUrl: './app.template.html'
})

IAMtheIAM commented Aug 10, 2017

Here's my updated loader config

This configuration allows for Hot Module replacement with SCSS files using webpack dev server, and extracttextplugin for production mode to emit actual .css files. Dev server mode simulated using extracted CSS because it loads the files in the when ?sourcemap=true on a previous loader

I also don't use the stylesUrl property, I import the .scss file outside of the @component decorator so that the styles load in the global context, rather than scoped by component.
Here's my working config

{
        test: /\.(scss)$/,
        use:
          isDevServer ? [
              {
                loader: 'style-loader',
              },            
              {
                loader: 'css-loader',
                options: { sourceMap: true }
              },
              {
                loader: 'postcss-loader',
                options: { postcss: [AutoPrefixer(autoPrefixerOptions)], sourceMap: true }
              },
              {
                loader: 'sass-loader',
                options: { sourceMap: true }
              },
              {
                loader: 'sass-resources-loader',
                options: {
                  resources: [
                    './src/assets/styles/variables.scss',
                    './src/assets/styles/mixins.scss']
                }
              }, 
              /**
               * The sass-vars-loader will convert the 'vars' property or any module.exports of 
               * a .JS or .JSON file into valid SASS and append to the beginning of each 
               * .scss file loaded.
               *
               * See: https://github.com/epegzz/sass-vars-loader
               */
              {
                loader: '@epegzz/sass-vars-loader?',
                options: querystring.stringify({
                  vars: JSON.stringify({
                    susyIsDevServer: susyIsDevServer
                  })
                })
              }] : // dev mode
          ExtractTextPlugin.extract({
            fallback: "css-loader",
            use: [
              {
                loader: 'css-loader',
                options: { sourceMap: true }
              },
              {
                loader: 'postcss-loader',
                options: { postcss: [AutoPrefixer(autoPrefixerOptions)], sourceMap: true }
              },
              {
                loader: 'sass-loader',
                options: { sourceMap: true }
              },
              {
                loader: 'sass-resources-loader',
                options: {
                  resources: [
                    './src/assets/styles/variables.scss',
                    './src/assets/styles/mixins.scss']
                }
              }, {
                loader: '@epegzz/sass-vars-loader?',
                options: querystring.stringify({
                  vars: JSON.stringify({
                    susyIsDevServer: susyIsDevServer
                  })
                  // // Or use 'files" object to specify vars in an external .js or .json file
                  // files: [
                  //    path.resolve(helpers.paths.appRoot + '/assets/styles/sass-js-variables.js')
                  // ],
                })
              }],
            publicPath: '/' // 'string' override the publicPath setting for this loader
          })
      },

app.component.css

import './app.style.scss'

/**
 * AppComponent Component
 * Top Level Component
 */
@Component({
   selector: 'body',
   encapsulation: ViewEncapsulation.None,
   host: { '[class.authenticated]': 'appState.state.isAuthenticated' },
   templateUrl: './app.template.html'
})

@thomaseizinger thomaseizinger referenced this issue Aug 24, 2017

Closed

Make HMR work properly #38

0 of 2 tasks complete
@orpheus

This comment has been minimized.

Show comment
Hide comment
@orpheus

orpheus Oct 4, 2017

chain css-hot-loader with ExtractTextPlugin:
https://www.npmjs.com/package/css-hot-loader

I now have no Flash of Unstyled Content because of the extracted text, and my css hot reloads on save because of css-hot-loader

orpheus commented Oct 4, 2017

chain css-hot-loader with ExtractTextPlugin:
https://www.npmjs.com/package/css-hot-loader

I now have no Flash of Unstyled Content because of the extracted text, and my css hot reloads on save because of css-hot-loader

@doomsbuster

This comment has been minimized.

Show comment
Hide comment
@doomsbuster

doomsbuster Oct 22, 2017

HMR needs css-loader and style-loader that support hot module css replacement. If you are using less or sass, use the less-loader or sass-loader and then simply use the loader configuration like below:

	module: {
		rules: [{
			test: /\.js$/,
			exclude: /node_modules|__tests__|__uitests__|__mocks__/,
			use: {
				loader: 'babel-loader'
			}
		}, {
			test: /\.less$/,
			exclude: /node_modules/,
			include: path.resolve(__dirname, 'ui/styles'),
			use: ['style-loader', 'css-loader', 'less-loader']
		}, {
			test: /\.(png|jpe?g|gif|svgz?|woff2?|eot)$/i,
			include: path.resolve(__dirname, 'ui/images'),
			use: {
				loader: 'file-loader',
				options: {
					name: '[name].[ext]'
				}
			}
		}, {
			test: /\.css$/,
			use: ['style-loader', 'css-loader']
		}]
	},

doomsbuster commented Oct 22, 2017

HMR needs css-loader and style-loader that support hot module css replacement. If you are using less or sass, use the less-loader or sass-loader and then simply use the loader configuration like below:

	module: {
		rules: [{
			test: /\.js$/,
			exclude: /node_modules|__tests__|__uitests__|__mocks__/,
			use: {
				loader: 'babel-loader'
			}
		}, {
			test: /\.less$/,
			exclude: /node_modules/,
			include: path.resolve(__dirname, 'ui/styles'),
			use: ['style-loader', 'css-loader', 'less-loader']
		}, {
			test: /\.(png|jpe?g|gif|svgz?|woff2?|eot)$/i,
			include: path.resolve(__dirname, 'ui/images'),
			use: {
				loader: 'file-loader',
				options: {
					name: '[name].[ext]'
				}
			}
		}, {
			test: /\.css$/,
			use: ['style-loader', 'css-loader']
		}]
	},
@sheerun

This comment has been minimized.

Show comment
Hide comment
@sheerun

sheerun Dec 9, 2017

I've created https://github.com/sheerun/extracted-loader that is dedicated to this use case. Usage:

config.module.rules.push({
  test: /\.css$/,
  use: ['extracted-loader'].concat(ExtractTextPlugin.extract({
    /* Your configuration here */
  }))
})

config.plugins.push(new ExtractTextPlugin('index.css'))

No configuration necessary :)

sheerun commented Dec 9, 2017

I've created https://github.com/sheerun/extracted-loader that is dedicated to this use case. Usage:

config.module.rules.push({
  test: /\.css$/,
  use: ['extracted-loader'].concat(ExtractTextPlugin.extract({
    /* Your configuration here */
  }))
})

config.plugins.push(new ExtractTextPlugin('index.css'))

No configuration necessary :)

@LG0012

This comment has been minimized.

Show comment
Hide comment
@LG0012

LG0012 Dec 9, 2017

LG0012 commented Dec 9, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment