├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── demo.yaml │ ├── docs.yaml │ └── release.yaml ├── .gitignore ├── .prettierrc ├── .vscode ├── launch.json └── settings.json ├── LICENSE ├── demo ├── .gitignore ├── assets │ └── main-it-rain.gif ├── cypress.config.ts ├── cypress │ ├── e2e │ │ └── route-activation.cy.ts │ ├── fixtures │ │ └── routes.json │ └── support │ │ ├── commands.ts │ │ └── e2e.ts ├── index.html ├── package-lock.json ├── package.json ├── src │ ├── app.css │ ├── app.svelte │ ├── lib │ │ ├── components │ │ │ ├── badge.svelte │ │ │ ├── code.svelte │ │ │ ├── container.svelte │ │ │ ├── default.svelte │ │ │ ├── file-link.svelte │ │ │ ├── inline-code.svelte │ │ │ └── routes │ │ │ │ ├── route-link.svelte │ │ │ │ ├── route-title.svelte │ │ │ │ └── route-wrapper.svelte │ │ ├── default-route-config.ts │ │ ├── router-history.ts │ │ └── session.svelte.ts │ ├── main.ts │ ├── routes │ │ ├── delayed.svelte │ │ ├── extras │ │ │ ├── dump.svelte │ │ │ ├── extras.svelte │ │ │ └── passing-down-props.svelte │ │ ├── hash │ │ │ └── hash.svelte │ │ ├── home.svelte │ │ ├── nested │ │ │ ├── level-1 │ │ │ │ ├── level-1.svelte │ │ │ │ └── level-2 │ │ │ │ │ ├── level-2.svelte │ │ │ │ │ └── level-3 │ │ │ │ │ └── level-3.svelte │ │ │ └── nested.svelte │ │ ├── not-found.svelte │ │ ├── paths-and-params │ │ │ ├── custom-not-found.svelte │ │ │ ├── display-params.svelte │ │ │ ├── paths-and-params.svelte │ │ │ └── querystring-matching.svelte │ │ ├── protected │ │ │ ├── account-state.svelte.ts │ │ │ ├── denied.svelte │ │ │ ├── login.svelte │ │ │ ├── main.svelte │ │ │ └── manage-account │ │ │ │ ├── auth-guard-fast.ts │ │ │ │ ├── auth-guard-slow.ts │ │ │ │ ├── balance.svelte │ │ │ │ ├── home.svelte │ │ │ │ ├── manage-account.svelte │ │ │ │ └── worker-client.svelte.ts │ │ └── transitions │ │ │ ├── fade.svelte │ │ │ ├── slide.svelte │ │ │ └── transitions.svelte │ └── vite-env.d.ts ├── svelte.config.ts ├── tailwind.config.ts ├── tsconfig.deployed.json ├── tsconfig.json ├── vercel.json └── vite.config.ts ├── docs ├── CNAME ├── changelog.md ├── debugging-logger.png ├── debugging.md ├── getting-started.md ├── helpers.md ├── hooks.md ├── logo.png ├── logo.svg ├── props.md ├── readme.md ├── registry.md ├── registry.png ├── routing.md ├── statuses.md └── styling.md ├── makefile ├── package-lock.json ├── package.json ├── src ├── lib │ ├── actions │ │ ├── active.svelte.ts │ │ ├── apply-classes.ts │ │ ├── options.ts │ │ └── route.svelte.ts │ ├── hash.test.ts │ ├── hash.ts │ ├── helpers │ │ ├── evaluators.test.ts │ │ ├── evaluators.ts │ │ ├── goto.ts │ │ ├── identify.ts │ │ ├── logging.ts │ │ ├── marshal.test.ts │ │ ├── marshal.ts │ │ ├── normalize.ts │ │ ├── objects.ts │ │ ├── query.ts │ │ ├── regexp.ts │ │ ├── runtime.ts │ │ ├── tracing.svelte.ts │ │ ├── urls.test.ts │ │ └── urls.ts │ ├── hooks.ts │ ├── index.ts │ ├── path.ts │ ├── query.svelte.ts │ ├── query.test.ts │ ├── registry.svelte.ts │ ├── route.svelte.ts │ ├── router-instance-config.ts │ ├── router-instance.svelte.ts │ ├── router.svelte │ ├── statuses.ts │ └── utilities.svelte.ts └── vite-env.d.ts ├── svelte.config.ts ├── tsconfig.json ├── typedoc.json ├── vite.config.ts └── vitest.setup.ts /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: mateothegreat 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. See error 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Logs** 23 | If available please provide any available logs, screenshots, etc. 24 | 25 | **Additional context** 26 | Add any other context about the problem here. 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: mateothegreat 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/workflows/demo.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: 3 | workflow_call: 4 | secrets: 5 | VERCEL_TOKEN: 6 | required: true 7 | push: 8 | tags: 9 | - "*" 10 | name: demo 11 | concurrency: 12 | group: demo 13 | cancel-in-progress: true 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: install vercel 20 | run: npm install --global vercel@latest 21 | - name: vercel pull 22 | run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} 23 | - name: vercel build 24 | run: vercel build --local-config demo/vercel.json --prod --token=${{ secrets.VERCEL_TOKEN }} 25 | - name: vercel deploy 26 | run: vercel deploy --local-config demo/vercel.json --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }} 27 | -------------------------------------------------------------------------------- /.github/workflows/docs.yaml: -------------------------------------------------------------------------------- 1 | name: docs 2 | on: 3 | workflow_dispatch: 4 | workflow_call: 5 | push: 6 | tags: 7 | - "*" 8 | permissions: 9 | id-token: write 10 | pages: write 11 | jobs: 12 | typedoc-github-pages: 13 | environment: 14 | name: github-pages 15 | url: ${{ steps.deployment.outputs.page_url }} 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Check out repo 19 | uses: actions/checkout@v4 20 | - name: Install Node.js dependencies 21 | run: npm install 22 | - name: Build documentation 23 | run: npm run docs:build 24 | - name: Upload Pages artifact 25 | uses: actions/upload-pages-artifact@v3.0.1 26 | with: 27 | path: tmp/build 28 | - id: deployment 29 | name: Deploy documentation to GitHub Pages 30 | uses: actions/deploy-pages@v4.0.5 31 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: 3 | push: 4 | tags: 5 | - "*" 6 | name: release 7 | concurrency: 8 | group: release 9 | cancel-in-progress: true 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | permissions: 14 | contents: write 15 | packages: write 16 | steps: 17 | - name: cicd app auth 18 | id: app 19 | uses: actions/create-github-app-token@v1 20 | with: 21 | app-id: ${{ secrets.CICD_APP_ID }} 22 | private-key: ${{ secrets.CICD_APP_PRIVATE_KEY }} 23 | - uses: actions/checkout@v4 24 | with: 25 | fetch-depth: 0 26 | persist-credentials: false 27 | - name: Setup Node.js 28 | uses: actions/setup-node@v3 29 | with: 30 | node-version: 20.x 31 | - name: generate changelog 32 | uses: orhun/git-cliff-action@v4.4.2 33 | id: git-cliff 34 | with: 35 | config: cliff.toml 36 | args: -vv --latest --strip header 37 | env: 38 | OUTPUT: tmp/changelog.md 39 | GITHUB_REPO: ${{ github.repository }} 40 | - name: bump version and push tag 41 | id: tag 42 | uses: mathieudutour/github-tag-action@v6.2 43 | with: 44 | github_token: ${{ secrets.GITHUB_TOKEN }} 45 | tag_prefix: "" 46 | - name: configure git 47 | run: | 48 | git config --global user.name "svelte5-router[bot]" 49 | git config --global user.email "svelte5-router[bot]@users.noreply.github.com" 50 | git remote set-url origin https://x-access-token:${{ steps.app.outputs.token }}@github.com/${{ github.repository }}.git 51 | - name: npm version 52 | run: npm version ${{ steps.tag.outputs.new_tag }} --force 53 | - name: concat changelogs 54 | run: | 55 | cat ${{ steps.git-cliff.outputs.changelog }} > tmp/new_changelog.md 56 | cat docs/changelog.md >> tmp/new_changelog.md 57 | mv tmp/new_changelog.md docs/changelog.md 58 | - name: commit 59 | run: | 60 | git add docs/changelog.md package.json 61 | git commit -am "chore(release): update changelog and bumping package.json" 62 | git push origin HEAD:main 63 | - name: write to .npmrc 64 | run: echo "//registry.npmjs.org/:_authToken=${{secrets.NPM_TOKEN}}" > ~/.npmrc 65 | - name: install 66 | run: npm install 67 | - name: build 68 | run: npm run build 69 | - name: publish 70 | run: cd dist && npm publish 71 | - name: create github release 72 | uses: softprops/action-gh-release@v2 73 | with: 74 | body: ${{ steps.git-cliff.outputs.content }} 75 | tag_name: ${{ steps.tag.outputs.new_tag }} 76 | docs: 77 | needs: build 78 | permissions: 79 | id-token: write 80 | pages: write 81 | uses: ./.github/workflows/docs.yaml 82 | demo: 83 | needs: build 84 | uses: ./.github/workflows/demo.yaml 85 | secrets: inherit 86 | notify: 87 | if: always() 88 | needs: 89 | - build 90 | runs-on: ubuntu-latest 91 | steps: 92 | - uses: sarisia/actions-status-discord@v1 93 | with: 94 | status: ${{ needs.build.result }} 95 | webhook: ${{ secrets.DISCORD_WEBHOOK }} 96 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | tmp 4 | .* 5 | !.github -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "singleQuote": false, 5 | "trailingComma": "none", 6 | "printWidth": 120, 7 | "endOfLine": "lf", 8 | "pluginSearchDirs": false, 9 | "overrides": [ 10 | { 11 | "files": "*.svelte", 12 | "options": { 13 | "parser": "svelte", 14 | "svelteIndentScriptAndStyle": true, 15 | "svelteStrictMode": false, 16 | "svelteSortOrder": "scripts-markup-styles-options", 17 | "svelteBracketNewLine": false 18 | } 19 | }, 20 | { 21 | "files": "*.md", 22 | "options": { 23 | "tabWidth": 2, 24 | "useTabs": false, 25 | "printWidth": 79 26 | } 27 | }, 28 | { 29 | "files": "*.html", 30 | "options": { 31 | "parser": "html" 32 | } 33 | }, 34 | { 35 | "files": "*.ts", 36 | "options": { 37 | "parser": "typescript" 38 | } 39 | }, 40 | { 41 | "files": "*", 42 | "options": { 43 | "tabWidth": 2 44 | } 45 | } 46 | ], 47 | "bracketSameLine": true, 48 | "singleAttributePerLine": true, 49 | "htmlWhitespaceSensitivity": "ignore" 50 | } 51 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "chrome", 6 | "request": "launch", 7 | "name": "Debug App", 8 | "url": "http://localhost:5173/params/one", 9 | "webRoot": "${workspaceFolder}/src", 10 | "sourceMapPathOverrides": { 11 | "webpack:///./src/*": "${webRoot}/*" 12 | }, 13 | "runtimeArgs": [ 14 | "--remote-debugging-port=9222" 15 | ], 16 | "sourceMaps": true 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Matthew Davis 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 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /demo/assets/main-it-rain.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mateothegreat/svelte5-router/b9b7b24f9b7ace452c7019211706ea7b73a22c00/demo/assets/main-it-rain.gif -------------------------------------------------------------------------------- /demo/cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "cypress"; 2 | 3 | export default defineConfig({ 4 | component: { 5 | devServer: { 6 | framework: "svelte", 7 | bundler: "vite", 8 | viteConfig: { 9 | server: { 10 | port: 8173, 11 | }, 12 | }, 13 | }, 14 | }, 15 | e2e: { 16 | setupNodeEvents(on, config) { 17 | // implement node event listeners here 18 | }, 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /demo/cypress/e2e/route-activation.cy.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | const routes = require("../fixtures/routes.json"); 4 | describe.only("route activation", () => { 5 | beforeEach(() => { 6 | cy.viewport(1500, 1500); 7 | cy.visit("http://localhost:8173"); 8 | }); 9 | 10 | routes.forEach((route) => { 11 | it(`should activate ${route.id} when visiting ${route.path}`, () => { 12 | cy.clickAndValidateActiveClasses(`a[href='${route.path}']`, "active", route.active); 13 | }); 14 | }); 15 | 16 | // it.only("displays two todo items by default", () => { 17 | // // cy.get("a[href='/props']").should("have.length", 1).click(); 18 | // // cy.contains("props.svelte").should("exist"); 19 | 20 | // // cy.get("a[href='/props/foo']").should("have.length", 1).click(); 21 | // // cy.contains("display-params.svelte").should("exist"); 22 | // // cy.contains(`"child": "foo"`).should("exist"); 23 | 24 | // // cy.get("a").filter('[href^="/props/bar?"]').should("have.length", 1).click(); 25 | // // cy.contains("display-params.svelte").should("exist"); 26 | // // cy.contains(`"child": "bar"`).should("exist"); 27 | 28 | // // cy.get("a").filter('[href="/props/foo"]').should("not.have.class", "active"); 29 | // // cy.get("a").filter('[href^="/props/bar"]').should("have.class", "active"); 30 | 31 | // cy.clickAndValidateActiveClasses("a[href='/props']", "active"); 32 | // }); 33 | 34 | // it("can add new todo items", () => { 35 | // // We'll store our item text in a variable so we can reuse it 36 | // const newItem = "Feed the cat"; 37 | 38 | // // Let's get the input element and use the `type` command to 39 | // // input our new list item. After typing the content of our item, 40 | // // we need to type the enter key as well in order to submit the input. 41 | // // This input has a data-test attribute so we'll use that to select the 42 | // // element in accordance with best practices: 43 | // // https://on.cypress.io/selecting-elements 44 | // cy.get("[data-test=new-todo]").type(`${newItem}{enter}`); 45 | 46 | // // Now that we've typed our new item, let's check that it actually was added to the list. 47 | // // Since it's the newest item, it should exist as the last element in the list. 48 | // // In addition, with the two default items, we should have a total of 3 elements in the list. 49 | // // Since assertions yield the element that was asserted on, 50 | // // we can chain both of these assertions together into a single statement. 51 | // cy.get(".todo-list li") 52 | // .should("have.length", 3) 53 | // .last() 54 | // .should("have.text", newItem); 55 | // }); 56 | 57 | // it("can check off an item as completed", () => { 58 | // // In addition to using the `get` command to get an element by selector, 59 | // // we can also use the `contains` command to get an element by its contents. 60 | // // However, this will yield the