Skip to content
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

Add HMR / Hot Support #3

Open
weaverryan opened this issue Jun 12, 2017 · 28 comments
Open

Add HMR / Hot Support #3

weaverryan opened this issue Jun 12, 2017 · 28 comments
Labels
Feature New Feature

Comments

@weaverryan
Copy link
Member

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.

@alOneh
Copy link
Contributor

alOneh commented Jun 13, 2017

I propose a patch here for HMR support.

@weaverryan weaverryan added the Feature New Feature label Jul 2, 2017
@TheMaxoor
Copy link
Contributor

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.

@weaverryan
Copy link
Member Author

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!

@tburschka
Copy link

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

@weaverryan
Copy link
Member Author

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

@tburschka
Copy link

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

@weaverryan
Copy link
Member Author

Awesome! And this works really well?

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

@tburschka
Copy link

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

@Rebolon
Copy link

Rebolon commented Jan 24, 2018

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

@weaverryan
Copy link
Member Author

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.

@henri-ly
Copy link

henri-ly commented Apr 20, 2018

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

@weaverryan
Copy link
Member Author

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

@lakefield
Copy link

Anyone have luck getting it to work with LESS?

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

@pascalwacker
Copy link

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

@Grawl
Copy link

Grawl commented Oct 31, 2018

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

@soullivaneuh
Copy link

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

@Grawl
Copy link

Grawl commented Jan 8, 2019

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

@vlajos
Copy link

vlajos commented Mar 8, 2019

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

weaverryan added a commit that referenced this issue Mar 25, 2019
…kan)

This PR was merged into the master branch.

Discussion
----------

Add Encore.disableCssExtraction() to the public API

This PR adds an `Encore.disableCssExtraction()` method that allows to disable the `mini-css-extract-plugin` and use the `style-loader` instead.

It can be used to solve various problems that, until now, required a really ugly workaround that relied on our internal implementation (for instance the following commit probably broke some builds that used previous versions of it: 6867443#diff-8beacd21a12ca072bafa4e8e3f1aae6b).

Related issues: #3, #256, #348, #527

Commits
-------

347feed Add Encore.disableCssExtraction() to the public API
@ohot2015
Copy link

need hmr css please

@Lyrkan
Copy link
Collaborator

Lyrkan commented Apr 29, 2019

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

@weaverryan
Copy link
Member Author

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

@aniolekx
Copy link

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

@b1rdex
Copy link

b1rdex commented Oct 24, 2019

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

@aniolekx
Copy link

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

@b1rdex
Copy link

b1rdex commented Oct 25, 2019 via email

@versedi
Copy link

versedi commented Oct 25, 2019

and what about Sass loader?

@aniolekx it does work with Sass loader for me.

@b1rdex
Copy link

b1rdex commented Jan 29, 2021

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.

@rwieruch
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature New Feature
Projects
None yet
Development

No branches or pull requests