diff --git a/doc/index.rst b/doc/index.rst index a235851..6717670 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -94,9 +94,11 @@ download the correct Tailwind binary for your system into a ``var/tailwind/`` directory. When you run ``tailwind:build``, that binary is used to compile -your CSS file into a ``var/tailwind/tailwind.built.css`` file. Finally, -when the contents of ``assets/styles/app.css`` is requested, the bundle -swaps the contents of that file with the contents of ``var/tailwind/tailwind.built.css``. +each CSS file into a ``var/tailwind/.built.css`` file. +Finally, when the contents of the CSS file is requested, the bundle swaps the +contents of that file with the contents of ``var/tailwind/.built.css``. + +E.g. : A request for ``assets/styles/app.css`` will be replaced by ``var/tailwind/app.built.css``. Nice! Deploying @@ -152,7 +154,7 @@ To see the full config from this bundle, run: $ php bin/console config:dump symfonycasts_tailwind The main option is ``input_css`` option, which defaults to ``assets/styles/app.css``. -This represents the "source" Tailwind file (the one that contains the ``@tailwind`` +This represents the "source" Tailwind files (the one that contains the ``@tailwind`` directives): .. code-block:: yaml @@ -161,6 +163,15 @@ directives): symfonycasts_tailwind: input_css: 'assets/styles/other.css' +It's possible to use multiple input files by providing an array: +.. code-block:: yaml + + # config/packages/symfonycasts_tailwind.yaml + symfonycasts_tailwind: + input_css: + - 'assets/styles/other.css' + - 'assets/styles/another.css' + Another option is the ``config_file`` option, which defaults to ``tailwind.config.js``. This represents the Tailwind configuration file: diff --git a/phpstan.neon.dist b/phpstan.neon.dist index d256b0e..1adc4ff 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -4,6 +4,6 @@ parameters: - src ignoreErrors: - - message: "#^Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeParentInterface\\:\\:scalarNode\\(\\)\\.$#" + message: "#^Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeParentInterface\\:\\:beforeNormalization\\(\\)\\.$#" count: 1 path: src/DependencyInjection/TailwindExtension.php diff --git a/src/AssetMapper/TailwindCssAssetCompiler.php b/src/AssetMapper/TailwindCssAssetCompiler.php index edd7264..aa0dce7 100644 --- a/src/AssetMapper/TailwindCssAssetCompiler.php +++ b/src/AssetMapper/TailwindCssAssetCompiler.php @@ -25,13 +25,16 @@ public function __construct(private TailwindBuilder $tailwindBuilder) public function supports(MappedAsset $asset): bool { - return realpath($asset->sourcePath) === realpath($this->tailwindBuilder->getInputCssPath()); + return \in_array( + realpath($asset->sourcePath), + $this->tailwindBuilder->getInputCssPaths(), + ); } public function compile(string $content, MappedAsset $asset, AssetMapperInterface $assetMapper): string { - $asset->addFileDependency($this->tailwindBuilder->getInternalOutputCssPath()); + $asset->addFileDependency($this->tailwindBuilder->getInternalOutputCssPath($asset->sourcePath)); - return $this->tailwindBuilder->getOutputCssContent(); + return $this->tailwindBuilder->getOutputCssContent($asset->sourcePath); } } diff --git a/src/Command/TailwindBuildCommand.php b/src/Command/TailwindBuildCommand.php index e8e505d..d86ee0b 100644 --- a/src/Command/TailwindBuildCommand.php +++ b/src/Command/TailwindBuildCommand.php @@ -11,6 +11,7 @@ use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -32,6 +33,7 @@ public function __construct( protected function configure(): void { $this + ->addArgument('input_css', InputArgument::OPTIONAL, 'The input CSS file to compile') ->addOption('watch', 'w', null, 'Watch for changes and rebuild automatically') ->addOption('poll', null, null, 'Use polling instead of filesystem events when watching') ->addOption('minify', 'm', InputOption::VALUE_NONE, 'Minify the output CSS') @@ -47,6 +49,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int watch: $input->getOption('watch'), poll: $input->getOption('poll'), minify: $input->getOption('minify'), + inputFile: $input->getArgument('input_css'), ); $process->wait(function ($type, $buffer) use ($io) { $io->write($buffer); diff --git a/src/Command/TailwindInitCommand.php b/src/Command/TailwindInitCommand.php index 7e0af32..353f468 100644 --- a/src/Command/TailwindInitCommand.php +++ b/src/Command/TailwindInitCommand.php @@ -92,7 +92,7 @@ private function createTailwindConfig(SymfonyStyle $io): bool private function addTailwindDirectives(SymfonyStyle $io): void { - $inputFile = $this->tailwindBuilder->getInputCssPath(); + $inputFile = $this->tailwindBuilder->getInputCssPaths()[0]; $contents = is_file($inputFile) ? file_get_contents($inputFile) : ''; if (str_contains($contents, '@tailwind base')) { $io->note(sprintf('Tailwind directives already exist in "%s"', $inputFile)); diff --git a/src/DependencyInjection/TailwindExtension.php b/src/DependencyInjection/TailwindExtension.php index 586b0f9..5bb2912 100644 --- a/src/DependencyInjection/TailwindExtension.php +++ b/src/DependencyInjection/TailwindExtension.php @@ -53,9 +53,11 @@ public function getConfigTreeBuilder(): TreeBuilder $rootNode ->children() - ->scalarNode('input_css') - ->info('Path to CSS file to process through Tailwind') - ->defaultValue('%kernel.project_dir%/assets/styles/app.css') + ->arrayNode('input_css') + ->prototype('scalar')->end() + ->beforeNormalization()->castToArray()->end() + ->info('Paths to CSS files to process through Tailwind') + ->defaultValue(['%kernel.project_dir%/assets/styles/app.css']) ->end() ->scalarNode('config_file') ->info('Path to the tailwind.config.js file') diff --git a/src/TailwindBuilder.php b/src/TailwindBuilder.php index 305c769..a8c2d4f 100644 --- a/src/TailwindBuilder.php +++ b/src/TailwindBuilder.php @@ -24,35 +24,39 @@ class TailwindBuilder { private ?SymfonyStyle $output = null; - private readonly string $inputPath; + private readonly array $inputPaths; public function __construct( private readonly string $projectRootDir, - string $inputPath, + array $inputPaths, private readonly string $tailwindVarDir, private CacheInterface $cache, private readonly ?string $binaryPath = null, private readonly ?string $binaryVersion = null, private readonly string $configPath = 'tailwind.config.js' ) { - if (is_file($inputPath)) { - $this->inputPath = $inputPath; - } else { - $this->inputPath = $projectRootDir.'/'.$inputPath; - - if (!is_file($this->inputPath)) { - throw new \InvalidArgumentException(sprintf('The input CSS file "%s" does not exist.', $inputPath)); - } + $paths = []; + foreach ($inputPaths as $inputPath) { + $paths[] = $this->validateInputFile($inputPath); } + + $this->inputPaths = $paths; } public function runBuild( bool $watch, bool $poll, bool $minify, + ?string $inputFile = null, ): Process { $binary = $this->createBinary(); - $arguments = ['-c', $this->configPath, '-i', $this->inputPath, '-o', $this->getInternalOutputCssPath()]; + + $inputPath = $this->validateInputFile($inputFile ?? $this->inputPaths[0]); + if (!\in_array($inputPath, $this->inputPaths)) { + throw new \InvalidArgumentException(sprintf('The input CSS file "%s" is not one of the configured input files.', $inputPath)); + } + + $arguments = ['-c', $this->configPath, '-i', $inputPath, '-o', $this->getInternalOutputCssPath($inputPath)]; if ($watch) { $arguments[] = '--watch'; if ($poll) { @@ -82,7 +86,7 @@ public function runBuild( return $process; } - public function runInit() + public function runInit(): Process { $binary = $this->createBinary(); $process = $binary->createProcess(['init']); @@ -102,14 +106,16 @@ public function setOutput(SymfonyStyle $output): void $this->output = $output; } - public function getInternalOutputCssPath(): string + public function getInternalOutputCssPath(string $inputPath): string { - return $this->tailwindVarDir.'/tailwind.built.css'; + $inputFileName = pathinfo($inputPath, \PATHINFO_FILENAME); + + return "{$this->tailwindVarDir}/{$inputFileName}.built.css"; } - public function getInputCssPath(): string + public function getInputCssPaths(): array { - return $this->inputPath; + return $this->inputPaths; } public function getConfigFilePath(): string @@ -117,13 +123,26 @@ public function getConfigFilePath(): string return $this->configPath; } - public function getOutputCssContent(): string + public function getOutputCssContent(string $inputFile): string { - if (!is_file($this->getInternalOutputCssPath())) { + if (!is_file($this->getInternalOutputCssPath($inputFile))) { throw new \RuntimeException('Built Tailwind CSS file does not exist: run "php bin/console tailwind:build" to generate it'); } - return file_get_contents($this->getInternalOutputCssPath()); + return file_get_contents($this->getInternalOutputCssPath($inputFile)); + } + + private function validateInputFile(string $inputPath): string + { + if (is_file($inputPath)) { + return realpath($inputPath); + } + + if (is_file($this->projectRootDir.'/'.$inputPath)) { + return realpath($this->projectRootDir.'/'.$inputPath); + } + + throw new \InvalidArgumentException(sprintf('The input CSS file "%s" does not exist.', $inputPath)); } private function createBinary(): TailwindBinary diff --git a/tests/AssetMapper/TailwindCssAssetCompilerTest.php b/tests/AssetMapper/TailwindCssAssetCompilerTest.php index 32f9f55..30db211 100644 --- a/tests/AssetMapper/TailwindCssAssetCompilerTest.php +++ b/tests/AssetMapper/TailwindCssAssetCompilerTest.php @@ -21,8 +21,8 @@ public function testCompile() { $builder = $this->createMock(TailwindBuilder::class); $builder->expects($this->any()) - ->method('getInputCssPath') - ->willReturn(__DIR__.'/../fixtures/assets/styles/app.css'); + ->method('getInputCssPaths') + ->willReturn([realpath(__DIR__.'/../fixtures/assets/styles/app.css')]); $builder->expects($this->once()) ->method('getInternalOutputCssPath'); $builder->expects($this->once()) diff --git a/tests/FunctionalTest.php b/tests/FunctionalTest.php index aa86b12..949d07c 100644 --- a/tests/FunctionalTest.php +++ b/tests/FunctionalTest.php @@ -24,7 +24,7 @@ protected function setUp(): void $fs->remove($tailwindVarDir); } $fs->mkdir($tailwindVarDir); - file_put_contents($tailwindVarDir.'/tailwind.built.css', <<wait(); $this->assertTrue($process->isSuccessful()); - $this->assertFileExists(__DIR__.'/fixtures/var/tailwind/tailwind.built.css'); + $this->assertFileExists(__DIR__.'/fixtures/var/tailwind/app.built.css'); - $outputFileContents = file_get_contents(__DIR__.'/fixtures/var/tailwind/tailwind.built.css'); + $outputFileContents = file_get_contents(__DIR__.'/fixtures/var/tailwind/app.built.css'); $this->assertStringContainsString("body {\n background-color: red;\n}", $outputFileContents, 'The output file should contain non-minified CSS.'); } @@ -60,7 +60,7 @@ public function testIntegrationWithMinify(): void { $builder = new TailwindBuilder( __DIR__.'/fixtures', - __DIR__.'/fixtures/assets/styles/app.css', + [__DIR__.'/fixtures/assets/styles/app.css'], __DIR__.'/fixtures/var/tailwind', new ArrayAdapter(), null, @@ -71,9 +71,30 @@ public function testIntegrationWithMinify(): void $process->wait(); $this->assertTrue($process->isSuccessful()); - $this->assertFileExists(__DIR__.'/fixtures/var/tailwind/tailwind.built.css'); + $this->assertFileExists(__DIR__.'/fixtures/var/tailwind/app.built.css'); - $outputFileContents = file_get_contents(__DIR__.'/fixtures/var/tailwind/tailwind.built.css'); + $outputFileContents = file_get_contents(__DIR__.'/fixtures/var/tailwind/app.built.css'); $this->assertStringContainsString('body{background-color:red}', $outputFileContents, 'The output file should contain minified CSS.'); } + + public function testBuildProvidedInputFile(): void + { + $builder = new TailwindBuilder( + __DIR__.'/fixtures', + [__DIR__.'/fixtures/assets/styles/app.css', __DIR__.'/fixtures/assets/styles/second.css'], + __DIR__.'/fixtures/var/tailwind', + new ArrayAdapter(), + null, + null, + __DIR__.'/fixtures/tailwind.config.js' + ); + $process = $builder->runBuild(watch: false, poll: false, minify: true, inputFile: 'assets/styles/second.css'); + $process->wait(); + + $this->assertTrue($process->isSuccessful()); + $this->assertFileExists(__DIR__.'/fixtures/var/tailwind/second.built.css'); + + $outputFileContents = file_get_contents(__DIR__.'/fixtures/var/tailwind/second.built.css'); + $this->assertStringContainsString('body{background-color:blue}', $outputFileContents, 'The output file should contain minified CSS.'); + } } diff --git a/tests/fixtures/TailwindTestKernel.php b/tests/fixtures/TailwindTestKernel.php index d2a0127..98ed999 100644 --- a/tests/fixtures/TailwindTestKernel.php +++ b/tests/fixtures/TailwindTestKernel.php @@ -51,7 +51,7 @@ protected function configureContainer(ContainerBuilder $container, LoaderInterfa ]); $container->loadFromExtension('symfonycasts_tailwind', [ - 'input_css' => __DIR__.'/assets/styles/app.css', + 'input_css' => [__DIR__.'/assets/styles/app.css'], ]); } diff --git a/tests/fixtures/assets/styles/second.css b/tests/fixtures/assets/styles/second.css new file mode 100644 index 0000000..e16ba4d --- /dev/null +++ b/tests/fixtures/assets/styles/second.css @@ -0,0 +1,7 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + background-color: blue; +}