Skip to content

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.

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.
  • 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 implement RegistersTailwindAssets, then writes resources/css/capell/frontend.css.

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/.
  • 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.
  • App mode: when tailwind.output_css points under resources/, imports and sources from packages are resolved relative to vendor/{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.

  1. The generator resolves the output path from tailwind.output_css.
  2. It aggregates entries from config, installed packages, and providers implementing RegistersTailwindAssets.
  3. It renders @import, @plugin, and @source lines and writes the file.
  4. If validate_sources is enabled, it checks each @source glob and logs warnings when no files match.

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');
}
}
}
MethodSignatureBehavior
registerSource()registerSource(string $source, ?string $origin = null): selfRegister a single @source glob. Strings are trimmed; empty strings ignored.
registerSources()registerSources(array $sources, ?string $origin = null): selfRegister multiple sources at once. Convenience wrapper.
registerImport()registerImport(string $import, ?string $origin = null): selfRegister a single @import statement. Strings are trimmed; empty strings ignored.
registerImports()registerImports(array $imports, ?string $origin = null): selfRegister multiple imports at once. Convenience wrapper.
registerPlugin()registerPlugin(string $plugin, ?string $origin = null): selfRegister a single @plugin directive. Strings are trimmed; empty strings ignored.
registerPlugins()registerPlugins(array $plugins, ?string $origin = null): selfRegister multiple plugins at once. Convenience wrapper.
registerThemeColor()registerThemeColor(string $name, string $value, ?string $origin = null): selfRegister 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): selfRegister 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(): boolWhether any theme colors are registered.
toReport()toReport(): arrayReturn 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, and plugins are de-duplicated by string value; the first registration wins. Results are sorted lexicographically.
  • themeColors are keyed by color name; later registrations override earlier ones.

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;
Terminal window
php artisan capell:frontend-tailwind-assets

Diagnostics-only (no write):

Terminal window
php artisan capell:frontend-tailwind-assets --report

This prints a JSON report with imports, plugins, and sources, each entry including value and origin.

  • Run the generator before your Tailwind build to keep capell/frontend.css fresh.
  • Consider disabling validate_sources in CI if globs are large or vendor paths are absent.
  • Missing classes: ensure your @source glob 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.
  • Use tailwindImports for CSS import specifiers; node module imports are preserved.
  • Use tailwindSources for globs; relative paths will be resolved and relativized against the output directory.
  • If tailwindSources is empty, a default resources/views/**/*.blade.php glob is added for your package.

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.