/dev/logs/charaf.rezrazia Laravel enthusiast notes and thoughts
vite

Setting up Laravel Nova with Vite and Typescript

·by Charaf Rezrazi
Cover Image for Setting up Laravel Nova with Vite and Typescript

It comes a time when you might need to build your own Laravel Nova components, whether it is a custom field, a tool or a card. And if you are conveniently satisfied with Tailwindcss as I am, you’ll find that using it with Nova isn’t that straightforward.

Nova ships with compiled Tailwindcss styles built with a custom configuration, so if a directive was not used anywhere in Nova’s source components, for instance backdrop-blur-sm, you won't be able to use as it is not already available in the compiled styles.

Another fun thing you might explore, is hooking up Vite instead of Mix. As of today, Nova ships natively with Laravel Mix, and all the stubs it provides have Mix built in as a bundler tool. Vite has been growing lately, and was integrated natively into Laravel in a recent version.

Here we go !

Replacing Laravel Mix with Vite

To get started, our first step is to replace Laravel Mix with Vite.

💡 If you want a quickstart template, you may want to check out https://github.com/oneduo/laravel-nova-vite-typescript-skeleton

You can start by removing the following files:

1rm webpack.mix.js nova.mix.js

We won’t be removing NPM dependencies for the time being, you may do that if you want.

After that, you want to install Vite and its dependencies:

1npm i -D vite \
2@vitejs/plugin-vue \
3@inertiajs/inertia \
4@inertiajs/inertia-vue3 \
5typescript \
6ts-loader \
7vue \
8vuex

This command will install Vite, required dependencies for Inertia, Typescript and Vue to get us started.

Let’s now create a new Vite config:

1touch vite.config.ts

And we can provide this sample configuration:

1import vue from "@vitejs/plugin-vue"
2import { resolve } from "path"
3import { defineConfig } from "vite"
4 
5export default defineConfig({
6 plugins: [vue()],
7 
8 root: "resources",
9 
10 build: {
11 outDir: resolve(__dirname, "dist"),
12 emptyOutDir: true,
13 manifest: true,
14 target: "es2018",
15 minify: true,
16 lib: {
17 entry: resolve(__dirname, "resources/js/tool.ts"),
18 name: "tool",
19 formats: ["umd"],
20 fileName: "js/tool",
21 },
22 rollupOptions: {
23 input: resolve(__dirname, "resources/js/tool.ts"),
24 external: ["vue", "Nova", "LaravelNova"],
25 output: {
26 globals: {
27 vue: "Vue",
28 nova: "Nova",
29 "laravel-nova": "LaravelNova",
30 },
31 
32 assetFileNames: "css/tool.css",
33 },
34 },
35 },
36 
37 optimizeDeps: {
38 include: ["vue", "@inertiajs/inertia", "@inertiajs/inertia-vue3", "axios"],
39 },
40})

Let’s take a look into this configuration.

1// This provides Vite with the required Vue plugin
2plugins: [vue()],
1// Since we are in a custom Nova component, we need to set the root
2// Typically you'd have a directory structure similar to this :
3/*
4 app/
5 config/
6 database/
7 nova-components/
8 ├─ MyTool/
9 │ ├─ resources/
10 │ │ ├─ js/
11 │ │ │ ├─ components/
12 │ │ │ ├─ tool.ts
13 │ │ ├─ css/
14 │ │ │ ├─ tool.css
15 │ ├─ vite.config.ts
16 routes/
17*/
18root: "resources",
1build: {
2 // This sets the output dir to nova-components/MyTool/dist
3 outDir: resolve(__dirname, "dist"),
4 
5 // Clears the outDir on every build
6 emptyOutDir: true,
7
8 // Generates a manifest.json we can use later on
9 manifest: true,
10 
11 // Build target
12 target: "es2018",
13 
14 // Minify build
15 minify: true,
16 
17 // Sets Vite to library mode
18 // https://vitejs.dev/guide/build.html#library-mode
19 lib: {
20 entry: resolve(__dirname, "resources/js/tool.ts"),
21 name: "tool",
22 formats: ["umd"], // We only need UMD format
23 fileName: "js/tool", // Built file is going to be name js/tool.umd.cjs
24 },
25 rollupOptions: {
26 input: resolve(__dirname, "resources/js/tool.ts"),
27 
28 // Define external dependencies and globally available objects
29 external: ["vue", "Nova", "LaravelNova"],
30 output: {
31 globals: {
32 vue: "Vue",
33 nova: "Nova",
34 "laravel-nova": "LaravelNova",
35 },
36 
37 // Set the file name for compiled CSS
38 assetFileNames: "css/tool.css",
39 },
40 },
41},

And finally, you need to add a tsconfig.json file:

1{
2 "compilerOptions": {
3 "module": "commonjs",
4 "target": "es5",
5 "noImplicitAny": true,
6 "sourceMap": true,
7 "declaration": true,
8 "outDir": "dist"
9 },
10 "include": [
11 "resources/js"
12 ]
13}

Now, we are ready to add our Vite script to the package.json file:

1{
2 "name": "tool-template",
3 "private": true,
4 "type": "module",
5 "files": [
6 "dist"
7 ],
8 "main": "./dist/js/tool.umd.cjs",
9 "scripts": {
10 "dev": "vite build --watch",
11 "build": "vite build",
12 "prod": "npm run build",
13 "production": "npm run build",
14 "nova:install": "npm --prefix='../../vendor/laravel/nova' ci"
15 }
16}

Installing Tailwindcss

To get started with Tailwindcss, you need to run the following command

1npm i -D tailwindcss autoprefixer postcss

Add the follwoing in a postcss.config.cjs file:

1module.exports = {
2 plugins: [require("postcss-import"), require("tailwindcss")],
3}

And then, add a tailwind.config.cjs file:

1/** @type {import("tailwindcss").Config} */
2module.exports = {
3 darkMode: "class",
4 content: ["./resources/**/*{js,ts,vue,blade.php}"],
5 theme: {
6 extend: {},
7 },
8 plugins: [],
9 important: ".tool-template",
10}

Since Nova ships with a built Tailwindcss stylesheet, we need to build our own, without breaking the existing or introducing conflicts.

Which explains the need of having an important: ".tool-template" defined.

We can then add it to our top level Vue component. Typically, this can be the resources/js/pages/Tool.vue

1<script setup lang="ts">
2</script>
3 
4<template>
5 <div class="tool-template">
6 <Head title="Tool Template" />
7 
8 <Heading class="mb-6">Tool Template</Heading>
9 
10 <Card
11 class="flex flex-col items-center justify-center"
12 style="min-height: 300px"
13 >
14 <h1 class="dark:text-white text-4xl font-light mb-6">
15 Nova x Typescript x Vite
16 </h1>
17 
18 <p class="dark:text-white text-lg opacity-70">
19 You can edit this tool's component at:
20 <code
21 class="ml-1 border border-gray-100 dark:border-gray-900 text-sm font-mono text-white bg-black rounded px-2 py-1"
22 >
23 /nova-components/ToolTemplate/resources/js/pages/Tool.vue
24 </code>
25 </p>
26 </Card>
27 </div>
28</template>
29 
30 
31<style>
32/* Scoped Styles */
33</style>

Setting up the service provider

Since we’re using Vite, let’s take full advantage of it and use the generated manifest file.

To get started, add the following dependency to your package:

1composer require innocenzi/laravel-vite

Once we got it installed, we need to head over to the service provider of our component, for instance your tool service provider, and change the boot method to the following:

1<?php
2 
3declare(strict_types=1);
4 
5namespace Oneduo\ToolTemplate;
6 
7use Illuminate\Http\Request;
8use Innocenzi\Vite\Exceptions\NoSuchEntrypointException;
9use Innocenzi\Vite\Manifest;
10use Laravel\Nova\Menu\MenuSection;
11use Laravel\Nova\Nova;
12use Laravel\Nova\Tool;
13 
14class ToolTemplate extends Tool
15{
16 /**
17 * Perform any tasks that need to happen when the tool is booted.
18 *
19 * @throws NoSuchEntrypointException
20 */
21 public function boot(): void
22 {
23 $manifest = Manifest::read(__DIR__ . '/../dist/manifest.json');
24 
25 $script = $manifest->getEntry('js/tool.ts')->file;
26 $style = $manifest->getChunk('style.css')->file;
27 
28 Nova::script('tool-template', __DIR__ . '/../dist/' . $script);
29 Nova::style('tool-template', __DIR__ . '/../dist/' . $style);
30 }