-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Asset] Adding a new version strategy that reads from a manifest JSON file #22046
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A big 👍 from me!
This is so common in modern setups that Symfony should really provide this asset versioning.
I also agree with being forgiving and returning the same path when it doesn't exist in the manifest file.
@@ -589,6 +597,12 @@ private function addAssetsSection(ArrayNodeDefinition $rootNode) | |||
}) | |||
->thenInvalid('You cannot use both "version_strategy" and "version" at the same time under "assets" packages.') | |||
->end() | |||
->validate() | |||
->ifTrue(function ($v) { | |||
return isset($v['version_strategy']) && $v['version_strategy'] == 'json_manifest' && !isset($v['manifest_path']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
== 'json_manifest'
-> === 'json_manifest'
@@ -558,6 +559,12 @@ private function addAssetsSection(ArrayNodeDefinition $rootNode) | |||
}) | |||
->thenInvalid('You cannot use both "version_strategy" and "version" at the same time under "assets".') | |||
->end() | |||
->validate() | |||
->ifTrue(function ($v) { | |||
return isset($v['version_strategy']) && $v['version_strategy'] == 'json_manifest' && !isset($v['manifest_path']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
== 'json_manifest'
-> === 'json_manifest'
{ | ||
"main.js": main.123abc.js", | ||
"css/styles.css": "css/styles.555def.css" | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should add a new line at the end of this file.
@@ -0,0 +1,4 @@ | |||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's clever to not use .json
extension to avoid fabbot issues ... but that could complicate the maintenance of this. Imagine looking at this conde in 1 year and wondering: why this file doesn't have an extension? Is this an error? Should I add the extension now? etc.
So, let's add the .json
extension and forget about fabbot issues.
Looks to replace https://github.com/irozgar/gulp-rev-versions-bundle. A useful feature would be to compile the manifest into a static array, to avoid reading it at runtime (in production). |
This changes the meaning of the |
{ | ||
$manifestPath = $this->getManifestPath($path); | ||
|
||
return $manifestPath ? $manifestPath : $path; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return $manifestPath ?: $path;
|
||
private $manifestData; | ||
|
||
public function __construct($manifestPath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please add the phpdoc describing the argument as a string, as we don't have a typehint to tell it
} | ||
|
||
$this->manifestData = json_decode(file_get_contents($this->manifestPath), true); | ||
if (0 < $errorCode = json_last_error()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$errorCode
is unused
{ | ||
$def = new ChildDefinition('assets.json_manifest_version_strategy'); | ||
$def | ||
->replaceArgument(0, $manifestPath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, we should also make this service private, to allow it to be inlined
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, but I copied this code from the above method, which is not private. So, might there be a reason for it to be public?
symfony/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
Lines 843 to 856 in 4927993
private function createPackageDefinition($basePath, array $baseUrls, Reference $version) | |
{ | |
if ($basePath && $baseUrls) { | |
throw new \LogicException('An asset package cannot have base URLs and base paths.'); | |
} | |
$package = new ChildDefinition($baseUrls ? 'assets.url_package' : 'assets.path_package'); | |
$package | |
->setPublic(false) | |
->replaceArgument(0, $baseUrls ?: $basePath) | |
->replaceArgument(1, $version) | |
; | |
return $package; |
I'm going to try making it private.
Awesome feature that I've seen implemented in many projects, this JSON format is so simple that it is always the one that is used. Is the solution to define an asset package for each manifest file ? |
@GromNaN is having multiple manifest still common? I only know the Webpack solution and they create just 1 |
…m a JSON manifest file
…ing the invalid JSON
(fabbot dislikes invaild JSON)
…s all that's needed to activate This keeps the original behavior of version_strategy: this is always a service id
7a652ae
to
99251c3
Compare
PR Updated!
This is ready to be reviewed again! Thanks! |
@@ -582,6 +595,7 @@ private function addAssetsSection(ArrayNodeDefinition $rootNode) | |||
->end() | |||
->end() | |||
->scalarNode('version_format')->defaultNull()->end() | |||
->scalarNode('json_manifest_path')->defaultNull()->end() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just asking: would manifest_path
be a more future-proof name than json_manifest_path
? Or is this too much future thinking? Can we expect to support other formats different than JSON?
Same for JsonManifestVersionStrategy
-> ManifestVersionStrategy
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a good thought. I don't know! It's a fairly easy thing to deprecate and change in the future anyways, and I can't see any other format in the near future. So let's go with what we like best right now. I changed to json_manifest_path
because I thought it might be more clear what this manifest thing should be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm as json*
… way is in 2 method calls
@javiereguiluz @weaverryan My actual use case is the following: Regarding the usage of a PHP file to cache the content of the JSON into a PHP array, from my experience on high traffic websites there is no performance issue at reading the same JSON file on each request: the OS caches the file content in memory and native JSON parsing is very fast. |
@GromNaN Hmm, in that case, it would be most convenient for the listener to be able to change the manifest file. If you created a default package and a // in the listener... if mobile
// using the container for clarity
$packages = $this->container->get('assets.packages');
$packages->setDefaultPackage($packages->getPackage('mobile')); And then your templates wouldn't need to specify a package everywhere. Of course, since they're using different templates, they could just specify the I think this is actually a pretty decent solution. In other words, I think this PR solves the 99%, and makes the 1% reasonably easy. Thanks for the reply :) |
@weaverryan For the invalid JSON file, just add it to |
@@ -4,6 +4,8 @@ CHANGELOG | |||
3.3.0 | |||
----- | |||
|
|||
* A new version strategy option called json_manifest_path was added |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added a new ...
@@ -1,6 +1,11 @@ | |||
CHANGELOG | |||
========= | |||
|
|||
3.3.0 | |||
----- | |||
* JsonManifestVersionStrategy was added as a way to read final, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added Json...
* "css/styles.css": "css/styles.555abc.css" | ||
* } | ||
* | ||
* You could then as for the version of "main.js" or "css/styles.css". |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: ask?
class JsonManifestVersionStrategy implements VersionStrategyInterface | ||
{ | ||
private $manifestPath; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
empty line can be removed
private $manifestData; | ||
|
||
/** | ||
* @param string $manifestPath Absolute path to the manifest file. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no dot at the end
* | ||
* @param string $path | ||
* | ||
* @return string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The manifest could be a remote file accessible over HTTP ; that appends when assets and the app are deployed on distinct servers asynchronously. In that case, it is necessary to use an HTTP client (other that file_get_contents) and a cache layer. |
@GromNaN I would say that we don't want to support that use case. |
👍 |
@GromNaN I thought about that use-case, it's also relevant with webpack-dev-server, but ultimately, I didn't want it, at least for now: the user should make sure there's a physical JSON file - it could be part of their deploy process (or, they can of course add their own strategy!) Tweaks made! But fabbot and I are still fighting - is the JSON check done through |
@fabpot if my silly invalid JSON file is going to be a problem with fabbot, let me know - I could re-work things - i.e. save a tmp invalid JSON file in the test and use that (so no committed invalid JSON file). |
@weaverryan Don't worry about that. I will see why it's not taken into account by fabbot. Keep the code as is. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 with optional comments
@@ -4,6 +4,8 @@ CHANGELOG | |||
3.3.0 | |||
----- | |||
|
|||
* Added a new new version strategy option called json_manifest_path |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
quotes around "json_manifest_path"?
@@ -4,6 +4,8 @@ CHANGELOG | |||
3.3.0 | |||
----- | |||
|
|||
* Added a new new version strategy option called json_manifest_path | |||
that allows you to use the JsonManifestVersionStrategy. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
backticks here?
@@ -582,6 +595,7 @@ private function addAssetsSection(ArrayNodeDefinition $rootNode) | |||
->end() | |||
->end() | |||
->scalarNode('version_format')->defaultNull()->end() | |||
->scalarNode('json_manifest_path')->defaultNull()->end() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm as json*
if (null !== $jsonManifestPath) { | ||
$def = new ChildDefinition('assets.json_manifest_version_strategy'); | ||
$def->replaceArgument(0, $jsonManifestPath); | ||
$def->setPublic(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move to xml?
{ | ||
$manifestPath = $this->getManifestPath($path); | ||
|
||
return $manifestPath ?: $path; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could be a oneliner :)
Minor comments addressed! |
Thank you @weaverryan. |
…tion (robfrawley) This PR was submitted for the master branch but it was merged into the 3.3 branch instead (closes #8004). Discussion ---------- [WIP] Document "framework.assets.json_manifest_path" option This pull request adds initial documentation for the newly introduced asset JSON manifest file path option: `framework.assets.json_manifest_path`. Resolves #7659. For reference on this new configuration option, see symfony/symfony#22046 and [symfony.com/blog/new-in-symfony-3-3-manifest-based-asset-versioning](https://symfony.com/blog/new-in-symfony-3-3-manifest-based-asset-versioning). Commits ------- 13a3070 document new framework.assets.json_manifest_path option
Hi guys!
Often, when using a frontend task manager or bundler (e.g. webpack of gulp), the final assets are dumped with a version or content hash in the filename itself (e.g. main.123abc.css). To know what the correct, current hashed filename is, you'll dump a
manifest.json
file - e.g.Examples: gulp-rev and webpack-manifest-plugin.
This PR adds a new version strategy that will look up the asset path (e.g.
main.css
) in that file and return the final, versioned path. Some people may dump manifest files in other formats, but I think this catches the most common use-case (and you can always still create your own version strategy). I've written this to be "forgiving" - if a path doesn't exist in the manifest, the path is simply returned, unaltered.Another implementation could have been to add a new Twig filter (e.g.
{{ asset('main.css|manifest_path) }}
) - but I thought I'd try first using the existing versioning system.Usage
TODO
.json
suffix :)