Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Tailwind CSS Standalone CLI utility filter #42

Merged
merged 8 commits into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
}
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