├── .gitignore ├── LICENSE ├── README.md ├── bin └── tailwindcss-linux-x64 ├── composer.json ├── composer.lock ├── makefile └── src └── TailwindPhp.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /.idea/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Arnaud Lemercier 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Tailwind Php 2 | ================== 3 | TailwindPHP - use Tailwind CSS in your PHP projects (without npm) 4 | 5 | Tailwind PHP allows you to compile TailwindCSS on the fly directly with PHP without dependencies (without npm or postcss). 6 | 7 | ## Installing 8 | 9 | IMPORTANT : Alpha version required linux x64. 10 | 11 | [PHP](https://php.net) 8.0+ and [Composer](https://getcomposer.org) are required. 12 | 13 | 14 | ```bash 15 | composer req arnolem/tailwindphp 16 | ``` 17 | Add execution rights 18 | ```bash 19 | chmod +x ./vendor/arnolem/tailwindphp/bin/* 20 | ``` 21 | 22 | ## Configuration 23 | 24 | 1- create a ``tailwind.config.js`` config file 25 | 26 | ```javascript 27 | /** @type {import('tailwindcss').Config} */ 28 | module.exports = { 29 | content: ["./templates/**/*.twig"], 30 | theme: { 31 | extend: {}, 32 | }, 33 | plugins: [], 34 | } 35 | ``` 36 | 2-Add a link to the css route 37 | 38 | ```html 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | ``` 48 | 49 | 3-Create a css route with TailwindPhp 50 | 51 | 52 | ```php 53 | 'text/css'] 70 | ); 71 | } 72 | } 73 | ``` 74 | 75 | Optional-Enable SCSS compilation 76 | 77 | 78 | ```php 79 | 'text/css'] 100 | ); 101 | } 102 | } 103 | ``` 104 | 105 | ## Credits 106 | 107 | - Arnaud Lemercier is based on [Wixiweb](https://wixiweb.fr). 108 | 109 | ## License 110 | 111 | TailwindPHP is licensed under [The MIT License (MIT)](LICENSE). -------------------------------------------------------------------------------- /bin/tailwindcss-linux-x64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arnolem/tailwindphp/8806bb0164aff548f754ccd3f621e3d529eaf015/bin/tailwindcss-linux-x64 -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arnolem/tailwindphp", 3 | "type": "library", 4 | "license": "MIT", 5 | "homepage": "https://www.wixiweb.fr", 6 | "keywords": ["tailwindcss", "tailwind", "symfony", "laravel", "php"], 7 | "description": "TailwindPHP - use Tailwind CSS in your PHP projects (without npm)", 8 | "minimum-stability": "stable", 9 | "prefer-stable": true, 10 | "authors": [ 11 | { 12 | "name": "Arnaud Lemercier", 13 | "email": "arnaud@wixiweb.fr" 14 | } 15 | ], 16 | "require": { 17 | "php": ">=7.4", 18 | "symfony/process": ">=6.3", 19 | "scssphp/scssphp": "^1.11" 20 | }, 21 | "autoload": { 22 | "psr-4": { 23 | "Arnolem\\TailwindPhp\\": "src/" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "ae958f3481cd2bb227d6097dd1dcb9a4", 8 | "packages": [ 9 | { 10 | "name": "scssphp/scssphp", 11 | "version": "v1.11.1", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/scssphp/scssphp.git", 15 | "reference": "ace2503684bab0dcc817d7614c8a54b865122414" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/scssphp/scssphp/zipball/ace2503684bab0dcc817d7614c8a54b865122414", 20 | "reference": "ace2503684bab0dcc817d7614c8a54b865122414", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "ext-ctype": "*", 25 | "ext-json": "*", 26 | "php": ">=5.6.0" 27 | }, 28 | "require-dev": { 29 | "bamarni/composer-bin-plugin": "^1.4", 30 | "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.3 || ^9.4", 31 | "sass/sass-spec": "*", 32 | "squizlabs/php_codesniffer": "~3.5", 33 | "symfony/phpunit-bridge": "^5.1", 34 | "thoughtbot/bourbon": "^7.0", 35 | "twbs/bootstrap": "~5.0", 36 | "twbs/bootstrap4": "4.6.1", 37 | "zurb/foundation": "~6.7.0" 38 | }, 39 | "suggest": { 40 | "ext-iconv": "Can be used as fallback when ext-mbstring is not available", 41 | "ext-mbstring": "For best performance, mbstring should be installed as it is faster than ext-iconv" 42 | }, 43 | "bin": [ 44 | "bin/pscss" 45 | ], 46 | "type": "library", 47 | "extra": { 48 | "bamarni-bin": { 49 | "forward-command": false, 50 | "bin-links": false 51 | } 52 | }, 53 | "autoload": { 54 | "psr-4": { 55 | "ScssPhp\\ScssPhp\\": "src/" 56 | } 57 | }, 58 | "notification-url": "https://packagist.org/downloads/", 59 | "license": [ 60 | "MIT" 61 | ], 62 | "authors": [ 63 | { 64 | "name": "Anthon Pang", 65 | "email": "apang@softwaredevelopment.ca", 66 | "homepage": "https://github.com/robocoder" 67 | }, 68 | { 69 | "name": "Cédric Morin", 70 | "email": "cedric@yterium.com", 71 | "homepage": "https://github.com/Cerdic" 72 | } 73 | ], 74 | "description": "scssphp is a compiler for SCSS written in PHP.", 75 | "homepage": "http://scssphp.github.io/scssphp/", 76 | "keywords": [ 77 | "css", 78 | "less", 79 | "sass", 80 | "scss", 81 | "stylesheet" 82 | ], 83 | "support": { 84 | "issues": "https://github.com/scssphp/scssphp/issues", 85 | "source": "https://github.com/scssphp/scssphp/tree/v1.11.1" 86 | }, 87 | "time": "2023-09-24T13:38:17+00:00" 88 | }, 89 | { 90 | "name": "symfony/process", 91 | "version": "v6.3.4", 92 | "source": { 93 | "type": "git", 94 | "url": "https://github.com/symfony/process.git", 95 | "reference": "0b5c29118f2e980d455d2e34a5659f4579847c54" 96 | }, 97 | "dist": { 98 | "type": "zip", 99 | "url": "https://api.github.com/repos/symfony/process/zipball/0b5c29118f2e980d455d2e34a5659f4579847c54", 100 | "reference": "0b5c29118f2e980d455d2e34a5659f4579847c54", 101 | "shasum": "" 102 | }, 103 | "require": { 104 | "php": ">=8.1" 105 | }, 106 | "type": "library", 107 | "autoload": { 108 | "psr-4": { 109 | "Symfony\\Component\\Process\\": "" 110 | }, 111 | "exclude-from-classmap": [ 112 | "/Tests/" 113 | ] 114 | }, 115 | "notification-url": "https://packagist.org/downloads/", 116 | "license": [ 117 | "MIT" 118 | ], 119 | "authors": [ 120 | { 121 | "name": "Fabien Potencier", 122 | "email": "fabien@symfony.com" 123 | }, 124 | { 125 | "name": "Symfony Community", 126 | "homepage": "https://symfony.com/contributors" 127 | } 128 | ], 129 | "description": "Executes commands in sub-processes", 130 | "homepage": "https://symfony.com", 131 | "support": { 132 | "source": "https://github.com/symfony/process/tree/v6.3.4" 133 | }, 134 | "funding": [ 135 | { 136 | "url": "https://symfony.com/sponsor", 137 | "type": "custom" 138 | }, 139 | { 140 | "url": "https://github.com/fabpot", 141 | "type": "github" 142 | }, 143 | { 144 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 145 | "type": "tidelift" 146 | } 147 | ], 148 | "time": "2023-08-07T10:39:22+00:00" 149 | } 150 | ], 151 | "packages-dev": [], 152 | "aliases": [], 153 | "minimum-stability": "stable", 154 | "stability-flags": [], 155 | "prefer-stable": true, 156 | "prefer-lowest": false, 157 | "platform": { 158 | "php": "8.*" 159 | }, 160 | "platform-dev": [], 161 | "plugin-api-version": "2.3.0" 162 | } 163 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | export COMPOSER_ALLOW_SUPERUSER=1 2 | 3 | default: # Commande make par défaut 4 | @make -s help 5 | 6 | help: # Aide : Liste les commandes possibles 7 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 8 | 9 | update-binaries: ## recupère les derniers binaires 10 | rm -fr bin 11 | wget -c -P bin https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64 12 | chmod +x bin/tailwindcss-linux-x64 -------------------------------------------------------------------------------- /src/TailwindPhp.php: -------------------------------------------------------------------------------- 1 | compileString($css)->getCss(); 35 | $css = self::unprotectTailwindFunctionForScss($css); 36 | } catch (Throwable $e) { 37 | return self::error($e->getMessage(), 'To resolve the issue, look for a SCSS syntax error.'); 38 | } 39 | } 40 | 41 | // Temp filepath 42 | if ($css) { 43 | 44 | $input = tempnam(sys_get_temp_dir(), 'css_'); 45 | file_put_contents($input, $css); 46 | 47 | $tailwindcss = new Process([ 48 | $binFolder . '/tailwindcss-linux-x64', 49 | '--no-autoprefixer', // for speed 50 | '-c', 51 | $configFile, 52 | '-i', 53 | $input, 54 | ], '..'); 55 | } else { 56 | 57 | $tailwindcss = new Process([ 58 | $binFolder . '/tailwindcss-linux-x64', 59 | '--no-autoprefixer', // for speed 60 | '-c', 61 | $configFile, 62 | ], '..'); 63 | } 64 | 65 | $status = $tailwindcss->run(); 66 | 67 | 68 | if ($status !== 0) { 69 | 70 | $output = str_replace("\n", "\\A", $tailwindcss->getErrorOutput()); 71 | $errors = trim(htmlspecialchars($output, ENT_COMPAT, 'UTF-8')); 72 | 73 | if (str_contains($errors, "Permission denied")) { 74 | $solution = 'To solve this issue: `chmod +x ./vendor/arnolem/tailwindphp/bin/*`'; 75 | } 76 | 77 | return self::error($errors, $solution ?? null); 78 | } 79 | 80 | // Delete tmpfile 81 | if (!empty($input)) { 82 | unlink($input); 83 | } 84 | 85 | return $tailwindcss->getOutput(); 86 | } 87 | 88 | private static function error($errors, $solution = null) 89 | { 90 | 91 | // Detect name (easter eggs) 92 | //$pattern = '/\/srv\/www\/vhosts\/([^\/]+)\/[^\/]+\/vendor\//'; 93 | 94 | $pattern = '/(?:\w+\.)+([^.]+)\.([^.]+)\.intra\.wixiweb\.net$/'; 95 | $domain = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://" . $_SERVER['HTTP_HOST']; 96 | if (preg_match($pattern, $domain, $matches)) { 97 | $name = ucfirst(strtolower($matches[1])); 98 | $solution = "Hi $name 🌈! " . $solution; 99 | } 100 | 101 | $message = implode('\A', [ 102 | $solution, 103 | '', 104 | 'TAILWINDPHP errors :', 105 | $errors, 106 | ]); 107 | 108 | return << *{ 110 | display: none; 111 | } 112 | body:before{ 113 | white-space: pre; 114 | border: 5px solid red; 115 | padding: 16px; 116 | border-radius: 16px; 117 | font-family: Verdana, sans-serif; 118 | color: #333333; 119 | line-height: 130%; 120 | background: #ffffff; 121 | display: block; 122 | content: "$message"; 123 | } 124 | CSS; 125 | } 126 | 127 | private static function protectTailwindFunctionForScss($css): string 128 | { 129 | $lines = explode("\n", $css); 130 | $result = ""; 131 | 132 | foreach ($lines as $line) { 133 | // Vérifie si la ligne contient "theme()" et la commente 134 | if (str_contains($line, 'theme(')) { 135 | $line = '/*TAILWINDPHP ' . trim($line) . '*/'; 136 | } 137 | $result .= $line . "\n"; 138 | } 139 | 140 | return $result; 141 | } 142 | 143 | 144 | private static function unprotectTailwindFunctionForScss($css): string 145 | { 146 | return preg_replace('/\/\*TAILWINDPHP(.*?)\*\//s', '$1', $css); 147 | } 148 | } 149 | --------------------------------------------------------------------------------