Tailwind Assets Registry & Generator
This document explains how Tailwind assets are collected across Capell packages/providers and how the generator produces a single CSS directive file used by Tailwind.
Overview
Section titled “Overview”Capell aggregates Tailwind asset declarations from multiple sources so your Tailwind build has a unified set of inputs:
- Imports:
@import "..."statements for base styles. - Plugins:
@plugin "..."directives to enable Tailwind plugins. - Sources:
@source "..."globs pointing to Blade/HTML/JS files Tailwind should scan.
Why it’s needed:
- Packages and plugins may define their own Tailwind inputs.
- A central registry ensures de-duplication, stable ordering, and a single generated file consumed by Tailwind.
Key Components
Section titled “Key Components”Capell\Core\Support\Tailwind\TailwindAssetsRegistry: a small in-memory registry capturing imports, plugins, and sources with their origin. It exposes unique, sorted values and a diagnostic report.Capell\Frontend\Support\Tailwind\TailwindAssetsGenerator: aggregates inputs from config, installed packages, and service providers that implementRegistersTailwindAssets, then writesresources/css/capell/frontend.css.
Configuration (Frontend)
Section titled “Configuration (Frontend)”Configure defaults under packages/frontend/config/capell-frontend.php:
return [ 'tailwind' => [ 'imports' => [ // e.g. tailwind base imports 'tailwindcss/base', 'tailwindcss/components', ], 'plugins' => [ // e.g. '@tailwindcss/form-builder' '@tailwindcss/form-builder', ], 'sources' => [ // Globs relative to the Frontend package 'resources/views/**/*.blade.php', 'resources/js/**/*.ts', ],
// Optional: enable source glob validation and warnings during generation 'validate_sources' => false, // Output CSS file path; absolute or relative. If starting with 'resources/', // it resolves under Laravel's resource_path(). 'output_css' => 'resources/css/capell/frontend.css', ],];- By default, the generator writes to your app’s
resources/css/capell/frontend.css. - If omitted, it falls back to the Frontend package path under
packages/frontend/resources/css/generated/.
Import Path Resolution
Section titled “Import Path Resolution”- Node module imports (e.g.,
tippy.js/dist/tippy.css,@tailwindcss/form-builder) are kept as-is in the generated file. - Package-relative imports and sources (e.g.,
resources/css/<package>.css,resources/views/**/*.blade.php) are resolved against their package path and then converted to be relative to the generated file directory. This ensures consistent builds when your output path is under your app resources.
Output Path Modes
Section titled “Output Path Modes”- App mode: when
tailwind.output_csspoints underresources/, imports and sources from packages are resolved relative tovendor/{package}. - Package dev mode: when output is inside a package (e.g.,
packages/frontend/resources/...), imports and sources are resolved relative to the package path.
Example (App mode: resources/css/capell/frontend.css)
Section titled “Example (App mode: resources/css/capell/frontend.css)”@import 'tippy.js/dist/tippy.css';@plugin "@tailwindcss/typography";@source "../../../vendor/capell-app/admin/resources/views/**/*.blade.php";@source "../../../vendor/capell-app/frontend/resources/views/**/*.blade.php";Each Capell-approved package contributing styles or scanned views adds its own @import and @source lines automatically. The generated file ends up listing every installed package; installs with Blog, themes, and other add-ons will see additional lines for each. See Approved packages for the current package registry.
Example (Package dev mode: output within a package)
Section titled “Example (Package dev mode: output within a package)”Paths will be relative to the package’s resources/css/generated directory and may include traversals into sibling packages if working in a monorepo.
How Generation Works
Section titled “How Generation Works”- The generator resolves the output path from
tailwind.output_css. - It aggregates entries from config, installed packages, and providers implementing
RegistersTailwindAssets. - It renders
@import,@plugin, and@sourcelines and writes the file. - If
validate_sourcesis enabled, it checks each@sourceglob and logs warnings when no files match.
Programmatic Registration
Section titled “Programmatic Registration”Beyond static configuration, packages and service providers can register Tailwind assets programmatically via TailwindAssetsRegistry. This is useful when your assets depend on runtime conditions, installed packages, or dynamic configuration.
The registry is container-bound and accessed in your service provider’s boot() method:
namespace MyPackage;
use Capell\Core\Support\Tailwind\TailwindAssetsRegistry;use Illuminate\Support\ServiceProvider;
class MyPackageServiceProvider extends ServiceProvider{ public function boot(): void { $registry = app(TailwindAssetsRegistry::class);
$registry->registerSource( 'resources/views/**/*.blade.php', 'my-package' );
$registry->registerImports([ '@my-package/styles.css', 'some-npm-module/dist/styles.css', ], 'my-package');
if (config('my-package.enable-theme')) { $registry->registerThemeColors([ 'brand-primary' => '#0066cc', 'brand-secondary' => '#ff6600', ], 'my-package'); } }}Registry Methods
Section titled “Registry Methods”| Method | Signature | Behavior |
|---|---|---|
registerSource() | registerSource(string $source, ?string $origin = null): self | Register a single @source glob. Strings are trimmed; empty strings ignored. |
registerSources() | registerSources(array $sources, ?string $origin = null): self | Register multiple sources at once. Convenience wrapper. |
registerImport() | registerImport(string $import, ?string $origin = null): self | Register a single @import statement. Strings are trimmed; empty strings ignored. |
registerImports() | registerImports(array $imports, ?string $origin = null): self | Register multiple imports at once. Convenience wrapper. |
registerPlugin() | registerPlugin(string $plugin, ?string $origin = null): self | Register a single @plugin directive. Strings are trimmed; empty strings ignored. |
registerPlugins() | registerPlugins(array $plugins, ?string $origin = null): self | Register multiple plugins at once. Convenience wrapper. |
registerThemeColor() | registerThemeColor(string $name, string $value, ?string $origin = null): self | Register a theme color by name. Both name and value trimmed; empty strings ignored. Later registrations override earlier ones. |
registerThemeColors() | registerThemeColors(array $colors, ?string $origin = null): self | Register multiple theme colors at once. Keys are color names, values are colors. |
sources() | sources(): Collection<int, string> | Get unique, sorted sources as a collection of strings. |
imports() | imports(): Collection<int, string> | Get unique, sorted imports as a collection of strings. |
plugins() | plugins(): Collection<int, string> | Get unique, sorted plugins as a collection of strings. |
themeColors() | themeColors(): Collection<string, string> | Get theme colors as a collection keyed by color name, sorted by name. |
hasThemeColors() | hasThemeColors(): bool | Whether any theme colors are registered. |
toReport() | toReport(): array | Return a diagnostic report with all entries (including origin metadata) for debugging. |
All register* methods are fluent — they return $this for method chaining. The $origin parameter is optional and used for tracing registration sources in diagnostic reports.
De-duplication rules:
sources,imports, andpluginsare de-duplicated by string value; the first registration wins. Results are sorted lexicographically.themeColorsare keyed by color name; later registrations override earlier ones.
Using the Generated File
Section titled “Using the Generated File”Import the generated file from your main Tailwind input (e.g., resources/css/app.css or your package’s frontend CSS):
@import './capell/frontend.css';
@tailwind base;@tailwind components;@tailwind utilities;CLI Usage
Section titled “CLI Usage”php artisan capell:frontend-tailwind-assetsDiagnostics-only (no write):
php artisan capell:frontend-tailwind-assets --reportThis prints a JSON report with imports, plugins, and sources, each entry including value and origin.
CI Tips
Section titled “CI Tips”- Run the generator before your Tailwind build to keep
capell/frontend.cssfresh. - Consider disabling
validate_sourcesin CI if globs are large or vendor paths are absent.
Troubleshooting
Section titled “Troubleshooting”- Missing classes: ensure your
@sourceglob is present and correctly resolved relative to the output directory. - Validation warnings: confirm glob correctness and existence of matched files.
- Duplicates: entries are de-duplicated across all sources.
Package Author Guidance
Section titled “Package Author Guidance”- Use
tailwindImportsfor CSS import specifiers; node module imports are preserved. - Use
tailwindSourcesfor globs; relative paths will be resolved and relativized against the output directory. - If
tailwindSourcesis empty, a defaultresources/views/**/*.blade.phpglob is added for your package.
Conditional Vendor Assets
Section titled “Conditional Vendor Assets”Use VendorAssetConditionRegistry when a package build asset should only load for a matching frontend request. Register the condition during package boot, then pass the condition name to VendorAssetData::buildAsset().
use Capell\Core\Data\VendorAssetData;use Capell\Core\Facades\CapellCore;use Capell\Core\Support\Assets\VendorAssetConditionRegistry;
app(VendorAssetConditionRegistry::class)->register( 'vendor.theme.carousel', fn (): bool => $this->currentPageUsesCarousel(),);
CapellCore::registerVendorAsset( VendorAssetData::buildAsset( path: 'vendor/theme/carousel', file: 'resources/js/carousel.js', packageName: 'vendor/theme', condition: 'vendor.theme.carousel', ),);Unconditional assets keep loading normally. Conditional assets with no registered condition do not load.