symfony / webpack-encore

A simple but powerful API for processing & compiling assets built around Webpack
https://symfony.com/doc/current/frontend.html
MIT License
2.23k stars 199 forks source link

Add HMR / Hot Support #3

Open weaverryan opened 7 years ago

weaverryan commented 7 years ago

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 commented 7 years ago

I propose a patch here for HMR support.

TheMaxoor commented 7 years ago

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 commented 7 years ago

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!

tina-junold commented 7 years ago

@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 commented 7 years ago

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

tina-junold commented 7 years ago

@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 commented 7 years ago

Awesome! And this works really well?

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

tina-junold commented 7 years ago

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

Rebolon commented 6 years ago

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

weaverryan commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

Anyone have luck getting it to work with LESS?

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

pascalwacker commented 6 years ago

@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 commented 6 years ago

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 commented 5 years ago

@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 commented 5 years ago

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

vlajos commented 5 years ago

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

ohot2015 commented 5 years ago

need hmr css please

Lyrkan commented 5 years ago

@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 commented 5 years ago

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

aniolekx commented 5 years ago

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

b1rdex commented 5 years ago

@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 commented 5 years ago

@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 commented 5 years ago

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 .

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

versedi commented 5 years ago

and what about Sass loader?

@aniolekx it does work with Sass loader for me.

b1rdex commented 3 years ago

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 commented 1 year ago

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

carsonbot commented 1 month ago

Thank you for this suggestion. There has not been a lot of activity here for a while. Would you still like to see this feature? Every feature is developed by the community. Perhaps someone would like to try? You can read how to contribute to get started.