Laravel Command to generate route endpoints that can be used in typescript fully threeshakable.
GenerateTypescriptRoutes.php
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Route;
class GenerateTypescriptRoutes extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:generate-typescript-routes';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Generate a typescript file with all the routes (treeshakable)';
/**
* Execute the console command.
*/
public function handle()
{
$contents = collect(Route::getRoutes()->getRoutesByName())->map(function ($route, $name) {
$routeIngoreList = [
'filament.',
'debugbar.',
'livewire.',
'ignition.',
'sanctum.csrf-cookie',
];
if (Str::startsWith($name, $routeIngoreList)) {
return '';
}
preg_match_all('/\{(.*?)\}/', $route->uri(), $args, PREG_SET_ORDER);
$argstr = collect($args)
->map(fn ($a) => $this->mapReservedWord($a[1]) . ": string | number")
->add('query?: QueryParams')
->join(', ');
$path = preg_replace_callback('/\{(.*?)\}/', function ($match) {
$isOptional = strpos($match[1], '?') !== false;
return '${' . str_replace('?', '', $this->mapReservedWord($match[1])) . ($isOptional ? " ?? ''" : '') . '}';
}, $route->uri());
if (substr($path, 0, 1) !== '/') {
$path = '/'. $path;
}
$safeName = Str::snake(str_replace(['-', '.'], '_', $name));
return "/** {$name} */
export function route_to_{$safeName}({$argstr}) { return withQuery(`{$path}`, query); }\n";
})->filter()->join("\n");
$header = '
// _______ _______ __ _ _______ ______ _______ _______ _______ ______
// | || || | | || || _ | | _ || || || |
// | ___|| ___|| |_| || ___|| | || | |_| ||_ _|| ___|| _ |
// | | __ | |___ | || |___ | |_||_ | | | | | |___ | | | |
// | || || ___|| _ || ___|| __ || | | | | ___|| |_| |
// | |_| || |___ | | | || |___ | | | || _ | | | | |___ | |
// |_______||_______||_| |__||_______||___| |_||__| |__| |___| |_______||______|
//
// Generated with:
// php artisan app:generate-typescript-routes
export type QueryParams = string | string[][] | Record<string, string> | URLSearchParams | undefined;
function withQuery(path: string, query: QueryParams) {
const searchParams = new URLSearchParams(query);
return `${path}${searchParams.size > 0 ? `?${searchParams.toString()}` : \'\'}`;
}
';
File::put(resource_path('js/routes.ts'), $header . "\n" . $contents);
$this->info(resource_path('js/routes.ts') . ' succesfully generated');
}
private function mapReservedWord($word) {
$jsReservedWords = [
'break',
'case',
'catch',
'class',
'const',
'continue',
'debugger',
'default',
'delete',
'do',
'else',
'enum',
'export',
'extends',
'false',
'finally',
'for',
'function',
'if',
'import',
'in',
'instanceof',
'let',
'new',
'null',
'return',
'super',
'switch',
'this',
'throw',
'true',
'try',
'typeof',
'var',
'void',
'while',
'with',
'yield',
'await',
'implements',
'interface',
'package',
'private',
'protected',
'public',
'static',
];
if (in_array($word, $jsReservedWords)) {
return '_' . $word;
}
return $word;
}
}
Example generated routes.ts
// _______ _______ __ _ _______ ______ _______ _______ _______ ______
// | || || | | || || _ | | _ || || || |
// | ___|| ___|| |_| || ___|| | || | |_| ||_ _|| ___|| _ |
// | | __ | |___ | || |___ | |_||_ | | | | | |___ | | | |
// | || || ___|| _ || ___|| __ || | | | | ___|| |_| |
// | |_| || |___ | | | || |___ | | | || _ | | | | |___ | |
// |_______||_______||_| |__||_______||___| |_||__| |__| |___| |_______||______|
//
// Generated with:
// php artisan app:generate-typescript-routes
export type QueryParams = string | string[][] | Record<string, string> | URLSearchParams | undefined;
function withQuery(path: string, query: QueryParams) {
const searchParams = new URLSearchParams(query);
return `${path}${searchParams.size > 0 ? `?${searchParams.toString()}` : ''}`;
}
/** register */
export function route_to_register(query?: QueryParams) { return withQuery(`/register`, query); }
/** login */
export function route_to_login(query?: QueryParams) { return withQuery(`/login`, query); }
Usage
php artisan app:generate-typescript-routes
App.vue
<template>
<a :href="route_to_login()">Login</a>
<a :href="route_to_login({ backUrl: '/homepage' })">Login</a>
</template>
<script lang="ts" setup">
import { route_to_login } from './routes';
</script>