@shuvi/platform-web 1.0.60 → 1.0.62

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.
@@ -22,6 +22,7 @@ import * as React from 'react';
22
22
  import { Link as LinkFromRouterReact, RouterContext } from '@shuvi/router-react';
23
23
  import { getFilesOfRoute } from '@shuvi/platform-shared/shared';
24
24
  import useIntersection from './utils/useIntersection';
25
+ import { awaitPageLoadAndIdle } from '@shuvi/utils/idleCallback';
25
26
  const ABSOLUTE_URL_REGEX = /^[a-zA-Z][a-zA-Z\d+\-.]*?:/;
26
27
  const prefetched = {};
27
28
  function hasSupportPrefetch() {
@@ -64,7 +65,10 @@ function prefetchFn(router, to) {
64
65
  return;
65
66
  const canPrefetch = hasSupportPrefetch();
66
67
  yield Promise.all(canPrefetch
67
- ? files.js.map(({ url, id }) => prefetchViaDom(url, id, 'script'))
68
+ ? files.js.map((_a) => __awaiter(this, [_a], void 0, function* ({ url, id }) {
69
+ yield awaitPageLoadAndIdle({ remainingTime: 49, timeout: 10 * 1000 });
70
+ yield prefetchViaDom(url, id, 'script');
71
+ }))
68
72
  : []);
69
73
  });
70
74
  }
@@ -77,7 +81,11 @@ export const Link = function LinkWithPrefetch(_a) {
77
81
  const previousHref = React.useRef(to);
78
82
  const [setIntersectionRef, isVisible, resetVisible] = useIntersection({});
79
83
  const { router } = React.useContext(RouterContext);
80
- const setRef = React.useCallback((el) => {
84
+ const setRef = React.useCallback((el) => __awaiter(this, void 0, void 0, function* () {
85
+ /**
86
+ * Lazy prefetching to avoid negative performance impact for the first page.
87
+ */
88
+ yield awaitPageLoadAndIdle({ remainingTime: 49, timeout: 10 * 1000 });
81
89
  // Before the link getting observed, check if visible state need to be reset
82
90
  if (isHrefValid && previousHref.current !== to) {
83
91
  resetVisible();
@@ -92,7 +100,7 @@ export const Link = function LinkWithPrefetch(_a) {
92
100
  ref.current = el;
93
101
  }
94
102
  }
95
- }, [to, isHrefValid, prefetch, resetVisible, setIntersectionRef, ref]);
103
+ }), [to, isHrefValid, prefetch, resetVisible, setIntersectionRef, ref]);
96
104
  React.useEffect(() => {
97
105
  const shouldPrefetch = isHrefValid && prefetch !== false && isVisible;
98
106
  if (shouldPrefetch && !prefetched[to]) {
@@ -1,5 +1,5 @@
1
1
  import { useCallback, useEffect, useRef, useState } from 'react';
2
- import { requestIdleCallback, cancelIdleCallback } from './requestIdleCallback';
2
+ import { requestIdleCallback, cancelIdleCallback } from '@shuvi/utils/idleCallback';
3
3
  const hasIntersectionObserver = typeof IntersectionObserver !== 'undefined';
4
4
  export default function useIntersection({ rootRef, rootMargin = '0px', disabled }) {
5
5
  const isDisabled = disabled || !hasIntersectionObserver;
@@ -16,11 +16,13 @@ exports.renderToHTML = renderToHTML;
16
16
  const resources_1 = __importDefault(require("@shuvi/service/lib/resources"));
17
17
  const trace_1 = require("@shuvi/shared/constants/trace");
18
18
  const renderer_1 = require("./renderer");
19
+ const AppConfigManager_1 = __importDefault(require("../../setup-app-config/AppConfigManager"));
19
20
  function renderToHTML(_a) {
20
21
  return __awaiter(this, arguments, void 0, function* ({ req, serverPluginContext }) {
21
22
  let result;
22
23
  const renderer = new renderer_1.Renderer({ serverPluginContext });
23
- const { config: { ssr }, appConfig: { router: { basename } } } = serverPluginContext;
24
+ const { config: { ssr } } = serverPluginContext;
25
+ const { basename } = AppConfigManager_1.default.getAppConfig(req).router;
24
26
  const { serverCreateAppTrace } = req._traces;
25
27
  const { application } = resources_1.default.server;
26
28
  const app = serverCreateAppTrace
@@ -1,12 +1,16 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.SpaRenderer = void 0;
4
7
  const shuvi_singleton_runtimeConfig_1 = require("@shuvi/platform-shared/shared/shuvi-singleton-runtimeConfig");
5
8
  const base_1 = require("./base");
9
+ const AppConfigManager_1 = __importDefault(require("../../../setup-app-config/AppConfigManager"));
6
10
  class SpaRenderer extends base_1.BaseRenderer {
7
11
  renderDocument({ req, app }) {
8
12
  const assets = this._getMainAssetTags(req);
9
- const { router: { basename } } = this._serverPluginContext.appConfig;
13
+ const { basename } = AppConfigManager_1.default.getAppConfig(req).router;
10
14
  const appData = {
11
15
  ssr: false,
12
16
  pageData: {},
@@ -1,5 +1,5 @@
1
1
  import { ServerResponse } from 'http';
2
- import { ShuviRequest } from '@shuvi/service';
2
+ import { ShuviRequest, IAppConfigByRequest } from '@shuvi/service';
3
3
  import { IAppContext } from '@shuvi/platform-shared/shared';
4
4
  import { IHtmlDocument } from '../html-render';
5
5
  export interface ModifyHtmlContext {
@@ -15,18 +15,10 @@ export type ISendHtml = (html: string, requestContext: RequestContext) => Promis
15
15
  type AppConfigCtx = {
16
16
  req: ShuviRequest;
17
17
  };
18
- export declare const getAppConfig: import("@shuvi/hook").SyncBailHook<void, AppConfigCtx, {
19
- router: {
20
- basename: string;
21
- };
22
- }>;
18
+ export declare const getAppConfig: import("@shuvi/hook").SyncBailHook<void, AppConfigCtx, IAppConfigByRequest>;
23
19
  export declare const extendedHooks: {
24
20
  getPageData: import("@shuvi/hook").AsyncParallelHook<void, IAppContext, Record<string, unknown>>;
25
- getAppConfig: import("@shuvi/hook").SyncBailHook<void, AppConfigCtx, {
26
- router: {
27
- basename: string;
28
- };
29
- }>;
21
+ getAppConfig: import("@shuvi/hook").SyncBailHook<void, AppConfigCtx, IAppConfigByRequest>;
30
22
  handlePageRequest: import("@shuvi/hook").AsyncSeriesWaterfallHook<IHandlePageRequest, void>;
31
23
  modifyHtml: import("@shuvi/hook").AsyncSeriesHook<IHtmlDocument, ModifyHtmlContext, void>;
32
24
  sendHtml: import("@shuvi/hook").AsyncSeriesWaterfallHook<ISendHtml, void>;
@@ -1,4 +1,4 @@
1
- import { ShuviRequestHandler, IServerPluginContext } from '@shuvi/service';
1
+ import { ShuviRequestHandler, IServerPluginContext, ShuviRequest } from '@shuvi/service';
2
2
  import { DevMiddleware } from '@shuvi/service/lib/server/middlewares/dev';
3
3
  export default class OnDemandRouteManager {
4
4
  devMiddleware: DevMiddleware | null;
@@ -7,6 +7,6 @@ export default class OnDemandRouteManager {
7
7
  constructor(serverPluginContext: IServerPluginContext);
8
8
  getServerMiddleware(): ShuviRequestHandler;
9
9
  ensureRoutesMiddleware(): ShuviRequestHandler;
10
- ensureRoutes(pathname: string): Promise<void>;
10
+ ensureRoutes(pathname: string, req: ShuviRequest): Promise<void>;
11
11
  private _activateModules;
12
12
  }
@@ -16,6 +16,7 @@ const router_1 = require("@shuvi/router");
16
16
  const utils_1 = require("@shuvi/router/lib/utils");
17
17
  const resources_1 = __importDefault(require("@shuvi/service/lib/resources"));
18
18
  const module_replace_plugin_1 = __importDefault(require("@shuvi/toolpack/lib/webpack/plugins/module-replace-plugin"));
19
+ const AppConfigManager_1 = __importDefault(require("../setup-app-config/AppConfigManager"));
19
20
  function acceptsHtml(header, { htmlAcceptHeaders = ['text/html', '*/*'] } = {}) {
20
21
  for (var i = 0; i < htmlAcceptHeaders.length; i++) {
21
22
  if (header.indexOf(htmlAcceptHeaders[i]) !== -1) {
@@ -76,7 +77,7 @@ class OnDemandRouteManager {
76
77
  }
77
78
  let err = null;
78
79
  try {
79
- yield this.ensureRoutes(req.pathname || '/');
80
+ yield this.ensureRoutes(req.pathname || '/', req);
80
81
  }
81
82
  catch (error) {
82
83
  err = error;
@@ -84,9 +85,9 @@ class OnDemandRouteManager {
84
85
  next(err);
85
86
  });
86
87
  }
87
- ensureRoutes(pathname) {
88
+ ensureRoutes(pathname, req) {
88
89
  return __awaiter(this, void 0, void 0, function* () {
89
- const { router: { basename } } = this._serverPluginContext.appConfig;
90
+ const { basename } = AppConfigManager_1.default.getAppConfig(req).router;
90
91
  const matchedRoutes = (0, router_1.matchRoutes)(resources_1.default.server.pageRoutes, pathname, (0, utils_1.normalizeBase)(basename)) || [];
91
92
  const modulesToActivate = matchedRoutes
92
93
  .map(({ route: { __componentRawRequest__ } }) => __componentRawRequest__)
@@ -0,0 +1,22 @@
1
+ import { ShuviRequest, IAppConfigByRequest } from '@shuvi/service';
2
+ export default class AppConfigManager {
3
+ /**
4
+ * A weakmap to store the appConfig by request
5
+ * Never share the same appConfig between requests! Race condition will happen.
6
+ */
7
+ private static _appConfigWeakMap;
8
+ /**
9
+ * default appConfig, can be override by `getAppConfig` hook
10
+ */
11
+ private static _DEFAULT_APP_CONFIG;
12
+ /**
13
+ * Retrieves the appConfig associated with the given request.
14
+ * @throws Error if appConfig is not set for the request.
15
+ */
16
+ static getAppConfig(req: ShuviRequest): IAppConfigByRequest;
17
+ /**
18
+ * Associates the appConfig with the given request.
19
+ * @throws Error if appConfig is already set for the request.
20
+ */
21
+ static setAppConfig(req: ShuviRequest, appConfig: IAppConfigByRequest): void;
22
+ }
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ class AppConfigManager {
4
+ /**
5
+ * Retrieves the appConfig associated with the given request.
6
+ * @throws Error if appConfig is not set for the request.
7
+ */
8
+ static getAppConfig(req) {
9
+ const appConfig = this._appConfigWeakMap.get(req);
10
+ if (!appConfig) {
11
+ return this._DEFAULT_APP_CONFIG;
12
+ }
13
+ return appConfig;
14
+ }
15
+ /**
16
+ * Associates the appConfig with the given request.
17
+ * @throws Error if appConfig is already set for the request.
18
+ */
19
+ static setAppConfig(req, appConfig) {
20
+ if (this._appConfigWeakMap.has(req)) {
21
+ // should only set appConfig once
22
+ return;
23
+ }
24
+ this._appConfigWeakMap.set(req, appConfig);
25
+ }
26
+ }
27
+ /**
28
+ * A weakmap to store the appConfig by request
29
+ * Never share the same appConfig between requests! Race condition will happen.
30
+ */
31
+ AppConfigManager._appConfigWeakMap = new WeakMap();
32
+ /**
33
+ * default appConfig, can be override by `getAppConfig` hook
34
+ */
35
+ AppConfigManager._DEFAULT_APP_CONFIG = {
36
+ router: {
37
+ basename: ''
38
+ }
39
+ };
40
+ exports.default = AppConfigManager;
@@ -1,6 +1,10 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.getSetupAppConfigMiddleware = void 0;
7
+ const AppConfigManager_1 = __importDefault(require("./AppConfigManager"));
4
8
  const getSetupAppConfigMiddleware = (context) => {
5
9
  return (req, _res, next) => {
6
10
  const appConfig = context.serverPluginRunner.getAppConfig({ req });
@@ -8,7 +12,7 @@ const getSetupAppConfigMiddleware = (context) => {
8
12
  if (typeof appConfig.router.basename !== 'string') {
9
13
  throw new Error('[ServerPlugin Hook getAppConfig] appConfig.router.basename must be a string');
10
14
  }
11
- context.appConfig = appConfig;
15
+ AppConfigManager_1.default.setAppConfig(req, appConfig);
12
16
  }
13
17
  next();
14
18
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shuvi/platform-web",
3
- "version": "1.0.60",
3
+ "version": "1.0.62",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/shuvijs/shuvi.git",
@@ -72,15 +72,15 @@
72
72
  },
73
73
  "dependencies": {
74
74
  "@next/react-refresh-utils": "12.1.6",
75
- "@shuvi/error-overlay": "1.0.60",
76
- "@shuvi/hook": "1.0.60",
77
- "@shuvi/platform-shared": "1.0.60",
78
- "@shuvi/router": "1.0.60",
79
- "@shuvi/router-react": "1.0.60",
80
- "@shuvi/runtime": "1.0.60",
81
- "@shuvi/shared": "1.0.60",
82
- "@shuvi/toolpack": "1.0.60",
83
- "@shuvi/utils": "1.0.60",
75
+ "@shuvi/error-overlay": "1.0.62",
76
+ "@shuvi/hook": "1.0.62",
77
+ "@shuvi/platform-shared": "1.0.62",
78
+ "@shuvi/router": "1.0.62",
79
+ "@shuvi/router-react": "1.0.62",
80
+ "@shuvi/runtime": "1.0.62",
81
+ "@shuvi/shared": "1.0.62",
82
+ "@shuvi/toolpack": "1.0.62",
83
+ "@shuvi/utils": "1.0.62",
84
84
  "content-type": "1.0.4",
85
85
  "core-js": "3.6.5",
86
86
  "doura": "0.0.13",
@@ -98,7 +98,7 @@
98
98
  "whatwg-fetch": "3.0.0"
99
99
  },
100
100
  "peerDependencies": {
101
- "@shuvi/service": "1.0.60"
101
+ "@shuvi/service": "1.0.62"
102
102
  },
103
103
  "devDependencies": {
104
104
  "@shuvi/service": "workspace:*",
@@ -1,2 +0,0 @@
1
- export declare const requestIdleCallback: ((callback: IdleRequestCallback, options?: IdleRequestOptions) => number) & typeof globalThis.requestIdleCallback;
2
- export declare const cancelIdleCallback: ((handle: number) => void) & typeof globalThis.cancelIdleCallback;
@@ -1,20 +0,0 @@
1
- export const requestIdleCallback = (typeof self !== 'undefined' &&
2
- self.requestIdleCallback &&
3
- self.requestIdleCallback.bind(window)) ||
4
- function (cb) {
5
- let start = Date.now();
6
- return setTimeout(function () {
7
- cb({
8
- didTimeout: false,
9
- timeRemaining: function () {
10
- return Math.max(0, 50 - (Date.now() - start));
11
- }
12
- });
13
- }, 1);
14
- };
15
- export const cancelIdleCallback = (typeof self !== 'undefined' &&
16
- self.cancelIdleCallback &&
17
- self.cancelIdleCallback.bind(window)) ||
18
- function (id) {
19
- return clearTimeout(id);
20
- };