Compare commits

...

288 Commits

Author SHA1 Message Date
Michael Brevard f8c2468767
Merge f054daccf8 into e644960794 2024-09-20 14:28:51 +01:00
tbitw2549 f054daccf8 docs: add conditional rendering and state caveats 2024-09-15 12:59:54 +03:00
tbitw2549 cd7cc48ca3 Merge branch 'patch-21' of https://github.com/GalacticHypernova/nuxt into patch-21 2024-09-15 09:20:11 +03:00
tbitw2549 59392c7f7e chore: remove ts-expect-error 2024-09-15 09:20:06 +03:00
Michael Brevard 100664e149
docs: typo 2024-09-15 07:53:32 +03:00
Michael Brevard 6945776b6c
docs: rephrase lazy time misuse case 2024-09-15 07:52:34 +03:00
Michael Brevard 6c741f7cad
Merge branch 'main' into patch-21 2024-09-15 07:44:08 +03:00
Michael Brevard 0bed244f5c
docs: reference pure conditional hydration as a LazyIf option 2024-09-15 07:03:24 +03:00
Michael Brevard bf131a2173
docs: hydration event callback example 2024-09-14 21:54:02 +03:00
tbitw2549 c0844b902e feat: add hydration emit for callbacks 2024-09-14 21:31:16 +03:00
tbitw2549 373e223268 fix: merge attrs at render
This fixes a reactivity issue and adds an accompanying test case
2024-09-14 20:43:23 +03:00
tbitw2549 70b7739739 fix: remove ref 2024-09-14 18:47:19 +03:00
tbitw2549 c39bad01e2 refactor: use props in stead of attrs 2024-09-14 18:44:25 +03:00
Michael Brevard 1ad62a16f5
docs: add custom strategies example via LazyIf 2024-09-14 16:57:14 +03:00
Michael Brevard 6f1757700c
docs: rephrase caveats and best practices 2024-09-14 16:40:56 +03:00
tbitw2549 fe9b2b96f5 feat: time and promise based delayed hydration 2024-09-14 14:01:50 +03:00
Michael Brevard 86384794e8
Merge branch 'main' into patch-21 2024-09-14 09:11:54 +03:00
Michael Brevard 3ced1449e6
chore: remove space 2024-09-13 15:04:38 +03:00
Michael Brevard 1429c11394
Merge branch 'main' into patch-21 2024-09-13 14:09:03 +03:00
Michael Brevard 2d2074a927
chore(test): rerun tests 2024-09-13 13:30:56 +03:00
Michael Brevard e068873e6e
test: add grace period for intersection observer 2024-09-13 13:01:03 +03:00
Michael Brevard 4e469b7de5
test: adjust test 2024-09-13 12:36:52 +03:00
tbitw2549 87cd1048ca Merge branch 'patch-21' of https://github.com/GalacticHypernova/nuxt into patch-21 2024-09-13 12:22:02 +03:00
tbitw2549 311ac8ad93 fix: return imports 2024-09-13 12:21:36 +03:00
Michael Brevard 5cc422b820
Merge branch 'main' into patch-21 2024-09-13 12:11:14 +03:00
tbitw2549 8b95e4e05a chore: fix media post conflict 2024-09-13 12:07:47 +03:00
tbitw2549 a9c76a405c Merge branch 'patch-21' of https://github.com/GalacticHypernova/nuxt into patch-21 2024-09-13 12:07:09 +03:00
tbitw2549 fb6d4a518a refactor: avoid conflicting renders
This refactors SSR handling with the existing approach, given the SSR limitation of establishing rendering separation.

This also simplifies SSR renders using the same data and adds early returns wherever possible to internally minimize delayed hydration dependency
2024-09-13 12:04:49 +03:00
Daniel Roe c32834fc2e
chore: type 2024-09-11 21:09:33 +01:00
Daniel Roe 07c7f2ca92
chore: lint 2024-09-11 21:07:25 +01:00
Daniel Roe 889a5642fb
Merge remote-tracking branch 'origin/main' into patch-21 2024-09-11 21:06:10 +01:00
tbitw2549 74231878ea chore: update components with documentation 2024-08-30 00:04:39 +03:00
tbitw2549 67d3cadcb2 wip: fix never hydrated, prepare tests 2024-08-29 11:58:48 +03:00
tbitw2549 124d1e2b49 test: immediately-available visible elem 2024-08-28 16:26:59 +03:00
tbitw2549 5e973777b7 refactor: avoid function chain 2024-08-28 16:26:35 +03:00
tbitw2549 f0c492a657 fix: default empty media query, add new templates 2024-08-25 14:03:00 +03:00
tbitw2549 e304d6c367 feat: support condition, media query, and never
This adds 3 more types of hydration to cover most of the use cases.
2024-08-25 13:53:29 +03:00
Michael Brevard 7fc29e1a56
test: refactor test without responses 2024-08-20 15:19:36 +03:00
Michael Brevard 1794ae33f1
test: begin waiting for response before events 2024-08-20 14:51:44 +03:00
autofix-ci[bot] 62fb11f253
[autofix.ci] apply automated fixes 2024-08-20 11:19:22 +00:00
Daniel Roe 31779d2371
Merge remote-tracking branch 'origin/main' into patch-21 2024-08-20 12:12:14 +01:00
Michael Brevard 309726060c
docs: fix createIdleLoader usage example 2024-08-20 14:03:00 +03:00
Michael Brevard 1a1fe463df
chore: remove commented out import 2024-08-20 13:54:31 +03:00
tbitw2549 60a34552cc style: refactor to reusable function 2024-08-16 23:51:42 +03:00
tbitw2549 47e2443a37 refactor: reuse the generated dynamic import 2024-08-16 18:50:45 +03:00
tbitw2549 784c34fc91 chore: revert export of useObserver 2024-08-16 18:14:14 +03:00
tbitw2549 f3f594f7f9 chore: rebase to vue 3.5 branch
rebases to #28285
2024-08-16 17:54:41 +03:00
Michael Brevard 3426fca610
Merge branch 'main' into patch-21 2024-08-13 23:13:16 +03:00
tbitw2549 b64dca110e refactor: use vue native hydration strategies 2024-08-13 19:18:03 +03:00
tbitw2549 cafc328ef2 fix: specify correct versions for non-changed deps 2024-08-13 18:37:24 +03:00
Michael Brevard 8d066205c2
Merge branch 'main' into patch-21 2024-08-13 18:22:59 +03:00
Michael Brevard b9b25a6e41
Merge branch 'main' into patch-21 2024-08-05 20:29:29 +03:00
Michael Brevard 2df3351048
Merge branch 'main' into patch-21 2024-07-12 14:01:28 +03:00
Michael Brevard 52789fa47a
fix: revert to map 2024-06-29 12:39:25 +03:00
Michael Brevard 3d88fce4c7
chore: use a WeakMap 2024-06-29 09:31:21 +03:00
Michael Brevard 0e4e92b716
test: try through hydration 2024-06-28 16:32:05 +03:00
Michael Brevard 6803f61667
chore: debug timeout tests 2024-06-28 16:05:00 +03:00
Michael Brevard 9060907f3f
chore: no async 2024-06-28 13:46:54 +03:00
Michael Brevard b28ee7896d
test: refactor custom component test 2024-06-28 13:39:00 +03:00
Michael Brevard e7621cb222
chore: attempt to close the page 2024-06-27 20:32:02 +03:00
Michael Brevard 97fd06e4ff
Merge branch 'main' into patch-21 2024-06-27 17:40:56 +03:00
tbitw2549 70789a0631 chore: rename loader to hydrate 2024-06-22 00:20:01 +03:00
autofix-ci[bot] 9456f3f12e
[autofix.ci] apply automated fixes 2024-06-21 15:15:04 +00:00
Michael Brevard a80843f9d2
Merge branch 'main' into patch-21 2024-06-21 18:12:51 +03:00
Michael Brevard 9cbdbc7814
chore: better wording 2024-06-20 09:47:25 +03:00
Michael Brevard d80c5562b7
docs: provide clearer separation between Lazy and DH 2024-06-20 09:45:13 +03:00
Michael Brevard f47b0d6c4e
chore: rename variable 2024-06-20 09:37:55 +03:00
Michael Brevard 0919a82a75
Merge branch 'main' into patch-21 2024-06-19 16:28:41 +03:00
Michael Brevard 5cb4e41553
docs: rephrase 2024-06-19 12:08:39 +03:00
Michael Brevard 0046959bf3
chore: remove createVNode import 2024-06-18 12:49:22 +03:00
Michael Brevard 0f79d798e9
perf: avoid wrapping client vnode in dynamic vnode 2024-06-18 12:45:23 +03:00
Michael Brevard b36d0b484e
docs: fix space and link 2024-06-18 00:24:15 +03:00
Michael Brevard bfb875e1fe
docs: remove warning, add modifier conflicting component info 2024-06-18 00:22:48 +03:00
Michael Brevard 66c831bd41
test: add fake delayed hydration comp 2024-06-17 23:45:53 +03:00
Michael Brevard 8344e87dc8
fix: use correct extension 2024-06-17 23:45:00 +03:00
Michael Brevard 0585f8aba0
Create EventView.server.vue 2024-06-17 23:44:30 +03:00
Michael Brevard b21f88e5da
Create EventView.client.ts 2024-06-17 23:43:30 +03:00
Michael Brevard 24fdcda7d4
test: add component name modifier conflict test 2024-06-17 23:40:30 +03:00
Michael Brevard 51f20645d9
fix: allow for components to begin with the names of modifiers 2024-06-17 23:00:16 +03:00
Michael Brevard cd7e85842c
Merge branch 'main' into patch-21 2024-06-17 17:20:13 +03:00
Michael Brevard 38fabb56e6
docs: update component prefix for visible 2024-06-17 17:19:35 +03:00
Michael Brevard 7025412d35
docs: update component prefix for event 2024-06-17 17:19:20 +03:00
Michael Brevard 022b836cdc
docs: update experimental config key 2024-06-17 17:18:49 +03:00
Michael Brevard b749509f9d
Merge branch 'main' into patch-21 2024-06-16 23:20:42 +03:00
Michael Brevard d54e58dc44
test: add default override test 2024-06-16 19:51:37 +03:00
Michael Brevard 794f46114f
chore: retry tests 2024-06-16 17:09:49 +03:00
Michael Brevard 5124ebe4d1
tests: use new delayedHydration key 2024-06-16 17:05:40 +03:00
Michael Brevard 768ebe4591
chore: delayed component cleanup 2024-06-16 16:50:35 +03:00
Michael Brevard 3f11337999
chore: loader cleanup 2024-06-16 16:49:43 +03:00
Michael Brevard 36ffef9fa5
fix: use lowercase boolean 2024-06-16 16:48:40 +03:00
Michael Brevard e2416801e4
chore: schema cleanup 2024-06-16 16:48:06 +03:00
Michael Brevard 7f51c8a133
feat: add intellisense for loaders 2024-06-16 16:11:58 +03:00
Michael Brevard 6cb16f41cf
feat: add delayed-hydration components to template 2024-06-16 12:53:14 +03:00
Michael Brevard ee6cd61e2d
Merge branch 'main' into patch-21 2024-06-16 12:23:21 +03:00
Michael Brevard 1fbe165da5
chore: begin response logging before click 2024-06-16 11:11:09 +03:00
Michael Brevard 7bdfc6cc86
fix: use custom loader element 2024-06-16 10:43:05 +03:00
Michael Brevard 86e88052fc
fix: update test 2024-06-16 10:20:51 +03:00
autofix-ci[bot] 34d13cbe0e
[autofix.ci] apply automated fixes 2024-06-16 07:11:03 +00:00
Michael Brevard 945a225d78
fix: autofix 2024-06-16 10:08:36 +03:00
Michael Brevard b72e45ef9e
fix: parentheses 2024-06-16 10:04:51 +03:00
Michael Brevard 751e5f8194
tests: add tests for delayed hydration triggers 2024-06-16 10:02:27 +03:00
Michael Brevard f8f752fc74
chore: prepare tests for custom trigger delayed hydration 2024-06-16 09:56:11 +03:00
Michael Brevard 587a5dbce0
Merge branch 'main' into patch-21 2024-06-15 17:51:59 +03:00
Michael Brevard 3727a435b5
fix: avoid setting another mounted if instance is registered 2024-06-15 17:04:19 +03:00
autofix-ci[bot] 5d5432cba7
[autofix.ci] apply automated fixes 2024-06-15 13:38:28 +00:00
Michael Brevard aa3df0bc6f
chore: autofix 2024-06-15 16:35:57 +03:00
Michael Brevard b8c36774f5
tests: refactor tests to use waitForResponse 2024-06-15 16:32:09 +03:00
Michael Brevard 76a267de4d
chore: attempt hard coded await for event 2024-06-15 14:18:46 +03:00
autofix-ci[bot] cf9a905993
[autofix.ci] apply automated fixes 2024-06-15 05:53:03 +00:00
Michael Brevard 0ab38fcd09
Merge branch 'main' into patch-21 2024-06-15 08:50:42 +03:00
Michael Brevard e12d4b13ee
fix: assert bounding box is not null 2024-06-15 08:25:03 +03:00
Michael Brevard 5390cae241
fix: await bounding vox 2024-06-15 01:26:02 +03:00
Michael Brevard d5bd5f8392
types: add stricter types 2024-06-15 01:24:36 +03:00
tbitw2549 76f2f5dc4a docs: add reference to event-based lazy loading 2024-06-15 01:10:20 +03:00
tbitw2549 06096dcc0c feat: add event-based lazy loading 2024-06-15 01:06:47 +03:00
Michael Brevard e3a0242048
chore: delete unused delayed hydration server plugin 2024-06-15 00:00:51 +03:00
tbitw2549 9ba1682eba fix: add space 2024-06-14 23:32:13 +03:00
tbitw2549 0c8672bc62 Merge branch 'patch-21' of https://github.com/GalacticHypernova/nuxt into patch-21 2024-06-14 23:27:41 +03:00
tbitw2549 1cb243ed08 feat: auto-import loaders and update docs 2024-06-14 23:27:22 +03:00
autofix-ci[bot] d556ac26f7
[autofix.ci] apply automated fixes 2024-06-14 19:45:02 +00:00
tbitw2549 4a1c1f5969 wip: add hardcoded await 2024-06-14 22:42:26 +03:00
tbitw2549 fa0bdc0e5b chore: revert for code style 2024-06-14 20:23:40 +03:00
tbitw2549 068d40a283 chore: attempt to provide default non-null value 2024-06-14 20:17:09 +03:00
tbitw2549 52e55ee044 Merge branch 'patch-21' of https://github.com/GalacticHypernova/nuxt into patch-21 2024-06-14 19:55:33 +03:00
tbitw2549 64179576b5 fix: provide strict typing for intersection 2024-06-14 19:55:14 +03:00
autofix-ci[bot] 85c1ac8266
[autofix.ci] apply automated fixes 2024-06-14 16:51:55 +00:00
tbitw2549 1c883b983e chore: return since annotation 2024-06-14 19:46:41 +03:00
tbitw2549 d6e475ec0d Merge branch 'patch-21' of https://github.com/GalacticHypernova/nuxt into patch-21 2024-06-14 19:45:38 +03:00
tbitw2549 9cec31ea49 chore: provide default options 2024-06-14 19:44:13 +03:00
autofix-ci[bot] d520d347f2
[autofix.ci] apply automated fixes 2024-06-14 16:24:13 +00:00
tbitw2549 dbdb3fe6a1 fix: remove unused type import 2024-06-14 19:21:26 +03:00
tbitw2549 712f81390d chore: refactor for consistency 2024-06-14 19:17:22 +03:00
tbitw2549 3602fe1947 Merge branch 'patch-21' of https://github.com/GalacticHypernova/nuxt into patch-21 2024-06-14 18:49:18 +03:00
tbitw2549 2021eac17b fix: wait for nuxt to be ready 2024-06-14 18:48:57 +03:00
autofix-ci[bot] c2e22c60e9
[autofix.ci] apply automated fixes 2024-06-14 15:41:51 +00:00
Michael Brevard 0ead860117
Merge branch 'main' into patch-21 2024-06-14 18:39:18 +03:00
tbitw2549 8c522836ed wip: review work 2024-06-14 18:31:21 +03:00
Michael Brevard 23d0b8e4c8
feat: add delayed-hydration loaders for fine-grained control 2024-06-14 16:06:54 +03:00
Michael Brevard c14580d8c1
chore: remove div in server render 2024-06-13 01:47:45 +03:00
Michael Brevard 0b769781c5
chore: remove comment and unnecessary divs 2024-06-13 01:11:17 +03:00
Michael Brevard da1db599cc
Merge branch 'main' into patch-21 2024-06-12 18:33:39 +03:00
Michael Brevard 0732872d74
Merge branch 'main' into patch-21 2024-06-12 14:08:25 +03:00
Michael Brevard 6e6832fc3e
Merge branch 'main' into patch-21 2024-06-11 19:20:31 +03:00
Michael Brevard 09489b9d7a
Merge branch 'main' into patch-21 2024-06-10 13:21:45 +03:00
Michael Brevard 10e9d00fa4
fix: assume network has already fetches 2024-06-10 11:57:14 +03:00
Michael Brevard 6be28279f1
docs: fix case 2024-06-10 11:56:41 +03:00
Michael Brevard 0792f95b86
chore: retry network evaluation 2024-06-10 11:41:33 +03:00
Michael Brevard fb3397701e
chore: revert test 2024-06-10 11:24:16 +03:00
Michael Brevard 2fd3aa422f
Merge branch 'main' into patch-21 2024-06-10 11:01:55 +03:00
Michael Brevard 09f341c3ef
chore: revert test page 2024-06-10 10:41:49 +03:00
Michael Brevard 2089f745a8
test: refactor test 2024-06-10 10:29:32 +03:00
Michael Brevard 7ad6b06809
chore: retry tests 2024-06-10 00:45:03 +03:00
Michael Brevard 1039735d45
chore: await another networkidle 2024-06-10 00:41:55 +03:00
Michael Brevard 2b765e0b88
chore: refactor the test 2024-06-10 00:30:13 +03:00
Michael Brevard 53c8bb92fe
chore: attempt to check through html 2024-06-10 00:20:49 +03:00
Michael Brevard ed16dc4a7d
chore: extract import 2024-06-09 23:45:22 +03:00
Michael Brevard b0f4f64d27
chore: retry without promise.all 2024-06-09 23:43:41 +03:00
Michael Brevard c8cbcfc56e
chore: use Promise.all to try and be faster than initial idle time 2024-06-09 23:36:04 +03:00
Michael Brevard 53d73baa48
test: attempt network idle test 2024-06-09 23:34:04 +03:00
autofix-ci[bot] 79a805fa8c
[autofix.ci] apply automated fixes 2024-06-09 20:08:02 +00:00
Michael Brevard b9e7354241
chore: fix network comp display 2024-06-09 23:05:34 +03:00
Michael Brevard ee7d8fa20e
fix: evaluate scroll after initial check 2024-06-09 22:25:06 +03:00
Michael Brevard b87a70c825
chore: attempt a higher div 2024-06-09 21:07:39 +03:00
Michael Brevard 86e7c44ca5
Merge branch 'main' into patch-21 2024-06-09 18:56:50 +03:00
Michael Brevard 4442d5f294
Merge branch 'main' into patch-21 2024-06-08 17:15:54 +03:00
Michael Brevard a80536d4c0
Merge branch 'main' into patch-21 2024-06-07 23:26:45 +03:00
Michael Brevard 6fa4dd2807
Merge branch 'main' into patch-21 2024-06-07 19:47:33 +03:00
Michael Brevard 6e4f05d31d
Merge branch 'main' into patch-21 2024-06-07 17:53:24 +03:00
Michael Brevard 7137a7b3b5
Merge branch 'main' into patch-21 2024-06-07 16:08:20 +03:00
Michael Brevard afa2e9aedf
Merge branch 'main' into patch-21 2024-06-04 12:21:01 +03:00
autofix-ci[bot] 4eb39d1062
[autofix.ci] apply automated fixes 2024-06-03 07:41:35 +00:00
Michael Brevard b764ae93be
fix: add componentLazyHydration 2024-06-03 10:39:14 +03:00
Michael Brevard 2f2cb8e0cf
fix: only enable delayed hydration with the experimental flag 2024-06-03 10:22:52 +03:00
autofix-ci[bot] e0a2f219db
[autofix.ci] apply automated fixes 2024-06-03 06:36:24 +00:00
Michael Brevard a53c5897a1
docs: add docs for delayed hydration 2024-06-03 09:35:49 +03:00
Michael Brevard ea0dea2a49
chore: rerun tests 2024-06-03 00:59:53 +03:00
Michael Brevard 440f4cc91a
chore: temporarily remove network component 2024-06-03 00:55:28 +03:00
autofix-ci[bot] a0f43b45f2
[autofix.ci] apply automated fixes 2024-06-02 21:41:22 +00:00
Michael Brevard 502383feea
chore: attempt to remove the pre-idle checks 2024-06-03 00:38:58 +03:00
Michael Brevard 046c1ce85f
chore: attempt to redo component 2024-06-03 00:27:40 +03:00
Michael Brevard 9746137b28
test: add network idle test 2024-06-03 00:04:43 +03:00
Michael Brevard f4f8ca08dc
chore: add network idle component 2024-06-03 00:02:31 +03:00
Michael Brevard d32cfdf09b
Create DelayedNetwork.server.vue 2024-06-03 00:01:15 +03:00
Michael Brevard a26603fafb
Create DelayedNetwork.client.vue 2024-06-03 00:00:54 +03:00
Michael Brevard 90fb0d5904
Update and rename DelayedHydration.server.vue to DelayedVisible.server.vue 2024-06-03 00:00:08 +03:00
Michael Brevard 6d3ec1714b
Update and rename DelayedHydration.client.vue to DelayedVisible.client.vue 2024-06-02 23:59:43 +03:00
Michael Brevard 5051c861b1
chore: remove VNode type 2024-06-02 23:49:48 +03:00
Michael Brevard 2a244f056f
chore: remove unused vnode 2024-06-02 23:43:29 +03:00
Michael Brevard 66dd659821
fix: delayed hydration for network idle 2024-06-02 23:40:37 +03:00
Michael Brevard 28647ecb30
chore: attempt to wait for network idle 2024-06-02 23:24:40 +03:00
Michael Brevard 41c4b53af7
chore: use new component 2024-06-02 22:33:25 +03:00
Michael Brevard d04d244799
fix: remove line 2024-06-02 22:04:03 +03:00
Michael Brevard b1c498bea7
test: named LazyVisible components 2024-06-02 21:56:49 +03:00
Michael Brevard b143038cef
fix: change name 2024-06-02 21:55:58 +03:00
Michael Brevard 6f806878fd
Create DelayedHydration.server.vue 2024-06-02 21:51:33 +03:00
Michael Brevard f5937803f8
chore: rename component 2024-06-02 21:51:05 +03:00
autofix-ci[bot] 2ac90f37b6
[autofix.ci] apply automated fixes 2024-06-02 14:20:58 +00:00
Michael Brevard 4616c172e0
fix: remove leftover previous code 2024-06-02 17:18:40 +03:00
Michael Brevard b1e66e5c05
fix: return component when server-side 2024-06-02 17:13:03 +03:00
Michael Brevard fa46fe3e4f
feat: refactor regex, add network idle 2024-06-02 17:12:01 +03:00
autofix-ci[bot] 3076e02082
[autofix.ci] apply automated fixes 2024-06-02 06:47:41 +00:00
Michael Brevard d65d3174b8
Merge branch 'main' into patch-21 2024-06-02 09:45:17 +03:00
Michael Brevard ad6bef5f70
Merge branch 'main' into patch-21 2024-05-03 12:13:44 +03:00
autofix-ci[bot] b45c3c9cae
[autofix.ci] apply automated fixes 2024-04-14 11:06:56 +00:00
Michael Brevard 850287bf18
fix: remove the preload for SSR render of delayed hydration 2024-04-14 14:04:45 +03:00
Michael Brevard 0cb15c6bbd
chore: remove wait for load state 2024-04-14 12:53:49 +03:00
autofix-ci[bot] 8eaf057ffe
[autofix.ci] apply automated fixes 2024-04-13 19:41:39 +00:00
Michael Brevard 9b8d33babf
fix: remove unused var 2024-04-13 22:39:23 +03:00
Michael Brevard 13949edeb3
wip: wait for response with the component 2024-04-13 22:37:03 +03:00
julien huang fd0adea179 fix: return render function and not vnode directly 2024-04-13 20:14:36 +02:00
autofix-ci[bot] 162908aac0
[autofix.ci] apply automated fixes 2024-04-13 16:08:11 +00:00
Michael Brevard 7821e7f441
feat(schema): add experimental lazy hydration option 2024-04-13 19:05:47 +03:00
Michael Brevard 1f196a57cd
Merge branch 'main' into patch-21 2024-04-13 17:26:46 +03:00
autofix-ci[bot] 5c300a4698
[autofix.ci] apply automated fixes 2024-04-13 10:38:41 +00:00
Julien Huang e4c9940269 fix: ssr improvement + static vnode rendering 2024-04-13 11:06:09 +02:00
Michael Brevard cfb6660fcd
fix: missing question marks 2024-04-11 00:42:29 +03:00
Michael Brevard cb7fc3f873
chore: rerunning tests 2024-04-11 00:33:42 +03:00
Michael Brevard 8555cec97d
fix: ensure el itself isn't null 2024-04-11 00:20:34 +03:00
Michael Brevard 2ee3cf53ee
fix: verify none null 2024-04-11 00:16:39 +03:00
Michael Brevard dc6d922773
fix: join the fragments 2024-04-11 00:13:22 +03:00
Michael Brevard e2d0350698
fix: use VNode type 2024-04-11 00:10:54 +03:00
Michael Brevard 580d9bd463
fix: import useNuxtApp and provide types 2024-04-11 00:06:49 +03:00
autofix-ci[bot] 31002ecc2d
[autofix.ci] apply automated fixes 2024-04-10 21:05:11 +00:00
Michael Brevard 2375650c36
feat: support lazy hydration on SSR 2024-04-11 00:02:48 +03:00
Michael Brevard b4c4d47721
Merge branch 'main' into patch-21 2024-04-10 10:43:21 +03:00
Michael Brevard effebbb02f
fix: strict types 2024-04-10 10:32:26 +03:00
Michael Brevard 2df20ac681
fix: remove comma 2024-04-10 10:02:39 +03:00
Michael Brevard 503b560d9b
wip: network idle based delayed component 2024-04-10 10:00:38 +03:00
Michael Brevard 817c3ace86
Merge branch 'nuxt:patch-21' into patch-21 2024-04-10 00:15:50 +03:00
julien huang 382bc93efe test: remove only 2024-04-09 23:14:44 +02:00
Michael Brevard 717a91c10a
Merge branch 'nuxt:patch-21' into patch-21 2024-04-10 00:11:00 +03:00
julien huang 45e49c5beb test: wait for network idle 2024-04-09 23:10:31 +02:00
Michael Brevard 8c1c23a422
chore: trying to resolve default 2024-04-09 23:47:41 +03:00
julien huang b3250e4a46 fix(nuxt): send the component loader and not the name 2024-04-09 21:55:27 +02:00
Michael Brevard d4bca6cd57
refactor: use page.evaluate 2024-04-09 22:24:48 +03:00
Michael Brevard 59edd16fce
chore: mark no side effects 2024-04-09 22:12:57 +03:00
Michael Brevard 3a8b1f3b75
chore: rerunning tests 2024-04-09 22:04:39 +03:00
Michael Brevard 9b94d100a2
chore: retrying without ClientOnly 2024-04-09 22:01:37 +03:00
autofix-ci[bot] 669fcee81b
[autofix.ci] apply automated fixes 2024-04-09 18:51:50 +00:00
Michael Brevard 2fa63ccfff
Merge branch 'main' into patch-21 2024-04-09 21:49:35 +03:00
Michael Brevard 988a99b771
fix: don't use the default that is used for slots 2024-04-09 21:49:02 +03:00
autofix-ci[bot] 26f2e4e7d1
[autofix.ci] apply automated fixes 2024-04-08 17:18:37 +00:00
Michael Brevard 5653d9e255
wip: retrying through mouse.wheel 2024-04-08 20:16:18 +03:00
autofix-ci[bot] bd7ca85fb4
[autofix.ci] apply automated fixes 2024-04-08 17:00:52 +00:00
Michael Brevard dd36a18140
wip: add scrolling to test for delayed hydration 2024-04-08 19:58:33 +03:00
Michael Brevard 36dc73152f
fix: append identifier before import 2024-04-08 19:28:58 +03:00
autofix-ci[bot] 2236f78566
[autofix.ci] apply automated fixes 2024-04-08 16:21:16 +00:00
Michael Brevard 4b0d88c54e
refactor: use a component to test transform 2024-04-08 19:18:47 +03:00
Michael Brevard d89e70e065
Create DelayedWrapperTestComponent.vue 2024-04-08 19:18:09 +03:00
Michael Brevard 84b2333cf5
fix: await 2024-04-08 19:09:35 +03:00
Michael Brevard 10f7f22c82
fix: check for count 2024-04-08 19:03:15 +03:00
Michael Brevard 00a68bd7ff
fix: async 2024-04-08 18:56:25 +03:00
Michael Brevard 88bae15ad7
wip: testing page text getting 2024-04-08 18:52:27 +03:00
autofix-ci[bot] d209a02492
[autofix.ci] apply automated fixes 2024-04-08 15:02:03 +00:00
Michael Brevard 4cc9efe563
wip: add initial lazy load check 2024-04-08 17:59:41 +03:00
autofix-ci[bot] 998c1ba551
[autofix.ci] apply automated fixes 2024-04-08 14:39:42 +00:00
Michael Brevard 6c683e0b6e
wip: add comp to lazy-import-components 2024-04-08 17:37:18 +03:00
autofix-ci[bot] 816ba111b4
[autofix.ci] apply automated fixes 2024-04-08 14:22:13 +00:00
Michael Brevard 20a32b2200
wip: provide hardcoded check to test delayed hydration runtime comp 2024-04-08 17:19:59 +03:00
autofix-ci[bot] 66938bc152
[autofix.ci] apply automated fixes 2024-04-08 07:24:28 +00:00
Michael Brevard 758824463d
Merge branch 'main' into patch-21 2024-04-08 10:21:48 +03:00
Michael Brevard 444e5be6e8
Merge branch 'main' into patch-21 2024-04-05 12:36:56 +03:00
Michael Brevard 8d435a1f45
Merge branch 'main' into patch-21 2024-04-02 12:12:41 +03:00
Michael Brevard 9713e32f07
Merge branch 'nuxt:main' into patch-21 2024-03-31 18:06:22 +03:00
Michael Brevard 586cfa5a3e
Merge branch 'main' into patch-21 2024-03-30 16:12:02 +03:00
Michael Brevard 36f2c75290
Merge branch 'main' into patch-21 2024-03-27 13:47:31 +02:00
Michael Brevard 600f55d2a3
wip: rename client-io-component.ts to client-delayed-component.ts for future wrappers 2024-03-26 16:20:49 +02:00
Michael Brevard 0caf8153ea
Merge branch 'main' into patch-21 2024-03-26 16:19:44 +02:00
autofix-ci[bot] 1e16b4a088
[autofix.ci] apply automated fixes 2024-03-25 17:23:25 +00:00
Michael Brevard f6ba2255e6
fix: ensure observer is not undefined 2024-03-25 19:21:01 +02:00
Michael Brevard 966c17be72
fix: provide a proper wrapper for IO with the comp 2024-03-25 19:19:35 +02:00
Michael Brevard caf73523c8
Merge branch 'main' into patch-21 2024-03-25 15:29:18 +02:00
autofix-ci[bot] b3b1676202
[autofix.ci] apply automated fixes 2024-03-25 13:17:29 +00:00
Michael Brevard f51c9362ce
fix: export function as opposed to type 2024-03-25 15:15:11 +02:00
Michael Brevard 7d2f345511
fix: remove unnecessary imports 2024-03-25 15:12:09 +02:00
Michael Brevard f039dfd09f
feat: provide exported function 2024-03-25 15:07:52 +02:00
Michael Brevard 624188a7cf
feat: extract useObserver 2024-03-25 15:06:53 +02:00
autofix-ci[bot] 85b4d6932b
[autofix.ci] apply automated fixes 2024-03-24 21:17:18 +00:00
Michael Brevard d94436b9c4
fix: import type Ref and provide emit 2024-03-24 23:15:02 +02:00
Michael Brevard 2ac2a975e0
fix: proper intersection callback type 2024-03-24 23:08:51 +02:00
Michael Brevard cb221ed579
types: client-io-component.ts 2024-03-24 22:24:55 +02:00
autofix-ci[bot] 932d143688
[autofix.ci] apply automated fixes 2024-03-24 17:54:31 +00:00
Michael Brevard 6a32dc1c9e
fix: client-io-component.ts 2024-03-24 19:52:16 +02:00
Michael Brevard ae2bb27ac0
fix: provide ref 2024-03-24 19:46:43 +02:00
Michael Brevard 84b0a71b8c
feat: provide an emit 2024-03-24 19:45:47 +02:00
Michael Brevard 9ac1261662
feat: client-io-component.ts barebones functionality 2024-03-24 19:41:29 +02:00
autofix-ci[bot] d64d78fde8
[autofix.ci] apply automated fixes 2024-03-24 17:36:36 +00:00
Michael Brevard 9f629ad45a
feat: lazy hydration 2024-03-24 19:30:58 +02:00
34 changed files with 805 additions and 13 deletions

View File

@ -119,6 +119,193 @@ const show = ref(false)
</template> </template>
``` ```
## Delayed Hydration
Lazy components are great for controlling the chunk sizes in your app, but they don't enhance runtime performance, as they still load eagerly unless conditionally rendered. In real world applications, some pages may include a lot of content and a lot of components, and most of the time not all of them need to be interactive as soon as the page is loaded. Having them all load eagerly can negatively impact performance and increase bundle size.
In order to optimize the page, you may want to delay the hydration of some components until they're visible, or until the browser is done with more important tasks for example. Delaying the hydration of components will ensure it is only loaded when necessary, which is great for making a good user experience and a performant app. Nuxt has first class support for delayed hydration to help with that, without requiring you to write all the related boilerplate.
In order to use delayed hydration, you first need to enable it in your experimental config in `nuxt.config`
```ts [nuxt.config.{ts,js}]
export default defineNuxtConfig({
experimental: {
delayedHydration: true
}
})
```
Nuxt has reserved component prefixes that will handle this delayed hydration for you, that extend dynamic imports. By prefixing your component with `LazyVisible`, Nuxt will automatically handle your component and delay its hydration until it will be on screen.
```vue [pages/index.vue]
<template>
<div>
<LazyVisibleMyComponent />
</div>
</template>
```
If you need the component to load as soon as possible, but not block the critical rendering path, you can use the `LazyIdle` prefix, which would handle your component's hydration whenever the browser goes idle.
```vue [pages/index.vue]
<template>
<div>
<LazyIdleMyComponent />
</div>
</template>
```
If you would like the component to load after certain events occur, like a click or a mouse over, you can use the `LazyEvent` prefix, which would only trigger the hydration when those events occur.
```vue [pages/index.vue]
<template>
<div>
<LazyEventMyComponent />
</div>
</template>
```
If you would like to load the component when the window matches a media query, you can use the `LazyMedia` prefix:
```vue [pages/index.vue]
<template>
<div>
<LazyMediaMyComponent />
</div>
</template>
```
If you would like to never hydrate a component, use the `LazyNever` prefix:
```vue [pages/index.vue]
<template>
<div>
<LazyNeverMyComponent />
</div>
</template>
```
If you would like to hydrate a component after a certain amount of time, use the `LazyTime` prefix:
```vue [pages/index.vue]
<template>
<div>
<LazyTimeMyComponent />
</div>
</template>
```
If you would like to hydrate a component once a promise is fulfilled, use the `LazyPromise` prefix:
```vue [pages/index.vue]
<template>
<div>
<LazyPromiseMyComponent />
</div>
</template>
```
Nuxt's delayed hydration system is highly flexible, allowing each developer to build upon it and implement their own hydration strategy.
If you have highly specific hydration triggers that aren't covered by the default strategies, or you want to have conditional hydraion, you can use the general purpose `LazyIf` prefix:
```vue [pages/index.vue]
<template>
<div>
<button @click="myFunction">Click me to start the custom hydration strategy</button>
<LazyIfMyComponent :hydrate="myCondition" />
</div>
</template>
<script setup lang="ts">
const myCondition = ref(false)
function myFunction() {
// trigger custom hydration strategy...
myCondition.value = true
}
</script>
```
### Custom hydration triggers
If you would like to override the default hydration triggers when dealing with delayed hydration, like changing the timeout, the options for the intersection observer, or the events to trigger the hydration, you can do so by supplying a `hydrate` prop to your lazy components.
```vue [pages/index.vue]
<template>
<div>
<LazyIdleMyComponent :hydrate="createIdleLoader(3000)" />
<LazyVisibleMyComponent :hydrate="createVisibleLoader({threshold: 0.2})" />
<LazyEventMyComponent :hydrate="createEventLoader(['click','mouseover'])" />
<LazyMediaMyComponent hydrate="(max-width: 500px)" />
<LazyIfMyComponent :hydrate="someCondition" />
<LazyTimeMyComponent :hydrate="3000" />
<LazyPromiseMyComponent :hydrate="promise" />
</div>
</template>
<script setup lang="ts">
const someCondition = ref(true)
const promise = Promise.resolve(42)
</script>
```
::read-more{to="/docs/api/utils/create-idle-loader"}
::
::read-more{to="/docs/api/utils/create-visible-loader"}
::
::read-more{to="/docs/api/utils/create-event-loader"}
::
::read-more{to="https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia"}
Read more about using `LazyMedia` components and the accepted values.
::
::important
If your components begin with a reserved delayed hydration prefix like Visible/Idle/Event, they will not have delayed hydration by default. This is made to ensure you have full control over all your components and prevent breaking dynamic imports for those components.
This also means you would need to explicitly add the prefix to those components for if you'd like for them to have delayed hydration.
For example, if you have a component named `IdleBar` and you'd like it to be delayed based on network idle time, you would need to use it like `<LazyIdleIdleBar>` and not `<LazyIdleBar>` to make it a delayed hydration component. Otherwise, it would be treated as a regular [dynamic import](/docs/guide/directory-structure/components#dynamic-imports)
::
### Listening to hydration events
All delayed hydration components have a `@hydrated` event that is fired whenever they are hydrated. You can listen to this event to trigger some action that depends on the component:
```vue [pages/index.vue]
<template>
<div>
<LazyVisibleMyComponent @hydrated="onHydrate" />
</div>
</template>
<script setup lang="ts">
function onHydrate() {
console.log("Component has been hydrated!")
}
</script>
```
### Caveats and best practices
Delayed hydration has many performance benefits, but in order to gain the most out of it, it's important to use it correctly:
1. Avoid delayed hydration components as much as possible for in-viewport content - delayed hydration is best for content that is not immediately available and requires some interaction to get to. If it is present on screen and is meant to be available for use immediately, using it as a normal component would provide better performance and loading times. Use this feature sparingly to avoid hurting the user experience, as there are only a few cases that warrant delayed hydration for on-screen content.
2. Delayed hydration with conditional rendering - when using `v-if` with delayed hydration components, note that `v-if` takes precedence. That means, the component will be hydrated when the `v-if` is truthy, as that will render exclusively on the client. If you need to render the component only when the condition is true, use a regular async component (`<LazyMyComponent />`) with a `v-if`. If you need it to hydrate when the condition is fulfilled, use a delayed hydration prefix with the `hydrate` prop.
3. Delayed hydration with a shared state - when using multiple components (for example, in a `v-for`) with the same `v-model`, where some components might get hydrated before others (for example, progressively increasing media queries), if one of the components updates the model, note that it will trigger hydration for all components with that same model. That is because Vue's reactivity system triggers an update for all the dependencies that rely on that state, forcing hydration in the process. Props are unaffected by this. Try to avoid multiple components with the same model if that is not an intended side effect.
4. Use each hydration strategy for its intended use case - each hydration strategy has built-in optimizations specifically designed for that strategy's purpose. Using them incorrectly could hurt performance and user experience. Examples include:
- Using `LazyIf` for always/never hydrated components (`:hydrate="true"`/`:hydrate="false"`) - you can use a regular component/`LazyNever` respectively, which would provide better performance for each use case. Keep `LazyIf` for components that could get hydrated, but might not get hydrated immediately.
- Using `LazyTime` as an alternative to `LazyIdle` - while these strategies share similarities, they are meant for different purposes. `LazyTime` is specifically designed to hydrate a component immediately after a certain amount of time has passed. `LazyIdle`, on the other hand, is meant to provide a limit for the browser to handle the hydration whenever it's idle. If you use `LazyTime` for idle-based hydration, the browser might handle the component's hydration while handling other, potentially more important components at the same time. This could slow down the hydration for all components being handled.
- \[ADVANCED\] Using only `LazyIf` to manually implement existing hydration strategies - while an option, using only `LazyIf` in stead of relying on the built-in supported strategies could impact the overall memory consumption and performance.
For example, in stead of handling promises manually and setting a boolean indicator for when the promise was fulfilled, which would then get passed to `LazyIf`, using `LazyPromise` directly would handle it without requiring another ref, reducing the complexity and the amount of work Vue's reactivity system would need to handle and track.
Always remember that while `LazyIf` allows for implementation of custom, highly-tailored hydration strategies, it should mainly be used when either pure conditional hydration is required (for example, hydration of a component when a separate button is clicked), or when no built-in strategy matches your specific use case, due to the internal optimizations each existing hydration strategy has.
## Direct Imports ## Direct Imports
You can also explicitly import components from `#components` if you want or need to bypass Nuxt's auto-importing functionality. You can also explicitly import components from `#components` if you want or need to bypass Nuxt's auto-importing functionality.

View File

@ -0,0 +1,33 @@
---
title: 'createEventLoader'
description: A utility function to select the events used for event-based delayed hydration.
links:
- label: Source
icon: i-simple-icons-github
to: https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/app/composables/hydrate.ts
size: xs
---
You can use this utility to set specific events that would trigger hydration in event-based delayed hydration components.
## Parameters
- `options`: An array of valid HTML events.
## Example
If you would like to trigger hydration when the element is either clicked or has the mouse over it:
```vue [pages/index.vue]
<template>
<div>
<LazyEventMyComponent :hydrate="createEventLoader(['click','mouseover'])"/>
</div>
<template>
```
::read-more{to="/docs/guide/directory-structure/components#delayed-hydration"}
::
::read-more{to="https://developer.mozilla.org/en-US/docs/Web/API/Element#events"}
Read more on the possible events that can be used.
::

View File

@ -0,0 +1,33 @@
---
title: 'createIdleLoader'
description: A utility function to customize delayed hydration based on network idle time.
links:
- label: Source
icon: i-simple-icons-github
to: https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/app/composables/hydrate.ts
size: xs
---
You can use this utility to customize the timeout of delayed hydration components based on network idle time.
## Parameters
- `timeout` : `number`
## Example
If you would like to give a timeout of 5 seconds for the components:
```vue [pages/index.vue]
<template>
<div>
<LazyIdleMyComponent :hydrate="createIdleLoader(5000)"/>
</div>
<template>
```
::read-more{to="/docs/guide/directory-structure/components#delayed-hydration"}
::
::read-more{to="https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback"}
This is based on the `requestIdleCallback` web API, and therefore only accepts the time in milliseconds for the max idle callback duration, which should be a number.
::

View File

@ -0,0 +1,33 @@
---
title: 'createVisibleLoader'
description: A utility function to customize delayed hydration based on visibility properties.
links:
- label: Source
icon: i-simple-icons-github
to: https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/app/composables/hydrate.ts
size: xs
---
You can use this utility to customize the conditions through which delayed hydration components would hydrate, based on their visiblity status and properties.
## Parameters
- `options`: `{ root, rootMargin, threshold }`
## Example
If you would like to change the threshold of the element:
```vue [pages/index.vue]
<template>
<div>
<LazyVisibleMyComponent :hydrate="createVisibleLoader({threshold: 0.2})"/>
</div>
<template>
```
::read-more{to="/docs/guide/directory-structure/components#delayed-hydration"}
::
::read-more{to="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API"}
This is based on the `IntersectionObserver` web API, and therefore only accepts the API's properties. You can specify only part of the properties, while the rest will default to the web API's defaults.
::

View File

@ -480,7 +480,7 @@ type CallbackFn = () => void
type ObserveFn = (element: Element, callback: CallbackFn) => () => void type ObserveFn = (element: Element, callback: CallbackFn) => () => void
function useObserver (): { observe: ObserveFn } | undefined { function useObserver (): { observe: ObserveFn } | undefined {
if (import.meta.server) { return } if (import.meta.server) { return { observe: () => () => {} } }
const nuxtApp = useNuxtApp() const nuxtApp = useNuxtApp()
if (nuxtApp._observer) { if (nuxtApp._observer) {
@ -488,7 +488,6 @@ function useObserver (): { observe: ObserveFn } | undefined {
} }
let observer: IntersectionObserver | null = null let observer: IntersectionObserver | null = null
const callbacks = new Map<Element, CallbackFn>() const callbacks = new Map<Element, CallbackFn>()
const observe: ObserveFn = (element, callback) => { const observe: ObserveFn = (element, callback) => {
@ -519,7 +518,6 @@ function useObserver (): { observe: ObserveFn } | undefined {
return _observer return _observer
} }
function isSlowConnection () { function isSlowConnection () {
if (import.meta.server) { return } if (import.meta.server) { return }

View File

@ -23,3 +23,21 @@ export const useHydration = <K extends keyof NuxtPayload, T = NuxtPayload[K]> (k
}) })
} }
} }
/**
* A `requestIdleCallback` options utility, used to determine custom timeout for idle-callback based delayed hydration.
* @param timeout the max timeout for the idle callback, in milliseconds
*/
export const createIdleLoader = (timeout: number) => timeout
/**
* An `IntersectionObserver` options utility, used to determine custom viewport-based delayed hydration behavior.
* @param opts the options object, containing the wanted viewport options
*/
export const createVisibleLoader = (opts: Partial<IntersectionObserverInit>) => opts
/**
* A utility used to determine which event/events should trigger hydration in components with event-based delayed hydration.
* @param events an event or array of events that will be used to trigger the hydration
*/
export const createEventLoader = (events: keyof HTMLElementEventMap | Array<keyof HTMLElementEventMap>) => events

View File

@ -21,7 +21,7 @@ export const loaderPlugin = createUnplugin((options: LoaderOptions) => {
const exclude = options.transform?.exclude || [] const exclude = options.transform?.exclude || []
const include = options.transform?.include || [] const include = options.transform?.include || []
const serverComponentRuntime = resolve(distDir, 'components/runtime/server-component') const serverComponentRuntime = resolve(distDir, 'components/runtime/server-component')
const clientDelayedComponentRuntime = resolve(distDir, 'components/runtime/client-delayed-component')
return { return {
name: 'nuxt:components-loader', name: 'nuxt:components-loader',
enforce: 'post', enforce: 'post',
@ -41,13 +41,16 @@ export const loaderPlugin = createUnplugin((options: LoaderOptions) => {
const imports = new Set<string>() const imports = new Set<string>()
const map = new Map<Component, string>() const map = new Map<Component, string>()
const s = new MagicString(code) const s = new MagicString(code)
const nuxt = tryUseNuxt()
// replace `_resolveComponent("...")` to direct import // replace `_resolveComponent("...")` to direct import
s.replace(/(?<=[ (])_?resolveComponent\(\s*["'](lazy-|Lazy(?=[A-Z]))?([^'"]*)["'][^)]*\)/g, (full: string, lazy: string, name: string) => { s.replace(/(?<=[ (])_?resolveComponent\(\s*["'](lazy-|Lazy(?=[A-Z]))?(Idle|Visible|idle-|visible-|Event|event-|Media|media-|If|if-|Never|never-|Time|time-|Promise|promise-)?([^'"]*)["'][^)]*\)/g, (full: string, lazy: string, modifier: string, name: string) => {
const component = findComponent(components, name, options.mode) const normalComponent = findComponent(components, name, options.mode)
const modifierComponent = !normalComponent && modifier ? findComponent(components, modifier + name, options.mode) : null
const component = normalComponent || modifierComponent
if (component) { if (component) {
// @ts-expect-error TODO: refactor to nuxi // @ts-expect-error TODO: refactor to nuxi
if (component._internal_install && tryUseNuxt()?.options.test === false) { if (component._internal_install && nuxt?.options.test === false) {
// @ts-expect-error TODO: refactor to nuxi // @ts-expect-error TODO: refactor to nuxi
import('../core/features').then(({ installNuxtModule }) => installNuxtModule(component._internal_install)) import('../core/features').then(({ installNuxtModule }) => installNuxtModule(component._internal_install))
} }
@ -72,9 +75,63 @@ export const loaderPlugin = createUnplugin((options: LoaderOptions) => {
} }
if (lazy) { if (lazy) {
imports.add(genImport('vue', [{ name: 'defineAsyncComponent', as: '__defineAsyncComponent' }])) const dynamicImport = `${genDynamicImport(component.filePath, { interopDefault: false })}.then(c => c.${component.export ?? 'default'} || c)`
identifier += '_lazy' if (modifier && normalComponent && nuxt?.options.experimental.delayedHydration === true) {
imports.add(`const ${identifier} = __defineAsyncComponent(${genDynamicImport(component.filePath, { interopDefault: false })}.then(c => c.${component.export ?? 'default'} || c)${isClientOnly ? '.then(c => createClientOnly(c))' : ''})`) switch (modifier) {
case 'Visible':
case 'visible-':
imports.add(genImport(clientDelayedComponentRuntime, [{ name: 'createLazyIOComponent' }]))
identifier += '_delayedIO'
imports.add(`const ${identifier} = createLazyIOComponent(${dynamicImport})`)
break
case 'Event':
case 'event-':
imports.add(genImport(clientDelayedComponentRuntime, [{ name: 'createLazyEventComponent' }]))
identifier += '_delayedEvent'
imports.add(`const ${identifier} = createLazyEventComponent(${dynamicImport})`)
break
case 'Idle':
case 'idle-':
imports.add(genImport(clientDelayedComponentRuntime, [{ name: 'createLazyNetworkComponent' }]))
identifier += '_delayedNetwork'
imports.add(`const ${identifier} = createLazyNetworkComponent(${dynamicImport})`)
break
case 'Media':
case 'media-':
imports.add(genImport(clientDelayedComponentRuntime, [{ name: 'createLazyMediaComponent' }]))
identifier += '_delayedMedia'
imports.add(`const ${identifier} = createLazyMediaComponent(${dynamicImport})`)
break
case 'If':
case 'if-':
imports.add(genImport(clientDelayedComponentRuntime, [{ name: 'createLazyIfComponent' }]))
identifier += '_delayedIf'
imports.add(`const ${identifier} = createLazyIfComponent(${dynamicImport})`)
break
case 'Never':
case 'never-':
imports.add(genImport('vue', [{ name: 'defineAsyncComponent', as: '__defineAsyncComponent' }]))
identifier += '_delayedNever'
imports.add(`const ${identifier} = __defineAsyncComponent({loader: ${dynamicImport}, hydrate: () => {}})`)
break
case 'Time':
case 'time-':
imports.add(genImport(clientDelayedComponentRuntime, [{ name: 'createLazyTimeComponent' }]))
identifier += '_delayedTime'
imports.add(`const ${identifier} = createLazyTimeComponent(${dynamicImport})`)
break
case 'Promise':
case 'promise-':
imports.add(genImport(clientDelayedComponentRuntime, [{ name: 'createLazyPromiseComponent' }]))
identifier += '_delayedPromise'
imports.add(`const ${identifier} = createLazyPromiseComponent(${dynamicImport})`)
break
}
} else {
imports.add(genImport('vue', [{ name: 'defineAsyncComponent', as: '__defineAsyncComponent' }]))
identifier += '_lazy'
imports.add(`const ${identifier} = __defineAsyncComponent(${dynamicImport}${isClientOnly ? '.then(c => createClientOnly(c))' : ''})`)
}
} else { } else {
imports.add(genImport(component.filePath, [{ name: component._raw ? 'default' : component.export, as: identifier }])) imports.add(genImport(component.filePath, [{ name: component._raw ? 'default' : component.export, as: identifier }]))

View File

@ -0,0 +1,175 @@
import { defineAsyncComponent, defineComponent, h, hydrateOnIdle, hydrateOnInteraction, hydrateOnMediaQuery, hydrateOnVisible, mergeProps, watch } from 'vue'
import type { AsyncComponentLoader, HydrationStrategy } from 'vue'
/* @__NO_SIDE_EFFECTS__ */
export const createLazyIOComponent = (loader: AsyncComponentLoader) => {
return defineComponent({
inheritAttrs: false,
props: {
hydrate: {
type: Object,
required: false,
},
},
emits: ['hydrated'],
setup (props, { attrs, emit }) {
const hydrated = () => { emit('hydrated') }
const comp = defineAsyncComponent({ loader, hydrate: hydrateOnVisible(props.hydrate as IntersectionObserverInit | undefined) })
// TODO: fix hydration mismatches on Vue's side. The data-allow-mismatch is ideally a temporary solution due to Vue's SSR limitation with hydrated content.
return () => h(comp, mergeProps(attrs, { 'data-allow-mismatch': '', 'onVnodeMounted': hydrated }))
},
})
}
/* @__NO_SIDE_EFFECTS__ */
export const createLazyNetworkComponent = (loader: AsyncComponentLoader) => {
return defineComponent({
inheritAttrs: false,
props: {
hydrate: {
type: Number,
required: false,
},
},
emits: ['hydrated'],
setup (props, { attrs, emit }) {
const hydrated = () => { emit('hydrated') }
if (props.hydrate === 0) {
const comp = defineAsyncComponent(loader)
return () => h(comp, mergeProps(attrs, { 'data-allow-mismatch': '', 'onVnodeMounted': hydrated }))
}
const comp = defineAsyncComponent({ loader, hydrate: hydrateOnIdle(props.hydrate) })
// TODO: fix hydration mismatches on Vue's side. The data-allow-mismatch is ideally a temporary solution due to Vue's SSR limitation with hydrated content.
return () => h(comp, mergeProps(attrs, { 'data-allow-mismatch': '', 'onVnodeMounted': hydrated }))
},
})
}
/* @__NO_SIDE_EFFECTS__ */
export const createLazyEventComponent = (loader: AsyncComponentLoader) => {
return defineComponent({
inheritAttrs: false,
props: {
hydrate: {
type: [String, Array],
required: false,
default: 'mouseover',
},
},
emits: ['hydrated'],
setup (props, { attrs, emit }) {
const hydrated = () => { emit('hydrated') }
// @ts-expect-error Cannot type HTMLElementEventMap in props
const comp = defineAsyncComponent({ loader, hydrate: hydrateOnInteraction(props.hydrate) })
// TODO: fix hydration mismatches on Vue's side. The data-allow-mismatch is ideally a temporary solution due to Vue's SSR limitation with hydrated content.
return () => h(comp, mergeProps(attrs, { 'data-allow-mismatch': '', 'onVnodeMounted': hydrated }))
},
})
}
/* @__NO_SIDE_EFFECTS__ */
export const createLazyMediaComponent = (loader: AsyncComponentLoader) => {
return defineComponent({
inheritAttrs: false,
props: {
hydrate: {
type: String,
required: false,
default: '(min-width: 1px)',
},
},
emits: ['hydrated'],
setup (props, { attrs, emit }) {
const hydrated = () => { emit('hydrated') }
const comp = defineAsyncComponent({ loader, hydrate: hydrateOnMediaQuery(props.hydrate) })
// TODO: fix hydration mismatches on Vue's side. The data-allow-mismatch is ideally a temporary solution due to Vue's SSR limitation with hydrated content.
return () => h(comp, mergeProps(attrs, { 'data-allow-mismatch': '', 'onVnodeMounted': hydrated }))
},
})
}
/* @__NO_SIDE_EFFECTS__ */
export const createLazyIfComponent = (loader: AsyncComponentLoader) => {
return defineComponent({
inheritAttrs: false,
props: {
hydrate: {
type: Boolean,
required: false,
default: true,
},
},
emits: ['hydrated'],
setup (props, { attrs, emit }) {
const hydrated = () => { emit('hydrated') }
if (props.hydrate) {
const comp = defineAsyncComponent(loader)
// TODO: fix hydration mismatches on Vue's side. The data-allow-mismatch is ideally a temporary solution due to Vue's SSR limitation with hydrated content.
return () => h(comp, mergeProps(attrs, { 'data-allow-mismatch': '', 'onVnodeMounted': hydrated }))
}
const strategy: HydrationStrategy = (hydrate) => {
const unwatch = watch(() => props.hydrate, () => hydrate(), { once: true })
return () => unwatch()
}
const comp = defineAsyncComponent({ loader, hydrate: strategy })
return () => h(comp, mergeProps(attrs, { 'data-allow-mismatch': '', 'onVnodeMounted': hydrated }))
},
})
}
/* @__NO_SIDE_EFFECTS__ */
export const createLazyTimeComponent = (loader: AsyncComponentLoader) => {
return defineComponent({
inheritAttrs: false,
props: {
hydrate: {
type: Number,
required: false,
default: 2000,
},
},
emits: ['hydrated'],
setup (props, { attrs, emit }) {
const hydrated = () => { emit('hydrated') }
if (props.hydrate === 0) {
const comp = defineAsyncComponent(loader)
return () => h(comp, mergeProps(attrs, { 'data-allow-mismatch': '', 'onVnodeMounted': hydrated }))
}
const strategy: HydrationStrategy = (hydrate) => {
const id = setTimeout(hydrate, props.hydrate)
return () => clearTimeout(id)
}
const comp = defineAsyncComponent({ loader, hydrate: strategy })
// TODO: fix hydration mismatches on Vue's side. The data-allow-mismatch is ideally a temporary solution due to Vue's SSR limitation with hydrated content.
return () => h(comp, mergeProps(attrs, { 'data-allow-mismatch': '', 'onVnodeMounted': hydrated }))
},
})
}
/* @__NO_SIDE_EFFECTS__ */
export const createLazyPromiseComponent = (loader: AsyncComponentLoader) => {
return defineComponent({
inheritAttrs: false,
props: {
hydrate: {
type: Promise,
required: false,
},
},
emits: ['hydrated'],
setup (props, { attrs, emit }) {
const hydrated = () => { emit('hydrated') }
if (!props.hydrate) {
const comp = defineAsyncComponent(loader)
// TODO: fix hydration mismatches on Vue's side. The data-allow-mismatch is ideally a temporary solution due to Vue's SSR limitation with hydrated content.
return () => h(comp, mergeProps(attrs, { 'data-allow-mismatch': '', 'onVnodeMounted': hydrated }))
}
const strategy: HydrationStrategy = (hydrate) => {
props.hydrate!.then(hydrate)
return () => {}
}
const comp = defineAsyncComponent({ loader, hydrate: strategy })
return () => h(comp, mergeProps(attrs, { 'data-allow-mismatch': '', 'onVnodeMounted': hydrated }))
},
})
}

View File

@ -115,14 +115,23 @@ export const componentsTypeTemplate = {
c.island || c.mode === 'server' ? `IslandComponent<${type}>` : type, c.island || c.mode === 'server' ? `IslandComponent<${type}>` : type,
] ]
}) })
const islandType = 'type IslandComponent<T extends DefineComponent> = T & DefineComponent<{}, {refresh: () => Promise<void>}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, SlotsType<{ fallback: { error: unknown } }>>' const islandType = 'type IslandComponent<T extends DefineComponent> = T & DefineComponent<{}, {refresh: () => Promise<void>}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, SlotsType<{ fallback: { error: unknown } }>>'
const delayedType = 'type DelayedComponent<T> = DefineComponent<{hydrate?: T},{},{},{},{},{},{},{hydrated: void}>'
return ` return `
import type { DefineComponent, SlotsType } from 'vue' import type { DefineComponent, SlotsType } from 'vue'
${nuxt.options.experimental.componentIslands ? islandType : ''} ${nuxt.options.experimental.componentIslands ? islandType : ''}
${nuxt.options.experimental.delayedHydration ? delayedType : ''}
interface _GlobalComponents { interface _GlobalComponents {
${componentTypes.map(([pascalName, type]) => ` '${pascalName}': ${type}`).join('\n')} ${componentTypes.map(([pascalName, type]) => ` '${pascalName}': ${type}`).join('\n')}
${componentTypes.map(([pascalName, type]) => ` 'Lazy${pascalName}': ${type}`).join('\n')} ${componentTypes.map(([pascalName, type]) => ` 'Lazy${pascalName}': ${type}`).join('\n')}
${componentTypes.map(([pascalName, type]) => ` 'LazyIdle${pascalName}': ${type} & DelayedComponent<number>`).join('\n')}
${componentTypes.map(([pascalName, type]) => ` 'LazyTime${pascalName}': ${type} & DelayedComponent<number>`).join('\n')}
${componentTypes.map(([pascalName, type]) => ` 'LazyPromise${pascalName}': ${type} & DelayedComponent<Promise<unknown>>`).join('\n')}
${componentTypes.map(([pascalName, type]) => ` 'LazyVisible${pascalName}': ${type} & DelayedComponent<IntersectionObserverInit>`).join('\n')}
${componentTypes.map(([pascalName, type]) => ` 'LazyEvent${pascalName}': ${type} & DelayedComponent<keyof HTMLElementEventMap | Array<keyof HTMLElementEventMap>>`).join('\n')}
${componentTypes.map(([pascalName, type]) => ` 'LazyMedia${pascalName}': ${type} & DelayedComponent<string>`).join('\n')}
${componentTypes.map(([pascalName, type]) => ` 'LazyIf${pascalName}': ${type} & DelayedComponent<unknown>`).join('\n')}
${componentTypes.map(([pascalName, type]) => ` 'LazyNever${pascalName}': ${type}`).join('\n')}
} }
declare module 'vue' { declare module 'vue' {
@ -131,6 +140,14 @@ declare module 'vue' {
${componentTypes.map(([pascalName, type]) => `export const ${pascalName}: ${type}`).join('\n')} ${componentTypes.map(([pascalName, type]) => `export const ${pascalName}: ${type}`).join('\n')}
${componentTypes.map(([pascalName, type]) => `export const Lazy${pascalName}: ${type}`).join('\n')} ${componentTypes.map(([pascalName, type]) => `export const Lazy${pascalName}: ${type}`).join('\n')}
${componentTypes.map(([pascalName, type]) => `export const LazyIdle${pascalName}: ${type} & DelayedComponent<number>`).join('\n')}
${componentTypes.map(([pascalName, type]) => `export const LazyTime${pascalName}: ${type} & DelayedComponent<number>`).join('\n')}
${componentTypes.map(([pascalName, type]) => `export const LazyPromise${pascalName}: ${type} & DelayedComponent<Promise<unknown>>`).join('\n')}
${componentTypes.map(([pascalName, type]) => `export const LazyVisible${pascalName}: ${type} & DelayedComponent<IntersectionObserverInit>`).join('\n')}
${componentTypes.map(([pascalName, type]) => `export const LazyEvent${pascalName}: ${type} & DelayedComponent<keyof HTMLElementEventMap | Array<keyof HTMLElementEventMap>>`).join('\n')}
${componentTypes.map(([pascalName, type]) => `export const LazyMedia${pascalName}: ${type} & DelayedComponent<string>`).join('\n')}
${componentTypes.map(([pascalName, type]) => `export const LazyIf${pascalName}: ${type} & DelayedComponent<unknown>`).join('\n')}
${componentTypes.map(([pascalName, type]) => `export const LazyNever${pascalName}: ${type}`).join('\n')}
export const componentNames: string[] export const componentNames: string[]
` `

View File

@ -42,7 +42,7 @@ const granularAppPresets: InlinePreset[] = [
from: '#app/composables/asyncData', from: '#app/composables/asyncData',
}, },
{ {
imports: ['useHydration'], imports: ['useHydration', 'createEventLoader', 'createIdleLoader', 'createVisibleLoader'],
from: '#app/composables/hydrate', from: '#app/composables/hydrate',
}, },
{ {

View File

@ -216,6 +216,13 @@ export default defineUntypedSchema({
}, },
}, },
/**
* Delayed component hydration
*
* @type {boolean}
*/
delayedHydration: false,
/** Resolve `~`, `~~`, `@` and `@@` aliases located within layers with respect to their layer source and root directories. */ /** Resolve `~`, `~~`, `@` and `@@` aliases located within layers with respect to their layer source and root directories. */
localLayerAliases: true, localLayerAliases: true,

View File

@ -2780,6 +2780,80 @@ describe('lazy import components', () => {
it('lazy load named component with mode server', () => { it('lazy load named component with mode server', () => {
expect(html).toContain('lazy-named-comp-server') expect(html).toContain('lazy-named-comp-server')
}) })
it('lazy load delayed hydration comps at the right time', async () => {
expect(html).toContain('This should be visible at first with network!')
const { page } = await renderPage('/lazy-import-components')
await page.waitForLoadState('networkidle')
expect(await page.locator('body').getByText('This shouldn\'t be visible at first with network!').all()).toHaveLength(1)
expect(await page.locator('body').getByText('This should be visible at first with viewport!').all()).toHaveLength(1)
expect(await page.locator('body').getByText('This should be visible at first with events!').all()).toHaveLength(2)
// The default value is immediately truthy, however, there is a hydration mismatch without the hack
expect(await page.locator('body').getByText('This should be visible at first with conditions!').all()).toHaveLength(1)
const component = page.locator('#lazyevent')
const rect = (await component.boundingBox())!
await page.mouse.move(rect.x + rect.width / 2, rect.y + rect.height / 2)
await page.waitForLoadState('networkidle')
expect(await page.locator('body').getByText('This shouldn\'t be visible at first with events!').all()).toHaveLength(1)
await page.locator('#conditionbutton').click()
await page.waitForLoadState('networkidle')
expect(await page.locator('body').getByText('This shouldn\'t be visible at first with conditions!').all()).toHaveLength(2)
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight))
await page.waitForTimeout(300) // Wait for the intersection observer to fire the callback
expect(await page.locator('body').getByText('This shouldn\'t be visible at first with viewport!').all()).toHaveLength(2)
expect(await page.locator('body').getByText('This should always be visible!').all()).toHaveLength(1)
await page.close()
})
it('respects custom delayed hydration triggers and overrides defaults', async () => {
const { page } = await renderPage('/lazy-import-components')
await page.waitForLoadState('networkidle')
const component = page.locator('#lazyevent2')
const rect = (await component.boundingBox())!
await page.mouse.move(rect.x + rect.width / 2, rect.y + rect.height / 2)
await page.waitForTimeout(500)
await page.waitForLoadState('networkidle')
expect(await page.locator('body').getByText('This should be visible at first with events!').all()).toHaveLength(2)
await page.locator('#lazyevent2').click()
await page.waitForLoadState('networkidle')
expect(await page.locator('body').getByText('This should be visible at first with events!').all()).toHaveLength(1)
expect(await page.locator('body').getByText('This shouldn\'t be visible at first with events!').all()).toHaveLength(1)
await page.close()
})
it('does not delay hydration of components named after modifiers', async () => {
const { page } = await renderPage('/lazy-import-components')
expect(await page.locator('body').getByText('This fake lazy event should be visible!').all()).toHaveLength(1)
expect(await page.locator('body').getByText('This fake lazy event shouldn\'t be visible!').all()).toHaveLength(0)
})
it('handles time-based hydration correctly', async () => {
const { page } = await renderPage('/lazy-import-components/time')
expect(await page.locator('body').getByText('This should be visible at first with time!').all()).toHaveLength(2)
await page.waitForTimeout(500)
expect(await page.locator('body').getByText('This should be visible at first with time!').all()).toHaveLength(1)
await page.waitForTimeout(1600) // Some room for falkiness and intermittent lag
expect(await page.locator('body').getByText('This should be visible at first with time!').all()).toHaveLength(0)
})
it('handles promise-based hydration correctly', async () => {
const { page } = await renderPage('/lazy-import-components/promise')
expect(await page.locator('body').getByText('This should be visible at first with promise!').all()).toHaveLength(1)
await page.waitForTimeout(2100) // Some room for falkiness and intermittent lag
expect(await page.locator('body').getByText('This should be visible at first with promise!').all()).toHaveLength(0)
})
it('keeps reactivity with models', async () => {
const { page } = await renderPage('/lazy-import-components/model-event')
expect(await page.locator('#count').textContent()).toBe('0')
for (let i = 0; i < 10; i++) {
expect(await page.locator('#count').textContent()).toBe(`${i}`)
await page.locator('#inc').click()
}
expect(await page.locator('#count').textContent()).toBe('10')
})
it('emits hydration events', async () => {
const { page, consoleLogs } = await renderPage('/lazy-import-components/model-event')
expect(consoleLogs.some(log => log.type === 'log' && log.text === 'Component hydrated')).toBeFalsy()
await page.locator('#count').click()
expect(consoleLogs.some(log => log.type === 'log' && log.text === 'Component hydrated')).toBeTruthy()
})
}) })
describe('defineNuxtComponent watch duplicate', () => { describe('defineNuxtComponent watch duplicate', () => {

View File

@ -0,0 +1,5 @@
<template>
<div>
This shouldn't be visible at first with conditions!
</div>
</template>

View File

@ -0,0 +1,5 @@
<template>
<div>
This should be visible at first with conditions!
</div>
</template>

View File

@ -0,0 +1,5 @@
<template>
<div>
This shouldn't be visible at first with events!
</div>
</template>

View File

@ -0,0 +1,5 @@
<template>
<div>
This should be visible at first with events!
</div>
</template>

View File

@ -0,0 +1,15 @@
<template>
<div>
<span id="count">{{ model }}</span>
<button
id="inc"
@click="model++"
>
Increment
</button>
</div>
</template>
<script setup lang="ts">
const model = defineModel()
</script>

View File

@ -0,0 +1,5 @@
<template>
<div>
This shouldn't be visible at first with network!
</div>
</template>

View File

@ -0,0 +1,5 @@
<template>
<div>
This should be visible at first with network!
</div>
</template>

View File

@ -0,0 +1,5 @@
<template>
<div>
This should never be visible!
</div>
</template>

View File

@ -0,0 +1,5 @@
<template>
<div>
This should always be visible!
</div>
</template>

View File

@ -0,0 +1,5 @@
<template>
<div>
This shouldn't be visible at first with promise!
</div>
</template>

View File

@ -0,0 +1,5 @@
<template>
<div>
This should be visible at first with promise!
</div>
</template>

View File

@ -0,0 +1,5 @@
<template>
<div>
This shouldn't be visible at first with time!
</div>
</template>

View File

@ -0,0 +1,5 @@
<template>
<div>
This should be visible at first with time!
</div>
</template>

View File

@ -0,0 +1,5 @@
<template>
<div>
This shouldn't be visible at first with viewport!
</div>
</template>

View File

@ -0,0 +1,5 @@
<template>
<div>
This should be visible at first with viewport!
</div>
</template>

View File

@ -0,0 +1,5 @@
<template>
<div>
This fake lazy event should be visible!
</div>
</template>

View File

@ -0,0 +1,5 @@
<template>
<div>
This fake lazy event shouldn't be visible!
</div>
</template>

View File

@ -254,6 +254,7 @@ export default defineNuxtConfig({
renderJsonPayloads: process.env.TEST_PAYLOAD !== 'js', renderJsonPayloads: process.env.TEST_PAYLOAD !== 'js',
headNext: true, headNext: true,
inlineRouteRules: true, inlineRouteRules: true,
delayedHydration: true,
}, },
appConfig: { appConfig: {
fromNuxtConfig: true, fromNuxtConfig: true,

View File

@ -3,5 +3,31 @@
<LazyNCompAll message="lazy-named-comp-all" /> <LazyNCompAll message="lazy-named-comp-all" />
<LazyNCompClient message="lazy-named-comp-client" /> <LazyNCompClient message="lazy-named-comp-client" />
<LazyNCompServer message="lazy-named-comp-server" /> <LazyNCompServer message="lazy-named-comp-server" />
<LazyEventDelayedEvent id="lazyevent" />
<LazyEventView />
<LazyVisibleDelayedVisible />
<LazyNeverDelayedNever />
<LazyEventDelayedEvent
id="lazyevent2"
:hydrate="createEventLoader(['click'])"
/>
<LazyIfDelayedCondition id="lazycondition" />
<button
id="conditionbutton"
@click="state++"
/>
<LazyIfDelayedCondition
id="lazycondition2"
:hydrate="state > 1"
/>
<LazyIdleDelayedNetwork />
<div style="height:3000px">
This is a very tall div
</div>
<LazyVisibleDelayedVisible />
</div> </div>
</template> </template>
<script setup lang="ts">
const state = useState('delayedHydrationCondition', () => 1)
</script>

View File

@ -0,0 +1,15 @@
<template>
<div>
<LazyEventDelayedModel
v-model="model"
@hydrated="log"
/>
</div>
</template>
<script setup lang="ts">
const model = ref(0)
function log () {
console.log('Component hydrated')
}
</script>

View File

@ -0,0 +1,15 @@
<script setup lang="ts">
const pr = new Promise((resolve) => { setTimeout(resolve, 500) })
const prImmediate = Promise.resolve(42)
</script>
<template>
<div>
<LazyPromiseDelayedPromise
:hydrate="pr"
/>
<LazyPromiseDelayedPromise
:hydrate="prImmediate"
/>
</div>
</template>

View File

@ -0,0 +1,8 @@
<template>
<div>
<LazyTimeDelayedTime />
<LazyTimeDelayedTime
:hydrate="500"
/>
</div>
</template>