Appearance
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/:
| File | What it is |
|---|---|
index.js | The Vite plugin, kitloomTheme(), and the helpers themeEntry(), themeRoot() and buildsWithVite(). |
page-types.js | The plugin that writes a TypeScript theme's resources/js/kitloom.d.ts. kitloomTheme() includes it. |
templates.js | The plugin that writes kitloom-templates.json. kitloomTheme() includes it. |
build-themes.js | A 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
| Export | Does |
|---|---|
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.jsonThe 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 buildRun 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 devWhile 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'snode_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):
| Key | Default | Meaning |
|---|---|---|
build_directory | build-{theme} | The build directory under public/; {theme} is replaced by the theme's name. |
entry | resources/app.js | The entry script, inside the theme. |
templates | resources/js/Templates | The 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.