Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Versioning manifest.json

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

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

165 lines webpack.config.js
... lines 1 - 4
const ManifestPlugin = require('webpack-manifest-plugin');
... lines 6 - 165

Below, in plugins, very simply, say new ManifestPlugin():

165 lines webpack.config.js
... 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!

copy-webpack-plugin & manifest.json

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.

Always emitting manifest.json

Oh, and quickly, add one option to the plugin: writeToFileEmit: true:

168 lines webpack.config.js
... 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!

Reading manifest.json

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?

The basePath Option

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

169 lines webpack.config.js
... 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.

Leave a comment!

4
Login or Register to join the conversation
thephilosoft Avatar
thephilosoft Avatar thephilosoft | posted 9 months ago

The joy of this tutorial is endless 😂 (as is the case with much of JavaScript in general and "browser-facing" JS specifically)

  • start small and simple, but with "legacy"
  • improve one thing
  • add cooooouple of lines to webpack.config.js
  • break one or two more things
  • fix them with couple more lines of config or extra libraries
  • occasionally hear "They MAY add support in the future" or that something else was broken in the process
  • rinse, repeat... victory?)
Reply
Jovan P. Avatar
Jovan P. Avatar Jovan P. | posted 5 years ago

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! =)

Reply
Jovan P. Avatar

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

Reply

Hey Jovan,

Yeah, looks like does not work yet. I think I found an opened issue that related to it: https://github.com/webpack-...

1 Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial explains the concepts of an old version of Webpack using an old version of Symfony. The most important concepts are still the same, but you should expect significant differences in new versions.

What PHP libraries does this tutorial use?

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

What JavaScript libraries does this tutorial use?

// 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
    }
}
userVoice