ElMassimo / iles

🏝 The joyful site generator
https://iles.pages.dev
MIT License
1.08k stars 31 forks source link

"RangeError: Maximum call stack size exceeded" when stacking components with client scripts #128

Closed mseele closed 2 years ago

mseele commented 2 years ago

Description πŸ“–

When you stack components with client scripts (client:idle / client:load) you get an RangeError: Maximum call stack size exceeded.

Reproduction 🐞

https://stackblitz.com/edit/iles-uiy2vm?file=src/components/ScriptOne.vue

~/projects/iles-uiy2vm
❯ yarn build
$ iles build
iles v0.7.34 vite v2.8.6
βœ” building client + server bundles
  done in 1.1s

βœ” resolving static paths
  done in 5ms

βœ” rendering pages
  done in 11ms

⚠ Skipping sitemap. Configure `siteUrl` to enable sitemap generation.
βœ” building islands bundle
  done in 475ms

βœ– writing pages
build error:
 RangeError: Maximum call stack size exceeded
    at eval (/home/projects/iles-uiy2vm/node_modules/iles/dist/node/chunk-QFNLXJ42.js:75:5)
    at Array.flatMap (<anonymous>)
    at resolveManifestEntries (/home/projects/iles-uiy2vm/node_modules/iles/dist/node/chunk-QFNLXJ42.js:73:21)
    at eval (/home/projects/iles-uiy2vm/node_modules/iles/dist/node/chunk-QFNLXJ42.js:75:28)
    at Array.flatMap (<anonymous>)
    at resolveManifestEntries (/home/projects/iles-uiy2vm/node_modules/iles/dist/node/chunk-QFNLXJ42.js:73:21)
    at eval (/home/projects/iles-uiy2vm/node_modules/iles/dist/node/chunk-QFNLXJ42.js:75:28)
    at Array.flatMap (<anonymous>)
    at resolveManifestEntries (/home/projects/iles-uiy2vm/node_modules/iles/dist/node/chunk-QFNLXJ42.js:73:21)
    at eval (/home/projects/iles-uiy2vm/node_modules/iles/dist/node/chunk-QFNLXJ42.js:75:28)
ElMassimo commented 2 years ago

Hi Michael, thanks for reporting this!

Currently, client scripts are implemented using islands (for simplicity), so you can't stack them.

This information is missing in documentation, I'll keep this open as a reminder to cover that, and perhaps explore a different way to render nested client scripts since it's a valid use case (unlike with islands).

ElMassimo commented 2 years ago

Initially I thought you were referring to Client Scripts.

After looking at the example you've linked, I'm afraid this is just incorrect usage.

Once you define a boundary with a client directive, anything inside that component (and its children) will be compiled as a Vue app, so having inner hydration directives would not make sense (they would be hydrated whenever the island is).

To fix the problem, remove the client:load directive in the inner child (in your example, ScriptTwo), it will remain interactive because it's already inside an island.

ouuan commented 2 years ago

To fix the problem, remove the client:load directive in the inner child (in your example, ScriptTwo), it will remain interactive because it's already inside an island.

What about the inner component being used in different outer components, where some outer components are interactive but others are not?

I mean, <component-a client:load /> in component b, and <component-b client:load /> in component c but <component-b /> in component d. A workaround is to make component b interactive in component d, which is acceptable but may increase the bundle size.

ElMassimo commented 2 years ago

Can you share a use case where you have this scenario? Seems unlikely that the same component would be both interactive and static.

Also, from the bundle size perspective, if b is ever interactive in any page, the same script would be reused across pages.

ouuan commented 2 years ago

Sorry, the example was incorrect. It should be b always non-interactive, c interactive and d non-interactive.

ouuan commented 2 years ago

It will be good if the inner client directives can be automatically ignored when there are outer ones.

Also, from the bundle size perspective, if b is ever interactive in any page, the same script would be reused across pages.

The total bundle size is virtually the same, but the island script needs to be loaded on more pages.

ouuan commented 2 years ago

Besides the bundle size, this can also make components more modular, as I don't need to care whether a component is used within an island or not.

ElMassimo commented 2 years ago

Unfortunately it's not possible to statically analyze this type of usage, so supporting this would greatly increase the complexity of the Islands runtime.

I don't discard supporting this in the future, but I want to see real-life use cases that don't have easy workarounds before embarking on that exploration.

Please share a real site/app/use case if you are interested in this type of usage.

ouuan commented 2 years ago

Please share a real site/app/use case if you are interested in this type of usage.

I have been working on my new blog these days. It's still a WIP and will hopefully be published this week.

In short, I want to display a few dynamic stats like relative creation/modification time and visitor count in the header of a blog post. And I also want to display the post header on the search result page. The post header is static on other pages but dynamic on the search result page. The bundle size is small so it's not a big problem for me, though.

mseele commented 2 years ago

Please share a real site/app/use case if you are interested in this type of usage.

@ElMassimo please look at https://github.com/mseele/sve-website/tree/c79c8d2a840d39ab0a573d76fd9b2c915af2894f for a real site use case.

This is where I ran into that error:

  - EventContent      - EventDetails (client:load)          - NewsSubscription              - EmailSubscription (client:load)

and

  - EventsContent      - NewsSubscription          - EmailSubscription (client:load)

In the current version I worked around by duplicate a little code, remove EmailSubscription and make NewsSubscription client:load or not.

ElMassimo commented 2 years ago

Thanks for providing an example, now tracking in: