├── .git-blame-ignore-revs ├── .prettierignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── UPGRADING.md ├── composer.json ├── dist ├── index.esm.js ├── index.js └── route.umd.js ├── package-lock.json ├── package.json ├── src ├── BladeRouteGenerator.php ├── CommandRouteGenerator.php ├── Output │ ├── File.php │ ├── MergeScript.php │ ├── Script.php │ └── Types.php ├── Ziggy.php ├── ZiggyServiceProvider.php └── js │ ├── Route.js │ ├── Router.js │ ├── browser.js │ ├── index.d.ts │ └── index.js └── tsconfig.json /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Install and run `prettier` 2 | 5cfa3fb64a6e9d40ed3b48c18abb3790ec6de2ca 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .github/ 2 | dist/ 3 | tests/fixtures/ 4 | vendor/ 5 | *.md 6 | .git-blame-ignore-revs 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), and the format of this changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). 6 | 7 | Breaking changes are marked with ⚠️. 8 | 9 | ## [Unreleased] 10 | 11 | ## [v2.5.3] - 2025-05-17 12 | 13 | **Fixed** 14 | 15 | * Fix type hinting for PHP 8.4 by @bodny in https://github.com/tighten/ziggy/pull/816 16 | * Fix checking current route in strict mode by @bakerkretzmar in https://github.com/tighten/ziggy/pull/840 17 | 18 | ## [v2.5.2] - 2025-02-27 19 | 20 | **Changed** 21 | 22 | * Test on Laravel 12 by @shuvroroy in https://github.com/tighten/ziggy/pull/820 23 | * Update extendability of `Types.php` by @rrmesquita in https://github.com/tighten/ziggy/pull/821 24 | 25 | ## [v2.5.1] - 2025-01-31 26 | 27 | **Fixed** 28 | 29 | * Fix `current()` type in strict mode by @bakerkretzmar in https://github.com/tighten/ziggy/pull/809 30 | 31 | ## [v2.5.0] - 2025-01-22 32 | 33 | **Added** 34 | 35 | - Support passing except/only args to ziggy command by @joshmanders in https://github.com/tighten/ziggy/pull/805 36 | 37 | **Fixed** 38 | 39 | * Redefine `ParsedQs` type by @bakerkretzmar in https://github.com/tighten/ziggy/pull/806 40 | * Only limit route names when calling route() by @simon-tma in https://github.com/tighten/ziggy/pull/790 41 | 42 | ## [v2.4.2] - 2025-01-02 43 | 44 | **Fixed** 45 | 46 | - Fix deprecated implictly nullable parameter by @datlechin in https://github.com/tighten/ziggy/pull/792 47 | 48 | ## [v2.4.1] - 2024-11-21 49 | 50 | **Fixed** 51 | 52 | - Fix `qs` type dependencies 53 | 54 | ## [v2.4.0] - 2024-11-09 55 | 56 | **Added** 57 | 58 | - Add ability to strictly type check route names by @bakerkretzmar and @Andyuu in https://github.com/tighten/ziggy/pull/787 59 | 60 | **Fixed** 61 | 62 | - Fix type definition for route() with only options by @bram-pkg in https://github.com/tighten/ziggy/pull/786 63 | - Fix `queryParams` return type by @bakerkretzmar in https://github.com/tighten/ziggy/pull/788 64 | 65 | **Changed** 66 | 67 | - Add note to use inject for vue 3 by @mhelaiwa in https://github.com/tighten/ziggy/pull/785 68 | - Update `README.md`. by @choowx in https://github.com/tighten/ziggy/pull/782 69 | 70 | ## [v2.3.1] - 2024-10-17 71 | 72 | **Fixed** 73 | 74 | - Escape special regex characters when matching routes by @aguingand in https://github.com/tighten/ziggy/pull/766 75 | - Fix params sometimes being double-decoded by @bakerkretzmar in https://github.com/tighten/ziggy/pull/778 76 | 77 | **Changed** 78 | 79 | - Optimize has() by @thomas-brx in https://github.com/tighten/ziggy/pull/770 80 | 81 | ## [v2.3.0] - 2024-07-21 82 | 83 | **Added** 84 | 85 | - Add ability to acess only the query params or the actual route params by @peter-emad99 in https://github.com/tighten/ziggy/pull/744 86 | 87 | **Changed** 88 | 89 | - Pest by @bakerkretzmar in https://github.com/tighten/ziggy/pull/756 90 | - Laravel route change by @hasanablak in https://github.com/tighten/ziggy/pull/763 91 | - Modernize some PHP syntax by @bakerkretzmar in https://github.com/tighten/ziggy/pull/757 92 | 93 | ## [v2.2.1] - 2024-05-16 94 | 95 | **Fixed** 96 | 97 | - Fix double slashes with domain params by @bakerkretzmar in https://github.com/tighten/ziggy/pull/754 98 | 99 | ## [v2.2.0] - 2024-05-16 100 | 101 | **Added** 102 | 103 | - Support Laravel Folio by @bakerkretzmar in https://github.com/tighten/ziggy/pull/733 104 | 105 | **Fixed** 106 | 107 | - Fix route().params type by @peter-emad99 in https://github.com/tighten/ziggy/pull/743 108 | 109 | ## [v2.1.0] - 2024-03-26 110 | 111 | **Added** 112 | 113 | - Improve generated types to include optional types by @alexmccabe in https://github.com/tighten/ziggy/pull/697 114 | 115 | **Fixed** 116 | 117 | - Fix Vue plugin on Vue 2 by @bakerkretzmar in https://github.com/tighten/ziggy/pull/738 118 | 119 | ## [v2.0.5] - 2024-03-19 120 | 121 | **Fixed** 122 | 123 | - Export the types file by @simon-tma in https://github.com/tighten/ziggy/pull/721 124 | 125 | ## [v2.0.4] - 2024-03-05 126 | 127 | **Fixed** 128 | 129 | - Fix event listener propagation by @bakerkretzmar in https://github.com/tighten/ziggy/pull/724 130 | 131 | ## [v2.0.3] - 2024-02-20 132 | 133 | **Fixed** 134 | 135 | - Add Vue plugin and React hook types by @bakerkretzmar in https://github.com/tighten/ziggy/pull/715 136 | 137 | ## [v2.0.2] - 2024-02-20 138 | 139 | **Fixed** 140 | 141 | - Fix package.json module and exports by @bakerkretzmar in https://github.com/tighten/ziggy/pull/714 142 | 143 | ## [v2.0.1] - 2024-02-20 144 | 145 | **Fixed** 146 | 147 | - Remove default export from type declarations by @bakerkretzmar in https://github.com/tighten/ziggy/pull/713 148 | 149 | ## [v2.0.0] - 2024-02-20 150 | 151 | See [UPGRADING](UPGRADING.md#upgrading-from-1x-to-2x) for detailed upgrade instructions. 152 | 153 | **Changed** 154 | 155 | - Changed PHP package namespace from `Tightenco\Ziggy` to `Tighten\Ziggy`. 156 | - Updated JavaScript package to only provide named exports. 157 | - Moved Vue and React integrations to root of JavaScript module. 158 | - Updated builds to only use ES Modules. 159 | - Updated minimum dependency versions to Laravel 9 and PHP 8.1. 160 | 161 | **Removed** 162 | 163 | - Made `makeDirectory` method of `CommandRouteGenerator` class private. 164 | - Removed deprecated JavaScript `check()` method. 165 | 166 | ## [v1.8.2] - 2024-02-20 167 | 168 | **Added** 169 | 170 | - Test on Laravel 11 by @shuvroroy in https://github.com/tighten/ziggy/pull/709 171 | 172 | **Fixed** 173 | 174 | - Fix `route().current()` with encoded characters by @bakerkretzmar in https://github.com/tighten/ziggy/pull/668 175 | - Fix retrieving ziggys config from an api endpoint link in readme by @RomainMazB in https://github.com/tighten/ziggy/pull/694 176 | - Fix `route().current()` with nested/object query params by @bakerkretzmar in https://github.com/tighten/ziggy/pull/712 177 | 178 | **Changed** 179 | 180 | - Enable provenance for npm package by @saibotk in https://github.com/tighten/ziggy/pull/684 181 | - Handle generated file extensions more robustly by @bakerkretzmar in https://github.com/tighten/ziggy/pull/687 182 | 183 | ## [v1.8.1] - 2023-10-12 184 | 185 | **Fixed** 186 | 187 | - Update: Correction of some spellings and articles in the Readme file. by @prince-noman in https://github.com/tighten/ziggy/pull/678 188 | - Fix `route().current()` with encoded characters by @bakerkretzmar in https://github.com/tighten/ziggy/pull/668 189 | 190 | **Changed** 191 | 192 | - Update Readme, formatting, and testing setup by @bakerkretzmar in https://github.com/tighten/ziggy/pull/675 193 | 194 | ## [v1.8.0] - 2023-10-06 195 | 196 | **Added** 197 | 198 | - Add type declarations and `ziggy.d.ts` generation by @lmeysel and @bakerkretzmar in https://github.com/tighten/ziggy/pull/664 199 | 200 | ## [v1.7.0] - 2023-10-04 201 | 202 | **Added** 203 | 204 | - Add `useRoute()` React hook by @bakerkretzmar in https://github.com/tighten/ziggy/pull/473 205 | 206 | **Fixed** 207 | 208 | - URL-encode fewer special characters inside in route parameters by @bakerkretzmar in https://github.com/tighten/ziggy/pull/662 209 | - Fix empty root URL with `absolute: false` by @bakerkretzmar in https://github.com/tighten/ziggy/pull/667 210 | 211 | ## [v1.6.2] - 2023-08-18 212 | 213 | **Fixed** 214 | 215 | - Make slash "not encoding" less restrictive by @aguingand in https://github.com/tighten/ziggy/pull/661 216 | 217 | ## [v1.6.1] - 2023-08-04 218 | 219 | **Fixed** 220 | 221 | - Fix numeric route names by @bakerkretzmar https://github.com/tighten/ziggy/pull/658 222 | 223 | ## [v1.6.0] - 2023-05-12 224 | 225 | **Added** 226 | 227 | - Add `'ziggy.output.path'` config option by @mhelaiwa https://github.com/tighten/ziggy/pull/633 228 | 229 | ## [v1.5.2] - 2023-05-05 230 | 231 | **Added** 232 | 233 | - Add Vite instructions to README.md by @fullstackfool https://github.com/tighten/ziggy/pull/627 234 | 235 | **Fixed** 236 | 237 | - Trim slashes between origin and optional first param by @bakerkretzmar in https://github.com/tighten/ziggy/pull/637 238 | - Fix `route().params` not being decoded by @Tofandel in https://github.com/tighten/ziggy/pull/610 239 | 240 | ## [v1.5.1] - 2023-04-22 241 | 242 | **Fixed** 243 | 244 | - Check for overridden `getKeyName()` and `$primaryKey` by @Jared0430 in https://github.com/tighten/ziggy/pull/630 245 | 246 | ## [v1.5.0] - 2022-09-23 247 | 248 | **Added** 249 | 250 | - Add support for negating route filter patterns with `!` by @bakerkretzmar in https://github.com/tighten/ziggy/pull/559 251 | 252 | **Changed** 253 | 254 | - Test on PHP 8.2 by @bakerkretzmar in https://github.com/tighten/ziggy/pull/584 255 | 256 | **Fixed** 257 | 258 | - Allow `null` as entire params argument by @bakerkretzmar in https://github.com/tighten/ziggy/pull/582 259 | 260 | ## [v1.4.6] - 2022-04-08 261 | 262 | **Fixed** 263 | 264 | - Use global regex instead of `replaceAll` for better browser support by @bakerkretzmar in https://github.com/tighten/ziggy/pulls/548 265 | 266 | ## [v1.4.5] - 2022-03-25 267 | 268 | **Fixed** 269 | 270 | - Remove dead code by @Tofandel in https://github.com/tighten/ziggy/pull/525 271 | - Fix `route().current()` with 'wheres' that include regex start/end anchors by @bakerkretzmar in https://github.com/tighten/ziggy/pull/535 272 | - Fix inherited custom route key name detection by @bakerkretzmar in https://github.com/tighten/ziggy/pull/540 273 | 274 | ## [v1.4.4] - 2022-03-11 275 | 276 | **Added** 277 | 278 | - Allow custom output via formatters by @jaulz in https://github.com/tighten/ziggy/pull/483 279 | - Add Vue 3 `provide`/`inject` support to the Vue plugin by @c-fitzmaurice in https://github.com/tighten/ziggy/pull/518 280 | 281 | **Fixed** 282 | 283 | - Encode non-slash characters in wildcard last parameter by @rodrigopedra in https://github.com/tighten/ziggy/pull/507 284 | - Update JS route matching to take 'wheres' into account by @Tofandel in https://github.com/tighten/ziggy/pull/514 285 | 286 | ## [v1.4.3] - 2022-01-28 287 | 288 | **Added** 289 | 290 | - Test on PHP 8.1 by @bakerkretzmar in https://github.com/tighten/ziggy/pull/484 291 | - Don't encode slashes in wildcard last parameter by @bakerkretzmar in https://github.com/tighten/ziggy/pull/500 292 | 293 | **Fixed** 294 | 295 | - Ignore route action parameters without URI segments by @jaulz in https://github.com/tighten/ziggy/pull/482 296 | 297 | ## [v1.4.2] - 2021-10-01 298 | 299 | **Fixed** 300 | 301 | - Fix `route().current()` matching route names with multiple periods ([#466](https://github.com/tighten/ziggy/pull/466)) 302 | 303 | ## [v1.4.1] - 2021-09-24 304 | 305 | **Fixed** 306 | 307 | - Fix `undefined` error if there's no global `Ziggy` variable ([#462](https://github.com/tighten/ziggy/pull/462)) 308 | - Fix payload caching for Octane ([#460](https://github.com/tighten/ziggy/pull/460)) 309 | 310 | ## [v1.4.0] - 2021-08-25 311 | 312 | **Added** 313 | 314 | - Add support for parameters inside single route segments ([#444](https://github.com/tighten/ziggy/pull/444)) 315 | 316 | ## [v1.3.6] - 2021-08-25 317 | 318 | **Fixed** 319 | 320 | - Fix nested object query params ([#451](https://github.com/tighten/ziggy/pull/451)) 321 | 322 | ## [v1.3.5] - 2021-07-20 323 | 324 | **Fixed** 325 | 326 | - Fix route model binding with abstract class ([#445](https://github.com/tighten/ziggy/pull/445)) 327 | 328 | ## [v1.3.4] - 2021-07-06 329 | 330 | **Fixed** 331 | 332 | - Fix property mangling and implement ES5 compatibility ([#442](https://github.com/tighten/ziggy/pull/442)) 333 | 334 | ## [v1.3.3] - 2021-07-02 335 | 336 | **Fixed** 337 | 338 | - Revert ([d781b16](https://github.com/tighten/ziggy/commit/d781b164b8f455730fe8a8b0cbf91f0f8cb55a73)) 339 | 340 | ## [v1.3.2] - 2021-07-01 341 | 342 | **Fixed** 343 | 344 | - Remove Babel preset to correctly transpile to ES5 by default ([d781b16](https://github.com/tighten/ziggy/commit/d781b164b8f455730fe8a8b0cbf91f0f8cb55a73)) 345 | 346 | ## [v1.3.1] - 2021-06-19 347 | 348 | **Changed** 349 | 350 | - Use `Object.assign()` to merge routes ([#429](https://github.com/tighten/ziggy/pull/429)) 351 | 352 | ## [v1.3.0] - 2021-06-17 353 | 354 | **Added** 355 | 356 | - Make `location` configurable (adds better support for SSR) ([#432](https://github.com/tighten/ziggy/pull/432)) 357 | 358 | ## [v1.2.0] - 2021-05-24 359 | 360 | **Added** 361 | 362 | - Add Vue plugins ([#407](https://github.com/tighten/ziggy/pull/407), [#424](https://github.com/tighten/ziggy/pull/424)) 363 | 364 | ## [v1.1.0] - 2021-04-02 365 | 366 | **Added** 367 | 368 | - Prepare for Laravel Octane ([#415](https://github.com/tighten/ziggy/pull/415)) 369 | 370 | ## [v1.0.5] - 2021-02-05 371 | 372 | **Added** 373 | 374 | - Add support for appending 'extra' parameters to the query string ([#390](https://github.com/tighten/ziggy/pull/390)) 375 | 376 | **Changed** 377 | 378 | - Remove source maps ([#395](https://github.com/tighten/ziggy/pull/395)) 379 | 380 | **Fixed** 381 | 382 | - Use Laravel's `Reflector` class to get model parameter class name ([#396](https://github.com/tighten/ziggy/pull/396)) 383 | 384 | ## [v1.0.4] - 2020-12-06 385 | 386 | **Fixed** 387 | 388 | - Fix bug where `route().current()` could incorrectly return `true` on URLs with no parameters ([#377](https://github.com/tighten/ziggy/pull/377)) 389 | - Fix several other bugs in `route().current()` with params ([#379](https://github.com/tighten/ziggy/pull/379)) 390 | - Revert [#334](https://github.com/tighten/ziggy/pull/334), default Ziggy's `url` back to `url('/')` instead of the `APP_URL` environment variable ([#386](https://github.com/tighten/ziggy/pull/386)) 391 | 392 | ## [v1.0.3] - 2020-11-20 393 | 394 | **Fixed** 395 | 396 | - Filter out unnamed cached routes with randomly generated names ([#370](https://github.com/tighten/ziggy/pull/370)) 397 | - Fix collision with JavaScript built-in method names like `shift` by casting empty `defaults` to an object ([#371](https://github.com/tighten/ziggy/pull/371)) 398 | 399 | ## [v1.0.2] - 2020-11-13 400 | 401 | **Fixed** 402 | 403 | - Make `ziggy:generate` URL behaviour consistent with Ziggy class and Blade directive ([#361](https://github.com/tighten/ziggy/pull/361)) 404 | - Fix `route().current()` error on unknown/unnamed routes ([#362](https://github.com/tighten/ziggy/pull/362)) 405 | 406 | ## [v1.0.1] - 2020-11-10 407 | 408 | **Fixed** 409 | 410 | - Fix `route().current()` on routes at the domain root ([#356](https://github.com/tighten/ziggy/pull/356)) 411 | 412 | ## [v1.0.0] - 2020-11-06 413 | 414 | **Added** 415 | 416 | - Document the `check()` method ([#294](https://github.com/tighten/ziggy/pull/294)) and how to install and use Ziggy via `npm` and over a CDN ([#299](https://github.com/tighten/ziggy/pull/299)) 417 | - Add support for [custom scoped route model binding](https://laravel.com/docs/7.x/routing#implicit-binding), e.g. `/users/{user}/posts/{post:slug}` ([#307](https://github.com/tighten/ziggy/pull/307)) 418 | - Add support for [implicit route model binding](https://laravel.com/docs/7.x/routing#implicit-binding) ([#315](https://github.com/tighten/ziggy/pull/315)) 419 | - Add support for passing parameters to `current()` to check against the current URL in addition to the route name ([#330](https://github.com/tighten/ziggy/pull/330)) 420 | 421 | **Changed** 422 | 423 | - ⚠️ Update `ziggy:generate` output path for Laravel 5.7+ `resources` directory structure, thanks [@Somethingideally](https://github.com/Somethingideally)! ([#269](https://github.com/tighten/ziggy/pull/269)) 424 | - ⚠️ Update automatic `id` parameter detection to check for higher priority named route parameters and allow passing `id` as a query parameter ([#301](https://github.com/tighten/ziggy/pull/301)) 425 | - ⚠️ Rename the `RoutePayload` class to `Ziggy` and remove its `compile` method in favour of constructing a new instance and calling `->toArray()` or `->toJson()` ([#305](https://github.com/tighten/ziggy/pull/305)) 426 | - Resolve the application router instance internally instead of passing it into the constructor – `new Ziggy(...)` now takes only 2 arguments, `$group` and `$url` 427 | - Change the default value of `basePort` from `false` to `null` 428 | - Remove the `getRoutePayload()` methods on the `BladeRouteGenerator` and `CommandRouteGenerator` classes 429 | - ⚠️ Rename all `whitelist` and `blacklist` functionality to `only` and `except` ([#300](https://github.com/tighten/ziggy/pull/300)) 430 | - Use Jest instead of Mocha for JS tests ([#309](https://github.com/tighten/ziggy/pull/309)) 431 | - Use [microbundle](https://github.com/developit/microbundle) instead of Webpack to build and distribute Ziggy ([#312](https://github.com/tighten/ziggy/pull/312)) 432 | - ⚠️ Default Ziggy's `baseUrl` to the value of the `APP_URL` environment variable instead of `url('/')` ([#334](https://github.com/tighten/ziggy/pull/334)) 433 | - ⚠️ Return a literal string from the `route()` function when any arguments are passed to it ([#336](https://github.com/tighten/ziggy/pull/336)) 434 | - ⚠️ Rename `namedRoutes` → `routes`, `defaultParameters` → `defaults`, `baseUrl` → `url`, and `basePort` → `port` ([#338](https://github.com/tighten/ziggy/pull/338)) 435 | - ⚠️ Make the `filter()` method on the `Ziggy` class return an instance of that class instead of a collection of routes ([#341](https://github.com/tighten/ziggy/pull/341)) 436 | - ⚠️ Make the `nameKeyedRoutes()`, `resolveBindings()`, `applyFilters()`, and `group()` methods on the `Ziggy` class, and the `generate()` method on the `CommandRouteGenerator` class, private ([#341](https://github.com/tighten/ziggy/pull/341)) 437 | - ⚠️ Export from `index.js` instead of `route.js` ([#344](https://github.com/tighten/ziggy/pull/344)) 438 | - ⚠️ Encode boolean query parameters as integers ([#345](https://github.com/tighten/ziggy/pull/345)) 439 | - ⚠️ Ensure `.current()` respects the value of the `absolute` option ([#353](https://github.com/tighten/ziggy/pull/353)) 440 | 441 | **Deprecated** 442 | 443 | - Deprecate the `with()` and `check()` methods ([#330](https://github.com/tighten/ziggy/pull/330)) 444 | 445 | **Removed** 446 | 447 | - ⚠️ Remove `Route` Facade macros `Route::only()` and `Route::except()` (previously `Route::whitelist()` and `Route::blacklist()`) ([#306](https://github.com/tighten/ziggy/pull/306)) 448 | - ⚠️ Remove the following undocumented public properties and methods from the `Router` class returned by the `route()` function ([#330](https://github.com/tighten/ziggy/pull/330)): 449 | - `name`, `absolute`, `ziggy`, `urlBuilder`, `template`, `urlParams`, `queryParams`, and `hydrated` 450 | - `normalizeParams()`, `hydrateUrl()`, `matchUrl()`, `constructQuery()`, `extractParams()`, `parse()`, and `trimParam()` 451 | - ⚠️ Remove the `UrlBuilder` class ([#330](https://github.com/tighten/ziggy/pull/330)) 452 | - ⚠️ Remove the `url()` method now that `route(...)` returns a string ([#336](https://github.com/tighten/ziggy/pull/336)) 453 | - ⚠️ Remove the `baseDomain` and `baseProtocol` properties on the Ziggy config object ([#337](https://github.com/tighten/ziggy/pull/337)) 454 | - ⚠️ Remove the `appendRouteToList()`, `isListedAs()`, `except()`, and `only()` methods from the `Ziggy` class ([#341](https://github.com/tighten/ziggy/pull/341)) 455 | 456 | **Fixed** 457 | 458 | - Fix automatic `id` parameter detection by also excluding routes with an _optional_ `id` parameter (`{id?}`), thanks [@Livijn](https://github.com/Livijn)! ([#263](https://github.com/tighten/ziggy/pull/263)) 459 | - Fix port not being added to URL for routes with subdomains ([#293](https://github.com/tighten/ziggy/pull/293)) 460 | - Fix getting parameters of routes in apps installed in subfolders ([#302](https://github.com/tighten/ziggy/pull/302)) 461 | - Ensure fallback routes are always last, thanks [@davejamesmiller](https://github.com/davejamesmiller)! ([#310](https://github.com/tighten/ziggy/pull/310)) 462 | - Allow getting the route name with `current()` when the current URL has a query string ([#330](https://github.com/tighten/ziggy/pull/330)) 463 | 464 | ## [v0.9.4] - 2020-06-05 465 | 466 | **Fixed** 467 | 468 | - Fix escaping of `.` characters in the `current()` method, thanks [@davejamesmiller](https://github.com/davejamesmiller)! ([#296](https://github.com/tighten/ziggy/pull/296)) 469 | 470 | ## [v0.9.3] - 2020-05-08 471 | 472 | **Added** 473 | 474 | - Add support for passing a CSP `nonce` attribute to the `@routes` Blade directive to be set on the script tag, thanks [@tminich](https://github.com/tminich)! (#287) 475 | 476 | **Changed** 477 | 478 | - Improve support for using Ziggy with server-side rendering, thanks [@emielmolenaar](https://github.com/emielmolenaar)! ([#260](https://github.com/tighten/ziggy/pull/260)) 479 | - Avoid resolving the Blade compiler unless necessary, thanks [@axlon](https://github.com/axlon)! ([#267](https://github.com/tighten/ziggy/pull/267)) 480 | - Use `dist/js/route.js` as the npm package's main target, thanks [@ankurk91](https://github.com/ankurk91) and [@benallfree](https://github.com/benallfree)! ([#276](https://github.com/tighten/ziggy/pull/276)) 481 | - Readme and quality-of-life improvements ([#289](https://github.com/tighten/ziggy/pull/289)) 482 | 483 | **Fixed** 484 | 485 | - Ensure Ziggy's assets are always generated in the correct location ([#290](https://github.com/tighten/ziggy/pull/290)) 486 | 487 | --- 488 | 489 | For previous changes see the [Releases](https://github.com/tighten/ziggy/releases) page. 490 | 491 | [Unreleased]: https://github.com/tighten/ziggy/compare/v2.5.3...HEAD 492 | [v2.5.3]: https://github.com/tighten/ziggy/compare/v2.5.2...v2.5.3 493 | [v2.5.2]: https://github.com/tighten/ziggy/compare/v2.5.1...v2.5.2 494 | [v2.5.1]: https://github.com/tighten/ziggy/compare/v2.5.0...v2.5.1 495 | [v2.5.0]: https://github.com/tighten/ziggy/compare/v2.4.2...v2.5.0 496 | [v2.4.2]: https://github.com/tighten/ziggy/compare/v2.4.1...v2.4.2 497 | [v2.4.1]: https://github.com/tighten/ziggy/compare/v2.4.0...v2.4.1 498 | [v2.4.0]: https://github.com/tighten/ziggy/compare/v2.3.1...v2.4.0 499 | [v2.3.1]: https://github.com/tighten/ziggy/compare/v2.3.0...v2.3.1 500 | [v2.3.0]: https://github.com/tighten/ziggy/compare/v2.2.1...v2.3.0 501 | [v2.2.1]: https://github.com/tighten/ziggy/compare/v2.2.0...v2.2.1 502 | [v2.2.0]: https://github.com/tighten/ziggy/compare/v2.1.0...v2.2.0 503 | [v2.1.0]: https://github.com/tighten/ziggy/compare/v2.0.5...v2.1.0 504 | [v2.0.5]: https://github.com/tighten/ziggy/compare/v2.0.4...v2.0.5 505 | [v2.0.4]: https://github.com/tighten/ziggy/compare/v2.0.3...v2.0.4 506 | [v2.0.3]: https://github.com/tighten/ziggy/compare/v2.0.2...v2.0.3 507 | [v2.0.2]: https://github.com/tighten/ziggy/compare/v2.0.1...v2.0.2 508 | [v2.0.1]: https://github.com/tighten/ziggy/compare/v2.0.0...v2.0.1 509 | [v2.0.0]: https://github.com/tighten/ziggy/compare/v1.8.2...v2.0.0 510 | [v1.8.2]: https://github.com/tighten/ziggy/compare/v1.8.1...v1.8.2 511 | [v1.8.1]: https://github.com/tighten/ziggy/compare/v1.8.0...v1.8.1 512 | [v1.8.0]: https://github.com/tighten/ziggy/compare/v1.7.0...v1.8.0 513 | [v1.7.0]: https://github.com/tighten/ziggy/compare/v1.6.2...v1.7.0 514 | [v1.6.2]: https://github.com/tighten/ziggy/compare/v1.6.1...v1.6.2 515 | [v1.6.1]: https://github.com/tighten/ziggy/compare/v1.6.0...v1.6.1 516 | [v1.6.0]: https://github.com/tighten/ziggy/compare/v1.5.2...v1.6.0 517 | [v1.5.2]: https://github.com/tighten/ziggy/compare/v1.5.1...v1.5.2 518 | [v1.5.1]: https://github.com/tighten/ziggy/compare/v1.5.0...v1.5.1 519 | [v1.5.0]: https://github.com/tighten/ziggy/compare/v1.4.6...v1.5.0 520 | [v1.4.6]: https://github.com/tighten/ziggy/compare/v1.4.5...v1.4.6 521 | [v1.4.5]: https://github.com/tighten/ziggy/compare/v1.4.4...v1.4.5 522 | [v1.4.4]: https://github.com/tighten/ziggy/compare/v1.4.3...v1.4.4 523 | [v1.4.3]: https://github.com/tighten/ziggy/compare/v1.4.2...v1.4.3 524 | [v1.4.2]: https://github.com/tighten/ziggy/compare/v1.4.1...v1.4.2 525 | [v1.4.1]: https://github.com/tighten/ziggy/compare/v1.4.0...v1.4.1 526 | [v1.4.0]: https://github.com/tighten/ziggy/compare/v1.3.6...v1.4.0 527 | [v1.3.6]: https://github.com/tighten/ziggy/compare/v1.3.5...v1.3.6 528 | [v1.3.5]: https://github.com/tighten/ziggy/compare/v1.3.4...v1.3.5 529 | [v1.3.4]: https://github.com/tighten/ziggy/compare/v1.3.3...v1.3.4 530 | [v1.3.3]: https://github.com/tighten/ziggy/compare/v1.3.2...v1.3.3 531 | [v1.3.2]: https://github.com/tighten/ziggy/compare/v1.3.1...v1.3.2 532 | [v1.3.1]: https://github.com/tighten/ziggy/compare/v1.3.0...v1.3.1 533 | [v1.3.0]: https://github.com/tighten/ziggy/compare/v1.2.0...v1.3.0 534 | [v1.2.0]: https://github.com/tighten/ziggy/compare/v1.1.0...v1.2.0 535 | [v1.1.0]: https://github.com/tighten/ziggy/compare/v1.0.5...v1.1.0 536 | [v1.0.5]: https://github.com/tighten/ziggy/compare/v1.0.4...v1.0.5 537 | [v1.0.4]: https://github.com/tighten/ziggy/compare/v1.0.3...v1.0.4 538 | [v1.0.3]: https://github.com/tighten/ziggy/compare/v1.0.2...v1.0.3 539 | [v1.0.2]: https://github.com/tighten/ziggy/compare/v1.0.1...v1.0.2 540 | [v1.0.1]: https://github.com/tighten/ziggy/compare/v1.0.0...v1.0.1 541 | [v1.0.0]: https://github.com/tighten/ziggy/compare/0.9.4...v1.0.0 542 | [v0.9.4]: https://github.com/tighten/ziggy/compare/0.9.3...0.9.4 543 | [v0.9.3]: https://github.com/tighten/ziggy/compare/v0.9.2...0.9.3 544 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Tighten Co. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Ziggy - Use your Laravel routes in JavaScript](https://raw.githubusercontent.com/tighten/ziggy/main/ziggy-banner.png) 2 | 3 | # Ziggy – Use your Laravel routes in JavaScript 4 | 5 | [![GitHub Actions Status](https://img.shields.io/github/actions/workflow/status/tighten/ziggy/test.yml?branch=main&style=flat)](https://github.com/tighten/ziggy/actions?query=workflow:Tests+branch:main) 6 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/tightenco/ziggy.svg?style=flat)](https://packagist.org/packages/tightenco/ziggy) 7 | [![Downloads on Packagist](https://img.shields.io/packagist/dt/tightenco/ziggy.svg?style=flat)](https://packagist.org/packages/tightenco/ziggy) 8 | [![Latest Version on NPM](https://img.shields.io/npm/v/ziggy-js.svg?style=flat)](https://npmjs.com/package/ziggy-js) 9 | [![Downloads on NPM](https://img.shields.io/npm/dt/ziggy-js.svg?style=flat)](https://npmjs.com/package/ziggy-js) 10 | 11 | Ziggy provides a JavaScript `route()` function that works like Laravel's, making it a breeze to use your named Laravel routes in JavaScript. 12 | 13 | - [**Installation**](#installation) 14 | - [**Usage**](#usage) 15 | - [`route()` function](#route-function) 16 | - [`Router` class](#router-class) 17 | - [Route-model binding](#route-model-binding) 18 | - [TypeScript](#typescript) 19 | - [**JavaScript frameworks**](#javascript-frameworks) 20 | - [Generating and importing Ziggy's configuration](#generating-and-importing-ziggys-configuration) 21 | - [Importing the `route()` function](#importing-the-route-function) 22 | - [Vue](#vue) 23 | - [React](#react) 24 | - [SPAs or separate repos](#spas-or-separate-repos) 25 | - [**Filtering Routes**](#filtering-routes) 26 | - [Including/excluding routes](#includingexcluding-routes) 27 | - [Filtering with groups](#filtering-with-groups) 28 | - [**Other**](#other) 29 | - [**Contributing**](#contributing) 30 | 31 | ## Installation 32 | 33 | Install Ziggy in your Laravel app with Composer: 34 | 35 | ```bash 36 | composer require tightenco/ziggy 37 | ``` 38 | 39 | Add the `@routes` Blade directive to your main layout (_before_ your application's JavaScript), and the `route()` helper function will be available globally! 40 | 41 | > By default, the output of the `@routes` Blade directive includes a list of all your application's routes and their parameters. This route list is included in the HTML of the page and can be viewed by end users. To configure which routes are included in this list, or to show and hide different routes on different pages, see [Filtering Routes](#filtering-routes). 42 | 43 | ## Usage 44 | 45 | ### `route()` function 46 | 47 | Ziggy's `route()` function works like [Laravel's `route()` helper](https://laravel.com/docs/helpers#method-route)—you can pass it the name of a route, and the parameters you want to pass to the route, and it will generate a URL. 48 | 49 | #### Basic usage 50 | 51 | ```php 52 | Route::get('posts', fn (Request $request) => /* ... */)->name('posts.index'); 53 | ``` 54 | 55 | ```js 56 | route('posts.index'); // 'https://ziggy.test/posts' 57 | ``` 58 | 59 | #### Parameters 60 | 61 | ```php 62 | Route::get('posts/{post}', fn (Post $post) => /* ... */)->name('posts.show'); 63 | ``` 64 | 65 | ```js 66 | route('posts.show', 1); // 'https://ziggy.test/posts/1' 67 | route('posts.show', [1]); // 'https://ziggy.test/posts/1' 68 | route('posts.show', { post: 1 }); // 'https://ziggy.test/posts/1' 69 | ``` 70 | 71 | #### Multiple parameters 72 | 73 | ```php 74 | Route::get('venues/{venue}/events/{event}', fn (Venue $venue, Event $event) => /* ... */) 75 | ->name('venues.events.show'); 76 | ``` 77 | 78 | ```js 79 | route('venues.events.show', [1, 2]); // 'https://ziggy.test/venues/1/events/2' 80 | route('venues.events.show', { venue: 1, event: 2 }); // 'https://ziggy.test/venues/1/events/2' 81 | ``` 82 | 83 | #### Query parameters 84 | 85 | Ziggy adds arguments that don't match any named route parameters as query parameters: 86 | 87 | ```php 88 | Route::get('venues/{venue}/events/{event}', fn (Venue $venue, Event $event) => /* ... */) 89 | ->name('venues.events.show'); 90 | ``` 91 | 92 | ```js 93 | route('venues.events.show', { 94 | venue: 1, 95 | event: 2, 96 | page: 5, 97 | count: 10, 98 | }); 99 | // 'https://ziggy.test/venues/1/events/2?page=5&count=10' 100 | ``` 101 | 102 | If you need to pass a query parameter with the same name as a route parameter, nest it under the special `_query` key: 103 | 104 | ```js 105 | route('venues.events.show', { 106 | venue: 1, 107 | event: 2, 108 | _query: { 109 | event: 3, 110 | page: 5, 111 | }, 112 | }); 113 | // 'https://ziggy.test/venues/1/events/2?event=3&page=5' 114 | ``` 115 | 116 | Like Laravel, Ziggy automatically encodes boolean query parameters as integers in the query string: 117 | 118 | ```js 119 | route('venues.events.show', { 120 | venue: 1, 121 | event: 2, 122 | _query: { 123 | draft: false, 124 | overdue: true, 125 | }, 126 | }); 127 | // 'https://ziggy.test/venues/1/events/2?draft=0&overdue=1' 128 | ``` 129 | 130 | #### Default parameter values 131 | 132 | Ziggy supports default route parameter values ([Laravel docs](https://laravel.com/docs/urls#default-values)). 133 | 134 | ```php 135 | Route::get('{locale}/posts/{post}', fn (Post $post) => /* ... */)->name('posts.show'); 136 | ``` 137 | 138 | ```php 139 | // app/Http/Middleware/SetLocale.php 140 | 141 | URL::defaults(['locale' => $request->user()->locale ?? 'de']); 142 | ``` 143 | 144 | ```js 145 | route('posts.show', 1); // 'https://ziggy.test/de/posts/1' 146 | ``` 147 | 148 | #### Examples 149 | 150 | HTTP request with `axios`: 151 | 152 | ```js 153 | const post = { id: 1, title: 'Ziggy Stardust' }; 154 | 155 | return axios.get(route('posts.show', post)).then((response) => response.data); 156 | ``` 157 | 158 | ### `Router` class 159 | 160 | Calling Ziggy's `route()` function with no arguments will return an instance of its JavaScript `Router` class, which has some other useful properties and methods. 161 | 162 | #### Check the current route: `route().current()` 163 | 164 | ```js 165 | // Laravel route called 'events.index' with URI '/events' 166 | // Current window URL is https://ziggy.test/events 167 | 168 | route().current(); // 'events.index' 169 | route().current('events.index'); // true 170 | route().current('events.*'); // true 171 | route().current('events.show'); // false 172 | ``` 173 | 174 | `route().current()` optionally accepts parameters as its second argument, and will check that their values also match in the current URL: 175 | 176 | ```js 177 | // Laravel route called 'venues.events.show' with URI '/venues/{venue}/events/{event}' 178 | // Current window URL is https://myapp.com/venues/1/events/2?hosts=all 179 | 180 | route().current('venues.events.show', { venue: 1 }); // true 181 | route().current('venues.events.show', { venue: 1, event: 2 }); // true 182 | route().current('venues.events.show', { hosts: 'all' }); // true 183 | route().current('venues.events.show', { venue: 6 }); // false 184 | ``` 185 | 186 | #### Check if a route exists: `route().has()` 187 | 188 | ```js 189 | // Laravel app has only one named route, 'home' 190 | 191 | route().has('home'); // true 192 | route().has('orders'); // false 193 | ``` 194 | 195 | #### Retrieve the current route params: `route().params` 196 | 197 | ```js 198 | // Laravel route called 'venues.events.show' with URI '/venues/{venue}/events/{event}' 199 | // Current window URL is https://myapp.com/venues/1/events/2?hosts=all 200 | 201 | route().params; // { venue: '1', event: '2', hosts: 'all' } 202 | ``` 203 | 204 | > Note: parameter values retrieved with `route().params` will always be returned as strings. 205 | 206 | ### Route-model binding 207 | 208 | Ziggy supports Laravel's [route-model binding](https://laravel.com/docs/routing#route-model-binding), and can even recognize custom route key names. If you pass `route()` a JavaScript object as a route parameter, Ziggy will use the registered route-model binding keys for that route to find the correct parameter value inside the object. If no route-model binding keys are explicitly registered for a parameter, Ziggy will use the object's `id` key. 209 | 210 | ```php 211 | // app/Models/Post.php 212 | 213 | class Post extends Model 214 | { 215 | public function getRouteKeyName() 216 | { 217 | return 'slug'; 218 | } 219 | } 220 | ``` 221 | 222 | ```php 223 | Route::get('blog/{post}', function (Post $post) { 224 | return view('posts.show', ['post' => $post]); 225 | })->name('posts.show'); 226 | ``` 227 | 228 | ```js 229 | const post = { 230 | id: 3, 231 | title: 'Introducing Ziggy v1', 232 | slug: 'introducing-ziggy-v1', 233 | date: '2020-10-23T20:59:24.359278Z', 234 | }; 235 | 236 | // Ziggy knows that this route uses the 'slug' route-model binding key: 237 | 238 | route('posts.show', post); // 'https://ziggy.test/blog/introducing-ziggy-v1' 239 | ``` 240 | 241 | Ziggy also supports [custom keys](https://laravel.com/docs/routing#customizing-the-key) for scoped bindings declared directly in a route definition: 242 | 243 | ```php 244 | Route::get('authors/{author}/photos/{photo:uuid}', fn (Author $author, Photo $photo) => /* ... */) 245 | ->name('authors.photos.show'); 246 | ``` 247 | 248 | ```js 249 | const photo = { 250 | uuid: '714b19e8-ac5e-4dab-99ba-34dc6fdd24a5', 251 | filename: 'sunset.jpg', 252 | } 253 | 254 | route('authors.photos.show', [{ id: 1, name: 'Ansel' }, photo]); 255 | // 'https://ziggy.test/authors/1/photos/714b19e8-ac5e-4dab-99ba-34dc6fdd24a5' 256 | ``` 257 | 258 | ### TypeScript 259 | 260 | Ziggy includes TypeScript type definitions, and an Artisan command that can generate additional type definitions to enable route name and parameter autocompletion. 261 | 262 | To generate route types, run the `ziggy:generate` command with the `--types` or `--types-only` option: 263 | 264 | ```bash 265 | php artisan ziggy:generate --types 266 | ``` 267 | 268 | To make your IDE aware that Ziggy's `route()` helper is available globally, and to type it correctly, add a declaration like this in a `.d.ts` file somewhere in your project: 269 | 270 | ```ts 271 | import { route as routeFn } from 'ziggy-js'; 272 | 273 | declare global { 274 | var route: typeof routeFn; 275 | } 276 | ``` 277 | 278 | If you don't have Ziggy's NPM package installed, add the following to your `jsconfig.json` or `tsconfig.json` to load Ziggy's types from your vendor directory: 279 | 280 | ```json 281 | { 282 | "compilerOptions": { 283 | "paths": { 284 | "ziggy-js": ["./vendor/tightenco/ziggy"] 285 | } 286 | } 287 | } 288 | ``` 289 | 290 | ## JavaScript frameworks 291 | 292 | > [!NOTE] 293 | > Many applications don't need the additional setup described here—the `@routes` Blade directive makes Ziggy's `route()` function and config available globally, including within bundled JavaScript files. 294 | 295 | If you are not using the `@routes` Blade directive, you can import Ziggy's `route()` function and configuration directly into JavaScript/TypeScript files. 296 | 297 | ### Generating and importing Ziggy's configuration 298 | 299 | Ziggy provides an Artisan command to output its config and routes to a file: 300 | 301 | ```bash 302 | php artisan ziggy:generate 303 | ``` 304 | 305 | This command places your configuration in `resources/js/ziggy.js` by default, but you can customize this path by passing an argument to the Artisan command or setting the `ziggy.output.path` config value. 306 | 307 | The file `ziggy:generate` creates looks something like this: 308 | 309 | ```js 310 | // resources/js/ziggy.js 311 | 312 | const Ziggy = { 313 | url: 'https://ziggy.test', 314 | port: null, 315 | routes: { 316 | home: { 317 | uri: '/', 318 | methods: [ 'GET', 'HEAD'], 319 | domain: null, 320 | }, 321 | login: { 322 | uri: 'login', 323 | methods: ['GET', 'HEAD'], 324 | domain: null, 325 | }, 326 | }, 327 | }; 328 | 329 | export { Ziggy }; 330 | ``` 331 | 332 | ### Importing the `route()` function 333 | 334 | You can import Ziggy like any other JavaScript library. Without the `@routes` Blade directive Ziggy's config is not available globally, so it must be passed to the `route()` function manually: 335 | 336 | ```js 337 | import { route } from '../../vendor/tightenco/ziggy'; 338 | import { Ziggy } from './ziggy.js'; 339 | 340 | route('home', undefined, undefined, Ziggy); 341 | ``` 342 | 343 | To simplify importing the `route()` function, you can create an alias to the vendor path: 344 | 345 | ```js 346 | // vite.config.js 347 | 348 | export default defineConfig({ 349 | resolve: { 350 | alias: { 351 | 'ziggy-js': path.resolve('vendor/tightenco/ziggy'), 352 | }, 353 | }, 354 | }); 355 | ``` 356 | 357 | Now your imports can be shortened to: 358 | 359 | ```js 360 | import { route } from 'ziggy-js'; 361 | ``` 362 | 363 | ### Vue 364 | 365 | Ziggy includes a Vue plugin to make it easy to use the `route()` helper throughout your Vue app: 366 | 367 | ```js 368 | import { createApp } from 'vue'; 369 | import { ZiggyVue } from 'ziggy-js'; 370 | import App from './App.vue'; 371 | 372 | createApp(App).use(ZiggyVue); 373 | ``` 374 | 375 | Now you can use the `route()` function anywhere in your Vue components and templates: 376 | 377 | ```vue 378 | Home 379 | ``` 380 | 381 | With ` 389 | ``` 390 | 391 | If you are not using the `@routes` Blade directive, import Ziggy's configuration too and pass it to `.use()`: 392 | 393 | ```js 394 | import { createApp } from 'vue'; 395 | import { ZiggyVue } from 'ziggy-js'; 396 | import { Ziggy } from './ziggy.js'; 397 | import App from './App.vue'; 398 | 399 | createApp(App).use(ZiggyVue, Ziggy); 400 | ``` 401 | 402 | If you're using TypeScript, you may need to add the following declaration to a `.d.ts` file in your project to avoid type errors when using the `route()` function in your Vue component templates: 403 | 404 | ```ts 405 | declare module 'vue' { 406 | interface ComponentCustomProperties { 407 | route: typeof routeFn; 408 | } 409 | } 410 | ``` 411 | 412 | ### React 413 | 414 | Ziggy includes a `useRoute()` hook to make it easy to use the `route()` helper in your React app: 415 | 416 | ```jsx 417 | import React from 'react'; 418 | import { useRoute } from 'ziggy-js'; 419 | 420 | export default function PostsLink() { 421 | const route = useRoute(); 422 | 423 | return Posts; 424 | } 425 | ``` 426 | 427 | If you are not using the `@routes` Blade directive, import Ziggy's configuration too and pass it to `useRoute()`: 428 | 429 | ```jsx 430 | import React from 'react'; 431 | import { useRoute } from 'ziggy-js'; 432 | import { Ziggy } from './ziggy.js'; 433 | 434 | export default function PostsLink() { 435 | const route = useRoute(Ziggy); 436 | 437 | return Posts; 438 | } 439 | ``` 440 | 441 | You can also make the `Ziggy` config object available globally, so you can call `useRoute()` without passing Ziggy's configuration to it every time: 442 | 443 | ```js 444 | // app.js 445 | import { Ziggy } from './ziggy.js'; 446 | globalThis.Ziggy = Ziggy; 447 | ``` 448 | 449 | ### SPAs or separate repos 450 | 451 | Ziggy's `route()` function is available as an NPM package, for use in JavaScript projects managed separately from their Laravel backend (i.e. without Composer or a `vendor` directory). You can install the NPM package with `npm install ziggy-js`. 452 | 453 | To make your routes available on the frontend for this function to use, you can either run `php artisan ziggy:generate` and add the generated config file to your frontend project, or you can return Ziggy's config as JSON from an endpoint in your Laravel API (see [Retrieving Ziggy's config from an API endpoint](#retrieving-ziggys-config-from-an-api-endpoint) below for an example of how to set this up). 454 | 455 | ## Filtering Routes 456 | 457 | Ziggy supports filtering the list of routes it outputs, which is useful if you have certain routes that you don't want to be included and visible in your HTML source. 458 | 459 | > [!IMPORTANT] 460 | > Hiding routes from Ziggy's output is not a replacement for thorough authentication and authorization. Routes that should not be accessibly publicly should be protected by authentication whether they're filtered out of Ziggy's output or not. 461 | 462 | ### Including/excluding routes 463 | 464 | To set up route filtering, create a config file in your Laravel app at `config/ziggy.php` and add **either** an `only` or `except` key containing an array of route name patterns. 465 | 466 | > Note: You have to choose one or the other. Setting both `only` and `except` will disable filtering altogether and return all named routes. 467 | 468 | ```php 469 | // config/ziggy.php 470 | 471 | return [ 472 | 'only' => ['home', 'posts.index', 'posts.show'], 473 | ]; 474 | ``` 475 | 476 | You can use asterisks as wildcards in route filters. In the example below, `admin.*` will exclude routes named `admin.login`, `admin.register`, etc.: 477 | 478 | ```php 479 | // config/ziggy.php 480 | 481 | return [ 482 | 'except' => ['_debugbar.*', 'horizon.*', 'admin.*'], 483 | ]; 484 | ``` 485 | 486 | ### Filtering with groups 487 | 488 | You can also define groups of routes that you want make available in different places in your app, using a `groups` key in your config file: 489 | 490 | ```php 491 | // config/ziggy.php 492 | 493 | return [ 494 | 'groups' => [ 495 | 'admin' => ['admin.*', 'users.*'], 496 | 'author' => ['posts.*'], 497 | ], 498 | ]; 499 | ``` 500 | 501 | Then, you can expose a specific group by passing the group name into the `@routes` Blade directive: 502 | 503 | ```blade 504 | {{-- authors.blade.php --}} 505 | 506 | @routes('author') 507 | ``` 508 | 509 | To expose multiple groups you can pass an array of group names: 510 | 511 | ```blade 512 | {{-- admin.blade.php --}} 513 | 514 | @routes(['admin', 'author']) 515 | ``` 516 | 517 | > Note: Passing group names to the `@routes` directive will always take precedence over your other `only` or `except` settings. 518 | 519 | ## Other 520 | 521 | ### TLS/SSL termination and trusted proxies 522 | 523 | 524 | 525 | If your application is using [TLS/SSL termination](https://en.wikipedia.org/wiki/TLS_termination_proxy) or is behind a load balancer or proxy, or if it's hosted on a service that is, Ziggy may generate URLs with a scheme of `http` instead of `https`, even if your app URL uses `https`. To fix this, set up your Laravel app's trusted proxies according to the documentation on [Configuring Trusted Proxies](https://laravel.com/docs/requests#configuring-trusted-proxies). 526 | 527 | ### Using `@routes` with a Content Security Policy 528 | 529 | A [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) (CSP) may block inline scripts, including those output by Ziggy's `@routes` Blade directive. If you have a CSP and are using a nonce to flag safe inline scripts, you can pass the nonce to the `@routes` directive and it will be added to Ziggy's script tag: 530 | 531 | ```php 532 | @routes(nonce: 'your-nonce-here') 533 | ``` 534 | 535 | ### Disabling the `route()` helper 536 | 537 | If you only want to use the `@routes` directive to make Ziggy's configuration available in JavaScript, but don't need the `route()` helper function, set the `ziggy.skip-route-function` config to `true`. 538 | 539 | ### Retrieving Ziggy's config from an API endpoint 540 | 541 | If you need to retrieve Ziggy's config from your Laravel backend over the network, you can create a route that looks something like this: 542 | 543 | ```php 544 | // routes/api.php 545 | 546 | use Tighten\Ziggy\Ziggy; 547 | 548 | Route::get('ziggy', fn () => response()->json(new Ziggy)); 549 | ``` 550 | 551 | ### Re-generating the routes file when your app routes change 552 | 553 | If you are generating your Ziggy config as a file by running `php artisan ziggy:generate`, you may want to re-run that command when your app's route files change. The example below is a Laravel Mix plugin, but similar functionality could be achieved without Mix. Huge thanks to [Nuno Rodrigues](https://github.com/nacr) for [the idea and a sample implementation](https://github.com/tighten/ziggy/issues/321#issuecomment-689150082). See [#655](https://github.com/tighten/ziggy/pull/655/files#diff-4aeb78f813e14842fcf95bdace9ced23b8a6eed60b23c165eaa52e8db2f97b61) or [vite-plugin-ziggy](https://github.com/aniftyco/vite-plugin-ziggy) for Vite examples. 554 | 555 |
556 | Laravel Mix plugin example 557 |

558 | 559 | ```js 560 | const mix = require('laravel-mix'); 561 | const { exec } = require('child_process'); 562 | 563 | mix.extend('ziggy', new class { 564 | register(config = {}) { 565 | this.watch = config.watch ?? ['routes/**/*.php']; 566 | this.path = config.path ?? ''; 567 | this.enabled = config.enabled ?? !Mix.inProduction(); 568 | } 569 | 570 | boot() { 571 | if (!this.enabled) return; 572 | 573 | const command = () => exec( 574 | `php artisan ziggy:generate ${this.path}`, 575 | (error, stdout, stderr) => console.log(stdout) 576 | ); 577 | 578 | command(); 579 | 580 | if (Mix.isWatching() && this.watch) { 581 | ((require('chokidar')).watch(this.watch)) 582 | .on('change', (path) => { 583 | console.log(`${path} changed...`); 584 | command(); 585 | }); 586 | }; 587 | } 588 | }()); 589 | 590 | mix.js('resources/js/app.js', 'public/js') 591 | .postCss('resources/css/app.css', 'public/css', []) 592 | .ziggy(); 593 | ``` 594 |
595 | 596 | ## Contributing 597 | 598 | To get started contributing to Ziggy, check out [the contribution guide](CONTRIBUTING.md). 599 | 600 | ## Credits 601 | 602 | - [Daniel Coulbourne](https://twitter.com/DCoulbourne) 603 | - [Jake Bathman](https://twitter.com/jakebathman) 604 | - [Matt Stauffer](https://twitter.com/stauffermatt) 605 | - [Jacob Baker-Kretzmar](https://twitter.com/bakerkretzmar) 606 | - [All contributors](https://github.com/tighten/ziggy/contributors) 607 | 608 | Thanks to [Caleb Porzio](http://twitter.com/calebporzio), [Adam Wathan](http://twitter.com/adamwathan), and [Jeffrey Way](http://twitter.com/jeffrey_way) for help solidifying the idea. 609 | 610 | ## Security 611 | 612 | Please review our [security policy](../../security/policy) on how to report security vulnerabilities. 613 | 614 | ## License 615 | 616 | Ziggy is open-source software released under the MIT license. See [LICENSE](LICENSE) for more information. 617 | -------------------------------------------------------------------------------- /UPGRADING.md: -------------------------------------------------------------------------------- 1 | # Upgrade Guide 2 | 3 | ## Upgrading from `1.x` to `2.x` 4 | 5 | - The PHP package namespace has changed from `Tightenco\Ziggy` to `Tighten\Ziggy` (note: the Composer package name, `tightenco/ziggy`, has not changed). 6 | - The `makeDirectory` method of the `CommandRouteGenerator` class is now private, overriding it is no longer supported. 7 | - The deprecated JavaScript `check()` method (e.g. `route().check('home')`) has been removed. Use `has()` instead. 8 | - Ziggy's JavaScript now provides named exports only, with no default export. Replace `import route from 'ziggy-js'` with `import { route } from 'ziggy-js'`. 9 | - Ziggy's Vue plugin and React hook have moved to the root of the module. Replace imports from `ziggy-js/vue` or `ziggy-js/react` with imports directly from `ziggy-js` (e.g. `import { route, ZiggyVue } from 'ziggy-js'`). 10 | - Ziggy now only includes ES Module builds. The default build, which [supports all modern browsers](https://github.com/developit/microbundle/?tab=readme-ov-file#-modern-mode-), is `./dist/index.js` (this is the default when you import from `ziggy-js` or `vendor/tightenco/ziggy`). A legacy ES Module build using fewer new language features is included too, at `./dist/index.esm.js`. The third build, `./dist/route.umd.js`, is for internal use in Ziggy's `@routes` Blade directive. 11 | - Ziggy now requires at least Laravel 9 and PHP 8.1. 12 | 13 | ## Upgrading from `0.9.x` to `1.x` 14 | 15 | Ziggy `1.0` includes significant improvements and changes, most of which won't require any changes to existing code! 16 | 17 | **TL;DR** – If all you're doing is dropping the `@routes` Blade directive into a view somewhere and using the Javascript `route()` helper function later, you only really need to worry about one thing: 18 | 19 | - `route()` _with any arguments_ returns a string now, so: 20 | - Anywhere you're calling `.url()` to get a literal string, remove it. 21 | - Anywhere you're passing route paramaters using `.with()`, pass them as the second argument to `route()` instead. 22 | - Anywhere you're passing query paramaters using `.withQuery()`, pass them along with your route parameters in the second argument to `route()`. (If any of their names collide with your route parameters, nest your query parameters under a `_query` key.) 23 | 24 | ### Overview 25 | 26 | - **New features** 27 | - [Added full route-model binding support](#user-content-route-model-binding) 28 | - [Added support for checking parameters with `current()`](#user-content-params-current) 29 | - **High-impact changes** 30 | - [`route(...)` now returns a string](#user-content-route-string) 31 | - [`url()` method removed](#user-content-url-removed) 32 | - **Medium-impact changes** 33 | - [Default `ziggy:generate` path changed](#user-content-generate-path-changed) 34 | - [`whitelist` and `blacklist` renamed](#user-content-whitelist-blacklist-renamed) 35 | - [Boolean query parameters are encoded as integers](#user-content-booleans-integers) 36 | - **Low-impact changes** 37 | - [`with()` and `withQuery()` methods removed](#user-content-with-withquery-removed) 38 | - [`Route` Facade macros removed](#user-content-macros-removed) 39 | - [`RoutePayload` renamed to `Ziggy`](#user-content-route-payload-renamed) 40 | - [`getRoutePayload()` method removed](#user-content-getroutepayload-removed) 41 | - [`UrlBuilder` class removed](#user-content-urlbuilder-removed) 42 | - [`baseProtocol` and `baseDomain` properties removed](#user-content-base-protocol-domain-removed) 43 | - [`base` and other prefixes removed](#user-content-prefixes-removed) 44 | - [`filter()` method made fluent](#user-content-filter-fluent) 45 | - [Unused PHP methods removed](#user-content-unused-php-removed) 46 | - [Internal PHP methods made private](#user-content-internal-methods-private) 47 | - [Undocumented Javascript methods removed](#user-content-undocumented-methods-removed) 48 | - [Javascript build asset renamed to `index.js`](#user-content-export-index) 49 | - [`check()` method deprecated](#user-content-check-deprecated) 50 | 51 | ### New features 52 | 53 | 1. **Ziggy now fully supports Laravel's route-model binding functionality.** 54 | 55 | Previously, Ziggy could accept an object as a parameter and use its `id` key as the actual parameter value in the URL, allowing you to pass, for example, a Javascript object representing an instance of one of your Laravel models, directly into the `route()` function. 56 | 57 | This feature has been fleshed out to more fully support route-model binding in two key ways: 58 | - Ziggy now fully supports [custom scoped route-model binding](https://laravel.com/docs/8.x/routing#implicit-binding) defined in route definitions, e.g. `/users/{user}/posts/{post:uuid}`. 59 | - Ziggy now supports [implicit route-model binding](https://laravel.com/docs/8.x/routing#implicit-binding) defined by type-hinting controller methods and closures. 60 | 61 | For example, take the following model and route: 62 | 63 | ```php 64 | class Post extends Model 65 | { 66 | public function getRouteKeyName() 67 | { 68 | return 'slug'; 69 | } 70 | } 71 | ``` 72 | 73 | ```php 74 | Route::post('posts/{post}', function (Post $post) { 75 | return view('posts.show', ['post' => $post]); 76 | })->name('posts.update'); 77 | ``` 78 | 79 | In Ziggy v1, you can pass an object with a `slug` key into the `route()` helper, and the slug will be used as the route parameter value: 80 | 81 | ```js 82 | const post = { id: 15, slug: 'announcing-ziggy-v1', author: 'Jacob', published: false }; 83 | 84 | route('posts.update', post); // 'https://ziggy.test/posts/announcing-ziggy-v1' 85 | ``` 86 | 87 | See [#307](https://github.com/tighten/ziggy/pull/307) and [#315](https://github.com/tighten/ziggy/pull/315) 88 | 89 | 1. **Ziggy now supports matching parameters using `current()`.** 90 | 91 | Ziggy's `current()` method, which can be passed a route name to check if the browser is currently 'on' that route, can now be passed an object of parameters as the second argument, and will return whether those parameter values match in the current URL. 92 | 93 | This addition makes the following checks possible: 94 | 95 | ```js 96 | // Route called 'events.venues.show', with URI '/events/{event}/venues/{venue}' 97 | // Window URL is https://myapp.com/events/1/venues/2?authors=all 98 | 99 | // Before (unchanged) 100 | route().current(); // 'events.venues.show' 101 | route().current('events.venues.show'); // true 102 | 103 | // New in Ziggy v1 104 | route().current('events.venues.show', { event: 1, venue: 2 }); // true 105 | route().current('events.venues.show', { authors: 'all' }); // true 106 | route().current('events.venues.show', { venue: 6 }); // false 107 | ``` 108 | 109 | See [#314](https://github.com/tighten/ziggy/pull/314) and [#330](https://github.com/tighten/ziggy/pull/330) 110 | 111 | ### High impact changes 112 | 113 | 1. **The `route()` function now returns a literal string if it is passed any arguments.** 114 | 115 | If you are chaining methods onto a `route(...)` call _with arguments_, such as `route('posts.show').url()` or `route('home').withQuery(...)`, remove the chained methods. In the case of `route(...).url()` you can just remove `.url()` and nothing will change, for other methods see below. 116 | 117 | Calls specifically to `route()`, with no arguments, are not affected and will still return an instance of the `Router` class, so things like `route().current()` and `route().params` still work as expected. 118 | 119 | See [#336](https://github.com/tighten/ziggy/pull/336) 120 | 121 | 1. **The `url()` method on the `Router` class was removed** and can safely be deleted from projects that used it. 122 | 123 | Because of the above change to `route(...)`, the `url()` method is no longer necessary. You can safely remove it, e.g. by finding and replacing instances of `'.url()'` in your project with `''` (nothing). 124 | 125 | See [#336](https://github.com/tighten/ziggy/pull/336) 126 | 127 | ### Medium impact changes 128 | 129 | 1. **The default `ziggy:generate` path has changed to `resources/js/ziggy.js`**, Laravel's default javascript asset location. 130 | 131 |
132 | Details 133 |

134 | 135 | The default output path of the `ziggy:generate` command has changed from `resources/assets/js/ziggy.js` to `resources/js/ziggy.js` to bring it in line with the changes to the `resources` directory structure introduced in Laravel 5.7. 136 | 137 | See [#269](https://github.com/tighten/ziggy/pull/269) 138 |
139 | 140 | 1. **The `whitelist` and `blacklist` features were renamed** to `only` and `except`. 141 | 142 |
143 | Details 144 |

145 | 146 | All `whitelist` and `blacklist` functionality, like the config keys and methods, was renamed to `only` and `except`. 147 | 148 | See [#300](https://github.com/tighten/ziggy/pull/300) 149 |
150 | 151 | 1. **Boolean query parameters are now encoded as integers.** 152 | 153 |
154 | Details 155 |

156 | 157 | Ziggy's `route()` function will now encode boolean query parameters as integers (`0`/`1`) instead of strings (`'true'`/`'false'`). 158 | 159 | See [#345](https://github.com/tighten/ziggy/pull/345) 160 |
161 | 162 | ### Low impact changes 163 | 164 | 1. **The `with()` and `withQuery()` methods were removed.** 165 | 166 |
167 | Details 168 |

169 | 170 | The `with()` and `withQuery()` methods on the `Router` class (the object returned by the `route()` function if it is passed no arguments) are deprecated. Instead of `with()`, pass parameters as the second argument to `route()`. Instead of `withQuery()`, you can pass query parameters in the same object with regular parameters, as the second argument to `route()`. If you have query parameters and named parameters with the same name, use the new special `_query` key. 171 | 172 | See [#330](https://github.com/tighten/ziggy/pull/330) and [#336](https://github.com/tighten/ziggy/pull/336) 173 |
174 | 175 | 1. **The `Route` Facade macros, `Route::whitelist()` and `Route::blacklist()`, were removed.** 176 | 177 |
178 | Details 179 |

180 | 181 | The `Route` Facade macros, `Route::only()` and `Route::except()` (previously `Route::whitelist()` and `Route::blacklist()`) were removed. Instead of using these macros in your route files, set the routes to include/exclude in `config/ziggy.php`. 182 | 183 | See [#306](https://github.com/tighten/ziggy/pull/306) 184 |
185 | 186 | 1. **The `RoutePayload` class was renamed to `Ziggy`** and refactored. 187 | 188 |
189 | Details 190 |

191 | 192 | The PHP `RoutePayload` class was renamed to `Ziggy` and its `->compile()` method was removed in favor of constructing a new instance and calling `->toArray()` or `->toJson()`. Also: 193 | 194 | - The application router instance is now resolved internally instead of being passed into the constructor, so `new Ziggy(...)` now takes only 2 arguments, `$group` and `$url` 195 | - The default value of `$basePort` was changed from `false` to `null` 196 | 197 |

198 | 199 | See [#305](https://github.com/tighten/ziggy/pull/305) 200 |
201 | 202 | 1. **The `getRoutePayload()` method was removed.** 203 | 204 |
205 | Details 206 |

207 | 208 | The `getRoutePayload()` method on the PHP `BladeRouteGenerator` and `CommandRouteGenerator` classes was removed. 209 | 210 | See [#305](https://github.com/tighten/ziggy/pull/305) 211 |
212 | 213 | 1. **The `UrlBuilder` class was removed.** 214 | 215 |
216 | Details 217 |

218 | 219 | The Javascript `UrlBuilder` class was removed. Refer to the `template()` getter on the new `Route` class if you need to re-implement this functionality yourself. 220 | 221 | See [#330](https://github.com/tighten/ziggy/pull/330) 222 |
223 | 224 | 1. **The `baseProtocol` and `baseDomain` properties were removed** from Ziggy's global configuration object. 225 | 226 |
227 | Details 228 |

229 | 230 | The `baseProtocol` and `baseDomain` keys were removed from Ziggy's config. Both these values were inferred from the `baseUrl` property, which is set to your app URL. Refer to the `template()` getter on the new `Route` class if you need to re-implement this functionality yourself. 231 | 232 | See [#337](https://github.com/tighten/ziggy/pull/337) 233 |
234 | 235 | 1. **`base` and other prefixes were removed** from config keys. 236 | 237 |
238 | Details 239 |

240 | 241 | The `namedRoutes`, `defaultParameters`, `baseUrl`, and `basePort` configuration properties were renamed to `routes`, `defaults`, `url`, and `port`. 242 | 243 | See [#338](https://github.com/tighten/ziggy/pull/338) 244 |
245 | 246 | 1. **The `filter()` method on the `Ziggy` class is now 'fluent'** and returns an instance of `Ziggy`. 247 | 248 |
249 | Details 250 |

251 | 252 | The `filter()` method on the `Ziggy` class now returns an instance of `Ziggy` instead of a collection of routes. 253 | 254 | See [#341](https://github.com/tighten/ziggy/pull/341) 255 |
256 | 257 | 1. **Unused PHP methods were removed.** 258 | 259 |
260 | Details 261 |

262 | 263 | The unused `appendRouteToList()` and `isListedAs()` methods, and the redundant/unnecessary `except()` and `only()` methods on the `Ziggy` class, were removed. 264 | 265 | See [#341](https://github.com/tighten/ziggy/pull/341) 266 |
267 | 268 | 1. **Some internal methods on Ziggy's PHP classes were made private.** 269 | 270 |
271 | Details 272 |

273 | 274 | The `nameKeyedRoutes()`, `resolveBindings()`, `applyFilters()`, and `group()` methods on the `Ziggy` class, and the `generate()` method on the `CommandRouteGenerator` class, are now private. 275 | 276 | See [#341](https://github.com/tighten/ziggy/pull/341) 277 |
278 | 279 | 1. **Several undocumented methods and properties were removed** from the Javascript `Router` class. 280 | 281 |
282 | Details 283 |

284 | 285 | Several undocumented methods and properties on the `Router` class (the object returned by the `route()` function when it's called with no arguments) were removed. Replace them with the suggestions below or refer to Ziggy's internals if you need to re-implement the functionality yourself. 286 | 287 | Removed properties: 288 | 289 | - `name`: use the name you were passing into `route()` as the first argument. 290 | - `absolute`: use the value you were passing into `route()` as the third argument. 291 | - `ziggy`: use the global `Ziggy` configuraton object. 292 | - `urlBuilder`: refer to the `template()` getter on the new `Route` class. 293 | - `template`: refer to the `template()` getter on the new `Route` class. 294 | - `urlParams`: use the value you were passing into `route()` as the second argument. 295 | - `queryParams`: use the value you were passing into `withQuery()`, or into `route()` as the second argument. 296 | - `hydrated`: use the returned URL string. 297 | 298 |

299 | 300 | Removed methods: 301 | 302 | - `normalizeParams()`: refer to the internal `_parse()` method. 303 | - `hydrateUrl()`: use the returned URL string. 304 | - `matchUrl()`: use `current()` or refer to the `current()` method on the new `Route` class. 305 | - `constructQuery()`: use the returned URL string. 306 | - `extractParams()`: refer to the `_dehydrate()` method on the `Router` class. 307 | - `parse()`: use the returned URL string. 308 | - `trimParam()`: use `.replace(/{|\??}/g, '')`. 309 | 310 |

311 | 312 | See [#330](https://github.com/tighten/ziggy/pull/330) 313 |
314 | 315 | 1. **Ziggy's main build asset/entrypoint is now called `index.js` instead of `route.js`.** 316 | 317 |
318 | Details 319 |

320 | 321 | Ziggy's main Javascript source and dist files are now called `index.js` instead of `route.js`. 322 | 323 | See [#344](https://github.com/tighten/ziggy/pull/344) 324 |
325 | 326 | 1. **The `check()` method is deprecated.** 327 | 328 |
329 | Details 330 |

331 | 332 | The `route().check()` method is deprecated and will be removed in a future major version of Ziggy. Use `route().has()` instead. 333 | 334 | See [#330](https://github.com/tighten/ziggy/pull/330) 335 |
336 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tightenco/ziggy", 3 | "description": "Use your Laravel named routes in JavaScript.", 4 | "keywords": [ 5 | "laravel", 6 | "routes", 7 | "javascript", 8 | "ziggy" 9 | ], 10 | "homepage": "https://github.com/tighten/ziggy", 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "Daniel Coulbourne", 15 | "email": "daniel@tighten.co" 16 | }, 17 | { 18 | "name": "Jake Bathman", 19 | "email": "jake@tighten.co" 20 | }, 21 | { 22 | "name": "Jacob Baker-Kretzmar", 23 | "email": "jacob@tighten.co" 24 | } 25 | ], 26 | "require": { 27 | "php": ">=8.1", 28 | "ext-json": "*", 29 | "laravel/framework": ">=9.0" 30 | }, 31 | "require-dev": { 32 | "laravel/folio": "^1.1", 33 | "orchestra/testbench": "^7.0 || ^8.0 || ^9.0 || ^10.0", 34 | "pestphp/pest": "^2.26|^3.0", 35 | "pestphp/pest-plugin-laravel": "^2.4|^3.0" 36 | }, 37 | "autoload": { 38 | "psr-4": { 39 | "Tighten\\Ziggy\\": "src/" 40 | } 41 | }, 42 | "autoload-dev": { 43 | "psr-4": { 44 | "Tests\\": "tests/" 45 | } 46 | }, 47 | "extra": { 48 | "laravel": { 49 | "providers": [ 50 | "Tighten\\Ziggy\\ZiggyServiceProvider" 51 | ] 52 | } 53 | }, 54 | "config": { 55 | "optimize-autoloader": true, 56 | "sort-packages": true, 57 | "allow-plugins": { 58 | "kylekatarnls/update-helper": true, 59 | "pestphp/pest-plugin": true 60 | } 61 | }, 62 | "minimum-stability": "dev", 63 | "prefer-stable": true 64 | } 65 | -------------------------------------------------------------------------------- /dist/index.esm.js: -------------------------------------------------------------------------------- 1 | function r(r,t){for(var n=0;n1;){var t=r.pop(),n=t.obj[t.prop];if(p(n)){for(var e=[],o=0;o=48&&a<=57||a>=65&&a<=90||a>=97&&a<=122||o===s.RFC1738&&(40===a||41===a)?u+=i.charAt(f):a<128?u+=y[a]:a<2048?u+=y[192|a>>6]+y[128|63&a]:a<55296||a>=57344?u+=y[224|a>>12]+y[128|a>>6&63]+y[128|63&a]:(a=65536+((1023&a)<<10|1023&i.charCodeAt(f+=1)),u+=y[240|a>>18]+y[128|a>>12&63]+y[128|a>>6&63]+y[128|63&a])}return u},isBuffer:function(r){return!(!r||"object"!=typeof r||!(r.constructor&&r.constructor.isBuffer&&r.constructor.isBuffer(r)))},isRegExp:function(r){return"[object RegExp]"===Object.prototype.toString.call(r)},maybeMap:function(r,t){if(p(r)){for(var n=[],e=0;e0?h.join(",")||null:void 0}];else if(m(f))k=f;else{var $=Object.keys(h);k=a?$.sort(a):$}for(var x=0;x-1?r.split(","):r},A=function(r,t,n,e){if(r){var o=n.allowDots?r.replace(/\.([^.[]+)/g,"[$1]"):r,i=/(\[[^[\]]*])/g,u=n.depth>0&&/(\[[^[\]]*])/.exec(o),f=u?o.slice(0,u.index):o,a=[];if(f){if(!n.plainObjects&&T.call(Object.prototype,f)&&!n.allowPrototypes)return;a.push(f)}for(var c=0;n.depth>0&&null!==(u=i.exec(o))&&c=0;--i){var u,f=r[i];if("[]"===f&&n.parseArrays)u=[].concat(o);else{u=n.plainObjects?Object.create(null):{};var a="["===f.charAt(0)&&"]"===f.charAt(f.length-1)?f.slice(1,-1):f,c=parseInt(a,10);n.parseArrays||""!==a?!isNaN(c)&&f!==a&&String(c)===a&&c>=0&&n.parseArrays&&c<=n.arrayLimit?(u=[])[c]=o:"__proto__"!==a&&(u[a]=o):u={0:o}}o=u}return o}(a,t,n,e)}},D=function(r,t){var n=function(r){if(!r)return x;if(null!=r.decoder&&"function"!=typeof r.decoder)throw new TypeError("Decoder has to be a function.");if(void 0!==r.charset&&"utf-8"!==r.charset&&"iso-8859-1"!==r.charset)throw new TypeError("The charset option must be either utf-8, iso-8859-1, or undefined");return{allowDots:void 0===r.allowDots?x.allowDots:!!r.allowDots,allowPrototypes:"boolean"==typeof r.allowPrototypes?r.allowPrototypes:x.allowPrototypes,arrayLimit:"number"==typeof r.arrayLimit?r.arrayLimit:x.arrayLimit,charset:void 0===r.charset?x.charset:r.charset,charsetSentinel:"boolean"==typeof r.charsetSentinel?r.charsetSentinel:x.charsetSentinel,comma:"boolean"==typeof r.comma?r.comma:x.comma,decoder:"function"==typeof r.decoder?r.decoder:x.decoder,delimiter:"string"==typeof r.delimiter||b.isRegExp(r.delimiter)?r.delimiter:x.delimiter,depth:"number"==typeof r.depth||!1===r.depth?+r.depth:x.depth,ignoreQueryPrefix:!0===r.ignoreQueryPrefix,interpretNumericEntities:"boolean"==typeof r.interpretNumericEntities?r.interpretNumericEntities:x.interpretNumericEntities,parameterLimit:"number"==typeof r.parameterLimit?r.parameterLimit:x.parameterLimit,parseArrays:!1!==r.parseArrays,plainObjects:"boolean"==typeof r.plainObjects?r.plainObjects:x.plainObjects,strictNullHandling:"boolean"==typeof r.strictNullHandling?r.strictNullHandling:x.strictNullHandling}}(t);if(""===r||null==r)return n.plainObjects?Object.create(null):{};for(var e="string"==typeof r?function(r,t){var n,e={},o=(t.ignoreQueryPrefix?r.replace(/^\?/,""):r).split(t.delimiter,Infinity===t.parameterLimit?void 0:t.parameterLimit),i=-1,u=t.charset;if(t.charsetSentinel)for(n=0;n-1&&(a=$(a)?[a]:a),e[f]=T.call(e,f)?b.combine(e[f],a):a}return e}(r,n):r,o=n.plainObjects?Object.create(null):{},i=Object.keys(e),u=0;u"+((null==(i=n.wheres[e])?void 0:i.replace(/(^\^)|(\$$)/g,""))||"[^/?]+")+")";return o?"("+t+u+")?":""+t+u}).replace(/^\w+:\/\//,""),o=r.replace(/^\w+:\/\//,"").split("?"),i=o[0],u=o[1],f=null!=(t=new RegExp("^"+e+"/?$").exec(i))?t:new RegExp("^"+e+"/?$").exec(decodeURI(i));if(f){for(var a in f.groups)f.groups[a]="string"==typeof f.groups[a]?decodeURIComponent(f.groups[a]):f.groups[a];return{params:f.groups,query:D(u)}}return!1},n.compile=function(r){var t=this;return this.parameterSegments.length?this.template.replace(/{([^}?]+)(\??)}/g,function(n,e,o){var i,u;if(!o&&[null,void 0].includes(r[e]))throw new Error("Ziggy error: '"+e+"' parameter is required for route '"+t.name+"'.");if(t.wheres[e]&&!new RegExp("^"+(o?"("+t.wheres[e]+")?":t.wheres[e])+"$").test(null!=(u=r[e])?u:""))throw new Error("Ziggy error: '"+e+"' parameter '"+r[e]+"' does not match required format '"+t.wheres[e]+"' for route '"+t.name+"'.");return encodeURI(null!=(i=r[e])?i:"").replace(/%7C/g,"|").replace(/%25/g,"%").replace(/\$/g,"%24")}).replace(this.config.absolute?/(\.[^/]+?)(\/\/)/:/(^)(\/\/)/,"$1/").replace(/\/+$/,""):this.template},t(r,[{key:"template",get:function(){var r=(this.origin+"/"+this.definition.uri).replace(/\/+$/,"");return""===r?"/":r}},{key:"origin",get:function(){return this.config.absolute?this.definition.domain?""+this.config.url.match(/^\w+:\/\//)[0]+this.definition.domain+(this.config.port?":"+this.config.port:""):this.config.url:""}},{key:"parameterSegments",get:function(){var r,t;return null!=(r=null==(t=this.template.match(/{[^}?]+\??}/g))?void 0:t.map(function(r){return{name:r.replace(/{|\??}/g,""),required:!/\?}$/.test(r)}}))?r:[]}}])}(),I=/*#__PURE__*/function(r){function e(t,e,o,i){var u;if(void 0===o&&(o=!0),(u=r.call(this)||this).t=null!=i?i:"undefined"!=typeof Ziggy?Ziggy:null==globalThis?void 0:globalThis.Ziggy,u.t=n({},u.t,{absolute:o}),t){if(!u.t.routes[t])throw new Error("Ziggy error: route '"+t+"' is not in the route list.");u.i=new P(t,u.t.routes[t],u.t),u.u=u.l(e)}return u}var o,u;u=r,(o=e).prototype=Object.create(u.prototype),o.prototype.constructor=o,i(o,u);var f=e.prototype;return f.toString=function(){var r=this,t=Object.keys(this.u).filter(function(t){return!r.i.parameterSegments.some(function(r){return r.name===t})}).filter(function(r){return"_query"!==r}).reduce(function(t,e){var o;return n({},t,((o={})[e]=r.u[e],o))},{});return this.i.compile(this.u)+function(r,t){var n,e=r,o=function(r){if(!r)return S;if(null!=r.encoder&&"function"!=typeof r.encoder)throw new TypeError("Encoder has to be a function.");var t=r.charset||S.charset;if(void 0!==r.charset&&"utf-8"!==r.charset&&"iso-8859-1"!==r.charset)throw new TypeError("The charset option must be either utf-8, iso-8859-1, or undefined");var n=s.default;if(void 0!==r.format){if(!h.call(s.formatters,r.format))throw new TypeError("Unknown format option provided.");n=r.format}var e=s.formatters[n],o=S.filter;return("function"==typeof r.filter||m(r.filter))&&(o=r.filter),{addQueryPrefix:"boolean"==typeof r.addQueryPrefix?r.addQueryPrefix:S.addQueryPrefix,allowDots:void 0===r.allowDots?S.allowDots:!!r.allowDots,charset:t,charsetSentinel:"boolean"==typeof r.charsetSentinel?r.charsetSentinel:S.charsetSentinel,delimiter:void 0===r.delimiter?S.delimiter:r.delimiter,encode:"boolean"==typeof r.encode?r.encode:S.encode,encoder:"function"==typeof r.encoder?r.encoder:S.encoder,encodeValuesOnly:"boolean"==typeof r.encodeValuesOnly?r.encodeValuesOnly:S.encodeValuesOnly,filter:o,format:n,formatter:e,serializeDate:"function"==typeof r.serializeDate?r.serializeDate:S.serializeDate,skipNulls:"boolean"==typeof r.skipNulls?r.skipNulls:S.skipNulls,sort:"function"==typeof r.sort?r.sort:null,strictNullHandling:"boolean"==typeof r.strictNullHandling?r.strictNullHandling:S.strictNullHandling}}(t);"function"==typeof o.filter?e=(0,o.filter)("",e):m(o.filter)&&(n=o.filter);var i=[];if("object"!=typeof e||null===e)return"";var u=g[t&&t.arrayFormat in g?t.arrayFormat:t&&"indices"in t?t.indices?"indices":"repeat":"indices"];n||(n=Object.keys(e)),o.sort&&n.sort(o.sort);for(var f=0;f0?l+c:""}(n({},t,this.u._query),{addQueryPrefix:!0,arrayFormat:"indices",encodeValuesOnly:!0,skipNulls:!0,encoder:function(r,t){return"boolean"==typeof r?Number(r):t(r)}})},f.v=function(r){var t=this;r?this.t.absolute&&r.startsWith("/")&&(r=this.p().host+r):r=this.h();var e={},o=Object.entries(this.t.routes).find(function(n){return e=new P(n[0],n[1],t.t).matchesUrl(r)})||[void 0,void 0];return n({name:o[0]},e,{route:o[1]})},f.h=function(){var r=this.p(),t=r.pathname,n=r.search;return(this.t.absolute?r.host+t:t.replace(this.t.url.replace(/^\w*:\/\/[^/]+/,""),"").replace(/^\/+/,"/"))+n},f.current=function(r,t){var e=this.v(),o=e.name,i=e.params,u=e.query,f=e.route;if(!r)return o;var a=new RegExp("^"+r.replace(/\./g,"\\.").replace(/\*/g,".*")+"$").test(o);if([null,void 0].includes(t)||!a)return a;var c=new P(o,f,this.t);t=this.l(t,c);var l=n({},i,u);if(Object.values(t).every(function(r){return!r})&&!Object.values(l).some(function(r){return void 0!==r}))return!0;var s=function(r,t){return Object.entries(r).every(function(r){var n=r[0],e=r[1];return Array.isArray(e)&&Array.isArray(t[n])?e.every(function(r){return t[n].includes(r)}):"object"==typeof e&&"object"==typeof t[n]&&null!==e&&null!==t[n]?s(e,t[n]):t[n]==e})};return s(t,l)},f.p=function(){var r,t,n,e,o,i,u="undefined"!=typeof window?window.location:{},f=u.host,a=u.pathname,c=u.search;return{host:null!=(r=null==(t=this.t.location)?void 0:t.host)?r:void 0===f?"":f,pathname:null!=(n=null==(e=this.t.location)?void 0:e.pathname)?n:void 0===a?"":a,search:null!=(o=null==(i=this.t.location)?void 0:i.search)?o:void 0===c?"":c}},f.has=function(r){return this.t.routes.hasOwnProperty(r)},f.l=function(r,t){var e=this;void 0===r&&(r={}),void 0===t&&(t=this.i),null!=r||(r={}),r=["string","number"].includes(typeof r)?[r]:r;var o=t.parameterSegments.filter(function(r){return!e.t.defaults[r.name]});if(Array.isArray(r))r=r.reduce(function(r,t,e){var i,u;return n({},r,o[e]?((i={})[o[e].name]=t,i):"object"==typeof t?t:((u={})[t]="",u))},{});else if(1===o.length&&!r[o[0].name]&&(r.hasOwnProperty(Object.values(t.bindings)[0])||r.hasOwnProperty("id"))){var i;(i={})[o[0].name]=r,r=i}return n({},this.m(t),this.j(r,t))},f.m=function(r){var t=this;return r.parameterSegments.filter(function(r){return t.t.defaults[r.name]}).reduce(function(r,e,o){var i,u=e.name;return n({},r,((i={})[u]=t.t.defaults[u],i))},{})},f.j=function(r,t){var e=t.bindings,o=t.parameterSegments;return Object.entries(r).reduce(function(r,t){var i,u,f=t[0],a=t[1];if(!a||"object"!=typeof a||Array.isArray(a)||!o.some(function(r){return r.name===f}))return n({},r,((u={})[f]=a,u));if(!a.hasOwnProperty(e[f])){if(!a.hasOwnProperty("id"))throw new Error("Ziggy error: object passed as '"+f+"' parameter is missing route model binding key '"+e[f]+"'.");e[f]="id"}return n({},r,((i={})[f]=a[e[f]],i))},{})},f.valueOf=function(){return this.toString()},t(e,[{key:"params",get:function(){var r=this.v();return n({},r.params,r.query)}},{key:"routeParams",get:function(){return this.v().params}},{key:"queryParams",get:function(){return this.v().query}}])}(/*#__PURE__*/f(String));function Z(r,t,n,e){var o=new I(r,t,n,e);return r?o.toString():o}var F={install:function(r,t){var n=function(r,n,e,o){return void 0===o&&(o=t),Z(r,n,e,o)};parseInt(r.version)>2?(r.config.globalProperties.route=n,r.provide("route",n)):r.mixin({methods:{route:n}})}};function q(r){if(!r&&!globalThis.Ziggy&&"undefined"==typeof Ziggy)throw new Error("Ziggy error: missing configuration. Ensure that a `Ziggy` variable is defined globally or pass a config object into the useRoute hook.");return function(t,n,e,o){return void 0===o&&(o=r),Z(t,n,e,o)}}export{F as ZiggyVue,Z as route,q as useRoute}; 2 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | function t(){return t=Object.assign?Object.assign.bind():function(t){for(var e=1;e1;){var e=t.pop(),r=e.obj[e.prop];if(u(r)){for(var n=[],o=0;o=48&&c<=57||c>=65&&c<=90||c>=97&&c<=122||i===o.RFC1738&&(40===c||41===c)?s+=u.charAt(f):c<128?s+=a[c]:c<2048?s+=a[192|c>>6]+a[128|63&c]:c<55296||c>=57344?s+=a[224|c>>12]+a[128|c>>6&63]+a[128|63&c]:(c=65536+((1023&c)<<10|1023&u.charCodeAt(f+=1)),s+=a[240|c>>18]+a[128|c>>12&63]+a[128|c>>6&63]+a[128|63&c])}return s},isBuffer:function(t){return!(!t||"object"!=typeof t||!(t.constructor&&t.constructor.isBuffer&&t.constructor.isBuffer(t)))},isRegExp:function(t){return"[object RegExp]"===Object.prototype.toString.call(t)},maybeMap:function(t,e){if(u(t)){for(var r=[],n=0;n0?w.join(",")||null:void 0}];else if(p(a))S=a;else{var N=Object.keys(w);S=s?N.sort(s):N}for(var T=0;T-1?t.split(","):t},R=function(t,e,r,n){if(t){var o=r.allowDots?t.replace(/\.([^.[]+)/g,"[$1]"):t,i=/(\[[^[\]]*])/g,u=r.depth>0&&/(\[[^[\]]*])/.exec(o),a=u?o.slice(0,u.index):o,s=[];if(a){if(!r.plainObjects&&j.call(Object.prototype,a)&&!r.allowPrototypes)return;s.push(a)}for(var f=0;r.depth>0&&null!==(u=i.exec(o))&&f=0;--i){var u,a=t[i];if("[]"===a&&r.parseArrays)u=[].concat(o);else{u=r.plainObjects?Object.create(null):{};var s="["===a.charAt(0)&&"]"===a.charAt(a.length-1)?a.slice(1,-1):a,f=parseInt(s,10);r.parseArrays||""!==s?!isNaN(f)&&a!==s&&String(f)===s&&f>=0&&r.parseArrays&&f<=r.arrayLimit?(u=[])[f]=o:"__proto__"!==s&&(u[s]=o):u={0:o}}o=u}return o}(s,e,r,n)}},S=function(t,e){var r=function(t){if(!t)return $;if(null!=t.decoder&&"function"!=typeof t.decoder)throw new TypeError("Decoder has to be a function.");if(void 0!==t.charset&&"utf-8"!==t.charset&&"iso-8859-1"!==t.charset)throw new TypeError("The charset option must be either utf-8, iso-8859-1, or undefined");return{allowDots:void 0===t.allowDots?$.allowDots:!!t.allowDots,allowPrototypes:"boolean"==typeof t.allowPrototypes?t.allowPrototypes:$.allowPrototypes,arrayLimit:"number"==typeof t.arrayLimit?t.arrayLimit:$.arrayLimit,charset:void 0===t.charset?$.charset:t.charset,charsetSentinel:"boolean"==typeof t.charsetSentinel?t.charsetSentinel:$.charsetSentinel,comma:"boolean"==typeof t.comma?t.comma:$.comma,decoder:"function"==typeof t.decoder?t.decoder:$.decoder,delimiter:"string"==typeof t.delimiter||f.isRegExp(t.delimiter)?t.delimiter:$.delimiter,depth:"number"==typeof t.depth||!1===t.depth?+t.depth:$.depth,ignoreQueryPrefix:!0===t.ignoreQueryPrefix,interpretNumericEntities:"boolean"==typeof t.interpretNumericEntities?t.interpretNumericEntities:$.interpretNumericEntities,parameterLimit:"number"==typeof t.parameterLimit?t.parameterLimit:$.parameterLimit,parseArrays:!1!==t.parseArrays,plainObjects:"boolean"==typeof t.plainObjects?t.plainObjects:$.plainObjects,strictNullHandling:"boolean"==typeof t.strictNullHandling?t.strictNullHandling:$.strictNullHandling}}(e);if(""===t||null==t)return r.plainObjects?Object.create(null):{};for(var n="string"==typeof t?function(t,e){var r,n={},o=(e.ignoreQueryPrefix?t.replace(/^\?/,""):t).split(e.delimiter,Infinity===e.parameterLimit?void 0:e.parameterLimit),i=-1,u=e.charset;if(e.charsetSentinel)for(r=0;r-1&&(s=w(s)?[s]:s),n[a]=j.call(n,a)?f.combine(n[a],s):s}return n}(t,r):t,o=r.plainObjects?Object.create(null):{},i=Object.keys(n),u=0;u({name:t.replace(/{|\??}/g,""),required:!/\?}$/.test(t)})))?t:[]}matchesUrl(t){var e;if(!this.definition.methods.includes("GET"))return!1;const r=this.template.replace(/[.*+$()[\]]/g,"\\$&").replace(/(\/?){([^}?]*)(\??)}/g,(t,e,r,n)=>{var o;const i=`(?<${r}>${(null==(o=this.wheres[r])?void 0:o.replace(/(^\^)|(\$$)/g,""))||"[^/?]+"})`;return n?`(${e}${i})?`:`${e}${i}`}).replace(/^\w+:\/\//,""),[n,o]=t.replace(/^\w+:\/\//,"").split("?"),i=null!=(e=new RegExp(`^${r}/?$`).exec(n))?e:new RegExp(`^${r}/?$`).exec(decodeURI(n));if(i){for(const t in i.groups)i.groups[t]="string"==typeof i.groups[t]?decodeURIComponent(i.groups[t]):i.groups[t];return{params:i.groups,query:S(o)}}return!1}compile(t){return this.parameterSegments.length?this.template.replace(/{([^}?]+)(\??)}/g,(e,r,n)=>{var o,i;if(!n&&[null,void 0].includes(t[r]))throw new Error(`Ziggy error: '${r}' parameter is required for route '${this.name}'.`);if(this.wheres[r]&&!new RegExp(`^${n?`(${this.wheres[r]})?`:this.wheres[r]}$`).test(null!=(i=t[r])?i:""))throw new Error(`Ziggy error: '${r}' parameter '${t[r]}' does not match required format '${this.wheres[r]}' for route '${this.name}'.`);return encodeURI(null!=(o=t[r])?o:"").replace(/%7C/g,"|").replace(/%25/g,"%").replace(/\$/g,"%24")}).replace(this.config.absolute?/(\.[^/]+?)(\/\/)/:/(^)(\/\/)/,"$1/").replace(/\/+$/,""):this.template}}class N extends String{constructor(e,r,n=!0,o){if(super(),this.t=null!=o?o:"undefined"!=typeof Ziggy?Ziggy:null==globalThis?void 0:globalThis.Ziggy,this.t=t({},this.t,{absolute:n}),e){if(!this.t.routes[e])throw new Error(`Ziggy error: route '${e}' is not in the route list.`);this.i=new x(e,this.t.routes[e],this.t),this.u=this.l(r)}}toString(){const e=Object.keys(this.u).filter(t=>!this.i.parameterSegments.some(({name:e})=>e===t)).filter(t=>"_query"!==t).reduce((e,r)=>t({},e,{[r]:this.u[r]}),{});return this.i.compile(this.u)+function(t,e){var r,n=t,i=function(t){if(!t)return v;if(null!=t.encoder&&"function"!=typeof t.encoder)throw new TypeError("Encoder has to be a function.");var e=t.charset||v.charset;if(void 0!==t.charset&&"utf-8"!==t.charset&&"iso-8859-1"!==t.charset)throw new TypeError("The charset option must be either utf-8, iso-8859-1, or undefined");var r=o.default;if(void 0!==t.format){if(!c.call(o.formatters,t.format))throw new TypeError("Unknown format option provided.");r=t.format}var n=o.formatters[r],i=v.filter;return("function"==typeof t.filter||p(t.filter))&&(i=t.filter),{addQueryPrefix:"boolean"==typeof t.addQueryPrefix?t.addQueryPrefix:v.addQueryPrefix,allowDots:void 0===t.allowDots?v.allowDots:!!t.allowDots,charset:e,charsetSentinel:"boolean"==typeof t.charsetSentinel?t.charsetSentinel:v.charsetSentinel,delimiter:void 0===t.delimiter?v.delimiter:t.delimiter,encode:"boolean"==typeof t.encode?t.encode:v.encode,encoder:"function"==typeof t.encoder?t.encoder:v.encoder,encodeValuesOnly:"boolean"==typeof t.encodeValuesOnly?t.encodeValuesOnly:v.encodeValuesOnly,filter:i,format:r,formatter:n,serializeDate:"function"==typeof t.serializeDate?t.serializeDate:v.serializeDate,skipNulls:"boolean"==typeof t.skipNulls?t.skipNulls:v.skipNulls,sort:"function"==typeof t.sort?t.sort:null,strictNullHandling:"boolean"==typeof t.strictNullHandling?t.strictNullHandling:v.strictNullHandling}}(e);"function"==typeof i.filter?n=(0,i.filter)("",n):p(i.filter)&&(r=i.filter);var u=[];if("object"!=typeof n||null===n)return"";var a=l[e&&e.arrayFormat in l?e.arrayFormat:e&&"indices"in e?e.indices?"indices":"repeat":"indices"];r||(r=Object.keys(n)),i.sort&&r.sort(i.sort);for(var s=0;s0?y+h:""}(t({},e,this.u._query),{addQueryPrefix:!0,arrayFormat:"indices",encodeValuesOnly:!0,skipNulls:!0,encoder:(t,e)=>"boolean"==typeof t?Number(t):e(t)})}p(e){e?this.t.absolute&&e.startsWith("/")&&(e=this.h().host+e):e=this.v();let r={};const[n,o]=Object.entries(this.t.routes).find(([t,n])=>r=new x(t,n,this.t).matchesUrl(e))||[void 0,void 0];return t({name:n},r,{route:o})}v(){const{host:t,pathname:e,search:r}=this.h();return(this.t.absolute?t+e:e.replace(this.t.url.replace(/^\w*:\/\/[^/]+/,""),"").replace(/^\/+/,"/"))+r}current(e,r){const{name:n,params:o,query:i,route:u}=this.p();if(!e)return n;const a=new RegExp(`^${e.replace(/\./g,"\\.").replace(/\*/g,".*")}$`).test(n);if([null,void 0].includes(r)||!a)return a;const s=new x(n,u,this.t);r=this.l(r,s);const f=t({},o,i);if(Object.values(r).every(t=>!t)&&!Object.values(f).some(t=>void 0!==t))return!0;const c=(t,e)=>Object.entries(t).every(([t,r])=>Array.isArray(r)&&Array.isArray(e[t])?r.every(r=>e[t].includes(r)):"object"==typeof r&&"object"==typeof e[t]&&null!==r&&null!==e[t]?c(r,e[t]):e[t]==r);return c(r,f)}h(){var t,e,r,n,o,i;const{host:u="",pathname:a="",search:s=""}="undefined"!=typeof window?window.location:{};return{host:null!=(t=null==(e=this.t.location)?void 0:e.host)?t:u,pathname:null!=(r=null==(n=this.t.location)?void 0:n.pathname)?r:a,search:null!=(o=null==(i=this.t.location)?void 0:i.search)?o:s}}get params(){const{params:e,query:r}=this.p();return t({},e,r)}get routeParams(){return this.p().params}get queryParams(){return this.p().query}has(t){return this.t.routes.hasOwnProperty(t)}l(e={},r=this.i){null!=e||(e={}),e=["string","number"].includes(typeof e)?[e]:e;const n=r.parameterSegments.filter(({name:t})=>!this.t.defaults[t]);return Array.isArray(e)?e=e.reduce((e,r,o)=>t({},e,n[o]?{[n[o].name]:r}:"object"==typeof r?r:{[r]:""}),{}):1!==n.length||e[n[0].name]||!e.hasOwnProperty(Object.values(r.bindings)[0])&&!e.hasOwnProperty("id")||(e={[n[0].name]:e}),t({},this.m(r),this.j(e,r))}m(e){return e.parameterSegments.filter(({name:t})=>this.t.defaults[t]).reduce((e,{name:r},n)=>t({},e,{[r]:this.t.defaults[r]}),{})}j(e,{bindings:r,parameterSegments:n}){return Object.entries(e).reduce((e,[o,i])=>{if(!i||"object"!=typeof i||Array.isArray(i)||!n.some(({name:t})=>t===o))return t({},e,{[o]:i});if(!i.hasOwnProperty(r[o])){if(!i.hasOwnProperty("id"))throw new Error(`Ziggy error: object passed as '${o}' parameter is missing route model binding key '${r[o]}'.`);r[o]="id"}return t({},e,{[o]:i[r[o]]})},{})}valueOf(){return this.toString()}}function T(t,e,r,n){const o=new N(t,e,r,n);return t?o.toString():o}const k={install(t,e){const r=(t,r,n,o=e)=>T(t,r,n,o);parseInt(t.version)>2?(t.config.globalProperties.route=r,t.provide("route",r)):t.mixin({methods:{route:r}})}};function C(t){if(!t&&!globalThis.Ziggy&&"undefined"==typeof Ziggy)throw new Error("Ziggy error: missing configuration. Ensure that a `Ziggy` variable is defined globally or pass a config object into the useRoute hook.");return(e,r,n,o=t)=>T(e,r,n,o)}export{k as ZiggyVue,T as route,C as useRoute}; 2 | -------------------------------------------------------------------------------- /dist/route.umd.js: -------------------------------------------------------------------------------- 1 | !function(t,r){"object"==typeof exports&&"undefined"!=typeof module?module.exports=r():"function"==typeof define&&define.amd?define(r):(t||self).route=r()}(this,function(){function t(t,r){for(var n=0;n1;){var r=t.pop(),n=r.obj[r.prop];if(p(n)){for(var e=[],o=0;o=48&&a<=57||a>=65&&a<=90||a>=97&&a<=122||o===s.RFC1738&&(40===a||41===a)?u+=i.charAt(f):a<128?u+=y[a]:a<2048?u+=y[192|a>>6]+y[128|63&a]:a<55296||a>=57344?u+=y[224|a>>12]+y[128|a>>6&63]+y[128|63&a]:(a=65536+((1023&a)<<10|1023&i.charCodeAt(f+=1)),u+=y[240|a>>18]+y[128|a>>12&63]+y[128|a>>6&63]+y[128|63&a])}return u},isBuffer:function(t){return!(!t||"object"!=typeof t||!(t.constructor&&t.constructor.isBuffer&&t.constructor.isBuffer(t)))},isRegExp:function(t){return"[object RegExp]"===Object.prototype.toString.call(t)},maybeMap:function(t,r){if(p(t)){for(var n=[],e=0;e0?h.join(",")||null:void 0}];else if(m(f))k=f;else{var $=Object.keys(h);k=a?$.sort(a):$}for(var x=0;x-1?t.split(","):t},A=function(t,r,n,e){if(t){var o=n.allowDots?t.replace(/\.([^.[]+)/g,"[$1]"):t,i=/(\[[^[\]]*])/g,u=n.depth>0&&/(\[[^[\]]*])/.exec(o),f=u?o.slice(0,u.index):o,a=[];if(f){if(!n.plainObjects&&T.call(Object.prototype,f)&&!n.allowPrototypes)return;a.push(f)}for(var c=0;n.depth>0&&null!==(u=i.exec(o))&&c=0;--i){var u,f=t[i];if("[]"===f&&n.parseArrays)u=[].concat(o);else{u=n.plainObjects?Object.create(null):{};var a="["===f.charAt(0)&&"]"===f.charAt(f.length-1)?f.slice(1,-1):f,c=parseInt(a,10);n.parseArrays||""!==a?!isNaN(c)&&f!==a&&String(c)===a&&c>=0&&n.parseArrays&&c<=n.arrayLimit?(u=[])[c]=o:"__proto__"!==a&&(u[a]=o):u={0:o}}o=u}return o}(a,r,n,e)}},D=function(t,r){var n=function(t){if(!t)return x;if(null!=t.decoder&&"function"!=typeof t.decoder)throw new TypeError("Decoder has to be a function.");if(void 0!==t.charset&&"utf-8"!==t.charset&&"iso-8859-1"!==t.charset)throw new TypeError("The charset option must be either utf-8, iso-8859-1, or undefined");return{allowDots:void 0===t.allowDots?x.allowDots:!!t.allowDots,allowPrototypes:"boolean"==typeof t.allowPrototypes?t.allowPrototypes:x.allowPrototypes,arrayLimit:"number"==typeof t.arrayLimit?t.arrayLimit:x.arrayLimit,charset:void 0===t.charset?x.charset:t.charset,charsetSentinel:"boolean"==typeof t.charsetSentinel?t.charsetSentinel:x.charsetSentinel,comma:"boolean"==typeof t.comma?t.comma:x.comma,decoder:"function"==typeof t.decoder?t.decoder:x.decoder,delimiter:"string"==typeof t.delimiter||b.isRegExp(t.delimiter)?t.delimiter:x.delimiter,depth:"number"==typeof t.depth||!1===t.depth?+t.depth:x.depth,ignoreQueryPrefix:!0===t.ignoreQueryPrefix,interpretNumericEntities:"boolean"==typeof t.interpretNumericEntities?t.interpretNumericEntities:x.interpretNumericEntities,parameterLimit:"number"==typeof t.parameterLimit?t.parameterLimit:x.parameterLimit,parseArrays:!1!==t.parseArrays,plainObjects:"boolean"==typeof t.plainObjects?t.plainObjects:x.plainObjects,strictNullHandling:"boolean"==typeof t.strictNullHandling?t.strictNullHandling:x.strictNullHandling}}(r);if(""===t||null==t)return n.plainObjects?Object.create(null):{};for(var e="string"==typeof t?function(t,r){var n,e={},o=(r.ignoreQueryPrefix?t.replace(/^\?/,""):t).split(r.delimiter,Infinity===r.parameterLimit?void 0:r.parameterLimit),i=-1,u=r.charset;if(r.charsetSentinel)for(n=0;n-1&&(a=$(a)?[a]:a),e[f]=T.call(e,f)?b.combine(e[f],a):a}return e}(t,n):t,o=n.plainObjects?Object.create(null):{},i=Object.keys(e),u=0;u"+((null==(i=n.wheres[e])?void 0:i.replace(/(^\^)|(\$$)/g,""))||"[^/?]+")+")";return o?"("+r+u+")?":""+r+u}).replace(/^\w+:\/\//,""),o=t.replace(/^\w+:\/\//,"").split("?"),i=o[0],u=o[1],f=null!=(r=new RegExp("^"+e+"/?$").exec(i))?r:new RegExp("^"+e+"/?$").exec(decodeURI(i));if(f){for(var a in f.groups)f.groups[a]="string"==typeof f.groups[a]?decodeURIComponent(f.groups[a]):f.groups[a];return{params:f.groups,query:D(u)}}return!1},n.compile=function(t){var r=this;return this.parameterSegments.length?this.template.replace(/{([^}?]+)(\??)}/g,function(n,e,o){var i,u;if(!o&&[null,void 0].includes(t[e]))throw new Error("Ziggy error: '"+e+"' parameter is required for route '"+r.name+"'.");if(r.wheres[e]&&!new RegExp("^"+(o?"("+r.wheres[e]+")?":r.wheres[e])+"$").test(null!=(u=t[e])?u:""))throw new Error("Ziggy error: '"+e+"' parameter '"+t[e]+"' does not match required format '"+r.wheres[e]+"' for route '"+r.name+"'.");return encodeURI(null!=(i=t[e])?i:"").replace(/%7C/g,"|").replace(/%25/g,"%").replace(/\$/g,"%24")}).replace(this.config.absolute?/(\.[^/]+?)(\/\/)/:/(^)(\/\/)/,"$1/").replace(/\/+$/,""):this.template},r(t,[{key:"template",get:function(){var t=(this.origin+"/"+this.definition.uri).replace(/\/+$/,"");return""===t?"/":t}},{key:"origin",get:function(){return this.config.absolute?this.definition.domain?""+this.config.url.match(/^\w+:\/\//)[0]+this.definition.domain+(this.config.port?":"+this.config.port:""):this.config.url:""}},{key:"parameterSegments",get:function(){var t,r;return null!=(t=null==(r=this.template.match(/{[^}?]+\??}/g))?void 0:r.map(function(t){return{name:t.replace(/{|\??}/g,""),required:!/\?}$/.test(t)}}))?t:[]}}])}(),F=/*#__PURE__*/function(t){function e(r,e,o,i){var u;if(void 0===o&&(o=!0),(u=t.call(this)||this).t=null!=i?i:"undefined"!=typeof Ziggy?Ziggy:null==globalThis?void 0:globalThis.Ziggy,u.t=n({},u.t,{absolute:o}),r){if(!u.t.routes[r])throw new Error("Ziggy error: route '"+r+"' is not in the route list.");u.i=new P(r,u.t.routes[r],u.t),u.u=u.l(e)}return u}var o,u;u=t,(o=e).prototype=Object.create(u.prototype),o.prototype.constructor=o,i(o,u);var f=e.prototype;return f.toString=function(){var t=this,r=Object.keys(this.u).filter(function(r){return!t.i.parameterSegments.some(function(t){return t.name===r})}).filter(function(t){return"_query"!==t}).reduce(function(r,e){var o;return n({},r,((o={})[e]=t.u[e],o))},{});return this.i.compile(this.u)+function(t,r){var n,e=t,o=function(t){if(!t)return S;if(null!=t.encoder&&"function"!=typeof t.encoder)throw new TypeError("Encoder has to be a function.");var r=t.charset||S.charset;if(void 0!==t.charset&&"utf-8"!==t.charset&&"iso-8859-1"!==t.charset)throw new TypeError("The charset option must be either utf-8, iso-8859-1, or undefined");var n=s.default;if(void 0!==t.format){if(!h.call(s.formatters,t.format))throw new TypeError("Unknown format option provided.");n=t.format}var e=s.formatters[n],o=S.filter;return("function"==typeof t.filter||m(t.filter))&&(o=t.filter),{addQueryPrefix:"boolean"==typeof t.addQueryPrefix?t.addQueryPrefix:S.addQueryPrefix,allowDots:void 0===t.allowDots?S.allowDots:!!t.allowDots,charset:r,charsetSentinel:"boolean"==typeof t.charsetSentinel?t.charsetSentinel:S.charsetSentinel,delimiter:void 0===t.delimiter?S.delimiter:t.delimiter,encode:"boolean"==typeof t.encode?t.encode:S.encode,encoder:"function"==typeof t.encoder?t.encoder:S.encoder,encodeValuesOnly:"boolean"==typeof t.encodeValuesOnly?t.encodeValuesOnly:S.encodeValuesOnly,filter:o,format:n,formatter:e,serializeDate:"function"==typeof t.serializeDate?t.serializeDate:S.serializeDate,skipNulls:"boolean"==typeof t.skipNulls?t.skipNulls:S.skipNulls,sort:"function"==typeof t.sort?t.sort:null,strictNullHandling:"boolean"==typeof t.strictNullHandling?t.strictNullHandling:S.strictNullHandling}}(r);"function"==typeof o.filter?e=(0,o.filter)("",e):m(o.filter)&&(n=o.filter);var i=[];if("object"!=typeof e||null===e)return"";var u=g[r&&r.arrayFormat in g?r.arrayFormat:r&&"indices"in r?r.indices?"indices":"repeat":"indices"];n||(n=Object.keys(e)),o.sort&&n.sort(o.sort);for(var f=0;f0?l+c:""}(n({},r,this.u._query),{addQueryPrefix:!0,arrayFormat:"indices",encodeValuesOnly:!0,skipNulls:!0,encoder:function(t,r){return"boolean"==typeof t?Number(t):r(t)}})},f.v=function(t){var r=this;t?this.t.absolute&&t.startsWith("/")&&(t=this.p().host+t):t=this.h();var e={},o=Object.entries(this.t.routes).find(function(n){return e=new P(n[0],n[1],r.t).matchesUrl(t)})||[void 0,void 0];return n({name:o[0]},e,{route:o[1]})},f.h=function(){var t=this.p(),r=t.pathname,n=t.search;return(this.t.absolute?t.host+r:r.replace(this.t.url.replace(/^\w*:\/\/[^/]+/,""),"").replace(/^\/+/,"/"))+n},f.current=function(t,r){var e=this.v(),o=e.name,i=e.params,u=e.query,f=e.route;if(!t)return o;var a=new RegExp("^"+t.replace(/\./g,"\\.").replace(/\*/g,".*")+"$").test(o);if([null,void 0].includes(r)||!a)return a;var c=new P(o,f,this.t);r=this.l(r,c);var l=n({},i,u);if(Object.values(r).every(function(t){return!t})&&!Object.values(l).some(function(t){return void 0!==t}))return!0;var s=function(t,r){return Object.entries(t).every(function(t){var n=t[0],e=t[1];return Array.isArray(e)&&Array.isArray(r[n])?e.every(function(t){return r[n].includes(t)}):"object"==typeof e&&"object"==typeof r[n]&&null!==e&&null!==r[n]?s(e,r[n]):r[n]==e})};return s(r,l)},f.p=function(){var t,r,n,e,o,i,u="undefined"!=typeof window?window.location:{},f=u.host,a=u.pathname,c=u.search;return{host:null!=(t=null==(r=this.t.location)?void 0:r.host)?t:void 0===f?"":f,pathname:null!=(n=null==(e=this.t.location)?void 0:e.pathname)?n:void 0===a?"":a,search:null!=(o=null==(i=this.t.location)?void 0:i.search)?o:void 0===c?"":c}},f.has=function(t){return this.t.routes.hasOwnProperty(t)},f.l=function(t,r){var e=this;void 0===t&&(t={}),void 0===r&&(r=this.i),null!=t||(t={}),t=["string","number"].includes(typeof t)?[t]:t;var o=r.parameterSegments.filter(function(t){return!e.t.defaults[t.name]});if(Array.isArray(t))t=t.reduce(function(t,r,e){var i,u;return n({},t,o[e]?((i={})[o[e].name]=r,i):"object"==typeof r?r:((u={})[r]="",u))},{});else if(1===o.length&&!t[o[0].name]&&(t.hasOwnProperty(Object.values(r.bindings)[0])||t.hasOwnProperty("id"))){var i;(i={})[o[0].name]=t,t=i}return n({},this.m(r),this.j(t,r))},f.m=function(t){var r=this;return t.parameterSegments.filter(function(t){return r.t.defaults[t.name]}).reduce(function(t,e,o){var i,u=e.name;return n({},t,((i={})[u]=r.t.defaults[u],i))},{})},f.j=function(t,r){var e=r.bindings,o=r.parameterSegments;return Object.entries(t).reduce(function(t,r){var i,u,f=r[0],a=r[1];if(!a||"object"!=typeof a||Array.isArray(a)||!o.some(function(t){return t.name===f}))return n({},t,((u={})[f]=a,u));if(!a.hasOwnProperty(e[f])){if(!a.hasOwnProperty("id"))throw new Error("Ziggy error: object passed as '"+f+"' parameter is missing route model binding key '"+e[f]+"'.");e[f]="id"}return n({},t,((i={})[f]=a[e[f]],i))},{})},f.valueOf=function(){return this.toString()},r(e,[{key:"params",get:function(){var t=this.v();return n({},t.params,t.query)}},{key:"routeParams",get:function(){return this.v().params}},{key:"queryParams",get:function(){return this.v().query}}])}(/*#__PURE__*/f(String));return function(t,r,n,e){var o=new F(t,r,n,e);return t?o.toString():o}}); 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ziggy-js", 3 | "version": "2.5.3", 4 | "description": "Use your Laravel named routes in JavaScript.", 5 | "keywords": [ 6 | "laravel", 7 | "routes", 8 | "ziggy" 9 | ], 10 | "homepage": "https://github.com/tighten/ziggy", 11 | "bugs": "https://github.com/tighten/ziggy/issues", 12 | "license": "MIT", 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/tighten/ziggy.git" 16 | }, 17 | "authors": [ 18 | { 19 | "name": "Daniel Coulbourne", 20 | "email": "daniel@tighten.co" 21 | }, 22 | { 23 | "name": "Jake Bathman", 24 | "email": "jake@tighten.co" 25 | }, 26 | { 27 | "name": "Jacob Baker-Kretzmar", 28 | "email": "jacob@tighten.co" 29 | } 30 | ], 31 | "files": [ 32 | "src/js/index.d.ts", 33 | "dist" 34 | ], 35 | "type": "module", 36 | "source": "./src/js/index.js", 37 | "exports": { 38 | "types": "./src/js/index.d.ts", 39 | "default": "./dist/index.js" 40 | }, 41 | "module": "./dist/index.esm.js", 42 | "types": "./src/js/index.d.ts", 43 | "scripts": { 44 | "build": "npm run build:esm && npm run build:umd", 45 | "build:esm": "microbundle -i ./src/js/index.js -o ./dist/index.js --format modern,esm --no-sourcemap --no-generateTypes --external none", 46 | "build:npm": "microbundle -i ./src/js/index.js -o ./dist/index.js --format modern,esm --no-sourcemap --no-generateTypes", 47 | "build:umd": "microbundle -i ./src/js/browser.js -o ./dist/route.js --format umd --name route --no-sourcemap --no-generateTypes --external none", 48 | "test": "vitest --typecheck", 49 | "format": "prettier . --write", 50 | "prepublishOnly": "rm -r ./dist/* && npm run build:npm" 51 | }, 52 | "mangle": { 53 | "regex": "^_(?!query)" 54 | }, 55 | "dependencies": { 56 | "@types/qs": "^6.9.17", 57 | "qs": "~6.9.7" 58 | }, 59 | "devDependencies": { 60 | "jsdom": "^26.1.0", 61 | "microbundle": "^0.15.1", 62 | "prettier": "^3.5.3", 63 | "typescript": "^5.8.3", 64 | "vitest": "^3.1.3" 65 | }, 66 | "prettier": { 67 | "printWidth": 100, 68 | "singleQuote": true, 69 | "tabWidth": 4 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/BladeRouteGenerator.php: -------------------------------------------------------------------------------- 1 | generateMergeJavascript($ziggy, $nonce); 20 | } 21 | 22 | static::$generated = true; 23 | 24 | $output = config('ziggy.output.script', Script::class); 25 | 26 | $routeFunction = config('ziggy.skip-route-function') ? '' : file_get_contents(__DIR__ . '/../dist/route.umd.js'); 27 | 28 | return (string) new $output($ziggy, $routeFunction, $nonce); 29 | } 30 | 31 | private function generateMergeJavascript(Ziggy $ziggy, string $nonce) 32 | { 33 | $output = config('ziggy.output.merge_script', MergeScript::class); 34 | 35 | return new $output($ziggy, $nonce); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/CommandRouteGenerator.php: -------------------------------------------------------------------------------- 1 | option('group'), $this->option('url') ? url($this->option('url')) : null); 27 | 28 | if ($this->option('except') && ! $this->option('only')) { 29 | $ziggy->filter(explode(',', $this->option('except')), false); 30 | } else if ($this->option('only') && ! $this->option('except')) { 31 | $ziggy->filter(explode(',', $this->option('only'))); 32 | } 33 | 34 | $path = $this->argument('path') ?? config('ziggy.output.path', 'resources/js/ziggy.js'); 35 | 36 | if ($filesystem->isDirectory(base_path($path))) { 37 | $path .= '/ziggy'; 38 | } else { 39 | $filesystem->ensureDirectoryExists(dirname(base_path($path)), recursive: true); 40 | } 41 | 42 | $name = preg_replace('/(\.d)?\.ts$|\.js$/', '', $path); 43 | 44 | if (! $this->option('types-only')) { 45 | $output = config('ziggy.output.file', File::class); 46 | 47 | $filesystem->put(base_path("{$name}.js"), new $output($ziggy)); 48 | } 49 | 50 | if ($this->option('types') || $this->option('types-only')) { 51 | $types = config('ziggy.output.types', Types::class); 52 | 53 | $filesystem->put(base_path("{$name}.d.ts"), new $types($ziggy)); 54 | } 55 | 56 | $this->info('Files generated!'); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Output/File.php: -------------------------------------------------------------------------------- 1 | ziggy->toJson()}; 18 | if (typeof window !== 'undefined' && typeof window.Ziggy !== 'undefined') { 19 | Object.assign(Ziggy.routes, window.Ziggy.routes); 20 | } 21 | export { Ziggy }; 22 | 23 | JAVASCRIPT; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Output/MergeScript.php: -------------------------------------------------------------------------------- 1 | ziggy->toArray()['routes']); 18 | 19 | return <<nonce}>Object.assign(Ziggy.routes,{$routes}); 21 | HTML; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Output/Script.php: -------------------------------------------------------------------------------- 1 | nonce}>const Ziggy={$this->ziggy->toJson()};{$this->function} 20 | HTML; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Output/Types.php: -------------------------------------------------------------------------------- 1 | routes(); 20 | 21 | return <<toJson(JSON_PRETTY_PRINT)} 25 | } 26 | export {}; 27 | 28 | JAVASCRIPT; 29 | } 30 | 31 | protected function routes(): Collection 32 | { 33 | return collect($this->ziggy->toArray()['routes'])->map(function ($route) { 34 | return collect($route['parameters'] ?? [])->map(function ($param) use ($route) { 35 | return Arr::has($route, "bindings.{$param}") 36 | ? ['name' => $param, 'required' => ! Str::contains($route['uri'], "{$param}?"), 'binding' => $route['bindings'][$param]] 37 | : ['name' => $param, 'required' => ! Str::contains($route['uri'], "{$param}?")]; 38 | }); 39 | }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Ziggy.php: -------------------------------------------------------------------------------- 1 | url = rtrim($url ?? url('/'), '/'); 31 | 32 | $this->routes = static::$cache ??= $this->nameKeyedRoutes(); 33 | } 34 | 35 | public static function clearRoutes() 36 | { 37 | static::$cache = null; 38 | } 39 | 40 | private function applyFilters($group) 41 | { 42 | if ($group) { 43 | return $this->group($group); 44 | } 45 | 46 | if (config()->has('ziggy.except') && ! config()->has('ziggy.only')) { 47 | return $this->filter(config('ziggy.except'), false)->routes; 48 | } 49 | 50 | if (config()->has('ziggy.only') && ! config()->has('ziggy.except')) { 51 | return $this->filter(config('ziggy.only'))->routes; 52 | } 53 | 54 | return $this->routes; 55 | } 56 | 57 | /** 58 | * Filter routes by group. 59 | */ 60 | private function group($group) 61 | { 62 | if (is_array($group)) { 63 | $filters = []; 64 | 65 | foreach ($group as $groupName) { 66 | $filters = array_merge($filters, Arr::wrap(config("ziggy.groups.{$groupName}"))); 67 | } 68 | 69 | return $this->filter($filters)->routes; 70 | } 71 | 72 | if (config()->has("ziggy.groups.{$group}")) { 73 | return $this->filter(config("ziggy.groups.{$group}"))->routes; 74 | } 75 | 76 | return $this->routes; 77 | } 78 | 79 | /** 80 | * Filter routes by name using the given patterns. 81 | */ 82 | public function filter($filters = [], $include = true): self 83 | { 84 | $filters = Arr::wrap($filters); 85 | 86 | $reject = collect($filters)->every(fn (string $pattern) => str_starts_with($pattern, '!')); 87 | 88 | $this->routes = $reject 89 | ? $this->routes->reject(function ($route, $name) use ($filters) { 90 | foreach ($filters as $pattern) { 91 | if (Str::is(substr($pattern, 1), $name)) { 92 | return true; 93 | } 94 | } 95 | }) 96 | : $this->routes->filter(function ($route, $name) use ($filters, $include) { 97 | if ($include === false) { 98 | return ! Str::is($filters, $name); 99 | } 100 | 101 | foreach ($filters as $pattern) { 102 | if (str_starts_with($pattern, '!') && Str::is(substr($pattern, 1), $name)) { 103 | return false; 104 | } 105 | } 106 | 107 | return Str::is($filters, $name); 108 | }); 109 | 110 | return $this; 111 | } 112 | 113 | /** 114 | * Get a list of the application's named routes, keyed by their names. 115 | */ 116 | private function nameKeyedRoutes(): Collection 117 | { 118 | [$fallbacks, $routes] = collect(app('router')->getRoutes()->getRoutesByName()) 119 | ->reject(fn ($route) => str_starts_with($route->getName(), 'generated::')) 120 | ->partition('isFallback'); 121 | 122 | $bindings = $this->resolveBindings($routes->toArray()); 123 | 124 | $fallbacks->each(fn ($route, $name) => $routes->put($name, $route)); 125 | 126 | return tap($this->folioRoutes(), fn ($all) => $routes->each( 127 | fn ($route, $name) => $all->put( 128 | $name, 129 | collect($route)->only(['uri', 'methods', 'wheres']) 130 | ->put('domain', $route->domain()) 131 | ->put('parameters', $route->parameterNames()) 132 | ->put('bindings', $bindings[$route->getName()] ?? []) 133 | ->when(config('ziggy.middleware'), fn ($collection, $middleware) => is_array($middleware) 134 | ? $collection->put('middleware', collect($route->middleware())->intersect($middleware)->values()->all()) 135 | : $collection->put('middleware', $route->middleware()), 136 | ) 137 | ->filter() 138 | ) 139 | )); 140 | } 141 | 142 | public function toArray(): array 143 | { 144 | return [ 145 | 'url' => $this->url, 146 | 'port' => parse_url($this->url, PHP_URL_PORT) ?? null, 147 | 'defaults' => app('url')->getDefaultParameters(), 148 | 'routes' => $this->applyFilters($this->group)->toArray(), 149 | ]; 150 | } 151 | 152 | public function jsonSerialize(): array 153 | { 154 | return [ 155 | ...($routes = $this->toArray()), 156 | 'defaults' => (object) $routes['defaults'], 157 | ]; 158 | } 159 | 160 | public function toJson(int $options = 0): string 161 | { 162 | return json_encode($this->jsonSerialize(), $options); 163 | } 164 | 165 | /** 166 | * Resolve route key names for any route parameters using Eloquent route model binding. 167 | */ 168 | private function resolveBindings(array $routes): array 169 | { 170 | foreach ($routes as $name => $route) { 171 | $bindings = []; 172 | 173 | foreach ($route->signatureParameters(UrlRoutable::class) as $parameter) { 174 | if (! in_array($parameter->getName(), $route->parameterNames())) { 175 | break; 176 | } 177 | 178 | $model = Reflector::getParameterClassName($parameter); 179 | 180 | $override = (new ReflectionClass($model))->isInstantiable() && ( 181 | (new ReflectionMethod($model, 'getRouteKeyName'))->class !== Model::class 182 | || (new ReflectionMethod($model, 'getKeyName'))->class !== Model::class 183 | || (new ReflectionProperty($model, 'primaryKey'))->class !== Model::class 184 | ); 185 | 186 | // Avoid booting this model if it doesn't override the default route key name 187 | $bindings[$parameter->getName()] = $override ? app($model)->getRouteKeyName() : 'id'; 188 | } 189 | 190 | $routes[$name] = [...$bindings, ...$route->bindingFields()]; 191 | } 192 | 193 | return $routes; 194 | } 195 | 196 | /** 197 | * @see https://github.com/laravel/folio/blob/master/src/Console/ListCommand.php 198 | */ 199 | private function folioRoutes(): Collection 200 | { 201 | if (! app()->has(FolioRoutes::class)) { 202 | return collect(); 203 | } 204 | 205 | // Use existing named Folio routes (instead of searching view files) to respect route caching 206 | return collect(app(FolioRoutes::class)->routes())->map(function (array $route) { 207 | $uri = rtrim($route['baseUri'], '/') . str_replace([$route['mountPath'], '.blade.php'], '', $route['path']); 208 | 209 | $segments = explode('/', $uri); 210 | $parameters = []; 211 | $bindings = []; 212 | 213 | foreach ($segments as $i => $segment) { 214 | // Folio doesn't support sub-segment parameters 215 | if (str_starts_with($segment, '[')) { 216 | $param = new PotentiallyBindablePathSegment($segment); 217 | 218 | $parameters[] = $name = $param->variable(); 219 | $segments[$i] = "{{$name}}"; 220 | 221 | if ($field = $param->field()) { 222 | $bindings[$name] = $field; 223 | } elseif ($param->bindable()) { 224 | $override = (new ReflectionClass($param->class()))->isInstantiable() && ( 225 | (new ReflectionMethod($param->class(), 'getRouteKeyName'))->class !== Model::class 226 | || (new ReflectionMethod($param->class(), 'getKeyName'))->class !== Model::class 227 | || (new ReflectionProperty($param->class(), 'primaryKey'))->class !== Model::class 228 | ); 229 | 230 | $bindings[$name] = $override ? app($param->class())->getRouteKeyName() : 'id'; 231 | } 232 | } 233 | } 234 | 235 | $uri = implode('/', $segments); 236 | $uri = Str::replaceEnd('/index', '', $uri); 237 | 238 | if ($route['domain'] && str_contains($route['domain'], '{')) { 239 | preg_match_all('/{(.*?)}/', $route['domain'], $matches); 240 | array_unshift($parameters, ...$matches[1]); 241 | } 242 | 243 | $middleware = []; 244 | 245 | if ($ziggyMiddleware = config('ziggy.middleware')) { 246 | $mountPath = Arr::first( 247 | app(FolioManager::class)->mountPaths(), 248 | fn ($mountPath) => $mountPath->path === realpath($route['mountPath']) 249 | ); 250 | $matchedView = new MatchedView(realpath($route['path']), [], $route['mountPath']); 251 | 252 | $middleware = $mountPath->middleware 253 | ->match($matchedView) 254 | ->prepend('web') 255 | ->merge($matchedView->inlineMiddleware()) 256 | ->unique() 257 | ->when(is_array($ziggyMiddleware), fn ($middleware) => $middleware->intersect($ziggyMiddleware)) 258 | ->values()->all(); 259 | } 260 | 261 | return array_filter([ 262 | 'uri' => $uri === '' ? '/' : trim($uri, '/'), 263 | 'methods' => ['GET'], 264 | 'domain' => $route['domain'], 265 | 'parameters' => $parameters, 266 | 'bindings' => $bindings, 267 | 'middleware' => $middleware, 268 | ]); 269 | }); 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /src/ZiggyServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->resolved('blade.compiler')) { 15 | $this->registerDirective($this->app['blade.compiler']); 16 | } else { 17 | $this->app->afterResolving('blade.compiler', $this->registerDirective(...)); 18 | } 19 | 20 | Event::listen(RequestReceived::class, function () { 21 | BladeRouteGenerator::$generated = false; 22 | }); 23 | 24 | if ($this->app->runningInConsole()) { 25 | $this->commands(CommandRouteGenerator::class); 26 | } 27 | } 28 | 29 | protected function registerDirective(BladeCompiler $blade): void 30 | { 31 | $blade->directive('routes', fn ($group) => "generate({$group}); ?>"); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/js/Route.js: -------------------------------------------------------------------------------- 1 | import { parse } from 'qs'; 2 | 3 | /** 4 | * A Laravel route. This class represents one route and its configuration and metadata. 5 | */ 6 | export default class Route { 7 | /** 8 | * @param {String} name - Route name. 9 | * @param {Object} definition - Route definition. 10 | * @param {Object} config - Ziggy configuration. 11 | */ 12 | constructor(name, definition, config) { 13 | this.name = name; 14 | this.definition = definition; 15 | this.bindings = definition.bindings ?? {}; 16 | this.wheres = definition.wheres ?? {}; 17 | this.config = config; 18 | } 19 | 20 | /** 21 | * Get a 'template' of the complete URL for this route. 22 | * 23 | * @example 24 | * https://{team}.ziggy.dev/user/{user} 25 | * 26 | * @return {String} Route template. 27 | */ 28 | get template() { 29 | const template = `${this.origin}/${this.definition.uri}`.replace(/\/+$/, ''); 30 | 31 | return template === '' ? '/' : template; 32 | } 33 | 34 | /** 35 | * Get a template of the origin for this route. 36 | * 37 | * @example 38 | * https://{team}.ziggy.dev/ 39 | * 40 | * @return {String} Route origin template. 41 | */ 42 | get origin() { 43 | // If we're building just a path there's no origin, otherwise: if this route has a 44 | // domain configured we construct the origin with that, if not we use the app URL 45 | return !this.config.absolute 46 | ? '' 47 | : this.definition.domain 48 | ? `${this.config.url.match(/^\w+:\/\//)[0]}${this.definition.domain}${ 49 | this.config.port ? `:${this.config.port}` : '' 50 | }` 51 | : this.config.url; 52 | } 53 | 54 | /** 55 | * Get an array of objects representing the parameters that this route accepts. 56 | * 57 | * @example 58 | * [{ name: 'team', required: true }, { name: 'user', required: false }] 59 | * 60 | * @return {Array} Parameter segments. 61 | */ 62 | get parameterSegments() { 63 | return ( 64 | this.template.match(/{[^}?]+\??}/g)?.map((segment) => ({ 65 | name: segment.replace(/{|\??}/g, ''), 66 | required: !/\?}$/.test(segment), 67 | })) ?? [] 68 | ); 69 | } 70 | 71 | /** 72 | * Get whether this route's template matches the given URL. 73 | * 74 | * @param {String} url - URL to check. 75 | * @return {Object|false} - If this route matches, returns the matched parameters. 76 | */ 77 | matchesUrl(url) { 78 | if (!this.definition.methods.includes('GET')) return false; 79 | 80 | // Transform the route's template into a regex that will match a hydrated URL, 81 | // by replacing its parameter segments with matchers for parameter values 82 | const pattern = this.template 83 | .replace(/[.*+$()[\]]/g, '\\$&') 84 | .replace(/(\/?){([^}?]*)(\??)}/g, (_, slash, segment, optional) => { 85 | const regex = `(?<${segment}>${ 86 | this.wheres[segment]?.replace(/(^\^)|(\$$)/g, '') || '[^/?]+' 87 | })`; 88 | return optional ? `(${slash}${regex})?` : `${slash}${regex}`; 89 | }) 90 | .replace(/^\w+:\/\//, ''); 91 | 92 | const [location, query] = url.replace(/^\w+:\/\//, '').split('?'); 93 | 94 | const matches = 95 | new RegExp(`^${pattern}/?$`).exec(location) ?? 96 | new RegExp(`^${pattern}/?$`).exec(decodeURI(location)); 97 | 98 | if (matches) { 99 | for (const k in matches.groups) { 100 | matches.groups[k] = 101 | typeof matches.groups[k] === 'string' 102 | ? decodeURIComponent(matches.groups[k]) 103 | : matches.groups[k]; 104 | } 105 | return { params: matches.groups, query: parse(query) }; 106 | } 107 | 108 | return false; 109 | } 110 | 111 | /** 112 | * Hydrate and return a complete URL for this route with the given parameters. 113 | * 114 | * @param {Object} params 115 | * @return {String} 116 | */ 117 | compile(params) { 118 | const segments = this.parameterSegments; 119 | 120 | if (!segments.length) return this.template; 121 | 122 | // This should probably be refactored to build the host and path separately (not the entire URL at once) 123 | // because that's how Laravel does it internally and it's more precise and less error-prone 124 | return this.template 125 | .replace(/{([^}?]+)(\??)}/g, (_, segment, optional) => { 126 | // If the parameter is missing but is not optional, throw an error 127 | if (!optional && [null, undefined].includes(params[segment])) { 128 | throw new Error( 129 | `Ziggy error: '${segment}' parameter is required for route '${this.name}'.`, 130 | ); 131 | } 132 | 133 | if (this.wheres[segment]) { 134 | if ( 135 | !new RegExp( 136 | `^${optional ? `(${this.wheres[segment]})?` : this.wheres[segment]}$`, 137 | ).test(params[segment] ?? '') 138 | ) { 139 | throw new Error( 140 | `Ziggy error: '${segment}' parameter '${params[segment]}' does not match required format '${this.wheres[segment]}' for route '${this.name}'.`, 141 | ); 142 | } 143 | } 144 | 145 | return encodeURI(params[segment] ?? '') 146 | .replace(/%7C/g, '|') 147 | .replace(/%25/g, '%') 148 | .replace(/\$/g, '%24'); 149 | }) 150 | .replace(this.config.absolute ? /(\.[^/]+?)(\/\/)/ : /(^)(\/\/)/, '$1/') 151 | .replace(/\/+$/, ''); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/js/Router.js: -------------------------------------------------------------------------------- 1 | import { stringify } from 'qs'; 2 | import Route from './Route.js'; 3 | 4 | /** 5 | * A collection of Laravel routes. This class constitutes Ziggy's main API. 6 | */ 7 | export default class Router extends String { 8 | /** 9 | * @param {String} [name] - Route name. 10 | * @param {(String|Number|Array|Object)} [params] - Route parameters. 11 | * @param {Boolean} [absolute] - Whether to include the URL origin. 12 | * @param {Object} [config] - Ziggy configuration. 13 | */ 14 | constructor(name, params, absolute = true, config) { 15 | super(); 16 | 17 | this._config = config ?? (typeof Ziggy !== 'undefined' ? Ziggy : globalThis?.Ziggy); 18 | this._config = { ...this._config, absolute }; 19 | 20 | if (name) { 21 | if (!this._config.routes[name]) { 22 | throw new Error(`Ziggy error: route '${name}' is not in the route list.`); 23 | } 24 | 25 | this._route = new Route(name, this._config.routes[name], this._config); 26 | this._params = this._parse(params); 27 | } 28 | } 29 | 30 | /** 31 | * Get the compiled URL string for the current route and parameters. 32 | * 33 | * @example 34 | * // with 'posts.show' route 'posts/{post}' 35 | * (new Router('posts.show', 1)).toString(); // 'https://ziggy.dev/posts/1' 36 | * 37 | * @return {String} 38 | */ 39 | toString() { 40 | // Get parameters that don't correspond to any route segments to append them to the query 41 | const unhandled = Object.keys(this._params) 42 | .filter((key) => !this._route.parameterSegments.some(({ name }) => name === key)) 43 | .filter((key) => key !== '_query') 44 | .reduce((result, current) => ({ ...result, [current]: this._params[current] }), {}); 45 | 46 | return ( 47 | this._route.compile(this._params) + 48 | stringify( 49 | { ...unhandled, ...this._params['_query'] }, 50 | { 51 | addQueryPrefix: true, 52 | arrayFormat: 'indices', 53 | encodeValuesOnly: true, 54 | skipNulls: true, 55 | encoder: (value, encoder) => 56 | typeof value === 'boolean' ? Number(value) : encoder(value), 57 | }, 58 | ) 59 | ); 60 | } 61 | 62 | /** 63 | * Get the parameters, values, and metadata from the given URL. 64 | * 65 | * @param {String} [url] - The URL to inspect, defaults to the current window URL. 66 | * @return {{ name: string, params: Object, query: Object, route: Route }} 67 | */ 68 | _unresolve(url) { 69 | if (!url) { 70 | url = this._currentUrl(); 71 | } else if (this._config.absolute && url.startsWith('/')) { 72 | // If we are using absolute URLs and a relative URL 73 | // is passed, prefix the host to make it absolute 74 | url = this._location().host + url; 75 | } 76 | 77 | let matchedParams = {}; 78 | const [name, route] = Object.entries(this._config.routes).find( 79 | ([name, route]) => 80 | (matchedParams = new Route(name, route, this._config).matchesUrl(url)), 81 | ) || [undefined, undefined]; 82 | 83 | return { name, ...matchedParams, route }; 84 | } 85 | 86 | _currentUrl() { 87 | const { host, pathname, search } = this._location(); 88 | 89 | return ( 90 | (this._config.absolute 91 | ? host + pathname 92 | : pathname 93 | .replace(this._config.url.replace(/^\w*:\/\/[^/]+/, ''), '') 94 | .replace(/^\/+/, '/')) + search 95 | ); 96 | } 97 | 98 | /** 99 | * Get the name of the route matching the current window URL, or, given a route name 100 | * and parameters, check if the current window URL and parameters match that route. 101 | * 102 | * @example 103 | * // at URL https://ziggy.dev/posts/4 with 'posts.show' route 'posts/{post}' 104 | * route().current(); // 'posts.show' 105 | * route().current('posts.index'); // false 106 | * route().current('posts.show'); // true 107 | * route().current('posts.show', { post: 1 }); // false 108 | * route().current('posts.show', { post: 4 }); // true 109 | * 110 | * @param {String} [name] - Route name to check. 111 | * @param {(String|Number|Array|Object)} [params] - Route parameters. 112 | * @return {(Boolean|String|undefined)} 113 | */ 114 | current(name, params) { 115 | const { name: current, params: currentParams, query, route } = this._unresolve(); 116 | 117 | // If a name wasn't passed, return the name of the current route 118 | if (!name) return current; 119 | 120 | // Test the passed name against the current route, matching some 121 | // basic wildcards, e.g. passing `events.*` matches `events.show` 122 | const match = new RegExp(`^${name.replace(/\./g, '\\.').replace(/\*/g, '.*')}$`).test( 123 | current, 124 | ); 125 | 126 | if ([null, undefined].includes(params) || !match) return match; 127 | 128 | const routeObject = new Route(current, route, this._config); 129 | 130 | params = this._parse(params, routeObject); 131 | const routeParams = { ...currentParams, ...query }; 132 | 133 | // If the current window URL has no route parameters, and the passed parameters are empty, return true 134 | if ( 135 | Object.values(params).every((p) => !p) && 136 | !Object.values(routeParams).some((v) => v !== undefined) 137 | ) 138 | return true; 139 | 140 | const isSubset = (subset, full) => { 141 | return Object.entries(subset).every(([key, value]) => { 142 | if (Array.isArray(value) && Array.isArray(full[key])) { 143 | return value.every((v) => full[key].includes(v)); 144 | } 145 | 146 | if ( 147 | typeof value === 'object' && 148 | typeof full[key] === 'object' && 149 | value !== null && 150 | full[key] !== null 151 | ) { 152 | return isSubset(value, full[key]); 153 | } 154 | 155 | return full[key] == value; 156 | }); 157 | }; 158 | 159 | // Check that all passed parameters match their values in the current window URL 160 | // Use weak equality because all values in the current window URL will be strings 161 | return isSubset(params, routeParams); 162 | } 163 | 164 | /** 165 | * Get an object representing the current location (by default this will be 166 | * the JavaScript `window` global if it's available). 167 | * 168 | * @return {Object} 169 | */ 170 | _location() { 171 | const { 172 | host = '', 173 | pathname = '', 174 | search = '', 175 | } = typeof window !== 'undefined' ? window.location : {}; 176 | 177 | return { 178 | host: this._config.location?.host ?? host, 179 | pathname: this._config.location?.pathname ?? pathname, 180 | search: this._config.location?.search ?? search, 181 | }; 182 | } 183 | 184 | /** 185 | * Get all parameter values from the current window URL. 186 | * 187 | * @example 188 | * // at URL https://tighten.ziggy.dev/posts/4?lang=en with 'posts.show' route 'posts/{post}' and domain '{team}.ziggy.dev' 189 | * route().params; // { team: 'tighten', post: 4, lang: 'en' } 190 | * 191 | * @return {Object} 192 | */ 193 | get params() { 194 | const { params, query } = this._unresolve(); 195 | 196 | return { ...params, ...query }; 197 | } 198 | 199 | get routeParams() { 200 | return this._unresolve().params; 201 | } 202 | 203 | get queryParams() { 204 | return this._unresolve().query; 205 | } 206 | 207 | /** 208 | * Check whether the given route exists. 209 | * 210 | * @param {String} name 211 | * @return {Boolean} 212 | */ 213 | has(name) { 214 | return this._config.routes.hasOwnProperty(name); 215 | } 216 | 217 | /** 218 | * Parse Laravel-style route parameters of any type into a normalized object. 219 | * 220 | * @example 221 | * // with route parameter names 'event' and 'venue' 222 | * _parse(1); // { event: 1 } 223 | * _parse({ event: 2, venue: 3 }); // { event: 2, venue: 3 } 224 | * _parse(['Taylor', 'Matt']); // { event: 'Taylor', venue: 'Matt' } 225 | * _parse([4, { uuid: 56789, name: 'Grand Canyon' }]); // { event: 4, venue: 56789 } 226 | * 227 | * @param {(String|Number|Array|Object)} params - Route parameters. 228 | * @param {Route} route - Route instance. 229 | * @return {Object} Normalized complete route parameters. 230 | */ 231 | _parse(params = {}, route = this._route) { 232 | params ??= {}; 233 | 234 | // If `params` is a string or integer, wrap it in an array 235 | params = ['string', 'number'].includes(typeof params) ? [params] : params; 236 | 237 | // Separate segments with and without defaults, and fill in the default values 238 | const segments = route.parameterSegments.filter(({ name }) => !this._config.defaults[name]); 239 | 240 | if (Array.isArray(params)) { 241 | // If the parameters are an array they have to be in order, so we can transform them into 242 | // an object by keying them with the template segment names in the order they appear 243 | params = params.reduce( 244 | (result, current, i) => 245 | segments[i] 246 | ? { ...result, [segments[i].name]: current } 247 | : typeof current === 'object' 248 | ? { ...result, ...current } 249 | : { ...result, [current]: '' }, 250 | {}, 251 | ); 252 | } else if ( 253 | segments.length === 1 && 254 | !params[segments[0].name] && 255 | (params.hasOwnProperty(Object.values(route.bindings)[0]) || params.hasOwnProperty('id')) 256 | ) { 257 | // If there is only one template segment and `params` is an object, that object is 258 | // ambiguous—it could contain the parameter key and value, or it could be an object 259 | // representing just the value (e.g. a model); we can inspect it to find out, and 260 | // if it's just the parameter value, we can wrap it in an object with its key 261 | params = { [segments[0].name]: params }; 262 | } 263 | 264 | return { 265 | ...this._defaults(route), 266 | ...this._substituteBindings(params, route), 267 | }; 268 | } 269 | 270 | /** 271 | * Populate default parameters for the given route. 272 | * 273 | * @example 274 | * // with default parameters { locale: 'en', country: 'US' } and 'posts.show' route '{locale}/posts/{post}' 275 | * defaults(...); // { locale: 'en' } 276 | * 277 | * @param {Route} route 278 | * @return {Object} Default route parameters. 279 | */ 280 | _defaults(route) { 281 | return route.parameterSegments 282 | .filter(({ name }) => this._config.defaults[name]) 283 | .reduce( 284 | (result, { name }, i) => ({ ...result, [name]: this._config.defaults[name] }), 285 | {}, 286 | ); 287 | } 288 | 289 | /** 290 | * Substitute Laravel route model bindings in the given parameters. 291 | * 292 | * @example 293 | * _substituteBindings({ post: { id: 4, slug: 'hello-world', title: 'Hello, world!' } }, { bindings: { post: 'slug' } }); // { post: 'hello-world' } 294 | * 295 | * @param {Object} params - Route parameters. 296 | * @param {Object} route - Route definition. 297 | * @return {Object} Normalized route parameters. 298 | */ 299 | _substituteBindings(params, { bindings, parameterSegments }) { 300 | return Object.entries(params).reduce((result, [key, value]) => { 301 | // If the value isn't an object, or if the key isn't a named route parameter, 302 | // there's nothing to substitute so we return it as-is 303 | if ( 304 | !value || 305 | typeof value !== 'object' || 306 | Array.isArray(value) || 307 | !parameterSegments.some(({ name }) => name === key) 308 | ) { 309 | return { ...result, [key]: value }; 310 | } 311 | 312 | if (!value.hasOwnProperty(bindings[key])) { 313 | if (value.hasOwnProperty('id')) { 314 | // As a fallback, we still accept an 'id' key not explicitly registered as a binding 315 | bindings[key] = 'id'; 316 | } else { 317 | throw new Error( 318 | `Ziggy error: object passed as '${key}' parameter is missing route model binding key '${bindings[key]}'.`, 319 | ); 320 | } 321 | } 322 | 323 | return { ...result, [key]: value[bindings[key]] }; 324 | }, {}); 325 | } 326 | 327 | valueOf() { 328 | return this.toString(); 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /src/js/browser.js: -------------------------------------------------------------------------------- 1 | export { route as default } from './index.js'; 2 | -------------------------------------------------------------------------------- /src/js/index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A list of routes and their parameters and bindings. 3 | * 4 | * Extended and filled by the route list generated with `php artisan ziggy:generate --types`. 5 | */ 6 | export interface RouteList {} 7 | 8 | /** 9 | * Marker interface to configure Ziggy's type checking behavior. 10 | */ 11 | export interface TypeConfig {} 12 | 13 | /** 14 | * A route name registered with Ziggy. 15 | */ 16 | type KnownRouteName = keyof RouteList; 17 | 18 | /** 19 | * A route name, or any string. 20 | */ 21 | type RouteName = KnownRouteName | (string & {}); 22 | // `(string & {})` prevents TypeScript from reducing this type to just `string`, 23 | // which would prevent intellisense from autocompleting known route names. 24 | // See https://stackoverflow.com/a/61048124/6484459. 25 | 26 | /** 27 | * A valid route name to pass to `route()` to generate a URL. 28 | */ 29 | type ValidRouteName = TypeConfig extends { strictRouteNames: true } ? KnownRouteName : RouteName; 30 | 31 | /** 32 | * Information about a single route parameter. 33 | */ 34 | type ParameterInfo = { name: string; required: boolean; binding?: string }; 35 | 36 | /** 37 | * A primitive route parameter value, as it would appear in a URL. 38 | */ 39 | type RawParameterValue = string | number; 40 | // TODO: Technically booleans work too, does it make sense to add them? Here? What would that look like? 41 | 42 | /** 43 | * An object parameter value containing the 'default' binding key `id`, e.g. representing an Eloquent model. 44 | */ 45 | type DefaultRoutable = { id: RawParameterValue } & Record; 46 | 47 | /** 48 | * A route parameter value. 49 | */ 50 | type ParameterValue = RawParameterValue | DefaultRoutable; 51 | 52 | /** 53 | * A parseable route parameter, either plain or nested inside an object under its binding key. 54 | */ 55 | type Routable = I extends { binding: string } 56 | ? ({ [K in I['binding']]: RawParameterValue } & Record) | RawParameterValue 57 | : ParameterValue; 58 | 59 | // Uncomment to test: 60 | // type A = Routable<{ name: 'foo', required: true, binding: 'bar' }>; 61 | // = RawParameterValue | { bar: RawParameterValue } 62 | // type B = Routable<{ name: 'foo', required: true, }>; 63 | // = RawParameterValue | DefaultRoutable 64 | 65 | // Utility types for KnownRouteParamsObject 66 | type RequiredParams = Extract; 67 | type OptionalParams = Extract; 68 | 69 | /** 70 | * An object containing a special '_query' key to target the query string of a URL. 71 | */ 72 | type HasQueryParam = { _query?: Record }; 73 | /** 74 | * An object of parameters for an unspecified route. 75 | */ 76 | type GenericRouteParamsObject = Record & HasQueryParam; 77 | // `keyof any` essentially makes it function as a plain `Record` 78 | /** 79 | * An object of parameters for a specific named route. 80 | */ 81 | type KnownRouteParamsObject = { 82 | [T in RequiredParams as T['name']]: Routable; 83 | } & { 84 | [T in OptionalParams as T['name']]?: Routable; 85 | } & GenericRouteParamsObject; 86 | // `readonly` allows TypeScript to determine the actual values of all the 87 | // parameter names inside the array, instead of just seeing `string`. 88 | // See https://github.com/tighten/ziggy/pull/664#discussion_r1329978447. 89 | 90 | // Uncomment to test: 91 | // type A = KnownRouteParamsObject<[{ name: 'foo'; required: true }, { name: 'bar'; required: false }]>; 92 | // = { foo: ... } & { bar?: ... } 93 | 94 | /** 95 | * An object of route parameters. 96 | */ 97 | type RouteParamsObject = N extends KnownRouteName 98 | ? KnownRouteParamsObject 99 | : GenericRouteParamsObject; 100 | 101 | /** 102 | * An array of parameters for an unspecified route. 103 | */ 104 | // TODO: this may be able to be more specific, like `Routable[]`, 105 | // depending how we want to handle nested objects inside parameter arrays 106 | type GenericRouteParamsArray = unknown[]; 107 | /** 108 | * An array of parameters for a specific named route. 109 | */ 110 | type KnownRouteParamsArray = [ 111 | ...{ [K in keyof I]: Routable }, 112 | ...unknown[], 113 | ]; 114 | // Because `K in keyof I` for a `readonly` array is always a number, even though this 115 | // looks like `{ 0: T, 1: U, 2: V }` TypeScript generates `[T, U, V]`. The nested 116 | // array destructing lets us type the first n items in the array, which are known 117 | // route parameters, and then allow arbitrary additional items. 118 | // See https://github.com/tighten/ziggy/pull/664#discussion_r1330002370. 119 | 120 | // Uncomment to test: 121 | // type B = KnownRouteParamsArray<[{ name: 'post'; required: true; binding: 'uuid' }]>; 122 | // = [RawParameterValue | { uuid: RawParameterValue }, ...unknown[]] 123 | 124 | /** 125 | * An array of route parameters. 126 | */ 127 | type RouteParamsArray = N extends KnownRouteName 128 | ? KnownRouteParamsArray 129 | : GenericRouteParamsArray; 130 | 131 | /** 132 | * All possible parameter argument shapes for a route. 133 | */ 134 | type RouteParams = RouteParamsObject | RouteParamsArray; 135 | 136 | /** 137 | * A route. 138 | */ 139 | interface Route { 140 | uri: string; 141 | methods: ('GET' | 'HEAD' | 'POST' | 'PATCH' | 'PUT' | 'OPTIONS' | 'DELETE')[]; 142 | domain?: string; 143 | parameters?: string[]; 144 | bindings?: Record; 145 | wheres?: Record; 146 | middleware?: string[]; 147 | } 148 | 149 | /** 150 | * Ziggy's config object. 151 | */ 152 | interface Config { 153 | url: string; 154 | port: number | null; 155 | defaults: Record; 156 | routes: Record; 157 | location?: { 158 | host?: string; 159 | pathname?: string; 160 | search?: string; 161 | }; 162 | } 163 | 164 | // qs's parsed query params type, so we don't have to have qs as a dependency 165 | interface ParsedQs { 166 | [key: string]: undefined | string | string[] | ParsedQs | ParsedQs[]; 167 | } 168 | 169 | /** 170 | * Ziggy's Router class. 171 | */ 172 | interface Router { 173 | current(): ValidRouteName | undefined; 174 | current(name: T, params?: ParameterValue | RouteParams): boolean; 175 | get params(): Record; 176 | get routeParams(): Record; 177 | get queryParams(): ParsedQs; 178 | has(name: T): boolean; 179 | } 180 | 181 | /** 182 | * Ziggy's route helper. 183 | */ 184 | // Called with no arguments - returns a Router instance 185 | export function route(): Router; 186 | 187 | // Called with configuration arguments only - returns a configured Router instance 188 | export function route( 189 | name: undefined, 190 | params: undefined, 191 | absolute?: boolean, 192 | config?: Config, 193 | ): Router; 194 | 195 | // Called with a route name and optional additional arguments - returns a URL string 196 | export function route( 197 | name: T, 198 | params?: RouteParams | undefined, 199 | absolute?: boolean, 200 | config?: Config, 201 | ): string; 202 | 203 | export function route( 204 | name: T, 205 | params?: ParameterValue | undefined, 206 | absolute?: boolean, 207 | config?: Config, 208 | ): string; 209 | 210 | /** 211 | * Ziggy's Vue plugin. 212 | */ 213 | export const ZiggyVue: { 214 | install(app: any, options?: Config): void; 215 | }; 216 | 217 | /** 218 | * Ziggy's React hook. 219 | */ 220 | export function useRoute(defaultConfig?: Config): typeof route; 221 | -------------------------------------------------------------------------------- /src/js/index.js: -------------------------------------------------------------------------------- 1 | import Router from './Router.js'; 2 | 3 | export function route(name, params, absolute, config) { 4 | const router = new Router(name, params, absolute, config); 5 | 6 | return name ? router.toString() : router; 7 | } 8 | 9 | export const ZiggyVue = { 10 | install(app, options) { 11 | const r = (name, params, absolute, config = options) => 12 | route(name, params, absolute, config); 13 | 14 | if (parseInt(app.version) > 2) { 15 | app.config.globalProperties.route = r; 16 | app.provide('route', r); 17 | } else { 18 | app.mixin({ 19 | methods: { 20 | route: r, 21 | }, 22 | }); 23 | } 24 | }, 25 | }; 26 | 27 | export function useRoute(defaultConfig) { 28 | if (!defaultConfig && !globalThis.Ziggy && typeof Ziggy === 'undefined') { 29 | throw new Error( 30 | 'Ziggy error: missing configuration. Ensure that a `Ziggy` variable is defined globally or pass a config object into the useRoute hook.', 31 | ); 32 | } 33 | 34 | return (name, params, absolute, config = defaultConfig) => 35 | route(name, params, absolute, config); 36 | } 37 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "moduleResolution": "Bundler", 5 | "target": "ESNext" 6 | }, 7 | "exclude": ["vendor", "tests/fixtures"] 8 | } 9 | --------------------------------------------------------------------------------