sv-router 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sv-router",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Modern Svelte Routing",
5
5
  "keywords": [
6
6
  "svelte",
package/src/cli/index.js CHANGED
@@ -18,10 +18,13 @@ function arg(option) {
18
18
  return args[pathArgIndex];
19
19
  }
20
20
 
21
- const pathArg = arg('path');
22
- if (pathArg) genConfig.routesPath = pathArg;
21
+ const allLazyArg = arg('allLazy');
22
+ if (allLazyArg) genConfig.allLazy = true;
23
23
 
24
24
  const jsArg = arg('js');
25
25
  if (jsArg) genConfig.routesInJs = true;
26
26
 
27
+ const pathArg = arg('path');
28
+ if (pathArg) genConfig.routesPath = pathArg;
29
+
27
30
  writeRouterCode();
@@ -16,6 +16,9 @@ export let params = $state({ value: {} });
16
16
 
17
17
  export let location = $state(updatedLocation());
18
18
 
19
+ let navigationIndex = 0;
20
+ let pendingNavigationIndex = 0;
21
+
19
22
  /** @type {{ name?: string }} */
20
23
  export const base = {
21
24
  name: undefined,
@@ -46,7 +49,7 @@ export function createRouter(r) {
46
49
  return params.value;
47
50
  },
48
51
  get pathname() {
49
- return location.pathname;
52
+ return /** @type {import('./index.d.ts').Path<T>} */ (location.pathname);
50
53
  },
51
54
  get search() {
52
55
  return location.search;
@@ -90,6 +93,10 @@ export async function onNavigate(path, options = {}) {
90
93
  if (!routes) {
91
94
  throw new Error('Router not initialized: `createRouter` was not called.');
92
95
  }
96
+
97
+ navigationIndex++;
98
+ const currentNavigationIndex = navigationIndex;
99
+
93
100
  let matchPath = path || globalThis.location.pathname;
94
101
  if (base.name && matchPath.startsWith(base.name)) {
95
102
  matchPath = matchPath.slice(base.name.length) || '/';
@@ -98,14 +105,22 @@ export async function onNavigate(path, options = {}) {
98
105
 
99
106
  for (const { beforeLoad } of hooks) {
100
107
  try {
108
+ pendingNavigationIndex = currentNavigationIndex;
101
109
  await beforeLoad?.();
102
110
  } catch {
103
111
  return;
104
112
  }
105
113
  }
106
114
 
107
- componentTree.value = await resolveRouteComponents(match ? [...layouts, match] : layouts);
108
- params.value = newParams || {};
115
+ const fromBeforeLoadHook = new Error().stack?.includes('beforeLoad');
116
+
117
+ const routeComponents = await resolveRouteComponents(match ? [...layouts, match] : layouts);
118
+ if (
119
+ navigationIndex !== currentNavigationIndex ||
120
+ (fromBeforeLoadHook && pendingNavigationIndex + 1 !== currentNavigationIndex)
121
+ ) {
122
+ return;
123
+ }
109
124
 
110
125
  if (path) {
111
126
  if (options.search) path += options.search;
@@ -115,6 +130,14 @@ export async function onNavigate(path, options = {}) {
115
130
  globalThis.history[historyMethod](options.state || {}, '', to);
116
131
  }
117
132
 
133
+ if (options.viewTransition && document.startViewTransition !== undefined) {
134
+ document.startViewTransition(() => {
135
+ componentTree.value = routeComponents;
136
+ });
137
+ } else {
138
+ componentTree.value = routeComponents;
139
+ }
140
+ params.value = newParams || {};
118
141
  syncSearchParams();
119
142
  Object.assign(location, updatedLocation());
120
143
 
@@ -139,13 +162,14 @@ export function onGlobalClick(event) {
139
162
  if (url.origin !== currentOrigin) return;
140
163
 
141
164
  event.preventDefault();
142
- const { replace, state, scrollToTop } = anchor.dataset;
165
+ const { replace, state, scrollToTop, viewTransition } = anchor.dataset;
143
166
  onNavigate(url.pathname, {
144
167
  replace: replace === '' || replace === 'true',
145
168
  search: url.search,
146
169
  state,
147
170
  hash: url.hash,
148
171
  scrollToTop: scrollToTop === 'false' ? false : /** @type ScrollBehavior */ (scrollToTop),
172
+ viewTransition: viewTransition === '' || viewTransition === 'true',
149
173
  });
150
174
  }
151
175
 
package/src/gen/config.js CHANGED
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * @type {{
3
- * routesPath: string;
3
+ * allLazy: boolean;
4
4
  * routesInJs: boolean;
5
+ * routesPath: string;
5
6
  * readonly genCodeDirPath: string;
6
7
  * readonly routerPath: string;
7
8
  * readonly tsconfigPath: string;
@@ -9,8 +10,9 @@
9
10
  * }}
10
11
  */
11
12
  export const genConfig = {
12
- routesPath: 'src/routes',
13
+ allLazy: false,
13
14
  routesInJs: false,
15
+ routesPath: 'src/routes',
14
16
  genCodeDirPath: '.router',
15
17
  get routerPath() {
16
18
  return '.router/router.' + (this.routesInJs ? 'js' : 'ts');
@@ -17,16 +17,17 @@ const HOOKS_FILENAME_REGEX = /(?<=[/.]|^)(hooks)(\.svelte)?\.(js|ts)$/; // hooks
17
17
 
18
18
  /**
19
19
  * @param {string} routesPath
20
+ * @param {{ allLazy?: boolean }} [options]
20
21
  * @returns {string}
21
22
  */
22
- export function generateRouterCode(routesPath) {
23
+ export function generateRouterCode(routesPath, options) {
23
24
  const absoluteRoutesPath = path.join(process.cwd(), routesPath);
24
25
  if (!fs.existsSync(absoluteRoutesPath)) {
25
26
  throw new Error(`Routes directory not found at \`${routesPath}\``);
26
27
  }
27
28
  const fileTree = buildFileTree(absoluteRoutesPath);
28
29
  const routeMap = createRouteMap(fileTree);
29
- return createRouterCode(routeMap, path.posix.join('..', routesPath));
30
+ return createRouterCode(routeMap, path.posix.join('..', routesPath), options);
30
31
  }
31
32
 
32
33
  /**
@@ -119,9 +120,10 @@ function filePathToRoute(filename) {
119
120
  /**
120
121
  * @param {GeneratedRoutes} routes
121
122
  * @param {string} routesPath
123
+ * @param {{ allLazy?: boolean }} [options]
122
124
  * @returns {string}
123
125
  */
124
- export function createRouterCode(routes, routesPath) {
126
+ export function createRouterCode(routes, routesPath, { allLazy = false } = {}) {
125
127
  if (!routesPath.endsWith('/')) {
126
128
  routesPath += '/';
127
129
  }
@@ -135,7 +137,7 @@ export function createRouterCode(routes, routesPath) {
135
137
  for (const [key, value] of Object.entries(routes)) {
136
138
  if (typeof value === 'object') {
137
139
  result[key] = handleImports(value, routesPath);
138
- } else if (key === 'hooks' || !value.endsWith('.lazy.svelte')) {
140
+ } else if (key === 'hooks' || (!value.endsWith('.lazy.svelte') && !allLazy)) {
139
141
  const variableName = pathToCorrectCasing(value);
140
142
  importsMap.set(variableName, routesPath + value);
141
143
  result[key] = variableName;
@@ -32,7 +32,7 @@ export function writeRouterCode() {
32
32
  writeFileIfDifferent(genConfig.tsconfigPath, JSON.stringify(tsConfig, undefined, 2));
33
33
 
34
34
  // Write `.router/router.ts` file
35
- const routerCode = generateRouterCode(genConfig.routesPath);
35
+ const routerCode = generateRouterCode(genConfig.routesPath, { allLazy: genConfig.allLazy });
36
36
  const written = writeFileIfDifferent(genConfig.routerPath, routerCode);
37
37
 
38
38
  if (written) {
@@ -61,6 +61,8 @@ export function matchRoute(pathname, routes) {
61
61
  }
62
62
  if (breakFromLayouts) {
63
63
  routePart = `(${routePart})`;
64
+ } else if ('layout' in routes && routes.layout) {
65
+ layouts.push(routes.layout);
64
66
  }
65
67
  const resolvedPath = /** @type {keyof Routes} */ (
66
68
  (index ? '/' : '') + routeParts.join('/')
package/src/index.d.ts CHANGED
@@ -134,7 +134,7 @@ export type RouterApi<T extends Routes> = {
134
134
  */
135
135
  params: AllParams<T>;
136
136
  /** The reactive pathname of the URL. */
137
- pathname: string;
137
+ pathname: Path<T>;
138
138
  /** The reactive query string part of the URL. */
139
139
  search: string;
140
140
  /** The reactive history state that can be passed to the `navigate` function. */
@@ -166,6 +166,7 @@ export type NavigateOptions =
166
166
  state?: string;
167
167
  hash?: string;
168
168
  scrollToTop?: ScrollBehavior | false;
169
+ viewTransition?: boolean;
169
170
  }
170
171
  | undefined;
171
172
 
@@ -2,17 +2,23 @@ import type { Plugin } from 'vite';
2
2
 
3
3
  export type RouterOptions = {
4
4
  /**
5
- * The path to the routes folder.
5
+ * If true, all routes will be lazy loaded by default.
6
6
  *
7
- * @default 'src/routes'
7
+ * @default false
8
8
  */
9
- path?: string;
9
+ allLazy?: boolean;
10
10
  /**
11
11
  * If true, generates the routes in a .js file instead of a .ts file.
12
12
  *
13
13
  * @default false
14
14
  */
15
15
  js?: boolean;
16
+ /**
17
+ * The path to the routes folder.
18
+ *
19
+ * @default 'src/routes'
20
+ */
21
+ path?: string;
16
22
  };
17
23
 
18
24
  export const router: (options?: RouterOptions) => Plugin;
@@ -7,8 +7,9 @@ import { writeRouterCode } from '../gen/write-router-code.js';
7
7
  * @returns {import('vite').Plugin}
8
8
  */
9
9
  export function router(options) {
10
- if (options?.path) genConfig.routesPath = options.path;
11
- if (options?.js) genConfig.routesInJs = options.js;
10
+ genConfig.allLazy = options?.allLazy || false;
11
+ genConfig.routesInJs = options?.js || false;
12
+ genConfig.routesPath = options?.path || 'src/routes';
12
13
 
13
14
  return {
14
15
  name: 'sv-router',