Skip to the content
OrielDocs
0.1.0-alpha.10GitHub

Frontend

Asset bundling (Vite)

Vite bundles your pages' scripts and styles. While you work, it serves them and updates them in the browser as you edit them; for production, it builds them into public/build.

Introduction#

Oriel's Vite plugin, oriel() from @orieljs/vite, connects Vite and your app, as Laravel's laravel-vite-plugin does: your views load the right files with vite(), from the dev server while it runs and from the build otherwise.

Every starter kit except the API kit has Vite set up. The API kit has no pages, so it has no vite.config.ts and no dev or build script.

Installation and setup#

Installing Node#

Vite runs on Node 22 or later, or Bun, as your app does.

Installing Vite and the Oriel plugin#

Every new app lists vite and @orieljs/vite in package.json, the API kit's too: its tests use them. To give an API app pages, add a vite.config.ts as below, and "dev": "vite" and "build": "vite build" to its scripts.

Configuring Vite#

vite.config.ts, in your app's root, lists your entry points, the files your pages load:

vite.config.ts
import { oriel } from '@orieljs/vite'import { defineConfig } from 'vite'export default defineConfig({  plugins: [oriel({ input: ['resources/css/app.css', 'resources/js/app.ts'] })],})

An Inertia app names its framework, which loads that framework's Vite plugin (@vitejs/plugin-react for React), and its server entry for SSR. The React kit's:

vite.config.ts
import { oriel } from '@orieljs/vite'import tailwindcss from '@tailwindcss/vite'import { defineConfig } from 'vite'export default defineConfig({  plugins: [    tailwindcss(),    oriel({ framework: 'react', input: ['resources/css/app.css', 'resources/js/app.tsx'], ssr: 'resources/js/ssr.tsx' }),  ],})

framework is 'react', 'vue', 'svelte', 'solid' or 'angular'. The Live kit names none: its components render on the server.

OptionWhat it is
inputThe entry points
frameworkThe Inertia framework, whose Vite plugin it loads
frameworkOptionsOptions for that plugin
ssrThe SSR entry, found by itself (resources/js/ssr.tsx) unless you name it; false turns SSR off
inertiaOptions for @inertiajs/vite, or false to leave it out
refreshThe server files that reload the page when they change: true for the defaults, false, or your own globs
typesThe generated page and route types: true, false, or { pages, routes, command }
publicDirectory, buildDirectoryWhere the build goes: public and build
hotFileThe file that tells your app the dev server is running: public/hot
ssrOutputDirectoryWhere the SSR build goes: bootstrap/ssr

Loading your scripts and styles#

vite() in a view writes the tags for your entry points:

resources/views/app.tsx
import { vite } from '@orieljs/framework'export default function App() {  return (    <html lang="en">      <head>        <meta charset="utf-8" />        {vite(['resources/css/app.css', 'resources/js/app.ts'])}      </head>      <body>...</body>    </html>  )}

While the dev server runs, the tags point at it. Otherwise they point at the built files, with the scripts and styles they import preloaded. Each script and stylesheet tag carries data-navigate-track, so Live's o:navigate loads a page in full when it names a new build's files; useScriptTagAttributes({ 'data-navigate-track': false }) leaves it out. A file that isn't in the build throws a ViteError: "Unable to locate file in Vite manifest: resources/js/app.ts.".

A React app also needs viteReactRefresh() before vite(), so that React's fast refresh works while you work:

const head = (  <head>    {viteReactRefresh()}    {vite(['resources/css/app.css', 'resources/js/app.tsx'])}  </head>)

Running Vite#

npm run dev starts the dev server, and npm run build builds your assets for production:

Terminal
npm run devnpm run build

Run your app's server next to the dev server, in a second terminal: node oriel serve --watch. Running your application explains how the two work together.

The kits with SSR build twice, vite build && vite build --ssr: the browser's bundle into public/build, and the server's into bootstrap/ssr.

While the dev server runs, it writes its URL to public/hot, and deletes the file when it stops. Don't deploy that file: while it exists, your pages load their assets from a dev server that isn't there.

Working with JavaScript#

Aliases#

@ is resources/js, so you can import @/components/Button from anywhere in your scripts.

Inertia#

The plugin loads @inertiajs/vite, which finds your pages in resources/js/pages and builds the SSR bundle. The Inertia page covers the rest.

Generated types#

In an Inertia app, the plugin writes your pages' prop types to resources/js/oriel/pages.d.ts and runs oriel route:types, which writes the route table behind route() in your pages. It runs both when Vite starts, and again while npm run dev runs: the page types when a file in app or routes changes, the route types when one in routes or app/Http/Controllers does. types: false turns it off, and types: { routes: false } keeps the page types only.

Environment variables#

Vite gives your scripts the variables in .env that start with VITE_, and only those:

Terminal
VITE_SENTRY_DSN_PUBLIC=https://example.com
const dsn = import.meta.env.VITE_SENTRY_DSN_PUBLIC

Working with stylesheets#

Give your CSS file to input, or import it from a script, and Vite processes it: the kits' resources/css/app.css is built with Tailwind CSS, through @tailwindcss/vite. Vite's own CSS features, such as PostCSS and CSS modules, work as its documentation describes.

Working with views and routes#

Processing static assets with Vite#

An image your script imports is in the build, with a hashed name. vite().asset() gives its URL in a view; to have images that no script imports built too, import them as a glob in your entry point:

resources/js/app.ts
import.meta.glob(['../images/**'])
const logo = <img src={vite().asset('resources/images/logo.png')} alt="Logo" />

vite().content('resources/css/mail.css') gives a built file's contents, such as styles to put in a mail.

Refreshing on save#

When a view, a route file, a translation or a Live component changes, Vite reloads the page in the browser:

FolderReloads
resources/views/**Views
routes/**Route files
app/Live/**Live components
lang/**, resources/lang/**Translations

refresh takes your own list instead: refresh: ['resources/views/**', 'app/Http/Controllers/**'].

Custom base URLs#

To serve your built assets from another host, such as a CDN, set ASSET_URL in .env. The build reads it, so the URLs inside your scripts and styles start with it:

Terminal
ASSET_URL=https://cdn.example.com

The tags vite() writes, and asset(), use app.asset_url. A new app's config/app.ts doesn't have it, so add it:

config/app.ts
import { env } from '@orieljs/framework'export default {  // ...  asset_url: env('ASSET_URL'),}

Disabling Vite in tests#

A test that renders a page needs the build's manifest, unless Vite is off. withoutVite() makes vite() and viteReactRefresh() write nothing for the rest of the test. The TestCase of the starter kits with pages calls it for every test:

tests/TestCase.ts
import { TestCase as BaseTestCase } from '@orieljs/framework/testing'import type { Application } from '@orieljs/framework'import { afterEach, beforeEach } from 'vitest'import { createApplication } from '../bootstrap/app'export class TestCase extends BaseTestCase {  static override hooks = { beforeEach, afterEach }  override createApplication(): Promise<Application> {    return createApplication()  }  override async setUp(): Promise<void> {    await super.setUp()    this.withoutVite()  }}

Decorators in tests#

Vitest transforms your TypeScript with Oxc, which leaves TC39 decorators, such as Live's @On, in place, and Node can't run them yet. The decorators() plugin lowers them; a new app's vitest.config.ts has it:

vitest.config.ts
import { decorators } from '@orieljs/vite'import { defineConfig } from 'vitest/config'export default defineConfig({  plugins: [decorators()],})

Server-side rendering (SSR)#

ssr names the server entry of an Inertia app, and vite build --ssr builds it into bootstrap/ssr. While the dev server runs, it renders the pages on the server itself. The Angular kit has no SSR. Inertia SSR covers running the SSR server.

Script and style tag attributes#

Content Security Policy (CSP) nonce#

vite().useCspNonce() makes a nonce for the request, and the tags vite() writes carry it. Call it in a middleware, and send the same nonce in your policy:

app/Http/Middleware/AddContentSecurityPolicyHeaders.ts
import { vite } from '@orieljs/framework'import type { HttpRequest, Middleware, Next } from '@orieljs/framework/contracts'export class AddContentSecurityPolicyHeaders implements Middleware {  async handle(request: HttpRequest, next: Next): Promise<Response> {    const nonce = vite().useCspNonce()    const response = await next(request)    response.headers.set('Content-Security-Policy', `script-src 'nonce-${nonce}'`)    return response  }}

vite().cspNonce() gives the nonce, for your own inline scripts: <script nonce={vite().cspNonce()}>. The starter kits' appearance script and welcome page use it. Each request has its own vite(), so the nonce stays in its request.

Subresource Integrity (SRI)#

When the manifest gives each file an integrity hash, such as one a plugin like vite-plugin-manifest-sri adds, the tags carry it. vite().useIntegrityKey('hash') reads another key, and useIntegrityKey(false) leaves it out.

Arbitrary attributes#

useScriptTagAttributes() and useStyleTagAttributes() add attributes to the tags, as an object or a function of the file:

vite().useScriptTagAttributes({ 'data-turbo-track': 'reload', async: true })vite().useStyleTagAttributes((src) => (src === 'resources/css/app.css' ? { 'data-theme': 'main' } : {}))

A value of true writes a bare attribute, and false leaves the attribute out. usePreloadTagAttributes(false) drops the preload tags.

Advanced customization#

vite() also has useHotFile(), useBuildDirectory(), useManifestFilename() and createAssetPathsUsing(), for a build that isn't in the usual place. Set them in a middleware, as the nonce is, or pass the build folder to vite(): vite(['resources/js/admin.ts'], 'admin-build').

Dev server CORS#

Your pages, on your app's URL, load scripts from the dev server, on another port. The dev server lets your APP_URL, localhost and any .test host load them. For another origin, set server.cors in vite.config.ts, as Vite's documentation describes.

Privacy policy ·

esc