Skip to content

Building Assets ​

Introduction ​

A Vue theme is built with Vite; a Blade theme has nothing to build. Every Vue theme gets a build of its own, public/build-{Theme}, with its own manifest — that is what lets an administrator switch between built themes without a rebuild.

kitloom/site-inertia brings what the build needs, in vendor/kitloom/site-inertia/vite/:

FileWhat it is
index.jsThe Vite plugin, kitloomTheme(), and the helpers themeEntry(), themeRoot() and buildsWithVite().
page-types.jsThe plugin that writes a TypeScript theme's resources/js/kitloom.d.ts. kitloomTheme() includes it.
templates.jsThe plugin that writes kitloom-templates.json. kitloomTheme() includes it.
build-themes.jsA script that builds every installed Vue theme, one after another.

The Vite config ​

js
import { defineConfig, loadEnv } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue';
import react from '@vitejs/plugin-react';
import kitloomTheme, { buildsWithVite, themeEntry } from './vendor/kitloom/site-inertia/vite/index.js';

export default defineConfig(({ mode }) => {
    // APP_THEME as the site reads it, from .env; the shell's wins.
    const theme = loadEnv(mode, process.cwd(), '').APP_THEME ?? '';

    // No theme, or a Blade one: nothing to build or serve.
    if (!buildsWithVite(theme)) {
        console.info('Vite has nothing to build.');
        process.exit(0);
    }

    return {
        plugins: [
            laravel({
                input: [themeEntry(theme)],
                buildDirectory: `build-${theme}`,
                refresh: true,
            }),
            ...kitloomTheme({ theme }),
            vue({
                template: {
                    transformAssetUrls: {
                        base: null,
                        includeAbsolute: false,
                    },
                },
            }),
            // A React theme's components; a Vue theme has none.
            react({ include: /\.[jt]sx$/ }),
        ],
        resolve: {
            preserveSymlinks: true,
        },
        server: {
            watch: {
                ignored: ['**/storage/framework/views/**'],
            },
        },
    };
});

One run of Vite builds one theme: the one APP_THEME names — in .env, read with Vite's loadEnv() as the site reads it, unless the shell that runs Vite sets its own (build-themes.js does, once per theme). With no theme, or a Blade theme, there is nothing to build: Vite says so and stops without an error, so composer run dev keeps the server, the queue and the logs going. A theme that is not installed is an error.

What the plugin does ​

ExportDoes
themeRoot(theme)Finds the theme's directory, relative to the application root: themes/{theme} when that folder exists, otherwise the package whose composer.json declares the theme in extra.laravel.themes (read from vendor/composer/installed.json). Throws when neither has it.
buildsWithVite(theme)Whether there is a build to make: a Vue or React theme, with resources/js/Templates. False for no theme and for a Blade theme; throws, as themeRoot() does, for a theme that is not installed.
themeEntry(theme, entry = 'resources/app.js')The theme's entry script for laravel-vite-plugin's input: vendor/kitloom/portfolio-theme/resources/app.js. A theme without app.js gets app.ts, app.tsx or app.jsx, the first it has.
kitloomTheme({ theme }) (default)Three plugins, so it is spread into the list. The first makes @ the theme's resources/js — import SiteLayout from '@/Layouts/SiteLayout.vue'. The second, kitloom-page-types, writes resources/js/kitloom.d.ts when the build starts, for a theme written in TypeScript. The third, kitloom-templates, on vite build only, writes kitloom-templates.json.

kitloom-templates.json ​

The build writes kitloom-templates.json next to the manifest: the names of the theme's templates, from resources/js/Templates — single-book.vue is single-book, templates/full-width.vue is templates/full-width.

text
public/build-Portfolio/
├── assets/
├── manifest.json
└── kitloom-templates.json

The server reads it twice:

  • to pick the first template of the hierarchy the build has — a template added after the last build is not used until the next one, so the browser is never sent a component it does not have;
  • to tell whether the theme is built — a Vue theme without the file is Not built yet on the Themes screen and cannot be activated.

When the active Vue theme renders a page, the site points @vite at that theme's build directory and gives the root view $themeEntry, so @vite([$themeEntry]) loads the active theme's build.

Building every theme ​

json
{
    "scripts": {
        "dev": "vite",
        "build": "vite build",
        "build:themes": "node vendor/kitloom/site-inertia/vite/build-themes.js"
    }
}

build-themes.js collects the themes the way themeRoot() finds them — folders under themes/ and packages that declare extra.laravel.themes — and runs npx vite build with APP_THEME set to each one that has resources/js/Templates. Blade themes are skipped. It takes no arguments; one theme is built with:

bash
APP_THEME=Studio npm run build

Run npm run build:themes on deploy. Keep the builds out of Git: /public/build-* in .gitignore.

The dev server ​

bash
APP_THEME=Studio npm run dev

While the dev server runs (public/hot exists), every Vue theme counts as built, and templates are looked up as files in resources/js/Templates, so a new template is used at once. Run the dev server for the active theme: the @ alias points at the one theme the server was started for.

Images and fonts ​

What the theme's CSS and JavaScript import — url('../fonts/inter.woff2'), import logo from '@/images/logo.svg' — goes through Vite into the theme's build, public/build-{Theme}/assets, with hashed names.

transformAssetUrls in the Vue plugin leaves absolute URLs in templates alone: <img src="/images/logo.svg"> stays as it is and is served from public/. Relative ones (./logo.svg) are imported by Vite as above.

Symlinked packages ​

When theme packages come from a Composer path repository, vendor/{vendor}/{theme} is a symlink to a directory outside the application. resolve.preserveSymlinks: true keeps Vite on the vendor/ path:

  • the manifest's keys are vendor/… paths, the same entry the server gives @vite;
  • the theme's imports (vue, @inertiajs/vue3, axios) are resolved from the application's node_modules, not from the package's real directory.

A theme installed from a regular Composer repository is a plain directory, and the option changes nothing.

Changing the paths ​

The paths the server expects are in config/kitloom-site-inertia.php (php artisan vendor:publish --tag=kitloom-site-inertia-config):

KeyDefaultMeaning
build_directorybuild-{theme}The build directory under public/; {theme} is replaced by the theme's name.
entryresources/app.jsThe entry script, inside the theme.
templatesresources/js/TemplatesThe templates directory, inside the theme.

The Vite config has to agree: buildDirectory for the first, themeEntry(theme, entry) for the second. The plugin always reads templates from resources/js/Templates.

Blade themes ​

A Blade theme is drawn on the server and has no build. A theme that wants compiled CSS builds it inside its own package and ships the result: kitloom/marketplace-theme compiles its Tailwind CSS with npm run build in the package into resources/dist/theme.css, commits it, and inlines it into its layout — the site runs no Node step for it.

kitloom — modules and themes for Laravel.
Pricing · Services · License · Refunds · Privacy · Community · [email protected]