TypeScript with Laravel and Vue.js
With pure Vue.js installation, you have an interactive installer that helps you automatically plug in the TypeScript support. With a frontend directly integrated into Laravel, you need to install it manually.
Adding TypeScript support to your Laravel project is simple and only takes minutes. Let’s review the process with a fresh Laravel Breeze installation with Vue 3.
TypeScript installation
First, install the TypeScript compiler into your project.
npm add -D typescript# oryarn add -D typescript
TypeScript configuration
The next step is to configure the compiler. TypeScript requires a configuration file tsconfig.json
in the root directory of your project.
The presence of a tsconfig.json file in a directory indicates that the directory is the root of a TypeScript project. The tsconfig.json file specifies the root files and the compiler options required to compile the project.
For many developers coming from Laravel, setting up the tsconfig.json
file turns out to be a massive blocker due to the overflow of available options.
In reality, you should only care about a few essential properties, such as paths to your files and the level of strictness. The rest is pre-configured well and only requires attention in some edge cases.
Vue 3 provides the official TypeScript configuration, which contains the basic options required for Vue 3 with Vite.
Install the package @vue/tsconfig
.
npm add -D @vue/tsconfig# oryarn add -D @vue/tsconfig
TL; DR. You can use a pre-configured Laravel-ready tsconfig.json, which enables pure JavaScript support and disables strict type-checking.
Create a file tsconfig.json
and make it reference the installed configuration file.
{"extends": "@vue/tsconfig/tsconfig.web.json",}
Next, list the paths to the files that will contain TypeScript code in the include
section of the config.
{"extends": "@vue/tsconfig/tsconfig.web.json","include": ["resources/**/*.ts","resources/**/*.d.ts","resources/**/*.vue"]}
If you’re just starting with TypeScript, it makes sense to start using types in new files and gradually add types to the existing components later. Your codebase does not have to reach 100% type coverage ever.
You can make TypeScript process pure .js
files and disable strict type checking.
{"compilerOptions": {"allowJs": true,"strict": false,}}
You may also want to create an alias referencing the root directory of your frontend in the paths
section so that you can access the root directory using the @/
shorthand.
{"compilerOptions": {"baseUrl": ".","paths": {"@/*": ["resources/js/*"]}}}
In order to get the support of Vite-specific methods and accessors, such as import.meta.glob
, you need to register vite/client
types in compilerOptions.types
of your tsconfig
.
{"compilerOptions": {"types": ["vite/client"],}}
From this point, you can already start writing typed code in .ts
files. The boilerplate configuration file already supports .js
files and pure JavaScript syntax, so you can keep the default entry point as is and start using TypeScript in new files and components only.
Switching to TypeScript
Initial Laravel installation comes with a boilerplate JavaScript entry point, which could be converted into TypeScript. All you need to do is rename .js
to .ts
and make slight changes.
First, change the main entry point file’s extension to .ts
:
resources/js/app.jsresources/js/app.ts
Update the path in your app.blade.php
:
@vite('resources/js/app.js')@vite('resources/js/app.ts')
Instruct Vite to use the renamed entry point in vite.config.js
.
import { defineConfig } from "vite"import laravel from "laravel-vite-plugin"export default defineConfig({plugins: [laravel({input: "resources/js/app.js",input: "resources/js/app.ts",refresh: true,}),],})
Now, you will need to make a few changes to the app.ts
file.
This is the content of the basic app.js
file shipped with Laravel Breeze:
import './bootstrap';import '../css/app.css';import { createApp, h } from 'vue';import { createInertiaApp } from '@inertiajs/vue3';import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';import { ZiggyVue } from '../../vendor/tightenco/ziggy/dist/vue.m';const appName = window.document.getElementsByTagName('title')[0]?.innerText || 'Laravel';createInertiaApp({title: (title) => `${title} - ${appName}`,resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`,import.meta.glob('./Pages/**/*.vue')),setup({ el, App, props, plugin }) {return createApp({ render: () => h(App, props) }).use(plugin).use(ZiggyVue, Ziggy).mount(el);},progress: {color: '#4B5563',},});
You may notice that your IDE throws a few errors. The issue is that TypeScript does allow you to use weak types (or no types at all) but doesn’t let you use incorrect types.
The first error you may encounter is the wrong return type of import.meta.glob
. The thing is that glob
is a generic method, and its return type could be defined explicitly. Import the type DefineComponent
and apply it to the method.
import type { DefineComponent } from "vue";createInertiaApp({resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`,import.meta.glob("./Pages/**/*.vue")import.meta.glob<DefineComponent>("./Pages/**/*.vue")),});
Another issue comes from Ziggy.js. First, Ziggy doesn’t provide TypeScript definitions.
The best solution. To get complete TypeScript support, it’s highly recommended to use the TypeScript-ready package Momentum Trail, which is built on top of Ziggy. The package supports all Ziggy’s methods and comes with IDE autocompletion and type-checking for route names and parameters.
You can omit this error by either applying a comment @ts-expect-error
on top of the import line or setting noImplicitAny
to false
in the compiler options.
// @ts-expect-errorimport { ZiggyVue } from "../../vendor/tightenco/ziggy/dist/vue.m";
or
{"compilerOptions": {"noImplicitAny": false,}}
You can use comments @ts-expect-error
or @ts-ignore
anywhere you feel like you don’t want to deal with insufficient or incorrect types.
Second, TypeScript can’t resolve Ziggy
, as it’s declared on the window
object, which doesn’t have a typed property called Ziggy
. Since it’s just a configuration object which you’re not going to refer to manually, you shouldn’t care much about its type.
As a temporary solution, you can instruct the compiler to consider window
a plain any
object and access Ziggy
from it.
createInertiaApp({setup({ el, App, props, plugin }) {createApp({ render: () => h(App, props) }).use(plugin).use(ZiggyVue, Ziggy).use(ZiggyVue, (window as any).Ziggy).mount(el);},});
The as
keyword is a Type Assertion in TypeScript, which tells the compiler to consider the object as another type than the type the compiler infers the object to be.
Vue components
You also may want to instruct the compiler and your IDE to interpret your components’ code as TypeScript. Append the lang="ts"
part to the component’s script part.
<script lang="ts" setup>interface Payload{name: stringamount: number}const submit = (payload: Payload) => {//}</script><template>...</template>
Starting now, Vite can parse and compile TypeScript code in your Vue components.
Linting code
Even if your IDE can help you with real-time syntax highlighting and autocompletion, there’s still room for mistakes and type errors. We use a CLI tool vue-tsc
to check them during the building process.
vue-tsc
is a Vue-specific wrapper around tsc
— a utility for command line type checking and type declaration generation.
npm add -D vue-tsc# oryarn add -D vue-tsc
To run the check manually, type the following command in the command line:
npx vue-tsc --noEmit
The --noEmit
flag instructs tsc
to not build output files and only perform the checks.
To perform the check automatically every time you build a project, modify the build
script in the package.json
file:
{"scripts": {"build": "vite build","build": "npm run type-check && vite build","type-check": "vue-tsc --noEmit"}}
That is it; you are all set! You can keep writing code the way you used to and start utilizing TypeScript features step-by-step.
VS Code Support
This section is only applicable to VS Code users.
Even though VS Code already has a built-in TypeScript engine, Volar (Vue.js extension) brings another TS service instance patched with Vue-specific support. To make Volar understand your plain .ts
files and get them Vue.js features support, you need to install the official extension TypeScript Vue Plugin.
While this basic setup will work, you will have two TypeScript engine instances running simultaneously, which may lead to performance issues in larger projects.
To improve performance and prevent possible conflicts between two services, Volar provides a feature called “Takeover Mode.”
To enable Takeover Mode, you need to disable VS Code’s built-in TS language service in your project’s workspace only by following these steps:
- In your project workspace, bring up the command palette with
Ctrl + Shift + P
(macOS:Cmd + Shift + P
). - Type built and select “Extensions: Show Built-in Extensions.”
- Type typescript in the extension search box (do not remove
@builtin
prefix). - Click the little gear icon of “TypeScript and JavaScript Language Features” and select “Disable (Workspace).”
- Reload the workspace. Takeover mode will be enabled when you open a Vue or TS file.
Package ecosystem
You might love to check the Inertia-ready package ecosystem Momentum to get comprehensive TypeScript support for essential Laravel features.
- Trail — Frontend package to use Laravel routes with Inertia
- Lock — Frontend package to use Laravel permissions with Inertia
- Paginator — Headless wrapper around Laravel Pagination
- Modal — Build dynamic modal dialogs in Inertia apps
- Layout — Persistent layouts for Vue 3 apps
- Preflight — Realtime backend-driven validation for Inertia apps
- Vite Plugin Watch — Vite plugin to run shell commands on file changes