├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── config └── tailwindcss.php ├── rector.php ├── src ├── Actions │ └── AppendTailwindTag.php ├── Commands │ ├── BuildCommand.php │ ├── DownloadCommand.php │ ├── InstallCommand.php │ └── WatchCommand.php ├── Http │ └── Middleware │ │ └── AddLinkHeaderForPreloadedAssets.php ├── Manifest.php ├── TailwindCssServiceProvider.php ├── Testing │ └── InteractsWithTailwind.php └── helpers.php └── stubs ├── postcss.config.js └── resources └── css └── app.css /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `tailwindcss-laravel` will be documented in this file. 4 | 5 | ## 2.2.1 - 2025-04-08 6 | 7 | ### What's Changed 8 | 9 | * Adds the `no-tty` flag by @gcavanunez in https://github.com/tonysm/tailwindcss-laravel/pull/45 10 | 11 | ### New Contributors 12 | 13 | * @gcavanunez made their first contribution in https://github.com/tonysm/tailwindcss-laravel/pull/45 14 | 15 | **Full Changelog**: https://github.com/tonysm/tailwindcss-laravel/compare/2.2.0...2.2.1 16 | 17 | ## 2.2.0 - 2025-03-31 18 | 19 | ### What's Changed 20 | 21 | * Use the HTTP Client's `sink` method for efficiency https://github.com/tonysm/tailwindcss-laravel/commit/f6f425cd4217817f4f2a258df186fb539b4d8cb1 22 | 23 | **Full Changelog**: https://github.com/tonysm/tailwindcss-laravel/compare/2.1.0...2.2.0 24 | 25 | ## 2.0.0 - 2025-02-12 26 | 27 | ### What's Changed 28 | 29 | - Upgrades to Tailwind V4. 30 | 31 | ## 1.2.0 - 2025-02-12 32 | 33 | ### What's Changed 34 | 35 | - Fallback to Tailwind V3 in the 1.x tagging. It was my mistake to bump to V4 without a major version. If you switched to V4, use the 2.x tag. 36 | 37 | **Full Changelog**: https://github.com/tonysm/tailwindcss-laravel/compare/1.1.0...1.2.0 38 | 39 | ## 1.1.0 - 2025-01-27 40 | 41 | ### What's Changed 42 | 43 | * Upgrade to Tailwind V4 by @tonysm in https://github.com/tonysm/tailwindcss-laravel/pull/37 44 | 45 | **Full Changelog**: https://github.com/tonysm/tailwindcss-laravel/compare/1.0.2...1.1.0 46 | 47 | ## 0.15.0 - 2024-08-13 48 | 49 | ### What's Changed 50 | 51 | * Bumps Tailwind binary version and tweaks the build command to use the Process facade by @tonysm in https://github.com/tonysm/tailwindcss-laravel/pull/33 52 | 53 | **Full Changelog**: https://github.com/tonysm/tailwindcss-laravel/compare/0.14.0...0.15.0 54 | 55 | ## 0.14.0 - 2024-03-06 56 | 57 | ### What's Changed 58 | 59 | * Laravel 11.x Compatibility by @laravel-shift in https://github.com/tonysm/tailwindcss-laravel/pull/30 60 | 61 | ### New Contributors 62 | 63 | * @laravel-shift made their first contribution in https://github.com/tonysm/tailwindcss-laravel/pull/30 64 | 65 | **Full Changelog**: https://github.com/tonysm/tailwindcss-laravel/compare/0.13.1...0.14.0 66 | 67 | ## 0.13.1 - 2024-02-23 68 | 69 | ### Changelog 70 | 71 | - **CHANGED**: Bump the Tailwind CLI version at https://github.com/tonysm/tailwindcss-laravel/commit/67a0c1ba51bf21daf70f0d9b5a91eb362681f39d 72 | - **CHANGED**: Tweaked the install command to only append the Tailwind link tags to the layout instead of replace the vite directive at https://github.com/tonysm/tailwindcss-laravel/commit/3e7c635e37d52642458505d6601645492566475e 73 | 74 | ## 0.12.1 - 2024-01-12 75 | 76 | ### Changelog 77 | 78 | - Fix `tailwind.config.js` file requiring plugins that fail 79 | 80 | ## 0.12.0 - 2024-01-12 81 | 82 | ### What's Changed 83 | 84 | * Add `Link` header by @tonysm in https://github.com/tonysm/tailwindcss-laravel/pull/28 85 | 86 | **Full Changelog**: https://github.com/tonysm/tailwindcss-laravel/compare/0.11.0...0.12.0 87 | 88 | ## 0.11.0 - 2023-11-12 89 | 90 | ### What's Changed 91 | 92 | - Fixes installation command not overriding the default `app.css` file even though it's empty 93 | - Bumps the default TailwindCSS bin version to v3.3.5 94 | 95 | **Full Changelog**: https://github.com/tonysm/tailwindcss-laravel/compare/0.10.1...0.11.0 96 | 97 | ## 0.10.1 - 2023-05-13 98 | 99 | ### What's changed 100 | 101 | - Fixed the TailwindCSS Config JS https://github.com/tonysm/tailwindcss-laravel/commit/37632f77cb1aec72c075f1db8b0cbcf17b03df01 102 | - Bump the Tailwind CLI default version to v3.3.2 https://github.com/tonysm/tailwindcss-laravel/commit/b48c26785cbd02ef6f412f6f76005d28db9b87f3 103 | 104 | ## 0.10.0 - 2023-05-10 105 | 106 | ### What's Changed 107 | 108 | - Tweaks the install command and tailwind.config stub in https://github.com/tonysm/tailwindcss-laravel/commit/8402efe71545473b9d12f3d846fee994b979cec7 109 | 110 | **Full Changelog**: https://github.com/tonysm/tailwindcss-laravel/compare/0.9.0...0.10.0 111 | 112 | ## 0.9.0 - 2023-02-14 113 | 114 | ### Changelog 115 | 116 | - **CHANGED**: Bump the default Tailwind CLI version to `v3.2.6` 117 | - **CHANGED**: Support for Laravel 10 118 | 119 | ## 0.8.0 - 2022-11-17 120 | 121 | ### Changelog 122 | 123 | - **NEW**: The `tailwindcss()` function throws an exception when the manifest file is missing. When testing, we don't want that behavior as we don't always need to compile our styles to run the tests (sometimes we do). For that reason, you may now use the new `InteractsWithTailwind` trait and call the `$this->withoutTailwind()` method on your tests (or in the base TestCase) to disable the missing manifest exception on your tests. Initial work by @andreasnij (thanks!) 124 | 125 | ## 0.7.0 - 2022-08-06 126 | 127 | ### Changelog 128 | 129 | - Bumps default Tailwind CLI version to 3.1.8 (https://github.com/tonysm/tailwindcss-laravel/commit/575332c3710d8a1a9beda423740c458ede68402c) 130 | 131 | ## 0.6.0 - 2022-07-03 132 | 133 | ### Changelog 134 | 135 | - **CHANGED**: The `tailwindcss:install` command was changed to work with the new frontend setup in Laravel 9, which uses Vite instead of Mix. It should also keep working on installs in Laravel apps using Mix. Of course, it also works on apps using neither (**cough* *cough** Importmap Laravel) 136 | 137 | ## 0.5.1 - 2022-06-27 138 | 139 | ### Changelog 140 | 141 | - Bumps default Tailwind CLI version to 3.1.4 (https://github.com/tonysm/tailwindcss-laravel/commit/a853d4a436d58a554e3cb0e2f878935884dac342) 142 | 143 | ## 0.5.0 - 2022-06-27 144 | 145 | ### Changelog 146 | 147 | - Changes default scaffolding from using `@tailwind` to using `@import` (https://github.com/tonysm/tailwindcss-laravel/commit/bcc0b9d09cdb375cad59020c670c05b51dae01fa) 148 | 149 | ## 0.4.1 - 2022-05-01 150 | 151 | ### Changelog 152 | 153 | - **FIXED**: Set the working directory explicitly to `base_path()` by @mucenica-bogdan https://github.com/tonysm/tailwindcss-laravel/pull/12 154 | 155 | ## 0.4.0 - 2022-04-12 156 | 157 | ### Changelog 158 | 159 | - Adds a `TAILWINDCSS_CLI_VERSION` envvar to allow overriding it without publishing the config file 160 | - Bumps the default CLI version to `v3.0.24` 161 | - Adds a new `--cli-version` option to the `tailwindcss:install` and `tailwindcss:download` commands which may be used by passing the `--cli-version="v3.0.24"` 162 | 163 | ## 0.3.0 - 2022-02-13 164 | 165 | ### Changelog 166 | 167 | - **CHANGED**: the `tailwindcss.php` config was updated: instead of specifying the destination file path, you define a destination PATH, we'll construct the full file path based on the source file relative path. 168 | - **CHANGED**: The binary destination file in the `tailwindcss.php` now checks if the file destination should end with `.exe` or not (for Windows support) 169 | - **FIXED**: the `tailwindcss:download` and `tailwindcss:build` commands now are working on Windows machines. 170 | - **NEW**: Increase `tailwindcss:download` default timeout when downloading the binary and allow users overriding it with the `--timeout` flag (accepts seconds) 171 | 172 | 173 | --- 174 | 175 | **If you have published the config, please, republish it again** (no need to publish it if you haven't done that yet) 176 | 177 | ## 0.2.0 - 2022-02-13 178 | 179 | ### Changelog 180 | 181 | - **CHANGED**: added a [new config entry for the manifest location path](https://github.com/tonysm/tailwindcss-laravel/blob/main/config/tailwindcss.php#L17). Also, the manifest is now prefixed with a dot. That's because Vapor will include dot files in the `public/` folder by default. I know that's something users can ignore, but that should do it for now. If you have published the `tailwindcss.php` config file, make sure to republish that. 182 | 183 | ## 0.1.0 - 2022-02-09 184 | 185 | ### Changelog 186 | 187 | - **CHANGED**: Laravel 9 support (nothing changed, just the version constraints) 188 | 189 | ## 0.0.3 - 2022-02-04 190 | 191 | ### Changelog 192 | 193 | - **FIXED:** the option got renamed from `--production` to `--prod` right before pushing and there were some places still referencing it. That's fixed (https://github.com/tonysm/tailwindcss-laravel/commit/4d8861597442babdd727541d2dcec1bf0ba42f61) 194 | 195 | ## 0.0.2 - 2022-02-04 196 | 197 | ### Changelog 198 | 199 | - **NEW**: There's now a new `--prod` option for the `tailwindcss:build` command, which combines the `--digest` and `--minify` flags. 200 | 201 | ## 1.0.0 - 202X-XX-XX 202 | 203 | - initial release 204 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) tonysm 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Tailwind CSS for Laravel

2 | 3 |

4 | 5 | Total Downloads 6 | 7 | 8 | Latest Stable Version 9 | 10 | 11 | License 12 | 13 |

14 | 15 | ## Introduction 16 | 17 | This package wraps the standalone [Tailwind CSS CLI tool](https://tailwindcss.com/blog/standalone-cli). No Node.js required. 18 | 19 | ### Inspiration 20 | 21 | This package was inspired by the [Tailwind CSS for Rails](https://github.com/rails/tailwindcss-rails) gem. 22 | 23 | ## Installation 24 | 25 | You can install the package via composer: 26 | 27 | ```bash 28 | composer require tonysm/tailwindcss-laravel 29 | ``` 30 | 31 | _Attention: Version 2.x requires Tailwind V4. Make sure your application was upgraded to it or install version 1.x instead._ 32 | 33 | Next, you may run the install command: 34 | 35 | ```bash 36 | php artisan tailwindcss:install 37 | ``` 38 | 39 | Optionally, you can publish the config file with: 40 | 41 | ```bash 42 | php artisan vendor:publish --tag="tailwindcss-config" 43 | ``` 44 | 45 | ## Usage 46 | 47 | The package consists of 4 commands and a helper function. 48 | 49 | ### Download the Tailwind CSS Standalone Binary 50 | 51 | Since each OS/CPU needs its own version of the compiled binary, the first thing you need to do is run the download command: 52 | 53 | ```bash 54 | php artisan tailwindcss:download 55 | ``` 56 | 57 | This will detect the correct version based on your OS and CPU architecture. 58 | 59 | By default, it will place the binary at the root of your app. The binary will be called `tailwindcss`. You may want to add that line to your project's `.gitignore` file. 60 | 61 | Alternatively, you may configure the location of this binary file in the `config/tailwindcss.php` (make sure you export the config file if you want to do that). 62 | 63 | ### Installing the Scaffolding 64 | 65 | There are some files needed for the setup to work. On a fresh Laravel application, you may run the install command, like so: 66 | 67 | ```bash 68 | php artisan tailwindcss:install 69 | ``` 70 | 71 | This will publish a new `resources/css/app.css` main Tailwind CSS file. Make sure you [upgrade your application to Tailwind V4](https://tailwindcss.com/docs/upgrade-guide). 72 | 73 | ### Building 74 | 75 | To build the Tailwind CSS styles, you may use the build command: 76 | 77 | ```bash 78 | php artisan tailwindcss:build 79 | ``` 80 | 81 | By default, that will read your `resources/css/app.css` file and generate the compiled CSS file at `public/css/app.css`. 82 | 83 | You may want to generate the final CSS file with a digest on the file name for cache busting reasons (ideal for production). You may do so with the `--digest` flag: 84 | 85 | ```bash 86 | php artisan tailwindcss:build --digest 87 | ``` 88 | 89 | You may also want to generate a minified version of the final CSS file (ideal for production). You may do so with the `--minify` flag: 90 | 91 | ```bash 92 | php artisan tailwindcss:build --minify 93 | ``` 94 | 95 | These two flags will make the ideal production combo. Alternatively, you may prefer using a single `--prod` flag instead, which is essentially the same thing, but shorter: 96 | 97 | ```bash 98 | php artisan tailwindcss:build --prod 99 | ``` 100 | 101 | ### Watching For File Changes 102 | 103 | When developing locally, it's handy to run the watch command, which will keep an eye on your local files and run a build again whenever you make a change locally: 104 | 105 | ```bash 106 | php artisan tailwindcss:watch 107 | ``` 108 | 109 | _Note: the watch command is not meant to be used in combination with `--digest` or `--minify` flags._ 110 | 111 | ### Using the Compiled Asset 112 | 113 | To use the compiled asset, you may use the `tailwindcss` helper function instead of the `mix` function like so: 114 | 115 | ```diff 116 | - 117 | + 118 | ``` 119 | 120 | That should be all you need. 121 | 122 | ### Deploying Your App 123 | 124 | When deploying the app, make sure you add the ideal build combo to your deploy script: 125 | 126 | ```bash 127 | php artisan tailwindcss:build --prod 128 | ``` 129 | 130 | If you're running on a "fresh" app (or an isolated environment, like Vapor), and you have added the binary to your `.gitignore` file, make sure you also add the download command to your deploy script before the build one. In these environments, your deploy script should have these two lines 131 | 132 | ```bash 133 | php artisan tailwindcss:download 134 | php artisan tailwindcss:build --prod 135 | ``` 136 | 137 | ### Preloading Assets as Link Header 138 | 139 | **For Laravel 10:** 140 | 141 | If you want to preload the TailwindCSS asset on Laravel 10, add the `AddLinkHeaderForPreloadedAssets` middleware to your `web` route group in the `app/Http/Kernel.php`: 142 | 143 | ```php 144 | [ 155 | // ... 156 | \Tonysm\TailwindCss\Http\Middleware\AddLinkHeaderForPreloadedAssets::class, 157 | ], 158 | 159 | 'api' => [ 160 | // ... 161 | ], 162 | ]; 163 | 164 | // ... 165 | } 166 | ``` 167 | 168 | **For Laravel 11:** 169 | 170 | If you want to preload the TailwindCSS asset on Laravel 11, add the `AddLinkHeaderForPreloadedAssets` middleware to your `web` route group in the `bootstrap/app.php`: 171 | 172 | ```php 173 | // ... 174 | ->withMiddleware(function (Middleware $middleware) { 175 | $middleware->web(append: [ 176 | \Tonysm\TailwindCss\Http\Middleware\AddLinkHeaderForPreloadedAssets::class, 177 | ]); 178 | }) 179 | // ... 180 | ``` 181 | 182 | The package will preload the asset by default. If you're linking an asset like: 183 | 184 | ```blade 185 | 186 | ``` 187 | 188 | It will add a Link header to the HTTP response like: 189 | 190 | ```http 191 | Link: ; rel=preload; as=style 192 | ``` 193 | 194 | It will keep any existing `Link` header as well. 195 | 196 | If you want to disable preloading with the Link header, set the flag to `false`: 197 | 198 | ```blade 199 | 200 | ``` 201 | 202 | You may also change or set additional attributes: 203 | 204 | ```blade 205 | 206 | ``` 207 | 208 | This will generate a preloading header like: 209 | 210 | ```http 211 | Link: ; rel=preload; as=style; crossorigin=anonymous 212 | ``` 213 | 214 | ### Mock Manifest When Testing 215 | 216 | The `tailwindcss()` function will throw an exception when the manifest file is missing. However, we don't always need the manifest file when running our tests. You may use the `InteractsWithTailwind` trait in your main TestCase to disable that exception throwing: 217 | 218 | ```php 219 | withoutTailwind(); 237 | } 238 | } 239 | ``` 240 | 241 | Alternatively, you may also use the trait on specific test cases if you want to, so we can toggle that behavior as you need: 242 | 243 | ```php 244 | expectException(Exception::class); 260 | 261 | $this->withoutExceptionHandling() 262 | ->get(route('login')); 263 | 264 | $this->fail('Expected exception to be thrown, but it did not.'); 265 | } 266 | 267 | /** @test */ 268 | public function can_disable_tailwindcss_exception() 269 | { 270 | $this->withoutTailwind() 271 | ->get(route('login')) 272 | ->assertOk(); 273 | } 274 | } 275 | ``` 276 | 277 | Both tests should pass. 278 | 279 | ## Changelog 280 | 281 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 282 | 283 | ## Contributing 284 | 285 | Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details. 286 | 287 | ## Security Vulnerabilities 288 | 289 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 290 | 291 | ## Credits 292 | 293 | - [Tony Messias](https://github.com/tonysm) 294 | - [All Contributors](../../contributors) 295 | 296 | ## License 297 | 298 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 299 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tonysm/tailwindcss-laravel", 3 | "description": "This package wraps up the standalone executable version of the Tailwind CSS framework.", 4 | "keywords": [ 5 | "laravel", 6 | "tailwindcss" 7 | ], 8 | "homepage": "https://github.com/tonysm/tailwindcss-laravel", 9 | "license": "MIT", 10 | "authors": [ 11 | { 12 | "name": "Tony Messias", 13 | "email": "tonysm@hey.com", 14 | "role": "Developer" 15 | } 16 | ], 17 | "require": { 18 | "php": "^8.2", 19 | "spatie/laravel-package-tools": "^1.16", 20 | "illuminate/contracts": "^11.0|^12.0" 21 | }, 22 | "require-dev": { 23 | "orchestra/testbench": "^9.0|^10.0", 24 | "phpunit/phpunit": "^10.5|^11.0" 25 | }, 26 | "autoload": { 27 | "psr-4": { 28 | "Tonysm\\TailwindCss\\": "src" 29 | }, 30 | "files": [ 31 | "src/helpers.php" 32 | ] 33 | }, 34 | "autoload-dev": { 35 | "psr-4": { 36 | "Tonysm\\TailwindCss\\Tests\\": "tests" 37 | } 38 | }, 39 | "scripts": { 40 | "analyse": "vendor/bin/phpstan analyse", 41 | "test": "vendor/bin/pest", 42 | "test-coverage": "vendor/bin/pest --coverage" 43 | }, 44 | "config": { 45 | "sort-packages": true, 46 | "allow-plugins": { 47 | "pestphp/pest-plugin": true, 48 | "phpstan/extension-installer": true 49 | } 50 | }, 51 | "extra": { 52 | "laravel": { 53 | "providers": [ 54 | "Tonysm\\TailwindCss\\TailwindCssServiceProvider" 55 | ] 56 | } 57 | }, 58 | "minimum-stability": "dev", 59 | "prefer-stable": true 60 | } 61 | -------------------------------------------------------------------------------- /config/tailwindcss.php: -------------------------------------------------------------------------------- 1 | [ 15 | 'source_file_path' => resource_path('css/app.css'), 16 | 'destination_path' => public_path('dist'), 17 | 'manifest_file_path' => public_path('.tailwindcss-manifest.json'), 18 | ], 19 | 20 | /* 21 | |--------------------------------------------------------------------- 22 | | Where the TailwindCSS binary can be located. 23 | |--------------------------------------------------------------------- 24 | | 25 | | The package is a wrapper around the tailwindcss binary. Each platform needs its own 26 | | version of the binary file, so the package does not ship with it out-of-the-box. 27 | | Use the `tailwindcss:download` Artisan command to ensure the binary is there. 28 | | 29 | */ 30 | 'bin_path' => PHP_OS === 'WINNT' ? base_path('tailwindcss.exe') : base_path('tailwindcss'), 31 | 32 | /* 33 | |--------------------------------------------------------------------- 34 | | The version of the binary file. 35 | |--------------------------------------------------------------------- 36 | | 37 | | The version we should ensure is installed locally. 38 | | 39 | | @see https://github.com/tailwindlabs/tailwindcss/releases to know the version availables. 40 | | 41 | */ 42 | 'version' => env('TAILWINDCSS_CLI_VERSION', 'v4.0.0'), 43 | ]; 44 | -------------------------------------------------------------------------------- /rector.php: -------------------------------------------------------------------------------- 1 | withPaths([ 10 | __DIR__.'/src', 11 | __DIR__.'/tests', 12 | ]) 13 | ->withPreparedSets( 14 | deadCode: true, 15 | codeQuality: true, 16 | typeDeclarations: true, 17 | privatization: true, 18 | earlyReturn: true, 19 | ) 20 | ->withAttributesSets() 21 | ->withPhpSets() 22 | ->withPhpVersion(PhpVersion::PHP_82); 23 | -------------------------------------------------------------------------------- /src/Actions/AppendTailwindTag.php: -------------------------------------------------------------------------------- 1 | )/', 15 | PHP_EOL.'\\1 '. 16 | "\\1 \\1\\2", 17 | $contents, 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Commands/BuildCommand.php: -------------------------------------------------------------------------------- 1 | error('Could not find the Tailwind CSS binary. Please, run the `tailwindcss:download` before trying to build or configure the path to the binary in your config/tailwindcss.php file.'); 30 | 31 | return self::FAILURE; 32 | } 33 | 34 | $this->info('Building assets...'); 35 | 36 | $sourcePath = $this->fixFilePathForOs(config('tailwindcss.build.source_file_path')); 37 | $sourceRelativePath = str_replace(rtrim(resource_path(), DIRECTORY_SEPARATOR), '', $sourcePath); 38 | $destinationPath = $this->fixFilePathForOs(config('tailwindcss.build.destination_path')); 39 | $destinationFileAbsolutePath = $destinationPath.DIRECTORY_SEPARATOR.trim($sourceRelativePath, DIRECTORY_SEPARATOR); 40 | $destinationFileRelativePath = str_replace(rtrim(public_path(), DIRECTORY_SEPARATOR), '', $destinationFileAbsolutePath); 41 | 42 | File::ensureDirectoryExists(dirname($destinationFileAbsolutePath)); 43 | File::cleanDirectory(dirname($destinationFileAbsolutePath)); 44 | 45 | if ($this->option('watch') || ! $this->shouldVersion()) { 46 | // Ensure there is at least one mix-manifest.json that points to the unversioned asset... 47 | File::put(Manifest::path(), json_encode([ 48 | $this->fixOsFilePathToUriPath($sourceRelativePath) => $this->fixOsFilePathToUriPath($destinationFileRelativePath), 49 | ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); 50 | } 51 | 52 | Process::forever() 53 | ->when( 54 | ! $this->option('no-tty'), 55 | fn ($process) => $process->tty(SymfonyProcess::isTtySupported()) 56 | ) 57 | ->path(base_path()) 58 | ->run(array_filter([ 59 | $binFile, 60 | '-i', $sourcePath, 61 | '-o', $destinationFileAbsolutePath, 62 | $this->option('watch') ? '--watch=always' : null, 63 | $this->shouldMinify() ? '-m' : null, 64 | ]), function ($type, $output): void { 65 | $this->output->write($output); 66 | }); 67 | 68 | if ($this->shouldVersion()) { 69 | $destinationFileAbsolutePath = $this->ensureAssetIsVersioned($destinationFileAbsolutePath); 70 | $destinationFileRelativePath = str_replace(rtrim(public_path(), DIRECTORY_SEPARATOR), '', $destinationFileAbsolutePath); 71 | } 72 | 73 | if (! $this->option('watch') && $this->shouldVersion()) { 74 | $this->info(sprintf('Generating the versioned %s file...', Manifest::filename())); 75 | 76 | File::put(Manifest::path(), json_encode([ 77 | $this->fixOsFilePathToUriPath($sourceRelativePath) => $this->fixOsFilePathToUriPath($destinationFileRelativePath), 78 | ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); 79 | } 80 | 81 | $this->info('Done!'); 82 | 83 | return self::SUCCESS; 84 | } 85 | 86 | protected function ensureAssetIsVersioned(string $generatedFile): string 87 | { 88 | $digest = sha1_file($generatedFile); 89 | 90 | $versionedFile = preg_replace( 91 | '/(\.css)$/', 92 | sprintf('-%s$1', $digest), 93 | $generatedFile, 94 | ); 95 | 96 | File::move($generatedFile, $versionedFile); 97 | 98 | return $versionedFile; 99 | } 100 | 101 | private function fixFilePathForOs(string $path): string 102 | { 103 | return str_replace('/', DIRECTORY_SEPARATOR, $path); 104 | } 105 | 106 | private function fixOsFilePathToUriPath(string $path): string 107 | { 108 | return str_replace(DIRECTORY_SEPARATOR, '/', $path); 109 | } 110 | 111 | private function shouldVersion(): bool 112 | { 113 | if ($this->option('digest')) { 114 | return true; 115 | } 116 | 117 | return (bool) $this->option('prod'); 118 | } 119 | 120 | private function shouldMinify(): bool 121 | { 122 | if ($this->option('minify')) { 123 | return true; 124 | } 125 | 126 | return (bool) $this->option('prod'); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/Commands/DownloadCommand.php: -------------------------------------------------------------------------------- 1 | 'tailwindcss-linux-x64', 27 | 'Linux-aarch64' => 'tailwindcss-linux-arm64', 28 | 'Darwin-arm64' => 'tailwindcss-macos-arm64', 29 | 'Darwin-x86_64' => 'tailwindcss-macos-x64', 30 | 'Windows NT-AMD64' => 'tailwindcss-windows-x64.exe', 31 | ]; 32 | 33 | if (! $targetArchitecture = ($architectureToBinary["{$os}-{$cpu}"] ?? false)) { 34 | $this->error(sprintf('Looks like you are running a platform that is currently not supported (%s-%s).', $os, $cpu)); 35 | 36 | return self::FAILURE; 37 | } 38 | 39 | $targetPath = config('tailwindcss.bin_path'); 40 | $targetVersion = $this->option('cli-version') ?: config('tailwindcss.version'); 41 | 42 | if (File::exists($targetPath) && ! $this->option('force')) { 43 | $this->warn('Tailwind CSS binary already exists. Use the --force flag if you want to override it.'); 44 | 45 | return self::SUCCESS; 46 | } 47 | 48 | $this->info(sprintf('Downloading the Tailwind CSS binary (%s/%s/%s)...', $os, $cpu, $targetVersion)); 49 | 50 | File::ensureDirectoryExists(dirname((string) $targetPath)); 51 | 52 | if (File::exists($targetPath)) { 53 | File::delete($targetPath); 54 | } 55 | 56 | $handle = fopen($targetPath, 'w'); 57 | 58 | Http::timeout($this->option('timeout')) 59 | ->sink($targetPath) 60 | ->throw() 61 | ->get($this->downloadUrl($targetArchitecture, $targetVersion)); 62 | 63 | fclose($handle); 64 | 65 | if (File::size($targetPath) === 0) { 66 | throw new RuntimeException('The downloaded binary file is empty.'); 67 | } 68 | 69 | File::chmod($targetPath, 0755); 70 | 71 | $this->info('Done!'); 72 | 73 | return self::SUCCESS; 74 | } 75 | 76 | private function downloadUrl(string $architecture, string $version): string 77 | { 78 | return sprintf( 79 | 'https://github.com/tailwindlabs/tailwindcss/releases/download/%s/%s', 80 | $version, 81 | $architecture, 82 | ); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Commands/InstallCommand.php: -------------------------------------------------------------------------------- 1 | ensureTailwindConfigExists(); 26 | $this->ensureTailwindCliBinaryExists(); 27 | $this->appendTailwindStylesToLayouts(); 28 | $this->installMiddleware('\Tonysm\TailwindCss\Http\Middleware\AddLinkHeaderForPreloadedAssets::class'); 29 | $this->addIngoreLines(); 30 | $this->runFirstBuild(); 31 | 32 | $this->newLine(); 33 | 34 | $this->components->info('Tailwind CSS Laravel was installed successfully.'); 35 | 36 | return self::SUCCESS; 37 | } 38 | 39 | protected function phpBinary(): string 40 | { 41 | return (new PhpExecutableFinder)->find(false) ?: 'php'; 42 | } 43 | 44 | private function ensureTailwindConfigExists(): void 45 | { 46 | $this->copyStubToApp( 47 | stub: __DIR__.'/../../stubs/postcss.config.js', 48 | to: base_path('postcss.config.js'), 49 | ); 50 | 51 | if (! File::exists($appCssFilePath = resource_path('css/app.css')) || in_array(trim(File::get($appCssFilePath)), ['', '0'], true) || $this->mainCssIsDefault($appCssFilePath)) { 52 | $this->copyStubToApp( 53 | stub: __DIR__.'/../../stubs/resources/css/app.css', 54 | to: $appCssFilePath, 55 | ); 56 | } 57 | } 58 | 59 | private function ensureTailwindCliBinaryExists(): void 60 | { 61 | if (! File::exists(config('tailwindcss.bin_path')) || $this->option('download')) { 62 | Process::forever()->tty(SymfonyProcess::isTtySupported())->run([ 63 | $this->phpBinary(), 64 | 'artisan', 65 | 'tailwindcss:download', 66 | '--cli-version', 67 | $this->option('cli-version') ?: config('tailwindcss.version'), 68 | ], function ($_type, $output): void { 69 | $this->output->write($output); 70 | }); 71 | } 72 | } 73 | 74 | private function copyStubToApp(string $stub, string $to): void 75 | { 76 | File::ensureDirectoryExists(dirname($to)); 77 | File::copy($stub, $to); 78 | } 79 | 80 | /** 81 | * Install the middleware to a group in the application Http Kernel. 82 | * 83 | * @param string $group 84 | */ 85 | private function installMiddlewareAfter(string $after, string $name, $group = 'web'): void 86 | { 87 | $httpKernel = file_get_contents(app_path('Http/Kernel.php')); 88 | 89 | $middlewareGroups = Str::before(Str::after($httpKernel, '$middlewareGroups = ['), '];'); 90 | $middlewareGroup = Str::before(Str::after($middlewareGroups, "'{$group}' => ["), '],'); 91 | 92 | if (str_contains($middlewareGroup, $name)) { 93 | return; 94 | } 95 | 96 | $modifiedMiddlewareGroup = str_replace( 97 | $after.',', 98 | $after.','.PHP_EOL.' '.$name.',', 99 | $middlewareGroup, 100 | ); 101 | 102 | file_put_contents(app_path('Http/Kernel.php'), str_replace( 103 | $middlewareGroups, 104 | str_replace($middlewareGroup, $modifiedMiddlewareGroup, $middlewareGroups), 105 | $httpKernel 106 | )); 107 | } 108 | 109 | private function appendTailwindStylesToLayouts(): void 110 | { 111 | $this->existingLayoutFiles() 112 | ->each(fn ($file) => File::put( 113 | $file, 114 | (new AppendTailwindTag)(File::get($file)), 115 | )); 116 | } 117 | 118 | private function existingLayoutFiles() 119 | { 120 | return collect(['app', 'guest']) 121 | ->map(fn ($file) => resource_path("views/layouts/{$file}.blade.php")) 122 | ->filter(fn ($file) => File::exists($file)); 123 | } 124 | 125 | private function installMiddleware(string $middleware): void 126 | { 127 | if (file_exists(app_path('Http/Kernel.php'))) { 128 | $this->installMiddlewareAfter('SubstituteBindings::class', $middleware); 129 | } else { 130 | $this->installMiddlewareToBootstrap($middleware); 131 | } 132 | } 133 | 134 | private function installMiddlewareToBootstrap(string $middleware, string $group = 'web', string $modifier = 'append'): void 135 | { 136 | $bootstrapApp = file_get_contents(base_path('bootstrap/app.php')); 137 | 138 | if (str_contains($bootstrapApp, $middleware)) { 139 | return; 140 | } 141 | 142 | $bootstrapApp = str_replace( 143 | '->withMiddleware(function (Middleware $middleware) {', 144 | '->withMiddleware(function (Middleware $middleware) {' 145 | .PHP_EOL." \$middleware->{$group}({$modifier}: [" 146 | .PHP_EOL." {$middleware}," 147 | .PHP_EOL.' ]);' 148 | .PHP_EOL, 149 | $bootstrapApp, 150 | ); 151 | 152 | file_put_contents(base_path('bootstrap/app.php'), $bootstrapApp); 153 | } 154 | 155 | private function addIngoreLines(): void 156 | { 157 | $binary = basename((string) config('tailwindcss.bin_path')); 158 | 159 | if (str_contains(File::get(base_path('.gitignore')), $binary)) { 160 | return; 161 | } 162 | 163 | File::append(base_path('.gitignore'), <<tty(SymfonyProcess::isTtySupported())->run([ 175 | $this->phpBinary(), 176 | 'artisan', 177 | 'tailwindcss:build', 178 | ], function ($_type, $output): void { 179 | $this->output->write($output); 180 | }); 181 | } 182 | 183 | private function mainCssIsDefault($appCssFilePath): bool 184 | { 185 | return trim(File::get($appCssFilePath)) === trim(<<<'CSS' 186 | @tailwind base; 187 | @tailwind components; 188 | @tailwind utilities; 189 | CSS); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/Commands/WatchCommand.php: -------------------------------------------------------------------------------- 1 | call('tailwindcss:build', [ 18 | '--watch' => true, 19 | '--no-tty' => $this->option('no-tty'), 20 | ]); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Http/Middleware/AddLinkHeaderForPreloadedAssets.php: -------------------------------------------------------------------------------- 1 | manifest->assetsForPreloading()) !== []) { 15 | $response->header('Link', trim(implode(', ', array_filter(array_merge( 16 | [$response->headers->get('Link', null)], 17 | collect($assets)->map(fn ($attributes, $asset): string => implode('; ', array_merge( 18 | ["<$asset>"], 19 | collect(array_merge(['rel' => 'preload', 'as' => 'style'], $attributes)) 20 | ->map(fn ($value, $key): string => "{$key}={$value}") 21 | ->all(), 22 | )))->all(), 23 | ))))); 24 | } 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Manifest.php: -------------------------------------------------------------------------------- 1 | preloading; 16 | } 17 | 18 | public static function filename(): string 19 | { 20 | return basename(self::path()); 21 | } 22 | 23 | public static function path(): string 24 | { 25 | return config('tailwindcss.build.manifest_file_path'); 26 | } 27 | 28 | public function __invoke(string $path, $preload = true): string|\Illuminate\Support\HtmlString 29 | { 30 | static $manifests = []; 31 | 32 | if (! Str::startsWith($path, '/')) { 33 | $path = "/{$path}"; 34 | } 35 | 36 | $manifestPath = static::path(); 37 | 38 | if (! isset($manifests[$manifestPath])) { 39 | if (! is_file($manifestPath)) { 40 | throw new Exception('The Tailwind CSS manifest does not exist.'); 41 | } 42 | 43 | $manifests[$manifestPath] = json_decode(file_get_contents($manifestPath), true); 44 | } 45 | 46 | $manifest = $manifests[$manifestPath]; 47 | 48 | if (! isset($manifest[$path])) { 49 | $exception = new Exception("Unable to locate Tailwind CSS compiled file: {$path}."); 50 | 51 | if (! app('config')->get('app.debug')) { 52 | report($exception); 53 | 54 | return $path; 55 | } 56 | 57 | throw $exception; 58 | } 59 | 60 | $asset = asset($manifest[$path]); 61 | 62 | if ($preload) { 63 | $this->preloading[$asset] = is_array($preload) ? $preload : []; 64 | } 65 | 66 | return new HtmlString($asset); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/TailwindCssServiceProvider.php: -------------------------------------------------------------------------------- 1 | name('tailwindcss') 19 | ->hasConfigFile() 20 | ->hasCommands([ 21 | Commands\DownloadCommand::class, 22 | Commands\InstallCommand::class, 23 | Commands\BuildCommand::class, 24 | Commands\WatchCommand::class, 25 | ]); 26 | } 27 | 28 | public function packageRegistered(): void 29 | { 30 | $this->app->scoped(Manifest::class); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Testing/InteractsWithTailwind.php: -------------------------------------------------------------------------------- 1 | swap(Manifest::class, fn (): \Illuminate\Support\HtmlString => new HtmlString('')); 16 | 17 | return $this; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/helpers.php: -------------------------------------------------------------------------------- 1 |