├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ └── publish.yml ├── .gitignore ├── .nvmrc ├── LICENSE ├── README.md ├── example ├── index.html ├── page-1.html ├── script.js └── styles.css ├── index.js ├── index.test.js ├── package-lock.json └── package.json /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "monthly" 8 | assignees: 9 | - "jgarber623" 10 | - package-ecosystem: "npm" 11 | directory: "/" 12 | schedule: 13 | interval: "monthly" 14 | assignees: 15 | - "jgarber623" 16 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | workflow_call: 6 | workflow_dispatch: 7 | 8 | jobs: 9 | test: 10 | name: Test (Node.js v${{ matrix.version }}) 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | version: [18, 20, 22] 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: actions/setup-node@v4 18 | with: 19 | node-version: ${{ matrix.version }} 20 | cache: npm 21 | - run: npm ci 22 | - run: npm test 23 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | ci: 9 | name: CI 10 | uses: ./.github/workflows/ci.yml 11 | publish-to-npm: 12 | name: Publish to npm 13 | permissions: 14 | contents: read 15 | id-token: write 16 | needs: ci 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: actions/setup-node@v4 21 | with: 22 | node-version-file: ".nvmrc" 23 | cache: npm 24 | registry-url: https://registry.npmjs.org 25 | - run: npm ci 26 | - run: npm publish 27 | env: 28 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 29 | publish-to-github-packages: 30 | name: Publish to GitHub Packages 31 | permissions: 32 | contents: read 33 | id-token: write 34 | packages: write 35 | needs: ci 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@v4 39 | - name: Change package scope 40 | run: | 41 | sed -i -E "s/@.+(\/routerrouter)/@${{ github.repository_owner }}\1/" package.json 42 | cat package.json 43 | - uses: actions/setup-node@v4 44 | with: 45 | node-version-file: ".nvmrc" 46 | cache: npm 47 | registry-url: https://npm.pkg.github.com 48 | - run: npm ci 49 | - run: npm publish 50 | env: 51 | NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /coverage/ 2 | /node_modules/ 3 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/jod 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2013 Jason Garber 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 | > [!IMPORTANT] 2 | > This project has moved to [codeberg.org/jgarber/RouterRouter](https://codeberg.org/jgarber/RouterRouter). 3 | 4 | # RouterRouter 5 | 6 | **A very small JavaScript routing library extracted from [Backbone's Router](http://backbonejs.org/docs/backbone.html#section-185).** 7 | 8 | [![npm](https://img.shields.io/npm/v/@jgarber/routerrouter.svg?logo=npm&style=for-the-badge)](https://www.npmjs.com/package/@jgarber/routerrouter) 9 | [![Downloads](https://img.shields.io/npm/dt/@jgarber/routerrouter.svg?logo=npm&style=for-the-badge)](https://www.npmjs.com/package/@jgarber/routerrouter) 10 | [![Build](https://img.shields.io/github/actions/workflow/status/jgarber623/RouterRouter/ci.yml?branch=main&logo=github&style=for-the-badge)](https://github.com/jgarber623/RouterRouter/actions/workflows/ci.yml) 11 | 12 | Using a modified version of Backbone's routing code, RouterRouter provides Backbone-style route definition while remaining a small, standalone, dependency-free library. RouterRouter maps specified routes (the value returned from `window.location.pathname`) to user-defined actions. This approach may be useful for websites with predictable URLs and modular, component-specific JavaScript. 13 | 14 | > [!NOTE] 15 | > RouterRouter is feature complete and will only be updated to address bugs or security issues. 16 | 17 | ### Key Features 18 | 19 | - Inspired by Backbone's routing API 20 | - Dependency-free 21 | - JavaScript module (ESM) 22 | 23 | ## Getting RouterRouter 24 | 25 | You've got a couple options for adding RouterRouter to your project: 26 | 27 | - [Download a release](https://github.com/jgarber623/RouterRouter/releases) from GitHub and do it yourself _(old school)_. 28 | - Install using [npm](https://www.npmjs.com/package/@jgarber/routerrouter): `npm install @jgarber/routerrouter --save` 29 | - Install using [Yarn](https://yarnpkg.com/en/package/@jgarber/routerrouter): `yarn add @jgarber/routerrouter` 30 | 31 | ## Usage 32 | 33 | ### Basic 34 | 35 | A basic example, matching a route: 36 | 37 | ```js 38 | const router = new RouterRouter(); 39 | 40 | // matches https://example.com/posts 41 | router.route("/posts", () => console.log("Hello!")); 42 | ``` 43 | 44 | Another example, this time using a named parameter to match a route: 45 | 46 | ```js 47 | const router = new RouterRouter(); 48 | 49 | // matches https://example.com/posts/hello-world and logs "hellow-world" 50 | router.route("/posts/:slug", (slug) => console.log(slug)); 51 | ``` 52 | 53 | RouterRouter supports a number of different matchers which are outlined below in the [Pattern Matching](#pattern-matching) section. 54 | 55 | ### Advanced 56 | 57 | A more complex example, demonstrating an alternative method of defining routes and actions: 58 | 59 | ```js 60 | const router = new RouterRouter({ 61 | // Routes are defined in the `routes` object: 62 | routes: { 63 | // Actions may be defined inline: 64 | "/": () => { 65 | console.log("This route matches the root URL"); 66 | }, 67 | 68 | // Routes may also be mapped to named actions: 69 | "/posts": "postsPageAction", 70 | 71 | // Matched patterns in routes are passed to actions 72 | // in the order they appear in the route: 73 | "/posts/:year/:month/:slug", "postPageAction" 74 | }, 75 | 76 | postPageAction: (year, month, slug) => { 77 | // Logs strings like "2018", "06", "hello-world" 78 | console.log(year, month, slug); 79 | }, 80 | 81 | postsPageAction: () => { 82 | console.log("This route matches the /posts URL"); 83 | } 84 | }); 85 | ``` 86 | 87 | ## Pattern Matching 88 | 89 | RouterRouter will match URL patterns similar to [Backbone's Router](http://backbonejs.org/#Router) with the notable exception that routes _must_ begin with a slash (`/`). 90 | 91 | **Pro Tip:** It's possible to define multiple routes and actions that match similar URLs (e.g. `/posts` and `/:section`). This could lead to confusion, though, so be judicious when defining routes and actions. 92 | 93 | ### String Matching 94 | 95 | | Route | Matched URLs | 96 | |:---------------------|:-------------| 97 | | `/` | https://example.com | 98 | | `/posts` | https://example.com/posts | 99 | | `/posts/hello-world` | https://example.com/posts/hello-world | 100 | 101 | ### Named Parameters 102 | 103 | Named parameters match patterns in routes and pass the captured values to the associated action for reuse. Captured values are passed to the action in the order they appear in the route. Named parameters are limited to strings of characters appearing _between_ slashes (`/`) in a URL. 104 | 105 | | Route | Matched URLs | Matched Patterns | 106 | |:------------------------|:--------------------------------------------|:-----------------------------| 107 | | `/posts/:slug` | https://example.com/posts/hello-world | `hello-world` | 108 | | `/:section/:subsection` | https://example.com/solar-systems/milky-way | `solar-systems`, `milky-way` | 109 | 110 | ### Wildcard Parameters 111 | 112 | Wildcard parameters match patterns in routes _including_ slashes (`/`) in URLs. For clarity, wildcard parameters may optionally include a named identifier (e.g. `*wildcard_parameter`). Similar to named parameters, captured values are passed to the action in the order they appear in the route and may include slashes. 113 | 114 | | Route | Matched URLs | Matched Patterns | 115 | |:--------------------------|:------------------------------------ |:-----------------| 116 | | `/posts/*/06/*` | https://example.com/posts/2018/06/23 | `2018`, `23` | 117 | | `/posts/*years_months/23` | https://example.com/posts/2018/06/23 | `2018/06` | 118 | 119 | ### Optional Parameters 120 | 121 | Optional parameters match patterns in routes conditionally and pass captured values to the specified action. By default, matched optional parameters _are not_ captured. In cases where an optional parameter _does not_ appear in the URL, `null` is passed to the action. Optional parameter matching is rather powerful and complex, so use this feature with care! 122 | 123 | | Route | Matched URLs | Matched Patterns | 124 | |:-------------------|:--------------------------------------|:-----------------| 125 | | `/posts(/)` | https://example.com/posts | | 126 | | | https://example.com/posts/ | | 127 | | `/posts(/:slug)` | https://example.com/posts | | 128 | | | https://example.com/posts/hello-world | `hello-world` | 129 | | `(/:section)/2018` | https://example.com/2018 | | 130 | | | https://example.com/posts/2018 | `posts` | 131 | | | https://example.com/archives/2018 | `archives` | 132 | 133 | ### Regular Expressions 134 | 135 | RouterRouter supports route definitions using [regular expressions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions). Regular expression matching may be as simple or complex as necessary and non-passive captured groups will be passed to the mapped action in the order they appear in the regular expression. 136 | 137 | **Pro Tip:** Routes defined using regular expressions are not limited to matching the _entire_ value of `window.location.pathname` and therefore do not need to begin with a slash (`/`). This feature can be used to interesting effect. 138 | 139 | ```js 140 | const router = new RouterRouter(); 141 | 142 | router.route(/\/comments\/?$/, () => { 143 | console.log("This route matches URLs ending in /comments or /comments/"); 144 | }); 145 | 146 | // Logs "links", "photos", or "posts" 147 | router.route(/^\/(links|photos|posts)\/(?:.*)$/, (section) => console.log(section)); 148 | ``` 149 | 150 | ### Examples 151 | 152 | For a full-featured RouterRouter demonstration, check out [the demo page](https://jgarber623.github.io/RouterRouter/example/) and [the example files](https://github.com/jgarber623/RouterRouter/tree/main/example). 153 | 154 | ## Browser Support 155 | 156 | **RouterRouter works in modern browsers.** The library makes use of several new(ish) JavaScript features and, in an effort to remain as lightweight and dependency-free as possible, leaves it up to you to choose whether or not to polyfill features for older browsers. 157 | 158 | ## Limitations 159 | 160 | RouterRouter matches the portion of a URL returned by `window.location.pathname`. This _does not_ include other aspects of [the `Location` interface](https://developer.mozilla.org/en-US/docs/Web/API/location) like query parameters (e.g. `?search=why+does+the+sun+shine`). Within an action, the `Location` interface may be used directly (`window.location`) or indirectly: 161 | 162 | ```js 163 | const router = new RouterRouter(); 164 | 165 | // Logs the internally cached version of `window.location` 166 | console.log(router.location); 167 | ``` 168 | 169 | RouterRouter doesn't natively support the [HTML5 History API](http://diveintohtml5.info/history.html). It may be possible to use RouterRouter with this feature, but for now, keeping the library as small as possible remains the project's primary goal. 170 | 171 | ## Acknowledgments 172 | 173 | Credit for the really difficult parts of RouterRouter goes to Jeremy Ashkenas, DocumentCloud, Investigative Reporters & Editors, and everyone else who has contributed code to Backbone. 174 | 175 | RouterRouter is written and maintained by [Jason Garber](https://sixtwothree.org) and is another in a growing collection of small, curiously-named JavaScript utilities: 176 | 177 | - [CashCash](https://github.com/jgarber623/CashCash), a very small DOM library inspired by [jQuery](https://jquery.com). 178 | - [RadioRadio](https://github.com/jgarber623/RadioRadio), a very small [PubSub](https://en.wikipedia.org/wiki/Publish–subscribe_pattern) library. 179 | - [TemplateTemplate](https://github.com/jgarber623/TemplateTemplate), a very small `