vike 0.4.147 → 0.4.148-commit-7596dcd

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.
Files changed (74) hide show
  1. package/dist/cjs/node/plugin/plugins/importUserCode/v1-design/getConfigValuesSerialized.js +17 -12
  2. package/dist/cjs/node/plugin/plugins/importUserCode/v1-design/getVikeConfig/configDefinitionsBuiltIn.js +3 -0
  3. package/dist/cjs/node/plugin/plugins/importUserCode/v1-design/getVikeConfig/crawlPlusFiles.js +116 -0
  4. package/dist/cjs/node/plugin/plugins/importUserCode/v1-design/getVikeConfig.js +33 -45
  5. package/dist/cjs/node/prerender/runPrerender.js +85 -84
  6. package/dist/cjs/node/runtime/html/injectAssets/injectAssets__public.js +1 -1
  7. package/dist/cjs/node/runtime/html/renderHtml.js +1 -1
  8. package/dist/cjs/node/runtime/renderPage/createHttpResponseObject/assertNoInfiniteHttpRedirect.js +12 -12
  9. package/dist/cjs/node/runtime/renderPage/createHttpResponseObject.js +3 -3
  10. package/dist/cjs/node/runtime/renderPage/executeOnBeforeRenderHook.js +1 -1
  11. package/dist/cjs/node/runtime/renderPage/executeOnRenderHtmlHook.js +3 -3
  12. package/dist/cjs/node/runtime/renderPage/getHttpResponseBody.js +1 -1
  13. package/dist/cjs/node/runtime/renderPage/renderPageAlreadyRouted.js +21 -18
  14. package/dist/cjs/node/runtime/renderPage.js +73 -49
  15. package/dist/cjs/shared/getPageFiles/parseGlobResults.js +3 -3
  16. package/dist/cjs/shared/hooks/executeHook.js +18 -29
  17. package/dist/cjs/shared/hooks/getHook.js +104 -3
  18. package/dist/cjs/shared/page-configs/helpers/getConfigDefinedAtString.js +1 -1
  19. package/dist/cjs/shared/route/executeGuardHook.js +3 -2
  20. package/dist/cjs/shared/route/executeOnBeforeRouteHook.js +4 -4
  21. package/dist/cjs/shared/route/loadPageRoutes.js +10 -15
  22. package/dist/cjs/shared/route/resolveRedirects.js +8 -5
  23. package/dist/cjs/utils/parseUrl-extras.js +6 -1
  24. package/dist/cjs/utils/parseUrl.js +24 -16
  25. package/dist/cjs/utils/projectInfo.js +1 -1
  26. package/dist/esm/client/client-routing-runtime/createPageContext.d.ts +1 -1
  27. package/dist/esm/client/client-routing-runtime/getPageContextFromHooks.js +20 -10
  28. package/dist/esm/client/client-routing-runtime/onBrowserHistoryNavigation.js +2 -2
  29. package/dist/esm/client/client-routing-runtime/renderPageClientSide.js +18 -12
  30. package/dist/esm/client/shared/executeOnRenderClientHook.js +1 -1
  31. package/dist/esm/client/shared/getPageContextSerializedInHtml.js +1 -1
  32. package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getConfigValuesSerialized.js +17 -12
  33. package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVikeConfig/configDefinitionsBuiltIn.js +3 -0
  34. package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVikeConfig/crawlPlusFiles.d.ts +5 -0
  35. package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVikeConfig/crawlPlusFiles.js +110 -0
  36. package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVikeConfig.js +34 -46
  37. package/dist/esm/node/prerender/runPrerender.js +87 -86
  38. package/dist/esm/node/runtime/html/injectAssets/injectAssets__public.js +1 -1
  39. package/dist/esm/node/runtime/html/renderHtml.js +1 -1
  40. package/dist/esm/node/runtime/renderPage/createHttpResponseObject/assertNoInfiniteHttpRedirect.d.ts +1 -1
  41. package/dist/esm/node/runtime/renderPage/createHttpResponseObject/assertNoInfiniteHttpRedirect.js +13 -13
  42. package/dist/esm/node/runtime/renderPage/createHttpResponseObject.d.ts +1 -1
  43. package/dist/esm/node/runtime/renderPage/createHttpResponseObject.js +3 -3
  44. package/dist/esm/node/runtime/renderPage/executeOnBeforeRenderHook.js +1 -1
  45. package/dist/esm/node/runtime/renderPage/executeOnRenderHtmlHook.d.ts +2 -2
  46. package/dist/esm/node/runtime/renderPage/executeOnRenderHtmlHook.js +3 -3
  47. package/dist/esm/node/runtime/renderPage/getHttpResponseBody.js +1 -1
  48. package/dist/esm/node/runtime/renderPage/renderPageAlreadyRouted.d.ts +7 -7
  49. package/dist/esm/node/runtime/renderPage/renderPageAlreadyRouted.js +22 -19
  50. package/dist/esm/node/runtime/renderPage.js +74 -50
  51. package/dist/esm/shared/getPageFiles/parseGlobResults.js +1 -1
  52. package/dist/esm/shared/hooks/executeHook.d.ts +2 -2
  53. package/dist/esm/shared/hooks/executeHook.js +18 -29
  54. package/dist/esm/shared/hooks/getHook.d.ts +17 -7
  55. package/dist/esm/shared/hooks/getHook.js +103 -3
  56. package/dist/esm/shared/page-configs/Config.d.ts +21 -13
  57. package/dist/esm/shared/page-configs/helpers/getConfigDefinedAtString.d.ts +1 -1
  58. package/dist/esm/shared/page-configs/helpers/getConfigDefinedAtString.js +1 -1
  59. package/dist/esm/shared/route/executeGuardHook.js +4 -3
  60. package/dist/esm/shared/route/executeOnBeforeRouteHook.d.ts +1 -8
  61. package/dist/esm/shared/route/executeOnBeforeRouteHook.js +6 -6
  62. package/dist/esm/shared/route/index.d.ts +2 -2
  63. package/dist/esm/shared/route/loadPageRoutes.d.ts +2 -2
  64. package/dist/esm/shared/route/loadPageRoutes.js +11 -16
  65. package/dist/esm/shared/route/resolveRedirects.js +8 -5
  66. package/dist/esm/utils/parseUrl-extras.d.ts +2 -0
  67. package/dist/esm/utils/parseUrl-extras.js +5 -0
  68. package/dist/esm/utils/parseUrl.js +24 -16
  69. package/dist/esm/utils/projectInfo.d.ts +2 -2
  70. package/dist/esm/utils/projectInfo.js +1 -1
  71. package/package.json +3 -3
  72. /package/dist/cjs/shared/page-configs/serialize/{assertPageConfigs.js → assertPageConfigsSerialized.js} +0 -0
  73. /package/dist/esm/shared/page-configs/serialize/{assertPageConfigs.d.ts → assertPageConfigsSerialized.d.ts} +0 -0
  74. /package/dist/esm/shared/page-configs/serialize/{assertPageConfigs.js → assertPageConfigsSerialized.js} +0 -0
@@ -12,7 +12,7 @@ async function executeOnBeforeRouteHook(pageContext) {
12
12
  const pageContextFromOnBeforeRouteHook = {};
13
13
  if (!pageContext._onBeforeRouteHook)
14
14
  return null;
15
- const pageContextFromHook = await executeHook(pageContext._onBeforeRouteHook, pageContext);
15
+ const pageContextFromHook = await getPageContextFromHook(pageContext._onBeforeRouteHook, pageContext);
16
16
  if (pageContextFromHook) {
17
17
  (0, utils_js_1.objectAssign)(pageContextFromOnBeforeRouteHook, pageContextFromHook);
18
18
  if ((0, utils_js_1.hasProp)(pageContextFromOnBeforeRouteHook, '_pageId', 'string') ||
@@ -37,11 +37,11 @@ async function executeOnBeforeRouteHook(pageContext) {
37
37
  return pageContextFromOnBeforeRouteHook;
38
38
  }
39
39
  exports.executeOnBeforeRouteHook = executeOnBeforeRouteHook;
40
- async function executeHook(onBeforeRouteHook, pageContext) {
41
- let hookReturn = onBeforeRouteHook.onBeforeRoute(pageContext);
40
+ async function getPageContextFromHook(onBeforeRouteHook, pageContext) {
41
+ let hookReturn = onBeforeRouteHook.hookFn(pageContext);
42
42
  (0, resolveRouteFunction_js_1.assertSyncRouting)(hookReturn, `The onBeforeRoute() hook ${onBeforeRouteHook.hookFilePath}`);
43
43
  // TODO/v1-release: make executeOnBeforeRouteHook() and route() sync
44
- hookReturn = await hookReturn;
44
+ hookReturn = await (0, utils_js_1.executeHook)(() => hookReturn, onBeforeRouteHook);
45
45
  const errPrefix = `The onBeforeRoute() hook defined by ${onBeforeRouteHook.hookFilePath}`;
46
46
  (0, utils_js_1.assertUsage)(hookReturn === null ||
47
47
  hookReturn === undefined ||
@@ -7,6 +7,7 @@ const deduceRouteStringFromFilesystemPath_js_1 = require("./deduceRouteStringFro
7
7
  const utils_js_2 = require("../utils.js");
8
8
  const helpers_js_1 = require("../page-configs/helpers.js");
9
9
  const resolveRouteFunction_js_1 = require("./resolveRouteFunction.js");
10
+ const getHook_js_1 = require("../hooks/getHook.js");
10
11
  async function loadPageRoutes(
11
12
  // Remove all arguments and use GlobalContext instead?
12
13
  pageFilesAll, pageConfigs, pageConfigGlobal, allPageIds) {
@@ -139,20 +140,8 @@ function getPageRoutes(filesystemRoots, pageFilesAll, pageConfigs, allPageIds) {
139
140
  function getGlobalHooks(pageFilesAll, pageConfigs, pageConfigGlobal) {
140
141
  // V1 Design
141
142
  if (pageConfigs.length > 0) {
142
- const hookName = 'onBeforeRoute';
143
- if (pageConfigGlobal.configValues[hookName]?.value) {
144
- const configValue = pageConfigGlobal.configValues[hookName];
145
- const { value: hookFn } = configValue;
146
- const hookFilePath = (0, helpers_js_1.getHookFilePathToShowToUser)(configValue);
147
- const hookDefinedAt = (0, helpers_js_1.getConfigDefinedAtString)('Hook', hookName, configValue);
148
- (0, utils_js_1.assertUsage)((0, utils_js_2.isCallable)(hookFn), `${hookDefinedAt} should be a function.`);
149
- const onBeforeRouteHook = {
150
- hookFilePath: hookFilePath,
151
- onBeforeRoute: hookFn
152
- };
153
- return { onBeforeRouteHook, filesystemRoots: null };
154
- }
155
- return { onBeforeRouteHook: null, filesystemRoots: null };
143
+ const hook = (0, getHook_js_1.getHookFromPageConfigGlobal)(pageConfigGlobal, 'onBeforeRoute');
144
+ return { onBeforeRouteHook: hook, filesystemRoots: null };
156
145
  }
157
146
  // Old design
158
147
  // TODO/v1-release: remove
@@ -165,7 +154,13 @@ function getGlobalHooks(pageFilesAll, pageConfigs, pageConfigGlobal) {
165
154
  if ('onBeforeRoute' in fileExports) {
166
155
  (0, utils_js_1.assertUsage)((0, utils_js_1.hasProp)(fileExports, 'onBeforeRoute', 'function'), `\`export { onBeforeRoute }\` of ${filePath} should be a function.`);
167
156
  const { onBeforeRoute } = fileExports;
168
- onBeforeRouteHook = { hookFilePath: `${filePath} > \`export { onBeforeRoute }\``, onBeforeRoute };
157
+ const hookName = 'onBeforeRoute';
158
+ onBeforeRouteHook = {
159
+ hookFilePath: filePath,
160
+ hookFn: onBeforeRoute,
161
+ hookName,
162
+ hookTimeout: (0, getHook_js_1.getHookTimeoutDefault)(hookName)
163
+ };
169
164
  }
170
165
  if ('filesystemRoutingRoot' in fileExports) {
171
166
  (0, utils_js_1.assertUsage)((0, utils_js_1.hasProp)(fileExports, 'filesystemRoutingRoot', 'string'), `\`export { filesystemRoutingRoot }\` of ${filePath} should be a string.`);
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.resolveRouteStringRedirect = exports.resolveRedirects = void 0;
7
7
  const assertIsNotBrowser_js_1 = require("../../utils/assertIsNotBrowser.js");
8
+ const parseUrl_extras_js_1 = require("../../utils/parseUrl-extras.js");
8
9
  const utils_js_1 = require("../utils.js");
9
10
  const resolveRouteString_js_1 = require("./resolveRouteString.js");
10
11
  const picocolors_1 = __importDefault(require("@brillout/picocolors"));
@@ -23,9 +24,9 @@ exports.resolveRedirects = resolveRedirects;
23
24
  function resolveRouteStringRedirect(urlSource, urlTarget, urlPathname) {
24
25
  (0, resolveRouteString_js_1.assertRouteString)(urlSource, `${configSrc} Invalid`);
25
26
  (0, utils_js_1.assertUsage)(urlTarget.startsWith('/') ||
26
- urlTarget.startsWith('http://') ||
27
- urlTarget.startsWith('https://') ||
28
- urlTarget === '*', `${configSrc} Invalid redirection target URL ${picocolors_1.default.cyan(urlTarget)}: the target URL should start with ${picocolors_1.default.cyan('/')}, ${picocolors_1.default.cyan('http://')}, ${picocolors_1.default.cyan('https://')}, or be ${picocolors_1.default.cyan('*')}`);
27
+ // Is allowing any protocol a safety issue? https://github.com/vikejs/vike/pull/1292#issuecomment-1828043917
28
+ (0, parseUrl_extras_js_1.isUriWithProtocol)(urlTarget) ||
29
+ urlTarget === '*', `${configSrc} Invalid redirection target URL ${picocolors_1.default.cyan(urlTarget)}: the target URL should start with ${picocolors_1.default.cyan('/')}, a valid protocol (${picocolors_1.default.cyan('https:')}, ${picocolors_1.default.cyan('http:')}, ${picocolors_1.default.cyan('ipfs:')}, ${picocolors_1.default.cyan('magnet:')}, ...), or be ${picocolors_1.default.cyan('*')}`);
29
30
  assertParams(urlSource, urlTarget);
30
31
  const match = (0, resolveRouteString_js_1.resolveRouteString)(urlSource, urlPathname);
31
32
  if (!match)
@@ -37,10 +38,12 @@ function resolveRouteStringRedirect(urlSource, urlTarget, urlPathname) {
37
38
  }
38
39
  urlResolved = urlResolved.replaceAll(key, val);
39
40
  });
40
- (0, utils_js_1.assert)(!urlResolved.includes('@'));
41
+ if (!urlResolved.startsWith('mailto:')) {
42
+ (0, utils_js_1.assertUsage)(!urlResolved.includes('@'), 'URL should not contain "@" unless it is a mailto link.');
43
+ }
41
44
  if (urlResolved === urlPathname)
42
45
  return null;
43
- (0, utils_js_1.assert)(urlTarget.startsWith('/') || urlTarget.startsWith('http'));
46
+ (0, utils_js_1.assert)(urlResolved.startsWith('/') || (0, parseUrl_extras_js_1.isUriWithProtocol)(urlResolved));
44
47
  return urlResolved;
45
48
  }
46
49
  exports.resolveRouteStringRedirect = resolveRouteStringRedirect;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.addUrlOrigin = exports.removeUrlOrigin = exports.modifyUrlPathname = exports.removeBaseServer = exports.normalizeUrlPathname = exports.isBaseAssets = exports.prependBase = void 0;
3
+ exports.isUriWithProtocol = exports.addUrlOrigin = exports.removeUrlOrigin = exports.modifyUrlPathname = exports.removeBaseServer = exports.normalizeUrlPathname = exports.isBaseAssets = exports.prependBase = void 0;
4
4
  const parseUrl_js_1 = require("./parseUrl.js");
5
5
  const assert_js_1 = require("./assert.js");
6
6
  const slice_js_1 = require("./slice.js");
@@ -103,3 +103,8 @@ function addUrlOrigin(url, origin) {
103
103
  return urlModified;
104
104
  }
105
105
  exports.addUrlOrigin = addUrlOrigin;
106
+ function isUriWithProtocol(uri) {
107
+ // https://en.wikipedia.org/wiki/List_of_URI_schemes
108
+ return /^[a-z0-9][a-z0-9\.\+\-]*:/i.test(uri);
109
+ }
110
+ exports.isUriWithProtocol = isUriWithProtocol;
@@ -55,7 +55,7 @@ function parseUrl(url, baseServer) {
55
55
  searchAll[key] = [...(searchAll.hasOwnProperty(key) ? searchAll[key] : []), val];
56
56
  });
57
57
  // Origin + pathname
58
- const { origin, pathname: pathnameResolved } = parsePathname(urlWithoutHashNorSearch, baseServer);
58
+ const { origin, pathname: pathnameResolved } = getPathname(urlWithoutHashNorSearch, baseServer);
59
59
  (0, assert_js_1.assert)(origin === null || origin === decodeSafe(origin)); // AFAICT decoding the origin is useless
60
60
  (0, assert_js_1.assert)(pathnameResolved.startsWith('/'));
61
61
  (0, assert_js_1.assert)(origin === null || url.startsWith(origin));
@@ -91,35 +91,43 @@ function decodeSafe(urlComponent) {
91
91
  return urlComponent;
92
92
  }
93
93
  function decodePathname(urlPathname) {
94
+ urlPathname = urlPathname.replace(/\s+$/, '');
94
95
  urlPathname = urlPathname
95
96
  .split('/')
96
97
  .map((dir) => decodeSafe(dir).split('/').join('%2F'))
97
98
  .join('/');
98
- urlPathname = urlPathname.replace(/\s/g, '');
99
99
  return urlPathname;
100
100
  }
101
- function parsePathname(urlWithoutHashNorSearch, baseServer) {
101
+ function getPathname(url, baseServer) {
102
+ // Search and hash already extracted
103
+ (0, assert_js_1.assert)(!url.includes('?') && !url.includes('#'));
104
+ // url has origin
102
105
  {
103
- const { origin, pathname } = parseOrigin(urlWithoutHashNorSearch);
106
+ const { origin, pathname } = parseOrigin(url);
104
107
  if (origin) {
105
108
  return { origin, pathname };
106
109
  }
107
- (0, assert_js_1.assert)(pathname === urlWithoutHashNorSearch);
110
+ (0, assert_js_1.assert)(pathname === url);
108
111
  }
109
- if (urlWithoutHashNorSearch.startsWith('/')) {
110
- return { origin: null, pathname: urlWithoutHashNorSearch };
112
+ // url doesn't have origin
113
+ if (url.startsWith('/')) {
114
+ return { origin: null, pathname: url };
111
115
  }
112
116
  else {
113
- // In the browser, this is the Base URL of the current URL
117
+ // url is a relative path
118
+ // In the browser, this is the Base URL of the current URL.
114
119
  // Safe access `window?.document?.baseURI` for users who shim `window` in Node.js
115
- let baseURI = typeof window !== 'undefined' && window?.document?.baseURI;
116
- if (baseURI)
117
- baseURI = parseOrigin(baseURI).pathname;
118
- const base = baseURI || baseServer;
119
- const pathname = resolveUrlPathnameRelative(urlWithoutHashNorSearch, base);
120
- // We need to parse the origin in case `base === window.document.baseURI`
121
- const parsed = parseOrigin(pathname);
122
- return parsed;
120
+ const baseURI = typeof window !== 'undefined' ? window?.document?.baseURI : undefined;
121
+ let base;
122
+ if (baseURI) {
123
+ const baseURIPathaname = parseOrigin(baseURI.split('?')[0]).pathname;
124
+ base = baseURIPathaname;
125
+ }
126
+ else {
127
+ base = baseServer;
128
+ }
129
+ const pathname = resolveUrlPathnameRelative(url, base);
130
+ return { origin: null, pathname };
123
131
  }
124
132
  }
125
133
  function parseOrigin(url) {
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PROJECT_VERSION = exports.projectInfo = void 0;
4
4
  const assertSingleInstance_js_1 = require("./assertSingleInstance.js");
5
- const PROJECT_VERSION = '0.4.147';
5
+ const PROJECT_VERSION = '0.4.148-commit-7596dcd';
6
6
  exports.PROJECT_VERSION = PROJECT_VERSION;
7
7
  const projectInfo = {
8
8
  projectName: 'Vike',
@@ -11,7 +11,7 @@ declare function createPageContext(urlOriginal: string): Promise<{
11
11
  _pageConfigGlobal: import("../../shared/page-configs/PageConfig.js").PageConfigGlobalRuntime;
12
12
  _allPageIds: string[];
13
13
  _pageRoutes: import("../../shared/route/loadPageRoutes.js").PageRoutes;
14
- _onBeforeRouteHook: import("../../shared/route/executeOnBeforeRouteHook.js").OnBeforeRouteHook | null;
14
+ _onBeforeRouteHook: import("../../shared/hooks/getHook.js").Hook | null;
15
15
  } & import("../../shared/addUrlComputedProps.js").PageContextUrlComputedPropsClient & {
16
16
  _urlRewrite: string | null;
17
17
  }>;
@@ -27,7 +27,7 @@ async function getPageContextFromHooks_firstRender(pageContext) {
27
27
  objectAssign(pageContextFromHooks, await loadPageFilesClientSide(pageContextFromHooks._pageId, pageContext));
28
28
  {
29
29
  const pageContextForHook = { ...pageContext, ...pageContextFromHooks };
30
- if (await onBeforeRenderClientOnlyExists(pageContextForHook)) {
30
+ if (onBeforeRenderClientOnlyExists(pageContextForHook)) {
31
31
  const pageContextFromHook = await executeOnBeforeRenderHookClientSide(pageContextForHook);
32
32
  objectAssign(pageContextFromHooks, pageContextFromHook);
33
33
  }
@@ -57,13 +57,17 @@ async function getPageContextFromHooks_uponNavigation(pageContext) {
57
57
  }
58
58
  async function getPageContextAlreadyRouted(pageContext, isErrorPage) {
59
59
  let pageContextFromHooks = {};
60
+ objectAssign(pageContextFromHooks, { _hasPageContextFromClient: false });
60
61
  objectAssign(pageContextFromHooks, await loadPageFilesClientSide(pageContext._pageId, pageContext));
62
+ let pageContextFetchedFromServer = false;
61
63
  // Needs to be called before any client-side hook, because it may contain pageContextInit.user which is needed for guard() and onBeforeRender()
62
64
  if (
63
65
  // For the error page, we cannot fetch pageContext from the server because the pageContext JSON request is based on the URL
64
66
  !isErrorPage &&
67
+ // true if pageContextInit has some client data or the onBeforeRender hook is server-side only:
65
68
  (await hasPageContextServer({ ...pageContext, ...pageContextFromHooks }))) {
66
69
  const pageContextFromServer = await fetchPageContextFromServer(pageContext);
70
+ pageContextFetchedFromServer = true;
67
71
  if (!pageContextFromServer['_isError']) {
68
72
  objectAssign(pageContextFromHooks, pageContextFromServer);
69
73
  }
@@ -72,6 +76,7 @@ async function getPageContextAlreadyRouted(pageContext, isErrorPage) {
72
76
  assert(errorPageId);
73
77
  pageContextFromHooks = {};
74
78
  objectAssign(pageContextFromHooks, {
79
+ _hasPageContextFromClient: false,
75
80
  isHydration: false,
76
81
  _pageId: errorPageId
77
82
  });
@@ -91,25 +96,29 @@ async function getPageContextAlreadyRouted(pageContext, isErrorPage) {
91
96
  if (!isErrorPage) {
92
97
  // Should we really call the guard() hook on the client-side? Shouldn't we make the guard() hook a server-side only hook? Or maybe make its env configurable like onBeforeRender()?
93
98
  await executeGuardHook({
94
- _hasPageContextFromClient: false,
95
99
  ...pageContext,
96
100
  ...pageContextFromHooks
97
101
  }, (pageContext) => preparePageContextForUserConsumptionClientSide(pageContext, true));
98
102
  }
99
103
  }
104
+ // For the error page, we also execute the client-side onBeforeRender() hook, but maybe we shouldn't? The server-side does it as well (but maybe it shouldn't).
100
105
  {
101
- // For the error page, we also execute the client-side onBeforeRender() hook, but maybe we shouldn't? The server-side does it as well (but maybe it shouldn't).
102
- const pageContextFromOnBeforeRender = await executeOnBeforeRenderHookClientSide({
103
- ...pageContext,
104
- ...pageContextFromHooks
105
- });
106
- objectAssign(pageContextFromHooks, pageContextFromOnBeforeRender);
106
+ const pageContextForHook = { ...pageContext, ...pageContextFromHooks };
107
+ if (onBeforeRenderClientOnlyExists(pageContextForHook) || !pageContextFetchedFromServer) {
108
+ // This won't do anything if no hook has been defined or if the hook's env.client is false.
109
+ const pageContextFromHook = await executeOnBeforeRenderHookClientSide(pageContextForHook);
110
+ objectAssign(pageContextFromHooks, pageContextFromHook);
111
+ }
112
+ else {
113
+ assert(pageContextFetchedFromServer);
114
+ }
107
115
  }
108
116
  return pageContextFromHooks;
109
117
  }
110
118
  async function executeOnBeforeRenderHookClientSide(pageContext) {
111
119
  const hook = getHook(pageContext, 'onBeforeRender');
112
120
  if (!hook) {
121
+ // No hook defined or hook's env.client is false
113
122
  const pageContextFromOnBeforeRender = {
114
123
  _hasPageContextFromClient: false
115
124
  };
@@ -123,7 +132,7 @@ async function executeOnBeforeRenderHookClientSide(pageContext) {
123
132
  ...pageContext,
124
133
  ...pageContextFromOnBeforeRender
125
134
  }, true);
126
- const hookResult = await executeHook(() => onBeforeRender(pageContextForUserConsumption), 'onBeforeRender', hook.hookFilePath);
135
+ const hookResult = await executeHook(() => onBeforeRender(pageContextForUserConsumption), hook);
127
136
  assertOnBeforeRenderHookReturn(hookResult, hook.hookFilePath);
128
137
  const pageContextFromHook = hookResult?.pageContext;
129
138
  objectAssign(pageContextFromOnBeforeRender, pageContextFromHook);
@@ -164,7 +173,7 @@ async function onBeforeRenderServerOnlyExists(pageContext) {
164
173
  return hasOnBeforeRenderServerSideOnlyHook;
165
174
  }
166
175
  }
167
- async function onBeforeRenderClientOnlyExists(pageContext) {
176
+ function onBeforeRenderClientOnlyExists(pageContext) {
168
177
  if (pageContext._pageConfigs.length > 0) {
169
178
  // V1
170
179
  const pageConfig = getPageConfig(pageContext._pageId, pageContext._pageConfigs);
@@ -174,6 +183,7 @@ async function onBeforeRenderClientOnlyExists(pageContext) {
174
183
  }
175
184
  else {
176
185
  // TODO/v1-release: remove
186
+ // Client-only onBeforeRender() hooks were never supported for the V0.4 design
177
187
  return false;
178
188
  }
179
189
  }
@@ -14,8 +14,8 @@ function onBrowserHistoryNavigation() {
14
14
  // - By user clicking on a hash link `<a href="#some-hash" />`
15
15
  // - The popstate event is *only* triggered if `href` starts with '#' (even if `href` is '/#some-hash' while the current URL's pathname is '/' then the popstate still isn't triggered)
16
16
  // - By JavaScript: `location.hash = 'some-hash'`
17
- // - The `event` of `window.addEventListener('popstate', (event) => /*...*/)` is useless: the History API doesn't provide the previous state (the popped state), see https://stackoverflow.com/questions/48055323/is-history-state-always-the-same-as-popstate-event-state
18
- window.addEventListener('popstate', () => {
17
+ // - The `event` argument of `window.addEventListener('popstate', (event) => /*...*/)` is useless: the History API doesn't provide the previous state (the popped state), see https://stackoverflow.com/questions/48055323/is-history-state-always-the-same-as-popstate-event-state
18
+ window.addEventListener('popstate', (ev) => {
19
19
  const currentState = getState();
20
20
  const scrollTarget = currentState.historyState.scrollPosition || 'scroll-to-top-or-hash';
21
21
  const isUserLandPushStateNavigation = currentState.historyState.triggedBy === 'user';
@@ -7,7 +7,7 @@ import { createPageContext } from './createPageContext.js';
7
7
  import { addLinkPrefetchHandlers } from './prefetch.js';
8
8
  import { assertInfo, assertWarning, isReact } from './utils.js';
9
9
  import { executeOnRenderClientHook } from '../shared/executeOnRenderClientHook.js';
10
- import { assertHook } from '../../shared/hooks/getHook.js';
10
+ import { assertHook, getHook } from '../../shared/hooks/getHook.js';
11
11
  import { isErrorFetchingStaticAssets } from '../shared/loadPageFilesClientSide.js';
12
12
  import { pushHistory } from './history.js';
13
13
  import { assertNoInfiniteAbortLoop, getPageContextFromAllRewrites, isAbortError, logAbortErrorHandled } from '../../shared/route/abort.js';
@@ -85,7 +85,11 @@ async function renderPageClientSide(renderArgs) {
85
85
  const callTransitionHooks = !isFirstRender;
86
86
  if (callTransitionHooks) {
87
87
  if (!globalObject.isTransitioning) {
88
- await globalObject.onPageTransitionStart?.(pageContext);
88
+ if (globalObject.onPageTransitionStart) {
89
+ const hook = globalObject.onPageTransitionStart;
90
+ const { hookFn } = hook;
91
+ await executeHook(() => hookFn(pageContext), hook);
92
+ }
89
93
  globalObject.isTransitioning = true;
90
94
  if (abortRender())
91
95
  return;
@@ -207,13 +211,14 @@ async function renderPageClientSide(renderArgs) {
207
211
  objectAssign(pageContext, pageContextFromHooks);
208
212
  // Set global onPageTransitionStart()
209
213
  assertHook(pageContext, 'onPageTransitionStart');
210
- globalObject.onPageTransitionStart = pageContext.exports.onPageTransitionStart;
214
+ const onPageTransitionStartHook = getHook(pageContext, 'onPageTransitionStart');
215
+ globalObject.onPageTransitionStart = onPageTransitionStartHook;
211
216
  // Set global hydrationCanBeAborted
212
217
  if (pageContext.exports.hydrationCanBeAborted) {
213
218
  setHydrationCanBeAborted();
214
219
  }
215
220
  else {
216
- assertWarning(!isReact(), 'You seem to be using React; we recommend setting hydrationCanBeAborted to true, see https://vike.dev/clientRouting', { onlyOnce: true });
221
+ assertWarning(!isReact(), 'You seem to be using React; we recommend setting hydrationCanBeAborted to true, see https://vike.dev/hydrationCanBeAborted', { onlyOnce: true });
217
222
  }
218
223
  // There wasn't any `await` but result may change because we just called setHydrationCanBeAborted()
219
224
  if (abortRender())
@@ -242,11 +247,10 @@ async function renderPageClientSide(renderArgs) {
242
247
  // onHydrationEnd()
243
248
  if (isFirstRender) {
244
249
  assertHook(pageContext, 'onHydrationEnd');
245
- const { onHydrationEnd } = pageContext.exports;
246
- if (onHydrationEnd) {
247
- const hookFilePath = pageContext.exportsAll.onHydrationEnd[0].exportSource;
248
- assert(hookFilePath);
249
- await executeHook(() => onHydrationEnd(pageContext), 'onHydrationEnd', hookFilePath);
250
+ const hook = getHook(pageContext, 'onHydrationEnd');
251
+ if (hook) {
252
+ const { hookFn } = hook;
253
+ await executeHook(() => hookFn(pageContext), hook);
250
254
  if (abortRender(true))
251
255
  return;
252
256
  }
@@ -256,9 +260,11 @@ async function renderPageClientSide(renderArgs) {
256
260
  return;
257
261
  // onPageTransitionEnd()
258
262
  if (callTransitionHooks) {
259
- if (pageContext.exports.onPageTransitionEnd) {
260
- assertHook(pageContext, 'onPageTransitionEnd');
261
- await pageContext.exports.onPageTransitionEnd(pageContext);
263
+ assertHook(pageContext, 'onPageTransitionEnd');
264
+ const hook = getHook(pageContext, 'onPageTransitionEnd');
265
+ if (hook) {
266
+ const { hookFn } = hook;
267
+ await executeHook(() => hookFn(pageContext), hook);
262
268
  if (abortRender(true))
263
269
  return;
264
270
  }
@@ -45,7 +45,7 @@ async function executeOnRenderClientHook(pageContext, isClientRouting) {
45
45
  const renderHook = hook.hookFn;
46
46
  assert(hookName);
47
47
  // We don't use a try-catch wrapper because rendering errors are usually handled by the UI framework. (E.g. React's Error Boundaries.)
48
- const hookResult = await executeHook(() => renderHook(pageContextForUserConsumption), hookName, hook.hookFilePath);
48
+ const hookResult = await executeHook(() => renderHook(pageContextForUserConsumption), hook);
49
49
  assertUsage(hookResult === undefined, `The ${hookName}() hook defined by ${hook.hookFilePath} isn't allowed to return a value`);
50
50
  }
51
51
  function getUrlToShowToUser(pageContext) {
@@ -4,7 +4,7 @@ export { getPageContextSerializedInHtml };
4
4
  function getPageContextSerializedInHtml() {
5
5
  const id = 'vike_pageContext';
6
6
  const elem = document.getElementById(id);
7
- assertUsage(elem, `The element #${id} (which vike automatically injects into the HTML) is missing from the DOM. This may happen if your HTML is malformed. Make sure your HTML isn't malformed, and make sure you don't remove #${id} from the HTML nor from the DOM.`);
7
+ assertUsage(elem, `The element #${id} (which Vike automatically injects into the HTML) is missing from the DOM. This may happen if your HTML is malformed. Make sure your HTML isn't malformed, and make sure you don't remove #${id} from the HTML nor from the DOM.`);
8
8
  const pageContextJson = elem.textContent;
9
9
  assert(pageContextJson);
10
10
  const pageContextSerializedInHtml = parse(pageContextJson);
@@ -1,7 +1,7 @@
1
1
  export { getConfigValuesSerialized };
2
2
  export { assertConfigValueIsSerializable };
3
3
  import { assert, assertUsage, getPropAccessNotation } from '../../../utils.js';
4
- import { isJsonSerializerError, stringify } from '@brillout/json-serializer/stringify';
4
+ import { stringify } from '@brillout/json-serializer/stringify';
5
5
  import pc from '@brillout/picocolors';
6
6
  import { getConfigValueFilePathToShowToUser } from '../../../../../shared/page-configs/helpers.js';
7
7
  import { serializeConfigValue } from '../../../../../shared/page-configs/serialize/serializeConfigValue.js';
@@ -47,22 +47,27 @@ function getConfigValueSerialized(value, configName, definedAt) {
47
47
  configValueSerialized = stringify(value, { valueName, forbidReactElements: true });
48
48
  }
49
49
  catch (err) {
50
- let serializationErrMsg = '';
50
+ /*
51
+ let serializationErrMsg = ''
51
52
  if (isJsonSerializerError(err)) {
52
- serializationErrMsg = err.messageCore;
53
- }
54
- else {
55
- // When a property getter throws an error
56
- console.error('Serialization error:');
57
- console.error(err);
58
- serializationErrMsg = 'see serialization error printed above';
53
+ serializationErrMsg = err.messageCore
54
+ } else {
55
+ // When a property getter throws an error
56
+ console.error('Serialization error:')
57
+ console.error(err)
58
+ serializationErrMsg = 'see serialization error printed above'
59
59
  }
60
+ */
60
61
  const configValueFilePathToShowToUser = getConfigValueFilePathToShowToUser({ definedAt });
61
62
  assert(configValueFilePathToShowToUser);
62
63
  assertUsage(false, [
63
- `The value of the config ${pc.cyan(configName)} cannot be defined inside the file ${configValueFilePathToShowToUser}:`,
64
- `its value must be defined in an another file and then imported by ${configValueFilePathToShowToUser}. (Because its value isn't serializable: ${serializationErrMsg}.)`,
65
- `Only serializable config values can be defined inside +config.h.js files, see https://vike.dev/header-file.`
64
+ `The code of ${pc.cyan(configName)} cannot live inside ${configValueFilePathToShowToUser},`,
65
+ 'see https://vike.dev/header-file#runtime-code'
66
+ /* I guess showing this is more confusing than adding value.
67
+ `(technically speaking: the value of ${pc.cyan(
68
+ configName
69
+ )} isn't serializable (${serializationErrMsg}) and it's therefore runtime code that needs to be imported).`
70
+ //*/
66
71
  ].join(' '));
67
72
  }
68
73
  configValueSerialized = JSON.stringify(configValueSerialized);
@@ -75,6 +75,9 @@ const configDefinitionsBuiltIn = {
75
75
  onBeforeRenderEnv: {
76
76
  env: { client: true },
77
77
  _computed: (configValueSources) => !isConfigSet(configValueSources, 'onBeforeRender') ? null : getConfigEnv(configValueSources, 'onBeforeRender')
78
+ },
79
+ hooksTimeout: {
80
+ env: { server: true, client: true }
78
81
  }
79
82
  };
80
83
  const configDefinitionsBuiltInGlobal = {
@@ -0,0 +1,5 @@
1
+ export { crawlPlusFiles };
2
+ declare function crawlPlusFiles(userRootDir: string, outDirAbsoluteFilesystem: string, isDev: boolean): Promise<{
3
+ filePathRelativeToUserRootDir: string;
4
+ filePathAbsoluteFilesystem: string;
5
+ }[]>;
@@ -0,0 +1,110 @@
1
+ export { crawlPlusFiles };
2
+ import { assertPosixPath, assert, toPosixPath, assertWarning, scriptFileExtensionList, scriptFileExtensions } from '../../../../utils.js';
3
+ import path from 'path';
4
+ import glob from 'fast-glob';
5
+ import { exec } from 'child_process';
6
+ import { promisify } from 'util';
7
+ const execA = promisify(exec);
8
+ async function crawlPlusFiles(userRootDir, outDirAbsoluteFilesystem, isDev) {
9
+ assertPosixPath(userRootDir);
10
+ assertPosixPath(outDirAbsoluteFilesystem);
11
+ assert(outDirAbsoluteFilesystem.startsWith(userRootDir));
12
+ const outDir = path.posix.relative(userRootDir, outDirAbsoluteFilesystem);
13
+ assert(!outDir.startsWith('.'));
14
+ const timeBase = new Date().getTime();
15
+ let files = [];
16
+ const res = await gitLsFiles(userRootDir, outDir);
17
+ if (res &&
18
+ // Fallback to fast-glob for users that dynamically generate plus files (we assume generetad plus files to be skipped because they are usually included in .gitignore)
19
+ res.length > 0) {
20
+ files = res;
21
+ }
22
+ else {
23
+ files = await fastGlob(userRootDir, outDir);
24
+ }
25
+ {
26
+ const time = new Date().getTime() - timeBase;
27
+ if (isDev) {
28
+ // We only warn in dev, because while building it's expected to take a long time as fast-glob is competing for resources with other tasks
29
+ assertWarning(time < 2 * 1000, `Crawling your user files took an unexpected long time (${time}ms). Create a new issue on Vike's GitHub.`, {
30
+ onlyOnce: 'slow-page-files-search'
31
+ });
32
+ }
33
+ }
34
+ const plusFiles = files.map((p) => {
35
+ p = toPosixPath(p);
36
+ assert(!p.startsWith(userRootDir));
37
+ const filePathRelativeToUserRootDir = path.posix.join('/', p);
38
+ const filePathAbsoluteFilesystem = path.posix.join(userRootDir, p);
39
+ return {
40
+ filePathRelativeToUserRootDir,
41
+ filePathAbsoluteFilesystem
42
+ };
43
+ });
44
+ return plusFiles;
45
+ }
46
+ // Same as fastGlob() but using `$ git ls-files`
47
+ async function gitLsFiles(userRootDir, outDir) {
48
+ // Test if Git is installed
49
+ {
50
+ let stdout;
51
+ try {
52
+ const res = await execA('git --version', { cwd: userRootDir });
53
+ stdout = res.stdout;
54
+ }
55
+ catch {
56
+ return null;
57
+ }
58
+ assert(stdout.startsWith('git version '));
59
+ }
60
+ const cmd = [
61
+ 'git ls-files',
62
+ ...scriptFileExtensionList.map((ext) => `"**/+*.${ext}"`),
63
+ ...getIgnorePatterns(outDir).map((pattern) => `--exclude="${pattern}"`),
64
+ // --others lists untracked files only (but using .gitignore because --exclude-standard)
65
+ // --cached adds the tracked files to the output
66
+ '--others --cached --exclude-standard'
67
+ ].join(' ');
68
+ let stdout;
69
+ try {
70
+ const res = await execA(cmd, { cwd: userRootDir });
71
+ stdout = res.stdout;
72
+ }
73
+ catch (err) {
74
+ if (err.message.includes('not a git repository'))
75
+ return null;
76
+ throw err;
77
+ }
78
+ let files = stdout.split('\n').filter(Boolean);
79
+ assert(!outDir.startsWith('/'));
80
+ files = files.filter(
81
+ // We have to repeat the same exclusion logic here because the `git ls-files` option --exclude only applies to untracked files. (We use --exclude only to speed up the command.)
82
+ (file) => getIgnoreFilter(file, outDir));
83
+ return files;
84
+ }
85
+ // Same as gitLsFiles() but using fast-glob
86
+ async function fastGlob(userRootDir, outDir) {
87
+ const files = await glob(`**/+*.${scriptFileExtensions}`, {
88
+ ignore: getIgnorePatterns(outDir),
89
+ cwd: userRootDir,
90
+ dot: false
91
+ });
92
+ return files;
93
+ }
94
+ // Same as getIgnoreFilter() but as glob pattern
95
+ function getIgnorePatterns(outDir) {
96
+ return [
97
+ '**/node_modules/**',
98
+ `${outDir}/**`,
99
+ // Allow:
100
+ // ```
101
+ // +Page.js
102
+ // +Page.telefunc.js
103
+ // ```
104
+ '**/*.telefunc.*'
105
+ ];
106
+ }
107
+ // Same as getIgnorePatterns() but for Array.filter()
108
+ function getIgnoreFilter(file, outDir) {
109
+ return !file.includes('node_modules/') && !file.includes('.telefunc.') && !file.startsWith(`${outDir}/`);
110
+ }