@uniweb/runtime 1.3.3 → 1.3.5
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/dist/ssr.js.map +1 -1
- package/package.json +2 -2
- package/src/carry-animations.js +50 -0
- package/src/index.jsx +3 -0
- package/src/wire-foundation.js +13 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/runtime",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.5",
|
|
4
4
|
"description": "Minimal runtime for loading Uniweb foundations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"esbuild": "^0.21.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.27.0 || ^0.28.0",
|
|
45
45
|
"vite": "^7.3.1",
|
|
46
46
|
"vitest": "^4.1.7",
|
|
47
|
-
"@uniweb/build": "1.13.
|
|
47
|
+
"@uniweb/build": "1.13.6"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"react": "^19.0.0",
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A page's CSS animations carry on across the browser's first render.
|
|
3
|
+
*
|
|
4
|
+
* ⭐ The browser renders over a prerendered page rather than hydrating it (`createRoot`, `index.jsx`),
|
|
5
|
+
* and a CSS animation's clock belongs to its element. So the commit that replaces the page restarts
|
|
6
|
+
* every animation in it, and a page's first commit usually lands while its entrance is still running:
|
|
7
|
+
* a hero already on screen would drop back to invisible and play again. `uniweb dev` never prerenders,
|
|
8
|
+
* so a foundation's author never sees it.
|
|
9
|
+
*
|
|
10
|
+
* Each animation of the new page takes as its start the moment the old page was first painted, which
|
|
11
|
+
* is when its twin started, so it goes on from where its twin was. The observer fires after the commit
|
|
12
|
+
* and before the browser paints, so no frame shows a restart. It disconnects at that first commit, so
|
|
13
|
+
* a later navigation is left as it was.
|
|
14
|
+
*
|
|
15
|
+
* ⭐ The time of first paint rather than the old animations' own start times, read before the commit:
|
|
16
|
+
* an animation that has ended with no fill is gone from `getAnimations()`, and its twin would play
|
|
17
|
+
* again.
|
|
18
|
+
*
|
|
19
|
+
* ⛔ Only a running animation on the document's timeline:
|
|
20
|
+
* - a paused one is waiting for something, like a reveal that starts when its section scrolls into
|
|
21
|
+
* view, and setting a start time would start it;
|
|
22
|
+
* - a scroll-driven one has a timeline of its own, whose time is no moment on this clock.
|
|
23
|
+
*
|
|
24
|
+
* It reaches CSS animations alone. One a component starts from JavaScript when it mounts never ran on
|
|
25
|
+
* the prerendered page: it starts at boot, over whatever that page has shown by then.
|
|
26
|
+
*
|
|
27
|
+
* Nothing to carry: an empty root (`uniweb dev`, a page served without a prerender), and a page not
|
|
28
|
+
* painted yet, whose first render is the first thing a visitor sees. That can include a page painted
|
|
29
|
+
* a frame ago, which the browser has not reported yet: its animations have hardly begun.
|
|
30
|
+
*
|
|
31
|
+
* @param {Element} root - The element React is about to render into.
|
|
32
|
+
*/
|
|
33
|
+
export function carryAnimations(root) {
|
|
34
|
+
if (!root.firstChild || !document.getAnimations) return
|
|
35
|
+
new MutationObserver((_, watch) => {
|
|
36
|
+
watch.disconnect()
|
|
37
|
+
const painted = performance.getEntriesByName('first-contentful-paint')[0]?.startTime
|
|
38
|
+
if (painted == null) return
|
|
39
|
+
for (const animation of document.getAnimations()) {
|
|
40
|
+
if (
|
|
41
|
+
animation.animationName &&
|
|
42
|
+
animation.playState === 'running' &&
|
|
43
|
+
animation.timeline === document.timeline &&
|
|
44
|
+
root.contains(animation.effect?.target)
|
|
45
|
+
) {
|
|
46
|
+
animation.startTime = painted
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}).observe(root, { childList: true })
|
|
50
|
+
}
|
package/src/index.jsx
CHANGED
|
@@ -12,6 +12,7 @@ import { initUniweb, decodeData } from './setup.js'
|
|
|
12
12
|
import { wireTracker } from './wire-foundation.js'
|
|
13
13
|
import { loadFoundation, loadExtensions } from './foundation-loader.js'
|
|
14
14
|
import { initAppearance } from './appearance.js'
|
|
15
|
+
import { carryAnimations } from './carry-animations.js'
|
|
15
16
|
import RuntimeProvider from './RuntimeProvider.jsx'
|
|
16
17
|
|
|
17
18
|
/**
|
|
@@ -146,6 +147,8 @@ async function initRuntime(foundationSource, options = {}) {
|
|
|
146
147
|
|
|
147
148
|
const app = <RuntimeProvider basename={routerBasename} development={development} />
|
|
148
149
|
|
|
150
|
+
// Armed before the render it watches for: the one that replaces a prerendered page.
|
|
151
|
+
carryAnimations(container)
|
|
149
152
|
createRoot(container).render(app)
|
|
150
153
|
|
|
151
154
|
// Log success
|
package/src/wire-foundation.js
CHANGED
|
@@ -17,26 +17,31 @@
|
|
|
17
17
|
* the DOM, the per-page render loop — stay in the per-environment
|
|
18
18
|
* entries; these helpers cover only the environment-agnostic L2 work.
|
|
19
19
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
20
|
+
* What this file keeps out of the SPA bundle is `react-dom/server`, not
|
|
21
|
+
* React: `FallbackRef` below is `React.createElement` alone. `setup.js`
|
|
22
|
+
* pulls `wire-foundation.js` directly, so this file must NOT transitively
|
|
22
23
|
* pull `ssr-renderer.js` (which imports `react-dom/server`). The L2
|
|
23
24
|
* helpers therefore live here, while the L3-composing
|
|
24
25
|
* `initPrerenderForLocale` lives in `ssr-renderer.js`.
|
|
25
26
|
*
|
|
26
27
|
* Adding a new framework-level capability:
|
|
27
|
-
* 1.
|
|
28
|
-
*
|
|
28
|
+
* 1. Name it in `extractFoundationRuntime`'s lists (`@uniweb/build`'s
|
|
29
|
+
* `src/runtime-schema.js`): the built entry carries only what they
|
|
30
|
+
* name, so anything else never reaches this file.
|
|
31
|
+
* 2. Read the foundation declaration via `foundation.default.capabilities.<name>`.
|
|
32
|
+
* 3. Apply it to the uniweb singleton (set a slot, call a build hook,
|
|
29
33
|
* register something on `activeWebsite`).
|
|
30
|
-
*
|
|
34
|
+
* 4. Provide a runtime fallback if the capability is one foundations
|
|
31
35
|
* may legitimately not declare (see `FallbackRef`).
|
|
32
36
|
*
|
|
33
37
|
* Foundation export shape contract: the runtime always loads the
|
|
34
38
|
* **built** foundation artifact (`dist/entry.js`) via
|
|
35
39
|
* `loadFoundation()` in `foundation-loader.js`, which does `import(url)`
|
|
36
40
|
* and returns a module namespace. The build pipeline
|
|
37
|
-
* (`@uniweb/build`'s `src/generate-entry.js`)
|
|
38
|
-
*
|
|
39
|
-
* sees a single canonical shape
|
|
41
|
+
* (`@uniweb/build`'s `src/generate-entry.js`) writes what the runtime
|
|
42
|
+
* reads of the foundation's source default export into
|
|
43
|
+
* `default.capabilities.*`, so the runtime sees a single canonical shape
|
|
44
|
+
* with no need for fallback chains. This
|
|
40
45
|
* differs from `@uniweb/press` / `@uniweb/unipress`,
|
|
41
46
|
* which DO need to handle a second shape because they're sometimes
|
|
42
47
|
* called from inside a foundation bundle (where the foundation imports
|