Why another asset bundle?
If you’ve ever maintained a project with a handful of external CSS/JS libraries, you know the pain points: copying files, fixing paths, updating versions, clearing caches. It’s time-consuming and error-prone. That’s where the AssetComposerBundle comes in: it serves your assets directly from the vendor directory, manages them centrally, and automatically appends a version based on the file’s modification time. Result: less overhead, less drift, always up-to-date assets.
The idea in one sentence
“Composer-native” asset management: install libraries as Composer packages and reference their files directly in Twig — no copy step — including cache busting via the file’s mtime.
Highlights
- Manage CSS/JS directly from Composer packages (no copying into
public/) - Automatic versioning via file timestamp for reliable cache busting
- Clean Twig integration (
addAssetComposer,renderAssetComposerStylesheets,renderAssetComposerJavascripts) - Optional control over which files are exposed (incl. dev-only) via
assetcomposer.json - Works with Symfony 6.4 and 7.0
Installation
composer require jbsnewmedia/asset-composer-bundle
The bundle registers automatically (Flex). Requirements: PHP ≥ 8.1, Symfony 6.4/7.0.
Quickstart: add and render assets
1) Install libraries (example):
composer require twbs/bootstrap
composer require components/font-awesome
composer require avalynx/avalynx-alert
2) Enqueue assets in any Twig template:
{% do addAssetComposer('twbs/bootstrap/dist/css/bootstrap.css') %}
{% do addAssetComposer('components/font-awesome/css/all.css') %}
{% do addAssetComposer('avalynx/avalynx-alert/dist/css/avalynx-alert.css') %}
{% do addAssetComposer('avalynx/avalynx-alert/dist/js/avalynx-alert.js') %}
3) Render them in your layout:
<!DOCTYPE html>
<html>
<head>
{% block stylesheets %}
{{ renderAssetComposerStylesheets() }}
{% endblock %}
</head>
<body>
{% block body %}{% endblock %}
{% block javascripts %}
{{ renderAssetComposerJavascripts() }}
{% endblock %}
{{ renderAssetComposerJavascripts('bottom') }}
</body>
</html>
The bundle ensures each asset path is suffixed with ?v=<mtime>. Browsers and proxies reliably detect changes — no manual versioning required.
Optional: assetcomposer.json
Packages can ship (and you can provide in your own packages) an assetcomposer.json that controls which files should be exposed in the project — separated for production and development:
{
"name": "library-name",
"files": [
"dist/css/styles.css",
"dist/js/scripts.js"
],
"files-dev": [
"src/css/dev-styles.css",
"src/js/dev-scripts.js"
]
}
This keeps the output deterministic and prevents accidentally exposing “internal” files in your project.
How does cache busting work?
Instead of relying on package versions or hash pipelines, the bundle uses the simple and robust file modification timestamp (mtime). Every URL gets ?v=<timestamp>. If the file changes, the timestamp changes — and so does the URL. Advantages:
- No additional build step required
- Works with any file, regardless of the package
- Just as reliable in production as it is locally
When is this bundle a good fit?
- You mainly use libraries from Composer (Bootstrap, Font Awesome, small JS utilities) and want to include them without copy workflows.
- You need reliable cache busting without running a full asset pipeline.
- You want to cleanly separate dev-only assets (e.g., unminified files).
It’s not the ideal approach if you run a complex frontend build (e.g., with Vite/Webpack, TypeScript, code splitting) — in that case, the bundle may only complement external vendor assets.
Practical tips
- Grouping: Render JavaScript “at the bottom” (
renderAssetComposerJavascripts('bottom')) to improve LCP. - Mixing with your own assets: Keep your own files in
public/as usual; vendor assets come via the bundle. This keeps responsibilities clear. - Production vs. development: Use
files-devinassetcomposer.jsonfor unminified files and switch tofiles(minified) in production.
Troubleshooting
- “File not found”: Is the path correct relative to the package root in
vendor/? Example:twbs/bootstrap/dist/css/bootstrap.css. - “Changes are not loading”: Check if the browser cache is active. With
?v=<mtime>, changes should appear immediately. - “I need SRI hashes”: The bundle focuses on pragmatic loading from
vendor/. For SRI you can add a small Twig extension or a build step if needed.
Conclusion
AssetComposerBundle is a small yet effective shortcut for many Symfony projects: no redundant copy steps, clean cache busting, clear Twig APIs. If you keep your frontend assets deliberately lean and use Composer as the central source, you’ll save time every day — and avoid common mistakes.
Links
- GitHub: https://github.com/jbsnewmedia/asset-composer-bundle
- Composer:
composer require jbsnewmedia/asset-composer-bundle