{"id":11046288,"url":"https://github.com/alshdavid/crayon-router","last_synced_at":"2025-04-06T15:12:55.597Z","repository":{"id":57210553,"uuid":"185551789","full_name":"alshdavid/crayon-router","owner":"alshdavid","description":"Simple framework agnostic UI router for SPAs","archived":false,"fork":false,"pushed_at":"2023-03-20T08:29:26.000Z","size":4722,"stargazers_count":327,"open_issues_count":5,"forks_count":13,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-30T13:09:07.162Z","etag":null,"topics":["react","router","spark","svelte","svelte-v3","vue"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alshdavid.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":"docs/contributing.md","funding":null,"license":"license","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2019-05-08T07:09:15.000Z","updated_at":"2024-06-11T23:49:24.000Z","dependencies_parsed_at":"2024-01-14T16:07:48.002Z","dependency_job_id":"0c0104b1-4d35-49f0-b913-c7fa6d9dc25b","html_url":"https://github.com/alshdavid/crayon-router","commit_stats":null,"previous_names":["alshdavid/crayon-router"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alshdavid%2Fcrayon-router","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alshdavid%2Fcrayon-router/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alshdavid%2Fcrayon-router/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alshdavid%2Fcrayon-router/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alshdavid","download_url":"https://codeload.github.com/alshdavid/crayon-router/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247500469,"owners_count":20948880,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["react","router","spark","svelte","svelte-v3","vue"],"created_at":"2024-06-12T04:08:00.169Z","updated_at":"2025-04-06T15:12:55.566Z","avatar_url":"https://github.com/alshdavid.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"\u003cimg align=\"left\" width=\"350px\" src=\"https://cdn.davidalsh.com/crayon/logo.png\"\u003e\n\u003cimg align=\"right\" width=\"350px\" src=\"https://cdn.davidalsh.com/crayon/crayon.gif\"\u003e\n\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\n## SPA Router, for all Frameworks\n\n![version](https://cdn.davidalsh.com/crayon/badges/version.svg)\n![size](https://cdn.davidalsh.com/crayon/badges/size.svg)\n![coverage](https://img.shields.io/badge/license-MIT-green.svg?cacheSeconds=2592000)\n![dependencies](https://img.shields.io/badge/dependencies-0-orange.svg?cacheSeconds=2592000)\n![coverage](https://img.shields.io/badge/test%20coverage-91.42%25-green.svg?cacheSeconds=2592000)\n\n- Clientside Router\n- Express like syntax\n- Select your framework with middleware\n- Select your animations with middleware\n- No dependencies\n\n### Example\n\n```jsx\nimport React from 'react'\nimport crayon from 'crayon'\nimport react from 'crayon-react'\n\nconst app = crayon.create()\n\napp.use(react.router())\n\napp.path('/', ctx =\u003e {\n    return ctx.mount(() =\u003e \u003ch1\u003eHello World\u003c/h1\u003e)\n})\n\napp.path('/users/:id', ctx =\u003e {\n    return ctx.mount(() =\u003e \u003cdiv\u003eHi { ctx.params.id }!\u003c/div\u003e)\n})\n\napp.path('/**', ctx =\u003e {\n    return ctx.mount(() =\u003e \u003cdiv\u003eNot Found!\u003c/div\u003e)\n})\n\napp.load()\n```\n\nTo nagivate use:\n\n```javascript\napp.navigate('/users/27')\n```\n\n### Introduction and Explanation\n\nCrayon is a simple client-side UI router that uses a time-tested and familiar pattern to route actions based on browser paths.\n\nThe routing style is seen in serverside frameworks like Express in Node and Gin in Go, so it's nothing new - but a tool I felt browser-based applications were lacking.\n\nWhile the router itself is only responsible for running a callback which it selects based on pattern matching the browser's path, you are able to compose behaviours using middleware.\n\nThis means that the front-end framework or animations you choose are a middleware concern, not a routing concern.\n\n*The philosophy behind Crayon is to ask less of our front-end frameworks, but get more*\n\n[Contributing Guide](docs/contributing.md)\n\n### Installing\n\n\n\n```bash\nnpm install --save crayon\n```\n\n### Framework Middlewares\n\n\u003cbr\u003e\n\u003cimg width=\"150px\" src=\"https://cdn.davidalsh.com/frameworks/react.png\"\u003e\n\u003cbr\u003e\n\n```bash\nnpm install --save crayon-react\n```\n\n```javascript\nimport react from 'crayon-react'\napp.use(react.router())\n```\n\u003cbr\u003e\n\u003cimg width=\"190px\" src=\"https://cdn.davidalsh.com/frameworks/preact.png\"\u003e\n\u003cbr\u003e\n\n```bash\nnpm install --save crayon-preact\n```\n\n```javascript\nimport preact from 'crayon-preact'\napp.use(preact.router())\n```\n\n\u003cbr\u003e\n\u003cimg width=\"150px\" src=\"https://cdn.davidalsh.com/frameworks/vue.png\"\u003e\n\u003cbr\u003e\n\n```bash\nnpm install --save crayon-vue\n```\n\n```javascript\nimport vue from 'crayon-vue'\napp.use(vue.router())\n```\n\n\u003cbr\u003e\n\u003cimg width=\"200px\" src=\"https://svelte.dev/svelte-logo-horizontal.svg\"\u003e\n\u003cbr\u003e\n\n```bash\nnpm install --save crayon-svelte\n```\n\n```javascript\nimport svelte from 'crayon-svelte'\napp.use(svelte.router())\n```\n\u003cbr\u003e\n\n### Coming soon\n\n\u003cbr\u003e\n\u003cimg width=\"200px\" src=\"https://cdn.davidalsh.com/frameworks/angular.png\"\u003e\n\u003cbr\u003e\n\u003cimg width=\"240px\" src=\"https://cdn.davidalsh.com/frameworks/customElements.png\"\u003e\n\u003cbr\u003e\n\n\n### Route Groups\n\nGroups are created using the `crayon.group` function, which creates a middleware\nof groups that you can use later\n\n```javascript\nconst items = crayon.group('/items')\n\nitems.use(your.middleware())\n\n// This will be \"/items\"\nitems.path('/', ctx =\u003e\n    ctx.mount(views.ItemsView)\n)\n\n// This will be \"/items/add\"\nitems.path('/add', ctx =\u003e\n    ctx.mount(views.ItemsAddView)\n)\n\napp.use(items)\napp.load()\n```\n\nIt also supplies an optional callback with the group object. This allows you to define variables within a scope dedicated to that group.\n\n```javascript\nconst items = crayon.group('/items', group =\u003e {\n    group.use(your.middleware())\n\n    group.path('/', ctx =\u003e\n        ctx.mount(views.ItemsView)\n    )\n\n    group.path('/add', ctx =\u003e\n        ctx.mount(views.ItemsAddView)\n    )\n})\n\napp.use(items)\napp.load()\n```\n\n### Route parameters and observing changes\n\nYou can add paramaters in the route path and observe the changes.\nThe observe method is used to prevent rerenders which can cause problems\nwhen dealing with nested routers and components that require preserved state\n\nFor the sake of reducing external dependencies and package size, I am not using\nrxjs. This uses a portion of the rxjs API to enable dealing with event streams.\n\nIn future, I intend to create a middleware that implements rxjs, allowing you to pipe\nthe stream into their operators/utilities (like .map() and .filter())\n\n```jsx\napp.path('/users/:id', ctx =\u003e {\n    let id = ctx.params.id\n\n    // subscribe to the event steam and pull out the\n    // \"ProgressEnd\" event\n    const sub = app.events.subscribe(event =\u003e {\n       if (event.type === RouterEventType.ProgressEnd) {\n           id = ctx.params.id\n       }\n    })\n\n    // A callback the router fires when you\n    // navigate away from this page\n    ctx.onLeave(() =\u003e sub.unsubscribe())\n})\n```\n\n### Nested routers\n\nThey work just fine, just be sure to destroy a router before leaving a page\n\n```javascript\nconst app = crayon.create('main')\napp.path('/dashboard/:tab', handler)\n\nconst nested = crayon.create('tab-view')\nnested.path('/dashboard/tab-a', handler)\nnested.path('/dashboard/tab-b', handler)\nnested.destroy()\n```\n\nYou would setup the nested router inside your component, targeting an element\nreference to obtain a mount-point\n\nTake a look at the example in `/examples/crayon-react-app`. It is the demo in the\nreadme gif and features a nested router as the tab view.\n\n### Animations Middleware\n\n#### This works on all frameworks\n\nRoute Transitions are done using a middleware that\napplies/removes CSS styles over the course of a routing\nevent.\n\nYou specify the \"name\" of the CSS class and the middleware\nwill add/remove the following classes:\n\n```css\n.name\n.name-exit\n.name-enter\n.name-enter-done\n.name-enter-first\n```\n\nThe middleware can be placed on the global level, on a group or inline on the route itself.\nTo declare defaults, use the following:\n\n```bash\nnpm install --save crayon-animate\n```\n\n```javascript\nimport animate from 'crayon-animate'\n\napp.use(animate.defaults({\n    name: 'css-class-name',\n    duration: 350\n}))\n```\n\nYou can specify custom rules for a few routes:\n\n```javascript\nimport animate from 'crayon-animate'\n\napp.use(animate.routes([\n    { from: '/a',  to: '/b',  name: 'slide-left' },\n    { from: '/b',  to: '/a',  name: 'slide-right' },\n    { from: '/**', to: '/c',  name: 'fade' },\n    { from: '/c',  to: '/**', name: 'fade' }\n]))\n```\n\nWhen provided inline on a route, you can omit the respecive to/from\n\n```jsx\nimport animate from 'crayon-animate'\n\napp.use(animate.defaults({\n    name: 'fade',\n    duration: 350\n}))\n\napp.path('/a', ctx =\u003e ctx.mount(() =\u003e \u003cdiv\u003eRoute A\u003c/div\u003e))\napp.path('/b', ctx =\u003e ctx.mount(() =\u003e \u003cdiv\u003eRoute B\u003c/div\u003e))\n\n// If you come from anywhere to /c slide-right\n// If you go to anywhere from /c slide-left\napp.path('/c',\n    animate.route([\n        { from: '/**', name: 'slide-right' },\n        { to:   '/**', name: 'slide-left' }\n    ]),\n    ctx =\u003e {\n        return ctx.mount(() =\u003e \u003cdiv\u003eAnimated\u003c/div\u003e)\n    }\n)\n```\n\n#### Animations package\n\nFor those who don't want to spend time writing animations, Crayon comes bundled with a bunch.\n\n```bash\nnpm install --save crayon-transition\n```\n\nJust use the middleware\n```javascript\nimport animate from 'crayon-animate'\nimport transition from 'crayon-transition'\n\napp.use(transition.loader())\napp.use(animate.defaults({\n    name: transition.pushLeft,\n    duration: 350\n}))\n```\n\n#### Available bundled animations\n\n```javascript\ntransition.fade\n\ntransition.pushUp\ntransition.pushDown\ntransition.pushLeft\ntransition.pushRight\n\ntransition.popUp\ntransition.popDown\ntransition.popLeft\ntransition.popRight\n\ntransition.slideUp\ntransition.slideDown\ntransition.slideLeft\ntransition.slideRight\n```\n\n\n\n### Code Spliting and Lazy Loading\n\nJust use the dynamic `import()` feature.\nIt's baked into modern browsers and available through module bundlers.\n\n#### Loading a route\n\n```javascript\napp.path('/', async ctx =\u003e {\n    const HomeView = await import('./home-view')\n    ctx.mount(HomeView)\n})\n```\n\n#### Code splitting a group\n\nFirst create a group in a file\n\n```javascript\n// my-group.js\nexport const myGroup = crayon.group('/my-group', myGroup =\u003e {\n    myGroup.path('/',\n        ctx =\u003e ctx.mount(MyView)\n    )\n})\n```\n\nThen load it in and use it\n\n```javascript\n// main.js\nvoid async function main() {\n    const app = crayon.create()\n    app.use(framework.loader())\n\n    const { myGroup } = await import('./my-group')\n    app.use(myGroup)\n\n    app.load()\n}()\n```\n\nLazy loading a group just requires you to trigger the load action\ninside a route handler\n\n```javascript\n// main.js\nvoid async function main() {\n    const app = crayon.create()\n    app.use(framework.loader())\n\n    // This will wait until the user is on /my-group\n    // before fetching and loading the routes into\n    // the browser\n    app.path('/my-group', async ctx =\u003e {\n        const { myGroup } = await import('./my-group')\n        app.use(myGroup)\n        app.load()\n    })\n\n    app.load()\n}()\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falshdavid%2Fcrayon-router","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falshdavid%2Fcrayon-router","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falshdavid%2Fcrayon-router/lists"}