@uniflowed/router 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/internal/base-path.js +32 -2
- package/internal/diagnostics.js +3 -1
- package/internal/head.js +17 -8
- package/package.json +4 -4
- package/rsc-client.js +1 -1
package/internal/base-path.js
CHANGED
|
@@ -36,13 +36,35 @@ export type RoutingSettings = {|
|
|
|
36
36
|
let installedBase: string = "";
|
|
37
37
|
let installedSlash: TrailingSlash = "ignore";
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* The string `@uniflowed/web`'s vitals beacon reads. Duplicated there on
|
|
41
|
+
* purpose: that package does not depend on this one, and the symbol is not
|
|
42
|
+
* part of the public API. The two have to move together. ubugeeei-prod/uf#1701
|
|
43
|
+
*/
|
|
44
|
+
const ROUTING_BASE: symbol = Symbol.for("@uniflowed/router.basePath");
|
|
45
|
+
|
|
46
|
+
function publishBase(base: string): void {
|
|
47
|
+
// `globalThis[symbol]` is a computed property on a namespace, which Flow
|
|
48
|
+
// rejects. `Object.defineProperty` is the write the rest of the repo uses.
|
|
49
|
+
Object.defineProperty(globalThis, ROUTING_BASE, {
|
|
50
|
+
value: base,
|
|
51
|
+
writable: true,
|
|
52
|
+
configurable: true,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
39
56
|
/**
|
|
40
57
|
* Say where this application is served and how its paths are spelled. Called
|
|
41
58
|
* once, by the entry that starts it.
|
|
59
|
+
*
|
|
60
|
+
* This is what publishes the base `vitalsBeacon` reads. The module does not
|
|
61
|
+
* publish when it loads: nothing installed is the root, and a shipped module
|
|
62
|
+
* cannot run a call at import time. ubugeeei-prod/uf#1701
|
|
42
63
|
*/
|
|
43
64
|
export function installRouting(settings: RoutingSettings): void {
|
|
44
65
|
installedBase = normalizeBase(settings.basePath ?? "");
|
|
45
66
|
installedSlash = settings.trailingSlash ?? "ignore";
|
|
67
|
+
publishBase(installedBase);
|
|
46
68
|
}
|
|
47
69
|
|
|
48
70
|
/**
|
|
@@ -143,10 +165,18 @@ export function spellPath(path: string, policy: TrailingSlash, underBase: boolea
|
|
|
143
165
|
return policy === "always" ? `${trimmed}/` : trimmed;
|
|
144
166
|
}
|
|
145
167
|
|
|
146
|
-
/**
|
|
168
|
+
/**
|
|
169
|
+
* Whether a path's last segment is a file: it has an extension, or it is
|
|
170
|
+
* Apple's association file.
|
|
171
|
+
*
|
|
172
|
+
* That file has no dot, and it is served only without a trailing slash.
|
|
173
|
+
* `trailingSlash: "always"` must not add one. The server's copy is
|
|
174
|
+
* `@uniflowed/server`'s `internal/routing.js`; `routing.test.js` holds the
|
|
175
|
+
* two to one answer. ubugeeei-prod/uf#1704
|
|
176
|
+
*/
|
|
147
177
|
function looksLikeAFile(path: string): boolean {
|
|
148
178
|
const last = path.slice(path.lastIndexOf("/") + 1);
|
|
149
|
-
return last.includes(".");
|
|
179
|
+
return last.includes(".") || last.toLowerCase() === "apple-app-site-association";
|
|
150
180
|
}
|
|
151
181
|
|
|
152
182
|
function splitPath(to: string): {| readonly path: string, readonly rest: string |} {
|
package/internal/diagnostics.js
CHANGED
|
@@ -48,6 +48,8 @@
|
|
|
48
48
|
// a URL, so it cannot be pointed at somebody else's server. Nothing leaves the
|
|
49
49
|
// machine.
|
|
50
50
|
|
|
51
|
+
import { basePath } from "./base-path.js";
|
|
52
|
+
|
|
51
53
|
/**
|
|
52
54
|
* The path `uf dev` serves the diagnostic channel on.
|
|
53
55
|
*
|
|
@@ -145,7 +147,7 @@ export function reportDiagnostic(diagnostic: BrowserDiagnostic, endpoint?: strin
|
|
|
145
147
|
// about development tooling. Losing the report is the right outcome when
|
|
146
148
|
// there is nothing listening; reporting the loss as an application error is
|
|
147
149
|
// not.
|
|
148
|
-
post(endpoint ?? DIAGNOSTIC_ENDPOINT
|
|
150
|
+
post(endpoint ?? `${basePath()}${DIAGNOSTIC_ENDPOINT}`, {
|
|
149
151
|
method: "POST",
|
|
150
152
|
body,
|
|
151
153
|
keepalive: true,
|
package/internal/head.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import * as React from "react";
|
|
12
12
|
|
|
13
|
+
import { addressOf } from "./base-path.js";
|
|
13
14
|
import type { JsonLd, Metadata, Robots } from "./resolve.js";
|
|
14
15
|
|
|
15
16
|
/**
|
|
@@ -22,18 +23,26 @@ import type { JsonLd, Metadata, Robots } from "./resolve.js";
|
|
|
22
23
|
* Three things it deliberately does not do. It does not resolve against the
|
|
23
24
|
* *page's* URL: `Head` renders inside the route and does not know it, and a
|
|
24
25
|
* `metadataBase` is a site-wide fact rather than a per-page one. It does not
|
|
25
|
-
* invent a
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* `
|
|
26
|
+
* invent a `metadataBase`: with none declared, a root-relative value is the
|
|
27
|
+
* address the router writes — the installed base in front of an application
|
|
28
|
+
* path — and every other value is emitted as written. The routing base is not
|
|
29
|
+
* a host, and a page that predates `metadataBase` still gets no origin invented
|
|
30
|
+
* for it. And it does not throw — a `metadataBase` that is not a URL is a
|
|
31
|
+
* mistake in one field, and turning it into a blank page would be a worse
|
|
32
|
+
* answer than an unresolved `og:image`.
|
|
30
33
|
*/
|
|
31
34
|
function absoluteUrl(value: string, base: void | string): string {
|
|
32
|
-
|
|
35
|
+
// `addressOf` leaves an absolute URL, a protocol-relative URL, `mailto:`,
|
|
36
|
+
// a query and a fragment as written, and it does not add a slash to a file.
|
|
37
|
+
// A path-absolute URL replaces `metadataBase`'s path, so the base has to be
|
|
38
|
+
// in the path before `URL` sees it or `og:image` drops `app.router.basePath`.
|
|
39
|
+
// ubugeeei-prod/uf#1705
|
|
40
|
+
const addressed = addressOf(value);
|
|
41
|
+
if (base == null) return addressed;
|
|
33
42
|
try {
|
|
34
|
-
return new URL(
|
|
43
|
+
return new URL(addressed, base).href;
|
|
35
44
|
} catch {
|
|
36
|
-
return
|
|
45
|
+
return addressed;
|
|
37
46
|
}
|
|
38
47
|
}
|
|
39
48
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniflowed/router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "The file-system router for Flow React applications: matching, layouts, loaders, navigation, server rendering and hydration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -73,8 +73,8 @@
|
|
|
73
73
|
}
|
|
74
74
|
},
|
|
75
75
|
"dependencies": {
|
|
76
|
-
"@uniflowed/hooks": "0.
|
|
77
|
-
"@uniflowed/server": "0.
|
|
78
|
-
"@uniflowed/validator": "0.
|
|
76
|
+
"@uniflowed/hooks": "0.17.0",
|
|
77
|
+
"@uniflowed/server": "0.17.0",
|
|
78
|
+
"@uniflowed/validator": "0.17.0"
|
|
79
79
|
}
|
|
80
80
|
}
|
package/rsc-client.js
CHANGED
|
@@ -47,7 +47,7 @@ import { readFormState } from "./internal/form-action.js";
|
|
|
47
47
|
|
|
48
48
|
// What `virtual:uf/client` calls in development when a server component
|
|
49
49
|
// changed; see "Hot updates" in `./internal/runtime.js`.
|
|
50
|
-
export { refreshForHotUpdate } from "./internal/runtime.js";
|
|
50
|
+
export { acceptHotRouteModules, refreshForHotUpdate } from "./internal/runtime.js";
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
53
|
* Hydrate a document React Server Components rendered.
|