If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeIf you read the documentation about the webpack-dev-server, you'll read about HMR: heavy metal rock. Or, actually, in this context, it means hot module replacement. HMR is amazing: it allows Webpack to update your page... without refreshing! But... you need to configure your system correctly to be able to do this. And, unless you do even more setup, it only works for CSS. You can make it work for JavaScript - especially if you're using a framework like React or Vue, but you won't get it out of the box.
Anyways, let's get HMR working for CSS at least.
To activate it, stop the dev server and re-run it with a --hot
flag... because HMR is the hotness... and also because it stands for hot module replacement:
./node_modules/.bin/webpack-dev-server --hot
Go refresh the browser to reset things. Now, make a change to our CSS and move back to the browser:
$brand-primary: darken(#428bca, 2.5%); // #3885c7 | |
... lines 2 - 82 |
Huh, it still refreshed. It's not working!
Go to your browser's console, click the gear icon and check "Preserve logs". Thanks to this, the console will not clear logs when the page reloads.
Refresh once more... wait for the page to load... and then clear the console. Go make another change to the app, then move back. It says "App updated", "Recompiling", but then it refreshes the whole page!
These messages are coming from the HMR system. And check this out: it says "Checking for updates on the server", and then you see a failed request to http://localhost:8000
. Finally, it says "Cannot find Update" and it refreshes the page.
What's going on? In addition to our built assets, the dev server - so localhost:8080
- serves a JSON file - this *.hot-update.json
- with information about what was just updated.
So, why is that failing? Well, since our site actually lives at localhost:8000
, when Webpack makes an AJAX call for this file, it makes it to localhost:8000
instead of localhost:8080
. Internally, Webpack doesn't know that all of our assets are being hosted on some other host!
This is actually more important than just the dev server. Sometimes - like with a cool feature called code splitting that we'll see later - Webpack needs to make an AJAX call back to the server. In order to do this, it must know what the public URL is to your assets. Actually, we've already set this in webpack.config.js
. In the output
section, we set publicPath
to /build/
:
... lines 1 - 30 | |
module.exports = { | |
... lines 32 - 36 | |
output: { | |
... lines 38 - 39 | |
publicPath: '/build/' | |
}, | |
... lines 42 - 117 | |
}; |
So... what's the problem? That is our public path, right? Not anymore! If you're using the webpack-dev-server
- or if you're using a CDN on production - this publicPath
must be absolute: it needs to include the host name where your assets are really stored. This is used for any Webpack AJAX calls and also to point to assets, like font files from inside CSS. That's why we saw some font file 404's in the last chapter.
Let's fix this! Above the config, set two new variables. First, set useDevServer
to true
:
... lines 1 - 4 | |
const useDevServer = true; | |
... lines 6 - 32 | |
module.exports = { | |
... lines 34 - 123 | |
}; |
That will allow us to easily turn this off later. Then, add const publicPath =
. If devServer
is true, set this to http://localhost:8080/build/
. Otherwise, use the normal /build/
:
... lines 1 - 4 | |
const useDevServer = true; | |
const publicPath = useDevServer ? 'http://localhost:8080/build/' : '/build/'; | |
... lines 7 - 32 | |
module.exports = { | |
... lines 34 - 123 | |
}; |
Down below, use this variable for publicPath
:
... lines 1 - 4 | |
const useDevServer = true; | |
const publicPath = useDevServer ? 'http://localhost:8080/build/' : '/build/'; | |
... lines 7 - 32 | |
module.exports = { | |
... lines 34 - 38 | |
output: { | |
... lines 40 - 41 | |
publicPath: publicPath, | |
}, | |
... lines 44 - 123 | |
}; |
And while we're here, scroll down to devServer
and add one new key called headers
. Add a single header: Access-Control-Allow-Origin
set to *
:
... lines 1 - 4 | |
const useDevServer = true; | |
const publicPath = useDevServer ? 'http://localhost:8080/build/' : '/build/'; | |
... lines 7 - 32 | |
module.exports = { | |
... lines 34 - 38 | |
output: { | |
... lines 40 - 41 | |
publicPath: publicPath, | |
}, | |
... lines 44 - 119 | |
devServer: { | |
... line 121 | |
headers: { 'Access-Control-Allow-Origin': '*' }, | |
} | |
}; |
Since our site is served on a different host than our assets... well really a different port... CORS security will prevent some requests from working. This header will allow those requests to be made.
Phew! Restart the dev server one more time. Then, manually refresh the page to reset everything. Clear out the log messages.
Ready for this? Go back to main.scss
, make a change, then come back:
$brand-primary: darken(#428bca, 2.5%); // #3885c7 | |
... lines 2 - 82 |
It says "App updated", "Recompiling"... then a few other things. But... it did not refresh! Did it work? I'm not sure! Let's make a more dramatic change: how about 2% to 99%:
$brand-primary: darken(#428bca, 2.5%); // #3885c7 | |
... lines 2 - 82 |
Move back to your browser. Wow! The button turns nearly black... without reloading! This is HMR.
But, as I mentioned earlier, this will only work for CSS. You can get this to work with JavaScript - especially if you're using a front-end framework, But, that's beyond this tutorial.
Next! Let's move our app towards being ready for production, by fixing the annoying CSS delay when we refresh.
// composer.json
{
"require": {
"php": "^7.2.0",
"symfony/symfony": "3.3.*", // v3.3.16
"twig/twig": "2.10.*", // v2.10.0
"doctrine/orm": "^2.5", // v2.7.0
"doctrine/doctrine-bundle": "^1.6", // 1.10.3
"doctrine/doctrine-cache-bundle": "^1.2", // 1.3.5
"symfony/swiftmailer-bundle": "^2.3", // v2.6.3
"symfony/monolog-bundle": "^2.8", // v2.12.1
"symfony/polyfill-apcu": "^1.0", // v1.4.0
"sensio/distribution-bundle": "^5.0", // v5.0.22
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.26
"incenteev/composer-parameter-handler": "^2.0", // v2.1.2
"friendsofsymfony/user-bundle": "^2.0", // v2.1.2
"doctrine/doctrine-fixtures-bundle": "~2.3", // v2.4.1
"doctrine/doctrine-migrations-bundle": "^1.2", // v1.3.2
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"friendsofsymfony/jsrouting-bundle": "^1.6" // 1.6.0
},
"require-dev": {
"sensio/generator-bundle": "^3.0", // v3.1.6
"symfony/phpunit-bridge": "^3.0" // v3.3.5
}
}
// package.json
{
"dependencies": [],
"devDependencies": {
"babel-core": "^6.25.0", // 6.25.0
"babel-loader": "^7.1.1", // 7.1.1
"babel-plugin-syntax-dynamic-import": "^6.18.0", // 6.18.0
"babel-preset-env": "^1.6.0", // 1.6.0
"bootstrap-sass": "^3.3.7", // 3.3.7
"clean-webpack-plugin": "^0.1.16", // 0.1.16
"copy-webpack-plugin": "^4.0.1", // 4.0.1
"core-js": "^2.4.1", // 2.4.1
"css-loader": "^0.28.4", // 0.28.4
"extract-text-webpack-plugin": "^3.0.0", // 3.0.0
"file-loader": "^0.11.2", // 0.11.2
"font-awesome": "^4.7.0", // 4.7.0
"jquery": "^3.2.1", // 3.2.1
"lodash": "^4.17.4", // 4.17.4
"node-sass": "^4.5.3", // 4.5.3
"resolve-url-loader": "^2.1.0", // 2.1.0
"sass-loader": "^6.0.6", // 6.0.6
"style-loader": "^0.18.2", // 0.18.2
"sweetalert2": "^6.6.6", // 6.6.6
"webpack": "^3.4.1", // 3.4.1
"webpack-chunk-hash": "^0.4.0", // 0.4.0
"webpack-dev-server": "^2.6.1", // 2.6.1
"webpack-manifest-plugin": "^1.2.1" // 1.2.1
}
}
that is cool, if I would be often working with css and JS, I would want it. Often pages are heavy, so to reload all page, takes lot of time,, especially in dev environment