Cómo utilizar rutas de Laravel en JS

por Halí - última modificación: Julio 05, 2020
LaravelJavaScriptVue.jsJSON

TL;DR Con un paquete
https://github.com/halivert/laravel-js-routes

Generalmente utilizamos JavaScript para el front-end de nuestras aplicaciones, si además empleamos Laravel, notaremos que incluye un módulo muy útil llamado Webpack Mix, que nos ayuda a preprocesar nuestros archivos JS.

Un problema surge cuando queremos acceder a las rutas que definimos en Laravel desde cualquier módulo de JS. Una de las soluciones más sencillas es exportar todas nuestras rutas a un JSON y después utilizar una función que tenga un comportamiento similar a la función route de Laravel pero en JS.

Código

Para ello creé un comando de Laravel (se puede utilizar con php artisan) compuesto por lo siguiente:

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Route;

class CreateJSRoutesCommand extends Command
{
    public function handle()
    {
      /*
       * Cuando llamamos a nuestro comando, esto se ejecuta primero.
       */
    }

    public function createFile($fileName, $contents)
    {
      /*
       * Crea un archivo con el nombre de $fileName y como contenido
       * $contents, además pregunta si deseas sobreescribir el
       * archivo en caso de que exista uno con el mismo nombre.
       */
    }

    private function includeRoute($value, $routeName)
    {
      /*
       * Función que determina si una ruta debe incluirse o no en nuestro
       * JSON, por defecto omite las rutas de "telescope".
       */
    }

    public function getJSPath($path)
    {
      /*
       * Devuelve la ruta en donde se va a crear el nuevo archivo con las
       * rutas y la función.
       */
    }
}

La función handle es la que hace la mayor parte del trabajo, lo primero que haremos será obtener las rutas de Laravel, las filtramos y conservamos solo su URI.

$routes = collect(
    Route::getRoutes()->getRoutesByName()
  )->filter(function ($route, $key) {
    return $this->includeRoute($route, $key);
  })->map(function ($route) {
    return [
    "uri" => $route->uri
    ];
  });

El contenido de nuestro archivo JS va a componerse del JSON y la función route así que comenzamos agregando el nombre de la variable que usaremos y el contenido.

$jsonFlags = JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE;

$content = 'const routes = ';
$content .= json_encode($routes, $jsonFlags);
$content .= ";\n\n";

Después construimos la función route (en un archivo aparte)

$content .= file_get_contents(
    __DIR__ . '/../assets/js/routeFunction.js'
);
// assets/js/routeFunction.js

// Esta funcion recibe un nombre de ruta y un arrreglo de parámetros.
const route = (routeName, params = []) => {
  // Busca en las rutas guardadas la que tenga ese nombre y si no existe
  // arroja un error
  const _route = routes[routeName];
  if (_route == null) throw "Requested route doesn't exist";

  let uri = _route.uri;

  // Si se encuentra una URI, reemplaza los parámetros con una RegEx
  // (no sé como la hice) y arroja otro error si faltan parámetros.
  // Los parámetros que sobran son ignorados.

  const matches = uri.match(/{[\w]+}/g) || [];
  const requiredParametersCount = matches.length;

  if (params instanceof Array) {
    if (params.length < requiredParametersCount) throw "Missing parameters";

    for (let i = 0; i < requiredParametersCount; i++)
      uri = uri.replace(/{[\w]+}/, params.shift());

    for (let i = 0; i < params.length; i++)
      uri += (i ? "&" : "?") + params[i] + "=" + params[i];
  } else if (params instanceof Object) {
    let extraParams = matches.reduce((ac, match) => {
      let key = match.substring(1, match.length - 1);
      if (params.hasOwnProperty(key)) {
        uri = uri.replace(new RegExp(match, "g"), params[key]);
        delete ac[key];
      }
      return ac;
    }, params);

    Object.keys(extraParams).forEach((key, i) => {
      uri += (i ? "&" : "?") + key + "=" + extraParams[key];
    });
  }

  if (uri.includes("}")) throw "Missing parameters";

  return "/" + uri;
};

export { route };

Por último creamos el archivo.

$fileName = $this->option('name') ?? config('app.jsroutes.name');
if ($this->createFile($fileName, $content)) {
  $this->info("$fileName created");
}

Instalación

composer require halivert/laravel-js-routes

Antes de usar

Ya que hemos agregado el comando a Laravel, entonces lo ejecutamos con:

php artisan route:tojs

Posterioremente debemos agregar el archivo a nuestro webpack.mix.js, para que sea procesado.

mix.js("resources/js/routes", "public/js");

Y después de un yarn prod o npm prod podremos hacer uso de nuestra nueva función route, cuando queramos llamar una ruta por su nombre en un archivo JS.

import { route } from "./routes.js";

Uso

Ejemplos de uso:

axios
  .get(route("routeName", [param1, param2]))
  .then(response => {
    console.log(response.data);
  });

Dudas al teléfono en pantalla 👋🏽