Webpack-encore: Add HMR / Hot Support

Created on 12 Jun 2017  ·  27Comments  ·  Source: symfony/webpack-encore

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.

feature

Most helpful comment

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

All 27 comments

I propose a patch here for HMR support.

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.

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 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?

@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 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();

Awesome! And this works really well?

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

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

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

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.

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 :)

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).

Anyone have luck getting it to work with LESS?

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

@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)

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 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 cool! So it's time to upgrade to v0.21.0 or latest version of Encore

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

need hmr css please

@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.

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

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

@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

@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?

Works for me as well.

пт, 25 окт. 2019 г. в 15:07, aniolekx notifications@github.com:

@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
.

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

and what about Sass loader?

@aniolekx it does work with Sass loader for me.

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

heitorvrb picture heitorvrb  ·  4Comments

Growiel picture Growiel  ·  3Comments

iammichiel picture iammichiel  ·  3Comments

wenmingtang picture wenmingtang  ·  4Comments

rebangm picture rebangm  ·  4Comments