@alignable/bifrost 0.0.11 → 0.0.13

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 (95) hide show
  1. package/dist/index.d.ts +6 -22
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/lib/PageShell.d.ts.map +1 -1
  4. package/dist/lib/getElementAttributes.d.ts +2 -0
  5. package/dist/lib/getElementAttributes.d.ts.map +1 -0
  6. package/dist/lib/getElementAttributes.js +7 -0
  7. package/dist/lib/turbolinks/adapter.d.ts +17 -0
  8. package/dist/lib/turbolinks/adapter.d.ts.map +1 -0
  9. package/dist/lib/turbolinks/adapter.js +1 -0
  10. package/dist/lib/turbolinks/browser_adapter.d.ts +28 -0
  11. package/dist/lib/turbolinks/browser_adapter.d.ts.map +1 -0
  12. package/dist/lib/turbolinks/browser_adapter.js +68 -0
  13. package/dist/lib/turbolinks/controller.d.ts +75 -0
  14. package/dist/lib/turbolinks/controller.d.ts.map +1 -0
  15. package/dist/lib/turbolinks/controller.js +219 -0
  16. package/dist/lib/turbolinks/head_details.d.ts +24 -0
  17. package/dist/lib/turbolinks/head_details.d.ts.map +1 -0
  18. package/dist/lib/turbolinks/head_details.js +97 -0
  19. package/dist/lib/turbolinks/index.d.ts +14 -0
  20. package/dist/lib/turbolinks/index.d.ts.map +1 -0
  21. package/dist/lib/turbolinks/index.js +55 -0
  22. package/dist/lib/turbolinks/location.d.ts +23 -0
  23. package/dist/lib/turbolinks/location.d.ts.map +1 -0
  24. package/dist/lib/turbolinks/location.js +75 -0
  25. package/dist/lib/turbolinks/lruCache.d.ts.map +1 -0
  26. package/dist/lib/turbolinks/mergeHead.d.ts +2 -0
  27. package/dist/lib/turbolinks/mergeHead.d.ts.map +1 -0
  28. package/dist/lib/{mergeHead.js → turbolinks/mergeHead.js} +27 -33
  29. package/dist/lib/turbolinks/progress_bar.d.ts +24 -0
  30. package/dist/lib/turbolinks/progress_bar.d.ts.map +1 -0
  31. package/dist/lib/turbolinks/progress_bar.js +99 -0
  32. package/dist/lib/turbolinks/types.d.ts +7 -0
  33. package/dist/lib/turbolinks/types.d.ts.map +1 -0
  34. package/dist/lib/turbolinks/types.js +3 -0
  35. package/dist/lib/turbolinks/util.d.ts +18 -0
  36. package/dist/lib/turbolinks/util.d.ts.map +1 -0
  37. package/dist/lib/turbolinks/util.js +125 -0
  38. package/dist/lib/turbolinks/visit.d.ts +55 -0
  39. package/dist/lib/turbolinks/visit.d.ts.map +1 -0
  40. package/dist/lib/turbolinks/visit.js +163 -0
  41. package/dist/proxy/pages/onRenderClient.d.ts.map +1 -1
  42. package/dist/proxy/pages/onRenderClient.js +40 -29
  43. package/dist/proxy/pages/onRenderHtml.d.ts.map +1 -1
  44. package/dist/proxy/pages/onRenderHtml.js +19 -5
  45. package/dist/proxy/pages/restorationVisit/+config.d.ts +2 -0
  46. package/dist/proxy/pages/restorationVisit/+config.d.ts.map +1 -1
  47. package/dist/proxy/pages/restorationVisit/+config.js +3 -0
  48. package/dist/proxy/pages/restorationVisit/onRenderClient.d.ts +3 -0
  49. package/dist/proxy/pages/restorationVisit/onRenderClient.d.ts.map +1 -0
  50. package/dist/proxy/pages/restorationVisit/onRenderClient.js +41 -0
  51. package/dist/renderer/getConfigOrPageContext.d.ts +8 -0
  52. package/dist/renderer/getConfigOrPageContext.d.ts.map +1 -0
  53. package/dist/renderer/getConfigOrPageContext.js +6 -0
  54. package/dist/renderer/onBeforeRoute.d.ts +1 -6
  55. package/dist/renderer/onBeforeRoute.d.ts.map +1 -1
  56. package/dist/renderer/onBeforeRoute.js +19 -11
  57. package/dist/renderer/onRenderClient.d.ts.map +1 -1
  58. package/dist/renderer/onRenderClient.js +31 -24
  59. package/dist/renderer/onRenderHtml.d.ts.map +1 -1
  60. package/dist/renderer/onRenderHtml.js +15 -18
  61. package/dist/renderer/utils/buildHead.d.ts +4 -0
  62. package/dist/renderer/utils/buildHead.d.ts.map +1 -0
  63. package/dist/renderer/utils/buildHead.js +10 -0
  64. package/dist/types/internal.d.ts +26 -23
  65. package/dist/types/internal.d.ts.map +1 -1
  66. package/package.json +13 -5
  67. package/dist/lib/dispatchTurbolinks.d.ts +0 -28
  68. package/dist/lib/dispatchTurbolinks.d.ts.map +0 -1
  69. package/dist/lib/dispatchTurbolinks.js +0 -5
  70. package/dist/lib/domUtils.d.ts +0 -3
  71. package/dist/lib/domUtils.d.ts.map +0 -1
  72. package/dist/lib/domUtils.js +0 -34
  73. package/dist/lib/linkInterceptor.d.ts +0 -2
  74. package/dist/lib/linkInterceptor.d.ts.map +0 -1
  75. package/dist/lib/linkInterceptor.js +0 -74
  76. package/dist/lib/lruCache.d.ts.map +0 -1
  77. package/dist/lib/mergeHead.d.ts +0 -2
  78. package/dist/lib/mergeHead.d.ts.map +0 -1
  79. package/dist/lib/navigateAnywhere.d.ts +0 -3
  80. package/dist/lib/navigateAnywhere.d.ts.map +0 -1
  81. package/dist/lib/navigateAnywhere.js +0 -15
  82. package/dist/lib/snapshots.d.ts +0 -9
  83. package/dist/lib/snapshots.d.ts.map +0 -1
  84. package/dist/lib/snapshots.js +0 -50
  85. package/dist/lib/turbolinks.d.ts +0 -5
  86. package/dist/lib/turbolinks.d.ts.map +0 -1
  87. package/dist/lib/turbolinks.js +0 -14
  88. package/dist/renderer/getDocumentProps.d.ts +0 -3
  89. package/dist/renderer/getDocumentProps.d.ts.map +0 -1
  90. package/dist/renderer/getDocumentProps.js +0 -3
  91. package/dist/renderer/utils/formatMetaObject.d.ts +0 -4
  92. package/dist/renderer/utils/formatMetaObject.d.ts.map +0 -1
  93. package/dist/renderer/utils/formatMetaObject.js +0 -3
  94. /package/dist/lib/{lruCache.d.ts → turbolinks/lruCache.d.ts} +0 -0
  95. /package/dist/lib/{lruCache.js → turbolinks/lruCache.js} +0 -0
@@ -0,0 +1,163 @@
1
+ import { navigate } from "vite-plugin-ssr/client/router";
2
+ import { uuid } from "./util";
3
+ export var TimingMetric;
4
+ (function (TimingMetric) {
5
+ TimingMetric["visitStart"] = "visitStart";
6
+ TimingMetric["requestStart"] = "requestStart";
7
+ TimingMetric["requestEnd"] = "requestEnd";
8
+ TimingMetric["visitEnd"] = "visitEnd";
9
+ })(TimingMetric || (TimingMetric = {}));
10
+ export var VisitState;
11
+ (function (VisitState) {
12
+ VisitState["initialized"] = "initialized";
13
+ VisitState["started"] = "started";
14
+ VisitState["canceled"] = "canceled";
15
+ VisitState["failed"] = "failed";
16
+ VisitState["completed"] = "completed";
17
+ })(VisitState || (VisitState = {}));
18
+ export class Visit {
19
+ constructor(controller, location, action, restorationIdentifier = uuid()) {
20
+ this.identifier = uuid();
21
+ this.timingMetrics = {};
22
+ this.progress = 0;
23
+ this.snapshotCached = false;
24
+ this.state = VisitState.initialized;
25
+ this.requestInFlight = false;
26
+ this.controller = controller;
27
+ this.location = location;
28
+ this.action = action;
29
+ this.adapter = controller.adapter;
30
+ this.restorationIdentifier = restorationIdentifier;
31
+ }
32
+ start() {
33
+ if (this.state == VisitState.initialized) {
34
+ this.recordTimingMetric(TimingMetric.visitStart);
35
+ this.state = VisitState.started;
36
+ this.adapter.visitStarted(this);
37
+ }
38
+ }
39
+ cancel() {
40
+ if (this.state == VisitState.started) {
41
+ this.requestInFlight = false;
42
+ this.cancelRender();
43
+ this.state = VisitState.canceled;
44
+ }
45
+ }
46
+ complete() {
47
+ if (this.state == VisitState.started) {
48
+ this.recordTimingMetric(TimingMetric.visitEnd);
49
+ this.state = VisitState.completed;
50
+ this.adapter.visitCompleted(this);
51
+ this.controller.visitCompleted(this);
52
+ }
53
+ }
54
+ fail() {
55
+ if (this.state == VisitState.started) {
56
+ this.state = VisitState.failed;
57
+ this.adapter.visitFailed(this);
58
+ }
59
+ }
60
+ changeHistory() {
61
+ // no-op since issueRequest calls navigate which handles all of this already
62
+ return;
63
+ }
64
+ issueRequest() {
65
+ if (!this.requestInFlight) {
66
+ if (this.shouldIssueRequest()) {
67
+ const url = new URL(this.location.toString(), this.location.getOrigin());
68
+ navigate(url.pathname + url.hash + url.search, {
69
+ overwriteLastHistoryEntry: this.action === "replace",
70
+ }).catch(console.error);
71
+ this.progress = 0;
72
+ this.requestInFlight = true;
73
+ }
74
+ }
75
+ }
76
+ getCachedSnapshot() {
77
+ const snapshot = this.controller.getCachedSnapshotForLocation(this.location);
78
+ if (snapshot) {
79
+ if (this.action == "restore") {
80
+ return snapshot;
81
+ }
82
+ }
83
+ }
84
+ hasCachedSnapshot() {
85
+ return this.getCachedSnapshot() != null;
86
+ }
87
+ loadCachedSnapshot() {
88
+ // no-op since issueRequest calls navigate which handles all of this already
89
+ return;
90
+ }
91
+ loadResponse() {
92
+ this.render(async () => {
93
+ if (!this.renderFn)
94
+ throw new Error("Render details not set before rendering");
95
+ this.cacheSnapshot();
96
+ await this.renderFn();
97
+ this.complete();
98
+ });
99
+ }
100
+ // HTTP request delegate
101
+ /*
102
+ requestStarted() {
103
+ this.recordTimingMetric(TimingMetric.requestStart);
104
+ this.adapter.visitRequestStarted(this);
105
+ }
106
+
107
+ requestProgressed(progress: number) {
108
+ this.progress = progress;
109
+ if (this.adapter.visitRequestProgressed) {
110
+ this.adapter.visitRequestProgressed(this);
111
+ }
112
+ }
113
+
114
+ requestCompletedWithResponse(
115
+ response: string,
116
+ redirectedToLocation?: Location
117
+ ) {
118
+ this.response = response;
119
+ this.redirectedToLocation = redirectedToLocation;
120
+ this.adapter.visitRequestCompleted(this);
121
+ }
122
+
123
+ requestFailedWithStatusCode(statusCode: number, response?: string) {
124
+ this.response = response;
125
+ this.adapter.visitRequestFailedWithStatusCode(this, statusCode);
126
+ }
127
+
128
+ requestFinished() {
129
+ this.recordTimingMetric(TimingMetric.requestEnd);
130
+ this.adapter.visitRequestFinished(this);
131
+ }
132
+ */
133
+ // Instrumentation
134
+ recordTimingMetric(metric) {
135
+ this.timingMetrics[metric] = new Date().getTime();
136
+ }
137
+ getTimingMetrics() {
138
+ return { ...this.timingMetrics };
139
+ }
140
+ // Private
141
+ shouldIssueRequest() {
142
+ return this.action == "restore" ? !this.hasCachedSnapshot() : true;
143
+ }
144
+ cacheSnapshot() {
145
+ if (!this.snapshotCached) {
146
+ this.controller.cacheSnapshot();
147
+ this.snapshotCached = true;
148
+ }
149
+ }
150
+ render(callback) {
151
+ this.cancelRender();
152
+ this.frame = requestAnimationFrame(() => {
153
+ delete this.frame;
154
+ callback.call(this);
155
+ });
156
+ }
157
+ cancelRender() {
158
+ if (this.frame) {
159
+ cancelAnimationFrame(this.frame);
160
+ delete this.frame;
161
+ }
162
+ }
163
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"onRenderClient.d.ts","sourceRoot":"","sources":["../../../proxy/pages/onRenderClient.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAajE,wBAA8B,cAAc,CAC1C,WAAW,EAAE,sBAAsB,iBAgDpC"}
1
+ {"version":3,"file":"onRenderClient.d.ts","sourceRoot":"","sources":["../../../proxy/pages/onRenderClient.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AASjE,wBAA8B,cAAc,CAC1C,WAAW,EAAE,sBAAsB,iBA4DpC"}
@@ -1,45 +1,56 @@
1
1
  import React from "react";
2
2
  import { renderReact } from "../../lib/renderReact.js";
3
3
  import { PageShell } from "../../lib/PageShell.js";
4
- import { turbolinksClickListener } from "../../lib/linkInterceptor.js";
5
- import { dispatchTurbolinks } from "../../lib/dispatchTurbolinks.js";
6
- import { mergeHead } from "../../lib/mergeHead.js";
7
- import { cacheProxiedBody, writeRestorationIdentifier, } from "../../lib/snapshots.js";
8
- import { navigateAnywhere } from "../../lib/navigateAnywhere.js";
9
- import { setupTurbolinks } from "../../lib/turbolinks.js";
10
- setupTurbolinks();
4
+ import { Turbolinks } from "../../lib/turbolinks/index.js";
5
+ import { copyElementAttributes, } from "../../lib/turbolinks/util.js";
6
+ import { getElementAttributes } from "../../lib/getElementAttributes.js";
7
+ Turbolinks.start();
11
8
  export default async function onRenderClient(pageContext) {
12
- if (navigateAnywhere(pageContext.redirectTo))
9
+ if (pageContext.redirectTo) {
10
+ Turbolinks.visit(pageContext.redirectTo);
13
11
  return;
14
- let body;
12
+ }
15
13
  const { layoutProps, layout } = pageContext;
16
- const Layout = pageContext.config.layoutMap[layout];
14
+ const { layoutMap } = pageContext.config;
15
+ if (!layoutMap) {
16
+ throw new Error("layoutMap needs to be defined in config");
17
+ }
18
+ const Layout = layoutMap[layout];
19
+ function render(body) {
20
+ renderReact(React.createElement(PageShell, { key: pageContext.urlOriginal, pageContext: pageContext },
21
+ React.createElement(Layout, { ...layoutProps },
22
+ React.createElement("div", { id: "proxied-body", dangerouslySetInnerHTML: { __html: body } }))), pageContext.isHydration);
23
+ }
24
+ let bodyEl;
17
25
  if (pageContext.isHydration) {
18
26
  // During hydration of initial ssr, body is in dom, not page props (to avoid double-send)
19
- body = document.getElementById("proxied-body").innerHTML;
27
+ bodyEl = document.getElementById("proxied-body");
28
+ render(bodyEl.innerHTML);
20
29
  }
21
30
  else {
22
31
  const { proxySendClient: proxy } = pageContext;
23
32
  if (!proxy) {
24
- console.error("proxy/+onRenderClient did not receive proxySendClient nor is there a cached snapshot");
33
+ console.error("proxy/+onRenderClient did not receive proxySendClient");
25
34
  return;
26
35
  }
27
- cacheProxiedBody();
28
- dispatchTurbolinks("turbolinks:before-render", { newBody: proxy.body });
29
- body = proxy.body;
30
- document.body
31
- .getAttributeNames()
32
- .forEach((n) => document.body.removeAttribute(n));
33
- for (const [name, value] of Object.entries(proxy.bodyAttrs)) {
34
- document.body.setAttribute(name, value);
35
- }
36
- mergeHead(proxy.head);
36
+ const parsed = document.createElement("html");
37
+ parsed.innerHTML = proxy;
38
+ bodyEl = parsed.querySelector("body");
39
+ const headEl = parsed.querySelector("head");
40
+ Turbolinks._vpsOnRenderClient(headEl, true, () => {
41
+ // merge body attributes
42
+ document.body
43
+ .getAttributeNames()
44
+ .forEach((n) => document.body.removeAttribute(n));
45
+ copyElementAttributes(document.body, bodyEl);
46
+ // render body with react
47
+ render(bodyEl.innerHTML);
48
+ });
37
49
  }
38
- writeRestorationIdentifier(pageContext);
39
- // addEventListener de-dupes so we are safe to just blindly call this every time
40
- // non-proxy pages remove the listener
41
- document.addEventListener("click", turbolinksClickListener);
42
- renderReact(React.createElement(PageShell, { key: pageContext.urlOriginal, pageContext: pageContext },
43
- React.createElement(Layout, { ...layoutProps },
44
- React.createElement("div", { id: "proxied-body", dangerouslySetInnerHTML: { __html: body } }))), pageContext.isHydration);
50
+ // cache page context will save it and return it to us during restoration visits
51
+ Turbolinks._vpsCachePageContext({
52
+ layoutProps,
53
+ layout,
54
+ bodyAttrs: getElementAttributes(bodyEl),
55
+ });
45
56
  }
@@ -1 +1 @@
1
- {"version":3,"file":"onRenderHtml.d.ts","sourceRoot":"","sources":["../../../proxy/pages/onRenderHtml.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAGjE,wBAA8B,YAAY,CACxC,WAAW,EAAE,sBAAsB;;;;;;GAsDpC"}
1
+ {"version":3,"file":"onRenderHtml.d.ts","sourceRoot":"","sources":["../../../proxy/pages/onRenderHtml.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAKjE,wBAA8B,YAAY,CACxC,WAAW,EAAE,sBAAsB;;;;;;GAkEpC"}
@@ -2,25 +2,39 @@ import React from "react";
2
2
  import ReactDOMServer from "react-dom/server";
3
3
  import { dangerouslySkipEscape, escapeInject } from "vite-plugin-ssr/server";
4
4
  import { PageShell } from "../../lib/PageShell.js";
5
+ import jsdom from "jsdom";
6
+ import { getElementAttributes } from "../../lib/getElementAttributes.js";
5
7
  export default async function onRenderHtml(pageContext) {
6
8
  if (pageContext.proxy) {
7
- const { proxy: { head, body, bodyAttrs }, layoutProps, layout, } = pageContext;
8
- const Layout = pageContext.config.layoutMap[layout];
9
+ const { proxy, layoutProps, layout } = pageContext;
10
+ const dom = new jsdom.JSDOM(proxy);
11
+ const doc = dom.window.document;
12
+ const bodyEl = doc.querySelector("body");
13
+ const head = doc.querySelector("head");
14
+ if (!bodyEl || !head) {
15
+ throw new Error("Proxy failed");
16
+ }
17
+ const { layoutMap } = pageContext.config;
18
+ if (!layoutMap) {
19
+ throw new Error("layoutMap needs to be defined in config");
20
+ }
21
+ const Layout = layoutMap[layout];
9
22
  if (!Layout)
10
23
  throw new Error(`${layout} layout not found`);
11
24
  const pageHtml = ReactDOMServer.renderToString(React.createElement(PageShell, { pageContext: pageContext },
12
25
  React.createElement(Layout, { ...layoutProps },
13
- React.createElement("div", { id: "proxied-body", dangerouslySetInnerHTML: { __html: body } }))));
26
+ React.createElement("div", { id: "proxied-body", dangerouslySetInnerHTML: { __html: bodyEl.innerHTML } }))));
14
27
  const documentHtml = escapeInject `
15
28
  <!DOCTYPE html>
16
29
  <html>
17
30
  <head>
18
- ${dangerouslySkipEscape(head)}
31
+ ${dangerouslySkipEscape(head.innerHTML)}
19
32
  ${
20
33
  // We need to fire turbolinks:load exactly on DCL, so it must be a blocking head script to catch DCL event.
21
34
  // Vite loads scripts with type="module" so the rest of our code will show up too late.
22
35
  // TODO: figure out how to bundle this better. at least read from a .js file
23
36
  dangerouslySkipEscape(`<script>
37
+ window.Turbolinks = {controller:{restorationIdentifier: ''}};
24
38
  addEventListener("DOMContentLoaded", () => {
25
39
  const event = new Event("turbolinks:load", { bubbles: true, cancelable: true });
26
40
  event.data = {url: window.location.href};
@@ -28,7 +42,7 @@ export default async function onRenderHtml(pageContext) {
28
42
  })
29
43
  </script>`)}
30
44
  </head>
31
- <body ${dangerouslySkipEscape(Object.entries(bodyAttrs)
45
+ <body ${dangerouslySkipEscape(Object.entries(getElementAttributes(bodyEl))
32
46
  .map(([name, value]) => `${name}="${value}"`)
33
47
  .join(" "))}>
34
48
  <div id="page-view">${dangerouslySkipEscape(pageHtml)}</div>
@@ -1,6 +1,8 @@
1
1
  declare const _default: {
2
2
  route: string;
3
3
  Page: string;
4
+ onRenderClient: "import:@alignable/bifrost/proxy/pages/restorationVisit/onRenderClient";
5
+ passToClient: string[];
4
6
  meta: {
5
7
  onBeforeRender: {
6
8
  env: "server-and-client";
@@ -1 +1 @@
1
- {"version":3,"file":"+config.d.ts","sourceRoot":"","sources":["../../../../proxy/pages/restorationVisit/+config.ts"],"names":[],"mappings":";;;;;;;;;AAEA,wBAWgC"}
1
+ {"version":3,"file":"+config.d.ts","sourceRoot":"","sources":["../../../../proxy/pages/restorationVisit/+config.ts"],"names":[],"mappings":";;;;;;;;;;;AAEA,wBAcgC"}
@@ -1,6 +1,9 @@
1
1
  export default {
2
2
  route: "import:@alignable/bifrost/proxy/pages/restorationVisit/route",
3
3
  Page: "import:@alignable/bifrost/proxy/pages/Page",
4
+ onRenderClient: "import:@alignable/bifrost/proxy/pages/restorationVisit/onRenderClient",
5
+ // See onBeforeRoute for how head and body are inserted from Turbolinks snapshot
6
+ passToClient: ["headEl", "bodyEl", "layout", "layoutProps"],
4
7
  meta: {
5
8
  onBeforeRender: {
6
9
  // We tell vite-plugin-ssr to load and execute onBeforeRender()
@@ -0,0 +1,3 @@
1
+ import { PageContextProxyRestorationVisit } from "../../../types/internal.js";
2
+ export default function onRenderClient(pageContext: PageContextProxyRestorationVisit): Promise<void>;
3
+ //# sourceMappingURL=onRenderClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"onRenderClient.d.ts","sourceRoot":"","sources":["../../../../proxy/pages/restorationVisit/onRenderClient.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,gCAAgC,EAAE,MAAM,4BAA4B,CAAC;AAO9E,wBAA8B,cAAc,CAC1C,WAAW,EAAE,gCAAgC,iBA2C9C"}
@@ -0,0 +1,41 @@
1
+ import React from "react";
2
+ import { PageShell } from "../../../lib/PageShell.js";
3
+ import { renderReact } from "../../../lib/renderReact.js";
4
+ import { Turbolinks } from "../../../lib/turbolinks/index.js";
5
+ import { getElementAttributes } from "../../../lib/getElementAttributes.js";
6
+ import { copyElementAttributes } from "../../../lib/turbolinks/util.js";
7
+ export default async function onRenderClient(pageContext) {
8
+ if (pageContext.isHydration) {
9
+ throw new Error("restoration visit should never happen on initial render");
10
+ }
11
+ const { layoutProps, layout, bodyEl, headEl } = pageContext;
12
+ const { layoutMap } = pageContext.config;
13
+ if (!layoutMap) {
14
+ throw new Error("layoutMap needs to be defined in config");
15
+ }
16
+ const Layout = layoutMap[layout];
17
+ function render(body) {
18
+ renderReact(React.createElement(PageShell, { key: pageContext.urlOriginal, pageContext: pageContext },
19
+ React.createElement(Layout, { ...layoutProps },
20
+ React.createElement("div", { id: "proxied-body", dangerouslySetInnerHTML: { __html: body } }))), pageContext.isHydration);
21
+ }
22
+ const proxyBodyEl = bodyEl.querySelector("#proxied-body");
23
+ if (!proxyBodyEl) {
24
+ throw new Error("proxied body not found in cached snapshot");
25
+ }
26
+ Turbolinks._vpsOnRenderClient(headEl, true, () => {
27
+ // merge body attributes
28
+ document.body
29
+ .getAttributeNames()
30
+ .forEach((n) => document.body.removeAttribute(n));
31
+ copyElementAttributes(document.body, bodyEl);
32
+ // render body with react
33
+ render(proxyBodyEl.innerHTML);
34
+ });
35
+ // cache page context will save it and return it to us during restoration visits
36
+ Turbolinks._vpsCachePageContext({
37
+ layoutProps,
38
+ layout,
39
+ bodyAttrs: getElementAttributes(bodyEl),
40
+ });
41
+ }
@@ -0,0 +1,8 @@
1
+ import { PageContextNoProxy } from "../types/internal.js";
2
+ type ConfigOrContext = PageContextNoProxy | PageContextNoProxy["config"];
3
+ /**
4
+ * Get page configs that are definable in config and in runtime via onBeforeRoute. documentProps, for example.
5
+ */
6
+ export declare function getPageContextOrConfig<T extends keyof ConfigOrContext>(pageContext: PageContextNoProxy, prop: T): ConfigOrContext[T];
7
+ export {};
8
+ //# sourceMappingURL=getConfigOrPageContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getConfigOrPageContext.d.ts","sourceRoot":"","sources":["../../renderer/getConfigOrPageContext.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,KAAK,eAAe,GAAG,kBAAkB,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC;AAEzE;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,MAAM,eAAe,EACpE,WAAW,EAAE,kBAAkB,EAC/B,IAAI,EAAE,CAAC,GACN,eAAe,CAAC,CAAC,CAAC,CAEpB"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Get page configs that are definable in config and in runtime via onBeforeRoute. documentProps, for example.
3
+ */
4
+ export function getPageContextOrConfig(pageContext, prop) {
5
+ return pageContext[prop] || pageContext.config[prop];
6
+ }
@@ -1,9 +1,4 @@
1
1
  export default function onBeforeRoute(_pageContext: any): {
2
- pageContext: {
3
- _pageId: string;
4
- layoutProps: Record<string, unknown>;
5
- proxySendClient?: import("../types/internal.js").Proxy | undefined;
6
- layout: string;
7
- };
2
+ pageContext: any;
8
3
  } | undefined;
9
4
  //# sourceMappingURL=onBeforeRoute.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"onBeforeRoute.d.ts","sourceRoot":"","sources":["../../renderer/onBeforeRoute.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,YAAY,EAAE,GAAG;;;;;;;cAYtD"}
1
+ {"version":3,"file":"onBeforeRoute.d.ts","sourceRoot":"","sources":["../../renderer/onBeforeRoute.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,YAAY,EAAE,GAAG;;cA2BtD"}
@@ -1,15 +1,23 @@
1
- import { getSnapshot } from "../lib/snapshots.js";
1
+ // do NOT import turbolinks in this file. It is used on server side.
2
2
  export default function onBeforeRoute(_pageContext) {
3
- const snapshot = getSnapshot();
4
- if (!!snapshot?.proxySendClient) {
5
- return {
6
- pageContext: {
7
- ...snapshot,
8
- _pageId: "/proxy/pages/restorationVisit",
9
- },
10
- };
11
- }
12
- else {
3
+ if (typeof window === "undefined")
13
4
  return undefined;
5
+ const Turbolinks = window.Turbolinks;
6
+ const currentVisit = Turbolinks.controller.currentVisit;
7
+ if (!currentVisit || currentVisit.state === "completed") {
8
+ // old/nonexistent currentVisit means VPS is doing history navigation. Ideally we might turn off VPS' onpopstate listener.
9
+ const snapshot = Turbolinks.controller.getCachedSnapshotForLocation(window.location.href);
10
+ Turbolinks.controller.historyPoppedToLocationWithRestorationIdentifier(window.location.href, "");
11
+ if (!!snapshot) {
12
+ return {
13
+ pageContext: {
14
+ ...snapshot.pageContext,
15
+ bodyEl: snapshot.bodyEl,
16
+ headEl: snapshot.headEl,
17
+ _pageId: "/proxy/pages/restorationVisit",
18
+ },
19
+ };
20
+ }
14
21
  }
22
+ return undefined;
15
23
  }
@@ -1 +1 @@
1
- {"version":3,"file":"onRenderClient.d.ts","sourceRoot":"","sources":["../../renderer/onRenderClient.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAWhE,wBAA8B,cAAc,CAC1C,WAAW,EAAE,wBAAwB,iBAmCtC"}
1
+ {"version":3,"file":"onRenderClient.d.ts","sourceRoot":"","sources":["../../renderer/onRenderClient.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAahE,wBAA8B,cAAc,CAC1C,WAAW,EAAE,wBAAwB,iBA6CtC"}
@@ -1,36 +1,43 @@
1
1
  import React from "react";
2
2
  import { renderReact } from "../lib/renderReact.js";
3
3
  import { PageShell } from "../lib/PageShell.js";
4
- import { turbolinksClickListener } from "../lib/linkInterceptor.js";
5
- import { getDocumentProps } from "./getDocumentProps.js";
6
- import { cacheProxiedBody } from "../lib/snapshots.js";
7
- import { navigateAnywhere } from "../lib/navigateAnywhere.js";
8
- import { setupTurbolinks } from "../lib/turbolinks.js";
9
- import { formatMetaObject } from "./utils/formatMetaObject.js";
10
- setupTurbolinks();
4
+ import { Turbolinks } from "../lib/turbolinks/index.js";
5
+ import { documentPropsToReact } from "./utils/buildHead.js";
6
+ import { getPageContextOrConfig } from "./getConfigOrPageContext.js";
7
+ import { createRoot } from "react-dom/client";
8
+ Turbolinks.start();
9
+ const PassThruLayout = ({ children, }) => React.createElement(React.Fragment, null, children);
11
10
  export default async function onRenderClient(pageContext) {
12
- if (navigateAnywhere(pageContext.redirectTo))
11
+ if (pageContext.redirectTo) {
12
+ Turbolinks.visit(pageContext.redirectTo);
13
13
  return;
14
+ }
14
15
  const { Page, pageProps } = pageContext;
15
- const { Layout, layoutProps } = pageContext.config;
16
+ const { Layout = PassThruLayout } = pageContext.config;
17
+ const layoutProps = getPageContextOrConfig(pageContext, "layoutProps") || {};
16
18
  if (!Page)
17
19
  throw new Error("Client-side render() hook expects Page to be exported");
18
- if (!Layout)
19
- throw new Error("Client-side render() hook expects Layout to be exported");
20
- document.removeEventListener("click", turbolinksClickListener);
21
- cacheProxiedBody();
22
- const { title = "", description = "", viewport } = getDocumentProps(pageContext);
23
- document.title = title;
24
- document.head
25
- .querySelector("meta[name='description']")
26
- ?.setAttribute("content", description);
27
- if (viewport) {
28
- document.head
29
- .querySelector("meta[name='viewport']")
30
- ?.setAttribute("content", formatMetaObject(viewport));
31
- }
32
20
  const page = (React.createElement(PageShell, { pageContext: pageContext },
33
21
  React.createElement(Layout, { ...layoutProps },
34
22
  React.createElement(Page, { ...pageProps }))));
35
- renderReact(page, pageContext.isHydration);
23
+ if (pageContext.isHydration) {
24
+ // During hydration of initial ssr, body is in dom, not page props (to avoid double-send)
25
+ renderReact(page, pageContext.isHydration);
26
+ }
27
+ else {
28
+ const head = document.createElement("head");
29
+ createRoot(head).render(documentPropsToReact(getPageContextOrConfig(pageContext, "documentProps") || {}));
30
+ pageContext.config.scripts?.forEach((s) => {
31
+ head.insertAdjacentHTML("beforeend", s);
32
+ });
33
+ requestAnimationFrame(() => {
34
+ Turbolinks._vpsOnRenderClient(head, false, () => {
35
+ // clear anything on body
36
+ document.body
37
+ .getAttributeNames()
38
+ .forEach((n) => document.body.removeAttribute(n));
39
+ renderReact(page, pageContext.isHydration);
40
+ });
41
+ });
42
+ }
36
43
  }
@@ -1 +1 @@
1
- {"version":3,"file":"onRenderHtml.d.ts","sourceRoot":"","sources":["../../renderer/onRenderHtml.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAIhE,wBAA8B,YAAY,CACxC,WAAW,EAAE,wBAAwB;;;GAmDtC"}
1
+ {"version":3,"file":"onRenderHtml.d.ts","sourceRoot":"","sources":["../../renderer/onRenderHtml.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAIhE,wBAA8B,YAAY,CACxC,WAAW,EAAE,wBAAwB;;;GAoDtC"}
@@ -2,11 +2,12 @@ import ReactDOMServer from "react-dom/server";
2
2
  import React from "react";
3
3
  import { PageShell } from "../lib/PageShell.js";
4
4
  import { escapeInject, dangerouslySkipEscape } from "vite-plugin-ssr/server";
5
- import { getDocumentProps } from "./getDocumentProps.js";
6
- import { formatMetaObject } from "./utils/formatMetaObject.js";
5
+ import { documentPropsToReact } from "./utils/buildHead.js";
6
+ import { getPageContextOrConfig } from "./getConfigOrPageContext.js";
7
7
  export default async function onRenderHtml(pageContext) {
8
8
  const { Page, pageProps } = pageContext;
9
- const { Layout, layoutProps } = pageContext.config;
9
+ const { Layout } = pageContext.config;
10
+ const layoutProps = getPageContextOrConfig(pageContext, "layoutProps") || {};
10
11
  if (!Page)
11
12
  throw new Error("Server-side render() hook expects Page to be exported");
12
13
  if (!Layout)
@@ -14,24 +15,20 @@ export default async function onRenderHtml(pageContext) {
14
15
  const pageHtml = ReactDOMServer.renderToString(React.createElement(PageShell, { pageContext: pageContext },
15
16
  React.createElement(Layout, { ...layoutProps },
16
17
  React.createElement(Page, { ...pageProps }))));
17
- const { googleAnalytics, osano } = pageContext.config.scripts;
18
- const googleAnalyticsTag = dangerouslySkipEscape(googleAnalytics);
19
- const osanoTag = dangerouslySkipEscape(osano);
20
- // // See https://vite-plugin-ssr.com/head
21
- const { title = "", description = "", viewport } = getDocumentProps(pageContext);
22
- if (!title) {
23
- console.warn(`No title set for ${pageContext.urlOriginal}!`);
24
- }
25
- const viewportTag = !viewport ? "" : escapeInject `<meta content="${formatMetaObject(viewport)}" name="viewport">`;
18
+ const headHtml = ReactDOMServer.renderToString(documentPropsToReact(getPageContextOrConfig(pageContext, "documentProps") || {}));
26
19
  const documentHtml = escapeInject `<!DOCTYPE html>
27
20
  <html lang="en">
28
21
  <head>
29
- ${googleAnalyticsTag}
30
- ${osanoTag}
31
- <title>${title}</title>
32
- <meta name="title" property="og:title" content="${title}"/>
33
- <meta name="description" content="${description}"/>
34
- ${viewportTag}
22
+ ${dangerouslySkipEscape(headHtml)}
23
+ ${dangerouslySkipEscape(Object.values(pageContext.config.scripts || {}).join(""))}
24
+ ${dangerouslySkipEscape(`<script>
25
+ window.Turbolinks = {controller:{restorationIdentifier: ''}};
26
+ addEventListener("DOMContentLoaded", () => {
27
+ const event = new Event("turbolinks:load", { bubbles: true, cancelable: true });
28
+ event.data = {url: window.location.href};
29
+ document.dispatchEvent(event);
30
+ })
31
+ </script>`)}
35
32
  </head>
36
33
  <body>
37
34
  <div id="page-view">${dangerouslySkipEscape(pageHtml)}</div>
@@ -0,0 +1,4 @@
1
+ import React from "react";
2
+ import { DocumentProps } from "../../types/internal";
3
+ export declare function documentPropsToReact({ title, description, viewport, }: DocumentProps): React.ReactElement;
4
+ //# sourceMappingURL=buildHead.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildHead.d.ts","sourceRoot":"","sources":["../../../renderer/utils/buildHead.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,wBAAgB,oBAAoB,CAAC,EACnC,KAAU,EACV,WAAgB,EAChB,QAAa,GACd,EAAE,aAAa,GAAG,KAAK,CAAC,YAAY,CAcpC"}
@@ -0,0 +1,10 @@
1
+ import React from "react";
2
+ export function documentPropsToReact({ title = "", description = "", viewport = {}, }) {
3
+ return (React.createElement(React.Fragment, null,
4
+ React.createElement("title", null, title),
5
+ React.createElement("meta", { name: "title", property: "og:title", content: title }),
6
+ React.createElement("meta", { name: "description", content: description }),
7
+ React.createElement("meta", { name: "viewport", content: Object.entries(viewport)
8
+ .map((e) => e.join("="))
9
+ .join(", ") })));
10
+ }