Compare commits

...

305 Commits

Author SHA1 Message Date
Michael Brevard
c75f5a0d76
Merge 6479a91928 into edc299a043 2024-11-20 06:35:52 -05:00
renovate[bot]
edc299a043
chore(deps): update all non-major dependencies (main) (#29995)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-20 06:35:43 -05:00
renovate[bot]
ad3ab4d310
chore(deps): update all non-major dependencies (main) (#29986)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-19 23:36:17 -05:00
tbitw2549
6479a91928 fix: unify component name detection 2024-11-09 00:48:41 +02:00
autofix-ci[bot]
3bd2bf7cf7
[autofix.ci] apply automated fixes 2024-11-08 22:05:45 +00:00
tbitw2549
41a15205fd fix: avoid backtrack by keeping second last char 2024-11-08 23:54:06 +02:00
tbitw2549
0c9dcb9f66 fix: escape string template literal 2024-11-08 23:16:56 +02:00
tbitw2549
91abe21fbf refactor: build plugin with prop based approach 2024-11-08 23:13:04 +02:00
tbitw2549
897b067e19 chore: add back delayedHydration config 2024-11-05 13:46:16 +02:00
tbitw2549
b2b2c4db63 chore: remove transient nuxt config 2024-11-05 13:45:05 +02:00
tbitw2549
7c6b5d73c7 Merge remote-tracking branch 'upstream/main' into patch-21 2024-11-05 13:42:55 +02:00
Michael Brevard
d3aef5549a
docs: tofandel review
Co-authored-by: Adrien Foulon <6115458+Tofandel@users.noreply.github.com>
2024-10-31 08:48:16 +02:00
Michael Brevard
d81740d439
Delete docs/3.api/3.utils/create-visible-loader.md 2024-10-09 10:29:54 +03:00
Michael Brevard
725727f397
Delete docs/3.api/3.utils/create-idle-loader.md 2024-10-09 10:29:38 +03:00
Michael Brevard
352d47c644
Delete docs/3.api/3.utils/create-event-loader.md 2024-10-09 10:29:23 +03:00
Michael Brevard
c4123fa09f
docs: remove usage of unnecessary utils 2024-10-09 10:28:32 +03:00
Michael Brevard
bd831cac14
chore: remove hydration loaders 2024-10-09 10:24:35 +03:00
Michael Brevard
beedda3f2a
chore: remove hydration loaders due to props being typed 2024-10-09 10:23:15 +03: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 711 additions and 22 deletions

View File

@ -248,7 +248,7 @@ jobs:
TEST_PAYLOAD: ${{ matrix.payload }} TEST_PAYLOAD: ${{ matrix.payload }}
SKIP_BUNDLE_SIZE: ${{ github.event_name != 'push' || matrix.env == 'dev' || matrix.builder == 'webpack' || matrix.context == 'default' || matrix.payload == 'js' || runner.os == 'Windows' }} SKIP_BUNDLE_SIZE: ${{ github.event_name != 'push' || matrix.env == 'dev' || matrix.builder == 'webpack' || matrix.context == 'default' || matrix.payload == 'js' || runner.os == 'Windows' }}
- uses: codecov/codecov-action@5c47607acb93fed5485fdbf7232e8a31425f672a # v5.0.2 - uses: codecov/codecov-action@985343d70564a82044c1b7fcb84c2fa05405c1a2 # v5.0.4
if: github.event_name != 'push' && matrix.env == 'built' && matrix.builder == 'vite' && matrix.context == 'default' && matrix.os == 'ubuntu-latest' && matrix.manifest == 'manifest-on' if: github.event_name != 'push' && matrix.env == 'built' && matrix.builder == 'vite' && matrix.context == 'default' && matrix.os == 'ubuntu-latest' && matrix.manifest == 'manifest-on'
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}

View File

@ -123,6 +123,176 @@ 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 properties that will handle this delayed hydration for you, that extend dynamic imports. By using the `hydrate:visible` prop, Nuxt will automatically handle your component and delay its hydration until it will be on screen.
```vue [pages/index.vue]
<template>
<div>
<LazyMyComponent hydrate:visible />
</div>
</template>
```
If you need the component to load as soon as possible, but not block the critical rendering path, you can use the `hdrate:idle` prop, which would handle your component's hydration whenever the browser goes idle.
```vue [pages/index.vue]
<template>
<div>
<LazyMyComponent hydrate:idle />
</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 `hydrate:event` prop, which would only trigger the hydration when those events occur.
```vue [pages/index.vue]
<template>
<div>
<LazyMyComponent hydrate:event />
</div>
</template>
```
If you would like to load the component when the window matches a media query, you can use the `hydrate:media` prop:
```vue [pages/index.vue]
<template>
<div>
<LazyMyComponent hydrate:media />
</div>
</template>
```
If you would like to never hydrate a component, use the `hydrate:never` prop:
```vue [pages/index.vue]
<template>
<div>
<LazyMyComponent hydrate:never />
</div>
</template>
```
If you would like to hydrate a component after a certain amount of time, use the `hydrate:time` prop:
```vue [pages/index.vue]
<template>
<div>
<LazyMyComponent hydrate:time />
</div>
</template>
```
If you would like to hydrate a component once a promise is fulfilled, use the `hydrate:promise` prefix:
```vue [pages/index.vue]
<template>
<div>
<LazyMyComponent hydrate:promise />
</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 hydration, you can use the general purpose `hydrate:if` prop:
```vue [pages/index.vue]
<template>
<div>
<button @click="myFunction">Click me to start the custom hydration strategy</button>
<LazyMyComponent :hydrate:if="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 value to the respetive props of your lazy components.
```vue [pages/index.vue]
<template>
<div>
<LazyMyComponent :hydrate:idle="3000" />
<LazyMyComponent :hydrate:visible="{threshold: 0.2}" />
<LazyMyComponent :hydrate:event="['click','mouseover']" />
<LazyMyComponent hydrate:media="(max-width: 500px)" />
<LazyMyComponent :hydrate:if="someCondition" />
<LazyMyComponent :hydrate:time="3000" />
<LazyMyComponent :hydrate:promise="promise" />
</div>
</template>
<script setup lang="ts">
const someCondition = ref(true)
const promise = Promise.resolve(42)
</script>
```
::read-more{to="https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia"}
Read more about using `LazyMedia` components and the accepted values.
::
### 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>
<LazyMyComponent hydrate:visible @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 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 parts of the app that depend 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 `hydrate:if` for always/never hydrated components (`:hydrate:if="true"`/`:hydrate:if="false"`) - you can use a regular component/`hydrate:never` 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 `hydrate:time` as an alternative to `hydrate:idle` - while these strategies share similarities, they are meant for different purposes. `hydrate:time` is specifically designed to hydrate a component immediately after a certain amount of time has passed. `hydrate:idle`, on the other hand, is meant to provide a limit for the browser to handle the hydration whenever it's idle. If you use `hydrate:time` 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 `hydrate:if` to manually implement existing hydration strategies - while an option, using only `hydrate:if` 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 `hydrate:if`, using `hydrate:promise` 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 `hydrate:if` 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

@ -91,7 +91,7 @@
"devalue": "5.1.1", "devalue": "5.1.1",
"eslint": "9.15.0", "eslint": "9.15.0",
"eslint-plugin-no-only-tests": "3.3.0", "eslint-plugin-no-only-tests": "3.3.0",
"eslint-plugin-perfectionist": "4.0.2", "eslint-plugin-perfectionist": "4.0.3",
"eslint-typegen": "0.3.2", "eslint-typegen": "0.3.2",
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e", "h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
"happy-dom": "15.11.6", "happy-dom": "15.11.6",
@ -118,7 +118,7 @@
"vue-router": "4.4.5", "vue-router": "4.4.5",
"vue-tsc": "2.1.10" "vue-tsc": "2.1.10"
}, },
"packageManager": "pnpm@9.13.2", "packageManager": "pnpm@9.14.1",
"engines": { "engines": {
"node": "^16.10.0 || >=18.0.0" "node": "^16.10.0 || >=18.0.0"
}, },

View File

@ -481,7 +481,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) {
@ -489,7 +489,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) => {

View File

@ -13,6 +13,7 @@ import { ComponentsChunkPlugin, IslandsTransformPlugin } from './plugins/islands
import { TransformPlugin } from './plugins/transform' import { TransformPlugin } from './plugins/transform'
import { TreeShakeTemplatePlugin } from './plugins/tree-shake' import { TreeShakeTemplatePlugin } from './plugins/tree-shake'
import { ComponentNamePlugin } from './plugins/component-names' import { ComponentNamePlugin } from './plugins/component-names'
import { DelayedHydrationPlugin } from './plugins/delayed-hydration'
const isPureObjectOrString = (val: any) => (!Array.isArray(val) && typeof val === 'object') || typeof val === 'string' const isPureObjectOrString = (val: any) => (!Array.isArray(val) && typeof val === 'object') || typeof val === 'string'
const isDirectory = (p: string) => { try { return statSync(p).isDirectory() } catch { return false } } const isDirectory = (p: string) => { try { return statSync(p).isDirectory() } catch { return false } }
@ -223,13 +224,17 @@ export default defineNuxtModule<ComponentsOptions>({
addBuildPlugin(ClientFallbackAutoIdPlugin({ sourcemap: !!nuxt.options.sourcemap.server, rootDir: nuxt.options.rootDir }), { client: false }) addBuildPlugin(ClientFallbackAutoIdPlugin({ sourcemap: !!nuxt.options.sourcemap.server, rootDir: nuxt.options.rootDir }), { client: false })
} }
const clientDelayedComponentRuntime = await findPath(join(distDir, 'components/runtime/client-delayed-component')) ?? join(distDir, 'components/runtime/client-delayed-component')
const sharedLoaderOptions = { const sharedLoaderOptions = {
getComponents, getComponents,
serverComponentRuntime, serverComponentRuntime,
transform: typeof nuxt.options.components === 'object' && !Array.isArray(nuxt.options.components) ? nuxt.options.components.transform : undefined, transform: typeof nuxt.options.components === 'object' && !Array.isArray(nuxt.options.components) ? nuxt.options.components.transform : undefined,
experimentalComponentIslands: !!nuxt.options.experimental.componentIslands, experimentalComponentIslands: !!nuxt.options.experimental.componentIslands,
clientDelayedComponentRuntime,
}
if (nuxt.options.experimental.delayedHydration) {
addBuildPlugin(DelayedHydrationPlugin({ sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client }))
} }
addBuildPlugin(LoaderPlugin({ ...sharedLoaderOptions, sourcemap: !!nuxt.options.sourcemap.client, mode: 'client' }), { server: false }) addBuildPlugin(LoaderPlugin({ ...sharedLoaderOptions, sourcemap: !!nuxt.options.sourcemap.client, mode: 'client' }), { server: false })
addBuildPlugin(LoaderPlugin({ ...sharedLoaderOptions, sourcemap: !!nuxt.options.sourcemap.server, mode: 'server' }), { client: false }) addBuildPlugin(LoaderPlugin({ ...sharedLoaderOptions, sourcemap: !!nuxt.options.sourcemap.server, mode: 'server' }), { client: false })

View File

@ -0,0 +1,35 @@
import { createUnplugin } from 'unplugin'
import { logger } from '@nuxt/kit'
import MagicString from 'magic-string'
import { isVue } from '../../core/utils'
export const DelayedHydrationPlugin = (options: { sourcemap: boolean }) => createUnplugin(() => {
const DELAYED_HYDRATION_RE = /<(?:Lazy|lazy-)([A-Z]\w*|[\w-]+)\b([^>]+)hydrate:(\w+)(="([^"]+)")?([ />])([^>]*(?:(?<=\/)|>[\s\S]+?<\/Lazy[A-Z]\w*|lazy-[\w-]+))>/g
const types = new Map([['time', 'Time'], ['promise', 'Promise'], ['if', 'If'], ['event', 'Event'], ['visible', 'Visible'], ['media', 'Media'], ['never', 'Never'], ['idle', 'Idle']])
const correctVals = new Map([['time', 'number'], ['promise', 'Promise'], ['event', 'string | string[]'], ['visible', 'IntersectionObserverInit'], ['media', 'string'], ['idle', 'number']])
return {
name: 'nuxt:delayed-hydration',
enforce: 'pre',
transformInclude (id) {
return isVue(id)
},
transform (code, id) {
const s = new MagicString(code)
s.replace(DELAYED_HYDRATION_RE, (_, comp, pre, type, selfOrValue, val, nearEndChar, post) => {
const shouldDefault = type !== 'if' && (val === 'true' || val === 'false')
if (!types.has(type)) { logger.warn(`Unexpected hydration strategy \`${type}\` when parsing \`${_}\` in \`${id}\`, this will default to visibility. For custom strategies, please use \`v-hydrate:if\` in stead.`) }
if (type === 'never' && selfOrValue) { logger.warn('`hydrate:never` does not accept any value. It is meant to be used as is, and the value will not affect its runtime behavior.') }
if (shouldDefault) { logger.warn(`Invalid value \`${val}\` for \`hydrate:${type}\` when parsing \`${_}\` in \`${id}\`. The prop is not meant to be assigned a boolean, but used as is or given ${type === 'never' ? 'no value' : `a value of type \`${correctVals.get(type) ?? 'unknown'}\``}. This will not affect runtime behavior and the defaults will be used in stead.`) }
return `<Lazy${types.get(type) ?? 'Visible'}${comp}${pre}${type == 'never' || shouldDefault ? '' : `:hydrate="${val}"`}${nearEndChar}${post}>`
})
if (s.hasChanged()) {
return {
code: s.toString(),
map: options.sourcemap
? s.generateMap({ hires: true })
: undefined,
}
}
},
}
})

View File

@ -12,12 +12,13 @@ interface LoaderOptions {
getComponents (): Component[] getComponents (): Component[]
mode: 'server' | 'client' mode: 'server' | 'client'
serverComponentRuntime: string serverComponentRuntime: string
clientDelayedComponentRuntime: string
sourcemap?: boolean sourcemap?: boolean
transform?: ComponentsOptions['transform'] transform?: ComponentsOptions['transform']
experimentalComponentIslands?: boolean experimentalComponentIslands?: boolean
} }
const REPLACE_COMPONENT_TO_DIRECT_IMPORT_RE = /(?<=[ (])_?resolveComponent\(\s*["'](lazy-|Lazy(?=[A-Z]))?([^'"]*)["'][^)]*\)/g const REPLACE_COMPONENT_TO_DIRECT_IMPORT_RE = /(?<=[ (])_?resolveComponent\(\s*["'](lazy-|Lazy(?=[A-Z]))?(Idle|Visible|idle-|visible-|Event|event-|Media|media-|If|if-|Never|never-|Time|time-|Promise|promise-)?([^'"]*)["'][^)]*\)/g
export const LoaderPlugin = (options: LoaderOptions) => createUnplugin(() => { export const LoaderPlugin = (options: LoaderOptions) => createUnplugin(() => {
const exclude = options.transform?.exclude || [] const exclude = options.transform?.exclude || []
const include = options.transform?.include || [] const include = options.transform?.include || []
@ -42,12 +43,13 @@ export const LoaderPlugin = (options: LoaderOptions) => createUnplugin(() => {
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)
// replace `_resolveComponent("...")` to direct import // replace `_resolveComponent("...")` to direct import
s.replace(REPLACE_COMPONENT_TO_DIRECT_IMPORT_RE, (full: string, lazy: string, name: string) => { s.replace(REPLACE_COMPONENT_TO_DIRECT_IMPORT_RE, (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) {
// TODO: refactor to nuxi
const internalInstall = ((component as any)._internal_install) as string const internalInstall = ((component as any)._internal_install) as string
if (internalInstall && nuxt?.options.test === false) { if (internalInstall && nuxt?.options.test === false) {
if (!nuxt.options.dev) { if (!nuxt.options.dev) {
@ -77,9 +79,63 @@ export const LoaderPlugin = (options: LoaderOptions) => createUnplugin(() => {
} }
if (lazy) { if (lazy) {
const dynamicImport = `${genDynamicImport(component.filePath, { interopDefault: false })}.then(c => c.${component.export ?? 'default'} || c)`
if (modifier && normalComponent && nuxt?.options.experimental.delayedHydration === true) {
switch (modifier) {
case 'Visible':
case 'visible-':
imports.add(genImport(options.clientDelayedComponentRuntime, [{ name: 'createLazyIOComponent' }]))
identifier += '_delayedIO'
imports.add(`const ${identifier} = createLazyIOComponent(${dynamicImport})`)
break
case 'Event':
case 'event-':
imports.add(genImport(options.clientDelayedComponentRuntime, [{ name: 'createLazyEventComponent' }]))
identifier += '_delayedEvent'
imports.add(`const ${identifier} = createLazyEventComponent(${dynamicImport})`)
break
case 'Idle':
case 'idle-':
imports.add(genImport(options.clientDelayedComponentRuntime, [{ name: 'createLazyNetworkComponent' }]))
identifier += '_delayedNetwork'
imports.add(`const ${identifier} = createLazyNetworkComponent(${dynamicImport})`)
break
case 'Media':
case 'media-':
imports.add(genImport(options.clientDelayedComponentRuntime, [{ name: 'createLazyMediaComponent' }]))
identifier += '_delayedMedia'
imports.add(`const ${identifier} = createLazyMediaComponent(${dynamicImport})`)
break
case 'If':
case 'if-':
imports.add(genImport(options.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(options.clientDelayedComponentRuntime, [{ name: 'createLazyTimeComponent' }]))
identifier += '_delayedTime'
imports.add(`const ${identifier} = createLazyTimeComponent(${dynamicImport})`)
break
case 'Promise':
case 'promise-':
imports.add(genImport(options.clientDelayedComponentRuntime, [{ name: 'createLazyPromiseComponent' }]))
identifier += '_delayedPromise'
imports.add(`const ${identifier} = createLazyPromiseComponent(${dynamicImport})`)
break
}
} else {
imports.add(genImport('vue', [{ name: 'defineAsyncComponent', as: '__defineAsyncComponent' }])) imports.add(genImport('vue', [{ name: 'defineAsyncComponent', as: '__defineAsyncComponent' }]))
identifier += '_lazy' identifier += '_lazy'
imports.add(`const ${identifier} = __defineAsyncComponent(${genDynamicImport(component.filePath, { interopDefault: false })}.then(c => c.${component.export ?? 'default'} || c)${isClientOnly ? '.then(c => createClientOnly(c))' : ''})`) 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

@ -116,14 +116,15 @@ 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 TypeWithDefault<T> = boolean | T\ntype HydrationStrategy = "idle" | "event" | "promise" | "if" | "visible" | "time" | "never"\ntype HydrationMap = { idle: number, event: string | string[], promise: Promise<unknown>, if: boolean | unknown, visible: IntersectionObserverInit, time: number, never: boolean }\ntype DelayedComponent<T extends DefineComponent> = T & DefineComponent<{[K in keyof HydrationMap as \`hydrate:$\{K}\`]?: TypeWithDefault<HydrationMap[K]>},{},{},{},{},{},{hydrated: void},{},{},{},{},{},{},{},{hydrate:{}}>`
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}': DelayedComponent<${type}>`).join('\n')}
} }
declare module 'vue' { declare module 'vue' {
@ -131,7 +132,7 @@ 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}: DelayedComponent<${type}>`).join('\n')}
export const componentNames: string[] export const componentNames: string[]
` `

View File

@ -219,6 +219,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

@ -114,8 +114,8 @@ importers:
specifier: 3.3.0 specifier: 3.3.0
version: 3.3.0 version: 3.3.0
eslint-plugin-perfectionist: eslint-plugin-perfectionist:
specifier: 4.0.2 specifier: 4.0.3
version: 4.0.2(eslint@9.15.0(jiti@2.4.0))(typescript@5.6.3) version: 4.0.3(eslint@9.15.0(jiti@2.4.0))(typescript@5.6.3)
eslint-typegen: eslint-typegen:
specifier: 0.3.2 specifier: 0.3.2
version: 0.3.2(eslint@9.15.0(jiti@2.4.0)) version: 0.3.2(eslint@9.15.0(jiti@2.4.0))
@ -4373,8 +4373,8 @@ packages:
resolution: {integrity: sha512-brcKcxGnISN2CcVhXJ/kEQlNa0MEfGRtwKtWA16SkqXHKitaKIMrfemJKLKX1YqDU5C/5JY3PvZXd5jEW04e0Q==} resolution: {integrity: sha512-brcKcxGnISN2CcVhXJ/kEQlNa0MEfGRtwKtWA16SkqXHKitaKIMrfemJKLKX1YqDU5C/5JY3PvZXd5jEW04e0Q==}
engines: {node: '>=5.0.0'} engines: {node: '>=5.0.0'}
eslint-plugin-perfectionist@4.0.2: eslint-plugin-perfectionist@4.0.3:
resolution: {integrity: sha512-zWdgyg2SdHqhp/P9d9vKwo5qD9is28xMAGzBslHqkZz5mVIikjyz1qvuJ4yS7Wrsf4KlbGorORefb4Kbe7Puzg==} resolution: {integrity: sha512-CyafnreF6boy4lf1XaF72U8NbkwrfjU/mOf1y6doaDMS9zGXhUU1DSk+ZPf/rVwCf1PL1m+rhHqFs+IcB8kDmA==}
engines: {node: ^18.0.0 || >=20.0.0} engines: {node: ^18.0.0 || >=20.0.0}
peerDependencies: peerDependencies:
eslint: '>=8.0.0' eslint: '>=8.0.0'
@ -11519,7 +11519,7 @@ snapshots:
eslint-plugin-no-only-tests@3.3.0: {} eslint-plugin-no-only-tests@3.3.0: {}
eslint-plugin-perfectionist@4.0.2(eslint@9.15.0(jiti@2.4.0))(typescript@5.6.3): eslint-plugin-perfectionist@4.0.3(eslint@9.15.0(jiti@2.4.0))(typescript@5.6.3):
dependencies: dependencies:
'@typescript-eslint/types': 8.15.0 '@typescript-eslint/types': 8.15.0
'@typescript-eslint/utils': 8.15.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.6.3) '@typescript-eslint/utils': 8.15.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.6.3)

View File

@ -2808,6 +2808,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

@ -172,6 +172,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,
}, },
compatibilityDate: '2024-06-28', compatibilityDate: '2024-06-28',
nitro: { nitro: {

View File

@ -3,5 +3,37 @@
<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" />
<LazyDelayedEvent
id="lazyevent"
hydrate:event
/>
<LazyEventView />
<LazyDelayedVisible hydrate:visible />
<LazyDelayedNever hydrate:never />
<LazyDelayedEvent
id="lazyevent2"
:hydrate:event="['click']"
/>
<LazyDelayedCondition
id="lazycondition"
hydrate:if
/>
<button
id="conditionbutton"
@click="state++"
/>
<LazyDelayedCondition
id="lazycondition2"
:hydrate:if="state > 1"
/>
<LazyDelayedNetwork hydrate:idle />
<div style="height:3000px">
This is a very tall div
</div>
<LazyDelayedVisible hydrate:visible />
</div> </div>
</template> </template>
<script setup lang="ts">
const state = useState('delayedHydrationCondition', () => 1)
</script>

View File

@ -0,0 +1,16 @@
<template>
<div>
<LazyDelayedModel
v-model="model"
hydrate:event
@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>
<LazyDelayedPromise
:hydrate:promise="pr"
/>
<LazyDelayedPromise
:hydrate:promise="prImmediate"
/>
</div>
</template>

View File

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