Quick Start Installation
This tutorial assumes that you already have a Laravel 10 (or later) project up and running with PHP 8.2, and that you use the vite bundler. These instructions are based on the examples provided in the laravext repository.
You can also check the starter kits available at the laravext repository, in the starter-kits directory.
Composer
First, install the composer package:
composer require arthurydalgo/laravext
you can also publish the config file to make changes such as default root view, nexus/strands directory, server side rendering, etc:
php artisan vendor:publish --tag=laravext-config
By default, the root_view
is set as sections.app
, which means you should either have that view file, or change the config to fit your needs.
The next step can be done at the end of your ./routes/web.php
file, or in a separated file, such as a ./routes/laravext.php
, for example, and then you can require it at the end of your ./routes/web.php
file. This is recommended, and is explained a little further in the Tools/Routing/Route Priority section of this documentation.
// ./routes/web.php
// Any other routes you might have
require __DIR__.'/laravext.php';
// ./routes/laravext.php
use Illuminate\Support\Facades\Route;
Route::laravext();
This technically optional, as there're other ways to generate your routes in a more granular way (which you can check at Tools/Routing), but it's entirely up to you on how you want to use it.
NPM
Install the npm modules:
Note: the additional instructions to use Typescript are at the end of this sectionnpm install @laravext/react
# Additionally, you should have the following package installed, if you haven't already
npm install @vitejs/plugin-react laravel-vite-plugin
# If you're planning to use typescript, you should also install the following packages
npm install --save-dev typescript @types/react @types/react-dom
or
npm install @laravext/vue3
# Additionally, you should have the following package installed, if you haven't already
npm install @vitejs/plugin-vue laravel-vite-plugin
# If you're planning to use typescript, you should also install the following packages
npm install --save-dev typescript vue-tsc
This example also assumes that you have a bootstrap.(js|ts)
at ./resources/js
and an app.css
in you ./resources/css
directory. You might or not have any need for those.
Now, you'll need to create an app.(js|jsx|ts|tsx)
in your ./resources/js
directory. The example below makes use of some npm packages such as i18n, moment, etc. You can remove them and the setup methods if you're not going to use them.
You'll then need to declare a createLaravextApp
function, and pass an object with (at least) the following properties (assuming you'll make use of the @nexus
and @strand
blade directives):
nexusResolver
: A function that resolves the nexus components. You can use theresolveComponent
function from the@laravext/react/tools
or@laravext/vue3/tools
package to do so.strandsResolver
: A function that resolves the strand components. You can use theresolveComponent
function mentioned above to do so.
There're some optional attributes included in the examples below that either commented or have comments explaining what they do. You can use them to set up global variables, internationalization, cookies, etc.
- React
- Vue
app.jsx
:
import './bootstrap';
import '../css/app.css';
import i18n from "i18next";
import Cookies from "js-cookie";
import pt from './../../lang/pt.json'
import mdxeditor from './../../lang/pt/mdxeditor.json'
import { initReactI18next } from "react-i18next";
import { createLaravextApp } from "@laravext/react"
import { resolveComponent } from "@laravext/react/tools"
import moment from 'moment/min/moment-with-locales';
document.addEventListener('DOMContentLoaded', function () {
createLaravextApp({
nexusResolver: (name) => resolveComponent(`./nexus/${name}`, import.meta.glob('./nexus/**/*')),
strandsResolver: (name) => resolveComponent(`./strands/${name}.jsx`, import.meta.glob('./strands/**/*.jsx')),
// The beforeSetup function is executed once, before any of the setups.
// You can use this to set something up, such as internationalization.
beforeSetup: ({ laravext }) => {
const user = laravext.page_data.shared_props?.auth?.user;
// This is just for example purposes, using i18n/moment is not a requirement
i18n
.use(initReactI18next)
.init({
resources: {
pt: {
translation: { ...pt, ...mdxeditor }
}
},
fallbackLng: "en",
interpolation: {
escapeValue: false
}
});
let locale = user?.locale ?? Cookies.get('locale') ?? 'en';
i18n.changeLanguage(locale);
moment.locale(locale);
},
// Like Inertia, there's a wrapper for the https://ricostacruz.com/nprogress library.
// You don't have to declare this, while using the createLaravextApp, but so
// you know, these are the default values:
progress: {
delay: 0, // How many miliseconds until the loading bar appears
color: '#29d', // The color of said bar
includeCSS: true, // Wether or not to use NProgress' default styling
showSpinner: false, // Wether or not to show the spinner
},
// or, if you don't want it at all:
// progress: false,
// This setup is applied to all components, including nexus and strands
// setup: ({ component, laravext }) => {
// return <AnyComponentOrProvider>
// {component}
// </AnyComponentOrProvider>
// },
/// The setupNexus function is applied only to the nexus component, after the 'setup'
// function, unless reverseSetupOrder is true
// setupNexus: ({ nexus, laravext }) => {
// return <AnyComponentOrProvider>{nexus}</AnyComponentOrProvider>;
// },
// The setupStrand function is applied only to the strand components, after the 'setup' function,
// unless reverseSetupOrder is true.
// The 'strandData' parameter is the data passed to the strand component from the blade
// where it was located, if applicable.
// setupStrand: ({strand, laravext, strandData}) => {
// return <AnyComponentOrProvider>{strand}</AnyComponentOrProvider>
// },
// If you want to reverse the order of the setup functions, set this to true
// reverseSetupOrder: true,
// If for some reason you to change the order of the file conventions, you can do so here.
// It'll be applied to the nexus components, from a first to last basis, encapsulating
// the page component. Check the File Conventions section for more details.
// conventions: [
// 'error',
// 'layout',
// 'middleware',
// ],
// If for some reason you must disable the pushState of the laravext client router, you can do
// so here. Be aware that this will disable the back navigation, and you're user will endup
// going back to the previous non-laravext page.
// disablePushedStateData: () => {
// // Your logic here, such as detecting browsers, etc.
// let logicResult = true
// return logicResult
// },
// By default, the popstate event registered by laravext will ignore the event if the state is null.
// If you want to change this behavior, you can do so here.
// ignorePopStateEvent: (event) => {
// let logicResult = event.state === null;
// return logicResult;
// },
})
}, false);
app.js
:
import './bootstrap';
import '../css/app.css';
import { createLaravextApp } from "@laravext/vue3"
import { createI18n } from 'vue-i18n'
import pt from './../../lang/pt.json'
import { plugin as fkPluging, defaultConfig as fkDefaultConfig } from '@formkit/vue'
import VueSweetalert2 from 'vue-sweetalert2';
import fkConfig from './../../formkit.theme.js'
import 'sweetalert2/dist/sweetalert2.min.css';
import { resolveComponent } from '@laravext/vue3/tools';
import Cookies from 'js-cookie';
document.addEventListener('DOMContentLoaded', function () {
createLaravextApp({
nexusResolver: (name) => resolveComponent(`./nexus/${name}`, import.meta.glob('./nexus/**/*')),
strandsResolver: (name) => resolveComponent(`./strands/${name}.vue`, import.meta.glob('./strands/**/*.vue')),
// The beforeSetup function is executed once, before any of the setups.
// You can use this to set something up, such as internationalization.
beforeSetup: ({ laravext }) => {
let user = laravext.page_data?.shared_props?.auth?.user;
let locale = user?.locale ?? Cookies.get('locale') ?? 'en';
// This is just for example purposes, using i18n is not a requirement
const i18n = createI18n({
legacy: false,
locale: locale,
fallbackLocale: 'en',
messages: {
pt
}
})
laravext.app.i18n = i18n;
},
// This setup is applied to all components, including nexus and strands
setup: ({ app, laravext }) => {
app.use(laravext.app.i18n);
app.use(VueSweetalert2);
app.use(fkPluging, fkDefaultConfig(fkConfig));
return app;
},
// Like Inertia, there's a wrapper for the https://ricostacruz.com/nprogress library.
// You don't have to declare this, while using the createLaravextApp, but so
// you know, these are the default values:
progress: {
delay: 0, // How many miliseconds until the loading bar appears
color: '#29d', // The color of said bar
includeCSS: true, // Wether or not to use NProgress' default styling
showSpinner: false, // Wether or not to show the spinner
},
// or, if you don't want it at all:
// progress: false,
// The setupNexus function is applied only to the nexus component, after the 'setup'
// function, unless reverseSetupOrder is true
// setupNexus: ({ nexus, laravext }) => {
// // Anything you want to do with the nexus app instance
// nexus.use(Something)
// return nexus;
// },
// The setupStrand function is applied only to the strand components, after the 'setup' function,
// unless reverseSetupOrder is true.
// The 'strandData' parameter is the data passed to the strand component from the blade
// where it was located, if applicable.
// setupStrand: ({strand, laravext, strandData}) => {
// // Anything you want to do with the strand app instance
// strand.use(SomethingElse)
// return strand
// },
// If you want to reverse the order of the setup functions (for some reason), set this to true
// reverseSetupOrder: true,
// If for some reason you to change the order of the file conventions, you can do so here.
// It'll be applied to the nexus components, from a first to last basis, encapsulating
// the page component. Check the File Conventions section for more details.
// conventions: [
// 'error',
// 'layout',
// 'middleware',
// ],
// If for some reason you must disable the pushState of the laravext client router, you can do
// so here. Be aware that this will disable the back navigation, and you're user will endup
// going back to the previous non-laravext page.
// disablePushedStateData: () => {
// // Your logic here, such as detecting browsers, etc.
// let logicResult = true
// return logicResult
// },
// By default, the popstate event registered by laravext will ignore the event if the state is null.
// If you want to change this behavior, you can do so here.
// ignorePopStateEvent: (event) => {
// let logicResult = event.state === null;
// return logicResult;
// },
})
}, false);
You can change the nexus and strands' locations if you want to. Make sure to change the nexus directory in the ./config/laravext.php
file. For more details on how the router works, check the router section.
Vite Configuration
Make sure you have a vite.config.js that looks something like this (assuming you've already installed either @vitejs/plugin-react or @vitejs/plugin-vue).
This example assumes you've defined a VITE_APP_ENV
and VITE_APP_DOMAIN
in your .env
file, which contain the APP_ENV
and APP_DOMAIN
values, respectively.
Change any configuration to fit your needs.
import { defineConfig, loadEnv } from 'vite';
import laravel, { refreshPaths } from "laravel-vite-plugin";
// For React
import react from "@vitejs/plugin-react";
// For Vue
import vue from '@vitejs/plugin-vue';
export default function ({ mode }) {
const env = loadEnv(mode, process.cwd());
const host = env.VITE_APP_ENV == "local" ? env.VITE_APP_DOMAIN : undefined;
return defineConfig({
server: { host },
plugins: [
laravel({
input: [
'resources/css/app.css',
'resources/js/app.(js|jsx|ts|tsx)', // change the extension to fit your needs
],
refresh: [...refreshPaths, "resources/js/**", "app/**"],
}),
// For React
react(),
// For Vue
vue({
template: {
transformAssetUrls: {
base: null,
includeAbsolute: false,
},
},
}),
],
})
};
Blade
Assuming you have a ./resources/views/layouts/app.blade.php
file, where a section is inserted, you'll need to insert some blade directives in it.
(The following code contains a $head
variable that is described in the Tools/Nexus Response/withHead section. You can remove it if you're not going to use it)
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<!-- Check the Tools/Nexus Response/withHeadTitle($title) and withHeadDescription($description) section in the docs for more details -->
<title>{{ @$head['title'] ?? config('app.name', 'Laravel') }}</title>
<meta name="description" content="{{ @$head['description'] ?? config('app.description') }}">
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
<!-- Style -->
@vite(['resources/css/app.css'])
</head>
<body class="font-sans text-gray-900 antialiased">
<div class="min-h-screen flex flex-col sm:justify-center items-center pt-6 sm:pt-0 bg-gray-100 dark:bg-gray-900">
<div class="w-full sm:max-w-[95%] mt-6 px-6 py-4 bg-white dark:bg-gray-800 shadow-md overflow-hidden sm:rounded-lg text-white">
@yield('content')
</div>
</div>
@laravextScripts <!-- This will create a __laravext context variable -->
@viteReactRefresh <!-- In case you're using React -->
@vite(['resources/js/app.jsx']) <!-- In case you're using React -->
@vite(['resources/js/app.js']) <!-- In case you're using Vue -->
</body>
</html>
then, assuming you have a ./resources/views/sections/app.blade.php
(or any other view that you're going to render a nexus):
@extends('layouts.app')
@section('content')
@nexus
@endsection
Now if you run
npm run dev
Everything should be up and running.
Nexus Directory
By default, the nexus directory is set as ./resources/js/nexus
. So this is where you should start placing the files that will be used by Laravext's Router. You may change this in the ./config/laravext.php
file, assuming you've published the config file.
You're now ready to start creating your project using the laravext router
Typescript
If you want to use Typescript, you might need to add some files to your project, and also install the NPM packages mentioned by the # If you're planning to use typescript [...]
comments above in the NPM section.
First, you'll need a tsconfig.json
file in your project root directory. Here's an example (your needs may vary, so feel free to change it):
- React
- Vue
tsconfig.json
:
{
"compilerOptions": {
"allowJs": true,
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"strict": true,
"isolatedModules": true,
"target": "ESNext",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"paths": {
"@/*": ["./resources/js/*"],
}
},
"include": ["resources/js/**/*.ts", "resources/js/**/*.tsx", "resources/js/**/*.d.ts"]
}
tsconfig.json
:
{
"compilerOptions": {
"allowJs": true,
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "preserve",
"strict": true,
"isolatedModules": true,
"target": "ESNext",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"skipLibCheck": true,
"paths": {
"@/*": ["./resources/js/*"],
}
},
"include": ["resources/js/**/*.ts", "resources/js/**/*.d.ts", "resources/js/**/*.vue"]
}
Now you'll need to define your types. Create a @types
directory in your ./resources/js
directory, and create a laravext.d.ts
file in it. Here's what needs to be inside of it:
- React
- Vue
laravext.d.ts
:
declare module "@laravext/react" {
export const createLaravextApp: any;
export const createLaravextSsrApp: any;
export const Head: any;
export const Link: any;
export const laravextPageData: any;
export const version: any;
export const nexus: any;
export const nexusProps: any;
export const sharedProps: any;
export const routeParams: any;
export const routeName: any;
export const queryParams: any;
export const visit: any;
}
declare module "@laravext/react/server" {
export const serve: any;
}
declare module "@laravext/react/tools" {
export const resolveComponent: any;
}
declare module "@laravext/react/progress" {
export const injectCSS: any;
export const setupProgress: any;
export const startProgress: any;
export const moveProgress: any;
export const endProgress: any;
}
laravext.d.ts
:
declare module "@laravext/vue3" {
export const createLaravextApp: any;
export const createLaravextSsrApp: any;
export const Head: any;
export const Link: any;
export const laravextPageData: any;
export const version: any;
export const nexus: any;
export const nexusProps: any;
export const sharedProps: any;
export const routeParams: any;
export const routeName: any;
export const queryParams: any;
export const visit: any;
}
declare module "@laravext/vue3/server" {
export const serve: any;
}
declare module "@laravext/vue3/tools" {
export const resolveComponent: any;
}
declare module "@laravext/vue3/progress" {
export const injectCSS: any;
export const setupProgress: any;
export const startProgress: any;
export const moveProgress: any;
export const endProgress: any;
}
If you haven't already, you might need to declare a ./resouces/js/@types/vite-env.d.ts
file, with the following content:
/// <reference types="vite/client" />
and you might also need a ./resources/js/@types/global.d.ts
file, to set up stuff you might be using, like Axios, for example:
import { AxiosInstance } from 'axios';
declare global {
interface Window {
axios: AxiosInstance;
}
}
and maybe a ./resources/js/@types/index.d.ts
file, to export all the types you might have:
export interface User {
id: number;
name: string;
email: string;
email_verified_at?: string;
}
export type sharedProps<T extends Record<string, unknown> = Record<string, unknown>> = T & {
auth: {
user: User;
};
motivation: string;
};
Needless to say, some of the examples from the NPM section above might need to be changed to fit your needs, like declaring the type of the name
parameter in the nexusResolver
and strandsResolver
functions, for example:
import './bootstrap';
import '../css/app.css';
// For React
import { createLaravextApp } from "@laravext/react"
import { resolveComponent } from "@laravext/react/tools"
// For Vue
import { createLaravextApp } from "@laravext/vue3"
import { resolveComponent } from "@laravext/vue3/tools"
createLaravextApp({
// Added the 'string' type here
nexusResolver: (name: string) => resolveComponent(`./nexus/${name}`, import.meta.glob('./nexus/**/*')),
// The rest of the code, which might also need some changes
});
Looking for server side rendering?
Credits: By Nintendo - https://www.mariowiki.com/File:Mushroom_Retainer_SMB1_W1-4_rescued.png, Fair use, Link (if any of Nintendo's lawyers end up here, please just let me know that I'll remove this image, no need to sue me)Check the Server Side Rendering section for more details on how to use it.