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
[Asset] Adding an Asset Pipeline: versioned/digested files in pure PHP #50112
base: 6.3
Are you sure you want to change the base?
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.
this misses tests for the whole Pipeline system.
src/Symfony/Bundle/FrameworkBundle/Command/AssetsPipelinePublishCommand.php
Outdated
Show resolved
Hide resolved
src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
Outdated
Show resolved
Hide resolved
"php": ">=8.1" | ||
"php": ">=8.1", | ||
"symfony/filesystem": "^5.4|^6.0", | ||
"symfony/mime": "^5.4|^6.0" |
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.
those should be optional dependencies, as only the asset pipeline needs them, which is an optional feature of the component (projects using webpack won't use the asset pipeline).
Alternatively, the AssetPipeline might be big enough to become its own component (depending on symfony/asset), with the additional benefit that FrameworkBundle could then have a smart default to enable it when the package is installed. Projects will generally need either the AssetPipeline or WebpackEncoreBundle but not both
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.
Yup - I'd be interested in what others think about this. If we keep this in Asset
, but make these optional (which is definitely more correct), then users might get met with extra Run "composer require symfony/mime" to use the pipeline
type of messages. Unless we also add an "asset pipeline pack" that contains all of it... but that doesn't make me super excited either.
If I had to lean towards something, it would probably be to split this into a new AssetPipeline
component.
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 would vote for a new component as well.
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.
if the split is just motivated by the extra deps, I think mime and filesystem are too niche in the new pipeline system to be a strong enough reason:
here is the only place where mime is used:
return self::EXTENSIONS_MAP[$extension] ?? $this->mimeTypes->guessMimeType($filePath);
Given the list in EXTENSIONS_MAP
, this means mime is almost never going to be used in practice. Instead of adding the dep to it, I'd rather suggest to throw an exception when an extenstion is unknown, and add a way to configure the mapping.
For filesystem, we're only using isAbsolutePath
, so either we borrow some logic from there, our we're fine with the dep. Borrowing might be my preference.
I'd prefer having all this in the Asset component personally.
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 is not only about the dependencies. It would also allow FrameworkBundle to enable the pipeline automatically based on the installed packages.
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.
And we could ship a recipe with a config file that adds your assets/ as a pipeline path. That recipe flexibility might be another reason - though I still need to think about how we’ll accomplish the mixture of the pipeline recipe, importmaps and stimulus recipes… as each system can technically be used without the others
It's one of my TODO items above :) |
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.
having a look on this :)
nice to get inspired by other ecosystems to improve frontend DX development for "backend" devs ^^
service('assets.pipeline'), | ||
]) | ||
|
||
->set('assets.pipeline.dev_server_subscriber', AssetPipelineDevServerSubscriber::class) |
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.
you describe in PR desc In the dev environment, a controller (technically a listener)
but I fail to see where this is conditionally registered in dev
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.
See the FrameworkExtension which conditionally removes that definition depending on whether the server is enabled or no (which defaults to kernel.debug
)
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.
thx @stof I get it better now
public static function getSubscribedEvents(): array | ||
{ | ||
return [ | ||
// priority higher than RouterListener |
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.
this seems touchy, how we can assert that this comment stay true overtime ?
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.
changing the priority of RouterListener would be a BC break for the ecosystem anyway, as many projects may have listeners that must run either before or after the router.
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.
Yup, so actually not nearly as touchy as it would seem at first!
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.
copy that
* | ||
* Originally sourced from https://github.com/rails/propshaft/blob/main/lib/propshaft/compilers/css_asset_urls.rb | ||
*/ | ||
class CssAssetUrlCompiler implements AssetCompilerInterface |
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.
nitpick, what about StyleSheetAsset as you named JavaScript and not JS in the class below :)
|
||
private function getPublicDirectoryName(string $projectDir): string | ||
{ | ||
$defaultPublicDir = 'public'; |
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.
what about making this variable? still have some projects using /web as "public" directory
can't we define in the kernel a public dir like there is a cache dir etc?
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.
For whatever reason, this IS configurable, but via composer.json. I stole this logic from the assets:install command, which seems to be the only other place where symfony is aware of your public directory.
* @final | ||
*/ | ||
#[AsCommand(name: 'assets:pipeline:publish', description: 'Publishes all pipeline assets to the final public output directory.')] | ||
class AssetsPipelinePublishCommand extends Command |
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.
what about
class AssetsPipelinePublishCommand extends Command | |
class AssetsPipelineCompileCommand extends Command |
to me, "publish" sound like "deploy" (I'm not native English speaker tho)
here it is just compiling/dumping the content
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 is an action you take during deploy though? Rails does call this command “precompile”, however…
@weaverryan how would be the workflow for folks using e.g. SCSS? Before we just used Webpack Encore. Should we keep using that now to compile it into CSS? Then, how do we connect the compiled assets from |
@javiereguiluz if the assets are already in the |
So, if you use anything that it's not pure CSS or JS, you can't use this, right? |
This will be the NUMBER 1 question & thing to document. If you need to compile scss -> css or use something like Tailwind, the answer is that you can/should now use their standard tools. For example, for Sass, you would install the Anyways, once you do that, you'll ultimately end up with plain, normal CSS files. And THOSE are what you'd reference in your app: <link rel="stylesheet" href="{{ asset('styles/app.css') }}"> Same goes for Tailwind. We'll definitely need to give people specific instructions / recommendations on how to set this all up. |
@weaverryan being a "pipeline", what about providing extra asset compilers for those external tools? |
That’s definitely an option :). Those would use the underlying binaries (e.g. sass), which I’m pretty sure is what you’re thinking too. But yes, certainly an option! Rails does something like this - iirc for tailwind for example - but I haven’t dug into the specifics. |
Plus making sure dev server requires the digest unless pre-digeste
I DO think this should be split into its own component if we want to make the recipe situation simpler. Basically, you could still install symfony/asset without opting into the pipeline - which is what Encore users would do, for example. It if you installed symfony/asset-pipeline, then we could ship a config file that activates the pipeline and likely ships with a starting assets/ directory. Other than splitting and some windows tests (the code loves /‘s), this is ready for review. |
This seems neat ! However I'm a bit confused too by the |
How will it works with HMR? |
If you go down this route you're just building a new Assetic. It seems more fitting to keep the scope of this component small and focused on vanilla CSS/JS, matching the related PR about importmaps (#48371). Webpack can still be the default option for people who need a compilation step. |
I borrowed this name from rails "Propshaft is an asset pipeline library for Rails" - https://github.com/rails/propshaft - but i'm open to other names (though pipeline is pretty catchy). There IS a "compiler" system in there where you can make changes to the source code, but at the moment (and this should perhaps remain this way) that is meant to be lightweight - it's used, e.g., for updating import paths in JS/CSS files.
AFAIK, HMR doesn't work without a built system like Webpack. If someone wanted to get crazy, an HMR system could be built using Mercure - e.g. run a command that watches CSS files for changes, use Mercure to send that change to the frontend, and on the frontend, swap the old CSS file out for the new one. It would be a neat extra for someone to build in an external bundle, I think. You might even be able to do this without Mercure (since it's just a dev thing) by having an endpoint that stays alive forever, watches the file changes in a loop, and return the server events as they happen.
This is fair. We shouldn't rush into adding asset compilers for transforming Sass or Tailwind. Let's document using those tools on their own the "normal" way and see how things go. |
|
Can you suggest some alternative names? I like descriptive names too - what kinds of words would be helpful? If not pipeline, then...
From a practical perspective, if it lives in the same component as
It does integrate nicely :). At the same time, there is only 1 small class that that interacts with the Asset component! 95% of the code in the new component is standalone. I just split it into its own component, and it felt very natural. |
Hi!
This will partner with (and remove some of the code from) the importmaps PR #48371 . The goal is to allow you to write modern JavaScript & CSS, without needing a build system. This idea comes from Rails: https://github.com/rails/propshaft - and that heavily inspires this PR.
Example app using this: https://github.com/weaverryan/testing-asset-pipeline
Here's how it works:
A) You activate the pipeline:
B) You put some files into your
assets/
directory (which sits at the root of your project - exactly like now with Encore). For example, you might create anassets/app.js
,assets/styles/app.css
andassets/images/skeletor.jpg
.C) Refer to those assets with the normal
asset()
functionThat's it! The final paths will look like this:
How does that work?
dev
environment, a controller (technically a listener) intercepts the requests starting with/assets/
, finds the file in the source/assets/
directory, and returns it.prod
environment, you run aassets:pipeline:publish
command, which copies all of the assets intopublic/assets/
so that the real files are returned. It also dumps apublic/assets/manifest.json
so that the source paths (eg.styles/app.css
) can be exchanged for their final paths super quickly.Extras Asset Compilers
There is also an "asset" compiler system to do some minor transformations in the source files. There are 3 built-in compilers:
A)
CssAssetUrlCompiler
- findsurl()
inside of CSS files and replaces with the real, final path - e.g.url(../images/skeletor.jpg')
becomesurl(/assets/images/skeletor-3f24cba25ce4e114a3116b5f6f1d2159.jpg)
- logic taken from RailsB)
SourceMappingUrlsCompiler
- also taken from Rails - if the CSS file already contains a sourcemap URL, this updates it in the same way as above (Note: actually ADDING sourcemaps is not currently supported)C)
JavaScriptImportPathCompiler
- experimental (I wrote the logic): replaces relative imports in JavaScript filesimport('./other')
with their final path - e.g.import('/assets/other.123456abcdef.js')
.Path "Namespaces" and Bundle Assets
You can also give each "path: in the pipeline a "namespace" - e.g. an alternative syntax to the config is:
In this case, if there is an
other_assets/foo.css
file, then you can use{{ asset('other_namespace/foo.css') }}
to get a path to it.In practice, users won't do this. However, this DOES automatically add the
Resources/public/
orpublic/
directory of every bundle as a "namespaced" path. For example, in EasyAdminBundle, the following code could be used:(Note: EA has some fancy versioning, but on a high-level, this is all correct). This would already work today thanks to
assets:install
. But as soon as this code is used in an app where the pipeline is activated, the pipeline would take over and would output a versioned filename - e.g.OPEN QUESTIONS / NOTES
TODO:
Configuration
optionsCheers!