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

Allow working with multiple entrypoints #58

Merged
merged 11 commits into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from 7 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: 4 additions & 3 deletions doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -151,15 +151,16 @@ 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``
The main option is ``input`` option, which defaults to ``assets/styles/app.css``.
This represents the "source" Tailwind files (the one that contains the ``@tailwind``
directives):

.. code-block:: yaml

# config/packages/symfonycasts_tailwind.yaml
symfonycasts_tailwind:
input_css: 'assets/styles/other.css'
input:
bocharsky-bw marked this conversation as resolved.
Show resolved Hide resolved
- 'assets/styles/other.css'

Another option is the ``config_file`` option, which defaults to ``tailwind.config.js``.
This represents the Tailwind configuration file:
Expand Down
2 changes: 1 addition & 1 deletion phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -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
9 changes: 6 additions & 3 deletions src/AssetMapper/TailwindCssAssetCompiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
bocharsky-bw marked this conversation as resolved.
Show resolved Hide resolved
);
}

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);
}
}
3 changes: 3 additions & 0 deletions src/Command/TailwindBuildCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,6 +33,7 @@ public function __construct(
protected function configure(): void
{
$this
->addArgument('input', InputArgument::OPTIONAL, 'The input CSS file to compile')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we really care about specifying the input file on running the command? Why can't we just build both files (I mean, all the input CSS files listed in the input_css option)? It will simplify things, no need to remember to pass the argument. Because as I understand, if you miss this arg (which is optional) - only the first entry on the input_css will be built. I'm afraid it will lead to WTF moments. IMO we should build all input CSS files listed by default, though I'm not against of keeping that optional arg in case you want to build a specific input CSS file only.

And how about to be consistent and call this input_css to match the config option? Does it make sense to you?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is possible to build both files, but since the Tailwind binary doesn't natively support this, we will need a workaround:

  • In default mode (without the --watch flag), it would run the process sequentially for each input file (or possibly in parallel if feasible).
  • In watch mode (with the --watch flag), we would need to spawn as many processes as there are input files. I'm not sure if this will be efficient in terms of memory usage. I'll look into potential improvements.

I also think we can keep input_css as the argument name. I initially used input because in the first implementation, I wanted to provide it as a flag. I'll rename it back to input_css.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, then the current implementation should be good IMO to keep things simple. But if anyone have any ideas how to improve it even further - feel free to share

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, thanks for your feedback.
I'll maybe try to improve this in another PR.

->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')
Expand All @@ -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'),
);
$process->wait(function ($type, $buffer) use ($io) {
$io->write($buffer);
Expand Down
2 changes: 1 addition & 1 deletion src/Command/TailwindInitCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
8 changes: 5 additions & 3 deletions src/DependencyInjection/TailwindExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()
bocharsky-bw marked this conversation as resolved.
Show resolved Hide resolved
->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')
Expand Down
57 changes: 38 additions & 19 deletions src/TailwindBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -82,7 +86,7 @@ public function runBuild(
return $process;
}

public function runInit()
public function runInit(): Process
{
$binary = $this->createBinary();
$process = $binary->createProcess(['init']);
Expand All @@ -102,28 +106,43 @@ 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
{
return $this->configPath;
}

public function getOutputCssContent(): string
public function getOutputCssContent(?string $inputFile = null): string
bocharsky-bw marked this conversation as resolved.
Show resolved Hide resolved
{
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
Expand Down
4 changes: 2 additions & 2 deletions tests/AssetMapper/TailwindCssAssetCompilerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
6 changes: 3 additions & 3 deletions tests/FunctionalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ protected function setUp(): void
$fs->remove($tailwindVarDir);
}
$fs->mkdir($tailwindVarDir);
file_put_contents($tailwindVarDir.'/tailwind.built.css', <<<EOF
file_put_contents($tailwindVarDir.'/app.built.css', <<<EOF
body {
padding: 17px;
background-image: url('../images/penguin.png');
Expand All @@ -35,8 +35,8 @@ protected function setUp(): void

protected function tearDown(): void
{
if (is_file(__DIR__.'/fixtures/var/tailwind/tailwind.built.css')) {
unlink(__DIR__.'/fixtures/var/tailwind/tailwind.built.css');
if (is_file(__DIR__.'/fixtures/var/tailwind/app.built.css')) {
unlink(__DIR__.'/fixtures/var/tailwind/app.built.css');
}
}

Expand Down
33 changes: 27 additions & 6 deletions tests/TailwindBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public function testIntegrationWithDefaultOptions(): 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,
Expand All @@ -50,17 +50,17 @@ public function testIntegrationWithDefaultOptions(): 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 {\n background-color: red;\n}", $outputFileContents, 'The output file should contain non-minified CSS.');
}

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,
Expand All @@ -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.');
}
}
2 changes: 1 addition & 1 deletion tests/fixtures/TailwindTestKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
]);
}

Expand Down
7 changes: 7 additions & 0 deletions tests/fixtures/assets/styles/second.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

body {
background-color: blue;
}
Loading