Closed nickdima closed 9 years ago
Why use extract-text-webpack-plugin
in development when you can use just css-loader
+ style-loader
which can do HMR?
Good point! :) Thanks, I'll give it a try.
@andreypopp how do you handle dev/production configuration changes? Just curious how other people do it, I'm using the NODE_ENV
variable.
@nickdima just if (process.env.NODE_ENV === 'production') { ... }
@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?
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:
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?
I'll take a look. I'm a bit in the dark there, just starting out with HMR :)
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...
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.
@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 Sorry if I'm not following something, but did you get HMR with the extract plugin working? Can you provide an example config?
No, I just use the script loaded css in dev and extracted only in production.
@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 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?
@nickdima i'm trying to get something similar working. Would you mind linking your webpack.config.js
?
@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.
@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)
}
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 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.
Doesn't style-loader!css-loader?sourceMap
work?
@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).
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!
When I use autoprefixer-loader it produces even more weird source path:
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"
]
@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?
@nickdima
Basically in your html's head you need to load one or the either based on the env.
How have you done this? Thanks!
@tsheaff I don't remember really well (one year has past) but looking at the discussion it seems I had a pull request that solved the multiple entry points issue.
@jsrobertson my index html is a handlebars template which gets the env variable passed from node.js so in a IF statement I load the css as a js script for dev and in prod I link to the stylesheet
Thanks for response @nickdima . I ended up going with just not using extract-text for my local builds, after I realized that the 12-factor dev/prod parity point is more about the difference between deployed dev (e.g. www-dev
) and deployed prod (www
) rather than local (localhost
) so now both deployed instances match in that they use extract-text :+1: while I still get hot-reloading on localhost by not using it there
@nickdima @tsheaff did you have performance issues with big inline source maps generated by Webpack without extract-text-plugin? I'm stuck, because developer tools slows down too much with inline source maps. It's impossible to edit css directly from browser.
I had quite a hard time to figure out why I can't enable live reloading for CSS in my setup. This issue seems to indicate that the problem is ExtractTextPlugin. I still don't really understand why ExtractTextPlugin doesn't support reloading. If this is on purpose it would be great if this could be highlighted in more detail on the README.
@donaldpipowitch this part of the webpack docs should clear things up https://webpack.github.io/docs/hot-module-replacement-with-webpack.html
Essentially, the bundle file that webpack outputs which includes all your apps code (js and css files) also includes the HMR runtime. This is where the magic happens. The HMR runtime is evident if you take a look at the outputted bundle file. You will see a quite a bit of weird webpack jargon code included in that bundle. That's the HMR runtime. When running your app, webpack checks for any changes in your source files and when a change occurs, it sends a signal to the HMR runtime included in that bundle file and then the HMR runtime applies those changes and updates your app.
When using the ExtractTextPlugin, take a look at the extracted file. As you can see, the webpack HMR runtime code is not included. It only includes your app code so there's no way for webpack to signal that extracted file to make the necessary changes. This is the intended behavior of the ExtractTextPlugin since it is only meant to be used for production builds.
@ipeters90 Thank you for the detailed explanation.
As you can see, the webpack HMR runtime code is not included. It only includes your app code so there's no way for webpack to signal that extracted file to make the necessary changes.
So could I integrate live reloading, if I'd include the HMR runtime code somehow?
This is the intended behavior of the ExtractTextPlugin since it is only meant to be used for production builds.
The problem with this is that production and dev builds behave differently now. The only (?) other alternative is {{style-loader}} which causes a flash of styles initially, because the styles are injected with a slight delay.
@donaldpipowitch I'm also using {{style-loader}} and experience the quick initial flash of styles as well in my dev environment. Honestly it's just something I've put up with since the benefit of CSS hot loading while i'm developing, to me, far outweighs the this slight inconvenience. But It would be nice to find a better alternative for a more accurate comparison between dev and prod on the initial render. This issue could have something to do with the initial compilation of the css in runtime when you first load the page, whereas, when the css file is already extracted when doing a build, all the css is already precompiled so it just needs to load the file. Just my guess but I haven't found a solution yet.
@ipeters90 I actually fallback to browser-sync for reloading the CSS with extract-text-webpack-plugin
for now: https://github.com/webpack/webpack/issues/1530#issuecomment-179711478.
@donaldpipowitch :+1:
@ptahdunbar you just saved us a loooot of headache with https://github.com/webpack/extract-text-webpack-plugin/issues/30#issuecomment-125757853 This should definitely go into official docs 👍
I was able to reload the extracted CSS file with the code below. I know it's not an elegant solution, but it works pretty well.
Assuming that you are using : new ExtractTextPlugin("bundle.css") .... Just add the following code in your browser application.
`if (process.env.NODE_ENV !== 'production') {
const cssFileName = 'bundle.css';
const originalCallback = window.webpackHotUpdate;
window.webpackHotUpdate = function (...args) {
const links = document.getElementsByTagName("link");
for (var i = 0; i < links.length; i++) {
const link = links[i];
if (link.href.search(cssFileName) !== -1) {
let linkHref = link.href;
link.href = 'about:blank';
link.href = linkHref;
originalCallback(...args);
return;
}
}
}
}`
@web2style @luismreis @sokra It is now possible to use angular2, webpack with hot module replacement, sass sourcemaps, and externally loaded css. It tooks me days of playing with it but I got it working!
The dependencies are style-loader
, css-loader
, and sass-loader
.
In your webpack config, you need this loader:
devtool: 'source-map',
{
test: /\.scss$/,
exclude: /node_modules/,
loader: 'style!css?sourceMap!sass?sourceMap&sourceComments'
}
Then, in your component, for example, app.component.ts, you would require your .scss file OUTSIDE of the @Component decorator. That is the trick. This will not work if you load styles the "angular2 way". Better to let webpack handle styles with lazy-loading anyway.
/*
* THIS IS WHERE WE REQUIRE CSS/SCSS FILES THAT THIS COMPONENT NEEDS
*
* Function: To enable so-called "Lazy Loading" CSS/SCSS files "on demand" as the app views need them.
* Do NOT add styles the "Angular2 Way" in the @Component decorator ("styles" and "styleUrls" properties)
*/
require('./app.scss');
/*
* App Component
* Top Level Component
*/
@Component({
selector: 'app',
pipes: [],
providers: [],
directives: [RouterActive],
encapsulation: ViewEncapsulation.None,
],
template: require('./app.html')
})
I have had no problems running this setup. Does anyone see a "problem" with doing this? If not, here you go!
I still can't get this working with this current setup @IAMtheIAM
bundle.js:33754 Refused to load the stylesheet 'blob:http%3A//localhost%3A4004/70f4cf7d-8f6c-4ac2-bb5a-5f23bd700372' because it violates the following Content Security Policy directive: "style-src 'self' 'unsafe-inline'".
This is the error I'll get in my broswer.
My Loaders are
loaders: [ { test: /\.scss$/, exclude: /node_modules/, loader: 'style!css?sourceMap!sass?sourceMap&sourceComments' }, { test: /\.css$/, loader: [ "style-loader", "css-loader?sourceMap" ]},
Hi there! You can use webpack-hot-middleware by @glenjamin to emit custom events like 'css-file-changed' and receive them on client side to reload css.
// somewhere in your dev-server.js
const webpackHotMiddleware = require('webpack-hot-middleware');
const chokidar = require('chokidar');
const hotMiddleware = webpackHotMiddleware(clientCompiler);
app.use(hotMiddleware);
// emit 'css-file-changed' on every `build.css` change
chokidar.watch('build/css/build.css').on('change', path => {
console.log(`Css file ${path} has been changed`);
hotMiddleware.publish({
type: 'css-file-changed' ,
// you can pass any additional stuff for your update logic
});
});
// at the top of your main client.js
import webpackHotMiddlewareCliient from 'webpack-hot-middleware/client';
webpackHotMiddlewareCliient.subscribe((updateEvent) => {
if (updateEvent.type === 'css-file-changed') {
// your style tag update logic here
// something like:
// styleLink.href = 'about:blank';
// styleLink.href = path;
}
});
// ...
@jamiehutber Interesting. Your loaders are correct, and the blob that was generated looks fine. It seems to be a browser issue. What browser and what version are you using? Did you try the latest version of Chrome or FireFox?
Here is some info on stackexchange that might help you solve that. Let me know how it goes.
@gustavorino 's original code did not work with my version of the dev server, but with some changes it worked perfectly. I added this to my index.html and id="css"
to my stylesheet link tag:
<script>
// Development: reload css on hot update
window.addEventListener("message", function (e) {
console.log('message:', e.data);
if (e.data.search('webpackHotUpdate') === -1) return;
const link = document.getElementById("css");
console.log('reloading css');
let linkHref = link.href;
link.href = 'about:blank';
link.href = linkHref;
}, false);
</script>
I got this to work by using something like this:
webpack.config.js:
module.exports = {
resolve: {
extensions: ['', '.js', '.ts', '.scss']
},
module: {
loaders: [
{
test: /\.scss$/,
exclude: /node_modules/,
loaders: ["style", "css?sourceMap", "sass?sourceMap"]
// loader: 'style!css?sourceMap!sass?sourceMap&sourceComments'
}
]
}
}
And I run it with: $ webpack-dev-server --inline --progress --hot --port 8080
I'm using this in an Angular2 project, so I followed the angular.io guide for Webpack setup, then I wanted to work with SASS and I was looking for a way to make CSS live reload, instead of refreshing the whole page. Notice the --hot
option.
I think I came up with a much cleaner solution for supporting live reload WITH extracted stylesheets in development. Instead of adding window.onmessage
handlers and reloading the stylesheet then, I simply moved my require('<path to css file>')
line from my entrypoint into a separate JavaScript module with:
require('./styles/main.scss');
if (module.hot) {
document.getElementById('css-bundle').href = '/assets/bundle.css?t=' + Date.now();
module.hot.accept();
}
@IAMtheIAM Yeah, I just got into that same solution after 2 nights digging and learning about all the CSS *-loaders
for the dev workflow, and then I saw your comment with the same result.
{
test: /\.scss$/,
loader: 'style!css?sourceMap!postcss?sourceMap!sass?sourceMap'
}
I was not expecting that it worked (and so nicely) after adding the ?sourceMap
.
I didn't expected that the style-loader were ready for any loader with ?sourceMap
and added a blob instead of embedding the styles... Thank you @sokra !
For the production, I just use the ExtractTextPlugin for now...
{
test: /\.scss$/,
loader: ExtractTextPlugin.extract('style', 'css!postcss!sass')
}
Here's another (hacky) solution, inspired by @L8D's comment. In my case I'm using require.context('.', true, /\.less$/)
and ExtractTextPlugin
, so none of the other solutions worked.
if (module.hot) {
const reporter = window.__webpack_hot_middleware_reporter__;
const success = reporter.success;
reporter.success = function () {
document.querySelectorAll('link[href][rel=stylesheet]').forEach((link) => {
const nextStyleHref = link.href.replace(/(\?\d+)?$/, `?${Date.now()}`);
link.href = nextStyleHref;
});
success();
};
}
@drosen0 smart way of fixing it, works like a charm.
@drosen0 Do you still get the warning in the console with this method?
process-update.js?e13e:81 [HMR] The following modules couldn't be hot updated: (Full reload needed)
This is usually because the modules which have changed (and their parents) do not know how to hot reload themselves. [..snip..]
process-update.js?e13e:89 [HMR] - ./web/static/app/styles/app.scss
Anyone know a way to silence that message?
@johnmcdowall, I don't get that message when updating .less files. A couple things to try:
require.context()
to import your .scss files.true
as the second argument (useSubdirectories
), for example, require.context('./app/styles', true, /app\.scss$/)
. I believe the reason is that HMR doesn't recognize the individual files when it's not a specific file reference.@L8D , so in your layout template do you just have an empty link tag?
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: