webpack-encore icon indicating copy to clipboard operation
webpack-encore copied to clipboard

Add HMR / Hot Support

Open weaverryan opened this issue 7 years ago • 29 comments

This should be easily possible for React and Vue.js (at least). It's not currently possible with CSS, because we're using ExtractTextWebpackPlugin all the time.

weaverryan avatar Jun 12 '17 18:06 weaverryan

I propose a patch here for HMR support.

alOneh avatar Jun 13 '17 09:06 alOneh

It looks like there is a way to enable HMR for SASS compilation, using css-hot-loader in this issue. I'm still trying to figure out how to add it to Encore.

TheMaxoor avatar Aug 14 '17 08:08 TheMaxoor

Interesting... I think I saw that library - https://github.com/shepherdwind/css-hot-loader - before, but it looked really low quality (even if it worked). But a lot of work has been done over the past 2 months. So, I'm curious to investigate this :).

@TheMaxoor To try this in Encore for now, you'd need to hack it a little bit. Something like this:

var config - Encore.getWebpackConfig();

// 5 is just a guess at the correct index for the .scss loader - you'll need to find out which is correct
// this is just a hack for now ;)
config.module.rules[5].use = ['css-hot-loader'].concat(config.module.rules[5].use);

module.exports = config;

That may not be 100% correct - I put it together quickly. But if you have time to try it, I'd love to know your feedback.

Cheers!

weaverryan avatar Aug 16 '17 03:08 weaverryan

@weaverryan It's not working for me... I've adopted your lines as well as i've tested the origial variant inject the ExtractTextWebpackPlugin directly:

const ExtractTextWebpackPlugin = require("extract-text-webpack-plugin");
config.module.rules[1].use = ['css-hot-loader'].concat(ExtractTextWebpackPlugin.extract({
    fallback: 'style-loader',
    use: ['css-loader', 'sass-loader'],
}));

After that, I've updated an scss file and the recompile worked:

 WAIT  Compiling...          23:54:59

webpack: Compiling...
 DONE  Compiled successfully in 571ms          23:55:00

Hash: b058ff23a19307486590
Version: webpack 3.6.0
Time: 571ms
	                       Asset       Size  Chunks                    Chunk Names
6fa2d9cde5b42b66ea41.hot-update.json   44 bytes          [emitted]         
	                      app.js    1.71 MB       0  [emitted]  [big]  app
32825a78dbf5cce8faca.hot-update.json   35 bytes          [emitted]         
	                     app.css  819 bytes       0  [emitted]         app
	               manifest.json  130 bytes          [emitted]         
[./app/Resources/assets/shared.scss] ./app/Resources/assets/shared.scss 41 bytes {0} [built]
[./node_modules/webpack/hot ^\.\/log$] (webpack)/hot nonrecursive ^\.\/log$ 170 bytes {0} [built]
    + 90 hidden modules
Child extract-text-webpack-plugin node_modules/extract-text-webpack-plugin/dist node_modules/css-loader/index.js??ref--4-2!node_modules/resolve-url-loader/index.js??ref--4-3!node_modules/sass-loader/lib/loader.js??ref--4-4!app/Resources/assets/shared.scss:
	                           Asset      Size  Chunks             Chunk Names
    6fa2d9cde5b42b66ea41.hot-update.json  44 bytes          [emitted]  

But the hot-update.json only contains the following lines

{"h":"b058ff23a19307486590","c":{}}

And the logs told me that nothing was updated:

[WDS] App updated. Recompiling...
client:80
[WDS] App hot update...
client:212
[HMR] Checking for updates on the server...
log.js:23
[HMR] Nothing hot updated.
log.js:23
[HMR] App is up to date.
log.js:23

Any idea?

tina-junold avatar Sep 29 '17 00:09 tina-junold

@tburschka Hmm, we just need to make sure that my hack was in fact the right hack (and that it's not the problem). What happens if you console.log(config.module.rules[1].use)? I want to make sure that looks right.

Also, did you try my hack more directly? e.g.

config.module.rules[1].use = ['css-hot-loader'].concat(config.module.rules[1].use);

What you had was probably identical to this in practice, but just in case... :)

weaverryan avatar Oct 06 '17 20:10 weaverryan

@weaverryan i've found the solution. in my case, i'm not using .enableSassLoader() anymore but adding the hot loader direct:

const Glob           = require('glob');
const Path           = require('path');
const Encore         = require('@symfony/webpack-encore');
const ExtractText    = require("extract-text-webpack-plugin");
const AssetsHostname = process.env.ASSETS_HOSTNAME || 'localhost';

Encore
    .setOutputPath('web/static/')
    .setPublicPath(Encore.isProduction() ? '/static' : 'http://' + AssetsHostname + '/static')
    .setManifestKeyPrefix('/static')
    .cleanupOutputBeforeBuild()
    .autoProvidejQuery()
    .enableSourceMaps(!Encore.isProduction())
    .enableVersioning(Encore.isProduction())
;

Encore.createSharedEntry('shared', Glob.sync('./{app,src,vendor}/**/assets/shared.js'));
let alias = { app: Path.resolve(__dirname, 'app/Resources/assets') };
for (let entryPath of Glob.sync('./{src,vendor}/**/assets/!(shared)*.js')) {
    const regex   = new RegExp('/(.*\/(.*)Bundle\/Resources\/assets)\/(.*).js');
    const matches = regex.exec(entryPath);
    alias[(matches[2] + 'bundle').toLowerCase()] = Path.resolve(__dirname, matches[1]);
    Encore.addEntry((matches[2] + matches[3]).toLowerCase(), entryPath);
}

let config = Encore.getWebpackConfig();

config.resolve.alias = Object.assign(config.resolve.alias, alias);
config.watchOptions = { poll: true, ignored: /node_modules/ };
config.devServer = {
    contentBase: Path.join(__dirname, 'web'),
    host: AssetsHostname,
    port: 80,
    headers: {
	"Access-Control-Allow-Origin": "*",
	"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
	"Access-Control-Allow-Headers": "X-Requested-With, content-type, Authorization"
    },
    open: false,
    overlay: true,
    compress: true
};
config.module.rules.push({
    test: /\.(s?c|sa)ss$/,
    use: ['css-hot-loader'].concat(ExtractText.extract({
	fallback: 'style-loader',
	use: ['css-loader', 'sass-loader'],
    }))
});
config.module.rules.push({
    test: /\.(eot|svg|ttf|woff2?)$/,
    loader: 'file-loader'
});


module.exports = config;

The second thing i've needed to to is to ensure that for javascript i've add in the specific entry point the following snipped:

if (module.hot)
    module.hot.accept();

tina-junold avatar Oct 06 '17 20:10 tina-junold

Awesome! And this works really well?

Can you tell me more about why the module.hot.accept() is needed?

weaverryan avatar Oct 12 '17 16:10 weaverryan

It's documented here https://webpack.js.org/api/hot-module-replacement/#accept but you can find lot of examples on stackoverflow...

tina-junold avatar Oct 13 '17 06:10 tina-junold

What about the integration of this inside encore ? With Vuejs, it really lacks styles update.

Rebolon avatar Jan 24 '18 09:01 Rebolon

It's on the TODO list :). HMR has some complexities because we always dump .css files, even in dev, instead of using the style-loader trick. That gives us consistency across environments (you always get styles from real .css files). But, we need to do some extra work to get HMR rocking.

weaverryan avatar Jan 25 '18 20:01 weaverryan

Hey I know it's been a while, but I was stressed that HMR didn't work for style, so I went take a look around and came up with this solution,

const Encore        = require('@symfony/webpack-encore');

Encore
    // .... your config here

    // You have to disable the sass loader
    // .enableSassLoader()

    // enable source maps during development
   // I didn't try without it
    .enableSourceMaps(!Encore.isProduction())

    .enableVueLoader(function(options) {
        options.loaders['scss'] = ['vue-style-loader',
                {loader: 'css-loader', options: {sourceMap: true}}, //Probably should change here with (!Encore.isProduction())
                {loader: 'sass-loader', options: {sourceMap: true}}
            ];
        options.loaders['sass'] = options.loaders['scss'];
    })

You need to disable the Encore sassLoader then force let the vue-style-loader taking care of the style. It works for my project but I wanna know if that could work for an another project :)

henri-ly avatar Apr 20 '18 13:04 henri-ly

Yea, that's pretty valid... basically HMR doesn't work with the css-loader, but works fine (and is intended for) the normal style-loader. We chose to use the css-loader consistently, because I think it's a bit weird to not need link tags in dev, but suddenly need them in production. But, this is totally valid

And, it does highlight a second possible approach to HMR: allow people to opt in to disabling the css-loader in dev... which would make HMR for styles just, work (basically an option to do what you're doing).

weaverryan avatar Apr 20 '18 21:04 weaverryan

Anyone have luck getting it to work with LESS?

I tried modifying @henri-ly's approach, but no go...

lakefield avatar Jun 29 '18 20:06 lakefield

@weaverryan have you taken a look at this project: https://github.com/man27382210/watchFile-webpack-plugin I'm not sure if I fully understand the issue, but if I did, this should be able to also use it for all kind of files (including changes in twig I think, not sure how the recompiling would be handled there though)

pascalwacker avatar Jul 05 '18 17:07 pascalwacker

Just fixed it like this:

const webpackConfig = Encore.getWebpackConfig()
for (let rule of webpackConfig.module.rules) {
	if (rule.test.toString() === '/\\.vue$/') {
		rule.use = ['css-hot-loader'].concat(rule.use)
	}
}

Grawl avatar Oct 31 '18 10:10 Grawl

@Grawl Vue seems to manage HMR directly with webpack 4. So the only thing to do for me is this:

for (const rule of config.module.rules) {
  if (rule.test.toString() === '/\\.s[ac]ss$/') {
    rule.use = ['css-hot-loader'].concat(rule.use);
  }
}

soullivaneuh avatar Jan 07 '19 11:01 soullivaneuh

@Soullivaneuh cool! So it's time to upgrade to v0.21.0 or latest version of Encore

Grawl avatar Jan 08 '19 09:01 Grawl

Starting with 0.24 the above concat based solutions had to be updated a little to this at least for me:

for (const rule of config.module.rules) {
    if (rule.test.toString() === '/\\.s[ac]ss$/') {
        rule.oneOf.forEach(function(i) {
            i.use = ['css-hot-loader'].concat(i.use);
        })
    }
}

Following this change: https://github.com/symfony/webpack-encore/pull/508/files#diff-8beacd21a12ca072bafa4e8e3f1aae6b

vlajos avatar Mar 08 '19 14:03 vlajos

need hmr css please

ohot2015 avatar Apr 29 '19 14:04 ohot2015

@ohot2015 You can already use it by calling disableCssExtraction() and then running yarn encore dev-server --hot:

if (Encore.isDevServer()) {
    Encore.disableCssExtraction();
}

There is a PR that could make it work with the CSS extraction enabled, but it's kinda stuck because it leads to inconsistent hashes in filenames.

Lyrkan avatar Apr 29 '19 15:04 Lyrkan

We should document this... and maybe also make disableCssExtraction() have a boolean first argument so you can use Encore.disableCssExtraction(Encore.isDevServer)

weaverryan avatar Apr 30 '19 17:04 weaverryan

Hey Guys, so what is required to make this work with CSS?

aniolekx avatar Oct 24 '19 06:10 aniolekx

@aniolekx: Hey Guys, so what is required to make this work with CSS?

Use dev server and disable CSS extraction. That's all. See https://github.com/symfony/webpack-encore/issues/3#issuecomment-487617913

b1rdex avatar Oct 24 '19 06:10 b1rdex

@aniolekx: Hey Guys, so what is required to make this work with CSS?

Use dev server and disable CSS extraction. That's all. See #3 (comment)

and what about Sass loader?

aniolekx avatar Oct 25 '19 05:10 aniolekx

Works for me as well.

пт, 25 окт. 2019 г. в 15:07, aniolekx [email protected]:

@aniolekx https://github.com/aniolekx: Hey Guys, so what is required to make this work with CSS?

Use dev server and disable CSS extraction. That's all. See #3 (comment) https://github.com/symfony/webpack-encore/issues/3#issuecomment-487617913

and what about Sass loader?

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/symfony/webpack-encore/issues/3?email_source=notifications&email_token=AACMMF2DVUQ3XZLOFYNBWPLQQJ5IDA5CNFSM4DO56ZT2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOECHGH4A#issuecomment-546202608, or unsubscribe https://github.com/notifications/unsubscribe-auth/AACMMF2F6NULOAFFJLKLXATQQJ5IDANCNFSM4DO56ZTQ .

-- Анатолий Пашин.

b1rdex avatar Oct 25 '19 05:10 b1rdex

and what about Sass loader?

@aniolekx it does work with Sass loader for me.

versedi avatar Oct 25 '19 10:10 versedi

Has anyone got this working on webpack 5? As I found, mini-css-extract-plugin supports HMR w/ webpack 5 so disableCssExtraction() isn't needed anymore.

b1rdex avatar Jan 29 '21 07:01 b1rdex

Does Symfony with Webpack Encore support HMR for React? Everyone talks about Vue here 😅

rwieruch avatar Aug 18 '23 20:08 rwieruch