Laravel-mix: Hot reloading not working with laravel/sail [laravel 8]

Created on 29 Dec 2020  ยท  19Comments  ยท  Source: JeffreyWay/laravel-mix

  • Laravel Mix Version: 5.0.9
  • Node Version (node -v): v15.5.0
  • NPM Version (npm -v): 7.3.0
  • OS: Windows with Ubuntu 20.04 WSL2

Description:

Hot reloading is not working using laravel/sail.

Steps To Reproduce:

I have tried with sail npm run watch sail npm run hot and nothing seems to work.

Also when I run npm run hot it output this:

โ„น ๏ฝขwds๏ฝฃ: Project is running at http://localhost:8080/
โ„น ๏ฝขwds๏ฝฃ: webpack output is served from http://localhost:8080/
โ„น ๏ฝขwds๏ฝฃ: Content not from webpack is served from /var/www/html/public
โ„น ๏ฝขwds๏ฝฃ: 404s will fallback to /index.html

but clicking on http://localhost:8080/ actually opens http://localhost:14252/ which is so weird.

I also tried to add EXPOSE 8080 to the Dockerfile and 8080:8080 to the docker-compose.yml then I rebuilt the image and but still not working and I can't see any side effect.

Any blade template I made changes on and save it, there is no autoreload on any browser. I have to activelly reload the page to check every change I made.

Most helpful comment

What finally, simply worked for me was the following:

  1. Add the 8080 port binding to docker-compose.yml:
laravel.test:
        ...
        ports:
            - '${APP_PORT:-80}:80'
            - 8080:8080
        ...
  1. Add the devServer setting to webpack.config.js:
module.exports = {
    ...
    devServer: {
        host: '0.0.0.0',
        port: 8080,
    },
};

Note that the hmrOptions stuff in webpack.mix.js isn't necessary, because it already defaults to using localhost and 8080.

All 19 comments

Hi @moracabanas, I have gotten one step closer to getting this working by:

  • Adding a port binding from 8080:8080 to the docker docker-compose.yml as you mentioned (EXPOSE 8080 isn't required)
  • Adding the following config to webpack.mix.js:
mix.webpackConfig({
    devServer: {
        host: '0.0.0.0',
        port: 8080,
    },
});

Now when I run sail npm run hot I can see that it picks up changes as I make them, and my javascript is now loading the first time in the browser. But currently my browser isn't picking up and replacing the new 'hot' javascript parts.

I will report back if I figure out how to solve this next issue.

Edit: sail npm run watch is a work around for me at the moment which works okay. Yet to figure out how to get HMR working though.

My setup is exactly the same as @moracabanas, except that I am using Laravel Mix 6.0.6.
I am also running Laravel 8 on Sail with Node 15.5.0 and NPM 7.3.0.
And I am also running a Ubuntu 20.04 WSL2 backend op Windows 10.

I finally got this to work (more or less) after tinkering around a lot.

I don't get any output regarding HMR when I run sail npm run hot. But perhaps that is not needed (anymore)?

What I do see is that webpack picks up my changes because I see a fresh Compiled successfully... message when I edit a Vue file.

Initially I couldn't get anything to load when visiting http://localhost:8080. After adding a port binding for 8080 into docker-compose.yml and restarting the containers I could theoretically connect to port 8080 but I wasn't able to access it from my local machine.

I also tried adding the devServer code to webpackConfig(), but that didn't fully work for me. I believe it has to do with the fact that Laravel Mix expects hmrOptions to be set because it will do some things based on that, like create the hot file (it will also automatically set the devServer options behind the scenes if I understand the source correctly).

So I added the following code into mix.options() in webpack.mix.js:

mix.options({
    hmrOptions: {
        host: 'laravel.test',
        port: 8080,
    },
});

_laravel.test is the service name in docker-compose.yml. It also seemed to work if I just put 0.0.0.0 as the host there, but then 0.0.0.0 will be put into the hot file and will be used as the JS src in the HTML, which I cannot access._

By doing the above and after running sail npm run hot I could then visit http://localhost:8080 but it would only return the following: Cannot GET / However... If I request http://localhost:8080/js/app.js I do get a response with compiled JS back.

After that, I found out that I can just visit http://localhost (without port 8080) and if the file hot exists in the public directory (which gets added and contains http://laravel.test:8080) then I can visit the site as usual and I will see http://laravel.test:8080/js/app.js being requested (because of the hot file and the mix() function generating the URL to the JS asset).

So I added laravel.test to my Hosts file (resolve to 127.0.0.1) and then I reloaded the page and I finally saw this message in my JS console: [webpack-dev-server] Hot Module Replacement enabled.

And indeed, after changing something in a Vue template, the change is reflected instantly in the browser!

Hopefully this can help you out too?

It looks like it is currently not possible to easily change the URL to something else besides laravel.test because Laravel Mix seems to use the host and port values from hmrOptions to (1) set the values for webpack-dev-server but also (2) create the hot file which will be used to create a link to the "HMR assets" when the mix() function is called. I think this should probably be configurable.

_PS: if you edit the hot file manually and remove the http or https from the URL, then the mix() function will fallback to http://localhost:8080 instead. Found this out by reading the ยดmix()ยด source code..._

Update! I read the Laravel Mix code a bit better.. It looks like the Webpack Config options can override the options set by hmrOptions... Which means we can simply do this:

webpack.mix.js (or you can replace localhost by something else if you have a custom domain set up in your Hosts file; but it doesn't really matter and localhost should probably always work)

mix.options({
    ...
    hmrOptions: {
        host: 'localhost',
        port: 8080,
    },
});

webpack.config.js (or put this in the webpackConfig() function which might be the default for Mix 5.x)
(you can also put laravel.test instead of 0.0.0.0 this will also work)

module.exports = {
    ...
    devServer: {
        host: '0.0.0.0',
        port: 8080,
    },
};

Those 2 combined, along with the 8080 port binding in docker-compose.yml is now happily working for me!

_Do keep in mind that you just need to visit the normal URL without :8080 appended. I didn't know this at first._

Quick tip: If you also want to reload the browser automatically and quickly when you edit .blade.php files (which I also still have lying around for some parts of my code and you might too?) I found out that this is very easy to set up using the onBeforeSetupMiddleware option of devServer and using chokidar:

const chokidar = require('chokidar');

and in the webpack config:

devServer: {
    host: '0.0.0.0',
    port: 8080,
    onBeforeSetupMiddleware(server) {
        chokidar.watch([
          './resources/views/**/*.blade.php'
        ]).on('all', function() {
          server.sockWrite(server.sockets, 'content-changed');
        })
    },
},

_Definitely beats adding an additional BrowserSync, which would take a bit of effort to get it to work together well with devServer HMR. (I couldn't get it working well, so I looked for alternatives and found chokidar)._

Thanks you for your work you put into the research about this issue. I am looking into it today.
There is something about hot reloading websockets and docker networking hostnames we are missing.
I think this issue is something happening with any containerized environment like Vue, React, Expo and all kind of stuff.
You probably can reach target server on localhost, but the hot reloading stuff which uses websockets and all that underlying stuff is not clear for me yet.
I will try 0.0.0.0 as you mention, because it is internal docker container self IP and and laravel.test as the hostname from the stack becuase of Sail docker-compose.

EDIT: I also used the laravel mix 6 with Laravel 8 upgrade from this guide. And then I downgraded bach to the tailwind patch because I thought it was a Mix 6 issue.

  • Laravel Mix Version: 5.0.1
  • Node Version (node -v): v15.4.0
  • NPM Version (npm -v): 7.0.15
  • OS: Manjaro Linux
  • Docker Version: 20.10.1

The solution in @jameshulse comment solved the problem for me.
But when running sail npm run hot the command takes a lot of time compared to running it outside sail

image

The solution in @jameshulse comment solved the problem for me.
But when running sail npm run hot the command takes a lot of time compared to running it outside sail

image

I will test it too. Maybe this behavior could be related to limited docker resources, in my case I explicitly limited WSL2 to take only 4Gb of ram and 4 cores.

Quick tip: If you also want to reload the browser automatically and quickly when you edit .blade.php files (which I also still have lying around for some parts of my code and you might too?) I found out that this is very easy to set up using the onBeforeSetupMiddleware option of devServer and using chokidar:

const chokidar = require('chokidar');

and in the webpack config:

devServer: {
    host: '0.0.0.0',
    port: 8080,
    onBeforeSetupMiddleware(server) {
        chokidar.watch([
          './resources/views/**/*.blade.php'
        ]).on('all', function() {
          server.sockWrite(server.sockets, 'content-changed');
        })
    },
},

_Definitely beats adding an additional BrowserSync, which would take a bit of effort to get it to work together well with devServer HMR. (I couldn't get it working well, so I looked for alternatives and found chokidar)._

This is not working for me.
I'm testing it in a new project:

After getting running sail up I edit webpack.mix.js with the next content:

const mix = require('laravel-mix');
const chokidar = require('chokidar');

/*
 |--------------------------------------------------------------------------
 | Mix Asset Management
 |--------------------------------------------------------------------------
 |
 | Mix provides a clean, fluent API for defining some Webpack build steps
 | for your Laravel applications. By default, we are compiling the CSS
 | file for the application as well as bundling up all the JS files.
 |
 */

mix.js('resources/js/app.js', 'public/js')
    .postCss('resources/css/app.css', 'public/css', [
        //
    ]);


mix.options({
    hmrOptions: {
        host: 'localhost',
        port: 8080,
    },
});

mix.webpackConfig({
    devServer: {
        host: '0.0.0.0',
        port: 8080,
        onBeforeSetupMiddleware(server) {
            chokidar.watch([
              './resources/views/**/*.blade.php'
            ]).on('all', function() {
              server.sockWrite(server.sockets, 'content-changed');
            })
        },
    },
});

Note I only add your suggested changes following Laravel's webpack config override guide using mix.

Then I run sail npm install + sail npm run hot

image

App is properly working on localhost but hot reloading blades is not happening

This is the latest Laravel 8 with Mix 6.0.6 by default.

It appreciate it so much if anyone could point me in the right direction about this.

This is not working for me.
I'm testing it in a new project:

But HMR does work for you? (For example for Vue files and/or for CSS changes?) And you also set up the 8080 port binding in Docker?

It does look very similar to what I have here. The only difference is that I import the webpackConfig from a separate file, but I don't think that this is an issue:

__webpack.mix.js__

    .webpackConfig(require('./webpack.config'))
    .options({
        postCss: [
            require('postcss-import'),
            require('tailwindcss'),
            require('autoprefixer'),
        ],
        hmrOptions: {
            host: 'localhost',
            port: 8080,
        },
    });

__webpack.config.js__

const path = require('path');
const chokidar = require('chokidar');

module.exports = {
    module: {
        rules: [
            {
                resourceQuery: /blockType=i18n/,
                type: 'javascript/auto',
                loader: '@intlify/vue-i18n-loader',
            }
        ],
    },
    resolve: {
        alias: {
            '@': path.resolve('resources/js'),
        },
    },
    devServer: {
        host: '0.0.0.0',
        port: 8080,
        onBeforeSetupMiddleware(server) {
            chokidar.watch([
              './resources/views/**/*.blade.php'
            ]).on('all', function() {
              server.sockWrite(server.sockets, 'content-changed');
            })
        },
    },
};

There's a few extra rules in there, but they are just for my specific project (postCss, vue-i18n)

Can you tell me what OS and Laravel versions you are running?
And/or could you perhaps share the test project with me?
I'm happy to take a look to see if I can find out why it's behaving differently.

_PS: In case you are only working with CSS and Blade files (not Vue or anything) you could probably also use something like BrowserSync instead_

I got lost as well. After setting up everything as @synio-wesley described above, I can see that changes are detected:

โœ” Compiled Successfully in 219ms
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                                                                                                                                                                                                                                                                                                            File โ”‚ Size     โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                                                                                                                                                                                                                      /js/app.eea2b40d3912bde50a95.hot-update.js โ”‚ 5.51 KiB โ”‚
โ”‚                                                                                                                                                                                                                                                                                                      /js/app.js โ”‚ 1.31 MiB โ”‚
โ”‚                                                                                                                                                                                                                                                                    _js_app.eea2b40d3912bde50a95.hot-update.json โ”‚ 31 bytes โ”‚
โ”‚                                                                                                                                                                                                                                                                                                     css/app.css โ”‚ 62 bytes

I can see the newest version of js file under http://localhost:8080/js/app.js but when I go to localhost:80 there is still old version of app.js.
Also, in public/js/app.js there is an old version of compiled file.
I need to run npm run watch to refresh public/js/app.js.

Maybe this has something to do with the way I embade js script in a blade template:
<script src="/js/app.js"></script>

I've been pulling my hairs out for hours trying to figure out where webpack is puts this app.js file. It must be somewhere if it's available throught http://localhost:8080/js/app.js, right?

Any help would be greatly appreciate ๐Ÿ™


webpack.mix.js

mix.js("resources/js/app.js", "public/js")
    .react()
    .sass("resources/sass/app.scss", "public/css");

mix.options({
    hmrOptions: {
        host: "localhost",
        port: 8080,
    },
});

mix.webpackConfig({
    devServer: {
        host: "0.0.0.0",
        port: 8080,
    },
});

Maybe this has something to do with the way I embade js script in a blade template:
<script src="/js/app.js"></script>

It is very important that you use Laravel's global mix function. This will load hashed/versioned assets on production, and when running HMR in development it will pull in the correct JS instead. So try this:
<script src="{{ mix('/js/app.js') }}"></script>

This is referenced (for production purposes) here: https://laravel.com/docs/8.x/mix#versioning-and-cache-busting

Does that work for you?

Thank you @synio-wesley. I made a step forward.
Hot realoading seems to work but I'm getting a few errors in a web console:

[Error] Wrong url scheme for WebSocket http://localhost:8080/sockjs-node
    WebsocketClient (app.js:35719)
    initWDSSocket (app.js:1536)
    ./node_modules/@pmmmwh/react-refresh-webpack-plugin/client/ErrorOverlayEntry.js (app.js:88)
    (anonymous function) (app.js:37188)
    __webpack_require__ (app.js:36668)
    checkDeferredModulesImpl (app.js:37845)
    (anonymous function) (app.js:37858)
    (anonymous function) (app.js:37865)
    Global Code (app.js:37866)
[Error] SyntaxError: The string did not match the expected pattern.
    (anonymous function) (app.js:35719)
    WebsocketClient (app.js:35719)
    initWDSSocket (app.js:1536)
    ./node_modules/@pmmmwh/react-refresh-webpack-plugin/client/ErrorOverlayEntry.js (app.js:88)
    (anonymous function) (app.js:37188)
    __webpack_require__ (app.js:36668)
    checkDeferredModulesImpl (app.js:37845)
    (anonymous function) (app.js:37858)
    (anonymous function) (app.js:37865)
    Global Code (app.js:37866)
[Error] Failed to load resource: the server responded with a status of 404 (Not Found) (index.mjs.map, line 0)

So my react app is not rendering at all, but I can see that files like app.be58e4ad78c37f4b3a37.hot-update.js are generated automatically when I make a change in the app.js file.

What finally, simply worked for me was the following:

  1. Add the 8080 port binding to docker-compose.yml:
laravel.test:
        ...
        ports:
            - '${APP_PORT:-80}:80'
            - 8080:8080
        ...
  1. Add the devServer setting to webpack.config.js:
module.exports = {
    ...
    devServer: {
        host: '0.0.0.0',
        port: 8080,
    },
};

Note that the hmrOptions stuff in webpack.mix.js isn't necessary, because it already defaults to using localhost and 8080.

@alexpcoleman It works! :+1:

@alexpcoleman It works! ๐Ÿ‘

I tried this method and couldnt get it to work, are you using the command sail npm run watch ?

@cthom-dev try this:

sail npm run hot

Btw, thanks @synio-wesley so much! The use of chokidar is more than what I had expected, combining with @cthom-dev solution, they all work perfectly!

With laravel-mix 6.0.16 running Windows WSL2 with Ubuntu and sail @alexpcoleman solution worked.

mix.webpackConfig({
    ...
    devServer: {
        host: "0.0.0.0"
    }
});

I did have to expose 8080 in docker-compose.yml.

I didn't need to specify the port in devServer config as 8080 seems to be the default.

I think it could be useful for us to have some kind of CLI flag that sets these or for us to somehow detect we're in a docker container / sail and change the dev server defaults. A PR doing this would be very welcome.

I've managed to get this working (kind of) but I have two issues that I haven't seen mentioned yet.

webpack.js

const mix = require('laravel-mix');

mix.js('resources/js/app.js', 'public/js')
    .sass('resources/sass/app.scss', 'public/css')
    .react();

mix.webpackConfig(require('./webpack.config'))

webpack.config.js

module.exports = {
    devServer: {
        host: '0.0.0.0',
        port: 8080,
    },
};

docker-compose.yml

...
        ports:
            - '${APP_PORT:-80}:80'
            - 8080:8080

Problem

  1. Everything compiles fine and my app.js file gets loaded initially, but I can no longer see my Example.js component, which I previously could and in the browser I see the errors below.
  2. When I edit a js file, it doesn't look like app.js is refreshed.

image

Any ideas?

Thanks!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Micaso picture Micaso  ยท  3Comments

nezaboravi picture nezaboravi  ยท  3Comments

Cheddam picture Cheddam  ยท  3Comments

kpilard picture kpilard  ยท  3Comments

RomainGoncalves picture RomainGoncalves  ยท  3Comments