Skip to content

Commit 4cde58e

Browse files
authored
Allow working with multiple entrypoints (#58)
* feat: Change input_css option to accept an array of input css files * feat: Allow backward compatibility by allowing to accept a string as input css * fix: Fix "The input CSS file is not one of the configured input files." when css file is provided as relative path to tailwind:build command * feat: Provide input css file as an optional argument instead of an option to `tailwind:build` command * fix: Fix failed PHPStan and PHP CS Fixer tests * fix: Store input files with their absolute path in Tailwind Builder * fix: Fix inconsistency in `getInputCssPaths` mocked return value. The mocked return value should use `realpath` * fix: Revert the documentation to use the `input_css` config option * docs: Update the documentation to explain the current internal working * refactor: Rename `tailwind:bundle`'s argument from `input` to `input_css` for consistency with config file * refactor: Change `getInputCssContent` parameter type from `?string` to `string` as it's always passed
1 parent 11b5a11 commit 4cde58e

File tree

12 files changed

+109
-43
lines changed

12 files changed

+109
-43
lines changed

doc/index.rst

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,11 @@ download the correct Tailwind binary for your system into a ``var/tailwind/``
9494
directory.
9595

9696
When you run ``tailwind:build``, that binary is used to compile
97-
your CSS file into a ``var/tailwind/tailwind.built.css`` file. Finally,
98-
when the contents of ``assets/styles/app.css`` is requested, the bundle
99-
swaps the contents of that file with the contents of ``var/tailwind/tailwind.built.css``.
97+
each CSS file into a ``var/tailwind/<filename>.built.css`` file.
98+
Finally, when the contents of the CSS file is requested, the bundle swaps the
99+
contents of that file with the contents of ``var/tailwind/<filename>.built.css``.
100+
101+
E.g. : A request for ``assets/styles/app.css`` will be replaced by ``var/tailwind/app.built.css``.
100102
Nice!
101103

102104
Deploying
@@ -152,7 +154,7 @@ To see the full config from this bundle, run:
152154
$ php bin/console config:dump symfonycasts_tailwind
153155
154156
The main option is ``input_css`` option, which defaults to ``assets/styles/app.css``.
155-
This represents the "source" Tailwind file (the one that contains the ``@tailwind``
157+
This represents the "source" Tailwind files (the one that contains the ``@tailwind``
156158
directives):
157159

158160
.. code-block:: yaml
@@ -161,6 +163,15 @@ directives):
161163
symfonycasts_tailwind:
162164
input_css: 'assets/styles/other.css'
163165
166+
It's possible to use multiple input files by providing an array:
167+
.. code-block:: yaml
168+
169+
# config/packages/symfonycasts_tailwind.yaml
170+
symfonycasts_tailwind:
171+
input_css:
172+
- 'assets/styles/other.css'
173+
- 'assets/styles/another.css'
174+
164175
Another option is the ``config_file`` option, which defaults to ``tailwind.config.js``.
165176
This represents the Tailwind configuration file:
166177

phpstan.neon.dist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ parameters:
44
- src
55
ignoreErrors:
66
-
7-
message: "#^Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeParentInterface\\:\\:scalarNode\\(\\)\\.$#"
7+
message: "#^Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeParentInterface\\:\\:beforeNormalization\\(\\)\\.$#"
88
count: 1
99
path: src/DependencyInjection/TailwindExtension.php

src/AssetMapper/TailwindCssAssetCompiler.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,16 @@ public function __construct(private TailwindBuilder $tailwindBuilder)
2525

2626
public function supports(MappedAsset $asset): bool
2727
{
28-
return realpath($asset->sourcePath) === realpath($this->tailwindBuilder->getInputCssPath());
28+
return \in_array(
29+
realpath($asset->sourcePath),
30+
$this->tailwindBuilder->getInputCssPaths(),
31+
);
2932
}
3033

3134
public function compile(string $content, MappedAsset $asset, AssetMapperInterface $assetMapper): string
3235
{
33-
$asset->addFileDependency($this->tailwindBuilder->getInternalOutputCssPath());
36+
$asset->addFileDependency($this->tailwindBuilder->getInternalOutputCssPath($asset->sourcePath));
3437

35-
return $this->tailwindBuilder->getOutputCssContent();
38+
return $this->tailwindBuilder->getOutputCssContent($asset->sourcePath);
3639
}
3740
}

src/Command/TailwindBuildCommand.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
use Symfony\Component\Console\Attribute\AsCommand;
1313
use Symfony\Component\Console\Command\Command;
14+
use Symfony\Component\Console\Input\InputArgument;
1415
use Symfony\Component\Console\Input\InputInterface;
1516
use Symfony\Component\Console\Input\InputOption;
1617
use Symfony\Component\Console\Output\OutputInterface;
@@ -32,6 +33,7 @@ public function __construct(
3233
protected function configure(): void
3334
{
3435
$this
36+
->addArgument('input_css', InputArgument::OPTIONAL, 'The input CSS file to compile')
3537
->addOption('watch', 'w', null, 'Watch for changes and rebuild automatically')
3638
->addOption('poll', null, null, 'Use polling instead of filesystem events when watching')
3739
->addOption('minify', 'm', InputOption::VALUE_NONE, 'Minify the output CSS')
@@ -47,6 +49,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
4749
watch: $input->getOption('watch'),
4850
poll: $input->getOption('poll'),
4951
minify: $input->getOption('minify'),
52+
inputFile: $input->getArgument('input_css'),
5053
);
5154
$process->wait(function ($type, $buffer) use ($io) {
5255
$io->write($buffer);

src/Command/TailwindInitCommand.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ private function createTailwindConfig(SymfonyStyle $io): bool
9292

9393
private function addTailwindDirectives(SymfonyStyle $io): void
9494
{
95-
$inputFile = $this->tailwindBuilder->getInputCssPath();
95+
$inputFile = $this->tailwindBuilder->getInputCssPaths()[0];
9696
$contents = is_file($inputFile) ? file_get_contents($inputFile) : '';
9797
if (str_contains($contents, '@tailwind base')) {
9898
$io->note(sprintf('Tailwind directives already exist in "%s"', $inputFile));

src/DependencyInjection/TailwindExtension.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,11 @@ public function getConfigTreeBuilder(): TreeBuilder
5353

5454
$rootNode
5555
->children()
56-
->scalarNode('input_css')
57-
->info('Path to CSS file to process through Tailwind')
58-
->defaultValue('%kernel.project_dir%/assets/styles/app.css')
56+
->arrayNode('input_css')
57+
->prototype('scalar')->end()
58+
->beforeNormalization()->castToArray()->end()
59+
->info('Paths to CSS files to process through Tailwind')
60+
->defaultValue(['%kernel.project_dir%/assets/styles/app.css'])
5961
->end()
6062
->scalarNode('config_file')
6163
->info('Path to the tailwind.config.js file')

src/TailwindBuilder.php

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,35 +24,39 @@
2424
class TailwindBuilder
2525
{
2626
private ?SymfonyStyle $output = null;
27-
private readonly string $inputPath;
27+
private readonly array $inputPaths;
2828

2929
public function __construct(
3030
private readonly string $projectRootDir,
31-
string $inputPath,
31+
array $inputPaths,
3232
private readonly string $tailwindVarDir,
3333
private CacheInterface $cache,
3434
private readonly ?string $binaryPath = null,
3535
private readonly ?string $binaryVersion = null,
3636
private readonly string $configPath = 'tailwind.config.js'
3737
) {
38-
if (is_file($inputPath)) {
39-
$this->inputPath = $inputPath;
40-
} else {
41-
$this->inputPath = $projectRootDir.'/'.$inputPath;
42-
43-
if (!is_file($this->inputPath)) {
44-
throw new \InvalidArgumentException(sprintf('The input CSS file "%s" does not exist.', $inputPath));
45-
}
38+
$paths = [];
39+
foreach ($inputPaths as $inputPath) {
40+
$paths[] = $this->validateInputFile($inputPath);
4641
}
42+
43+
$this->inputPaths = $paths;
4744
}
4845

4946
public function runBuild(
5047
bool $watch,
5148
bool $poll,
5249
bool $minify,
50+
?string $inputFile = null,
5351
): Process {
5452
$binary = $this->createBinary();
55-
$arguments = ['-c', $this->configPath, '-i', $this->inputPath, '-o', $this->getInternalOutputCssPath()];
53+
54+
$inputPath = $this->validateInputFile($inputFile ?? $this->inputPaths[0]);
55+
if (!\in_array($inputPath, $this->inputPaths)) {
56+
throw new \InvalidArgumentException(sprintf('The input CSS file "%s" is not one of the configured input files.', $inputPath));
57+
}
58+
59+
$arguments = ['-c', $this->configPath, '-i', $inputPath, '-o', $this->getInternalOutputCssPath($inputPath)];
5660
if ($watch) {
5761
$arguments[] = '--watch';
5862
if ($poll) {
@@ -82,7 +86,7 @@ public function runBuild(
8286
return $process;
8387
}
8488

85-
public function runInit()
89+
public function runInit(): Process
8690
{
8791
$binary = $this->createBinary();
8892
$process = $binary->createProcess(['init']);
@@ -102,28 +106,43 @@ public function setOutput(SymfonyStyle $output): void
102106
$this->output = $output;
103107
}
104108

105-
public function getInternalOutputCssPath(): string
109+
public function getInternalOutputCssPath(string $inputPath): string
106110
{
107-
return $this->tailwindVarDir.'/tailwind.built.css';
111+
$inputFileName = pathinfo($inputPath, \PATHINFO_FILENAME);
112+
113+
return "{$this->tailwindVarDir}/{$inputFileName}.built.css";
108114
}
109115

110-
public function getInputCssPath(): string
116+
public function getInputCssPaths(): array
111117
{
112-
return $this->inputPath;
118+
return $this->inputPaths;
113119
}
114120

115121
public function getConfigFilePath(): string
116122
{
117123
return $this->configPath;
118124
}
119125

120-
public function getOutputCssContent(): string
126+
public function getOutputCssContent(string $inputFile): string
121127
{
122-
if (!is_file($this->getInternalOutputCssPath())) {
128+
if (!is_file($this->getInternalOutputCssPath($inputFile))) {
123129
throw new \RuntimeException('Built Tailwind CSS file does not exist: run "php bin/console tailwind:build" to generate it');
124130
}
125131

126-
return file_get_contents($this->getInternalOutputCssPath());
132+
return file_get_contents($this->getInternalOutputCssPath($inputFile));
133+
}
134+
135+
private function validateInputFile(string $inputPath): string
136+
{
137+
if (is_file($inputPath)) {
138+
return realpath($inputPath);
139+
}
140+
141+
if (is_file($this->projectRootDir.'/'.$inputPath)) {
142+
return realpath($this->projectRootDir.'/'.$inputPath);
143+
}
144+
145+
throw new \InvalidArgumentException(sprintf('The input CSS file "%s" does not exist.', $inputPath));
127146
}
128147

129148
private function createBinary(): TailwindBinary

tests/AssetMapper/TailwindCssAssetCompilerTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ public function testCompile()
2121
{
2222
$builder = $this->createMock(TailwindBuilder::class);
2323
$builder->expects($this->any())
24-
->method('getInputCssPath')
25-
->willReturn(__DIR__.'/../fixtures/assets/styles/app.css');
24+
->method('getInputCssPaths')
25+
->willReturn([realpath(__DIR__.'/../fixtures/assets/styles/app.css')]);
2626
$builder->expects($this->once())
2727
->method('getInternalOutputCssPath');
2828
$builder->expects($this->once())

tests/FunctionalTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ protected function setUp(): void
2424
$fs->remove($tailwindVarDir);
2525
}
2626
$fs->mkdir($tailwindVarDir);
27-
file_put_contents($tailwindVarDir.'/tailwind.built.css', <<<EOF
27+
file_put_contents($tailwindVarDir.'/app.built.css', <<<EOF
2828
body {
2929
padding: 17px;
3030
background-image: url('../images/penguin.png');
@@ -35,8 +35,8 @@ protected function setUp(): void
3535

3636
protected function tearDown(): void
3737
{
38-
if (is_file(__DIR__.'/fixtures/var/tailwind/tailwind.built.css')) {
39-
unlink(__DIR__.'/fixtures/var/tailwind/tailwind.built.css');
38+
if (is_file(__DIR__.'/fixtures/var/tailwind/app.built.css')) {
39+
unlink(__DIR__.'/fixtures/var/tailwind/app.built.css');
4040
}
4141
}
4242

tests/TailwindBuilderTest.php

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public function testIntegrationWithDefaultOptions(): void
3939
{
4040
$builder = new TailwindBuilder(
4141
__DIR__.'/fixtures',
42-
__DIR__.'/fixtures/assets/styles/app.css',
42+
[__DIR__.'/fixtures/assets/styles/app.css'],
4343
__DIR__.'/fixtures/var/tailwind',
4444
new ArrayAdapter(),
4545
null,
@@ -50,17 +50,17 @@ public function testIntegrationWithDefaultOptions(): void
5050
$process->wait();
5151

5252
$this->assertTrue($process->isSuccessful());
53-
$this->assertFileExists(__DIR__.'/fixtures/var/tailwind/tailwind.built.css');
53+
$this->assertFileExists(__DIR__.'/fixtures/var/tailwind/app.built.css');
5454

55-
$outputFileContents = file_get_contents(__DIR__.'/fixtures/var/tailwind/tailwind.built.css');
55+
$outputFileContents = file_get_contents(__DIR__.'/fixtures/var/tailwind/app.built.css');
5656
$this->assertStringContainsString("body {\n background-color: red;\n}", $outputFileContents, 'The output file should contain non-minified CSS.');
5757
}
5858

5959
public function testIntegrationWithMinify(): void
6060
{
6161
$builder = new TailwindBuilder(
6262
__DIR__.'/fixtures',
63-
__DIR__.'/fixtures/assets/styles/app.css',
63+
[__DIR__.'/fixtures/assets/styles/app.css'],
6464
__DIR__.'/fixtures/var/tailwind',
6565
new ArrayAdapter(),
6666
null,
@@ -71,9 +71,30 @@ public function testIntegrationWithMinify(): void
7171
$process->wait();
7272

7373
$this->assertTrue($process->isSuccessful());
74-
$this->assertFileExists(__DIR__.'/fixtures/var/tailwind/tailwind.built.css');
74+
$this->assertFileExists(__DIR__.'/fixtures/var/tailwind/app.built.css');
7575

76-
$outputFileContents = file_get_contents(__DIR__.'/fixtures/var/tailwind/tailwind.built.css');
76+
$outputFileContents = file_get_contents(__DIR__.'/fixtures/var/tailwind/app.built.css');
7777
$this->assertStringContainsString('body{background-color:red}', $outputFileContents, 'The output file should contain minified CSS.');
7878
}
79+
80+
public function testBuildProvidedInputFile(): void
81+
{
82+
$builder = new TailwindBuilder(
83+
__DIR__.'/fixtures',
84+
[__DIR__.'/fixtures/assets/styles/app.css', __DIR__.'/fixtures/assets/styles/second.css'],
85+
__DIR__.'/fixtures/var/tailwind',
86+
new ArrayAdapter(),
87+
null,
88+
null,
89+
__DIR__.'/fixtures/tailwind.config.js'
90+
);
91+
$process = $builder->runBuild(watch: false, poll: false, minify: true, inputFile: 'assets/styles/second.css');
92+
$process->wait();
93+
94+
$this->assertTrue($process->isSuccessful());
95+
$this->assertFileExists(__DIR__.'/fixtures/var/tailwind/second.built.css');
96+
97+
$outputFileContents = file_get_contents(__DIR__.'/fixtures/var/tailwind/second.built.css');
98+
$this->assertStringContainsString('body{background-color:blue}', $outputFileContents, 'The output file should contain minified CSS.');
99+
}
79100
}

0 commit comments

Comments
 (0)