Repository: pateketrueke/yrv Branch: master Commit: 9b853a2d9ebb Files: 42 Total size: 76.3 KB Directory structure: gitextract_601t9p7l/ ├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .github/ │ └── workflows/ │ ├── release.yml │ └── testing.yml ├── .gitignore ├── CHANGELOG.md ├── Makefile ├── README.md ├── components.js ├── debug.js ├── e2e/ │ ├── app/ │ │ ├── import.esm.js │ │ ├── routers.js │ │ └── testing.js │ ├── cases/ │ │ ├── main.test.js │ │ └── routers.test.js │ ├── components/ │ │ ├── Example.svelte │ │ ├── Import.svelte │ │ ├── Main.svelte │ │ ├── Testing.svelte │ │ └── nested-routers/ │ │ ├── App.svelte │ │ ├── Home.svelte │ │ ├── List.svelte │ │ ├── Login.svelte │ │ ├── NewTeam.svelte │ │ ├── NotFound.svelte │ │ └── Players.svelte │ └── helpers.js ├── package.json ├── src/ │ ├── index.js │ ├── lib/ │ │ ├── Link.svelte │ │ ├── Route.svelte │ │ ├── Router.svelte │ │ ├── router.js │ │ └── utils.js │ ├── test/ │ │ ├── _layout.pug │ │ ├── folder.pug │ │ ├── import.pug │ │ ├── index.pug │ │ └── routers.pug │ └── vendor.js └── types/ └── index.d.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintignore ================================================ e2e/public/assets e2e/components/Import.svelte ================================================ FILE: .eslintrc ================================================ { "env": { "es6": true, "browser": true }, "extends": "airbnb-base", "plugins": [ "svelte3" ], "overrides": [ { "files": ["*.svelte"], "processor": "svelte3/svelte3" } ], "parserOptions": { "ecmaVersion": 2019, "sourceType": "module" }, "rules" : { "max-len": ["error", { "code": 150 }], "arrow-parens": ["error", "as-needed"], "indent": 0, "strict": 0, "prefer-const": 0, "no-console": 0, "no-labels": 0, "no-unused-labels": 0, "no-restricted-syntax": 0, "no-multi-assign": 0, "prefer-destructuring": 0, "function-paren-newline": 0, "global-require": 0, "prefer-spread": 0, "prefer-rest-params": 0, "prefer-arrow-callback": 0, "arrow-body-style": 0, "no-restricted-globals": 0, "consistent-return": 0, "no-param-reassign": 0, "no-underscore-dangle": 0, "no-multiple-empty-lines": 0, "import/first": 0, "import/extensions": 0, "import/no-unresolved": 0, "import/no-dynamic-require": 0, "import/no-mutable-exports": 0, "import/no-extraneous-dependencies": 0, "import/prefer-default-export": 0 } } ================================================ FILE: .gitattributes ================================================ package-lock.json -diff dist/* -diff ================================================ FILE: .github/workflows/release.yml ================================================ name: Release on: push: branches: - master jobs: Release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 with: persist-credentials: false fetch-depth: 0 - uses: actions/setup-node@v3 with: node-version: 14 - run: npm i - run: npm test - run: HASHCHANGE=true npm test - run: | git config --local user.name "Release Bot" git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" - run: npm run release - name: Push Changes uses: ad-m/github-push-action@master with: github_token: ${{ secrets.GITHUB_TOKEN }} branch: ${{ github.ref }} tags: true - name: Publish run: make release && npm publish env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} ================================================ FILE: .github/workflows/testing.yml ================================================ name: build on: pull_request: branches: - master jobs: build-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - uses: tacoss/nodejs@v2 with: args: make ci ================================================ FILE: .gitignore ================================================ .DS_Store *.todo dist build cache.json node_modules e2e/public/assets package-lock.json ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. ### [0.0.57](https://github.com/pateketrueke/yrv/compare/v0.0.56...v0.0.57) (2022-06-26) ### Bug Fixes * use actions/setup-node instead ([7106391](https://github.com/pateketrueke/yrv/commit/7106391a64783c514c9578147e129021c201087b)) ### [0.0.56](https://github.com/pateketrueke/yrv/compare/v0.0.55...v0.0.56) (2022-06-26) ### [0.0.55](https://github.com/pateketrueke/yrv/compare/v0.0.52...v0.0.55) (2022-06-26) ### Bug Fixes * revert alias support ([8e1b907](https://github.com/pateketrueke/yrv/commit/8e1b9072f1b0bc462018c19ddcac3f5b53b896c7)) * run tests before releasing ([77e30d8](https://github.com/pateketrueke/yrv/commit/77e30d840cc73acdf49a084da55b147b33accd18)) ### [0.0.54](https://github.com/pateketrueke/yrv/compare/v0.0.52...v0.0.54) (2022-06-26) ### Bug Fixes * revert alias support ([8e1b907](https://github.com/pateketrueke/yrv/commit/8e1b9072f1b0bc462018c19ddcac3f5b53b896c7)) * run tests before releasing ([77e30d8](https://github.com/pateketrueke/yrv/commit/77e30d840cc73acdf49a084da55b147b33accd18)) ### [0.0.52](https://github.com/pateketrueke/yrv/compare/v0.0.51...v0.0.52) (2022-01-21) ### [0.0.51](https://github.com/pateketrueke/yrv/compare/v0.0.50...v0.0.51) (2022-01-16) ### [0.0.50](https://github.com/pateketrueke/yrv/compare/v0.0.49...v0.0.50) (2021-08-13) ### Bug Fixes * make sure vendor.js is bundled ([b863696](https://github.com/pateketrueke/yrv/commit/b8636962b82bb5488c2ec5286c2c9c3097165104)) ### [0.0.49](https://github.com/pateketrueke/yrv/compare/v0.0.48...v0.0.49) (2021-08-13) ### [0.0.48](https://github.com/pateketrueke/yrv/compare/v0.0.47...v0.0.48) (2021-08-11) ### [0.0.47](https://github.com/pateketrueke/yrv/compare/v0.0.46...v0.0.47) (2021-06-30) ### [0.0.46](https://github.com/pateketrueke/yrv/compare/v0.0.45...v0.0.46) (2021-06-27) ### Bug Fixes * run tests on CI for PRs only, master is for release ([c5ad7a4](https://github.com/pateketrueke/yrv/commit/c5ad7a4c66cab235cd09970f6f9bffe13560701a)) * setup NPM token before releasing ([258dbe2](https://github.com/pateketrueke/yrv/commit/258dbe2211514c4d544470e8b00e6c8011e7fdae)) ### [0.0.45](https://github.com/pateketrueke/yrv/compare/v0.0.44...v0.0.45) (2021-06-27) ### Bug Fixes * move release step outside npm scripts ([40a870c](https://github.com/pateketrueke/yrv/commit/40a870cbe99a7dc05cdb4a633083914011c4fac4)) ### [0.0.44](https://github.com/pateketrueke/yrv/compare/v0.0.43...v0.0.44) (2021-06-27) ### Bug Fixes * debug entry-point ([e500aad](https://github.com/pateketrueke/yrv/commit/e500aad01fb3314f2a84cdf7079b5f194b5015ad)) * enable DEBUG while on dev and CI ([2bddc76](https://github.com/pateketrueke/yrv/commit/2bddc761be5e791005b345d97e43b0b06e10a084)) * rebuild before tests to enable cross-env changes ([6aea3f8](https://github.com/pateketrueke/yrv/commit/6aea3f8d9cc5daac53e97127d56901ae33362ed2)) * use ...activeProps for active Route components ([cf41085](https://github.com/pateketrueke/yrv/commit/cf41085c3aaa20bd4d25d76f9e1b6650a997e808)) ### [0.0.43](https://github.com/pateketrueke/yrv/compare/v0.0.42...v0.0.43) (2021-06-27) ### [0.0.42](https://github.com/pateketrueke/yrv/compare/v0.0.41...v0.0.42) (2021-05-09) ### [0.0.41](https://github.com/pateketrueke/yrv/compare/v0.0.40...v0.0.41) (2021-05-09) ### [0.0.40](https://github.com/pateketrueke/yrv/compare/v0.0.39...v0.0.40) (2021-05-09) ### [0.0.39](https://github.com/pateketrueke/yrv/compare/v0.0.38...v0.0.39) (2021-03-31) ### [0.0.38](https://github.com/pateketrueke/yrv/compare/v0.0.37...v0.0.38) (2021-03-10) ### Bug Fixes * make sure types are included on the package; closes [#72](https://github.com/pateketrueke/yrv/issues/72) ([72c07f2](https://github.com/pateketrueke/yrv/commit/72c07f2fa57aefef23af093fab58430e318231a9)) ### [0.0.37](https://github.com/pateketrueke/yrv/compare/v0.0.35...v0.0.37) (2021-03-10) ### Features * adds in auto release and auto-publishing ([f587f9b](https://github.com/pateketrueke/yrv/commit/f587f9b35c48586b05bcc08f645021ed801c073b)) * Adds in Standard Version ([af0a5e4](https://github.com/pateketrueke/yrv/commit/af0a5e4d6b9f25480a5b3b88ed3151c00516792c)), closes [#28](https://github.com/pateketrueke/yrv/issues/28) * Adds in TypeScript definitions for the repository ([4273f09](https://github.com/pateketrueke/yrv/commit/4273f09a778ff38b7c11eb0ef108209ac5f12749)) ### Bug Fixes * Adds changes to move to ubuntu runner instead of self-hosted ([83f605c](https://github.com/pateketrueke/yrv/commit/83f605ce8f0eedc58de761af1b9ee8901a0dda8c)) * patch top-level pending component on routers, thanks to [@jhechtf](https://github.com/jhechtf) - closes [#34](https://github.com/pateketrueke/yrv/issues/34) ([83f74bc](https://github.com/pateketrueke/yrv/commit/83f74bcae474ba122c5dacc8c4aeb0625b2645ea)) ### [0.0.36](https://github.com/pateketrueke/yrv/compare/v0.0.35...v0.0.36) (2021-03-09) ### Features * adds in auto release and auto-publishing ([f587f9b](https://github.com/pateketrueke/yrv/commit/f587f9b35c48586b05bcc08f645021ed801c073b)) * Adds in Standard Version ([af0a5e4](https://github.com/pateketrueke/yrv/commit/af0a5e4d6b9f25480a5b3b88ed3151c00516792c)), closes [#28](https://github.com/pateketrueke/yrv/issues/28) * Adds in TypeScript definitions for the repository ([4273f09](https://github.com/pateketrueke/yrv/commit/4273f09a778ff38b7c11eb0ef108209ac5f12749)) ### Bug Fixes * Adds changes to move to ubuntu runner instead of self-hosted ([83f605c](https://github.com/pateketrueke/yrv/commit/83f605ce8f0eedc58de761af1b9ee8901a0dda8c)) * patch top-level pending component on routers, thanks to [@jhechtf](https://github.com/jhechtf) - closes [#34](https://github.com/pateketrueke/yrv/issues/34) ([83f74bc](https://github.com/pateketrueke/yrv/commit/83f74bcae474ba122c5dacc8c4aeb0625b2645ea)) ### [0.0.35](https://github.com/pateketrueke/yrv/compare/v0.0.34...v0.0.35) (2020-11-13) ### [0.0.34](https://github.com/pateketrueke/yrv/compare/v0.0.33...v0.0.34) (2020-11-13) ### Bug Fixes * skip :headless ([1e4d0eb](https://github.com/pateketrueke/yrv/commit/1e4d0eb426945635c48c150a8b26dbf970dc2841)) * wait 1sec before check ([a6479cf](https://github.com/pateketrueke/yrv/commit/a6479cfd12d49350e14189d21e58d70bd60e5132)) ### [0.0.33](https://github.com/pateketrueke/yrv/compare/v0.0.32...v0.0.33) (2020-09-24) ### Bug Fixes * clean ./build before test ([60fc680](https://github.com/pateketrueke/yrv/commit/60fc6800e9bd952d14fa6cb98cc8267c106aeb3f)) * dev, test and build ready; lock deps ([1dfe683](https://github.com/pateketrueke/yrv/commit/1dfe683e5e03b8cd0f7b6ed91d86585aee384200)) * disable DEBUG on dist, include src/ files ([eea9b5a](https://github.com/pateketrueke/yrv/commit/eea9b5a15704e660a3d2b070f6f73b4363d783b7)) * enable DEBUG for whole testing ([64a21cf](https://github.com/pateketrueke/yrv/commit/64a21cfd48065bdeb582f38f76af10f52ff9d46d)) * import from ./src during development ([6616e98](https://github.com/pateketrueke/yrv/commit/6616e986483731ba443fb675482583a1b369e918)) * print fallback branch when missing DEBUG ([d34b6ab](https://github.com/pateketrueke/yrv/commit/d34b6abb8d291bc0e749abaf310c844cbf02071f)) * remove cache too ([a438b4e](https://github.com/pateketrueke/yrv/commit/a438b4e2080e0849298d38c1b6b36a2e7088dc1e)) * right setup for DEBUG mode ([7f247fd](https://github.com/pateketrueke/yrv/commit/7f247fd259702149982f20674f3265c7e1ac352d)) * split sub tasks ([7fca020](https://github.com/pateketrueke/yrv/commit/7fca020283f955b8826923323029199bd98ba8b3)) ### [0.0.32](https://github.com/pateketrueke/yrv/compare/v0.0.31...v0.0.32) (2020-09-23) ### Bug Fixes * package built files, not sources ([610f016](https://github.com/pateketrueke/yrv/commit/610f0169d7c1dc38c11ca68efaa15a976c5596f1)) ### [0.0.31](https://github.com/pateketrueke/yrv/compare/v0.0.30...v0.0.31) (2020-09-23) ### [0.0.30](https://github.com/pateketrueke/yrv/compare/v0.0.29...v0.0.30) (2020-04-11) ### [0.0.29](https://github.com/pateketrueke/yrv/compare/v0.0.28...v0.0.29) (2020-04-10) ### [0.0.28](https://github.com/pateketrueke/yrv/compare/v0.0.27...v0.0.28) (2020-04-10) ### [0.0.27](https://github.com/pateketrueke/yrv/compare/v0.0.26...v0.0.27) (2020-03-25) ### [0.0.26](https://github.com/pateketrueke/yrv/compare/v0.0.25...v0.0.26) (2020-03-15) ### [0.0.25](https://github.com/pateketrueke/yrv/compare/v0.0.24...v0.0.25) (2020-03-06) ### [0.0.24](https://github.com/pateketrueke/yrv/compare/v0.0.23...v0.0.24) (2020-02-29) ### [0.0.23](https://github.com/pateketrueke/yrv/compare/v0.0.22...v0.0.23) (2020-02-29) ### [0.0.22](https://github.com/pateketrueke/yrv/compare/v0.0.21...v0.0.22) (2020-02-29) ### [0.0.21](https://github.com/pateketrueke/yrv/compare/v0.0.20...v0.0.21) (2020-02-29) ### [0.0.20](https://github.com/pateketrueke/yrv/compare/v0.0.19...v0.0.20) (2020-02-29) ### [0.0.19](https://github.com/pateketrueke/yrv/compare/v0.0.18...v0.0.19) (2020-02-20) ### [0.0.18](https://github.com/pateketrueke/yrv/compare/v0.0.17...v0.0.18) (2020-01-25) ### [0.0.17](https://github.com/pateketrueke/yrv/compare/v0.0.16...v0.0.17) (2020-01-20) ### [0.0.16](https://github.com/pateketrueke/yrv/compare/v0.0.15...v0.0.16) (2019-12-14) ### [0.0.15](https://github.com/pateketrueke/yrv/compare/v0.0.14...v0.0.15) (2019-12-07) ### [0.0.14](https://github.com/pateketrueke/yrv/compare/v0.0.13...v0.0.14) (2019-11-19) ### [0.0.13](https://github.com/pateketrueke/yrv/compare/v0.0.12...v0.0.13) (2019-11-16) ### [0.0.12](https://github.com/pateketrueke/yrv/compare/v0.0.10...v0.0.12) (2019-11-08) ### [0.0.10](https://github.com/pateketrueke/yrv/compare/v0.0.9...v0.0.10) (2019-11-04) ### [0.0.9](https://github.com/pateketrueke/yrv/compare/v0.0.8...v0.0.9) (2019-11-01) ### [0.0.8](https://github.com/pateketrueke/yrv/compare/v0.0.7...v0.0.8) (2019-10-28) ### [0.0.7](https://github.com/pateketrueke/yrv/compare/v0.0.6...v0.0.7) (2019-10-26) ### [0.0.6](https://github.com/pateketrueke/yrv/compare/v0.0.5...v0.0.6) (2019-10-18) ### [0.0.5](https://github.com/pateketrueke/yrv/compare/v0.0.4...v0.0.5) (2019-10-18) ### [0.0.4](https://github.com/pateketrueke/yrv/compare/v0.0.3...v0.0.4) (2019-10-18) ### [0.0.3](https://github.com/pateketrueke/yrv/compare/v0.0.2...v0.0.3) (2019-10-17) ### [0.0.2](https://github.com/pateketrueke/yrv/compare/v0.0.1...v0.0.2) (2019-10-17) ================================================ FILE: Makefile ================================================ ifneq ($(DEBUG),) E2E_FLAGS=--debug-on-fail endif help: Makefile @awk -F':.*?##' '/^[a-z0-9\\%!:-]+:.*##/{gsub("%","*",$$1);gsub("\\\\",":*",$$1);printf "\033[36m%8s\033[0m %s\n",$$1,$$2}' $< ci: src deps clean ## Run CI scripts @npm test -- --color $(E2E_FLAGS) @HASHCHANGE=true npm test -- --color $(E2E_FLAGS) dev: src deps ## Start dev tasks @npm run dev e2e: src deps ## Run E2E tests locally @npm run test:e2e -- e2e/cases $(E2E_FLAGS) test: src deps clean @npm test deps: package*.json @(((ls node_modules | grep .) > /dev/null 2>&1) || npm i) || true clean: @rm -f cache.json @rm -rf build/* release: clean @NODE_ENV=production npm run build ifneq ($(CI),) @echo '//registry.npmjs.org/:_authToken=$${NODE_AUTH_TOKEN}' > .npmrc endif ================================================ FILE: README.md ================================================
![yrv](Japan_road_sign_201-D.svg) ![Build status](https://github.com/pateketrueke/yrv/workflows/build/badge.svg) [![NPM version](https://img.shields.io/npm/v/yrv)](https://www.npmjs.com/package/yrv) [![Known Vulnerabilities](https://snyk.io/test/npm/yrv/badge.svg)](https://snyk.io/test/npm/yrv) [![donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=8MXLRJ7QQXGYY)
> The `v` is for Svelte Built on top of [abstract-nested-router](https://www.npmjs.com/package/abstract-nested-router), so you can use nested routers, also: - Advanced parameters can be used, e.g. `/:id<\d+>` — [see docs](https://www.npmjs.com/package/abstract-nested-router#params) - ARIA-compliant, sets `[aria-current="page"]` on active links - Seamless `` integration - Conditionals and redirection through props - Fallback `` handlers - Hash and URI-based routes - Support for [query-string](https://www.npmjs.com/package/query-string) - [REPL ready!](https://svelte.dev/repl/0f07c6134b16432591a9a3a0095a80de?version=3.12.1) > `yrv` will use any _base-href_ found on the current page to rewrite links and routes. ## Usage Install `yrv` through NPM or Yarn: ```html Home | Hello | NotFound

Hello World Not found Hey {router.params.name}!

``` > Notice `fallback` routes can’t be placed at the beginning, otherwise further routes will not be mounted. :bomb: ## Components > You MUST declare at least, one top-level `Router` to setup the bindings. ### `` This component will hold any given routes as children, `path` is always derived from parent routes. Available props: - `{path}` — Any segment to derive a fullpath from, defaults to `/` - `{pending}` — Svelte-component or String; top-level `pending` support - `{disabled}` — Boolean; Similar to condition, but for bound props - `{condition}` — Function; if given, render only if evaluates to true - `{nofallback}` — If set, non-matched routes will never raise a failure > Nested routers does not need the same path to be declared inside, e.g. if the router for `/top` has a `/sub` router inside, inner router will use the route `/top/sub`, (the same as declaring `/top/sub` route outside the parent router). ### `` Main container for routing, they can hold any component or children. Available props: - `{key}` — The route identity, not its path; defaults to random pseudo-hash - `{path}` — Any segment to derive a fullpath from, default to `/` - `{exact}` — If set, the route will render only if the route exactly matches - `{pending}` — Svelte-component or String; rendered during the loading of dynamic components - `{fallback}` — If set, the route will render only if no more routes were matched - `{component}` — Accepts either a valid svelte-component, a promise, or a dynamic import function - `{disabled}` — Boolean; Similar to `condition`, but for bound props - `{condition}` — Function; if given, the route will render only if evaluates to `true` - `{redirect}` — Alternate redirection location, only if the previous condition was `true` - `let:router` — Injects the `router` context, it also provides `failure` in case of errors > If you omit `exact`, then `/x` would match both `/` and `/x` routes — [see docs](https://www.npmjs.com/package/abstract-nested-router#params) When `yrv` adds a new route, it'll use any given `key` from its props — once routes are detached they're also removed from the router registry, due to that, the next time the same route is mounted a new key is generated (if isn't present already). ```html Home | Svelte component | Promised component | Lazy component

Hello World

``` > Behind the scenes, for making dynamic-imports work, the bundler _should_ inline them or just write-out the required chunks to make it work natively (` ``` > `document.write()` is used because conditional comments were dropped in IE10, so this way you can conditionally load polyfills anyway. Also, you MUST [enable either `buble` or `babel`](https://github.com/sveltejs/svelte/issues/2621) within your build pipeline to transpile down to ES5. ### Frequently Asked Questions **How to conditionally render a `` component?** Both Route/Router components support the `disabled` and `condition` props, but: - Use `condition` to allow/disallow route-dispatching dynamically - Use `disabled` to skip from rendering, it will add/remove the route This new `disabled` prop would work as you're expecting: ```html ... ``` **What means the `exact` property and how it works?** Say you have three routes: - `/a` (exact) - `/a/b` (non-exact) - `/a/b/c` (exact) Now, you navigate from `/a` to `/a/b/c`: - Since `/a` was active, and it was exact, `yrv` clears out the `routeInfo` for that route. - Since `/a/b` is not exact, `yrv` activate this route because is half-way to the final route. > If you plan to have more routes nested, then the route will never be `exact` (at least at top-levels). This is also true for `` components — as soon as they match the `[aria-current]` attribute will be added on them to denote _active_ links. If the link for `/a` were also `exact`, then it'll be _active_ if the matching route is `/a` only. **Why `path` can't be an empty string like other routers does?** Even if browsers treat `http://localhost:8080` and `http://localhost:8080/` as the same thing I wanted to keep paths clear as possible. Internally `yrv` normalizes any given URI to keep a trailing slash, so `/foo` is `/foo/` for matching purposes. Also, the default path is usually `/` so there's no point on having to declare anything else: ```html OK OK ``` **What is `routeInfo` and how can I access it outside routes?** This object is very similar to what you get with `let:router` inside components. Use the `$router` store to access it, e.g. ```html
{JSON.stringify($router, null, 2)}
``` **Why does Yrv not work with Parcel or webpack/snowpack?** If you're getting any of the errors below: - store.subscribe is not a function - Class constructor SvelteComponent cannot be invoked without 'new' - 'on_outro' is not exported by [...] - 'target' is a required option Make sure you're using the right settings: 1. Add mainFields into resolve config, e.g. `mainFields: ['svelte', 'browser', 'module', 'main']` 2. Remove `exclude: /node_modules/` from `svelte-loader` config > If you're using an online tool that is not the official Svelte REPL the behavior is unexpected and no further support will be granted. **Can I use hash-based routes _à la_ Gmail? e.g. `index.html#/profile`, `index.html#/book/42`?** Yes, URIs like that are suitable for embedded apps like Electron, where normal URLs would fail. Also this mode is the default used on the Svelte REPL, because is not an iframe, nor a regular webpage... it's a weird thing! > If you enable `router.hashchange = true` all your regular links will be automatically rewritten to hash-based URIs instead, see how it works in our test suite. **Why I'm getting ` was created with unknown prop 'router'` in the browser's console?** If you're not using the `router` prop inside your route-components then just add: ```html ``` That will remove the warning and also will make `eslint-plugin-svelte3` in your workflow happy. **Why `router.subscribe` is called two times when I first open the page?** Any subscription to stores will fire twice as they have an initial value, once the router resolves (e.g. the initial route) then a second event is fired. > In this case, and additional property `initial` is added to identify such event. **Is there any method that allows me to detect route change?** Yes, you can subscribe to the router store, e.g. `router.subscribe(...)` — [see above](#route-info). **Is there a way to reduce the bundle size of yrv?** Since `v0.0.46` you'll be getting the most reduced version we can ship, however it comes without development warnings. > Consume it as `import { ... } from 'yrv/debug'` right away and you'll get a more complete version with included DEBUG information. ================================================ FILE: components.js ================================================ export { default as Router } from './src/lib/Router.svelte'; export { default as Route } from './src/lib/Route.svelte'; export { default as Link } from './src/lib/Link.svelte'; export { navigateTo, router } from './src/lib/utils'; ================================================ FILE: debug.js ================================================ export * from './build/dev/index.js'; ================================================ FILE: e2e/app/import.esm.js ================================================ import App from '../components/Import.svelte'; new App({ target: document.body }); // eslint-disable-line ================================================ FILE: e2e/app/routers.js ================================================ import App from '../components/nested-routers/App.svelte'; new App({ target: document.body }); // eslint-disable-line ================================================ FILE: e2e/app/testing.js ================================================ import App from '../components/Main.svelte'; /* global USE_HASH_CHANGE */ let hashchange; if (typeof USE_HASH_CHANGE !== 'undefined' && USE_HASH_CHANGE) { hashchange = USE_HASH_CHANGE; } new App({ target: document.body, props: { hashchange } }); // eslint-disable-line ================================================ FILE: e2e/cases/main.test.js ================================================ import { Selector } from 'testcafe'; import { url, href } from '../helpers'; /* global fixture, test */ fixture('yrv (dsl)') .page(url('/')); test('it just loads!', async t => { await t.expect(Selector('h1').withText('Example page').visible).ok(); await t.expect(Selector('[data-test=counter]').innerText).contains(1); await t.expect(Selector('a').withText('Home').getAttribute('href')).eql(href('/')); await t.expect(Selector('a').withText('Home').hasAttribute('aria-current')).ok(); }); test('it would mount Route-less content', async t => { await t.expect(Selector('p[data-test=routeless]').visible).ok(); await t.expect(Selector('[data-test=counter]').innerText).contains(1); }); test('it should mount from slot-content nodes', async t => { await t.click(Selector('a').withText('Test page')); await t.expect(Selector('h2').withText('Testing features').visible).ok(); await t.expect(Selector('[data-test=counter]').innerText).contains(2); }); test('it should allow to bind and such', async t => { await t.typeText(Selector('[data-test=custominput]'), 'success'); await t.expect(Selector('[data-test=customhref]').getAttribute('href')).contains('/success'); await t.expect(Selector('[data-test=counter]').innerText).contains(1); }); fixture('yrv (example)') .page(url('/example')); test('it should mount "Hello World"', async t => { await t.expect(Selector('[data-test=example]').withText('Hello World').visible).ok(); await t.expect(Selector('[data-test=counter]').innerText).contains(1); }); test('it should mount nested content', async t => { await t.click(Selector('a').withText('Link')); await t.expect(Selector('[data-test=example]').withText('Hello a').visible).ok(); await t.expect(Selector('[data-test=counter]').innerText).contains(2); await t.expect(Selector('[data-test=unordered]').innerText).notContains('III'); await t.click(Selector('a').withText('List')); await t.expect(Selector('[data-test=unordered]').innerText).eql('III'); await t.click(Selector('a').withText('Show')); await t.expect(Selector('[data-test=unordered]').innerText).eql('II III'); await t.click(Selector('a').withText('Edit')); await t.expect(Selector('[data-test=unordered]').innerText).eql('I II III'); }); test('it should fallback on unmatched routes', async t => { await t.click(Selector('a').withText('Broken link')); await t.expect(Selector('[data-test=example]').withText('Not found').visible).ok(); await t.expect(Selector('[data-test=example]').innerText).notContains('Hello a'); await t.expect(Selector('[data-test=counter]').innerText).contains(2); }); fixture('yrv (fallback)') .page(url('/e')); test('should not mount any fallback et all', async t => { await t.expect(Selector('h2[data-test=fallback]').exists).notOk(); await t.expect(Selector('[data-test=counter]').innerText).contains(1); }); test.page(url('/e/im_not_exists'))('should handle non-matched routes as fallback', async t => { await t.expect(Selector('h2').withText('NOT FOUND').visible).ok(); await t.expect(Selector('[data-test=counter]').innerText).contains(1); }); fixture('yrv (buttons)') .page(url('/test')); test('it should disable Link buttons if they are active', async t => { const UndoButton = Selector('button').withText('Undo'); const Parameters = Selector('[data-test=parameters]'); await t.expect(UndoButton.visible).ok(); await t.expect(UndoButton.hasAttribute('disabled')).ok(); await t.click(Selector('a').withText('Test props')); await t.expect(Parameters.visible).ok(); await t.expect(UndoButton.hasAttribute('disabled')).notOk(); await t.click(UndoButton); await t.expect(Parameters.exists).notOk(); await t.expect(UndoButton.hasAttribute('disabled')).ok(); await t.expect(Selector('[data-test=counter]').innerText).contains(3); }); fixture('yrv (query params)') .page(url('/test/props')); test('it should parse from location.search', async t => { await t.expect(Selector('li').withText('query: {}').exists).ok(); await t.expect(Selector('[data-test=counter]').innerText).contains(1); await t.expect(Selector('a').withText('Test page').getAttribute('href')).eql(href('/test')); await t.expect(Selector('a').withText('Test page').hasAttribute('aria-current')).ok(); await t.expect(Selector('a').withText('Test props').getAttribute('href')).eql(href('/test/props')); await t.expect(Selector('a').withText('Test props').hasAttribute('aria-current')).ok(); }); test('it should take queryParams from navigateTo()', async t => { await t.click(Selector('a').withText('Do not click!')); await t.expect(Selector('li').withText('query: {"truth":"42"}').exists).ok(); await t.typeText(Selector('[data-test=key]'), 'x'); await t.typeText(Selector('[data-test=value]'), 'y'); await t.click(Selector('[data-test=append]')); await t.expect(Selector('li').withText('query: {"truth":"42","x":"y"}').exists).ok(); await t.expect(Selector('[data-test=counter]').innerText).contains(3); }); fixture('yrv (middleware)') .page(url('/test/props')); test('it should redirect if the given route matches', async t => { await t.click(Selector('a').withText('Redirect')); await t.expect(Selector('button').withText('Undo').hasAttribute('disabled')).ok(); await t.expect(Selector('[data-test=counter]').innerText).contains(3); }); test('it should mount or redirect based on given condition', async t => { await t.setNativeDialogHandler(() => false); await t.click(Selector('a').withText('Protected')); await t.expect(Selector('[data-test=redirect]').innerText).contains('Wrong!'); await t.setNativeDialogHandler(() => true); await t.click(Selector('a').withText('Protected')); await t.expect(Selector('[data-test=redirect]').innerText).contains('Yay!'); await t.expect(Selector('[data-test=counter]').innerText).contains(4); }); fixture('yrv (nested params)') .page(url('/test/props')); test('it should inject params from resolved routes', async t => { await t.click(Selector('a').withText('Hello World.')); await t.expect(Selector('p').withText('Value: Hello World').visible).ok(); await t.expect(Selector('[data-test=counter]').innerText).contains(2); }); if (!process.env.HASHCHANGE) { fixture('yrv (anchored routes)') .page(url('/sub')); test('it should inject params from resolved routes', async t => { await t.click(Selector('a').withText('Root')); await t.expect(Selector('p[data-test=anchored]').innerText).contains('HOME'); await t.expect(Selector('p[data-test=anchored]').innerText).notContains('ABOUT'); await t.expect(Selector('[data-test=counter]').innerText).contains(2); }); test('it should skip non-exact routes from matched ones', async t => { await t.click(Selector('a').withText('About page')); await t.expect(Selector('p[data-test=anchored]').innerText).contains('ABOUT'); await t.expect(Selector('p[data-test=anchored]').innerText).notContains('HOME'); await t.expect(Selector('[data-test=counter]').innerText).contains(2); }); test('it should handle non-matched routes as fallback', async t => { await t.click(Selector('a').withText('Broken anchor')); await t.expect(Selector('h2[data-test=fallback]').exists).notOk(); await t.expect(Selector('fieldset').innerText).contains("Unreachable '/sub#broken'"); await t.expect(Selector('[data-test=counter]').innerText).contains(2); }); } fixture('yrv (nested routes)') .page(url('/top')); test('it should nothing at top-level', async t => { await t.expect(Selector('p[data-test=nested]').innerText).contains('?'); await t.expect(Selector('p[data-test=nested]').innerText).notContains('a'); await t.expect(Selector('p[data-test=nested]').innerText).notContains('b'); await t.expect(Selector('p[data-test=nested]').innerText).notContains('c'); await t.click(Selector('a').withText('1')); await t.expect(Selector('p[data-test=nested]').innerText).notContains('?'); await t.expect(Selector('p[data-test=nested]').innerText).contains('a'); await t.expect(Selector('p[data-test=nested]').innerText).notContains('b'); await t.expect(Selector('p[data-test=nested]').innerText).notContains('c'); await t.click(Selector('a').withText('2')); await t.expect(Selector('p[data-test=nested]').innerText).notContains('?'); await t.expect(Selector('p[data-test=nested]').innerText).notContains('a'); await t.expect(Selector('p[data-test=nested]').innerText).contains('b'); await t.expect(Selector('p[data-test=nested]').innerText).notContains('c'); await t.click(Selector('a').withText('3')); await t.expect(Selector('p[data-test=nested]').innerText).notContains('?'); await t.expect(Selector('p[data-test=nested]').innerText).notContains('a'); await t.expect(Selector('p[data-test=nested]').innerText).notContains('b'); await t.expect(Selector('p[data-test=nested]').innerText).contains('c'); await t.expect(Selector('[data-test=counter]').innerText).contains(4); }); fixture('yrv (hashed routes)') .page(url('/gist')); test('it should load root-handlers', async t => { await t.expect(Selector('[data-test=hashed]').innerText).contains('GIST INFO'); await t.expect(Selector('[data-test=hashed]').innerText).notContains('SHA1: N/A'); await t.expect(Selector('[data-test=hashed]').innerText).notContains('(edit)'); await t.expect(Selector('[data-test=hashed]').innerText).notContains('(save)'); await t.expect(Selector('[data-test=counter]').innerText).contains(1); }); test.page(url('/gist#test'))('it should load sub-handlers', async t => { await t.expect(Selector('[data-test=hashed]').innerText).notContains('GIST INFO'); await t.expect(Selector('[data-test=hashed]').innerText).contains('SHA1: test'); await t.expect(Selector('[data-test=hashed]').innerText).notContains('(edit)'); await t.expect(Selector('[data-test=hashed]').innerText).notContains('(save)'); await t.expect(Selector('[data-test=counter]').innerText).contains(1); }); test.page(url('/gist#test/edit'))('it should load nested sub-handlers (/edit)', async t => { await t.expect(Selector('[data-test=hashed]').innerText).notContains('GIST INFO'); await t.expect(Selector('[data-test=hashed]').innerText).contains('SHA1: test'); await t.expect(Selector('[data-test=hashed]').innerText).contains('(edit)'); await t.expect(Selector('[data-test=hashed]').innerText).notContains('(save)'); await t.expect(Selector('[data-test=counter]').innerText).contains(1); }); test.page(url('/gist#test/save'))('it should load nested root-handlers (/save)', async t => { await t.expect(Selector('[data-test=hashed]').innerText).notContains('GIST INFO'); await t.expect(Selector('[data-test=hashed]').innerText).contains('SHA1: test'); await t.expect(Selector('[data-test=hashed]').innerText).notContains('(edit)'); await t.expect(Selector('[data-test=hashed]').innerText).contains('(save)'); await t.expect(Selector('[data-test=counter]').innerText).contains(1); }); test.page(url('/gist#test/not_found'))('it should fail on unreachable routes', async t => { await t.expect(Selector('[data-test=hashed]').innerText).notContains('GIST INFO'); await t.expect(Selector('[data-test=hashed]').innerText).contains('SHA1: test'); await t.expect(Selector('[data-test=hashed]').innerText).notContains('(edit)'); await t.expect(Selector('[data-test=hashed]').innerText).notContains('(save)'); await t.expect(Selector('[data-test=hashed]').innerText).contains('Unreachable'); await t.expect(Selector('[data-test=counter]').innerText).contains(1); }); fixture('yrv (conditional routes)') .page(url('/auth')); test('it should redirect from protected pages', async t => { await t.click(Selector('a').withText('Protected page')); await t.expect(Selector('[data-test=logged]').innerText).contains('Log-in'); await t.expect(Selector('[data-test=logged]').innerText).notContains('Welcome back.'); await t.expect(Selector('[data-test=logged]').innerText).notContains('O.K.'); await t.expect(Selector('[data-test=counter]').innerText).contains(4); }); test('it should skip redirections otherwise', async t => { await t.click(Selector('[data-test=logged]').find('input')); await t.click(Selector('a').withText('→')); await t.expect(Selector('[data-test=logged]').innerText).contains('Welcome back.'); await t.expect(Selector('[data-test=logged]').innerText).notContains('Log-in'); await t.expect(Selector('[data-test=logged]').innerText).notContains('O.K.'); await t.expect(Selector('[data-test=counter]').innerText).contains(3); }); test('it should allow routes if conditions are met', async t => { await t.expect(Selector('[data-test=secret]').exists).notOk(); await t.click(Selector('[data-test=logged]').find('input')); await t.click(Selector('a').withText('Protected page')); await t.expect(Selector('[data-test=secret]').innerText).contains('Top-secret'); await t.expect(Selector('[data-test=logged]').innerText).notContains('Log-in'); await t.expect(Selector('[data-test=logged]').innerText).notContains('Welcome back.'); await t.expect(Selector('[data-test=logged]').innerText).contains('O.K.'); await t.click(Selector('[data-test=logged]').find('input')); await t.expect(Selector('[data-test=secret]').exists).notOk(); await t.expect(Selector('[data-test=logged]').innerText).contains('Log-in'); await t.expect(Selector('[data-test=logged]').innerText).notContains('Welcome back.'); await t.expect(Selector('[data-test=logged]').innerText).notContains('O.K.'); await t.expect(Selector('[data-test=counter]').innerText).contains(4); }); fixture('yrv (dynamic imports)') .page(url('/import', true)); test('it should allow routes to be loaded through dynamic-imports', async t => { await t .expect(Selector('[data-test=container]').innerText).contains('Loading...') .expect(Selector('[data-test=import]').exists) .ok(); }); if (!process.env.HASHCHANGE) { fixture('yrv (base-href)') .page(url('/folder', true)); test('it should rebase all links to preserve base-href location', async t => { await t.expect(Selector('a').withText('Home').getAttribute('href')).eql(href('/folder')); await t.expect(Selector('a').withText('Home').hasAttribute('aria-current')).ok(); }); test('it should handle on all routes and links', async t => { await t.click(Selector('a').withText('Test page')); await t.expect(Selector('h2').withText('Testing features').visible).ok(); await t.click(Selector('a').withText('Test props')); await t.click(Selector('a').withText('Do not click!')); await t.expect(Selector('li').withText('query: {"truth":"42"}').exists).ok(); await t.click(Selector('a').withText('Anchor page')); await t.click(Selector('a').withText('Root')); await t.expect(Selector('p[data-test=anchored]').innerText).contains('HOME'); await t.expect(Selector('p[data-test=anchored]').innerText).notContains('ABOUT'); await t.click(Selector('a').withText('Link')); await t.expect(Selector('p[data-test=example').innerText).contains('Hello a'); await t.expect(Selector('[data-test=counter]').innerText).contains(7); }); } // this test seems to be working if you don't use chrom*:headless if (!process.argv.some(x => x.includes(':headless'))) { fixture('yrv (links a new-tab)') .page(url('/')); test('should open links on new tabs', async t => { const initialURL = await t.eval(() => document.documentURI); await t.click(Selector('a').withText('Test page'), { modifiers: { meta: true } }).wait(1000); const openedURL = await t.eval(() => document.documentURI); await t.expect(initialURL).notEql(openedURL); try { // this crashes the browser after closing await t.eval(() => window.close()); } catch (e) { // ok } const prevURL = await t.eval(() => document.documentURI); await t.expect(initialURL).eql(prevURL); }); } ================================================ FILE: e2e/cases/routers.test.js ================================================ import { Selector } from 'testcafe'; import { url } from '../helpers'; /* global fixture, test */ fixture('yrv (nested routers)') .page(url('/routers', true)); test('should keep working as expected when isLoggedIn is unchecked', async t => { await t .click(Selector('input[type=checkbox]')) .click(Selector('input[type=checkbox]')); await t .expect(Selector('h1').innerText).contains('This is Home') .expect(Selector('a').withText('Home').hasAttribute('aria-current')).ok(); await t .click(Selector('a').withText('Players')) .expect(Selector('h1').innerText).contains('This is Players'); await t.expect(Selector('h2').innerText).contains('This is List'); await t .expect(Selector('a').withText('Players').hasAttribute('aria-current')).ok() .expect(Selector('a').withText('List').hasAttribute('aria-current')).ok(); await t .click(Selector('a').withText('New Team')) .expect(Selector('h2').innerText).contains('This is New Team'); await t.expect(Selector('a').withText('New Team').hasAttribute('aria-current')).ok(); await t .click(Selector('a').withText('Not found')) .expect(Selector('h1').innerText).contains('This is NotFound'); await t.expect(Selector('a').withText('Not found').hasAttribute('aria-current')).ok(); }); test('should fallback to when isLoggedIn is unchecked', async t => { await t .click(Selector('input[type=checkbox]')); await t .expect(Selector('a').withText('Home').exists).ok() .expect(Selector('h1').innerText).contains('This is Login'); await t .click(Selector('a').withText('Players')) .expect(Selector('h1').innerText).contains('This is Login'); await t .click(Selector('a').withText('Not found')) .expect(Selector('h1').innerText).contains('This is Login'); }); ================================================ FILE: e2e/components/Example.svelte ================================================

Components can be lazy-loaded through `() => import('...')` calls.

================================================ FILE: e2e/components/Import.svelte ================================================
delay(import('./Example.svelte'))} />
================================================ FILE: e2e/components/Main.svelte ================================================

Example page {$router.path}

This content is static, always shown.

Home | Test page | Anchor page | Error page

Links can open windows, and they can set callbacks too: /* eslint-disable no-alert */ alert('GREAT!')}>Open window

Link | Broken link

Not found? Hello World Hello {router.params.name}

Routes with exactly one segment are considered nested routes, e.g. 1 | 2 | 3 | ?

Link's `href` can be changed as well, e.g. Change me!

? a b c

Testing features

This content is always mounted when the current URL starts-with /test.

Undo | Test props | Redirect | Protected

Wrong! /* eslint-disable no-alert */ window.confirm('Are you sure?')}>Yay!

Any Route-less content is always shown!

GIST INFO SHA1: {router.params.sha1 || 'N/A'} (edit) (save)
gists | gshow | gedit | gsave
→ | Login | Not found | Protected page loggedIn} redirect="/auth/login">O.K. Log-in loggedIn} exact redirect="/auth/login" /> Welcome back.

Shhhh! Top-secret

Root | About page | Broken anchor

HOME ABOUT

It works!

NOT FOUND

You can also hook into the router's state with `router.subscribe(...)`, e.g.

Times router-info has been changed: {count}

I II III

List Show Edit ================================================ FILE: e2e/components/Testing.svelte ================================================

Injected parameters

router
  • key: {JSON.stringify(router.key)}
  • matches: {JSON.stringify(router.matches)}
  • params: {JSON.stringify(router.params)}
  • route: {JSON.stringify(router.route)}
  • query: {JSON.stringify(router.query)}
  • path: {JSON.stringify(router.path)}
{#each Object.entries(router.query) as [key, value]} {/each}
QueryParams
key value
{key} {value}
overrideQueryParams('truth', 42)}>Do not click! | Hello World.

Value: {router.params.value}

================================================ FILE: e2e/components/nested-routers/App.svelte ================================================ Home | Players | Not found | isLoggedIn {#if isLoggedIn} {:else} {/if} ================================================ FILE: e2e/components/nested-routers/Home.svelte ================================================

This is Home

================================================ FILE: e2e/components/nested-routers/List.svelte ================================================

This is List

================================================ FILE: e2e/components/nested-routers/Login.svelte ================================================

This is Login

================================================ FILE: e2e/components/nested-routers/NewTeam.svelte ================================================

This is New Team

================================================ FILE: e2e/components/nested-routers/NotFound.svelte ================================================

This is NotFound

================================================ FILE: e2e/components/nested-routers/Players.svelte ================================================

This is Players

List | New Team ================================================ FILE: e2e/helpers.js ================================================ const prefix = process.env.BASE_URL || 'http://localhost:8080'; export function url(x, y) { if (!y && process.env.HASHCHANGE) { return `${prefix}#${x}`; } return prefix + x; } export function href(x) { if (process.env.HASHCHANGE) { return `#${x !== '/' ? x : ''}`; } return x; } ================================================ FILE: package.json ================================================ { "name": "yrv", "version": "0.0.57", "description": "Your routing!", "types": "types/index.d.ts", "_main": "build/dist/index.js", "_module": "build/src/index.js", "svelte": "build/dist/index.js", "files": [ "build/dev/**", "build/dist/**", "build/src/index.js", "types/*.*", "debug.js" ], "scripts": { "release": "standard-version", "lint": "eslint --ext js,svelte src e2e", "dev": "npm run build:test -- -w", "postbuild": "npm run copy:vendor && npm run copy:index", "prebuild": "rm -rf build", "copy:vendor": "cp build/src/vendor.js build/dev/ && cp build/src/vendor.js build/dist/", "copy:index": "cp src/index.js build/dev/ && cp src/index.js build/dist/", "mortero": "mortero -ayrv:./components USE_HASH_CHANGE=$HASHCHANGE DEBUG=true --platform browser", "build:module": "npm run mortero -- src -Xtest -Dbuild/src -Nsvelte -fyindex.js --format esm", "build:debug": "npm run mortero -- src/lib -Xtest -fKDbuild/dev --format esm", "build:test": "npm run mortero -- src e2e -X{lib,cases,components} -Xhelpers.js -X'**/_*'", "build:all": "mortero src/lib -fKXtest -Dbuild/dist --format esm --platform browser", "build": "npm run build:all && npm run build:debug && npm run build:module", "test:e2e": "testcafe ${BROWSER:-chrome:headless} --color -q", "test": "npm run build:test -- -fyjs && npm run test:e2e -- -a 'npm run dev' e2e/cases" }, "mortero": { "bundle": [ "**/app/*.js", "**/index.js", "**/vendor.js" ], "rename": [ "**/test/**/!(index).html:{basedir}/{name}/index.html", "**/{src,e2e}/**:{filepath/1}", "**/test/**:{filepath/1}" ] }, "bugs": { "url": "https://github.com/pateketrueke/yrv/issues" }, "homepage": "https://github.com/pateketrueke/yrv", "repository": { "type": "git", "url": "git+https://github.com/pateketrueke/yrv.git" }, "author": "Alvaro Cabrera ", "license": "MIT", "keywords": [ "router", "svelte", "svelte3", "svelte-router", "svelte3-router" ], "peerDependencies": { "svelte": "3.x" }, "devDependencies": { "abstract-nested-router": "^0.2.1", "eslint": "^7.27.0", "eslint-config-airbnb-base": "^14.0.0", "eslint-plugin-import": "^2.18.2", "eslint-plugin-svelte3": "^3.2.0", "eslint-utils": ">=1.4.1", "mortero": "^0.0.62", "pug": "^3.0.0", "query-string": "^6.8.3", "standard-version": "^9.0.0", "svelte": "^3.48.0", "testcafe": "1.18.6" } } ================================================ FILE: src/index.js ================================================ export { default as Router } from './lib/Router.svelte'; export { default as Route } from './lib/Route.svelte'; export { default as Link } from './lib/Link.svelte'; export { navigateTo, router } from './lib/utils'; ================================================ FILE: src/lib/Link.svelte ================================================ {#if button} {:else} {/if} ================================================ FILE: src/lib/Route.svelte ================================================ {#if failure}

{failure}

{/if} {#if activeRouter} {#if !hasLoaded} {#if pending || pendingComponent} {#if isSvelteComponent(pending)} {:else if isSvelteComponent(pendingComponent)} {:else} {pending || pendingComponent} {/if} {/if} {:else} {#if component} {:else} {/if} {/if} {/if} ================================================ FILE: src/lib/Router.svelte ================================================ {#if !disabled} {/if} {#if failure && !fallback && !nofallback}
Router failure: {path}
{failure}
{/if} ================================================ FILE: src/lib/router.js ================================================ import { writable } from 'svelte/store'; import { Router, parse } from '../vendor'; import { ROOT_URL, navigateTo, cleanPath, isActive, router, } from './utils'; export const baseRouter = new Router(); export const routeInfo = writable({}); // private registries const onError = {}; const shared = {}; let errors = []; let routers = 0; let interval; let currentURL; // take snapshot from current state... router.subscribe(value => { shared.router = value; }); routeInfo.subscribe(value => { shared.routeInfo = value; }); export function doFallback(failure, fallback) { routeInfo.update(defaults => ({ ...defaults, [fallback]: { ...shared.router, failure, }, })); } export function handleRoutes(map, params, enforce) { map.some(x => { if (x.key && (enforce || (x.matches && !shared.routeInfo[x.key]))) { if (x.redirect && (x.condition === null || x.condition(shared.router) !== true)) { if (x.exact && shared.router.path !== x.path) return false; navigateTo(x.redirect); return true; } if (x.exact && x.path !== currentURL) { if (currentURL.replace(/[#/]$/, '') !== x.path) return false; } if (enforce && x.fallback) { return false; } Object.assign(params, x.params); // upgrade matching routes! routeInfo.update(defaults => ({ ...defaults, [x.key]: { ...shared.router, ...x, }, })); } return false; }); } export function evtHandler() { let baseUri = !router.hashchange ? window.location.href.replace(window.location.origin, '') : window.location.hash || '/'; let failure; // unprefix active URL if (ROOT_URL !== '/') { baseUri = baseUri.replace(cleanPath(ROOT_URL), ''); } // skip given anchors if already exists on document, see #43 if ( /^#[\w-]+$/.test(window.location.hash) && document.querySelector(window.location.hash) && currentURL === baseUri.split('#')[0] ) return; // trailing slash is required to keep route-info on nested routes! // see: https://github.com/pateketrueke/abstract-nested-router/commit/0f338384bddcfbaee30f3ea2c4eb0c24cf5174cd const normalizedURL = baseUri.replace('/#', '#').replace(/^#\//, '/'); const [path, qs] = normalizedURL.split('?'); const fullpath = path.replace(/\/?$/, '/'); const params = {}; if (currentURL !== normalizedURL) { currentURL = normalizedURL; router.set({ path: cleanPath(fullpath), query: parse(qs), params, }); } routeInfo.set({}); // load all matching routes... baseRouter.resolve(fullpath, (err, result) => { if (err) { failure = err; return; } handleRoutes(result, params); }); if (!failure) { try { handleRoutes(baseRouter.find(fullpath), params, true); } catch (e) { // noop } } // it's fine to omit failures for '/' paths if (failure && failure.path !== '/') { console.debug(failure); } else { failure = null; } // clear previously failed handlers errors.forEach(cb => cb()); errors = []; let fallback; // invoke error-handlers to clear out previous state! Object.keys(onError).forEach(root => { if (isActive(root, fullpath, false)) { const fn = onError[root].callback; fn(failure); errors.push(fn); } if (!fallback && onError[root].fallback) { fallback = onError[root].fallback; } }); // handle unmatched fallbacks if (failure && fallback) { doFallback(failure, fallback); } } export function findRoutes() { clearTimeout(interval); interval = setTimeout(evtHandler); } export function addRouter(root, fallback, callback) { if (!routers) { window.addEventListener('popstate', findRoutes, false); } // register error-handlers if (!onError[root] || fallback) { onError[root] = { fallback, callback }; } routers += 1; return () => { routers -= 1; if (!routers) { window.removeEventListener('popstate', findRoutes, false); } }; } ================================================ FILE: src/lib/utils.js ================================================ import { writable } from 'svelte/store'; import { Router, stringify } from '../vendor'; const cache = {}; const baseTag = document.getElementsByTagName('base'); const basePrefix = (baseTag[0] && baseTag[0].href) || '/'; export const ROOT_URL = basePrefix.replace(window.location.origin, ''); export const router = writable({ path: '/', query: {}, params: {}, initial: true, }); export const CTX_ROUTER = {}; export const CTX_ROUTE = {}; // use location.hash on embedded pages, e.g. Svelte REPL let HASHCHANGE = window.location.origin === 'null'; export function hashchangeEnable(value) { if (typeof value === 'boolean') { HASHCHANGE = !!value; } return HASHCHANGE; } Object.defineProperty(router, 'hashchange', { set: value => hashchangeEnable(value), get: () => hashchangeEnable(), configurable: false, enumerable: false, }); export function fixedLocation(path, callback, doFinally) { const baseUri = router.hashchange ? window.location.hash.replace('#', '') : window.location.pathname; // this will rebase anchors to avoid location changes if (path.charAt() !== '/') { path = baseUri + path; } const currentURL = baseUri + window.location.hash + window.location.search; // do not change location et all... if (currentURL !== path) { callback(path); } // invoke final guard regardless of previous result if (typeof doFinally === 'function') { doFinally(); } } export function cleanPath(uri, fix) { return uri !== '/' || fix ? uri.replace(/\/$/, '') : uri; } export function navigateTo(path, options) { const { reload, replace, params, queryParams, } = options || {}; // If path empty or no string, throws error if (!path || typeof path !== 'string' || (path[0] !== '/' && path[0] !== '#')) { throw new Error(`Expecting '/${path}' or '#${path}', given '${path}'`); } if (params) { path = path.replace(/:([a-zA-Z][a-zA-Z0-9_-]*)/g, (_, key) => params[key]); } if (queryParams) { const qs = stringify(queryParams); if (qs) { path += `?${qs}`; } } if (router.hashchange) { let fixedURL = path.replace(/^#|#$/g, ''); if (ROOT_URL !== '/') { fixedURL = fixedURL.replace(cleanPath(ROOT_URL), ''); } window.location.hash = fixedURL !== '/' ? fixedURL : ''; return; } // If no History API support, fallbacks to URL redirect if (reload || !window.history.pushState || !window.dispatchEvent) { window.location.href = path; return; } // If has History API support, uses it fixedLocation(path, nextURL => { window.history[replace ? 'replaceState' : 'pushState'](null, '', nextURL); window.dispatchEvent(new Event('popstate')); }); } export function getProps(given, required) { const { props: sub, ...others } = given; // prune all declared props from this component required.forEach(k => { delete others[k]; }); return { ...sub, ...others, }; } export function isActive(uri, path, exact) { if (!cache[[uri, path, exact]]) { if (exact !== true && path.indexOf(uri) === 0) { cache[[uri, path, exact]] = /^[#/?]?$/.test(path.substr(uri.length, 1)); } else if (uri.includes('*') || uri.includes(':')) { cache[[uri, path, exact]] = Router.matches(uri, path); } else { cache[[uri, path, exact]] = cleanPath(path) === uri; } } return cache[[uri, path, exact]]; } export function isPromise(object) { return object && typeof object.then === 'function'; } export function isSvelteComponent(object) { return object && object.prototype; } ================================================ FILE: src/test/_layout.pug ================================================ doctype html html(lang='en') head meta(charset='UTF-8') meta(name='viewport', content='width=device-width, initial-scale=1.0') meta(http-equiv='X-UA-Compatible', content='ie=edge') block head style. a[aria-current]{font-weight:bold} body script. if (!!window.MSInputMethodContext && !!document.documentMode) document.write('