@tramvai/module-router 2.40.0 → 2.44.2

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/README.md CHANGED
@@ -268,7 +268,7 @@ const action = declareAction({
268
268
 
269
269
  ### Working with navigation in React components
270
270
 
271
- You can work with routing inside React components using hooks and components - `useNavigate`, `useRoute`, `Link` from the [@tinkoff/router](references/libs/router.md#интеграция-с-react)
271
+ You can work with routing inside React components using hooks and components - `useNavigate` and `useRoute` from the [@tinkoff/router](references/libs/router.md#интеграция-с-react)
272
272
 
273
273
  <p>
274
274
  <details>
@@ -279,6 +279,53 @@ You can work with routing inside React components using hooks and components - `
279
279
  </details>
280
280
  </p>
281
281
 
282
+ #### Link
283
+
284
+ A wrapper for a react component that makes it clickable
285
+
286
+ > If the react component is passed to the Link as children, then this passed component will be rendered and the `href`, `onClick` props will be passed as props to that component and they should be used to make the navigation. Otherwise, the `<a>` tag will be rendered with children as a child.
287
+ > Your passed component need to be wrapped in the `forwardRef` for routes assets prefetching.
288
+
289
+ ```ts
290
+ import { Link } from '@tramvai/module-router';
291
+ import CustomLink from '@custom-scope/link';
292
+
293
+ export const Component = () => {
294
+ return (
295
+ <Link url="/test/">
296
+ <CustomLink />
297
+ </Link>
298
+ );
299
+ };
300
+
301
+ export const WrapLink = () => {
302
+ return <Link url="/test/">Click me</Link>;
303
+ };
304
+ ```
305
+
306
+ ##### Page resources prefetch
307
+
308
+ `Link` component will try to prefetch resources for passed `url`, if this `url` is handled by the application router.
309
+
310
+ It will help to make subsequent page-loads faster because target page assets already be saved in browser cache.
311
+
312
+ How it works:
313
+
314
+ - Component determines when it is in the viewport (using `Intersection Observer`)
315
+ - waits until the browser is idle (using `requestIdleCallback`)
316
+ - checks if the user isn't on a slow connection (using `navigator.connection.effectiveType`) or has data-saver enabled (using `navigator.connection.saveData`)
317
+ - triggers page resources (js, css) prefetching
318
+
319
+ Main reference for this feature - [quicklink](https://github.com/GoogleChromeLabs/quicklink) library.
320
+
321
+ If you want to disable this behaviour, pass `prefetch={false}` property.
322
+
323
+ ```tsx
324
+ export const WrapLink = () => {
325
+ return <Link url="/test/" prefetch={false}>Click me</Link>;
326
+ };
327
+ ```
328
+
282
329
  ### How to set static routes
283
330
 
284
331
  [RouterModule](references/modules/router/base.md) allows you to add new routes when configuring your application. The second way is to pass static routes to DI via the `ROUTES_TOKEN` token.
@@ -0,0 +1,49 @@
1
+ 'use strict';
2
+
3
+ var tslib = require('tslib');
4
+
5
+ var _1LinkFromModule = (function (api) { return tslib.__awaiter(void 0, void 0, void 0, function () {
6
+ return tslib.__generator(this, function (_a) {
7
+ switch (_a.label) {
8
+ case 0: return [4 /*yield*/, api.transform(function (_a, _b, _c) {
9
+ var source = _a.source;
10
+ var j = _b.j;
11
+ var printOptions = _c.printOptions;
12
+ var parsed = j(source);
13
+ var routerLibImport = parsed.find(j.ImportDeclaration, {
14
+ source: { value: '@tinkoff/router' },
15
+ });
16
+ if (routerLibImport) {
17
+ var linkImportSpecifier_1;
18
+ routerLibImport.forEach(function (p) {
19
+ var _a;
20
+ (_a = p.node.specifiers) === null || _a === void 0 ? void 0 : _a.forEach(function (specifier) {
21
+ if ('imported' in specifier && specifier.imported.name === 'Link') {
22
+ linkImportSpecifier_1 = specifier;
23
+ }
24
+ });
25
+ });
26
+ if (linkImportSpecifier_1) {
27
+ routerLibImport.forEach(function (p) {
28
+ var _a, _b;
29
+ // eslint-disable-next-line no-param-reassign
30
+ p.node.specifiers = (_a = p.node.specifiers) === null || _a === void 0 ? void 0 : _a.filter(function (specifier) {
31
+ return !('imported' in specifier && specifier.imported.name === 'Link');
32
+ });
33
+ if (((_b = p.node.specifiers) === null || _b === void 0 ? void 0 : _b.length) === 0) {
34
+ routerLibImport.remove();
35
+ }
36
+ });
37
+ parsed.addImport(j.importDeclaration([linkImportSpecifier_1], j.stringLiteral('@tramvai/module-router')));
38
+ return parsed.toSource(printOptions);
39
+ }
40
+ }
41
+ })];
42
+ case 1:
43
+ _a.sent();
44
+ return [2 /*return*/];
45
+ }
46
+ });
47
+ }); });
48
+
49
+ module.exports = _1LinkFromModule;
@@ -0,0 +1,19 @@
1
+ interface Props {
2
+ children?: any;
3
+ url: string;
4
+ query?: Record<string, string>;
5
+ replace?: boolean;
6
+ target?: string;
7
+ onClick?: Function;
8
+ navigateOptions?: Record<string, any>;
9
+ /**
10
+ * @description enable or disable target page resources prefetching
11
+ * @default true
12
+ */
13
+ prefetch?: boolean;
14
+ }
15
+ declare function Link(props: Props): JSX.Element;
16
+ declare namespace Link {
17
+ var displayName: string;
18
+ }
19
+ export { Link };
@@ -0,0 +1,3 @@
1
+ import type { ExtractDependencyType } from '@tinkoff/dippy';
2
+ import { PAGE_SERVICE_TOKEN } from '@tramvai/tokens-router';
3
+ export declare const usePageService: () => ExtractDependencyType<typeof PAGE_SERVICE_TOKEN>;
@@ -0,0 +1,5 @@
1
+ export declare const usePrefetch: ({ url, target, prefetch, }: {
2
+ url: string;
3
+ target: Element | null;
4
+ prefetch: boolean;
5
+ }) => void;
@@ -0,0 +1,5 @@
1
+ export declare const usePrefetch: ({ url, target, prefetch, }: {
2
+ url: string;
3
+ target: Element | null;
4
+ prefetch: boolean;
5
+ }) => void;
@@ -1,23 +1,27 @@
1
- import { provide, commandLineListTokens, Module, DI_TOKEN, COMMAND_LINE_RUNNER_TOKEN } from '@tramvai/core';
2
- import { Provider, setLogger, NoSpaRouter, Router } from '@tinkoff/router';
3
- export { Link, Provider, useNavigate, useRoute, useRouter, useUrl } from '@tinkoff/router';
1
+ import { provide, commandLineListTokens, createToken as createToken$1, Module, DI_TOKEN, COMMAND_LINE_RUNNER_TOKEN } from '@tramvai/core';
2
+ import { Provider, setLogger, NoSpaRouter, Router, useNavigate, useRoute } from '@tinkoff/router';
3
+ export { Provider, useNavigate, useRoute, useRouter, useUrl } from '@tinkoff/router';
4
4
  import noop from '@tinkoff/utils/function/noop';
5
- import { LOGGER_TOKEN, BUNDLE_MANAGER_TOKEN, ACTION_REGISTRY_TOKEN, RESPONSE_MANAGER_TOKEN, DISPATCHER_CONTEXT_TOKEN, CONTEXT_TOKEN, COMPONENT_REGISTRY_TOKEN, COMBINE_REDUCERS, STORE_TOKEN, ACTION_PAGE_RUNNER_TOKEN } from '@tramvai/tokens-common';
6
- import { ROUTER_GUARD_TOKEN, ROUTE_TRANSFORM_TOKEN, ROUTER_TOKEN, ROUTES_TOKEN, ROUTE_RESOLVE_TOKEN, ROUTER_SPA_ACTIONS_RUN_MODE_TOKEN, PAGE_SERVICE_TOKEN } from '@tramvai/tokens-router';
5
+ import { LOGGER_TOKEN, BUNDLE_MANAGER_TOKEN, ACTION_REGISTRY_TOKEN, RESPONSE_MANAGER_TOKEN, DISPATCHER_TOKEN, DISPATCHER_CONTEXT_TOKEN, CONTEXT_TOKEN, COMPONENT_REGISTRY_TOKEN, COMBINE_REDUCERS, STORE_TOKEN, ACTION_PAGE_RUNNER_TOKEN } from '@tramvai/tokens-common';
6
+ import { ROUTER_GUARD_TOKEN, PAGE_SERVICE_TOKEN, ROUTE_TRANSFORM_TOKEN, ROUTER_TOKEN, ROUTES_TOKEN, ROUTE_RESOLVE_TOKEN, ROUTER_SPA_ACTIONS_RUN_MODE_TOKEN, LINK_PREFETCH_MANAGER_TOKEN } from '@tramvai/tokens-router';
7
7
  export * from '@tramvai/tokens-router';
8
8
  import { EXTEND_RENDER, TRAMVAI_RENDER_MODE } from '@tramvai/tokens-render';
9
9
  import { createEvent, createReducer } from '@tramvai/state';
10
10
  import flatten from '@tinkoff/utils/array/flatten';
11
11
  import { createToken } from '@tinkoff/dippy';
12
+ import isArray from '@tinkoff/utils/is/array';
13
+ import { resolveLazyComponent, useDi } from '@tramvai/react';
12
14
  import identity from '@tinkoff/utils/function/identity';
13
15
  import compose from '@tinkoff/utils/function/compose';
14
16
  import replace from '@tinkoff/utils/string/replace';
15
17
  import { jsx } from 'react/jsx-runtime';
16
18
  import { isFileSystemPageComponent, fileSystemPagesEnabled, getStaticFileSystemPages, fileSystemPageToRoute } from '@tramvai/experiments';
19
+ import { addQuery } from '@tinkoff/url';
17
20
  import uniq from '@tinkoff/utils/array/uniq';
18
21
  import { isRedirectFoundError, isNotFoundError } from '@tinkoff/errors';
19
22
  import { __decorate } from 'tslib';
20
23
  import { CHILD_APP_INTERNAL_ROOT_STATE_ALLOWED_STORE_TOKEN } from '@tramvai/tokens-child-app';
24
+ import { useEffect, useState, useCallback, isValidElement, cloneElement } from 'react';
21
25
 
22
26
  const setCurrentNavigation = createEvent('SET_CURRENT_ROUTE');
23
27
  const setUrlOnRehydrate = createEvent('SET_URL_ON_REHYDRATE');
@@ -64,7 +68,7 @@ const afterUpdateCurrentHooksToken = createToken('router afterUpdateCurrentHooks
64
68
  const routeTransformToken = createToken('router finalRouteTransform');
65
69
  createToken('router bundleInfoAdditional');
66
70
 
67
- const loadBundle = ({ bundleManager, logger, actionRegistry, responseManager, dispatcherContext, }) => {
71
+ const loadBundle = ({ bundleManager, logger, actionRegistry, responseManager, dispatcher, dispatcherContext, pageService, }) => {
68
72
  const log = logger('route:load-bundles');
69
73
  return async ({ to }) => {
70
74
  var _a;
@@ -72,7 +76,7 @@ const loadBundle = ({ bundleManager, logger, actionRegistry, responseManager, di
72
76
  event: 'load-bundle',
73
77
  route: to,
74
78
  });
75
- const { bundle, pageComponent } = to.config;
79
+ const { bundle, pageComponent, nestedLayoutComponent } = to.config;
76
80
  if (!bundleManager.has(bundle, pageComponent)) {
77
81
  log.info({
78
82
  event: 'load-bundle-not-found',
@@ -87,12 +91,33 @@ const loadBundle = ({ bundleManager, logger, actionRegistry, responseManager, di
87
91
  // несмотря на наличие бандла в разметке, все равно мы должны дождаться
88
92
  // его загрузки перед рендером
89
93
  try {
90
- const { components, reducers } = await bundleManager.get(bundle, pageComponent);
94
+ const promises = [];
95
+ // for file-system pages bundle is virtual, so it will be loaded in parallel with nested layout
96
+ // for classic bundles, page component will be loaded after bundle loading
97
+ promises.push(bundleManager.get(bundle, pageComponent));
98
+ if (nestedLayoutComponent) {
99
+ promises.push(resolveLazyComponent(pageService.resolveComponentFromConfig('nestedLayout')));
100
+ }
101
+ const [resolvedBundle, resolvedLayout] = await Promise.all(promises);
102
+ const { components, reducers } = resolvedBundle;
91
103
  const component = components[pageComponent];
92
104
  // pageComponent should have required fields even if it is lazy,
93
105
  // thanks to `git log 9466cb32bfb71ba49144ef839d3d5bce246e213c -L90,103:packages/modules/common/src/bundleManager/bundleManager.ts`
94
106
  (_a = component === null || component === void 0 ? void 0 : component.reducers) === null || _a === void 0 ? void 0 : _a.forEach((reducer) => dispatcherContext.getStore(reducer));
95
107
  reducers === null || reducers === void 0 ? void 0 : reducers.forEach((reducer) => dispatcherContext.getStore(reducer));
108
+ // @todo: reuse logic from bundleManager?
109
+ // register nested layout actions and reducers for current page
110
+ if (resolvedLayout) {
111
+ if ('actions' in resolvedLayout && isArray(resolvedLayout.actions)) {
112
+ actionRegistry.add(pageComponent, resolvedLayout.actions);
113
+ }
114
+ if ('reducers' in resolvedLayout && isArray(resolvedLayout.reducers)) {
115
+ resolvedLayout.reducers.forEach((reducer) => {
116
+ dispatcher.registerStore(reducer);
117
+ dispatcherContext.getStore(reducer);
118
+ });
119
+ }
120
+ }
96
121
  }
97
122
  catch (error) {
98
123
  log.error({
@@ -128,7 +153,9 @@ const commonGuards = [
128
153
  bundleManager: BUNDLE_MANAGER_TOKEN,
129
154
  actionRegistry: ACTION_REGISTRY_TOKEN,
130
155
  responseManager: RESPONSE_MANAGER_TOKEN,
156
+ dispatcher: DISPATCHER_TOKEN,
131
157
  dispatcherContext: DISPATCHER_CONTEXT_TOKEN,
158
+ pageService: PAGE_SERVICE_TOKEN,
132
159
  },
133
160
  },
134
161
  {
@@ -430,6 +457,111 @@ const providers$1 = [
430
457
  },
431
458
  ];
432
459
 
460
+ const PREFETCHED_LINKS_CACHE_TOKEN = createToken$1();
461
+ const PREFETCHED_LINKS_QUEUE_TOKEN = createToken$1();
462
+ const prefetchProviders = [
463
+ provide({
464
+ provide: PREFETCHED_LINKS_CACHE_TOKEN,
465
+ useValue: new Set(),
466
+ }),
467
+ provide({
468
+ provide: PREFETCHED_LINKS_QUEUE_TOKEN,
469
+ useFactory: () => {
470
+ let queue = Promise.resolve();
471
+ return {
472
+ add(run) {
473
+ queue = queue.then(run);
474
+ return queue;
475
+ },
476
+ };
477
+ },
478
+ }),
479
+ provide({
480
+ provide: LINK_PREFETCH_MANAGER_TOKEN,
481
+ useFactory: ({ router, routeTransform, routeResolve, componentRegistry, bundleManager, logger, prefetchedLinksCache, prefetchedLinksQueue, }) => {
482
+ const log = logger('link-prefetch-manager');
483
+ const prefetch = async (url) => {
484
+ // prefetch needed for cache assets by browser, so we want to prefetch resources for any page only once.
485
+ // cache will work only for SPA-router, but anyway resources loading deduplication logic already included in @loadable
486
+ if (prefetchedLinksCache.has(url)) {
487
+ return;
488
+ }
489
+ prefetchedLinksCache.add(url);
490
+ log.info({ event: 'prefetch-route-init', url });
491
+ // first, try to find static or resolved dynamic route
492
+ let route = router.resolve(url);
493
+ // if route not found, try to resolve dynamic route,
494
+ // logic from `ROUTER_TOKEN` provider factory, without `router.addRoute` method call
495
+ if (!route && routeResolve) {
496
+ // add query `prefetchRoute` to indicate to any `routeResolve` implementation
497
+ // that this route request no need to be cached or have any personalization
498
+ const parsedUrl = addQuery(url, { prefetchRoute: 'true' });
499
+ route = await routeResolve({
500
+ url: parsedUrl,
501
+ type: 'navigate',
502
+ });
503
+ if (route) {
504
+ route = routeTransform(route);
505
+ }
506
+ }
507
+ if (!route) {
508
+ log.info({ event: 'prefetch-route-not-found', url });
509
+ return;
510
+ }
511
+ // @todo: очередь запросов!
512
+ // get name of everything what we need to prefetch,
513
+ // @loadable will load all scripts and styles for us
514
+ const { config: { pageComponent, bundle, nestedLayoutComponent }, } = route;
515
+ const promises = [];
516
+ // no need for bundle and page component actions amd reducers initialization,
517
+ // so copy only part of `modules/guards/common/loadBundle.ts` logic,
518
+ // all relative assets will be fetched
519
+ if (bundleManager.has(bundle, pageComponent)) {
520
+ promises.push(bundleManager.get(bundle, pageComponent));
521
+ }
522
+ // no need for nestedLayoutComponent actions amd reducers initialization,
523
+ // so copy only part of `modules/guards/common/loadBundle.ts` logic,
524
+ // all relative assets will be fetched
525
+ if (nestedLayoutComponent) {
526
+ promises.push(resolveLazyComponent(componentRegistry.get(nestedLayoutComponent, isFileSystemPageComponent(pageComponent) ? '__default' : bundle)));
527
+ }
528
+ await Promise.all(promises)
529
+ .then(() => {
530
+ log.info({ event: 'prefetch-route-success', url });
531
+ })
532
+ .catch((error) => {
533
+ log.warn({
534
+ event: 'prefetch-fail',
535
+ url,
536
+ error,
537
+ });
538
+ });
539
+ };
540
+ return {
541
+ prefetch: (url) => {
542
+ // fetch assets sequentially
543
+ return prefetchedLinksQueue.add(() => prefetch(url));
544
+ },
545
+ };
546
+ },
547
+ deps: {
548
+ router: ROUTER_TOKEN,
549
+ routeTransform: routeTransformToken,
550
+ routeResolve: {
551
+ token: ROUTE_RESOLVE_TOKEN,
552
+ optional: true,
553
+ },
554
+ componentRegistry: COMPONENT_REGISTRY_TOKEN,
555
+ bundleManager: BUNDLE_MANAGER_TOKEN,
556
+ logger: LOGGER_TOKEN,
557
+ prefetchedLinksCache: PREFETCHED_LINKS_CACHE_TOKEN,
558
+ prefetchedLinksQueue: PREFETCHED_LINKS_QUEUE_TOKEN,
559
+ },
560
+ }),
561
+ ];
562
+
563
+ const clientTokens = [...prefetchProviders];
564
+
433
565
  const stopRunAtError = (error) => {
434
566
  if (isNotFoundError(error) || isRedirectFoundError(error)) {
435
567
  return true;
@@ -461,6 +593,7 @@ const runActionsFactory = ({ store, router, actionRegistry, actionPageRunner, })
461
593
  };
462
594
 
463
595
  const providers = [
596
+ ...clientTokens,
464
597
  ...providers$1,
465
598
  provide({
466
599
  provide: commandLineListTokens.customerStart,
@@ -529,7 +662,9 @@ const providers = [
529
662
  // run navigation to current location.
530
663
  if (renderMode === 'client' &&
531
664
  (!currentRoute || (currentRoute && currentRoute.actualPath !== window.location.pathname))) {
532
- return router.navigate(window.location.href);
665
+ // replace because otherwice we will push in the history the same url twice,
666
+ // and history.back will return to the same url
667
+ return router.navigate({ url: window.location.href, replace: true });
533
668
  }
534
669
  };
535
670
  },
@@ -683,4 +818,102 @@ RouterChildAppModule = __decorate([
683
818
  })
684
819
  ], RouterChildAppModule);
685
820
 
686
- export { NoSpaRouterModule, RouterChildAppModule, RouterStore, SpaRouterModule, generateForRoot, setCurrentNavigation, setUrlOnRehydrate };
821
+ const requestIdleCallback = window.requestIdleCallback ||
822
+ function (callback) {
823
+ return window.setTimeout(callback, 1);
824
+ };
825
+ const cancelIdleCallback = window.cancelIdleCallback ||
826
+ function (timer) {
827
+ return window.clearTimeout(timer);
828
+ };
829
+
830
+ const isUserNetworkConditionsSuitableForPrefetch = () => {
831
+ var _a;
832
+ const { saveData, effectiveType = '' } = (_a = navigator.connection) !== null && _a !== void 0 ? _a : {};
833
+ return !saveData && !(effectiveType === '2g' || effectiveType === 'slow-2g');
834
+ };
835
+ const usePrefetch = ({ url, target, prefetch, }) => {
836
+ const linkPrefetchManager = useDi(LINK_PREFETCH_MANAGER_TOKEN);
837
+ useEffect(() => {
838
+ if (!target || !prefetch) {
839
+ return;
840
+ }
841
+ let idleId = null;
842
+ const observer = new IntersectionObserver((entries) => {
843
+ entries.forEach((entry) => {
844
+ // when `Link` element in viewport
845
+ if (entry.isIntersecting && entry.intersectionRatio > 0) {
846
+ // only once
847
+ observer.disconnect();
848
+ if (isUserNetworkConditionsSuitableForPrefetch()) {
849
+ // trigger prefetching when browser is idle
850
+ idleId = requestIdleCallback(() => {
851
+ linkPrefetchManager.prefetch(url);
852
+ });
853
+ }
854
+ }
855
+ });
856
+ });
857
+ observer.observe(target);
858
+ return () => {
859
+ observer.disconnect();
860
+ cancelIdleCallback(idleId);
861
+ };
862
+ }, [url, target, prefetch, linkPrefetchManager]);
863
+ };
864
+
865
+ function Link(props) {
866
+ const { children, onClick, url, query, replace, target, navigateOptions, prefetch = true, ...otherProps } = props;
867
+ const navigate = useNavigate({ url, query, replace, ...navigateOptions });
868
+ const [linkElement, setLinkElement] = useState(null);
869
+ usePrefetch({
870
+ url,
871
+ prefetch,
872
+ target: linkElement,
873
+ });
874
+ const handleClick = useCallback((event) => {
875
+ // ignores the navigation when clicked using right mouse button or
876
+ // by holding a special modifier key: ctrl, command, win, alt, shift
877
+ if (target ||
878
+ event.ctrlKey ||
879
+ event.metaKey ||
880
+ event.altKey ||
881
+ event.shiftKey ||
882
+ event.button !== 0) {
883
+ return;
884
+ }
885
+ event.preventDefault();
886
+ navigate();
887
+ onClick && onClick(event);
888
+ }, [navigate, target, onClick]);
889
+ const extraProps = { href: url, onClick: handleClick, target };
890
+ if (isValidElement(children)) {
891
+ return cloneElement(children, {
892
+ // @ts-expect-error
893
+ ref: (element) => {
894
+ // @ts-expect-error
895
+ const { ref } = children;
896
+ // preserve original ref
897
+ if (typeof ref === 'function') {
898
+ ref(element);
899
+ }
900
+ else if (typeof ref === 'object' && ref !== null) {
901
+ ref.current = element;
902
+ }
903
+ setLinkElement(element);
904
+ },
905
+ ...extraProps,
906
+ });
907
+ }
908
+ return (
909
+ // eslint-disable-next-line react/jsx-props-no-spreading
910
+ jsx("a", { ref: (element) => setLinkElement(element), ...otherProps, ...extraProps, children: children }));
911
+ }
912
+ Link.displayName = 'Link';
913
+
914
+ const usePageService = () => {
915
+ useRoute();
916
+ return useDi(PAGE_SERVICE_TOKEN);
917
+ };
918
+
919
+ export { Link, NoSpaRouterModule, RouterChildAppModule, RouterStore, SpaRouterModule, generateForRoot, setCurrentNavigation, setUrlOnRehydrate, usePageService };
package/lib/index.d.ts CHANGED
@@ -1,6 +1,27 @@
1
1
  export { NoSpaRouterModule, SpaRouterModule } from './modules/server';
2
2
  export { RouterChildAppModule } from './modules/child-app';
3
- export { Provider, useNavigate, useRoute, useRouter, useUrl, Link } from '@tinkoff/router';
3
+ export { Provider, useNavigate, useRoute, useRouter, useUrl } from '@tinkoff/router';
4
+ export { Link } from './components/link';
4
5
  export * from '@tramvai/tokens-router';
5
6
  export { generateForRoot } from './modules/utils/forRoot';
6
7
  export * from './stores/RouterStore';
8
+ export * from './hooks/usePageService';
9
+ type RouteConfig = {
10
+ [key: string]: any;
11
+ bundle?: string;
12
+ pageComponent?: string;
13
+ layoutComponent?: string;
14
+ nestedLayoutComponent?: string;
15
+ errorBoundaryComponent?: string;
16
+ meta?: {
17
+ seo?: {
18
+ metaTags?: Record<string, any>;
19
+ shareSchema?: Record<string, any>;
20
+ };
21
+ };
22
+ };
23
+ declare module '@tramvai/react' {
24
+ interface PageComponentOptions {
25
+ routeConfig?: RouteConfig;
26
+ }
27
+ }
package/lib/index.es.js CHANGED
@@ -1,13 +1,15 @@
1
1
  import { __decorate } from 'tslib';
2
2
  import { provide, commandLineListTokens, Module } from '@tramvai/core';
3
- import { Provider, setLogger, isWildcard, isHistoryFallback, Router } from '@tinkoff/router';
4
- export { Link, Provider, useNavigate, useRoute, useRouter, useUrl } from '@tinkoff/router';
5
- import { LOGGER_TOKEN, BUNDLE_MANAGER_TOKEN, ACTION_REGISTRY_TOKEN, RESPONSE_MANAGER_TOKEN, DISPATCHER_CONTEXT_TOKEN, CONTEXT_TOKEN, COMPONENT_REGISTRY_TOKEN, COMBINE_REDUCERS, REQUEST_MANAGER_TOKEN, STORE_TOKEN, ACTION_PAGE_RUNNER_TOKEN } from '@tramvai/tokens-common';
6
- import { ROUTER_GUARD_TOKEN, ROUTE_TRANSFORM_TOKEN, ROUTER_TOKEN, ROUTES_TOKEN, ROUTE_RESOLVE_TOKEN, ROUTER_SPA_ACTIONS_RUN_MODE_TOKEN, PAGE_SERVICE_TOKEN } from '@tramvai/tokens-router';
3
+ import { Provider, setLogger, isWildcard, isHistoryFallback, Router, useNavigate, useRoute } from '@tinkoff/router';
4
+ export { Provider, useNavigate, useRoute, useRouter, useUrl } from '@tinkoff/router';
5
+ import { LOGGER_TOKEN, BUNDLE_MANAGER_TOKEN, ACTION_REGISTRY_TOKEN, RESPONSE_MANAGER_TOKEN, DISPATCHER_TOKEN, DISPATCHER_CONTEXT_TOKEN, CONTEXT_TOKEN, COMPONENT_REGISTRY_TOKEN, COMBINE_REDUCERS, REQUEST_MANAGER_TOKEN, STORE_TOKEN, ACTION_PAGE_RUNNER_TOKEN } from '@tramvai/tokens-common';
6
+ import { ROUTER_GUARD_TOKEN, PAGE_SERVICE_TOKEN, ROUTE_TRANSFORM_TOKEN, ROUTER_TOKEN, ROUTES_TOKEN, ROUTE_RESOLVE_TOKEN, ROUTER_SPA_ACTIONS_RUN_MODE_TOKEN, LINK_PREFETCH_MANAGER_TOKEN } from '@tramvai/tokens-router';
7
7
  export * from '@tramvai/tokens-router';
8
8
  import flatten from '@tinkoff/utils/array/flatten';
9
9
  import { createToken } from '@tinkoff/dippy';
10
10
  import { createEvent, createReducer } from '@tramvai/state';
11
+ import isArray from '@tinkoff/utils/is/array';
12
+ import { resolveLazyComponent, useDi } from '@tramvai/react';
11
13
  import { EXTEND_RENDER } from '@tramvai/tokens-render';
12
14
  import identity from '@tinkoff/utils/function/identity';
13
15
  import compose from '@tinkoff/utils/function/compose';
@@ -20,6 +22,7 @@ import { SERVER_MODULE_PAPI_PUBLIC_ROUTE } from '@tramvai/tokens-server';
20
22
  import prop from '@tinkoff/utils/object/prop';
21
23
  import { createPapiMethod } from '@tramvai/papi';
22
24
  import { CHILD_APP_INTERNAL_ROOT_STATE_ALLOWED_STORE_TOKEN } from '@tramvai/tokens-child-app';
25
+ import { useState, useCallback, isValidElement, cloneElement } from 'react';
23
26
 
24
27
  const routerClassToken = createToken('router routerClassToken');
25
28
  const additionalRouterParameters = createToken('router additionalParameters');
@@ -66,7 +69,7 @@ const RouterStore = createReducer('router', initialState)
66
69
  };
67
70
  });
68
71
 
69
- const loadBundle = ({ bundleManager, logger, actionRegistry, responseManager, dispatcherContext, }) => {
72
+ const loadBundle = ({ bundleManager, logger, actionRegistry, responseManager, dispatcher, dispatcherContext, pageService, }) => {
70
73
  const log = logger('route:load-bundles');
71
74
  return async ({ to }) => {
72
75
  var _a;
@@ -74,7 +77,7 @@ const loadBundle = ({ bundleManager, logger, actionRegistry, responseManager, di
74
77
  event: 'load-bundle',
75
78
  route: to,
76
79
  });
77
- const { bundle, pageComponent } = to.config;
80
+ const { bundle, pageComponent, nestedLayoutComponent } = to.config;
78
81
  if (!bundleManager.has(bundle, pageComponent)) {
79
82
  log.info({
80
83
  event: 'load-bundle-not-found',
@@ -89,12 +92,33 @@ const loadBundle = ({ bundleManager, logger, actionRegistry, responseManager, di
89
92
  // несмотря на наличие бандла в разметке, все равно мы должны дождаться
90
93
  // его загрузки перед рендером
91
94
  try {
92
- const { components, reducers } = await bundleManager.get(bundle, pageComponent);
95
+ const promises = [];
96
+ // for file-system pages bundle is virtual, so it will be loaded in parallel with nested layout
97
+ // for classic bundles, page component will be loaded after bundle loading
98
+ promises.push(bundleManager.get(bundle, pageComponent));
99
+ if (nestedLayoutComponent) {
100
+ promises.push(resolveLazyComponent(pageService.resolveComponentFromConfig('nestedLayout')));
101
+ }
102
+ const [resolvedBundle, resolvedLayout] = await Promise.all(promises);
103
+ const { components, reducers } = resolvedBundle;
93
104
  const component = components[pageComponent];
94
105
  // pageComponent should have required fields even if it is lazy,
95
106
  // thanks to `git log 9466cb32bfb71ba49144ef839d3d5bce246e213c -L90,103:packages/modules/common/src/bundleManager/bundleManager.ts`
96
107
  (_a = component === null || component === void 0 ? void 0 : component.reducers) === null || _a === void 0 ? void 0 : _a.forEach((reducer) => dispatcherContext.getStore(reducer));
97
108
  reducers === null || reducers === void 0 ? void 0 : reducers.forEach((reducer) => dispatcherContext.getStore(reducer));
109
+ // @todo: reuse logic from bundleManager?
110
+ // register nested layout actions and reducers for current page
111
+ if (resolvedLayout) {
112
+ if ('actions' in resolvedLayout && isArray(resolvedLayout.actions)) {
113
+ actionRegistry.add(pageComponent, resolvedLayout.actions);
114
+ }
115
+ if ('reducers' in resolvedLayout && isArray(resolvedLayout.reducers)) {
116
+ resolvedLayout.reducers.forEach((reducer) => {
117
+ dispatcher.registerStore(reducer);
118
+ dispatcherContext.getStore(reducer);
119
+ });
120
+ }
121
+ }
98
122
  }
99
123
  catch (error) {
100
124
  log.error({
@@ -130,7 +154,9 @@ const commonGuards = [
130
154
  bundleManager: BUNDLE_MANAGER_TOKEN,
131
155
  actionRegistry: ACTION_REGISTRY_TOKEN,
132
156
  responseManager: RESPONSE_MANAGER_TOKEN,
157
+ dispatcher: DISPATCHER_TOKEN,
133
158
  dispatcherContext: DISPATCHER_CONTEXT_TOKEN,
159
+ pageService: PAGE_SERVICE_TOKEN,
134
160
  },
135
161
  },
136
162
  {
@@ -573,6 +599,17 @@ const bundleInfoPapi = createPapiMethod({
573
599
  },
574
600
  });
575
601
 
602
+ // do nothing on server side
603
+ const prefetchManager = {
604
+ prefetch: async (url) => { },
605
+ };
606
+ const prefetchProviders = [
607
+ provide({
608
+ provide: LINK_PREFETCH_MANAGER_TOKEN,
609
+ useValue: prefetchManager,
610
+ }),
611
+ ];
612
+
576
613
  const serverTokens = [
577
614
  {
578
615
  provide: additionalRouterParameters,
@@ -587,6 +624,7 @@ const serverTokens = [
587
624
  multi: true,
588
625
  useValue: bundleInfoPapi,
589
626
  },
627
+ ...prefetchProviders,
590
628
  ];
591
629
 
592
630
  const generateForRoot = (mainModule) => {
@@ -675,4 +713,53 @@ RouterChildAppModule = __decorate([
675
713
  })
676
714
  ], RouterChildAppModule);
677
715
 
678
- export { NoSpaRouterModule, RouterChildAppModule, RouterStore, SpaRouterModule, generateForRoot, setCurrentNavigation, setUrlOnRehydrate };
716
+ function Link(props) {
717
+ const { children, onClick, url, query, replace, target, navigateOptions, prefetch = true, ...otherProps } = props;
718
+ const navigate = useNavigate({ url, query, replace, ...navigateOptions });
719
+ const [linkElement, setLinkElement] = useState(null);
720
+ const handleClick = useCallback((event) => {
721
+ // ignores the navigation when clicked using right mouse button or
722
+ // by holding a special modifier key: ctrl, command, win, alt, shift
723
+ if (target ||
724
+ event.ctrlKey ||
725
+ event.metaKey ||
726
+ event.altKey ||
727
+ event.shiftKey ||
728
+ event.button !== 0) {
729
+ return;
730
+ }
731
+ event.preventDefault();
732
+ navigate();
733
+ onClick && onClick(event);
734
+ }, [navigate, target, onClick]);
735
+ const extraProps = { href: url, onClick: handleClick, target };
736
+ if (isValidElement(children)) {
737
+ return cloneElement(children, {
738
+ // @ts-expect-error
739
+ ref: (element) => {
740
+ // @ts-expect-error
741
+ const { ref } = children;
742
+ // preserve original ref
743
+ if (typeof ref === 'function') {
744
+ ref(element);
745
+ }
746
+ else if (typeof ref === 'object' && ref !== null) {
747
+ ref.current = element;
748
+ }
749
+ setLinkElement(element);
750
+ },
751
+ ...extraProps,
752
+ });
753
+ }
754
+ return (
755
+ // eslint-disable-next-line react/jsx-props-no-spreading
756
+ jsx("a", { ref: (element) => setLinkElement(element), ...otherProps, ...extraProps, children: children }));
757
+ }
758
+ Link.displayName = 'Link';
759
+
760
+ const usePageService = () => {
761
+ useRoute();
762
+ return useDi(PAGE_SERVICE_TOKEN);
763
+ };
764
+
765
+ export { Link, NoSpaRouterModule, RouterChildAppModule, RouterStore, SpaRouterModule, generateForRoot, setCurrentNavigation, setUrlOnRehydrate, usePageService };
package/lib/index.js CHANGED
@@ -10,6 +10,8 @@ var tokensRouter = require('@tramvai/tokens-router');
10
10
  var flatten = require('@tinkoff/utils/array/flatten');
11
11
  var dippy = require('@tinkoff/dippy');
12
12
  var state = require('@tramvai/state');
13
+ var isArray = require('@tinkoff/utils/is/array');
14
+ var react = require('@tramvai/react');
13
15
  var tokensRender = require('@tramvai/tokens-render');
14
16
  var identity = require('@tinkoff/utils/function/identity');
15
17
  var compose = require('@tinkoff/utils/function/compose');
@@ -22,10 +24,12 @@ var tokensServer = require('@tramvai/tokens-server');
22
24
  var prop = require('@tinkoff/utils/object/prop');
23
25
  var papi = require('@tramvai/papi');
24
26
  var tokensChildApp = require('@tramvai/tokens-child-app');
27
+ var react$1 = require('react');
25
28
 
26
29
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
27
30
 
28
31
  var flatten__default = /*#__PURE__*/_interopDefaultLegacy(flatten);
32
+ var isArray__default = /*#__PURE__*/_interopDefaultLegacy(isArray);
29
33
  var identity__default = /*#__PURE__*/_interopDefaultLegacy(identity);
30
34
  var compose__default = /*#__PURE__*/_interopDefaultLegacy(compose);
31
35
  var replace__default = /*#__PURE__*/_interopDefaultLegacy(replace);
@@ -77,7 +81,7 @@ const RouterStore = state.createReducer('router', initialState)
77
81
  };
78
82
  });
79
83
 
80
- const loadBundle = ({ bundleManager, logger, actionRegistry, responseManager, dispatcherContext, }) => {
84
+ const loadBundle = ({ bundleManager, logger, actionRegistry, responseManager, dispatcher, dispatcherContext, pageService, }) => {
81
85
  const log = logger('route:load-bundles');
82
86
  return async ({ to }) => {
83
87
  var _a;
@@ -85,7 +89,7 @@ const loadBundle = ({ bundleManager, logger, actionRegistry, responseManager, di
85
89
  event: 'load-bundle',
86
90
  route: to,
87
91
  });
88
- const { bundle, pageComponent } = to.config;
92
+ const { bundle, pageComponent, nestedLayoutComponent } = to.config;
89
93
  if (!bundleManager.has(bundle, pageComponent)) {
90
94
  log.info({
91
95
  event: 'load-bundle-not-found',
@@ -100,12 +104,33 @@ const loadBundle = ({ bundleManager, logger, actionRegistry, responseManager, di
100
104
  // несмотря на наличие бандла в разметке, все равно мы должны дождаться
101
105
  // его загрузки перед рендером
102
106
  try {
103
- const { components, reducers } = await bundleManager.get(bundle, pageComponent);
107
+ const promises = [];
108
+ // for file-system pages bundle is virtual, so it will be loaded in parallel with nested layout
109
+ // for classic bundles, page component will be loaded after bundle loading
110
+ promises.push(bundleManager.get(bundle, pageComponent));
111
+ if (nestedLayoutComponent) {
112
+ promises.push(react.resolveLazyComponent(pageService.resolveComponentFromConfig('nestedLayout')));
113
+ }
114
+ const [resolvedBundle, resolvedLayout] = await Promise.all(promises);
115
+ const { components, reducers } = resolvedBundle;
104
116
  const component = components[pageComponent];
105
117
  // pageComponent should have required fields even if it is lazy,
106
118
  // thanks to `git log 9466cb32bfb71ba49144ef839d3d5bce246e213c -L90,103:packages/modules/common/src/bundleManager/bundleManager.ts`
107
119
  (_a = component === null || component === void 0 ? void 0 : component.reducers) === null || _a === void 0 ? void 0 : _a.forEach((reducer) => dispatcherContext.getStore(reducer));
108
120
  reducers === null || reducers === void 0 ? void 0 : reducers.forEach((reducer) => dispatcherContext.getStore(reducer));
121
+ // @todo: reuse logic from bundleManager?
122
+ // register nested layout actions and reducers for current page
123
+ if (resolvedLayout) {
124
+ if ('actions' in resolvedLayout && isArray__default["default"](resolvedLayout.actions)) {
125
+ actionRegistry.add(pageComponent, resolvedLayout.actions);
126
+ }
127
+ if ('reducers' in resolvedLayout && isArray__default["default"](resolvedLayout.reducers)) {
128
+ resolvedLayout.reducers.forEach((reducer) => {
129
+ dispatcher.registerStore(reducer);
130
+ dispatcherContext.getStore(reducer);
131
+ });
132
+ }
133
+ }
109
134
  }
110
135
  catch (error) {
111
136
  log.error({
@@ -141,7 +166,9 @@ const commonGuards = [
141
166
  bundleManager: tokensCommon.BUNDLE_MANAGER_TOKEN,
142
167
  actionRegistry: tokensCommon.ACTION_REGISTRY_TOKEN,
143
168
  responseManager: tokensCommon.RESPONSE_MANAGER_TOKEN,
169
+ dispatcher: tokensCommon.DISPATCHER_TOKEN,
144
170
  dispatcherContext: tokensCommon.DISPATCHER_CONTEXT_TOKEN,
171
+ pageService: tokensRouter.PAGE_SERVICE_TOKEN,
145
172
  },
146
173
  },
147
174
  {
@@ -584,6 +611,17 @@ const bundleInfoPapi = papi.createPapiMethod({
584
611
  },
585
612
  });
586
613
 
614
+ // do nothing on server side
615
+ const prefetchManager = {
616
+ prefetch: async (url) => { },
617
+ };
618
+ const prefetchProviders = [
619
+ core.provide({
620
+ provide: tokensRouter.LINK_PREFETCH_MANAGER_TOKEN,
621
+ useValue: prefetchManager,
622
+ }),
623
+ ];
624
+
587
625
  const serverTokens = [
588
626
  {
589
627
  provide: additionalRouterParameters,
@@ -598,6 +636,7 @@ const serverTokens = [
598
636
  multi: true,
599
637
  useValue: bundleInfoPapi,
600
638
  },
639
+ ...prefetchProviders,
601
640
  ];
602
641
 
603
642
  const generateForRoot = (mainModule) => {
@@ -686,10 +725,55 @@ exports.RouterChildAppModule = tslib.__decorate([
686
725
  })
687
726
  ], exports.RouterChildAppModule);
688
727
 
689
- Object.defineProperty(exports, 'Link', {
690
- enumerable: true,
691
- get: function () { return router.Link; }
692
- });
728
+ function Link(props) {
729
+ const { children, onClick, url, query, replace, target, navigateOptions, prefetch = true, ...otherProps } = props;
730
+ const navigate = router.useNavigate({ url, query, replace, ...navigateOptions });
731
+ const [linkElement, setLinkElement] = react$1.useState(null);
732
+ const handleClick = react$1.useCallback((event) => {
733
+ // ignores the navigation when clicked using right mouse button or
734
+ // by holding a special modifier key: ctrl, command, win, alt, shift
735
+ if (target ||
736
+ event.ctrlKey ||
737
+ event.metaKey ||
738
+ event.altKey ||
739
+ event.shiftKey ||
740
+ event.button !== 0) {
741
+ return;
742
+ }
743
+ event.preventDefault();
744
+ navigate();
745
+ onClick && onClick(event);
746
+ }, [navigate, target, onClick]);
747
+ const extraProps = { href: url, onClick: handleClick, target };
748
+ if (react$1.isValidElement(children)) {
749
+ return react$1.cloneElement(children, {
750
+ // @ts-expect-error
751
+ ref: (element) => {
752
+ // @ts-expect-error
753
+ const { ref } = children;
754
+ // preserve original ref
755
+ if (typeof ref === 'function') {
756
+ ref(element);
757
+ }
758
+ else if (typeof ref === 'object' && ref !== null) {
759
+ ref.current = element;
760
+ }
761
+ setLinkElement(element);
762
+ },
763
+ ...extraProps,
764
+ });
765
+ }
766
+ return (
767
+ // eslint-disable-next-line react/jsx-props-no-spreading
768
+ jsxRuntime.jsx("a", { ref: (element) => setLinkElement(element), ...otherProps, ...extraProps, children: children }));
769
+ }
770
+ Link.displayName = 'Link';
771
+
772
+ const usePageService = () => {
773
+ router.useRoute();
774
+ return react.useDi(tokensRouter.PAGE_SERVICE_TOKEN);
775
+ };
776
+
693
777
  Object.defineProperty(exports, 'Provider', {
694
778
  enumerable: true,
695
779
  get: function () { return router.Provider; }
@@ -710,11 +794,13 @@ Object.defineProperty(exports, 'useUrl', {
710
794
  enumerable: true,
711
795
  get: function () { return router.useUrl; }
712
796
  });
797
+ exports.Link = Link;
713
798
  exports.RouterStore = RouterStore;
714
799
  exports.SpaRouterModule = SpaRouterModule;
715
800
  exports.generateForRoot = generateForRoot;
716
801
  exports.setCurrentNavigation = setCurrentNavigation;
717
802
  exports.setUrlOnRehydrate = setUrlOnRehydrate;
803
+ exports.usePageService = usePageService;
718
804
  Object.keys(tokensRouter).forEach(function (k) {
719
805
  if (k !== 'default' && !exports.hasOwnProperty(k)) Object.defineProperty(exports, k, {
720
806
  enumerable: true,
@@ -1,9 +1,12 @@
1
1
  import type { NavigationGuard } from '@tinkoff/router';
2
- import type { BUNDLE_MANAGER_TOKEN, ACTION_REGISTRY_TOKEN, LOGGER_TOKEN, RESPONSE_MANAGER_TOKEN, DISPATCHER_CONTEXT_TOKEN } from '@tramvai/tokens-common';
3
- export declare const loadBundle: ({ bundleManager, logger, actionRegistry, responseManager, dispatcherContext, }: {
2
+ import type { BUNDLE_MANAGER_TOKEN, ACTION_REGISTRY_TOKEN, LOGGER_TOKEN, RESPONSE_MANAGER_TOKEN, DISPATCHER_CONTEXT_TOKEN, DISPATCHER_TOKEN } from '@tramvai/tokens-common';
3
+ import type { PAGE_SERVICE_TOKEN } from '@tramvai/tokens-router';
4
+ export declare const loadBundle: ({ bundleManager, logger, actionRegistry, responseManager, dispatcher, dispatcherContext, pageService, }: {
4
5
  bundleManager: typeof BUNDLE_MANAGER_TOKEN;
5
6
  logger: typeof LOGGER_TOKEN;
6
7
  actionRegistry: typeof ACTION_REGISTRY_TOKEN;
7
8
  responseManager: typeof RESPONSE_MANAGER_TOKEN;
9
+ dispatcher: typeof DISPATCHER_TOKEN;
8
10
  dispatcherContext: typeof DISPATCHER_CONTEXT_TOKEN;
11
+ pageService: typeof PAGE_SERVICE_TOKEN;
9
12
  }) => NavigationGuard;
@@ -0,0 +1,19 @@
1
+ export declare const clientTokens: (import("@tinkoff/dippy").Provider<unknown, Set<string>> | import("@tinkoff/dippy").Provider<unknown, {
2
+ add(run: () => Promise<void>): Promise<void>;
3
+ }> | import("@tinkoff/dippy").Provider<{
4
+ router: import("@tinkoff/dippy").BaseTokenInterface<import("@tinkoff/router").AbstractRouter>;
5
+ routeTransform: import("@tinkoff/dippy").BaseTokenInterface<import("@tramvai/tokens-router").RouteTransform>;
6
+ routeResolve: {
7
+ token: import("@tinkoff/dippy").BaseTokenInterface<import("@tramvai/tokens-router").RouteResolve>;
8
+ optional: boolean;
9
+ };
10
+ componentRegistry: import("@tinkoff/dippy").BaseTokenInterface<import("@tramvai/tokens-common").ComponentRegistry>;
11
+ bundleManager: import("@tinkoff/dippy").BaseTokenInterface<import("@tramvai/tokens-common").BundleManager>;
12
+ logger: import("@tinkoff/dippy").BaseTokenInterface<import("@tramvai/tokens-common").LoggerFactory>;
13
+ prefetchedLinksCache: import("@tinkoff/dippy").BaseTokenInterface<Set<string>>;
14
+ prefetchedLinksQueue: import("@tinkoff/dippy").BaseTokenInterface<{
15
+ add(run: () => Promise<void>): Promise<void>;
16
+ }>;
17
+ }, {
18
+ prefetch: (url: string) => Promise<void>;
19
+ }>)[];
@@ -0,0 +1,19 @@
1
+ export declare const prefetchProviders: (import("@tramvai/core").Provider<unknown, Set<string>> | import("@tramvai/core").Provider<unknown, {
2
+ add(run: () => Promise<void>): Promise<void>;
3
+ }> | import("@tramvai/core").Provider<{
4
+ router: import("@tinkoff/dippy").BaseTokenInterface<import("@tinkoff/router").AbstractRouter>;
5
+ routeTransform: import("@tinkoff/dippy").BaseTokenInterface<import("@tramvai/tokens-router").RouteTransform>;
6
+ routeResolve: {
7
+ token: import("@tinkoff/dippy").BaseTokenInterface<import("@tramvai/tokens-router").RouteResolve>;
8
+ optional: boolean;
9
+ };
10
+ componentRegistry: import("@tinkoff/dippy").BaseTokenInterface<import("@tramvai/tokens-common").ComponentRegistry>;
11
+ bundleManager: import("@tinkoff/dippy").BaseTokenInterface<import("@tramvai/tokens-common").BundleManager>;
12
+ logger: import("@tinkoff/dippy").BaseTokenInterface<import("@tramvai/tokens-common").LoggerFactory>;
13
+ prefetchedLinksCache: import("@tinkoff/dippy").BaseTokenInterface<Set<string>>;
14
+ prefetchedLinksQueue: import("@tinkoff/dippy").BaseTokenInterface<{
15
+ add(run: () => Promise<void>): Promise<void>;
16
+ }>;
17
+ }, {
18
+ prefetch: (url: string) => Promise<void>;
19
+ }>)[];
@@ -0,0 +1,3 @@
1
+ export declare const prefetchProviders: import("@tramvai/core").Provider<unknown, {
2
+ prefetch: (url: string) => Promise<void>;
3
+ }>[];
@@ -1,6 +1,6 @@
1
1
  import type { REQUEST_MANAGER_TOKEN, RESPONSE_MANAGER_TOKEN } from '@tramvai/tokens-common';
2
2
  import type { Router } from '@tinkoff/router';
3
- declare type RouterOptions = Pick<ConstructorParameters<typeof Router>[0], 'onRedirect' | 'onNotFound' | 'onBlock' | 'defaultRedirectCode'>;
3
+ type RouterOptions = Pick<ConstructorParameters<typeof Router>[0], 'onRedirect' | 'onNotFound' | 'onBlock' | 'defaultRedirectCode'>;
4
4
  export declare const routerOptions: ({ requestManager, responseManager, }: {
5
5
  requestManager: typeof REQUEST_MANAGER_TOKEN;
6
6
  responseManager: typeof RESPONSE_MANAGER_TOKEN;
@@ -1,7 +1,7 @@
1
1
  import type { Provider } from '@tinkoff/dippy';
2
2
  import type { Route } from '@tinkoff/router';
3
3
  import { ROUTER_SPA_ACTIONS_RUN_MODE_TOKEN } from '@tramvai/tokens-router';
4
- declare type ForRoot<T = any> = (routes: Route[], options?: {
4
+ type ForRoot<T = any> = (routes: Route[], options?: {
5
5
  spaActionsMode?: typeof ROUTER_SPA_ACTIONS_RUN_MODE_TOKEN;
6
6
  }) => {
7
7
  mainModule: T;
@@ -1,8 +1,8 @@
1
1
  import type { Url } from '@tinkoff/url';
2
2
  import type { NavigationRoute, NavigateOptions, UpdateCurrentRouteOptions, HistoryOptions } from '@tinkoff/router';
3
3
  import type { TramvaiComponent } from '@tramvai/react';
4
- import type { PAGE_SERVICE_TOKEN } from '@tramvai/tokens-router';
5
- declare type PageServiceInterface = typeof PAGE_SERVICE_TOKEN;
4
+ import type { PageServiceComponentType, PAGE_SERVICE_TOKEN } from '@tramvai/tokens-router';
5
+ type PageServiceInterface = typeof PAGE_SERVICE_TOKEN;
6
6
  export declare class PageService implements PageServiceInterface {
7
7
  private router;
8
8
  private componentRegistry;
@@ -22,7 +22,7 @@ export declare class PageService implements PageServiceInterface {
22
22
  go(to: number, options?: HistoryOptions): Promise<void>;
23
23
  addComponent(name: string, component: TramvaiComponent): void;
24
24
  getComponent(name: string): TramvaiComponent | undefined;
25
- resolveComponentFromConfig(property: 'page' | 'layout' | 'header' | 'footer' | 'errorBoundary'): TramvaiComponent;
25
+ resolveComponentFromConfig(property: PageServiceComponentType): TramvaiComponent;
26
26
  private getComponentsGroupName;
27
27
  }
28
28
  export {};
@@ -1,6 +1,6 @@
1
1
  import type { AbstractRouter } from '@tinkoff/router';
2
2
  import { getDiWrapper } from '@tramvai/test-helpers';
3
- declare type Options = Parameters<typeof getDiWrapper>[0] & {
3
+ type Options = Parameters<typeof getDiWrapper>[0] & {
4
4
  router?: AbstractRouter;
5
5
  };
6
6
  export declare const testGuard: (options: Options) => {
@@ -0,0 +1,2 @@
1
+ export declare const requestIdleCallback: (((callback: IdleRequestCallback, options?: IdleRequestOptions) => number) & typeof globalThis.requestIdleCallback) | ((callback: () => void) => number);
2
+ export declare const cancelIdleCallback: ((handle: number) => void) & typeof globalThis.cancelIdleCallback;
package/package.json CHANGED
@@ -1,17 +1,19 @@
1
1
  {
2
2
  "name": "@tramvai/module-router",
3
- "version": "2.40.0",
3
+ "version": "2.44.2",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "browser": {
7
7
  "./lib/modules/server.js": "./lib/modules/browser.js",
8
+ "./lib/hooks/usePrefetch.js": "./lib/hooks/usePrefetch.browser.js",
8
9
  "./lib/index.es.js": "./lib/index.browser.js"
9
10
  },
10
11
  "typings": "lib/index.d.ts",
11
12
  "files": [
12
13
  "lib",
13
14
  "tests.js",
14
- "tests.d.ts"
15
+ "tests.d.ts",
16
+ "__migrations__"
15
17
  ],
16
18
  "sideEffects": false,
17
19
  "repository": {
@@ -25,25 +27,26 @@
25
27
  },
26
28
  "dependencies": {
27
29
  "@tinkoff/errors": "0.3.5",
28
- "@tinkoff/router": "0.2.4",
30
+ "@tinkoff/router": "0.2.5",
29
31
  "@tinkoff/url": "0.8.4",
30
- "@tramvai/tokens-child-app": "2.40.0",
31
- "@tramvai/tokens-render": "2.40.0",
32
- "@tramvai/tokens-router": "2.40.0",
33
- "@tramvai/tokens-server": "2.40.0",
34
- "@tramvai/experiments": "2.40.0"
32
+ "@tramvai/react": "2.44.2",
33
+ "@tramvai/tokens-child-app": "2.44.2",
34
+ "@tramvai/tokens-render": "2.44.2",
35
+ "@tramvai/tokens-router": "2.44.2",
36
+ "@tramvai/tokens-server": "2.44.2",
37
+ "@tramvai/experiments": "2.44.2"
35
38
  },
36
39
  "peerDependencies": {
37
40
  "@tinkoff/utils": "^2.1.2",
38
- "@tramvai/cli": "2.40.0",
39
- "@tramvai/core": "2.40.0",
40
- "@tramvai/module-log": "2.40.0",
41
- "@tramvai/module-server": "2.40.0",
42
- "@tramvai/papi": "2.40.0",
43
- "@tramvai/state": "2.40.0",
44
- "@tramvai/test-helpers": "2.40.0",
45
- "@tramvai/test-mocks": "2.40.0",
46
- "@tramvai/tokens-common": "2.40.0",
41
+ "@tramvai/cli": "2.44.2",
42
+ "@tramvai/core": "2.44.2",
43
+ "@tramvai/module-log": "2.44.2",
44
+ "@tramvai/module-server": "2.44.2",
45
+ "@tramvai/papi": "2.44.2",
46
+ "@tramvai/state": "2.44.2",
47
+ "@tramvai/test-helpers": "2.44.2",
48
+ "@tramvai/test-mocks": "2.44.2",
49
+ "@tramvai/tokens-common": "2.44.2",
47
50
  "@tinkoff/dippy": "0.8.9",
48
51
  "react": "*",
49
52
  "tslib": "^2.4.0"