cartalyst/assetic-filters
Cartalyst Assetic Filters extends Assetic with additional asset filters for Laravel projects, making it easier to preprocess and compile CSS, JavaScript, and related front-end assets in Cartalyst-based applications.
Confirm the ground truth before writing any code:
composer show assetic/framework cartalyst/assetic-filters scssphp/scssphp
Three things must be true before this package earns its place:
Assetic\Filter\FilterInterface implementations. If composer show assetic/framework returns nothing, you are not extending a pipeline — you are standing one up, and the correct answer is Vite.scssphp), i.e. it exists so you can compile stylesheets without Node or a sass binary on the host.Minimal install:
composer require cartalyst/assetic-filters:^5.0
Then read the source of the filter classes in vendor/cartalyst/assetic-filters/src/ — that is the documentation. You are looking for the constructor signature (options passed straight through to the underlying compiler) and the concrete class name of the SASS filter. Budget ten minutes for this; nothing else will tell you what options are supported.
First use case — a build script, not the app. Do not wire this into a service provider on day one. Write a standalone script (an artisan command or a composer script) that:
use Assetic\Asset\AssetCollection;
use Assetic\Asset\FileAsset;
use Assetic\FilterManager;
use Assetic\Factory\AssetFactory;
use Cartalyst\AsseticFilters\SassFilter; // verify exact FQCN in vendor/
$fm = new FilterManager();
$fm->set('sass', new SassFilter([
'import_paths' => [base_path('resources/assets/sass')],
'style' => 'compressed', // or nested/expanded in dev
]));
$factory = new AssetFactory(base_path('resources/assets'));
$factory->setFilterManager($fm);
$factory->setDebug(app()->environment('local'));
$asset = new AssetCollection([
new FileAsset(base_path('resources/assets/sass/app.scss'), [$fm->get('sass')]),
]);
$asset->load();
file_put_contents(public_path('build/app.css'), $asset->dump());
Once that writes one correct file, you have proven the toolchain. Everything after that is scaling the pattern.
Filter registration + named application. Register filters in the FilterManager and reference them by name from asset definitions rather than newing them inline. This keeps the filter configuration in one place and lets you swap the SASS filter for a different one later without touching every asset definition.
Chain order is compile → rewrite URLs → minify. The classic SASS build is:
$asset = new AssetCollection([...], [
$fm->get('sass'),
new Assetic\Filter\CssRewriteFilter(), // fix relative url() before minifying
new Assetic\Filter\CssMinFilter(),
]);
CssRewriteFilter is not optional if your SCSS references url(../fonts/...) — the compiled file lands at a different depth than the source, and without rewriting, every font and image 404s in production while looking fine locally if you serve from a different root. Compile first, rewrite second, minify last.
One entry point per bundle, driven by a plain array. Keep the bundle list as data, not code:
$bundles = [
'app.css' => ['resources/assets/sass/app.scss', ['sass', 'cssrewrite', 'cssmin']],
'vendor.css' => ['resources/assets/sass/vendor.scss', ['sass', 'cssrewrite', 'cssmin']],
];
foreach ($bundles as $output => [$source, $filters]) { /* build + write */ }
This is your inventory. It is also the artifact you diff when deciding what to migrate to Vite later — one entry per bundle, one source glob, one filter chain.
Versioned filenames via a manifest. Do not rely on browser caching heuristics for a build you run by hand. Write app.<hash>.css and emit a manifest.json (or a PHP array) that the Blade asset() call consults. This is the single highest-value thing you add on top of the package, because hand-run builds and cache busting otherwise diverge on every deploy.
Watch loop for dev. There is no HMR and no watcher. A 5-line while loop over inotifywait -r -e modify resources/assets/sass invoking your build command gets you 80% of the ergonomics. Run it in debug mode (unminified, line numbers on) so browser devtools are usable.
Golden-file test in CI. Before you trust the filter, snapshot current CSS output, swap the pipeline, dump, and diff. This is the only defence against the silent-regression class of failure — a successful build with subtly different output. Keep the snapshot in the repo and fail the build on mismatch.
Run old and new pipelines in parallel for one release. Old pipeline stays authoritative; new output is written to a side path and diffed in CI. Cut over only after N clean builds.
Extension point: if you need a small behaviour change (different import resolution, a post-processing pass), subclass the package filter and override the compiler setup, or write an Assetic\Filter\FilterInterface of your own. That is the cheap fork — a single class, no upstream dependency. Prefer this to patching vendor/.
It is not a Laravel package. No auto-discovery, no vendor:publish, no provider. If a plan or estimate assumes those steps, the estimate is wrong. The integration surface is a build script.
Assetic's dynamic controller is a production trap. If you enable Assetic's debug route (/assetic/*) so filters run on request, you have moved SASS compilation into the request path — slow, and a file-read surface you do not want exposed. Compile at build time, serve static files.
debug mode changes filenames. With debug on, Assetic appends a mtime query string and suffixes asset names with _debug. If you see two sets of output files or a URL with ?1234567890, that is why — and it means your manifest keys won't match between dev and prod. Normalise before hashing.
Import paths are the number-one failure. @import 'partials/base' resolves relative to the importing file for some lookups and against the configured import paths for others. Set the import path explicitly to your SASS root and keep partials in a conventional partials//base/ layout. If an import suddenly fails after you move one file, this is the cause.
Compiler parity is the real risk, not Assetic. A PHP SASS port (scssphp) is not Dart Sass. Expect friction with the module system (@use/@forward), math.div vs /, colour functions and colour-space changes, calc() and CSS custom property interpolation, and newer at-rules. If your design system uses modern Sass, compile a representative stylesheet through the filter before committing to the migration — output parity is your go/no-go.
Caching bites in two places. (1) scssphp has its own compile cache if configured — stale partials after an edit are almost always this, clear the cache dir. (2) Assetic caches asset formulas via AssetCache; if you wire it to the Laravel cache store, a stale formula survives a code change. In CI, disable both caches; run assetic:dump --no-debug-equivalent clean.
Filter exceptions are often opaque. Parse errors surface from inside the filter, sometimes wrapped. Catch \Exception, print getMessage() plus the file, and make the build command hard-fail (set -e / non-zero exit). A build that "succeeds" with an empty CSS file is worse than a crash — add a non-empty assertion after writing.
load() before dump(). AssetCollection::dump() without a prior load() may skip filters depending on the asset graph. Always load() first; it also surfaces errors earlier in the script.
Pin everything, and verify PHP version support. You have five movable versions: PHP, assetic/framework, this package, scssphp, and your CI image. Pin all of them. Run the build once on your target PHP minor (8.1/8.2/8.3) before you rely on it — nothing else in the ecosystem has exercised this combination, so your first run is the compatibility test. If it breaks and upstream is silent, the fix is a one-file fork under BSD-3.
Assign an owner. The failure mode for build tooling is the bridge becoming permanent by default. Name an owner for the filter and for the eventual migration, and put the migration on the roadmap as its own workstream, not a footnote to the framework upgrade.
When to stop extending it. The moment you need PostCSS plugins, TypeScript/ESM bundling, image optimisation, source maps good enough to debug compiled output in devtools, or daily designer iteration — you have left this package's envelope. That is the signal to schedule the Vite migration, not to write another custom filter.
How can I help you explore Laravel packages today?