This tutorial offer to use
pnpm
as Node.js package manager but you can use npm
.From vitejs.dev
Remove old css
and js
rm -rf resources/cssrm -rf resources/js
.gitignore
public/build
Remove Node.js from package.json
pnpm remove axios lodash postcss
Install vite
Add vite
with typescript
.
pnpm add @types/node dotenv typescript vite -D
package.json
scripts
Update package.json
in scripts
.
{ "scripts": { "dev": "vite --config vite.config.ts --port 3100 --host", "build": "vite build --config vite.config.ts" }}
Configuration files
touch tsconfig.jsontouch vite.config.ts
{ "compilerOptions": { "target": "esnext", "module": "esnext", "moduleResolution": "node", "strict": true, "jsx": "preserve", "sourceMap": true, "resolveJsonModule": true, "esModuleInterop": true, "skipLibCheck": true, "noImplicitAny": false, "baseUrl": ".", "lib": ["esnext", "dom"], "types": ["vite/client"], "typeRoots": ["./node_modules/@types", "resources/**/*.d.ts"], "plugins": [], "paths": { "@/*": ["./*"], "~": ["./"], "~/*": ["./*"], "~/app": ["resources"], "~/app/*": ["resources/*"] } }, "include": ["resources/**/*.ts", "resources/**/*.d.ts", "resources/**/*.vue"], "exclude": ["node_modules"]}
import { defineConfig } from 'vite'import type { PluginOption } from 'vite'import Dotenv from 'dotenv'Dotenv.config()/** * Enable full reload for blade file */const bladePlugin = (): PluginOption => ({ name: 'vite:laravel', handleHotUpdate({ file, server }) { if (file.endsWith('.blade.php')) { server.ws.send({ type: 'full-reload', path: '*', }) } },})// https://vitejs.dev/config/export default defineConfig({ server: { hmr: { host: process.env.VITE_DEV_SERVER_HOST, }, }, base: '', root: 'resources', publicDir: 'static', build: { outDir: '../public/build', emptyOutDir: true, manifest: true, rollupOptions: { input: '/app.ts', }, }, cacheDir: '../node_modules/.vite', // views config resolve: { alias: { '~/app': './', }, }, plugins: [bladePlugin()],})
In .env
.env
VITE_DEV_SERVER_HOST=localhost
Laravel Vite
Create configuration files.
touch config/vite.phpmkdir -p app/Facades/ ; touch app/Facades/ViteManifest.phpmkdir -p app/Support/ ; touch app/Support/LaravelViteManifest.php
Add vite.php
<?phpreturn [ 'dev_server' => env('VITE_DEV_SERVER', 'local' === env('APP_ENV')), 'dev_server_host' => env('VITE_DEV_SERVER_HOST', '127.0.0.1'),];
Add ViteManifest.php
<?phpnamespace App\Facades;use Illuminate\Support\Facades\Facade;class ViteManifest extends Facade{ /** * Get the registered name of the component. */ protected static function getFacadeAccessor(): string { return 'laravel-vite-manifest'; }}
Add LaravelViteManifest.php
<?phpnamespace App\Support;use Illuminate\Support\Facades\Config;use Illuminate\Support\Facades\File;class LaravelViteManifest{ private $manifestCache = []; public function embed(?string $name = 'views', ?string $entry = 'app.ts', ?int $port = 3100): string { if (Config::get('vite.dev_server')) { $host = Config::get('vite.dev_server_host'); return $this->jsImports( "http://{$host}:{$port}/{$entry}" ); } if ($assets = $this->productionAssets($name, $entry)) { return $this->jsImports($assets) .$this->jsPreloadImports($name, $entry) .$this->cssImports($name, $entry); } return ''; } private function getManifest(string $name): array { if (! empty($this->manifestCache[$name])) { return $this->manifestCache[$name]; } $manifest = public_path("build/manifest.json"); if (File::exists($manifest)) { $this->manifestCache[$name] = json_decode(File::get($manifest), true); } return $this->manifestCache[$name] ?? []; } private function jsImports(string $url): string { return "<script type=\"module\" crossorigin src=\"{$url}\"></script>"; } private function jsPreloadImports(string $name, string $entry): string { $res = ''; foreach ($this->preloadUrls($name, $entry) as $url) { $res .= "<link rel=\"modulepreload\" href=\"{$url}\">"; } return $res; } private function preloadUrls(string $name, string $entry): array { $urls = []; $manifest = $this->getManifest($name); if (! empty($manifest[$entry]['imports'])) { foreach ($manifest[$entry]['imports'] as $imports) { $urls[] = asset("build/".$manifest[$imports]['file']); } } return $urls; } private function cssImports(string $name, string $entry): string { $tags = ''; foreach ($this->cssUrls($name, $entry) as $url) { $tags .= "<link rel=\"stylesheet\" href=\"{$url}\">"; } return $tags; } private function cssUrls(string $name, string $entry): array { $urls = []; $manifest = $this->getManifest($name); if (! empty($manifest[$entry]['css'])) { foreach ($manifest[$entry]['css'] as $file) { $urls[] = asset("build/{$file}"); } } return $urls; } private function productionAssets(string $name, string $entry): string { $manifest = $this->getManifest($name); if (! isset($manifest[$entry])) { return ''; } return asset("build/".$manifest[$entry]['file']); }}
Add AppServiceProvider.php
<?phpnamespace App\Providers;use App\Support\LaravelViteManifest;use Illuminate\Support\Facades\Blade;use Illuminate\Support\ServiceProvider;class AppServiceProvider extends ServiceProvider{ /** * Register any application services. */ public function register() { $this->app->singleton('laravel-vite-manifest', function () { return new LaravelViteManifest(); }); } /** * Bootstrap any application services. */ public function boot() { Blade::directive('vite', function ($expression) { return '{!! App\Facades\ViteManifest::embed('.$expression.') !!}'; }); }}
Blade
Create files
Remove current Blade view.
rm resources/views/welcome.blade.php
Create new Blade files.
mkdir -p public/assetsmkdir -p resources/views/components ; touch resources/views/components/app.blade.phpmkdir -p resources/views/pages ; touch resources/views/pages/index.blade.phptouch resources/app.csstouch resources/app.tstouch resources/global.d.ts
Create new app
component.
<!DOCTYPE html><html lang="{{ str_replace('_', '-', app()->getLocale()) }}"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="csrf-token" content="{{ csrf_token() }}"> <title> {{ config('app.name') }} </title> <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"> <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"> <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"> <link rel="manifest" href="/site.webmanifest"> @vite() @stack('styles')</head><body class="{{ config('app.env') === 'local' ? 'debug-screens' : '' }}"> {{ $slot }} @stack('scripts')</body></html>
Create a new page.
<x-app> <div>Welcome</div></x-app>
TypeScript & configuration
Create an app.css
/* your style */
Create app.ts
import './app.css'
Create TypeScript global.d.ts
interface.
/** * From https://bobbyhadz.com/blog/typescript-make-types-global */declare global {}export {}
Routes
Create route to serve page.
php artisan make:controller MainController
<?phpnamespace App\Http\Controllers;class MainController extends Controller{ public function index() { return view('pages.index'); }}
<?phpuse App\Http\Controllers\MainController;use Illuminate\Support\Facades\Route;- Route::get('/', function () {- return view('welcome');- });+ Route::get('/', [MainController::class, 'index'])->name('welcome');
Serve app
In one hand, keep serve
.
php artisan serve
In other hand, keep dev
.
pnpm dev