├── .eslintignore ├── .gitignore ├── .npmignore ├── example ├── .gitignore ├── src │ ├── index.js │ ├── pages │ │ ├── home.html │ │ ├── other.html │ │ ├── subthing.html │ │ ├── things.html │ │ └── thing.html │ ├── app.html │ └── routes.js ├── public │ ├── index.html │ └── global.css ├── package.json └── webpack.config.js ├── .editorconfig ├── .prettierrc ├── .eslintrc ├── NestedRoute.html ├── .vscode ├── launch.json └── settings.json ├── package.json ├── helpers.js ├── Router.html └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | dist/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /example/ 2 | /.* 3 | /yarn.lock 4 | /package-lock.json -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | public/bundle.* 4 | package-lock.json 5 | yarn.lock -------------------------------------------------------------------------------- /example/src/index.js: -------------------------------------------------------------------------------- 1 | import App from "./app.html"; 2 | 3 | const app = new App({ 4 | target : document.body, 5 | }); 6 | 7 | export default app; -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = false -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "printWidth": 80, 4 | "trailingComma": "all", 5 | "bracketSpacing": true, 6 | "jsxBracketSameLine": false, 7 | "singleQuote": true 8 | } 9 | -------------------------------------------------------------------------------- /example/src/pages/home.html: -------------------------------------------------------------------------------- 1 |

Home

2 | 3 | 14 | -------------------------------------------------------------------------------- /example/src/pages/other.html: -------------------------------------------------------------------------------- 1 |

Other

2 | 3 | 14 | -------------------------------------------------------------------------------- /example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Svelte app 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /example/src/pages/subthing.html: -------------------------------------------------------------------------------- 1 |

Subthing {params.thing}!

2 | 3 | 18 | -------------------------------------------------------------------------------- /example/src/app.html: -------------------------------------------------------------------------------- 1 |

APP

2 | 3 | Home | Things | Other 4 | 5 | 6 | 7 | 21 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": ["standard", "prettier", "prettier/standard"], 4 | "plugins": [ 5 | "standard", "prettier", "html" 6 | ], 7 | "parser": "babel-eslint", 8 | "settings": { 9 | "html/html-extensions": [".html", ".svelte"], 10 | "html/indent": "+2", 11 | "html/report-bad-indent": "warn" 12 | }, 13 | "env": { 14 | "browser": true 15 | }, 16 | "parserOptions": { 17 | "ecmaFeatures": { 18 | "modules": true 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /NestedRoute.html: -------------------------------------------------------------------------------- 1 | {#if page} 2 | 9 | {:else} 10 | 11 | {/if} 12 | 13 | 27 | -------------------------------------------------------------------------------- /example/src/pages/things.html: -------------------------------------------------------------------------------- 1 |

Things!

2 | 3 |

foo: {foo}

4 | 5 | Thing 1 | 6 | Thing 2 7 | 8 | 23 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-app", 3 | "version": "1.0.0", 4 | "devDependencies": { 5 | "cross-env": "^5.2.0", 6 | "css-loader": "^1.0.0", 7 | "mini-css-extract-plugin": "^0.4.2", 8 | "serve": "^10.0.0", 9 | "style-loader": "^0.23.0", 10 | "svelte": "^2.13.2", 11 | "svelte-loader": "2.11.0", 12 | "webpack": "^4.17.1", 13 | "webpack-cli": "^3.1.0", 14 | "webpack-serve": "^2.0.2" 15 | }, 16 | "scripts": { 17 | "build": "cross-env NODE_ENV=production webpack", 18 | "dev": "webpack-serve --content public" 19 | }, 20 | "dependencies": {} 21 | } 22 | -------------------------------------------------------------------------------- /example/src/pages/thing.html: -------------------------------------------------------------------------------- 1 |

Thing {params.thing}!

2 | 3 | Thing | Subthing 4 | 5 | 6 | 7 | 25 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "chrome", 9 | "request": "launch", 10 | "name": "Launch Chrome against localhost", 11 | "url": "http://localhost:8080", 12 | "runtimeExecutable": "/usr/bin/chromium", 13 | "webRoot": "${workspaceFolder}/example/src" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /example/src/routes.js: -------------------------------------------------------------------------------- 1 | import home from './pages/home.html' 2 | 3 | export default { 4 | '/': { 5 | component: home, 6 | preload: ['/other', '/things'], 7 | }, 8 | '/other': { 9 | loader: () => import('./pages/other.html'), 10 | }, 11 | '/things': { 12 | loader: () => import('./pages/things.html'), 13 | data: { foo: 'This is a foo value' }, 14 | }, 15 | '/things/:thing*': { 16 | loader: () => import('./pages/thing.html'), 17 | preload: ['/things/:thing/subthing/'], 18 | }, 19 | '/things/:thing/subthing/': { 20 | loader: () => import('./pages/subthing.html'), 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.trimAutoWhitespace": true, 3 | 4 | "files.trimTrailingWhitespace": true, 5 | 6 | "prettier.eslintIntegration": true, 7 | "prettier.stylelintIntegration": true, 8 | 9 | "javascript.validate.enable": false, 10 | "html.validate.scripts": false, 11 | "html.validate.styles": true, 12 | 13 | "svelte.plugin.typescript.diagnostics.enable": false, 14 | 15 | "eslint.autoFixOnSave": true, 16 | "eslint.validate": [ 17 | "javascript", 18 | "javascriptreact", 19 | { "language": "html", "autoFix": true }, 20 | { "language": "svelte", "autoFix": true } 21 | ], 22 | 23 | "debug.allowBreakpointsEverywhere": true 24 | } -------------------------------------------------------------------------------- /example/public/global.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | position: relative; 3 | width: 100%; 4 | height: 100%; 5 | } 6 | 7 | body { 8 | color: #333; 9 | margin: 0; 10 | padding: 8px; 11 | box-sizing: border-box; 12 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 13 | } 14 | 15 | a { 16 | color: rgb(0,100,200); 17 | text-decoration: none; 18 | } 19 | 20 | a:hover { 21 | text-decoration: underline; 22 | } 23 | 24 | a:visited { 25 | color: rgb(0,80,160); 26 | } 27 | 28 | label { 29 | display: block; 30 | } 31 | 32 | input, button, select, textarea { 33 | font-family: inherit; 34 | font-size: inherit; 35 | padding: 0.4em; 36 | margin: 0 0 0.5em 0; 37 | box-sizing: border-box; 38 | border: 1px solid #ccc; 39 | border-radius: 2px; 40 | } 41 | 42 | input:disabled { 43 | color: #ccc; 44 | } 45 | 46 | input[type="range"] { 47 | height: 0; 48 | } 49 | 50 | button { 51 | background-color: #f4f4f4; 52 | outline: none; 53 | } 54 | 55 | button:active { 56 | background-color: #ddd; 57 | } 58 | 59 | button:focus { 60 | border-color: #666; 61 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-page", 3 | "version": "2.2.1", 4 | "license": "MIT", 5 | "description": "Why not another option for routing in svelte?", 6 | "author": "Christian Kaisermann ", 7 | "repository": "https://github.com/kaisermann/svelte-router", 8 | "main": "./Router.html", 9 | "svelter": "./Router.html", 10 | "keywords": [ 11 | "svelte", 12 | "router" 13 | ], 14 | "scripts": { 15 | "lint": "eslint \"*.html\"", 16 | "format": "eslint --fix \"*.html\"", 17 | "prepublishOnly": "npm run format" 18 | }, 19 | "devDependencies": { 20 | "babel-eslint": "^10.0.1", 21 | "eslint": "^5.9.0", 22 | "eslint-config-prettier": "^3.3.0", 23 | "eslint-config-standard": "^12.0.0", 24 | "eslint-plugin-html": "^5.0.0", 25 | "eslint-plugin-import": "^2.14.0", 26 | "eslint-plugin-node": "^8.0.0", 27 | "eslint-plugin-prettier": "^3.0.0", 28 | "eslint-plugin-promise": "^4.0.1", 29 | "eslint-plugin-standard": "^4.0.0", 30 | "prettier": "^1.15.2", 31 | "svelte": "^2.15.3" 32 | }, 33 | "peerDependencies": { 34 | "svelte": "^2.15.3" 35 | }, 36 | "dependencies": { 37 | "page": "^1.11.3" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /example/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 3 | const { resolve } = require('path'); 4 | 5 | const mode = process.env.NODE_ENV || 'development'; 6 | const prod = mode === 'production'; 7 | 8 | module.exports = { 9 | entry: { 10 | bundle: ['./src/index.js'] 11 | }, 12 | resolve: { 13 | extensions: ['.js', '.html'], 14 | alias: { 15 | svelte: resolve(__dirname, 'node_modules', 'svelte') 16 | } 17 | }, 18 | output: { 19 | path: resolve(__dirname, 'public'), 20 | filename: '[name].js', 21 | chunkFilename: '[name].[id].js' 22 | }, 23 | module: { 24 | rules: [ 25 | { 26 | test: /\.html$/, 27 | exclude: /node_modules/, 28 | use: { 29 | loader: 'svelte-loader', 30 | options: { 31 | skipIntroByDefault: true, 32 | nestedTransitions: true, 33 | emitCss: true, 34 | hotReload: true 35 | } 36 | } 37 | }, 38 | { 39 | test: /\.css$/, 40 | use: [ 41 | /** 42 | * MiniCssExtractPlugin doesn't support HMR. 43 | * For developing, use 'style-loader' instead. 44 | * */ 45 | prod ? MiniCssExtractPlugin.loader : 'style-loader', 46 | 'css-loader' 47 | ] 48 | } 49 | ] 50 | }, 51 | mode, 52 | plugins: [ 53 | new MiniCssExtractPlugin({ 54 | filename: '[name].css' 55 | }) 56 | ], 57 | devtool: prod ? false: 'source-map' 58 | }; 59 | -------------------------------------------------------------------------------- /helpers.js: -------------------------------------------------------------------------------- 1 | /** Creates a route callback with the specified data */ 2 | export const getRouteMiddleware = (routePath, routes) => { 3 | if (routes[routePath].constructor !== Object) { 4 | routes[routePath] = { component: routes[routePath] }; 5 | } 6 | 7 | const { data = {}, loader } = routes[routePath]; 8 | let hasPreloaded = false; 9 | 10 | const loadPreloads = () => { 11 | const { preload } = routes[routePath]; 12 | setTimeout(() => 13 | preload.forEach(preloadRoute => { 14 | const loader = routes[preloadRoute].loader; 15 | if (typeof loader === 'function') { 16 | loader().then(({ default: component }) => { 17 | routes[preloadRoute].component = component; 18 | }); 19 | } 20 | }), 21 | ); 22 | hasPreloaded = true; 23 | }; 24 | 25 | return (ctx, next) => { 26 | const { 27 | components, 28 | params, 29 | state: { path, ...state }, 30 | } = ctx; 31 | 32 | const { component, preload } = routes[routePath]; 33 | 34 | /** Remove pathregexp path match */ 35 | delete params[0]; 36 | const routeData = Object.assign({}, data, state, { params }); 37 | 38 | /** If no component present and */ 39 | if (!component) { 40 | /** If there's a loader function, load it */ 41 | if (typeof loader === 'function') { 42 | loader().then(({ default: component }) => { 43 | components.push({ component, data: routeData }); 44 | if (preload && !hasPreloaded) { 45 | loadPreloads(); 46 | } 47 | next(); 48 | }); 49 | } else { 50 | throw new Error( 51 | `[svelte-router] No component for route "${routePath}"`, 52 | ); 53 | } 54 | return; 55 | } 56 | 57 | components.push({ component, data: routeData }); 58 | if (preload && !hasPreloaded) { 59 | loadPreloads(); 60 | } 61 | next(); 62 | }; 63 | }; 64 | 65 | export const getSveltedHierarchy = Router => ctx => { 66 | const { components } = ctx; 67 | const props = { 68 | page: null, 69 | context: ctx, 70 | path: ctx.path, 71 | }; 72 | 73 | /** Data needs to always be an object or else nesting won't work */ 74 | components.reduce((prev, { component, data = {} }) => { 75 | data.page = null; 76 | prev.page = { 77 | child: component, 78 | props: data, 79 | }; 80 | 81 | return prev.page.props; 82 | }, props); 83 | 84 | if(Router.store){ 85 | Router.store.fire('router:beforeNavigation', ctx); 86 | } 87 | 88 | Router.set(props); 89 | 90 | if(Router.store) { 91 | Router.store.fire('router:navigation', ctx); 92 | } 93 | }; 94 | -------------------------------------------------------------------------------- /Router.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # svelte-page 2 | 3 | > Nested routing with [`page.js`](https://github.com/visionmedia/page.js) for svelte. For svelte v2 only. 4 | 5 | ## How to use 6 | 7 | 1. Build a object with all your routes: 8 | 9 | ```js 10 | export default { 11 | '/': home, 12 | '/other': other, 13 | '/things': { 14 | component: things, 15 | data: { foo: 'A custom property' }, 16 | }, 17 | '/things/:thing*': thing, 18 | '/things/:thing/subthing/': subthing, 19 | } 20 | ``` 21 | 22 | 2. Pass it to the `` 23 | 24 | ```html 25 | 26 | 27 | 38 | ``` 39 | 40 | Every page may receive the following properties: 41 | 42 | - Any property passed to the page programatically with the `Router.go(path, { ...props })` or in the `routes.js` definition 43 | - `params` - An object representing all the parameters from the URL 44 | - `page` 45 | - `child` - The nested route component that should be rendered; 46 | - `props` - The props that should be passed to the nested component 47 | 48 | 3. For nested routes, use the `` 49 | 50 | ```html 51 | Here is my sub-page: 52 |
53 | 54 | 55 | 62 | ``` 63 | 64 | `` can fire events to allow communication between parent and child pages. 65 | 66 | Available events: 67 | 68 | - `action` 69 | - `response` 70 | - `event` 71 | 72 | **Example:** 73 | 74 | ```html 75 | 76 | ``` 77 | 78 | ## Props 79 | 80 | ```html 81 | 86 | ``` 87 | 88 | - `routes` - The routes object `(default: undefined)` 89 | - `strict` - If false, match trailling slash `(default: true)` 90 | - `hashbang` - Add `#!` before urls `(default: true)` 91 | 92 | **Dynamic**: 93 | 94 | - `context` - The `page.js` context object 95 | - `path` - The current router path 96 | 97 | ## Slots 98 | 99 | Both `` and `` have the optional `default` slot which is only rendered when the current route isn't associated with any component. 100 | 101 | ```html 102 | 103 | 404 - Route not found 104 | 105 | 106 | 107 | Nested route not found 108 | 109 | ``` 110 | 111 | ## Events 112 | 113 | ### Component events 114 | 115 | #### `notFound` 116 | 117 | When a route isn't found, both the `` and `` fire a `notFound` event. 118 | 119 | ```html 120 | 121 | 122 | ``` 123 | 124 | #### `change` 125 | 126 | When a route change occurs, the `` component fires a `change` event: 127 | 128 | ```html 129 | 130 | ``` 131 | 132 | This event is also fired at the `root` component with the name of `router:change`: 133 | 134 | ```js 135 | export default { 136 | oncreate() { 137 | this.root.on('router:change', context => { 138 | console.log('New path:', context.path) 139 | }) 140 | }, 141 | } 142 | ``` 143 | 144 | --- 145 | 146 | The `` adds itself to the `root` component as a `router` property, so it's also possible to get the context by observing its lifecycle: 147 | 148 | ```html 149 | 150 | 159 | ``` 160 | 161 | ## Static Methods 162 | 163 | ```html 164 | 165 | 179 | ``` 180 | 181 | ## Example 182 | 183 | **App.html** 184 | 185 | ```html 186 | 187 | 188 | 218 | ``` 219 | 220 | **AboutPersonTemplate.html** 221 | 222 | ```html 223 | 224 | 225 | 232 | ``` 233 | 234 | For more examples, please check the [`example/src/routes.js`](https://github.com/kaisermann/svelte-router/blob/master/example/src/routes.js) file. 235 | 236 | ## Credits and inspirations 237 | 238 | - [tivac/svelte-routing](https://github.com/tivac/svelte-routing/) :grin: 239 | --------------------------------------------------------------------------------