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 SubscribeOur script
and link
tags need to be updated for the new filenames. But... those filenames are now constantly changing!
Hmm. We need a map: a map to treasure. But also, another map that translates each original filename to the current, hashed filename. That map is called a manifest. And a new library - webpack-manifest-plugin
- can create it!
Copy the package name, then find your terminal. Install it!
yarn add webpack-manifest-plugin --dev
Perfect! At the top of webpack.config.js
, import that with const ManifestPlugin = require()
and then the library name:
... lines 1 - 4 | |
const ManifestPlugin = require('webpack-manifest-plugin'); | |
... lines 6 - 165 |
Below, in plugins
, very simply, say new ManifestPlugin()
:
... lines 1 - 4 | |
const ManifestPlugin = require('webpack-manifest-plugin'); | |
... lines 6 - 38 | |
const webpackConfig = { | |
... lines 40 - 106 | |
plugins: [ | |
... lines 108 - 133 | |
new ManifestPlugin(), | |
], | |
... lines 136 - 140 | |
}; | |
... lines 142 - 165 |
That is it. Go stop webpack and restart it:
yarn dev
Ok, it dumped the same files... but now we have a new file: manifest.json
! This thing is perfect! It's a map from the original filename to the current, hashed filename. It's almost as good as a treasure map! If we can get our server-side code to read this map... we are in business!
Oh, and just a note: any files copied by the copy-webpack-plugin
are not in the manifest. They may add support for this to webpack-manifest-plugin
in the future. But right now, even though it is possible to add hashes to the copied filenames... they won't show up in the manifest.
Oh, and quickly, add one option to the plugin: writeToFileEmit: true
:
... lines 1 - 38 | |
const webpackConfig = { | |
... lines 40 - 106 | |
plugins: [ | |
... lines 108 - 133 | |
new ManifestPlugin({ | |
// always dump manifest | |
writeToFileEmit: true | |
}), | |
], | |
... lines 139 - 143 | |
}; | |
... lines 145 - 168 |
If you're using the webpack-dev-server
, no files are written to web/build
... including manifest.json
. With this option, that file is always written. We need that... because we're about to read it inside our app!
Open up the base layout: app/Resources/views/base.html.twig
. Whenever we reference an asset filename, we wrap it in this asset()
function:
... lines 1 - 2 | |
<head> | |
... lines 4 - 10 | |
{% block stylesheets %} | |
<link rel="stylesheet" href="{{ asset('build/layout.css') }}"> | |
{% endblock %} | |
... lines 14 - 15 | |
</head> | |
... lines 17 - 106 |
Thanks to that, in Symfony 3.3, we can configure a system that will look for that filename in manifest.json
and replace it with the hashed version!
To activate it, open app/config/config.yml
and, under assets
, add a key called json_manifest_path
set to %kernel.project_dir%
- that points to the root of our project - /web/build/manifest.json
:
... lines 1 - 10 | |
framework: | |
... lines 12 - 33 | |
assets: | |
json_manifest_path: '%kernel.project_dir%/web/build/manifest.json' | |
... lines 36 - 86 |
And... that's it! Go back and... refresh! It works! Wait.. no it doesn't! What's up?
View the page source. Huh... it's still not using the hashed filename. When you use the json_manifest_path
system, it literally takes whatever string is passed to asset()
- so build/layout.css
- and looks for that in manifest.json
. Oh! But the key is just layout.css
: there is no build/
! Can we add that prefix? Yep! With an option called basePath
set to build/
:
... lines 1 - 38 | |
const webpackConfig = { | |
... lines 40 - 106 | |
plugins: [ | |
... lines 108 - 133 | |
new ManifestPlugin({ | |
basePath: 'build/', | |
... lines 136 - 137 | |
}), | |
], | |
... lines 140 - 144 | |
}; | |
... lines 146 - 169 |
Tip
In a future version of the plugin, you may need to also set publicPath
to build/
.
Re-run webpack:
yarn dev
Check out the manifest file. It's beautiful! And... refresh! Yes! It updates the path perfectly! Guys! We have built-in versioning! Whenever we update our files, Webpack will write a new filename and our app will use it.
This is great. But to take full advantage of it, you need to configure your web server to set a far-future Expires
headers for everything in your /build
directory... except for /build/static
.
1. Hears "Any files copied by the CopyWebpackPlugin are not in manifet. They MAY add support in the future.."
2. Quickly opens up the manifest and spots the static files (with hashes)
3. Celebrates out loud! =)
Aaand that happens when I hurry up with celebration!
"build/static/curve.4a0a43.png": "/build/static/curve.4a0a43.png"
No wonder, it does not work :D
Hey Jovan,
Yeah, looks like does not work yet. I think I found an opened issue that related to it: https://github.com/webpack-...
// 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
}
}
The joy of this tutorial is endless 😂 (as is the case with much of JavaScript in general and "browser-facing" JS specifically)
webpack.config.js