Skip to content

Commit

Permalink
Add Tailwind CSS Standalone CLI utility filter (#42)
Browse files Browse the repository at this point in the history
  • Loading branch information
bennothommo authored Jul 8, 2024
1 parent 2dfc881 commit fde0642
Show file tree
Hide file tree
Showing 9 changed files with 827 additions and 1 deletion.
7 changes: 7 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ jobs:
sudo apt-get -y install jpegoptim libjpeg-progs optipng
npm install
- name: Install Tailwind CSS Standalone CLI
uses: supplypike/setup-bin@v4
with:
uri: 'https://github.com/tailwindlabs/tailwindcss/releases/download/v3.4.4/tailwindcss-linux-x64'
name: 'tailwindcss'
version: '3.4.4'

- name: Install PHP
uses: shivammathur/setup-php@v2
with:
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ The core provides the following filters in the `Assetic\Filter` namespace:
* `SeparatorFilter`: inserts a separator between assets to prevent merge failures
* `StylesheetMinifyFilter`: compresses stylesheet CSS files
* `StylusFilter`: parses STYL into CSS
* `TailwindCssFilter`: builds a Tailwind CSS stylesheet using the Tailwind CSS standalone CLI utility
* `TypeScriptFilter`: parses TypeScript into Javascript
* `UglifyCssFilter`: minifies CSS
* `UglifyJs2Filter`: minifies Javascript
Expand Down
26 changes: 25 additions & 1 deletion src/Assetic/Filter/BaseProcessFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ abstract class BaseProcessFilter extends BaseFilter
*/
protected $binaryPath;

/**
* @var string|null Defines the working directory for the process.
*/
protected $workingDirectory = null;

/**
* @var boolean Flag to indicate that the process will output the result to the input file
*/
Expand Down Expand Up @@ -75,6 +80,25 @@ public function setTimeout($timeout)
$this->timeout = $timeout;
}

/**
* Sets the binary path for the process.
*/
public function setBinaryPath(string $binaryPath): void
{
$this->binaryPath = $binaryPath;
}

/**
* Sets the working directory for the process.
*
* Some processes may require a specified working directory to function correctly, ie. locating configurations,
* assets, etc.
*/
public function setWorkingDirectory(?string $workingDirectory = null): void
{
$this->workingDirectory = $workingDirectory;
}

/**
* Creates a new process.
*
Expand All @@ -83,7 +107,7 @@ public function setTimeout($timeout)
*/
protected function createProcess(array $arguments = [])
{
$process = new Process($arguments);
$process = new Process($arguments, $this->workingDirectory);

if (null !== $this->timeout) {
$process->setTimeout($this->timeout);
Expand Down
88 changes: 88 additions & 0 deletions src/Assetic/Filter/TailwindCssFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

namespace Assetic\Filter;

use Assetic\Contracts\Asset\AssetInterface;

/**
* Tailwind CSS filter.
*
* Compiles Tailwind CSS into standard CSS, using the standalone Tailwind CSS CLI tool.
*
* @author Ben Thomson <[email protected]>
*/
class TailwindCssFilter extends BaseProcessFilter
{
/**
* @var string Path to the binary for this process based filter
*/
protected $binaryPath = '/usr/bin/tailwindcss';

/**
* @var string|null Path to the Tailwind configuration file.
*/
protected $configPath = null;

/**
* @var bool Is minification enabled?
*/
protected $minify = false;

/**
* @var bool Is autoprefixing enabled?
*/
protected $autoprefix = true;

/**
* Sets the path for the configuration file.
*/
public function setConfigPath(string $configPath): void
{
$this->configPath = $configPath;
}

/**
* Enable minification.
*/
public function minify(): void
{
$this->minify = true;
}

/**
* Disable autoprefixer.
*/
public function withoutAutoprefixing(): void
{
$this->autoprefix = false;
}

/**
* {@inheritDoc}
*/
public function filterLoad(AssetInterface $asset)
{
$args = [
'--input',
'{INPUT}',
'--output',
'{OUTPUT}'
];

if (!is_null($this->configPath)) {
$args[] = '--config';
$args[] = $this->configPath;
}

if ($this->minify) {
$args[] = '--minify';
}

if (!$this->autoprefix) {
$args[] = '--no-autoprefixer';
}

$result = $this->runProcess($asset->getContent(), $args);
$asset->setContent($result);
}
}
89 changes: 89 additions & 0 deletions tests/Assetic/Test/Filter/TailwindCssFilterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php

namespace Assetic\Test\Filter;

use Assetic\Asset\FileAsset;
use Assetic\Filter\TailwindCssFilter;

/**
* @group integration
*/
class TailwindCssFilterTest extends FilterTestCase
{
/** @var TailwindCssFilter|null */
private $filter;

protected function setUp(): void
{
$tailwindCssBin = $this->findExecutable('tailwindcss', 'TAILWINDCSS_BIN');

if (!$tailwindCssBin) {
$this->markTestSkipped('Unable to find `tailwindcss` executable.');
}

$this->filter = new TailwindCssFilter($tailwindCssBin);
}

protected function tearDown(): void
{
$this->filter = null;
}

public function testFilterLoad()
{
$fileAsset = new FileAsset(__DIR__ . '/fixtures/tailwindcss/css/style.css');
$fileAsset->load();

$this->filter->setConfigPath(__DIR__ . '/fixtures/tailwindcss/tailwind.config.js');
$this->filter->setWorkingDirectory(__DIR__ . '/fixtures/tailwindcss');
$this->filter->filterLoad($fileAsset);
$contents = $fileAsset->getContent();

// Detect boilerplate TailwindCSS styling
$expected = <<<'STYLE'
::backdrop {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
STYLE;

$this->assertStringContainsString($expected, $contents);

// Detect class defined in HTML
$expected = <<<'STYLE'
.text-gray-800 {
--tw-text-opacity: 1;
color: rgb(31 41 55 / var(--tw-text-opacity));
}
STYLE;

$this->assertStringContainsString($expected, $contents);
}

public function testFilterLoadWithMinification()
{
$fileAsset = new FileAsset(__DIR__ . '/fixtures/tailwindcss/css/style.css');
$fileAsset->load();

$this->filter->setConfigPath(__DIR__ . '/fixtures/tailwindcss/tailwind.config.js');
$this->filter->setWorkingDirectory(__DIR__ . '/fixtures/tailwindcss');
$this->filter->minify();
$this->filter->filterLoad($fileAsset);
$contents = $fileAsset->getContent();

// Detect boilerplate TailwindCSS styling
$expected = <<<'STYLE'
*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;
STYLE;

$this->assertStringContainsString($expected, $contents);

// Detect class defined in HTML
$expected = <<<'STYLE'
.text-gray-800{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity))}
STYLE;

$this->assertStringContainsString($expected, $contents);
}
}
3 changes: 3 additions & 0 deletions tests/Assetic/Test/Filter/fixtures/tailwindcss/css/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
15 changes: 15 additions & 0 deletions tests/Assetic/Test/Filter/fixtures/tailwindcss/html/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tailwind CSS test</title>
</head>
<body class="antialiased bg-white text-gray-800">
<div class="flex flex-row gap-10">
<div class="bg-red text-white p-4">Red</div>
<div class="bg-green text-white p-4">Green</div>
<div class="bg-blue text-white p-4">Blue</div>
</div>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./html/**/*.html"],
theme: {
extend: {},
},
plugins: [],
}
Loading

0 comments on commit fde0642

Please sign in to comment.