nickdekruijk / minify
A package for automaticaly minifying CSS, SCSS and Javascript for Laravel
Requires
- php: ^8.2
- scssphp/scssphp: ^2.0
- tedivm/jshrink: ^1.3
Requires (Dev)
- laravel/pint: ^1.18
- orchestra/testbench: ^10.0|^11.0
- phpunit/phpunit: ^11.0|^12.0
README
A simple package to minify CSS/SCSS and Javascript on the fly without the need of tools like Laravel Mix or Webpack. It combines all stylesheet files or javascript files into a single, minified file with simple but effective cachebusting on the content hash of the build.
Version 2 is a completely new package (version 1 is archived here) using scssphp/scssphp and tedivm/jshrink. Because minify now replaces natxet/cssmin with scssphp it can now compile SASS/SCSS code too!
Installation
Begin by installing this package with composer.
composer require nickdekruijk/minify
Upgrading to 5.0
Existing calls, filenames and config keys keep working, and the cache busting token quietly
becomes a content hash instead of a filemtime(). What makes this a major is that two
things change on their own.
staging is now in the default skip_environment, so a staging server serves its build
instead of compiling one. If yours relied on compiling live, run php artisan minify:build
in the deploy, or publish the config and take staging back out. A deployed environment
under another name (acceptance, uat, demo) was never in the list and still compiles
live; add it while you are there. See Source maps for why this
matters beyond the compiling.
And the stylesheet <link> no longer carries blocking="render". It never did anything, a
stylesheet link blocks rendering by itself and the attribute is for resources that do not,
and low in the body it forbade the browser from painting what stood above it. Only matters
if you assert on that HTML.
Views can use @minifyStylesheet and @minifyJavascript instead of {!! Minify::… !!} now.
The facade is not going anywhere; see Stylesheet below.
Two things are worth knowing. Each build now has a .manifest.json next to it, recording
every file the build was compiled from. It holds paths relative to the project and no file
contents.
Committing it along with the build is worth doing but not required. An environment that
compiles writes its own manifest on the first request. An environment that only serves —
anything in skip_environment — falls back to the filemtime() token when there is no
manifest, exactly as 4.0 did, and nothing breaks. What you give up is a stable token: git
does not preserve mtimes, so every deploy hands the build a new one and browsers re-download
CSS that never changed. With the manifest the token only moves when the content does.
And source maps are on wherever minify compiles, which means a build compiled while
developing carries a sourceMappingURL comment. If you commit builds and deploy them, run
php artisan minify:build with your production environment before deploying: it writes
neither a map nor the comment. Otherwise commit the .map too, and know that it contains
your complete Sass source.
Building for production
skip_environment holds production and staging: the environments that are deployed and
reachable from the outside. If yours goes by another name, acceptance, accept, uat,
demo, publish the config and add it. Compiling on the fly costs a stat of every source on
every request, and it writes a source map that holds your complete Sass.
That leaves you deploying a build somebody remembered to commit. Configure your builds instead and let the deploy compile them:
// config/minify.php 'builds' => [ ['stylesheet' => ['reset.css', 'app.scss']], ['javascript' => ['app.js']], ],
php artisan minify:build
It ignores skip_environment, mirrors the file lists you pass in your views, and takes an
optional 'output' per build. --force compiles even when nothing changed; --clean
removes the build, its manifest and its map first.
Changing output afterwards leaves the previous build where it was — minify keeps no record
of paths it used before, so remove those by hand.
Debugging in the browser
Compressed CSS is one long line, so without a source map the inspector can only point at
app.css:1. Minify writes a .map next to the build wherever it compiles, and none in a
skip_environment. The sourcemap config key forces it on or off.
The map embeds your Sass sources, because resources/ is not reachable over the web and the
browser could not fetch them otherwise. A published map is therefore your complete Sass
source, which is why it is off where builds are only served.
Which makes skip_environment the setting that decides this. Any environment outside that
list compiles, and any environment that compiles and is reachable from the outside serves
its own Sass. production and staging are in it; add whatever else of yours is deployed.
Javascript has no equivalent: JShrink cannot produce source maps. Setting jsMinify to
null leaves the bundle unminified wherever minify compiles, which at least keeps it
readable. It defaults to true, because a build compiled while developing is often the one
that ends up deployed.
Suggested .gitignore:
/public/css/builds/*.map
/public/js/builds/*.map
Sass support
Stylesheets are compiled by scssphp, which implements
@import but not Sass modules — @use and @forward raise
Sass modules are not implemented yet.
Entry files are @imported rather than pasted together, so each keeps its own name and line
numbers in the source map. Variables still carry over from one entry file to the next, the
way concatenating them did. Files that are not Sass are pasted in at their own position,
because Sass turns @import of a .css file into a CSS @import rule instead of inlining
it.
Upgrading from 3.x to 4.0
Two defaults changed. If you never published config/minify.php, both apply to you.
Import paths are now absolute. They used to be relative ('../resources/sass/'), which
only resolves when the working directory is public/ — true for a web request, false for
anything run through artisan. So a compile from the console looked for your stylesheets one
directory above the project and threw not found within importPaths. The defaults now use
resource_path() and public_path(), which resolve the same files from any working
directory. A published config keeps whatever is in it, so if yours still has relative paths
and you run minify from the console, make them absolute the same way.
testing is no longer skipped. With it in skip_environment, minify did not compile
during tests at all: it pointed at the output file and hoped it was there. That made a test
suite depend on a build left behind by an earlier browser request — green locally, and
either absent or stale on a fresh CI checkout. Tests now compile from your sources. To keep
the old behaviour, publish the config and add 'testing' back to skip_environment.
The default arguments of Minify::stylesheet() and Minify::javascript() dropped their
relative prefix, from ['../resources/sass/app.scss'] to ['app.scss'] and from
['../resources/js/app.js'] to ['app.js']. Both resolve through the import paths, so this
works whether your config is relative or absolute. Calls that pass files explicitly are
unaffected.
Upgrading from 1.x
When upgrading change your projects composer.json to require nickdekruijk/minify with at least version "^2.0" and run composer update.
If you use .gitignore to ignore the old builds in js/builds and css/builds dont' forget to remove them from your .gitignore file and delete all obsolete build .css and .js files.
You may also need to change the Minify::stylesheet and Minify::javascript calls in your code/views since pathname might change depending on your configuration.
Laravel installation
Publish the config file if the defaults doesn't suite your needs:
php artisan vendor:publish --provider="NickDeKruijk\Minify\ServiceProvider" --tag=config
Stylesheet
{{-- app/views/hello.blade.php --}} <html> <head> ... @minifyStylesheet(['lightbox.css', 'fonts.css', 'styles.css']) </head> ... </html>
Javascript
{{-- app/views/hello.blade.php --}} <html> <body> ... @minifyJavascript(['lazyload.min.js', 'scripts.js']) {{-- Or: --}} @minifyJavascript(['https://cdn.jsdelivr.net/npm/vanilla-lazyload@12.4.0/dist/lazyload.min.js', 'scripts.js']) </body> </html>
The facade is still there and does the same thing, for a controller or anything else that is not a view:
$tag = Minify::stylesheet(['lightbox.css', 'fonts.css', 'styles.css']);
Attributes
Both take an array of extra attributes for the tag. true writes a boolean attribute on its
own, false and null leave it out, so a caller can decide with an expression instead of
building the array conditionally.
@minifyJavascript(['scripts.js'], null, ['defer' => true]) @minifyStylesheet(['print.scss'], null, ['media' => 'print'])
A script at the foot of the body still blocks the parser where it stands, and the parser has
to finish before anything paints, so defer is usually what you want.
Inline
@minifyInlineStylesheet and @minifyInlineJavascript write the compiled build into the
page instead of linking to it:
@minifyInlineStylesheet(['reset.css', 'app.scss'])
A link costs a request on a connection that has only just finished the first, and nothing can paint until it lands. Inline it arrives with the HTML that needs it. The price is that the bundle rides along on every page and the browser can no longer cache it across them, so this is for sites that mostly get first visits, or for a small critical bundle beside a linked one.
The build is compiled and cached exactly as it is for a link, so the two are interchangeable. There is no cache busting token, because there is no request to bust.
An inlined stylesheet points at its source map by url rather than by filename: written into
the page there is no file for a bare app.css.map to resolve against, so the browser would
look for it next to the document.
Config
See the config file at /config/minify.php