├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .prettierrc.js ├── CHANGELOG.md ├── LICENSE ├── README.md ├── babel.config.json ├── config ├── webpack.config.js └── webpack.config.ssr.js ├── docs-app ├── server.js └── src │ ├── App.ssr.svelte │ ├── App.svelte │ ├── Components │ ├── MainMenu.svelte │ └── menuData.js │ ├── Layout │ └── MainLayout.svelte │ ├── Pages │ ├── Index.svelte │ ├── Markdown.svelte │ ├── NotFound.svelte │ └── Playground.svelte │ ├── Router │ ├── index.js │ ├── index.ssr.js │ └── utils.js │ ├── Store │ └── index.js │ ├── assets │ ├── favicons │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-256x256.png │ │ ├── apple-touch-icon.png │ │ ├── browserconfig.xml │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── mstile-150x150.png │ │ ├── safari-pinned-tab.svg │ │ └── site.webmanifest │ ├── logo.png │ └── logo.svg │ ├── global.css │ ├── index_template.ejs │ ├── main.js │ └── texts │ ├── en │ ├── application-requirements.md │ ├── css-transitions.md │ ├── current-route-info.md │ ├── dynamic-matching.md │ ├── getting-started.md │ ├── installation.md │ ├── loading-data-in-hooks.md │ ├── named-outlets.md │ ├── navigation-guards.md │ ├── nested-routes.md │ ├── programmatic-navigation.md │ ├── router-links.md │ ├── silent-mode.md │ ├── ssr-build-setup.md │ ├── ssr-configuring-server.md │ └── ssr-introduction.md │ └── ru │ ├── application-requirements.md │ ├── css-transitions.md │ ├── current-route-info.md │ ├── dynamic-matching.md │ ├── getting-started.md │ ├── installation.md │ ├── loading-data-in-hooks.md │ ├── named-outlets.md │ ├── navigation-guards.md │ ├── nested-routes.md │ ├── programmatic-navigation.md │ ├── router-links.md │ ├── silent-mode.md │ ├── ssr-build-setup.md │ ├── ssr-configuring-server.md │ └── ssr-introduction.md ├── easyroute-docs.yaml ├── index.js ├── jest.config.js ├── nodemon.json ├── package.json ├── src ├── EasyrouteProvider.svelte ├── RouterLink.svelte ├── RouterOutlet.svelte └── __tests__ │ ├── Parsing.spec.js │ └── Router.spec.js ├── ssr ├── index.d.ts ├── index.js └── registerRouterSSR │ ├── index.d.ts │ └── index.js ├── tsconfig.json ├── types.d.ts └── useCurrentRoute ├── index.d.ts └── index.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 3 | parserOptions: { 4 | ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features 5 | sourceType: 'module' // Allows for the use of imports 6 | }, 7 | extends: [ 8 | 'plugin:@typescript-eslint/recommended', 9 | 'prettier/@typescript-eslint', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier 10 | 'plugin:prettier/recommended' 11 | ], 12 | rules: { 13 | '@typescript-eslint/explicit-module-boundary-types': 'off', 14 | '@typescript-eslint/no-non-null-assertion': 'off', 15 | '@typescript-eslint/no-explicit-any': 'off', 16 | '@typescript-eslint/no-var-requires': 'off' 17 | }, 18 | ignorePatterns: [ 19 | '**/dist/**', 20 | '**/config/**', 21 | '**/public/**', 22 | '**/router-builds/**', 23 | '**/docs-app/ssr/**', 24 | '**/__tests__/**' 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | Makefile 3 | ./dist 4 | /testapp/node_modules/ 5 | .idea 6 | docs-app/public 7 | docs-app/ssr 8 | router-builds 9 | test-rollup 10 | package-lock.json -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | docs-app 3 | config 4 | node_modules 5 | test-rollup 6 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | tabWidth: 2, 4 | tabs: false, 5 | semi: false, 6 | singleQuote: true, 7 | quoteProps: 'as-needed', 8 | trailingComma: 'none', 9 | bracketSpacing: true, 10 | jsxBracketSameLine: false, 11 | arrowParens: 'always' 12 | } 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### v3.1.2 2 | * `easyroute-core` updated to 1.4.4 (vite compatibility; better types). 3 | 4 | ### v3.1.1 5 | * fix: removed circular dependency in EasyrouteProvider; 6 | * fix: correct type definitions for registerRouterSSR; 7 | * feat: added type definitions for usage with TypeScript. 8 | 9 | ### v3.1.0 10 | * **breaking**: easyroute-core updated to 1.4.0 - difference in hooks definitions ([changelog](https://github.com/easyroute-router/easyroute-core/blob/master/CHANGELOG.md#v140)); 11 | * **beraking**: removed current route info access method via `export let currentRoute`; 12 | * **breaking**: better tree-shaking support: ssr functionality and 13 | `useCurrentRoute` hook are no longer part of the main library; 14 | * refactor: reorganized directory structure; 15 | * RouterOutlet now uses `$$restProps` svelte feature; 16 | 17 | ### v3.0.7 18 | * easyroute-core updated to v1.3.5 (fix `from` object issue). 19 | 20 | ### v3.0.6 21 | * easyroute-core updated to v1.3.4-1; 22 | * introducing `omitTrailingSlash` option; 23 | * route paths fixed in demo-app. 24 | 25 | ### v3.0.5 26 | * fix: `currentRoute` empty on startup (#26); 27 | * fix: outlet auto-restore after visiting unknown route (#27); 28 | * `currentRoute` prop is deprecated and will be removed in 3.1.0; 29 | * demo-app fix: active menu buttons highlighted. 30 | 31 | ### v3.0.4 32 | * `easyroute-core` updated to 1.3.3 ([changelog](https://github.com/easyroute-router/easyroute-core/blob/master/CHANGELOG.md#v133)). 33 | 34 | ### v3.0.3 35 | * `easyroute-core` updated to 1.3.2; 36 | * updated docs (https://github.com/easyroute-router/svelte-easyroute/pull/23). 37 | 38 | ### v3.0.2 39 | * `easyroute-core` updated to 1.3.1; 40 | * fixed errors when using `base` option; 41 | * passing router as a prop inside RouterOutlet has been removed; 42 | * updated test suites. 43 | 44 | ### v3.0.1 45 | * Size reducing; 46 | * `easyroute-core` updated to 1.3.0; 47 | * RouterLink now uses `router.push()` method due to `easyroute-core` updates. 48 | 49 | ### v3.0.0 50 | * SSR is here; 51 | * documentation app translated to russian language 52 | 53 | ### v2.2.1 54 | * `easyroute-core` updated to 1.2.0: size reducing 55 | [(see changelog)](https://github.com/lyohaplotinka/easyroute-core/blob/master/CHANGELOG.md#v120) 56 | 57 | ### v2.2.0 58 | * `easyroute-core` updated to 1.1.0; 59 | * named outlets - use two `RouterOutlet` on a single 60 | page, declare components for them by name; 61 | * individual beforeEnter hooks for routes; 62 | * `useCurrentRoute` hook for easy access to current 63 | route object in every route. 64 | 65 | ### v2.1.6 66 | * `easyroute-core` updated to 1.0.2; 67 | * fixed premature afterHook trigger with lazy loaded components. 68 | 69 | ### v2.1.5 70 | * Fixed: unable to use RouterLink outside of RouterOutlet (via EasyrouteProvider, see next); 71 | * EasyrouteProvider component: top-level wrapper for app which provides all required data 72 | for outlets and links; 73 | * added extra 10ms to entering transition for better UX; 74 | * ability to pass extra attributes (classes etc.) to RouterOutlet wrapper element; 75 | * better error throwing; 76 | * deleted extra files from repository (left after v2.1.4); 77 | * DemoApp: github buttons script are connecting in onMount hook. 78 | 79 | ### v2.1.4-1 80 | * Updated easyroute-core to 1.0.1 (due to modules bug). 81 | 82 | ### v2.1.4 83 | * Core logic of router is now in separated package - `easyroute-core` [(repo)](https://github.com/lyohaplotinka/easyroute). 84 | 85 | ### v2.1.3 86 | * Seamless compatibility with both Rollup and Webpack: building router with Rollup now; 87 | * renamed main router export file for TypeScript declarations reasons; 88 | * from now, you can access outlet HTML element in components inside the router-outlet. 89 | 90 | ### v2.1.1 91 | * Building router with Webpack and ts-loader; 92 | * all dependencies went to dev-dependencies; 93 | * updated directory structure. 94 | 95 | ### v2.1.0 96 | * Lodash is no longer required; 97 | * fixed possible memory leak. 98 | 99 | ### v2.0.0 100 | * Completely rewritten code base; 101 | * introducing nested routes; 102 | * one module for both Rollup and Webpack projects; 103 | * unit tests; 104 | * including demo application. 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Lyoha Plotinka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # svelte-easyroute moved 2 | 3 | ### You can find it [HERE](https://github.com/easyroute-router/easyroute/tree/main/packages/svelte) 4 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"] 3 | } -------------------------------------------------------------------------------- /config/webpack.config.js: -------------------------------------------------------------------------------- 1 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 2 | const HtmlWebpackPlugin = require('html-webpack-plugin') 3 | const path = require('path') 4 | const { CleanWebpackPlugin } = require('clean-webpack-plugin') 5 | const CopyPlugin = require('copy-webpack-plugin') 6 | 7 | module.exports = (env, argv) => { 8 | const mode = argv.mode 9 | const prod = mode === 'production' 10 | console.log('Building DEMO app for', mode) 11 | return { 12 | entry: { 13 | bundle: ['./docs-app/src/main.js'] 14 | }, 15 | resolve: { 16 | alias: { 17 | svelte: path.resolve('node_modules', 'svelte'), 18 | '@router': path.resolve('.') 19 | }, 20 | extensions: ['.mjs', '.js', '.svelte'], 21 | mainFields: ['svelte', 'browser', 'module', 'main'] 22 | }, 23 | output: { 24 | path: __dirname + './../docs-app/public', 25 | filename: 'files/js/[name].[contenthash].js', 26 | chunkFilename: 'files/js/[name].[contenthash].js', 27 | publicPath: '/' 28 | }, 29 | module: { 30 | rules: [ 31 | { 32 | test: /\.svelte$/, 33 | use: { 34 | loader: 'svelte-loader', 35 | options: { 36 | emitCss: true, 37 | hotReload: true, 38 | hydratable: true 39 | } 40 | } 41 | }, 42 | { 43 | test: /\.css$/, 44 | use: [ 45 | /** 46 | * MiniCssExtractPlugin doesn't support HMR. 47 | * For developing, use 'style-loader' instead. 48 | * */ 49 | prod ? MiniCssExtractPlugin.loader : 'style-loader', 50 | 'css-loader' 51 | ] 52 | }, 53 | { 54 | test: /\.(png|jpe?g|gif|svg)$/i, 55 | loader: 'file-loader', 56 | options: { 57 | name: 'files/assets/[name].[contenthash].[ext]' 58 | } 59 | } 60 | ] 61 | }, 62 | mode, 63 | plugins: [ 64 | new CleanWebpackPlugin(), 65 | new MiniCssExtractPlugin({ 66 | filename: 'files/css/[name].[contenthash].css' 67 | }), 68 | new HtmlWebpackPlugin({ 69 | template: 'docs-app/src/index_template.ejs', 70 | title: 'Svelte Easyroute', 71 | filename: prod ? 'app.html' : 'index.html' 72 | }), 73 | new CopyPlugin({ 74 | patterns: [ 75 | { from: '**/*', to: 'files/pages', context: 'docs-app/src/texts' }, 76 | { 77 | from: '*', 78 | to: './', 79 | context: 'docs-app/src/assets/favicons' 80 | } 81 | ] 82 | }) 83 | ], 84 | devtool: prod ? false : 'source-map', 85 | devServer: { 86 | historyApiFallback: true 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /config/webpack.config.ssr.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { CleanWebpackPlugin } = require('clean-webpack-plugin') 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 4 | 5 | module.exports = (env, argv) => { 6 | console.log('Building DEMO app for SSR') 7 | return { 8 | entry: { 9 | bundle: ['./docs-app/src/App.ssr.svelte'] 10 | }, 11 | resolve: { 12 | alias: { 13 | svelte: path.resolve('node_modules', 'svelte'), 14 | '@router': path.resolve('.') 15 | }, 16 | extensions: ['.mjs', '.js', '.svelte'], 17 | mainFields: ['svelte', 'browser', 'module', 'main'] 18 | }, 19 | output: { 20 | path: __dirname + './../docs-app/ssr', 21 | filename: 'docs-app.ssr.js', 22 | publicPath: '/', 23 | libraryTarget: 'commonjs' 24 | }, 25 | module: { 26 | rules: [ 27 | { 28 | test: /\.svelte$/, 29 | use: { 30 | loader: 'svelte-loader', 31 | options: { 32 | emitCss: true, 33 | hotReload: true, 34 | hydratable: true, 35 | generate: 'ssr' 36 | } 37 | } 38 | }, 39 | { 40 | test: /\.css$/, 41 | use: [ 42 | /** 43 | * MiniCssExtractPlugin doesn't support HMR. 44 | * For developing, use 'style-loader' instead. 45 | * */ 46 | MiniCssExtractPlugin.loader, 47 | 'css-loader' 48 | ] 49 | }, 50 | { 51 | test: /\.(png|jpe?g|gif|svg)$/i, 52 | loader: 'file-loader', 53 | options: { 54 | name: 'files/assets/[name].[contenthash].[ext]' 55 | } 56 | } 57 | ] 58 | }, 59 | mode: 'production', 60 | plugins: [ 61 | new CleanWebpackPlugin(), 62 | new MiniCssExtractPlugin({ 63 | filename: 'files/css/[name].[contenthash].css' 64 | }) 65 | ], 66 | devtool: false, 67 | target: 'node', 68 | optimization: { 69 | minimize: false 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /docs-app/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const app = express() 3 | const fs = require('fs') 4 | const demoApp = require('./ssr/docs-app.ssr').default 5 | const renderer = require('../ssr')() 6 | 7 | app.use('/', express.static(__dirname + '/public')) 8 | 9 | const html = fs.readFileSync(__dirname + '/public/app.html', 'utf8') 10 | 11 | function insertRendered(rendered, template) { 12 | return template 13 | .replace('{$ HTML $}', rendered.html) 14 | .replace('{$ STYLES $}', rendered.css.code) 15 | .replace('{$ HEAD $}', rendered.head) 16 | } 17 | 18 | app.get('*', async (req, res) => { 19 | const rendered = await renderer({ 20 | component: demoApp, 21 | props: {}, 22 | url: req.url 23 | }) 24 | const ssrHtml = insertRendered(rendered, html) 25 | res.send(ssrHtml) 26 | }) 27 | 28 | app.listen(3456, () => { 29 | console.log('Svelte Easyroute demo app started!') 30 | }) 31 | -------------------------------------------------------------------------------- /docs-app/src/App.ssr.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs-app/src/App.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs-app/src/Components/MainMenu.svelte: -------------------------------------------------------------------------------- 1 | 33 | 34 | 57 | 58 | -------------------------------------------------------------------------------- /docs-app/src/Components/menuData.js: -------------------------------------------------------------------------------- 1 | export default { 2 | ru: [ 3 | { 4 | title: 'header', 5 | label: 'Введение' 6 | }, 7 | { 8 | url: '/page/installation', 9 | title: 'Установка' 10 | }, 11 | { 12 | url: '/page/getting-started', 13 | title: 'Введение' 14 | }, 15 | { 16 | title: 'header', 17 | label: 'Основные возможности' 18 | }, 19 | { 20 | url: '/page/dynamic-matching', 21 | title: 'Динамическое сопоставление маршрутов' 22 | }, 23 | { 24 | url: '/page/current-route-info', 25 | title: 'Информация о текущем маршруте' 26 | }, 27 | { 28 | url: '/page/router-links', 29 | title: 'Ссылки' 30 | }, 31 | { 32 | url: '/page/programmatic-navigation', 33 | title: 'Программная навигация' 34 | }, 35 | { 36 | url: '/page/nested-routes', 37 | title: 'Вложенные маршруты' 38 | }, 39 | { 40 | url: '/page/navigation-guards', 41 | title: 'Навигационные хуки' 42 | }, 43 | { 44 | url: '/page/loading-data-in-hooks', 45 | title: 'Загрузка данных в хуках' 46 | }, 47 | { 48 | url: '/page/css-transitions', 49 | title: 'CSS переходы' 50 | }, 51 | { 52 | url: '/page/named-outlets', 53 | title: 'Именованные представления' 54 | }, 55 | { 56 | url: '/page/silent-mode', 57 | title: '"Тихий" режим' 58 | }, 59 | { 60 | title: 'header', 61 | label: 'Рендеринг на стороне сервера' 62 | }, 63 | { 64 | url: 'https://github.com/lyohaplotinka/svelte-easyroute-ssr-template', 65 | title: ` 66 | 67 | 68 | Шаблон SSR` 69 | }, 70 | { 71 | title: 'divider' 72 | }, 73 | { 74 | url: '/page/ssr-introduction', 75 | title: 'Введение в SSR' 76 | }, 77 | { 78 | url: '/page/application-requirements', 79 | title: 'Требования к приложению' 80 | }, 81 | { 82 | url: '/page/ssr-build-setup', 83 | title: 'Настройка сборки' 84 | }, 85 | { 86 | url: '/page/ssr-configuring-server', 87 | title: 'Конфигурация сервера' 88 | }, 89 | { 90 | title: 'header', 91 | label: 'Поиграться' 92 | }, 93 | { 94 | url: '/playground/demo/params?text=query', 95 | title: 'Поиграться' 96 | } 97 | ], 98 | en: [ 99 | { 100 | title: 'header', 101 | label: 'Introduction' 102 | }, 103 | { 104 | url: '/page/installation', 105 | title: 'Installation' 106 | }, 107 | { 108 | url: '/page/getting-started', 109 | title: 'Getting started' 110 | }, 111 | { 112 | title: 'header', 113 | label: 'Basic features' 114 | }, 115 | { 116 | url: '/page/dynamic-matching', 117 | title: 'Dynamic route matching' 118 | }, 119 | { 120 | url: '/page/current-route-info', 121 | title: 'Current route info' 122 | }, 123 | { 124 | url: '/page/router-links', 125 | title: 'Router links' 126 | }, 127 | { 128 | url: '/page/programmatic-navigation', 129 | title: 'Programmatic navigation' 130 | }, 131 | { 132 | url: '/page/nested-routes', 133 | title: 'Nested routes' 134 | }, 135 | { 136 | url: '/page/navigation-guards', 137 | title: 'Navigation hooks' 138 | }, 139 | { 140 | url: '/page/loading-data-in-hooks', 141 | title: 'Loading route data' 142 | }, 143 | { 144 | url: '/page/css-transitions', 145 | title: 'CSS transitions' 146 | }, 147 | { 148 | url: '/page/named-outlets', 149 | title: 'Named outlets (views)' 150 | }, 151 | { 152 | url: '/page/silent-mode', 153 | title: 'Silent mode' 154 | }, 155 | { 156 | title: 'header', 157 | label: 'Server-side rendering' 158 | }, 159 | { 160 | url: 'https://github.com/lyohaplotinka/svelte-easyroute-ssr-template', 161 | title: ` 162 | 163 | 164 | SSR template` 165 | }, 166 | { 167 | title: 'divider' 168 | }, 169 | { 170 | url: '/page/ssr-introduction', 171 | title: 'SSR: introduction' 172 | }, 173 | { 174 | url: '/page/application-requirements', 175 | title: 'Application requirements' 176 | }, 177 | { 178 | url: '/page/ssr-build-setup', 179 | title: 'Build setup' 180 | }, 181 | { 182 | url: '/page/ssr-configuring-server', 183 | title: 'Server config' 184 | }, 185 | { 186 | title: 'header', 187 | label: 'Playground' 188 | }, 189 | { 190 | url: '/playground/demo/params?text=query', 191 | title: 'Playground' 192 | } 193 | ] 194 | } 195 | -------------------------------------------------------------------------------- /docs-app/src/Layout/MainLayout.svelte: -------------------------------------------------------------------------------- 1 | 18 | 19 |
20 |
21 |
22 |
23 | 24 |
25 |
26 |
27 | 35 | 43 | v{ pkg.version } 44 | GitHub 45 |
46 |
47 |
48 |
49 |
50 |
51 | 58 |
59 |
60 |
61 |
62 | 63 |
64 |
65 |
66 |
67 |
68 | 69 | -------------------------------------------------------------------------------- /docs-app/src/Pages/Index.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | {#if $langStore === 'en'} 6 | 7 |
8 |

Welcome to Svelte Easyroute homepage!

9 | 10 |

Svelte Easyroute is a simple and convenient router for your Svelte projects.

11 | 12 |

TL;DR Features:

13 | 51 | 52 |

Why you should try it?

53 |
    54 |
  1. 55 | Well-known syntax.
    It was inspired by the router for Vue.js, so this router will be understandable to many of you. 56 |
  2. 57 |
  3. 58 | Still developing. Many features of the router are still ahead. Already now it can be used in projects, and I’m happy to know what will make it better. 59 |
  4. 60 |
  5. 61 | Community-friendly. Repository cloning and pull requests are welcome! Together we can make the perfect tool for developing on Svelte 62 |
  6. 63 |
64 |
65 | {/if} 66 | 67 | {#if $langStore === 'ru'} 68 | 69 |
70 |

Добро пожаловать в документацию Svelte Easyroute!

71 | 72 |

Svelte Easyroute - это простой и удобный роутер для ваших Svelte-проектов.

73 | 74 |

TL;DR Возможности:

75 | 113 | 114 |

Почему стоит попробовать?

115 |
    116 |
  1. 117 | Хорошо знакомый синтаксис.
    118 | Сделан по образу и подобию роутра для Vue.js, так что этот роутер будет понятен многим из вас. 119 |
  2. 120 |
  3. 121 | Всё ещё разрабатывается.
    122 | Многие возможности ещё впереди. Уже сейчас его можно использовать в проектах, и я буду рад узнать, что можно сделать лучше. 123 |
  4. 124 |
  5. 125 | В сообществе.
    126 | Буду рад вашим пулл-реквестам! Вместе мы можем сделать идеальный инструмент для разработки на Svelte. 127 |
  6. 128 |
129 |
130 | 131 | {/if} 132 | -------------------------------------------------------------------------------- /docs-app/src/Pages/Markdown.svelte: -------------------------------------------------------------------------------- 1 | 38 | 39 |
40 |
41 | {@html renderedContent} 42 |
43 |
44 | -------------------------------------------------------------------------------- /docs-app/src/Pages/NotFound.svelte: -------------------------------------------------------------------------------- 1 |

404 not found

-------------------------------------------------------------------------------- /docs-app/src/Pages/Playground.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | {#if $langStore === 'en'} 10 |
11 |

Playground

12 |

13 | This is {param1} page that shows you how {param2} are working. 14 |

15 |

Try edit parts of url on this page in your address bar.

16 |
17 |
18 | Path params 19 |
20 |             { JSON.stringify(currentRoute.params, null, 2) }
21 |         
22 |
23 |
24 | Query 25 |
26 |             { JSON.stringify(currentRoute.query, null, 2) }
27 |         
28 |
29 |
30 |
31 | {:else if $langStore === 'ru'} 32 |
33 |

Поиграться

34 |

35 | Это {param1} страница, которая показывает, как работают {param2}. 36 |

37 |

Попробуйте изменять параметры пути в адресной строке.

38 |
39 |
40 | Параметры пути 41 |
42 |             { JSON.stringify(currentRoute.params, null, 2) }
43 |         
44 |
45 |
46 | Query-параметры 47 |
48 |             { JSON.stringify(currentRoute.query, null, 2) }
49 |         
50 |
51 |
52 |
53 | {/if} -------------------------------------------------------------------------------- /docs-app/src/Router/index.js: -------------------------------------------------------------------------------- 1 | import MainLayout from '../Layout/MainLayout.svelte' 2 | import NotFound from '../Pages/NotFound.svelte' 3 | import { fetchSlugMarkdown } from './utils' 4 | import Router from '../../../index' 5 | import nprogress from 'nprogress' 6 | import { langStore } from '../Store' 7 | 8 | const routes = [ 9 | { 10 | path: '/', 11 | component: MainLayout, 12 | name: 'Index', 13 | children: [ 14 | { 15 | path: '', 16 | component: () => 17 | import(/*webpackChunkName: "Index" */ '../Pages/Index.svelte'), 18 | name: 'Index', 19 | meta: { 20 | title: 'Welcome' 21 | } 22 | }, 23 | { 24 | name: 'Page', 25 | path: 'page/:slug', 26 | meta: { 27 | test: 'test' 28 | }, 29 | beforeEnter: async (to, from, next) => { 30 | console.log(`[beforeEnter hook]: fetching page data`) 31 | const { slug } = to.params 32 | try { 33 | to.meta.pageText = await fetchSlugMarkdown(slug) 34 | const titlePart = to.meta.pageText 35 | .split('\n')[0] 36 | .replace(/^(#+ )/, '') 37 | document.title = titlePart 38 | ? `${titlePart} | Svelte Easyroute` 39 | : 'Svelte Easyroute' 40 | next() 41 | } catch (e) { 42 | console.error(e) 43 | next('/not-found') 44 | } 45 | }, 46 | component: () => 47 | import(/*webpackChunkName: "mdpage" */ '../Pages/Markdown.svelte') 48 | }, 49 | { 50 | path: 'playground/:param1/:param2', 51 | meta: { 52 | title: 'Playground' 53 | }, 54 | component: () => 55 | import( 56 | /* webpackChunkName: "playground" */ '../Pages/Playground.svelte' 57 | ) 58 | }, 59 | { 60 | path: '(.*)', 61 | component: NotFound 62 | } 63 | ] 64 | } 65 | ] 66 | 67 | const router = new Router({ 68 | mode: 'history', 69 | omitTrailingSlash: true, 70 | routes 71 | }) 72 | 73 | router.beforeEach(async (to, from, next) => { 74 | const { lang } = to.query 75 | if (lang === 'ru' || lang === 'en') langStore.set(lang) 76 | else langStore.set('en') 77 | 78 | nprogress.start() 79 | if (to.name === 'Page') next() 80 | document.title = to.meta.title 81 | ? `${to.meta.title} | Svelte Easyroute` 82 | : 'Svelte Easyroute' 83 | next() 84 | }) 85 | 86 | router.afterEach(() => { 87 | nprogress.done() 88 | }) 89 | 90 | export default router 91 | -------------------------------------------------------------------------------- /docs-app/src/Router/index.ssr.js: -------------------------------------------------------------------------------- 1 | import MainLayout from '../Layout/MainLayout.svelte' 2 | import NotFound from '../Pages/NotFound.svelte' 3 | import Index from '../Pages/Index.svelte' 4 | import Markdown from '../Pages/Markdown.svelte' 5 | import Playground from '../Pages/Playground.svelte' 6 | import { fetchSlugMarkdown } from './utils' 7 | import Router from '@router' 8 | import { langStore } from '../Store' 9 | 10 | const routes = [ 11 | { 12 | path: '/', 13 | component: MainLayout, 14 | name: 'Index', 15 | children: [ 16 | { 17 | path: '', 18 | component: Index, 19 | name: 'Index', 20 | meta: { 21 | title: 'Welcome' 22 | } 23 | }, 24 | { 25 | name: 'Page', 26 | path: 'page/:slug', 27 | meta: { 28 | test: 'test' 29 | }, 30 | beforeEnter: async (to, from, next) => { 31 | console.log(`[beforeEnter hook]: fetching page data`) 32 | const { slug } = to.params 33 | try { 34 | to.meta.pageText = await fetchSlugMarkdown(slug) 35 | next() 36 | } catch (e) { 37 | console.error(e) 38 | next('/not-found') 39 | } 40 | }, 41 | component: Markdown 42 | }, 43 | { 44 | path: 'playground/:param1/:param2', 45 | meta: { 46 | title: 'Playground' 47 | }, 48 | component: Playground 49 | }, 50 | { 51 | path: '(.*)', 52 | component: NotFound 53 | } 54 | ] 55 | }, 56 | { 57 | path: '(.*)', 58 | component: MainLayout, 59 | children: [ 60 | { 61 | path: '(.*)', 62 | component: NotFound 63 | } 64 | ] 65 | } 66 | ] 67 | 68 | const router = new Router({ 69 | mode: 'history', 70 | routes 71 | }) 72 | 73 | router.beforeEach((to, from, next) => { 74 | const { lang } = to.query 75 | if (lang === 'ru' || lang === 'en') langStore.set(lang) 76 | else langStore.set('en') 77 | next() 78 | }) 79 | 80 | export default router 81 | -------------------------------------------------------------------------------- /docs-app/src/Router/utils.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { get } from 'svelte/store' 3 | import { langStore } from '../Store' 4 | import { isBrowser } from 'easyroute-core/lib/utils' 5 | 6 | export async function fetchSlugMarkdown(slug) { 7 | const lang = get(langStore) 8 | const browser = isBrowser() 9 | const prefix = browser ? '' : 'http://localhost:3456' 10 | const requested = await axios.get(`${prefix}/files/pages/${lang}/${slug}.md`) 11 | return requested.data 12 | } 13 | -------------------------------------------------------------------------------- /docs-app/src/Store/index.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store' 2 | 3 | export const langStore = writable('en') 4 | -------------------------------------------------------------------------------- /docs-app/src/assets/favicons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easyroute-router/svelte-easyroute/64431a2c33af9b3c3515ae51e103202863e7bd48/docs-app/src/assets/favicons/android-chrome-192x192.png -------------------------------------------------------------------------------- /docs-app/src/assets/favicons/android-chrome-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easyroute-router/svelte-easyroute/64431a2c33af9b3c3515ae51e103202863e7bd48/docs-app/src/assets/favicons/android-chrome-256x256.png -------------------------------------------------------------------------------- /docs-app/src/assets/favicons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easyroute-router/svelte-easyroute/64431a2c33af9b3c3515ae51e103202863e7bd48/docs-app/src/assets/favicons/apple-touch-icon.png -------------------------------------------------------------------------------- /docs-app/src/assets/favicons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs-app/src/assets/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easyroute-router/svelte-easyroute/64431a2c33af9b3c3515ae51e103202863e7bd48/docs-app/src/assets/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /docs-app/src/assets/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easyroute-router/svelte-easyroute/64431a2c33af9b3c3515ae51e103202863e7bd48/docs-app/src/assets/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /docs-app/src/assets/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easyroute-router/svelte-easyroute/64431a2c33af9b3c3515ae51e103202863e7bd48/docs-app/src/assets/favicons/favicon.ico -------------------------------------------------------------------------------- /docs-app/src/assets/favicons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easyroute-router/svelte-easyroute/64431a2c33af9b3c3515ae51e103202863e7bd48/docs-app/src/assets/favicons/mstile-150x150.png -------------------------------------------------------------------------------- /docs-app/src/assets/favicons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /docs-app/src/assets/favicons/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-256x256.png", 12 | "sizes": "256x256", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /docs-app/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easyroute-router/svelte-easyroute/64431a2c33af9b3c3515ae51e103202863e7bd48/docs-app/src/assets/logo.png -------------------------------------------------------------------------------- /docs-app/src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Artboard 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /docs-app/src/global.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, menu, nav, section { 29 | display: block; 30 | } 31 | 32 | blockquote, q { 33 | quotes: none; 34 | } 35 | blockquote:before, blockquote:after, 36 | q:before, q:after { 37 | content: ''; 38 | content: none; 39 | } 40 | table { 41 | border-collapse: collapse; 42 | border-spacing: 0; 43 | } 44 | 45 | .fade-leave-active, 46 | .fade-enter-active { 47 | transition: opacity .2s, transform .2s; 48 | } 49 | 50 | .fade-enter { 51 | opacity: 0; 52 | transform: translateX(-10px); 53 | } 54 | 55 | .fade-leave-to { 56 | opacity: 0; 57 | transform: translateX(10px); 58 | } 59 | 60 | #nprogress .bar { 61 | background: #ff3e00!important; 62 | } 63 | 64 | #nprogress .peg { 65 | box-shadow: 0 0 10px #ff3e00, 0 0 5px #ff3e00; 66 | } 67 | 68 | #nprogress .spinner-icon { 69 | border-top-color: #ff3e00!important; 70 | border-left-color: #ff3e00!important; 71 | } -------------------------------------------------------------------------------- /docs-app/src/index_template.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | <%= htmlWebpackPlugin.options.title %> 16 | {$ HEAD $} 17 | 18 | 19 | 20 | 21 |
22 | {$ HTML $} 23 |
24 | 25 | -------------------------------------------------------------------------------- /docs-app/src/main.js: -------------------------------------------------------------------------------- 1 | import App from './App.svelte' 2 | import './global.css' 3 | import 'uikit/dist/css/uikit.css' 4 | import 'uikit/dist/js/uikit' 5 | import 'nprogress/nprogress.css' 6 | 7 | const app = new App({ 8 | target: document.getElementById('app'), 9 | hydrate: true 10 | }) 11 | window.app = app 12 | -------------------------------------------------------------------------------- /docs-app/src/texts/en/application-requirements.md: -------------------------------------------------------------------------------- 1 | ## Application Requirements 2 | 3 | Svelte Easyroute has three main application requirements: 4 | 1. "Universal" code that runs both in the browser and in Node.js; 5 | 2. No dynamically imported components in 6 | router configuration; 7 | 3. Only "history" router mode supports SSR. 8 | 9 | The second condition is due to the fact that dynamic loading 10 | components (for example, in webpack) uses the browser 11 | API for downloading JS files. 12 | 13 | **However** if you want to keep this functionality in 14 | client application, you can create a duplicate 15 | file with router settings exclusively for SSR (for example, 16 | "router.ssr.js"). -------------------------------------------------------------------------------- /docs-app/src/texts/en/css-transitions.md: -------------------------------------------------------------------------------- 1 | ## CSS transitions 2 | 3 | Using CSS transitions in Easyroute is pretty easy, 4 | especially if you worked with Vue.js 5 | before. For example, let's create "fade" transition. 6 | First of all, in a GLOBAL css file (note: you **NEED** 7 | to use a **globally included CSS file**) create the 8 | following classes: 9 | 10 | ```css 11 | /* State when app enters a new page */ 12 | .fade-enter { 13 | opacity: 0; 14 | transform: translateX(50px); 15 | } 16 | 17 | /* State when app finished going out from the route */ 18 | .fade-leave-to { 19 | opacity: 0; 20 | transform: translateX(50px); 21 | } 22 | 23 | /* This class applies when entering process is on */ 24 | .fade-enter-active { 25 | transition: opacity .2s ease, transform .2s ease; 26 | } 27 | 28 | /* This class applies when leaving process is on 29 | .fade-leave-active { 30 | transition: opacity .2s ease, transform .2s ease; 31 | } 32 | 33 | /* State when app finished going in new route */ 34 | .fade-enter-to { 35 | opacity: 1; 36 | transform: translateX(0px); 37 | } 38 | 39 | /* State when app starts to leave a page */ 40 | .fade-leave { 41 | opacity: 1; 42 | transform: translateX(0px); 43 | } 44 | ``` 45 | 46 | Of course, you can write it in more accurate way: 47 | ```css 48 | .fade-enter, .fade-leave-to { 49 | opacity: 0; 50 | transform: translateX(50px); 51 | } 52 | .fade-enter-active, .fade-leave-active { 53 | transition: opacity .2s ease, transform .2s ease; 54 | } 55 | .fade-enter-to, .fade-leave { 56 | opacity: 1; 57 | transform: translateX(0px); 58 | } 59 | ``` 60 | 61 | In this example `fade` is the name of transition. 62 | Every transition should have name because we can 63 | use different transitions on different outlets. 64 | 65 | Now you can put this name inside `transition` property 66 | of outlet which you would like to animate: 67 | ```javascript 68 | 69 | ``` 70 | 71 | That's all, now you're animated :) 72 | -------------------------------------------------------------------------------- /docs-app/src/texts/en/current-route-info.md: -------------------------------------------------------------------------------- 1 | ## Current route info 2 | From every child component you can access current 3 | route state. 4 | 5 | In any component wrapped with ``, 6 | on any level of nesting, you can use `useCurrentRoute` 7 | hook. It is a custom implementation of Observable 8 | pattern, so you can "subscribe" to current route 9 | object. It goes like this: 10 | 11 | ```html 12 | 24 | ``` 25 | **Don't forget** to `unsibscribe` when leaving your component! 26 | If you will not, it can cause memory leak. 27 | -------------------------------------------------------------------------------- /docs-app/src/texts/en/dynamic-matching.md: -------------------------------------------------------------------------------- 1 | ## Dynamic route matching 2 | 3 | Svelte Easyroute supports route parameters. It is routes 4 | that have dynamic segment in path (just like in Vue.js). 5 | Take a look at this example: 6 | 7 | ```javascript 8 | routes: [ 9 | ... 10 | { 11 | path: "/playground/:param1/params/:param2", 12 | component: ParamsPlayground, 13 | name: "ParamsPlayground" 14 | } 15 | ] 16 | ``` 17 | This route has 4 parts: playground, param1, params 18 | and param2. Here "playground" and "params" 19 | are static parts of route: you cannot change them. 20 | But "param1" and "param2" are dynamic, so you can use 21 | them to pass data. You can access this data from 22 | currentRoute object. 23 | 24 | -------------------------------------------------------------------------------- /docs-app/src/texts/en/getting-started.md: -------------------------------------------------------------------------------- 1 | ## Getting started 2 | 3 | ### Creating a router 4 | 5 | In order to create a router instance we need to specify routing mode and 6 | matches between URL paths and rendered components: 7 | 8 | **router.js** 9 | ```javascript 10 | import Router from 'svelte-easyroute' 11 | import Index from './pages/Index.svelte' 12 | import About from './pages/About.svelte' 13 | 14 | export const router = new Router({ 15 | mode: "hash", // "hash", "history" or "silent" 16 | omitTrailingSlash: true, // should we remove the last slash in the url, 17 | // e.g. "/my/path/" => "/my/path" 18 | routes:[ 19 | { 20 | path: '/', 21 | component: Index, 22 | name: 'Index' 23 | }, 24 | { 25 | path: '/about/me', 26 | component: About, 27 | name: 'About me' 28 | }, 29 | { 30 | path: '/lazy-load', 31 | component: () => import('src/LazyPage.svelte'), 32 | name: 'This is a lazy-loading page' 33 | } 34 | ] 35 | }) 36 | ``` 37 | 38 | The `mode` key allows you to specify the navigation mode: 39 | * `hash`: based on everything that comes after the "#" sign in the URL (`window.location.hash`) 40 | * `history`: based on [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API) 41 | * `silent`: navigation mode without updating the URL in the browser bar 42 | 43 | ### Adding routes 44 | The `routes` key is an array of the registered routes. 45 | In the example above we defines two routes: 46 | - link `//yoursite.com/#/` will render the `Index` component 47 | - link `//yoursite.com/#/about/me` will render the `About` component 48 | 49 | ### Next step 50 | Then, in your root component, wrap all data in `EasyrouteProvider` component and pass 51 | the router instance as a prop. Don't worry: it doesn't create a real DOM-element and won't break your styles, it's just a logical wrapper. 52 | 53 | **App.svelte** 54 | ```svelte 55 | 59 | 60 | 61 | ... 62 | 63 | ``` 64 | **It is important** to wrap your **root** component with ``. Without it the `` and `` will have no access to the router instance. 65 | 66 | ### Last step 67 | Now, when you start an app, you will see errors in console. 68 | This is happening because you don't specify the place where to 69 | render a component matched with the current URL path. 70 | 71 | To specify the place where to put matched router component we do this with the `` element. Put it in any level inside the ``: 72 | 73 | **Layout.svelte** 74 | ```svelte 75 | 78 | 79 |
80 | 81 |
82 | 85 | ``` 86 | 87 | If the current URL path matched with several components from the `routes` array, 88 | you can provide the `name` prop in `` to select the appropriate route. Otherwise the first mathed component will be rendered. 89 | -------------------------------------------------------------------------------- /docs-app/src/texts/en/installation.md: -------------------------------------------------------------------------------- 1 | ## Installation 2 | 3 | Via npm: 4 | ```bash 5 | npm install svelte-easyroute@latest 6 | ``` 7 | 8 | After that you can use default exported class for your projects. -------------------------------------------------------------------------------- /docs-app/src/texts/en/loading-data-in-hooks.md: -------------------------------------------------------------------------------- 1 | ## Loading data in hooks 2 | 3 | By default, the object with information about the current 4 | route is immutable, however, you can transfer data through 5 | the "meta" field. 6 | 7 | ```javascript 8 | console.log(currentRoute) 9 | // console output: 10 | 11 | { 12 | "fullPath": "/test/value?name=Alex&age=23", 13 | "params": { 14 | "param1": "value" 15 | }, 16 | "query": { 17 | "name": "Alex", 18 | "age": "23" 19 | }, 20 | // "meta": can be passed in hooks 21 | "meta": { 22 | "pageTitle": "Title!" 23 | } 24 | } 25 | ``` 26 | 27 | Navigation hooks are great for this. They 28 | can be asynchronous, and the router will not go to a new page, 29 | until the hook completes. 30 | 31 | Inside the hook, there is a `to` object that represents a route, 32 | to which the transition is made. You can write data 33 | to the key value `to.meta`. The data can be any type. -------------------------------------------------------------------------------- /docs-app/src/texts/en/named-outlets.md: -------------------------------------------------------------------------------- 1 | ## Named outlets 2 | 3 | If you need to display multiple route-depending 4 | views on a single page, for example, creating 5 | a layout with sidebar views and main views - 6 | this is where you may need named outlets (or 7 | named views, like in Vue.js). 8 | Instead of having one single outlet 9 | in your view, you can have multiple and give 10 | each of them a name. A `RouterOutlet` without a 11 | name will be given default as its name. 12 | 13 | ```html 14 | 15 | 16 | 17 | ``` 18 | An outlet is rendered by using a component, 19 | therefore multiple outlets require multiple 20 | components for the same route. Make sure to 21 | use the components (with an s) option: 22 | 23 | ```javascript 24 | const router = new Router({ 25 | routes: [ 26 | { 27 | path: '/', 28 | components: { 29 | default: Foo, 30 | 'sidebar-left': Bar, 31 | 'sidebar-right': Baz 32 | } 33 | } 34 | ] 35 | }) 36 | ``` 37 | 38 | You can use named outlets on each level, even in 39 | nested routes: just put a name attribute to `` -------------------------------------------------------------------------------- /docs-app/src/texts/en/navigation-guards.md: -------------------------------------------------------------------------------- 1 | ## Navigation guards 2 | 3 | If you want to do something before component is 4 | changed by router, you can add navigation guards. 5 | 6 | ### Global guards and hooks 7 | 8 | There are three types of global navigation 9 | hooks: beforeEach, afterEach and transitionOut. 10 | 11 | You can specify them like that: 12 | ```javascript 13 | router.beforeEach((to, from, next) => { 14 | console.log(to.fullPath) 15 | console.log(from.fullPath) 16 | next() 17 | }) 18 | 19 | router.afterEach((to, from) => { 20 | console.log('We are on new page!') 21 | 22 | }) 23 | 24 | router.transitionOut((to, from, next) => { 25 | console.log('Page "faded away": do something!') 26 | next() 27 | }) 28 | ``` 29 | 30 | "to" and "from" are objects with routes info. "next" 31 | is function that resolves hook's promise and 32 | continues transition. If you will not put "next()" 33 | in beforeEach or transitionOut hook - transition will NEVER 34 | complete. 35 | 36 | transitionOut is a hook that is executed after the router 37 | "leaving" animation has finished, but before the "enter" 38 | animation starts. If no transition is configured for the 39 | RouterOutlet, the hook is ignored. 40 | 41 | ### Individual route guard 42 | You can set an individual guard for each route: 43 | ```javascript 44 | const router = new Router({ 45 | // ... 46 | routes: [ 47 | { 48 | path: '/path', 49 | component: Component, 50 | beforeEnter: (to, from, next) { 51 | console.log('I am here!') 52 | next() 53 | }, 54 | transitionOut: (to, from, next) { 55 | console.log('Leaving transition finished!') 56 | next() 57 | } 58 | } 59 | ] 60 | }) 61 | ``` 62 | These guards have the exact same signature as global before guards. 63 | 64 | **Note**: all router guards functions can be `async`. 65 | ### More control 66 | 67 | `next` can be used without arguments, then route 68 | changing will continue. Next arguments are also valid: 69 | * `true` - same as no argument passed; 70 | * `false` - route changing will be cancelled; 71 | * path, for example `/login` – redirect to another route. 72 | 73 | You can use this for auth control, 404 redirects based 74 | on resources fetch failure, etc. 75 | -------------------------------------------------------------------------------- /docs-app/src/texts/en/nested-routes.md: -------------------------------------------------------------------------------- 1 | ## Nested routes 2 | Working with nested routes is pretty similar to Vue Router. 3 | 4 | First, you should define `children` property in route: 5 | ```javascript 6 | routes:[ 7 | { 8 | path: '/', 9 | component: Index, 10 | name: 'Index', 11 | children: [ 12 | { 13 | path: 'nested', 14 | component: Nested, 15 | name: 'Nested' 16 | } 17 | ] 18 | }, 19 | ``` 20 | 21 | Then, add into `Index` component RouterOutlet: 22 | ```javascript 23 | // Index.svelte 24 | 27 | 28 | 29 | ``` 30 | Now you will see both rendered 31 | components on the screen. 32 | 33 | #### Important: 34 | Svelte Easyroute ecosystem uses [Svelte context API](https://svelte.dev/docs#setContext). 35 | Context name is `easyrouteContext`. Never redefine it in 36 | your components! 37 | -------------------------------------------------------------------------------- /docs-app/src/texts/en/programmatic-navigation.md: -------------------------------------------------------------------------------- 1 | ## Programmatic navigation 2 | 3 | If you have access to route object in your component, 4 | you can use its `push` method to navigate to another 5 | page. 6 | 7 | **Important:** in version 2.0.0 only string literal 8 | is supported 9 | 10 | ```javascript 11 | // SomeComponent.svelte 12 | 16 | ``` -------------------------------------------------------------------------------- /docs-app/src/texts/en/router-links.md: -------------------------------------------------------------------------------- 1 | ## Router links 2 | 3 | To add link to the another route you can use `RouterLink` component. 4 | 5 | ```javascript 6 | import { RouterLink } from 'svelte-easyroute' 7 | 8 | 9 | 10 | ``` 11 | "to" prop is an url where you want to navigate. You can use query parameters here, like: 12 | -------------------------------------------------------------------------------- /docs-app/src/texts/en/silent-mode.md: -------------------------------------------------------------------------------- 1 | ## Silent mode 2 | 3 | Svelte Easyroute has a third router mode - "silent". 4 | You can use it if you don't want to change URL in 5 | address bar. Define your routes as usual: 6 | ```javascript 7 | export var router = new Router({ 8 | mode: "silent", 9 | routes: [ 10 | ... 11 | ] 12 | }) 13 | ``` 14 | Then, just place a regular RouterLink anywhere you 15 | want. 16 | This mode has it's own history. You can use this two 17 | methods: 18 | ```javascript 19 | export let router 20 | 21 | router.back() // navigates your router back in silent mode 22 | router.go(1) // navigates your router forward in silent mode 23 | ``` 24 | **Why this mode not uses history api by default?** 25 | Because history api is not supported in some older 26 | versions of browsers. However, you can manipulate 27 | browser history in this mode using navigation hooks :) -------------------------------------------------------------------------------- /docs-app/src/texts/en/ssr-build-setup.md: -------------------------------------------------------------------------------- 1 | ## Build setup 2 | 3 | The first step is to set up the build of your application for 4 | SSR. The Svelte compiler can generate code like this, so 5 | all you need is to create an additional configuration 6 | in rollup or webpack, which has two main differences: 7 | 8 | * As the input file you need to specify the component of the top 9 | a level configured for building in SSR (for example, "App.ssr.svelte"); 10 | * in Svelte plugin settings add: `generate: 'ssr'`. 11 | 12 | ### Main Component Modifications 13 | 14 | "App.ssr.svelte" is a copy of the "main" component 15 | in your application. It is very important to do the following in it: 16 | in the block ` 27 | ``` 28 | 29 | ### Next steps 30 | 31 | After that, you need to build applications for both the client and 32 | server. Optionally - in the input js file of the client application, 33 | in instantiating the top level component class 34 | add `hydrate: true`: 35 | 36 | ```javascript 37 | new App ({ 38 | target: document.getElementById ('app'), 39 | hydrate: true, 40 | props: {} 41 | }) 42 | ``` 43 | 44 | This will make the application use the already rendered HTML 45 | for work, and will not duplicate the layout. 46 | -------------------------------------------------------------------------------- /docs-app/src/texts/en/ssr-configuring-server.md: -------------------------------------------------------------------------------- 1 | ## Server config 2 | 3 | Svelte Easyroute allows you to use any Node.js server 4 | or a framework of your choice. This documentation will 5 | considered setting up a server on Express. 6 | 7 | The Express setup itself remains the same. SSR in Easyroute is not 8 | a view engine for Express, so you need to import 9 | and create a renderer: 10 | 11 | ```javascript 12 | const renderer = require('svelte-easyroute/ssr')() 13 | ``` 14 | 15 | Next, import the application built for SSR: 16 | 17 | ```javascript 18 | const App = require ('./ssr/app.ssr.js').default 19 | ``` 20 | 21 | ### Preparing HTML 22 | 23 | Since Svelte (by default) only generates application code, 24 | you need to create an HTML template that you will enter 25 | the code. You can use any template engine (EJS, etc.), or 26 | the same simple HTML file with placeholders. In the most simplified 27 | it might look like this: 28 | 29 | ```html 30 | 31 | 32 | 33 | 34 | {$ HEAD $} 35 | 36 | 37 | 38 |
39 | {$ HTML $} 40 |
41 | 42 | 43 | 44 | 45 | ``` 46 | 47 | ### Rendering the application 48 | 49 | All the "magic" happens in the route handler. Better do 50 | the callback function asynchronous, since the renderer created above 51 | is an asynchronous function. 52 | 53 | ```javascript 54 | // Read the prepared HTML 55 | const template = fs.readFileSync(__dirname + './app.template.html', 'utf8') 56 | 57 | app.get ('*', async (req, res) => { 58 | const rendered = await renderer ({ 59 | component: App, 60 | props: {}, 61 | url: req.url 62 | }) 63 | const ssrHtml = template 64 | .replace('{$ HTML $}', rendered.html) 65 | .replace('{$ STYLES $}', rendered.css.code) 66 | .replace('{$ HEAD $}', rendered.head) 67 | res.send(ssrHtml) 68 | }) 69 | ``` 70 | 71 | You can pass any props to the component. As a result 72 | executing the code above Easyroute will follow the URL from the Request object, will run all global and individual hooks, 73 | prepare a list of route components and call the method 74 | `render` for the App component. The received data will be inserted into 75 | HTML template. 76 | -------------------------------------------------------------------------------- /docs-app/src/texts/en/ssr-introduction.md: -------------------------------------------------------------------------------- 1 | ## Introduction to SSR 2 | 3 | Server side rendering is a generation 4 | HTML code from Svelte application on the Node.js server side, 5 | allowing you to respond to a browser request with ready-made code. 6 | 7 | SSR has many advantages, for example it is better for SEO, 8 | time-to-content metric. SSR topic has been raised more than once 9 | in the context of JavaScript application development. 10 | 11 | ### Development rules for using SSR 12 | 13 | You must understand that your application should behave 14 | it is the same in two environments at once: in the browser and in Node.js. 15 | To do this, you need to take into account the peculiarities of the environments (for example, 16 | Node.js does not have a global Window object). 17 | 18 | In other words, you need to write universal code. As 19 | for example, you can look at the sources of this application. 20 | 21 | ### SSR in Svelte Easyroute 22 | 23 | Using this router, you will be able to set up rendering to 24 | server side without too much difficulty. Separate advantage - 25 | availability of navigation hooks in which you can load 26 | data for Svelte components on the server side, and give 27 | the client a ready-made code with data. -------------------------------------------------------------------------------- /docs-app/src/texts/ru/application-requirements.md: -------------------------------------------------------------------------------- 1 | ## Требования к приложению 2 | 3 | У Svelte Easyroute есть три основных требований к приложению, 4 | которое использует SSR: 5 | 1. "Универсальный" код, выполняемый и в браузере, и в Node.js; 6 | 2. отсутствие динамически импортируемых компонентов в 7 | конфигурации роутера; 8 | 3. только режим "history" подходит для SSR. 9 | 10 | Второе условие связано с тем, что динамическая загрузка 11 | компонентов (к примеру, в webpack) использует браузерный 12 | API для скачивания JS-файлов. 13 | 14 | **Однако**, если вы хотите сохранить этот функционал в 15 | клиентском приложении, вы можете создать дублирующий 16 | файл с настройками роутера исключительно для SSR (к примеру, 17 | "router.ssr.js"). -------------------------------------------------------------------------------- /docs-app/src/texts/ru/css-transitions.md: -------------------------------------------------------------------------------- 1 | ## CSS-переходы 2 | 3 | Использование CSS-переходов в Easyroute - это просто, особенно 4 | если вы работали с Vue.js. К примеру, давайте создадим переход 5 | "fade". Прежде всего, в ГЛОБАЛЬНОМ css файле, создайте следующие 6 | классы: 7 | 8 | ```css 9 | /* Приложение входит на новую страницу */ 10 | .fade-enter { 11 | opacity: 0; 12 | transform: translateX(50px); 13 | } 14 | 15 | /* Приложение завершило выход со страницы */ 16 | .fade-leave-to { 17 | opacity: 0; 18 | transform: translateX(50px); 19 | } 20 | 21 | /* Начался процесс входа */ 22 | .fade-enter-active { 23 | transition: opacity .2s ease, transform .2s ease; 24 | } 25 | 26 | /* Начался процесс выхода */ 27 | .fade-leave-active { 28 | transition: opacity .2s ease, transform .2s ease; 29 | } 30 | 31 | /* Приложение вошло в новый маршрут */ 32 | .fade-enter-to { 33 | opacity: 1; 34 | transform: translateX(0px); 35 | } 36 | 37 | /* Приложение начало покидать страницу */ 38 | .fade-leave { 39 | opacity: 1; 40 | transform: translateX(0px); 41 | } 42 | ``` 43 | 44 | Конечно, вы можете записать это более аккуратно: 45 | ```css 46 | .fade-enter, .fade-leave-to { 47 | opacity: 0; 48 | transform: translateX(50px); 49 | } 50 | .fade-enter-active, .fade-leave-active { 51 | transition: opacity .2s ease, transform .2s ease; 52 | } 53 | .fade-enter-to, .fade-leave { 54 | opacity: 1; 55 | transform: translateX(0px); 56 | } 57 | ``` 58 | 59 | В этом примере `fade` - название анимации перехода. Каждая 60 | анимация должна иметь имя, так как мы можем использовать 61 | разные анимации на разных представлениях. 62 | 63 | Теперь вы можете вписать имя в свойство `transition` компонента 64 | представления, который вы хотите анимировать: 65 | ```javascript 66 | 67 | ``` 68 | 69 | Вот и всё, теперь вы анимированы :) 70 | -------------------------------------------------------------------------------- /docs-app/src/texts/ru/current-route-info.md: -------------------------------------------------------------------------------- 1 | ## Информация о текущем маршруте 2 | Из каждого дочернего для представления компонента вы 3 | можете получить доступ к текущему маршруту. 4 | 5 | В каждом компоненте, обёрнутом в ``, 6 | на любом уровне вложенности, вы можете использовать хук 7 | `useCurrentRoute`. Это имплементация паттерна Observable, так 8 | что вы можете "подписаться" на объект текущего маршрута. Вот так: 9 | 10 | ```html 11 | 23 | ``` 24 | 25 | **Не забудьте** отписаться (`unsubscribe`), когда покидаете 26 | компонент! Если этого не сделать, возможны утечки памяти. 27 | 28 | -------------------------------------------------------------------------------- /docs-app/src/texts/ru/dynamic-matching.md: -------------------------------------------------------------------------------- 1 | ## Динамическое сопоставление маршрутов 2 | 3 | Svelte Easyroute поддерживает параметры маршрутов. Это 4 | маршруты, которые имеют динамические сегменты в своём пути 5 | (прямо как в Vue.js). Взгляните на пример: 6 | 7 | ```javascript 8 | routes: [ 9 | ... 10 | { 11 | path: "/playground/:param1/params/:param2", 12 | component: ParamsPlayground, 13 | name: "ParamsPlayground" 14 | } 15 | ] 16 | ``` 17 | 18 | Этот маршрут имеет 4 части: playground, param1, params 19 | и param2. Здесь "playground" и "params" - статические 20 | части пути: вы не можете их поменять. Но "param1" и 21 | "param2" - динамические, так что вы можете использовать их, 22 | чтобы передать какие-либо данные. Доступ к ним вы можете 23 | получить через объект `currentRoute`. 24 | 25 | -------------------------------------------------------------------------------- /docs-app/src/texts/ru/getting-started.md: -------------------------------------------------------------------------------- 1 | ## Введение 2 | 3 | ### Создаём роутер 4 | 5 | Для создания экземпляра роутера, необходимо указать его режим работы и 6 | сопоставления между путями и соответствующими этим путям компопонентами: 7 | 8 | **router.js** 9 | ```javascript 10 | import Router from 'svelte-easyroute' 11 | import Index from './pages/Index.svelte' 12 | import About from './pages/About.svelte' 13 | 14 | export const router = new Router({ 15 | mode: "hash", // "hash", "history" или "silent" 16 | omitTrailingSlash: true, // нужно ли удалять последний слэш в пути, 17 | // например "/my/path/" => "/my/path" 18 | routes:[ 19 | { 20 | path: '/', 21 | component: Index, 22 | name: 'Index' 23 | }, 24 | { 25 | path: '/about/me', 26 | component: About, 27 | name: 'About me' 28 | }, 29 | { 30 | path: '/lazy-load', 31 | component: () => import('src/pages/LazyPage.svelte'), 32 | name: 'This is a lazy-loading page' 33 | } 34 | ] 35 | }) 36 | ``` 37 | 38 | Ключ `mode` позволяет вам выбрать режим навигации: 39 | * `hash`: используется всё, что следует за знаком `#` в URL (`window.location.hash`) 40 | * `history`: основан на [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API) 41 | * `silent`: тихий режим навигации без обновления URL в строке браузера 42 | 43 | ### Добавление маршрутов 44 | Ключ `routes` - перечисляет массив зарегистрированных маршрутов. 45 | В примере выше мы указали два маршрута: 46 | - ссылка `//yoursite.com/#/` отобразит компонент `Index` 47 | - ссылка `//yoursite.com/#/about/me` отобразит компонент `About` 48 | 49 | ### Следующий шаг 50 | Далее, в вашем компоненте верхнего уровня, оберните все элементы в `EasyrouteProvider`, и передайте в него экземпляр роутера. Не волнуйтесь: он не создать DOM-элемент и не нарушит ваши стили. Это всего лишь логическая обёртка. 51 | 52 | **App.svelte** 53 | ```svelte 54 | 58 | 59 | 60 | ... 61 | 62 | ``` 63 | 64 | **Очень важно** обернуть ваш **корневой** компонент в ``. 65 | Без этого `` и `` не будут иметь доступа к экземпляру роутера. 66 | 67 | ### Последний шаг 68 | Сейчас, если вы запустите ваше приложение, то вы увидите ошибки 69 | в консоли. Это происходит потому, что мы не указали место, 70 | куда нужно помещать компонент, сопоставленный в `routes`. 71 | 72 | Для указания места, куда вставить сопоставленный компонент из `routes`, 73 | служит ``: 74 | 75 | **Layout.svelte** 76 | ```svelte 77 | 80 | 81 |
82 | 83 |
84 | 87 | ``` 88 | 89 | Если под текущий URL сопоставлено сразу несколько компонент из `routes`, 90 | то выбрать конкретное сопоставление можно с помощью свойства `name`: 91 | ``, иначе будет вставлен первый компонент, 92 | подходящий под сопоставление. 93 | -------------------------------------------------------------------------------- /docs-app/src/texts/ru/installation.md: -------------------------------------------------------------------------------- 1 | ## Установка 2 | 3 | Через npm: 4 | ```bash 5 | npm install svelte-easyroute@latest 6 | ``` 7 | 8 | После этого вы можете использовать экспортируемый по умолчанию класс. 9 | -------------------------------------------------------------------------------- /docs-app/src/texts/ru/loading-data-in-hooks.md: -------------------------------------------------------------------------------- 1 | ## Загрузка данных в хуках 2 | 3 | По умолчанию объект с информацией о текущем маршруте 4 | иммутабельный, однако, вы можете передать данные через 5 | поле "meta". 6 | 7 | ```javascript 8 | console.log(currentRoute) 9 | // вывод консоли: 10 | 11 | { 12 | "fullPath": "/test/value?name=Alex&age=23", 13 | "params": { 14 | "param1": "value" 15 | }, 16 | "query": { 17 | "name": "Alex", 18 | "age": "23" 19 | }, 20 | // "meta": может быть передано из хука 21 | "meta": { 22 | "pageTitle": "Title!" 23 | } 24 | } 25 | ``` 26 | 27 | Для этих целей отлично подходят навигационные хуки. Они 28 | могут быть асинхронными, и роутер не перейдёт на новую страницу, 29 | пока не завершится выполнение хука. 30 | 31 | Внутри хука есть объект `to`, обозначающий собой маршрут, 32 | на который совершается переход. Вы можете записать данные 33 | в значение ключа `to.meta`. Данные могут быть любыми. -------------------------------------------------------------------------------- /docs-app/src/texts/ru/named-outlets.md: -------------------------------------------------------------------------------- 1 | ## Именованные представления 2 | 3 | Если вам нужно отобразить несколько представлений, зависящих 4 | от маршрута, на одной странице (к примеру, нужно представление 5 | на сайдбаре и в центральном блоке) - вам понадобятся именованные 6 | представления. Вместо использования одного `RouterOutlet`, 7 | вы можете поместить на страницу несколько, назначив каждому 8 | из них имя. По умолчания имя представления - "default". 9 | 10 | ```html 11 | 12 | 13 | 14 | ``` 15 | Представление отображается с помощью компонента, 16 | поэтому для нескольких представлений требуется несколько 17 | компонентов для одного и того же маршрута. Убедитесь, что 18 | используйте параметр components (с s), а не component: 19 | 20 | ```javascript 21 | const router = new Router({ 22 | routes: [ 23 | { 24 | path: '/', 25 | components: { 26 | default: Foo, 27 | 'sidebar-left': Bar, 28 | 'sidebar-right': Baz 29 | } 30 | } 31 | ] 32 | }) 33 | ``` 34 | 35 | Вы можете использовать именованные представления на каждом 36 | уровне, даже на вложенных маршрутах. 37 | -------------------------------------------------------------------------------- /docs-app/src/texts/ru/navigation-guards.md: -------------------------------------------------------------------------------- 1 | ## Навигационные хуки 2 | 3 | Если вы хотите сделать что-либо до или после того, как 4 | компонент переключен роутером, вы можете добавить 5 | навигационные хуки. 6 | 7 | ### Глобальные хуки 8 | 9 | Существует три типа глобальных хука: beforeEach, afterEach и transitionOut. 10 | 11 | Вы можете назначить их так: 12 | ```javascript 13 | router.beforeEach((to, from, next) => { 14 | console.log(to.fullPath) 15 | console.log(from.fullPath) 16 | next() 17 | }) 18 | 19 | router.afterEach((to, from) => { 20 | console.log('Мы на новой странице!') 21 | 22 | }) 23 | 24 | router.transitionOut((to, from, next) => { 25 | console.log('Страница "уплыла": можно что-то сделать!') 26 | next() 27 | }) 28 | ``` 29 | 30 | "to" и "from" - объекты с информацией о маршрутах, следующем 31 | и текущем. "next" - это функция, которая разрешает Promise хука 32 | и продолжает процесс перехода. Если вы не вызовете "next", переход 33 | не завершится никогда. 34 | 35 | transitionOut - хук, который выполняется после того, как 36 | завершилась анимация "ухода" роутера, но до начала 37 | анимации "входа". Если для RouterOutlet не настроен переход, 38 | хук игнорируется. 39 | 40 | ### Индивидуальный хук маршрутов 41 | 42 | Вы можете добавить индивидуальный хук для каждого 43 | маршрута, beforeEnter: 44 | ```javascript 45 | const router = new Router({ 46 | // ... 47 | routes: [ 48 | { 49 | path: '/path', 50 | component: Component, 51 | beforeEnter: (to, from, next) { 52 | console.log('I am here!') 53 | next() 54 | }, 55 | transitionOut: (to, from, next) { 56 | console.log('Анимация ухода завершилась!') 57 | next() 58 | } 59 | } 60 | ] 61 | }) 62 | ``` 63 | Свойства хука точно такие же, как свойства глобального хука 64 | beforeEach. 65 | 66 | **Кстати**: все хуки могут быть `async`. 67 | 68 | ### Больше контроля 69 | 70 | `next` может быть вызван без аргументов: тогда переход просто 71 | продолжится. Однако, следующие аргументы валидны: 72 | 73 | * `true` - то же самое, что не передать аргумент; 74 | * `false` - переход будет отменён; 75 | * URL, к примеру `/login` – редирект на другой маршрут. 76 | 77 | Вы можете использовать это для контроля авторизации или редиректов 78 | 404, связанных с проблемой загрузки ресурсов. 79 | -------------------------------------------------------------------------------- /docs-app/src/texts/ru/nested-routes.md: -------------------------------------------------------------------------------- 1 | ## Вложенные маршруты 2 | 3 | Работа с вложенными маршрутами очень похожа на работу с ними 4 | в Vue Router. 5 | 6 | Прежде всего, создайте массив `children` в маршруте: 7 | ```javascript 8 | routes:[ 9 | { 10 | path: '/', 11 | component: Index, 12 | name: 'Index', 13 | children: [ 14 | { 15 | path: 'nested', 16 | component: Nested, 17 | name: 'Nested' 18 | } 19 | ] 20 | }, 21 | ``` 22 | 23 | Далее, в компонент `Index` добавьте RouterOutlet: 24 | ```javascript 25 | // Index.svelte 26 | 29 | 30 | 31 | ``` 32 | 33 | Теперь вы увидите оба компонента на экране. 34 | 35 | #### Важно: 36 | Svelte Easyroute использует [Svelte context API](https://ru.svelte.dev/docs#setContext). 37 | Имя контекста - `easyrouteContext`. Никогда не переопределяйте его в компонентах! 38 | -------------------------------------------------------------------------------- /docs-app/src/texts/ru/programmatic-navigation.md: -------------------------------------------------------------------------------- 1 | ## Программная навигация 2 | 3 | Если у вас есть доступ к объекту роутера, вы можете 4 | использовать его метод `push`, чтобы перейти на другой 5 | маршрут. 6 | 7 | ```javascript 8 | // SomeComponent.svelte 9 | 13 | ``` -------------------------------------------------------------------------------- /docs-app/src/texts/ru/router-links.md: -------------------------------------------------------------------------------- 1 | ## Ссылки 2 | 3 | Чтобы добавить ссылку на другой маршрут, используйте 4 | компонент `RouterLink`. 5 | 6 | ```javascript 7 | import { RouterLink } from 'svelte-easyroute' 8 | 9 | 10 | 11 | ``` 12 | 13 | Свойство "to" - это URL, на который вы хотите перейти. 14 | Вы можете использовать query-параметры, если это необходимо. 15 | -------------------------------------------------------------------------------- /docs-app/src/texts/ru/silent-mode.md: -------------------------------------------------------------------------------- 1 | ## "Тихий" режим 2 | 3 | У Svelte Easyroute есть третий режим - "silent". Вы можете 4 | использовать его, если не хотите менять URL в строке 5 | браузера. 6 | ```javascript 7 | export var router = new Router({ 8 | mode: "silent", 9 | routes: [ 10 | ... 11 | ] 12 | }) 13 | ``` 14 | У этого режима есть своя история маршрутов. Используйте 15 | эти два метода: 16 | 17 | ```javascript 18 | export let router 19 | 20 | router.back() // перемещает на один маршрут назад 21 | router.go(1) // перемещает на N маршрутов назад или вперёд 22 | ``` 23 | **Почему этот режим не использует History API?** 24 | Потому что History API не поддерживается в некоторых старых 25 | версиях браузеров. Однако, вы можете манипулировать историей 26 | в навигационных хуках :) 27 | -------------------------------------------------------------------------------- /docs-app/src/texts/ru/ssr-build-setup.md: -------------------------------------------------------------------------------- 1 | ## Настройка сборки 2 | 3 | Первым делом нужно настроить сборку вашего приложения для 4 | SSR. Компилятор Svelte умеет создавать такой код, поэтому 5 | всё, что вам нужно - создать дополнительную конфигурацию 6 | в rollup или webpack, в которой будут два основных отличия: 7 | 8 | * В качестве входного файла нужно указать компонент верхнего 9 | уровня, настроенный для сборки в SSR (к примеру, "App.ssr.svelte"); 10 | * в настройках плагина Svelte нужно добавить: `generate: 'ssr'`. 11 | 12 | ### Модификации главного компонента 13 | 14 | "App.ssr.svelte" - это копия "главного" компонента 15 | в вашем приложении. В нём очень важно сделать следущее: 16 | в блоке ` 27 | ``` 28 | 29 | ### Дальнейшие шаги 30 | 31 | После этого вам нужно собрать приложения и для клиента, и для 32 | сервера. Опционально - во входном js-файле клиентского приложения, 33 | в создании экземпляра класса компонента верхнего уровня 34 | добавьте `hydrate: true`: 35 | 36 | ```javascript 37 | new App({ 38 | target: document.getElementById('app'), 39 | hydrate: true, 40 | props: {} 41 | }) 42 | ``` 43 | 44 | Так приложение будет использовать уже отрендеренный HTML 45 | для работы, и не будет дублировать вёрстку. 46 | -------------------------------------------------------------------------------- /docs-app/src/texts/ru/ssr-configuring-server.md: -------------------------------------------------------------------------------- 1 | ## Конфигурация сервера 2 | 3 | Svelte Easyroute позволяет использовать любой Node.js сервер 4 | или фреймворк на ваше усмотрение. В данной документации будет 5 | рассмотрена настройка сервера на Express. 6 | 7 | Сама настройка Express остаётся прежней. SSR в Easyroute не 8 | является view-движком для Express, поэтому вам нужно импортировать 9 | и создать рендерер: 10 | 11 | ```javascript 12 | const renderer = require('svelte-easyroute/ssr')() 13 | ``` 14 | 15 | Дальше, импортируйте собранное для SSR приложение: 16 | 17 | ```javascript 18 | const App = require('./ssr/app.ssr.js').default 19 | ``` 20 | 21 | ### Подготовка HTML 22 | 23 | Так как Svelte (по умолчанию) генерирует только код приложения, 24 | вам нужно создать HTML-шаблон, в который вы будете вписывать 25 | код. Вы можете использовать любой шаблонизатор (EJS, etc.), или 26 | же простой HTML-файл с плейсхолдерами. В самом упрощённом 27 | виде он может выглядеть так: 28 | 29 | ```html 30 | 31 | 32 | 33 | 34 | {$ HEAD $} 35 | 36 | 37 | 38 |
39 | {$ HTML $} 40 |
41 | 42 | 43 | 44 | 45 | ``` 46 | 47 | ### Рендеринг приложения 48 | 49 | Вся "магия" происходит в обработчике маршрутов. Лучше сделать 50 | коллбэк-функцию асинхронной, так как `renderer`, созданный выше, 51 | является асинхронной функцией. 52 | 53 | ```javascript 54 | // Читаем подготовленный HTML 55 | const template = fs.readFileSync(__dirname + './app.template.html', 'utf8') 56 | 57 | app.get('*', async (req, res) => { 58 | const rendered = await renderer({ 59 | component: App, 60 | props: {}, 61 | url: req.url 62 | }) 63 | const ssrHtml = template 64 | .replace('{$ HTML $}', rendered.html) 65 | .replace('{$ STYLES $}', rendered.css.code) 66 | .replace('{$ HEAD $}', rendered.head) 67 | res.send(ssrHtml) 68 | }) 69 | ``` 70 | 71 | Вы можете передавать любые props в компонент. В результате 72 | выполнения кода выше Easyroute перейдёт по URL из объекта 73 | Request, запустит все глобальные и индивидуальные хуки, 74 | подготовит список компонентов маршрутов и вызовет метод 75 | `render` у компонента App. Полученные данные подставит в 76 | HTML-шаблон. 77 | -------------------------------------------------------------------------------- /docs-app/src/texts/ru/ssr-introduction.md: -------------------------------------------------------------------------------- 1 | ## Введение в SSR 2 | 3 | Рендеринг на стороне сервера - это процедура генерации 4 | HTML-кода из приложения Svelte на стороне Node.js сервера, 5 | позволяющая ответить на запрос браузера готовым кодом. 6 | 7 | У SSR есть много преимуществ, к примеру, это лучше для SEO, 8 | показателя time-to-content. Тема SSR не раз поднималась 9 | в контексте разработки JavaScript-приложений, поэтому 10 | я не хочу останавливаться на этом подробнее. 11 | 12 | ### Правила разработки для использования SSR 13 | 14 | Вы должны понимать, что ваше приложения должно вести себя 15 | одинаково в двух окружениях сразу: в браузере и в Node.js. 16 | Для этого нужно учесть особенности окружений (к примеру, 17 | отсутствие в Node.js глобального объекта Window). 18 | 19 | Иными словами, вам нужно писать универсальный код. В качестве 20 | примера вы можете рассмотреть исходники этого приложения. 21 | 22 | ### SSR в Svelte Easyroute 23 | 24 | Используя этот роутер, вы сможете настроить рендеринг на 25 | стороне сервера без особых трудностей. Отдельный плюс - 26 | наличие навигационных хуков, в которых вы сможете загружать 27 | данные для Svelte-компонентов на стороне сервера, и отдавать 28 | клиенту уже готовый код с данными. -------------------------------------------------------------------------------- /easyroute-docs.yaml: -------------------------------------------------------------------------------- 1 | apps: 2 | - script: 'server.js' 3 | cwd: './docs-app' 4 | name: 'SvelteEasyroute' 5 | args: '--mode="production"' 6 | max_memory_restart: '1G' 7 | watch: false 8 | autorestart: true 9 | env: 10 | TZ: 'Europe/Moscow' -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | export { default } from 'easyroute-core' 2 | export { default as RouterOutlet } from './src/RouterOutlet.svelte' 3 | export { default as RouterLink } from './src/RouterLink.svelte' 4 | export { default as EasyrouteProvider } from './src/EasyrouteProvider.svelte' 5 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transform: { 3 | '^.+\\.svelte$': 'jest-transform-svelte', 4 | '^.+\\.jsx?$': 'babel-jest' 5 | }, 6 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.js?$', 7 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], 8 | transformIgnorePatterns: ['/node_modules/(?!easyroute-core).+\\.js$'] 9 | } 10 | -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["docs-app/public/", "docs-app/ssr/"], 3 | "ext": "js, css, html" 4 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-easyroute", 3 | "description": "Config-based router for Svelte in style of Vue Router with SSR support", 4 | "version": "3.1.2", 5 | "main": "./index.js", 6 | "types": "./types.d.ts", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/lyohaplotinka/svelte-easyroute.git" 10 | }, 11 | "bugs": { 12 | "url": "https://github.com/lyohaplotinka/svelte-easyroute/issues" 13 | }, 14 | "homepage": "https://easyroute.lyoha.info", 15 | "keywords": [ 16 | "svelte", 17 | "router", 18 | "route", 19 | "framework", 20 | "javascript", 21 | "ssr" 22 | ], 23 | "author": "Lyoha Plotinka", 24 | "license": "MIT", 25 | "devDependencies": { 26 | "@babel/polyfill": "^7.12.1", 27 | "@babel/preset-env": "^7.10.3", 28 | "@rollup/plugin-typescript": "^6.0.0", 29 | "@testing-library/svelte": "^3.0.0", 30 | "@types/jest": "^26.0.0", 31 | "@types/node": "^14.0.13", 32 | "@types/path-to-regexp": "^1.7.0", 33 | "@types/query-string": "^6.3.0", 34 | "@types/url-join": "^4.0.0", 35 | "@typescript-eslint/eslint-plugin": "^3.4.0", 36 | "@typescript-eslint/parser": "^3.4.0", 37 | "axios": "^0.21.0", 38 | "babel-jest": "^26.1.0", 39 | "babel-loader": "^8.1.0", 40 | "clean-webpack-plugin": "^3.0.0", 41 | "connect-history-api-fallback": "^1.6.0", 42 | "copy-webpack-plugin": "^6.0.2", 43 | "cross-env": "^5.2.0", 44 | "css-loader": "^2.1.1", 45 | "eslint": "^7.3.1", 46 | "eslint-config-prettier": "^6.11.0", 47 | "eslint-plugin-prettier": "^3.1.4", 48 | "express": "^4.17.1", 49 | "express-history-api-fallback": "^2.2.1", 50 | "file-loader": "^6.0.0", 51 | "html-webpack-plugin": "^4.3.0", 52 | "husky": "^4.2.5", 53 | "jest": "^26.1.0", 54 | "jest-transform-svelte": "^2.1.1", 55 | "markdown-it": "^11.0.0", 56 | "mini-css-extract-plugin": "^0.6.0", 57 | "nodemon": "^2.0.6", 58 | "nprogress": "^0.2.0", 59 | "path-to-regexp": "^6.1.0", 60 | "prettier": "^2.0.5", 61 | "query-string": "^6.13.1", 62 | "rollup": "^2.26.11", 63 | "rollup-plugin-commonjs": "^10.1.0", 64 | "rollup-plugin-node-resolve": "^5.2.0", 65 | "rollup-plugin-typescript": "^1.0.1", 66 | "serve": "^11.0.0", 67 | "style-loader": "^0.23.1", 68 | "svelte": "^3.0.0", 69 | "svelte-loader": "2.13.3", 70 | "ts-jest": "^26.1.1", 71 | "ts-loader": "^7.0.5", 72 | "ts-node-dev": "^1.0.0-pre.48", 73 | "tslib": "^2.0.1", 74 | "typescript": "^3.9.5", 75 | "uikit": "^3.5.4", 76 | "url-join": "^4.0.1", 77 | "webpack": "^4.30.0", 78 | "webpack-cli": "^3.3.0", 79 | "webpack-dev-server": "^3.3.1" 80 | }, 81 | "scripts": { 82 | "build:app": "webpack --mode=production --progress --config config/webpack.config.js", 83 | "dev:app": "webpack-dev-server --content-base docs-app/public --config config/webpack.config.js", 84 | "build:ssr": "webpack --progress --config config/webpack.config.ssr.js", 85 | "dev:ssr": "nodemon ./docs-app/server.js", 86 | "build:all": "npm run build:app && npm run build:ssr", 87 | "lint": "eslint '*/**/*.{js,ts}' --fix", 88 | "test": "jest" 89 | }, 90 | "dependencies": { 91 | "easyroute-core": "1.4.4" 92 | }, 93 | "husky": { 94 | "hooks": { 95 | "pre-commit": "npm run lint && npm run test" 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/EasyrouteProvider.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/RouterLink.svelte: -------------------------------------------------------------------------------- 1 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/RouterOutlet.svelte: -------------------------------------------------------------------------------- 1 | 94 | 95 |
100 | {#if firstRouteResolved} 101 | 102 | {/if} 103 |
104 | -------------------------------------------------------------------------------- /src/__tests__/Parsing.spec.js: -------------------------------------------------------------------------------- 1 | import '@babel/polyfill' 2 | import Router from 'easyroute-core' 3 | 4 | const config = { 5 | mode: 'history', 6 | routes: [ 7 | { 8 | name: 'Route1', 9 | path: '/route1', 10 | component: () => 'route1' 11 | }, 12 | { 13 | name: 'Route2', 14 | path: '/route2', 15 | component: () => 'route2', 16 | children: [ 17 | { 18 | path: '/route2-1/noslug', 19 | name: 'Route2-1-noslug', 20 | component: () => 'route2-1-noslug' 21 | }, 22 | { 23 | path: '/route2-1/:slug', 24 | name: 'Route2-1', 25 | component: () => 'route2-1-slug' 26 | } 27 | ] 28 | } 29 | ] 30 | } 31 | 32 | describe('Parsing tests', () => { 33 | let router 34 | it('creating an instance', (done) => { 35 | router = new Router(config) 36 | setTimeout(() => { 37 | done() 38 | }, 0) 39 | }) 40 | 41 | it('Correctly sets IDs and parentIDs', () => { 42 | expect(router.routes[0].parentId).toBe(null) 43 | expect(router.routes[3].parentId).toBe(null) 44 | expect(router.routes[3].id).toBe(router.routes[1].parentId) 45 | }) 46 | 47 | it('Correctly finds single-level no-slug route', (done) => { 48 | router.push('/route1').then(() => { 49 | expect(router.currentRoute.name).toBe(config.routes[0].name) 50 | done() 51 | }) 52 | }) 53 | 54 | it('Correctly generates route object on nested routes without slug', (done) => { 55 | router.push('/route2/route2-1/noslug').then(() => { 56 | expect(router.currentRoute.name).toBe(config.routes[1].children[0].name) 57 | done() 58 | }) 59 | }) 60 | 61 | it('Resolves all correct components on nested routes without slug', (done) => { 62 | router.currentMatched.getValue.forEach((route, idx) => { 63 | if (idx === 0) { 64 | expect(route.component()).toBe(config.routes[1].children[0].component()) 65 | } 66 | if (idx === 1) { 67 | expect(route.component()).toBe(config.routes[1].component()) 68 | } 69 | }) 70 | done() 71 | }) 72 | 73 | it('Correctly parses path slugs', (done) => { 74 | router.push('/route2/route2-1/easyroute').then(() => { 75 | expect(router.currentRoute.params.slug).toBe('easyroute') 76 | done() 77 | }) 78 | }) 79 | 80 | it('Correctly parses query parameters', (done) => { 81 | router.push('/route2/route2-1/noslug?test=easyroute').then(() => { 82 | expect(router.currentRoute.query.test).toBe('easyroute') 83 | done() 84 | }) 85 | }) 86 | }) 87 | -------------------------------------------------------------------------------- /src/__tests__/Router.spec.js: -------------------------------------------------------------------------------- 1 | import '@babel/polyfill' 2 | import Router from 'easyroute-core' 3 | 4 | let windowSpy 5 | 6 | const config = { 7 | mode: 'history', 8 | routes: [ 9 | { 10 | name: 'Route1', 11 | path: '/route1', 12 | component: () => 'route1' 13 | } 14 | ] 15 | } 16 | 17 | describe('Basic funcs', () => { 18 | let router 19 | 20 | beforeEach(() => { 21 | windowSpy = jest.spyOn(global, 'window', 'get') 22 | }) 23 | 24 | afterEach(() => { 25 | windowSpy.mockRestore() 26 | }) 27 | 28 | it('Should throw error if no config passed', () => { 29 | expect(() => { 30 | router = new Router() 31 | }).toThrowError() 32 | }) 33 | 34 | it('Creates instance', () => { 35 | expect(() => { 36 | router = new Router(config) 37 | }).not.toThrowError() 38 | }) 39 | 40 | it('Returns empty string if base url is nullable', () => { 41 | expect(router.base).toBe('') 42 | }) 43 | 44 | it('Adds event listener on popstate event in history mode', (done) => { 45 | const listeners = [] 46 | windowSpy.mockImplementation(() => ({ 47 | location: { 48 | pathname: '/' 49 | }, 50 | addEventListener: function (evtName, listener) { 51 | listeners.push(evtName) 52 | }, 53 | history: { 54 | pushState: () => true 55 | } 56 | })) 57 | router = new Router(config) 58 | setTimeout(() => { 59 | expect(listeners.includes('popstate')).toBeTruthy() 60 | done() 61 | }, 0) 62 | }) 63 | 64 | it('Adds event listener on hashchange event in hash mode', (done) => { 65 | const configOverride = { ...config, mode: 'hash' } 66 | const listeners = [] 67 | windowSpy.mockImplementation(() => ({ 68 | location: { 69 | pathname: '/' 70 | }, 71 | addEventListener: function (evtName, listener) { 72 | listeners.push(evtName) 73 | } 74 | })) 75 | router = new Router(configOverride) 76 | setTimeout(() => { 77 | expect(listeners.includes('hashchange')).toBeTruthy() 78 | done() 79 | }, 0) 80 | }) 81 | 82 | it('Executes beforeEnter hook', (done) => { 83 | let hookState = 'NOT_RUNNING' 84 | config.routes[0].beforeEnter = (to, from, next) => { 85 | hookState = 'RUNNING' 86 | next('/') 87 | } 88 | router = new Router(config) 89 | router.push('/route1') 90 | setTimeout(() => { 91 | expect(hookState).toBe('RUNNING') 92 | done() 93 | }, 0) 94 | }) 95 | 96 | it('Skips nonexistent before hook', (done) => { 97 | let hookState = 'NOT_RUNNING' 98 | config.routes[0].beforeEnter = undefined 99 | router = new Router(config) 100 | router.push('/route1') 101 | setTimeout(() => { 102 | expect(hookState).toBe('NOT_RUNNING') 103 | done() 104 | }, 0) 105 | }) 106 | }) 107 | -------------------------------------------------------------------------------- /ssr/index.d.ts: -------------------------------------------------------------------------------- 1 | import { SvelteComponent } from 'svelte/internal' 2 | 3 | declare type RenderResult = { 4 | html: string 5 | css: { 6 | code: string 7 | map: null 8 | } 9 | head: string 10 | } 11 | 12 | declare type RenderOptions = { 13 | url: string 14 | props: { 15 | [key: string]: any 16 | } 17 | component: SvelteComponent 18 | } 19 | 20 | declare type renderFunction = ( 21 | renderOptions: RenderOptions 22 | ) => Promise 23 | 24 | declare function configureEasyrouteSSR(): renderFunction 25 | 26 | export default configureEasyrouteSSR 27 | -------------------------------------------------------------------------------- /ssr/index.js: -------------------------------------------------------------------------------- 1 | const deleteLastSlash = (url) => url.replace(/\/$/, '') 2 | const stripBase = (url, base) => (Boolean(base) ? url.replace(base, '') : url) 3 | 4 | function configureEasyrouteSSR() { 5 | return async (renderOptions) => { 6 | const router = global.$$$easyrouteRouter 7 | if (!router) 8 | throw new Error("[Easyroute SSR] Couldn't find Router instance") 9 | const { component, props, url } = renderOptions 10 | 11 | let [pathname, search] = url.split('?') 12 | search = Boolean(search) ? '?' + search : '' 13 | pathname = deleteLastSlash(pathname) + '/' 14 | 15 | await router.push(stripBase(`${pathname}${search}`, router.base)) 16 | return component.render({ 17 | ...props 18 | }) 19 | } 20 | } 21 | 22 | module.exports = configureEasyrouteSSR 23 | -------------------------------------------------------------------------------- /ssr/registerRouterSSR/index.d.ts: -------------------------------------------------------------------------------- 1 | import Router from 'easyroute-core' 2 | 3 | declare function registerRouter(router: Router): void 4 | 5 | export default registerRouter 6 | -------------------------------------------------------------------------------- /ssr/registerRouterSSR/index.js: -------------------------------------------------------------------------------- 1 | function registerRouterSSR(router) { 2 | global.$$$easyrouteRouter = router 3 | } 4 | 5 | export default registerRouterSSR 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "outDir": "./lib/dist/router", 7 | "rootDir": "./lib/src", 8 | "strict": true, 9 | "noImplicitAny": true, 10 | "strictNullChecks": true, 11 | "strictFunctionTypes": true, 12 | "strictPropertyInitialization": true, 13 | "noImplicitThis": true, 14 | "alwaysStrict": true, 15 | "noUnusedLocals": true, 16 | "noUnusedParameters": true, 17 | "downlevelIteration": true, 18 | "esModuleInterop": true, 19 | "skipLibCheck": true, 20 | "forceConsistentCasingInFileNames": true 21 | }, 22 | "include": [ 23 | "lib/src/**/*" 24 | ], 25 | "exclude": [ 26 | "node_modules", 27 | "lib/dist", 28 | "dist", 29 | "**/*.spec.ts" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /types.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { SvelteComponentTyped } from 'svelte' 4 | import Router from 'easyroute-core' 5 | 6 | export interface RouterOutletProps 7 | extends svelte.JSX.HTMLAttributes { 8 | /** 9 | * @default null 10 | */ 11 | transition?: string | null 12 | 13 | /** 14 | * @default false 15 | */ 16 | forceRemount?: boolean 17 | 18 | /** 19 | * default "default" 20 | */ 21 | name?: string 22 | } 23 | 24 | export interface RouterLinkProps 25 | extends svelte.JSX.HTMLAttributes { 26 | to: string 27 | } 28 | 29 | export default Router 30 | export class RouterOutlet extends SvelteComponentTyped {} 31 | export class RouterLink extends SvelteComponentTyped {} 32 | export class EasyrouteProvider extends SvelteComponentTyped<{ 33 | router: Router 34 | }> {} 35 | -------------------------------------------------------------------------------- /useCurrentRoute/index.d.ts: -------------------------------------------------------------------------------- 1 | export default useCurrentRoute 2 | declare function useCurrentRoute(listener: (value: any) => void): () => void 3 | -------------------------------------------------------------------------------- /useCurrentRoute/index.js: -------------------------------------------------------------------------------- 1 | import { getContext } from 'svelte' 2 | 3 | const useCurrentRoute = (listener) => { 4 | const context = getContext('easyrouteContext') 5 | if (!context) throw new Error('[Easyroute] No router context found') 6 | return context.router.currentRouteData.subscribe(listener) 7 | } 8 | 9 | export default useCurrentRoute 10 | --------------------------------------------------------------------------------