39 | Controlling data loading via route metadata
40 |
41 |
42 | Server side rendering (SSR) that is easy to configure v3.0.0+
43 |
44 |
45 | Hash, history and silent modes
46 |
47 |
48 | Svelte + TypeScript support v3.1.1+
49 |
50 |
51 |
52 |
Why you should try it?
53 |
54 |
55 | Well-known syntax. It was inspired by the router for Vue.js, so this router will be understandable to many of you.
56 |
57 |
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 |
60 |
61 | Community-friendly. Repository cloning and pull requests are welcome! Together we can make the perfect tool for developing on Svelte
62 |
98 | Глобальные и индивидуальные навигационные хуки
99 |
100 |
101 | Управление загрузкой данных через метаданные маршрутов
102 |
103 |
104 | Рендеринг на стороне сервера (SSR), который легко настроить v3.0.0+
105 |
106 |
107 | Режими hash, history и "тихий"
108 |
109 |
110 | Поддержка Svelte + TypeScript v3.1.1+
111 |
112 |
113 |
114 |
Почему стоит попробовать?
115 |
116 |
117 | Хорошо знакомый синтаксис.
118 | Сделан по образу и подобию роутра для Vue.js, так что этот роутер будет понятен многим из вас.
119 |
120 |
121 | Всё ещё разрабатывается.
122 | Многие возможности ещё впереди. Уже сейчас его можно использовать в проектах, и я буду рад узнать, что можно сделать лучше.
123 |
124 |
125 | В сообществе.
126 | Буду рад вашим пулл-реквестам! Вместе мы можем сделать идеальный инструмент для разработки на Svelte.
127 |
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 |