vike 0.4.180-commit-648cd01 → 0.4.181-commit-ee50efa

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 (49) hide show
  1. package/dist/cjs/node/plugin/plugins/envVars.js +7 -12
  2. package/dist/cjs/node/runtime/renderPage/createHttpResponseObject/assertNoInfiniteHttpRedirect.js +1 -2
  3. package/dist/cjs/{shared/route → node/runtime/renderPage}/resolveRedirects.js +9 -11
  4. package/dist/cjs/node/runtime/renderPage.js +6 -9
  5. package/dist/cjs/shared/getPageContextUrlComputed.js +43 -37
  6. package/dist/cjs/shared/modifyUrl.js +31 -0
  7. package/dist/cjs/shared/route/abort.js +4 -10
  8. package/dist/cjs/shared/route/executeOnBeforeRouteHook.js +3 -1
  9. package/dist/cjs/utils/parseUrl.js +168 -87
  10. package/dist/cjs/utils/projectInfo.js +1 -1
  11. package/dist/cjs/utils/redirectHard.js +7 -0
  12. package/dist/cjs/utils/urlToFile.js +1 -1
  13. package/dist/esm/client/client-routing-runtime/createPageContext.d.ts +1 -1
  14. package/dist/esm/client/client-routing-runtime/getPageContextFromHooks.js +2 -2
  15. package/dist/esm/client/client-routing-runtime/navigate.js +2 -1
  16. package/dist/esm/client/client-routing-runtime/prefetch.js +5 -4
  17. package/dist/esm/client/client-routing-runtime/renderPageClientSide.js +23 -21
  18. package/dist/esm/client/client-routing-runtime/skipLink.js +11 -22
  19. package/dist/esm/client/client-routing-runtime/utils.d.ts +1 -2
  20. package/dist/esm/client/client-routing-runtime/utils.js +1 -2
  21. package/dist/esm/node/plugin/plugins/envVars.d.ts +0 -2
  22. package/dist/esm/node/plugin/plugins/envVars.js +6 -12
  23. package/dist/esm/node/runtime/renderPage/createHttpResponseObject/assertNoInfiniteHttpRedirect.js +2 -3
  24. package/dist/esm/node/runtime/renderPage/renderPageAlreadyRouted.d.ts +5 -5
  25. package/dist/esm/{shared/route → node/runtime/renderPage}/resolveRedirects.js +9 -11
  26. package/dist/esm/node/runtime/renderPage.d.ts +2 -2
  27. package/dist/esm/node/runtime/renderPage.js +7 -10
  28. package/dist/esm/shared/getPageContextUrlComputed.d.ts +2 -24
  29. package/dist/esm/shared/getPageContextUrlComputed.js +43 -37
  30. package/dist/esm/shared/modifyUrl.d.ts +14 -0
  31. package/dist/esm/shared/modifyUrl.js +28 -0
  32. package/dist/esm/shared/route/abort.js +5 -11
  33. package/dist/esm/shared/route/executeOnBeforeRouteHook.js +4 -2
  34. package/dist/esm/types/index.d.ts +1 -1
  35. package/dist/esm/utils/parseUrl.d.ts +43 -10
  36. package/dist/esm/utils/parseUrl.js +167 -86
  37. package/dist/esm/utils/projectInfo.d.ts +2 -2
  38. package/dist/esm/utils/projectInfo.js +1 -1
  39. package/dist/esm/utils/redirectHard.d.ts +1 -0
  40. package/dist/esm/utils/redirectHard.js +3 -0
  41. package/dist/esm/utils/urlToFile.js +1 -1
  42. package/package.json +17 -2
  43. package/dist/cjs/utils/isExternalLink.js +0 -7
  44. package/dist/cjs/utils/serverSideRouteTo.js +0 -7
  45. package/dist/esm/utils/isExternalLink.d.ts +0 -2
  46. package/dist/esm/utils/isExternalLink.js +0 -4
  47. package/dist/esm/utils/serverSideRouteTo.d.ts +0 -2
  48. package/dist/esm/utils/serverSideRouteTo.js +0 -4
  49. /package/dist/esm/{shared/route → node/runtime/renderPage}/resolveRedirects.d.ts +0 -0
@@ -21,11 +21,10 @@ export * from '../../utils/objectAssign.js';
21
21
  export * from '../../utils/parseUrl.js';
22
22
  export * from '../../utils/projectInfo.js';
23
23
  export * from '../../utils/PromiseType.js';
24
- export * from '../../utils/serverSideRouteTo.js';
24
+ export * from '../../utils/redirectHard.js';
25
25
  export * from '../../utils/sleep.js';
26
26
  export * from '../../utils/slice.js';
27
27
  export * from '../../utils/throttle.js';
28
28
  export * from '../../utils/assertRoutingType.js';
29
29
  export * from '../../utils/onPageVisibilityChange.js';
30
- export * from '../../utils/isExternalLink.js';
31
30
  export * from '../../utils/augmentType.js';
@@ -1,5 +1,3 @@
1
1
  export { envVarsPlugin };
2
- export { applyEnvVar };
3
2
  import type { Plugin } from 'vite';
4
3
  declare function envVarsPlugin(): Plugin;
5
- declare function applyEnvVar(envName: string, envVal: string, code: string): string;
@@ -1,6 +1,4 @@
1
1
  export { envVarsPlugin };
2
- // For ./envVars.spec.ts
3
- export { applyEnvVar };
4
2
  import { loadEnv } from 'vite';
5
3
  import { assert, assertPosixPath, assertUsage, assertWarning, escapeRegex, isArray, lowerFirst } from '../utils.js';
6
4
  import { sourceMapPassthrough } from '../shared/rollupSourceMap.js';
@@ -43,12 +41,13 @@ function envVarsPlugin() {
43
41
  return !envPrefix.some((prefix) => key.startsWith(prefix));
44
42
  })
45
43
  .forEach(([envName, envVal]) => {
44
+ const envStatement = `import.meta.env.${envName}`;
45
+ const envStatementRegEx = new RegExp(escapeRegex(envStatement) + '\\b', 'g');
46
46
  // Security check
47
47
  {
48
- const envStatement = getEnvStatement(envName);
49
48
  const isPrivate = !envName.startsWith(PUBLIC_ENV_PREFIX) && !PUBLIC_ENV_WHITELIST.includes(envName);
50
49
  if (isPrivate && isClientSide) {
51
- if (!code.includes(envStatement))
50
+ if (!envStatementRegEx.test(code))
52
51
  return;
53
52
  const modulePath = getModuleFilePath(id, config);
54
53
  const errMsgAddendum = isBuild ? '' : ' (Vike will prevent your app from building for production)';
@@ -67,7 +66,7 @@ function envVarsPlugin() {
67
66
  assert(!(isPrivate && isClientSide) || !isBuild);
68
67
  }
69
68
  // Apply
70
- code = applyEnvVar(envName, envVal, code);
69
+ code = applyEnvVar(envStatementRegEx, envVal, code);
71
70
  });
72
71
  // Line numbers didn't change.
73
72
  // - We only break the column number of a couple of lines, wich is acceptable.
@@ -77,13 +76,8 @@ function envVarsPlugin() {
77
76
  }
78
77
  };
79
78
  }
80
- function applyEnvVar(envName, envVal, code) {
81
- const envStatement = getEnvStatement(envName);
82
- const regex = new RegExp(escapeRegex(envStatement) + '\\b', 'g');
83
- return code.replace(regex, JSON.stringify(envVal));
84
- }
85
- function getEnvStatement(envName) {
86
- return `import.meta.env.${envName}`;
79
+ function applyEnvVar(envStatementRegEx, envVal, code) {
80
+ return code.replace(envStatementRegEx, JSON.stringify(envVal));
87
81
  }
88
82
  function getIsClientSide(config, options) {
89
83
  const isBuild = config.command === 'build';
@@ -1,11 +1,11 @@
1
1
  export { assertNoInfiniteHttpRedirect };
2
- import { assert, assertUsage, getGlobalObject, isUriWithProtocol } from '../../utils.js';
2
+ import { assert, assertUsage, getGlobalObject } from '../../utils.js';
3
3
  import pc from '@brillout/picocolors';
4
4
  const globalObject = getGlobalObject('assertNoInfiniteHttpRedirect.ts', {
5
5
  redirectGraph: {}
6
6
  });
7
7
  function assertNoInfiniteHttpRedirect(urlRedirectTarget, urlLogical) {
8
- if (isUriWithProtocol(urlRedirectTarget)) {
8
+ if (!urlRedirectTarget.startsWith('/')) {
9
9
  // We assume that urlRedirectTarget points to an origin that is external (not the same origin), and we can therefore assume that the app doesn't define an infinite loop (in itself).
10
10
  // - There isn't a reliable way to check whether the redirect points to an external origin or the same origin. For same origins, we assume/hope the user to pass the URL without origin.
11
11
  // ```js
@@ -14,7 +14,6 @@ function assertNoInfiniteHttpRedirect(urlRedirectTarget, urlLogical) {
14
14
  // ```
15
15
  return;
16
16
  }
17
- assert(urlRedirectTarget.startsWith('/'));
18
17
  assert(urlLogical.startsWith('/'));
19
18
  const graph = copy(globalObject.redirectGraph);
20
19
  graph[urlRedirectTarget] ?? (graph[urlRedirectTarget] = new Set());
@@ -60,7 +60,7 @@ declare function prerenderPage(pageContext: PageContextInitEnhanced & PageFiles
60
60
  _urlHandler: ((url: string) => string) | null;
61
61
  isClientSideNavigation: boolean;
62
62
  } & {
63
- urlParsed: import("../../../shared/getPageContextUrlComputed.js").Url;
63
+ urlParsed: import("../utils.js").UrlPublic;
64
64
  urlPathname: string;
65
65
  url: string;
66
66
  } & {
@@ -111,7 +111,7 @@ declare function prerenderPage(pageContext: PageContextInitEnhanced & PageFiles
111
111
  _urlHandler: ((url: string) => string) | null;
112
112
  isClientSideNavigation: boolean;
113
113
  } & {
114
- urlParsed: import("../../../shared/getPageContextUrlComputed.js").Url;
114
+ urlParsed: import("../utils.js").UrlPublic;
115
115
  urlPathname: string;
116
116
  url: string;
117
117
  } & {
@@ -163,7 +163,7 @@ declare function prerender404Page(renderContext: RenderContext, pageContextInit_
163
163
  _urlHandler: ((url: string) => string) | null;
164
164
  isClientSideNavigation: boolean;
165
165
  } & {
166
- urlParsed: import("../../../shared/getPageContextUrlComputed.js").Url;
166
+ urlParsed: import("../utils.js").UrlPublic;
167
167
  urlPathname: string;
168
168
  url: string;
169
169
  } & {
@@ -214,7 +214,7 @@ declare function prerender404Page(renderContext: RenderContext, pageContextInit_
214
214
  _urlHandler: ((url: string) => string) | null;
215
215
  isClientSideNavigation: boolean;
216
216
  } & {
217
- urlParsed: import("../../../shared/getPageContextUrlComputed.js").Url;
217
+ urlParsed: import("../utils.js").UrlPublic;
218
218
  urlPathname: string;
219
219
  url: string;
220
220
  } & {
@@ -274,7 +274,7 @@ declare function getPageContextInitEnhanced(pageContextInit: {
274
274
  _urlHandler: ((url: string) => string) | null;
275
275
  isClientSideNavigation: boolean;
276
276
  } & {
277
- urlParsed: import("../../../shared/getPageContextUrlComputed.js").Url;
277
+ urlParsed: import("../utils.js").UrlPublic;
278
278
  urlPathname: string;
279
279
  url: string;
280
280
  } & {
@@ -1,13 +1,13 @@
1
1
  export { resolveRedirects };
2
2
  // For ./resolveRedirects.spec.ts
3
3
  export { resolveRouteStringRedirect };
4
- import { assertIsNotBrowser } from '../../utils/assertIsNotBrowser.js';
5
- import { assert, assertUsage, isUriWithProtocol } from '../utils.js';
6
- import { resolveUrlPathname } from './resolveUrlPathname.js';
7
- import { assertRouteString, resolveRouteString } from './resolveRouteString.js';
4
+ import { assertIsNotBrowser } from '../../../utils/assertIsNotBrowser.js';
5
+ import { assert, assertUsage, assertUsageUrlRedirectTarget, isUrlRedirectTarget } from '../../../shared/utils.js';
6
+ import { resolveUrlPathname } from '../../../shared/route/resolveUrlPathname.js';
7
+ import { assertRouteString, resolveRouteString } from '../../../shared/route/resolveRouteString.js';
8
8
  import pc from '@brillout/picocolors';
9
9
  assertIsNotBrowser(); // Don't bloat the client
10
- // TODO/v1-release: update
10
+ // TODO/next-major-release: update
11
11
  const configSrc = '[vite.config.js > vike({ redirects })]';
12
12
  function resolveRedirects(redirects, urlPathname) {
13
13
  for (const [urlSource, urlTarget] of Object.entries(redirects)) {
@@ -19,10 +19,8 @@ function resolveRedirects(redirects, urlPathname) {
19
19
  }
20
20
  function resolveRouteStringRedirect(urlSource, urlTarget, urlPathname) {
21
21
  assertRouteString(urlSource, `${configSrc} Invalid`);
22
- assertUsage(urlTarget.startsWith('/') ||
23
- // Is allowing any protocol a safety issue? https://github.com/vikejs/vike/pull/1292#issuecomment-1828043917
24
- isUriWithProtocol(urlTarget) ||
25
- urlTarget === '*', `${configSrc} Invalid redirection target URL ${pc.cyan(urlTarget)}: the target URL should start with ${pc.cyan('/')}, a valid protocol (${pc.cyan('https:')}, ${pc.cyan('http:')}, ${pc.cyan('mailto:')}, ${pc.cyan('ipfs:')}, ${pc.cyan('magnet:')}, ...), or be ${pc.cyan('*')}`);
22
+ // Is allowing any protocol a safety issue? https://github.com/vikejs/vike/pull/1292#issuecomment-1828043917
23
+ assertUsageUrlRedirectTarget(urlTarget, `${configSrc} The URL redirection target`, true);
26
24
  assertParams(urlSource, urlTarget);
27
25
  const match = resolveRouteString(urlSource, urlPathname);
28
26
  if (!match)
@@ -30,7 +28,7 @@ function resolveRouteStringRedirect(urlSource, urlTarget, urlPathname) {
30
28
  const urlResolved = resolveUrlPathname(urlTarget, match.routeParams);
31
29
  if (urlResolved === urlPathname)
32
30
  return null;
33
- assert(urlResolved.startsWith('/') || isUriWithProtocol(urlResolved));
31
+ assert(isUrlRedirectTarget(urlResolved));
34
32
  return urlResolved;
35
33
  }
36
34
  function assertParams(urlSource, urlTarget) {
@@ -38,7 +36,7 @@ function assertParams(urlSource, urlTarget) {
38
36
  routeSegments.forEach((routeSegment) => {
39
37
  if (routeSegment.startsWith('@') || routeSegment.startsWith('*')) {
40
38
  const segments = urlSource.split('/');
41
- assertUsage(segments.includes(routeSegment), `${configSrc} The redirection source URL ${pc.cyan(urlSource)} is missing the URL parameter ${pc.cyan(routeSegment)} used by the redirection target URL ${pc.cyan(urlTarget)}`);
39
+ assertUsage(segments.includes(routeSegment), `${configSrc} The redirection source URL ${pc.string(urlSource)} is missing the URL parameter ${pc.string(routeSegment)} used by the redirection target URL ${pc.string(urlTarget)}`);
42
40
  }
43
41
  });
44
42
  }
@@ -1,7 +1,7 @@
1
1
  export { renderPage };
2
2
  export { renderPage_addWrapper };
3
3
  import { HttpResponse } from './renderPage/createHttpResponseObject.js';
4
- import type { PageContextBuiltInServer } from '../../types/index.js';
4
+ import type { PageContextServer } from '../../types/index.js';
5
5
  declare let renderPage_wrapper: <PageContext>(_httpRequestId: number, ret: () => Promise<PageContext>) => Promise<{
6
6
  pageContextReturn: Awaited<PageContext>;
7
7
  }>;
@@ -13,4 +13,4 @@ declare function renderPage<PageContextUserAdded extends {}, PageContextInit ext
13
13
  urlOriginal: string;
14
14
  }>(pageContextInit: PageContextInit): Promise<PageContextInit & {
15
15
  httpResponse: HttpResponse | null;
16
- } & Partial<PageContextBuiltInServer & PageContextUserAdded>>;
16
+ } & Partial<PageContextServer & PageContextUserAdded>>;
@@ -2,7 +2,7 @@ export { renderPage };
2
2
  export { renderPage_addWrapper };
3
3
  import { getRenderContext, getPageContextInitEnhanced, renderPageAlreadyRouted } from './renderPage/renderPageAlreadyRouted.js';
4
4
  import { route } from '../../shared/route/index.js';
5
- import { assert, hasProp, objectAssign, isParsable, parseUrl, assertEnv, assertWarning, getGlobalObject, checkType, assertUsage, normalizeUrlPathname, removeBaseServer, modifyUrlPathname, prependBase, removeUrlOrigin, addUrlOrigin, createUrlFromComponents, isUriWithProtocol } from './utils.js';
5
+ import { assert, hasProp, objectAssign, isUrl, parseUrl, assertEnv, assertWarning, getGlobalObject, checkType, assertUsage, normalizeUrlPathname, removeBaseServer, modifyUrlPathname, prependBase, removeUrlOrigin, addUrlOrigin, createUrlFromComponents, isUri } from './utils.js';
6
6
  import { assertNoInfiniteAbortLoop, getPageContextFromAllRewrites, isAbortError, logAbortErrorHandled } from '../../shared/route/abort.js';
7
7
  import { getGlobalContext, initGlobalContext } from './globalContext.js';
8
8
  import { handlePageContextRequestUrl } from './renderPage/handlePageContextRequestUrl.js';
@@ -17,7 +17,7 @@ import { serializePageContextAbort, serializePageContextClientSide } from './htm
17
17
  import { getErrorPageId } from '../../shared/error-page.js';
18
18
  import { handleErrorWithoutErrorPage } from './renderPage/handleErrorWithoutErrorPage.js';
19
19
  import { loadUserFilesServerSide } from './renderPage/loadUserFilesServerSide.js';
20
- import { resolveRedirects } from '../../shared/route/resolveRedirects.js';
20
+ import { resolveRedirects } from './renderPage/resolveRedirects.js';
21
21
  const globalObject = getGlobalObject('runtime/renderPage.ts', {
22
22
  httpRequestsCount: 0,
23
23
  pendingRequestsCount: 0
@@ -326,12 +326,9 @@ function getRequestId() {
326
326
  return httpRequestId;
327
327
  }
328
328
  function isIgnoredUrl(urlOriginal) {
329
- const isViteClientRequest = urlOriginal.endsWith('/@vite/client') || urlOriginal.startsWith('/@fs/');
330
- assertWarning(!isViteClientRequest, `The vike middleware renderPage() was called with the URL ${urlOriginal} which is unexpected because the HTTP request should have already been handled by Vite's development middleware. Make sure to 1. install Vite's development middleware and 2. add Vite's middleware *before* Vike's middleware, see https://vike.dev/renderPage`, { onlyOnce: true });
331
- return (urlOriginal.endsWith('/__vite_ping') ||
332
- urlOriginal.endsWith('/favicon.ico') ||
333
- !isParsable(urlOriginal) ||
334
- isViteClientRequest);
329
+ const isViteRequest = urlOriginal.endsWith('/@vite/client') || urlOriginal.startsWith('/@fs/');
330
+ assertWarning(!isViteRequest, `The vike middleware renderPage() was called with the URL ${urlOriginal} which is unexpected because the HTTP request should have already been handled by Vite's development middleware. Make sure to 1. install Vite's development middleware and 2. add Vite's middleware *before* Vike's middleware, see https://vike.dev/renderPage`, { onlyOnce: true });
331
+ return (urlOriginal.endsWith('/__vite_ping') || urlOriginal.endsWith('/favicon.ico') || !isUrl(urlOriginal) || isViteRequest);
335
332
  }
336
333
  function normalizeUrl(pageContextInit, httpRequestId) {
337
334
  const { trailingSlash, disableUrlNormalization, baseServer } = getGlobalContext();
@@ -359,9 +356,9 @@ function getPermanentRedirect(pageContextInit, httpRequestId) {
359
356
  const urlTarget = resolveRedirects(redirects, urlPathname);
360
357
  if (urlTarget === null)
361
358
  return null;
362
- if (!isParsable(urlTarget)) {
359
+ if (!isUrl(urlTarget)) {
363
360
  // E.g. `urlTarget === 'mailto:some@example.com'`
364
- assert(isUriWithProtocol(urlTarget) && !urlTarget.startsWith('http'));
361
+ assert(isUri(urlTarget));
365
362
  urlTargetExternal = urlTarget;
366
363
  return null;
367
364
  }
@@ -4,32 +4,10 @@ export type { PageContextUrlInternal };
4
4
  export type { PageContextUrlClient };
5
5
  export type { PageContextUrlServer };
6
6
  export type { PageContextUrlSource };
7
- export type { Url };
8
- type Url = {
9
- /** The URL origin, e.g. `https://example.com` of `https://example.com/product/42?details=yes#reviews` */
10
- origin: null | string;
11
- /** The URL pathname, e.g. `/product/42` of `https://example.com/product/42?details=yes#reviews` */
12
- pathname: string;
13
- /** URL pathname including the Base URL, e.g. `/some-base-url/product/42` of `https://example.com/some-base-url/product/42` (whereas `pageContext.urlParsed.pathname` is `/product/42`) */
14
- pathnameOriginal: string;
15
- /** The URL search parameters, e.g. `{ details: 'yes' }` for `https://example.com/product/42?details=yes#reviews` */
16
- search: Record<string, string>;
17
- /** The URL search parameters array, e.g. `{ fruit: ['apple', 'orange'] }` for `https://example.com?fruit=apple&fruit=orange` **/
18
- searchAll: Record<string, string[]>;
19
- /** The URL search parameterer string, e.g. `?details=yes` of `https://example.com/product/42?details=yes#reviews` */
20
- searchOriginal: null | string;
21
- /** The URL hash, e.g. `reviews` of `https://example.com/product/42?details=yes#reviews` */
22
- hash: string;
23
- /** The URL hash string, e.g. `#reviews` of `https://example.com/product/42?details=yes#reviews` */
24
- hashOriginal: null | string;
25
- /** @deprecated */
26
- hashString: null | string;
27
- /** @deprecated */
28
- searchString: null | string;
29
- };
7
+ import { type UrlPublic } from './utils.js';
30
8
  type PageContextUrlComputed = {
31
9
  /** Parsed information about the current URL */
32
- urlParsed: Url;
10
+ urlParsed: UrlPublic;
33
11
  /** The URL pathname, e.g. `/product/42` of `https://example.com/product/42?details=yes#reviews` */
34
12
  urlPathname: string;
35
13
  /** @deprecated */
@@ -28,11 +28,6 @@ function getPageContextUrlComputed(pageContext) {
28
28
  return pageContextUrlComputed;
29
29
  }
30
30
  function getUrlParsed(pageContext) {
31
- // We need a url handler function because the onBeforeRoute() hook may set pageContext.urlLogical (typically for i18n)
32
- let urlHandler = pageContext._urlHandler;
33
- if (!urlHandler) {
34
- urlHandler = (url) => url;
35
- }
36
31
  // Example of i18n app using `throw render()`:
37
32
  // 1. User goes to '/fr-FR/admin'.
38
33
  // 2. The first onBeforeRoute() call accesses pageContext.urlPathname (its value is '/fr-FR/admin': the pathname of pageContext.urlOriginal, since both pageContext.urlLogical and pageContext._urlRewrite are undefined) and sets pageContext.urlLogical to '/admin'.
@@ -41,23 +36,36 @@ function getUrlParsed(pageContext) {
41
36
  // 5. The second onBeforeRoute() call accesses pageContext.urlPathname (its value is '/fr-FR/login': the pathname of pageContext._urlRewrite, since pageContext.urlLogical is undefined) and sets pageContext.urlLogical to '/login'.
42
37
  // 6. The value of pageContext.urlPathname is now '/login': the pathname of `pageContext.urlLogical`. (While pageContext.urlOriginal is still '/fr-FR/admin'.)
43
38
  // Reproduction: https://github.com/vikejs/vike/discussions/1436#discussioncomment-8142023
44
- let urlResolved =
45
- // Set by onBeforeRoute()
46
- pageContext.urlLogical ??
39
+ // Determine logical URL
40
+ let urlResolved;
41
+ let baseToBeRemoved;
42
+ if (pageContext.urlLogical) {
43
+ // Set by onBeforeRoute()
44
+ urlResolved = pageContext.urlLogical;
45
+ baseToBeRemoved = false;
46
+ }
47
+ else if (pageContext._urlRewrite) {
47
48
  // Set by `throw render()`
48
- pageContext._urlRewrite ??
49
+ urlResolved = pageContext._urlRewrite;
50
+ baseToBeRemoved = false;
51
+ }
52
+ else {
49
53
  // Set by renderPage()
50
- pageContext.urlOriginal;
51
- urlResolved = urlHandler(urlResolved);
52
- /*
53
- console.log('pageContext.urlLogical', pageContext.urlLogical)
54
- console.log('pageContext._urlRewrite', pageContext._urlRewrite)
55
- console.log('pageContext.urlOriginal', pageContext.urlOriginal)
56
- console.log()
57
- //*/
58
- const baseServer = pageContext._baseServer;
54
+ urlResolved = pageContext.urlOriginal;
55
+ baseToBeRemoved = true;
56
+ }
59
57
  assert(urlResolved && typeof urlResolved === 'string');
60
- assert(baseServer.startsWith('/'));
58
+ // Remove .pageContext.json
59
+ let urlHandler = pageContext._urlHandler;
60
+ if (!urlHandler)
61
+ urlHandler = (url) => url;
62
+ urlResolved = urlHandler(urlResolved);
63
+ // Remove Base URL.
64
+ // - We assume there isn't any Base URL to the URLs set by the user at `throw render()` and onBeforeRoute()
65
+ // - This makes sense because the Base URL is merely a setting: ideally the user should write code that doesn't know anything about it (so that the user can remove/add/change Base URL without having to modify any code).
66
+ // - pageContext.urlOriginal is the URL of the HTTP request and thus contains the Base URL.
67
+ const baseServer = !baseToBeRemoved ? '/' : pageContext._baseServer;
68
+ // Parse URL
61
69
  return parseUrl(urlResolved, baseServer);
62
70
  }
63
71
  function urlPathnameGetter() {
@@ -72,47 +80,45 @@ function urlGetter() {
72
80
  return urlPathnameGetter.call(this);
73
81
  }
74
82
  function urlParsedGetter() {
75
- const urlParsedOriginal = getUrlParsed(this);
76
- const { origin, pathname, pathnameOriginal, search, searchAll, searchOriginal, hash, hashOriginal } = urlParsedOriginal;
83
+ const {
84
+ // remove hasBaseServer as it isn't part of UrlPublic
85
+ hasBaseServer: _, ...urlParsed } = getUrlParsed(this);
77
86
  const hashIsAvailable = isBrowser();
78
87
  const warnHashNotAvailable = (prop) => {
79
88
  assertWarning(hashIsAvailable, `pageContext.urlParsed.${prop} isn't available on the server-side (HTTP requests don't include the URL hash)`, { onlyOnce: true, showStackTrace: true });
80
89
  };
81
- const urlParsed = {
82
- origin,
83
- pathname,
84
- pathnameOriginal,
85
- search,
86
- searchAll,
87
- searchOriginal,
90
+ const urlParsedEnhanced = {
91
+ ...urlParsed,
88
92
  get hash() {
89
93
  warnHashNotAvailable('hash');
90
- return hash;
94
+ return urlParsed.hash;
91
95
  },
92
96
  get hashOriginal() {
93
97
  warnHashNotAvailable('hashOriginal');
94
- return hashOriginal;
98
+ return urlParsed.hashOriginal;
95
99
  },
100
+ // TODO/next-major-release: remove
96
101
  get hashString() {
97
102
  assertWarning(false, 'pageContext.urlParsed.hashString has been renamed to pageContext.urlParsed.hashOriginal', {
98
103
  onlyOnce: true,
99
104
  showStackTrace: true
100
105
  });
101
106
  warnHashNotAvailable('hashString');
102
- return hashOriginal;
107
+ return urlParsed.hashOriginal;
103
108
  },
109
+ // TODO/next-major-release: remove
104
110
  get searchString() {
105
111
  assertWarning(false, 'pageContext.urlParsed.searchString has been renamed to pageContext.urlParsed.searchOriginal', { onlyOnce: true, showStackTrace: true });
106
- return searchOriginal;
112
+ return urlParsed.searchOriginal;
107
113
  }
108
114
  };
109
- changeEnumerable(urlParsed, 'hashString', false);
110
- changeEnumerable(urlParsed, 'searchString', false);
115
+ changeEnumerable(urlParsedEnhanced, 'hashString', false);
116
+ changeEnumerable(urlParsedEnhanced, 'searchString', false);
111
117
  if (!hashIsAvailable) {
112
- changeEnumerable(urlParsed, 'hash', false);
113
- changeEnumerable(urlParsed, 'hashOriginal', false);
118
+ changeEnumerable(urlParsedEnhanced, 'hash', false);
119
+ changeEnumerable(urlParsedEnhanced, 'hashOriginal', false);
114
120
  }
115
- return urlParsed;
121
+ return urlParsedEnhanced;
116
122
  }
117
123
  function assertPageContextUrl(pageContext) {
118
124
  assert(typeof pageContext.urlOriginal === 'string');
@@ -0,0 +1,14 @@
1
+ export { modifyUrl };
2
+ /**
3
+ * Modify a URL.
4
+ *
5
+ * Example: changing the URL pathname for internationalization.
6
+ *
7
+ * https://vike.dev/modifyUrl
8
+ */
9
+ declare function modifyUrl(url: string, modify: {
10
+ pathname?: string;
11
+ hostname?: string;
12
+ port?: number;
13
+ protocol?: string;
14
+ }): string;
@@ -0,0 +1,28 @@
1
+ export { modifyUrl };
2
+ import { createUrlFromComponents, parseUrl } from './utils.js';
3
+ /**
4
+ * Modify a URL.
5
+ *
6
+ * Example: changing the URL pathname for internationalization.
7
+ *
8
+ * https://vike.dev/modifyUrl
9
+ */
10
+ function modifyUrl(url, modify) {
11
+ const urlParsed = parseUrl(url, '/');
12
+ // Pathname
13
+ const pathname = modify.pathname ?? urlParsed.pathname;
14
+ // Origin
15
+ const originParts = [
16
+ modify.protocol ?? urlParsed.protocol ?? '',
17
+ modify.hostname ?? urlParsed.hostname ?? ''
18
+ ];
19
+ const port = modify.port ?? urlParsed.port;
20
+ if (port || port === 0) {
21
+ originParts.push(`:${port}`);
22
+ }
23
+ const origin = originParts.join('');
24
+ const urlModified = createUrlFromComponents(origin, pathname,
25
+ // Should we also support modifying search and hash?
26
+ urlParsed.searchOriginal, urlParsed.hashOriginal);
27
+ return urlModified;
28
+ }
@@ -8,7 +8,7 @@ export { getPageContextFromAllRewrites };
8
8
  export { AbortRender };
9
9
  export { assertNoInfiniteAbortLoop };
10
10
  import { isUserHookError } from '../hooks/executeHook.js';
11
- import { assert, assertInfo, assertUsage, assertWarning, checkType, hasProp, isUriWithProtocol, joinEnglish, objectAssign, truncateString } from './utils.js';
11
+ import { assert, assertInfo, assertUsage, assertUsageUrlPathnameAbsolute, assertUsageUrlRedirectTarget, assertWarning, checkType, hasProp, joinEnglish, objectAssign, truncateString } from './utils.js';
12
12
  import pc from '@brillout/picocolors';
13
13
  /**
14
14
  * Abort the rendering of the current page, and redirect the user to another URL instead.
@@ -20,7 +20,7 @@ import pc from '@brillout/picocolors';
20
20
  */
21
21
  function redirect(url, statusCode) {
22
22
  const abortCaller = 'throw redirect()';
23
- assertUrl(url, abortCaller, true);
23
+ assertUsageUrlRedirectTarget(url, getErrPrefix(abortCaller));
24
24
  const args = [JSON.stringify(url)];
25
25
  if (!statusCode) {
26
26
  statusCode = 302;
@@ -60,7 +60,7 @@ function render_(urlOrStatusCode, abortReason, abortCall, abortCaller, pageConte
60
60
  }
61
61
  if (typeof urlOrStatusCode === 'string') {
62
62
  const url = urlOrStatusCode;
63
- assertUrl(url, abortCaller);
63
+ assertUsageUrlPathnameAbsolute(url, getErrPrefix(abortCaller));
64
64
  objectAssign(pageContextAbort, {
65
65
  _urlRewrite: url
66
66
  });
@@ -168,12 +168,6 @@ function assertNoInfiniteAbortLoop(rewriteCount, redirectCount) {
168
168
  .join(' and ');
169
169
  assertUsage(rewriteCount + redirectCount <= 7, `Maximum chain length of 7 ${abortCalls} exceeded. Did you define an infinite loop of ${abortCalls}?`);
170
170
  }
171
- function assertUrl(url, abortCaller, allowAbsoluteUrl) {
172
- assertUsage(url.startsWith('/') || (allowAbsoluteUrl && isUriWithProtocol(url)), [
173
- `Invalid URL ${pc.cyan(url)} passed to ${pc.cyan(abortCaller)}:`,
174
- `the URL should start with ${pc.cyan('/')}`,
175
- allowAbsoluteUrl && `or a valid protocol (${pc.cyan('https:')}, ${pc.cyan('ipfs:')}, ...)`
176
- ]
177
- .filter(Boolean)
178
- .join(' '));
171
+ function getErrPrefix(abortCaller) {
172
+ return `URL passed to ${pc.code(abortCaller)}`;
179
173
  }
@@ -1,6 +1,6 @@
1
1
  export { executeOnBeforeRouteHook };
2
2
  import { assertPageContextProvidedByUser } from '../assertPageContextProvidedByUser.js';
3
- import { assertUsage, hasProp, isObjectWithKeys, objectAssign, assertWarning, assertUsageUrl, joinEnglish, assert } from './utils.js';
3
+ import { assertUsage, hasProp, isObjectWithKeys, objectAssign, assertWarning, assertUsageUrlPathnameAbsolute, joinEnglish, assert } from './utils.js';
4
4
  import { assertRouteParams, assertSyncRouting } from './resolveRouteFunction.js';
5
5
  import pc from '@brillout/picocolors';
6
6
  import { executeHook } from '../hooks/executeHook.js';
@@ -65,7 +65,9 @@ async function getPageContextFromHook(onBeforeRouteHook, pageContext) {
65
65
  delete hookReturn.pageContext.urlOriginal;
66
66
  }
67
67
  if (hasProp(hookReturn.pageContext, 'urlLogical')) {
68
- assertUsageUrl(hookReturn.pageContext.urlLogical, `${errPrefix} returned ${pc.cyan('{ pageContext: { urlLogical } }')} but ${pc.cyan('urlLogical')}`);
68
+ assertUsageUrlPathnameAbsolute(
69
+ // We type-cast to string instead of assertUsage() in order to save client-side KBs
70
+ hookReturn.pageContext.urlLogical, `${errPrefix} returned ${pc.cyan('{ pageContext: { urlLogical } }')} but ${pc.cyan('urlLogical')}`);
69
71
  }
70
72
  assertPageContextProvidedByUser(hookReturn.pageContext, {
71
73
  hookFilePath: onBeforeRouteHook.hookFilePath,
@@ -10,7 +10,7 @@ export type { Config, ConfigMeta as Meta, ImportString, DataAsync, DataSync, Gua
10
10
  export type { ConfigEnv } from '../shared/page-configs/PageConfig.js';
11
11
  export type { ConfigDefinition, ConfigEffect } from '../node/plugin/plugins/importUserCode/v1-design/getVikeConfig/configDefinitionsBuiltIn.js';
12
12
  export type { ConfigEntries } from '../shared/getPageFiles/getExports.js';
13
- export type { Url } from '../shared/getPageContextUrlComputed.js';
13
+ export type { UrlPublic as Url } from '../utils/parseUrl.js';
14
14
  export type { InjectFilterEntry } from '../node/runtime/html/injectAssets/getHtmlTags.js';
15
15
  export { defineConfig } from './defineConfig.js';
16
16
  import type { ConfigEnv } from '../shared/page-configs/PageConfig.js';
@@ -1,24 +1,57 @@
1
1
  export { parseUrl };
2
- export { isParsable };
3
- export { assertUsageUrl };
2
+ export { assertUsageUrlPathname };
3
+ export { assertUsageUrlPathnameAbsolute };
4
+ export { assertUsageUrlRedirectTarget };
5
+ export { isUrl };
6
+ export { isUri };
7
+ export { isUrlRedirectTarget };
8
+ export { isUrlExternal };
4
9
  export { isBaseServer };
5
10
  export { assertUrlComponents };
6
11
  export { createUrlFromComponents };
7
- export { isUriWithProtocol };
8
- declare function isParsable(url: string): boolean;
9
- declare function assertUsageUrl(url: unknown, errPrefix: string): asserts url is string;
10
- declare function parseUrl(url: string, baseServer: string): {
12
+ export type { UrlPublic };
13
+ export type { UrlPrivate };
14
+ type UrlPublic = {
15
+ /** The full URL. */
16
+ href: string;
17
+ /** The URL protocol, e.g. `https://` in `https://example.com` */
18
+ protocol: null | string;
19
+ /** The URL hostname, e.g. `example.com` in `https://example.com/product` and `localhost` in `http://localhost:3000/product` */
20
+ hostname: null | string;
21
+ /** The URL host port, e.g. `3000` in `http://localhost:3000/product` */
22
+ port: null | number;
23
+ /** The URL origin, e.g. `https://example.com` in `https://example.com/product/42` */
11
24
  origin: null | string;
25
+ /** The URL pathname, e.g. `/product/42` in `https://example.com/product/42?details=yes#reviews` */
12
26
  pathname: string;
27
+ /** URL pathname including the Base URL, e.g. `/some-base-url/product/42` in `https://example.com/some-base-url/product/42` (whereas `pageContext.urlParsed.pathname` is `/product/42`) */
13
28
  pathnameOriginal: string;
14
- hasBaseServer: boolean;
29
+ /** The URL search parameters, e.g. `{ details: 'yes' }` for `https://example.com/product/42?details=yes#reviews` */
15
30
  search: Record<string, string>;
31
+ /** The URL search parameters array, e.g. `{ fruit: ['apple', 'orange'] }` for `https://example.com?fruit=apple&fruit=orange` **/
16
32
  searchAll: Record<string, string[]>;
33
+ /** The URL search parameterer string, e.g. `?details=yes` in `https://example.com/product/42?details=yes#reviews` */
17
34
  searchOriginal: null | string;
35
+ /** The URL hash, e.g. `reviews` in `https://example.com/product/42?details=yes#reviews` */
18
36
  hash: string;
37
+ /** The URL hash string, e.g. `#reviews` in `https://example.com/product/42?details=yes#reviews` */
19
38
  hashOriginal: null | string;
39
+ /** @deprecated */
40
+ hashString: null | string;
41
+ /** @deprecated */
42
+ searchString: null | string;
43
+ };
44
+ type UrlPrivate = Omit<UrlPublic, 'hashString' | 'searchString'> & {
45
+ hasBaseServer: boolean;
20
46
  };
47
+ declare function parseUrl(url: string, baseServer: string): UrlPrivate;
21
48
  declare function isBaseServer(baseServer: string): boolean;
22
- declare function assertUrlComponents(url: string, origin: string | null, pathname: string, searchOriginal: string | null, hashOriginal: string | null): void;
23
- declare function createUrlFromComponents(origin: string | null, pathname: string, searchOriginal: string | null, hashOriginal: string | null): string;
24
- declare function isUriWithProtocol(str: string): boolean;
49
+ declare function assertUrlComponents(url: string, origin: string | null, pathnameOriginal: string, searchOriginal: string | null, hashOriginal: string | null): void;
50
+ declare function createUrlFromComponents(origin: string | null, pathname: string, search: string | null, hash: string | null): string;
51
+ declare function isUrl(url: string): boolean;
52
+ declare function isUrlRedirectTarget(url: string): boolean;
53
+ declare function isUrlExternal(url: string): boolean;
54
+ declare function isUri(uri: string): boolean;
55
+ declare function assertUsageUrlPathname(url: string, errPrefix: string): void;
56
+ declare function assertUsageUrlPathnameAbsolute(url: string, errPrefix: string): void;
57
+ declare function assertUsageUrlRedirectTarget(url: string, errPrefix: string, isUnresolved?: true): void;