You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The heart of Stencil is its ability to automatically segment code and load things dynamically. With a fast network and no errors this is a great experience for developers and end users and can provide high performance UI coupled with smart code loading.
It is easy, however, for developers to forget that async calls have the potential to be very slow and that errors can occur. Handling the cases where calls are slow or fail is very hard to do and requires handling at multiple places in the stack (Stencil, Ionic Core, and developer code).
Some examples in Ionic Core where slow performance can lead to problems include:
Rather than repeating all of the details here you can refer to these specific write-ups for more details about the issues that arise.
Part of the challenge is that Stencil makes it easy to do things that will not perform well on slow networks and it is extremely difficult to fully handle these situations without architecting it through all the layers (Stencil, Ionic Core and develolper code). The intent here is not to document how to make things faster but rather how to deal with things when they happen to be slow or fail.
Stencil 2.0 provides an opportunity for breaking changes that may not happen again for a while so it seems like a good time to look at potential improvements related to async loading and error handling.
Key areas for potential improvement include:
handling slow network latency
handling network errors
improved rendering approaches for components that display async data
hooks for ui cues related to in progress rendering
hooks for ui cues related to errors
This document attempts to provide an overview of some the issues and possible approaches vs. providing details about a single potential new feature. I considered splitting out smaller portions of the issue (sorry it is so long) but many things might be interrelated so I tried to create a broader view.
Handling slow network latency:
Consider the case of a Stencil based web application running on a desktop browser and using a slow network. A key issue that impacts the Stencil architecture is the latency even if the download speed is high. I recently had a user with a Comcast cable modem at 50mb download that was experiencing 400ms latency. That can have a huge impact if many round trips are required.
Running a Stencil based website with a simulated Slow 3G network in DevTools is an easy way to notice what happens with a slow network. If the code is not already on the machine then the user experience is pretty bad.
Part of the promise of Stencil is that it handles all the async loading so that you don't even need to think about it.
I created a relatively simple page in Stencil / Ionic Core that happened to use a number of nested components. Something like <my-page> renders <my-comp-a> which renders <my-comp-b> which renders <my-comp-c>. Nothing too fancy (all tiny components) and everything works great during dev.
So what happens when I try to display this page with a Slow 3G network (with 2s latency)?
1. click on a button to navigate to the page. `<ion-nav>` is used to display the page.
2. `<my-page>` js file is loaded (takes 2s)
3. my-page import depencies are loaded (2s)
4. my-page.render() is called
5. `<my-comp-a>` js file is loaded (2s)
6. my-comp-a import dependencies are loaded (2s)
7. my-comp-a.render() is called
8. `<my-comp-b>` js file is loaded (2s)
9. my-comp-b import dependencies are loaded (2s)
10. my-comp-b.render() is called
11. `<my-comp-c>` js file is loaded (2s)
12. my-comp-c import dependencies are loaded (2s)
13. my-comp-c.render() is called
14. the page is displayed. At this point 16 seconds have elapsed
Yes this case is a bit contrived because components are likely to be in the cache, dependencies are likely to be loaded, etc. But it can and does happen (especially with complex design systems and not one person in control). And it doesn't even include async data requests that would add more time. I had an actual page in an app with lots of component nesting that worked fine during dev. It worked pretty well doing a refresh over the internet with a fast connection, but took 32 seconds with Slow 3G. So the promise of "auto-magic" loading is not quite enough for these situations.
So there are things that can be done to try to parallelize some of the component loading but this is fairly complicated for the developer and hard to handle all permutations of how components are used. Stencil actually does an extremely good job of pre-requesting downstream imports to reduce round trips for code dependencies. An observation here is that no matter what you do there are situations where rendering may be slow. Faster is always better but additionally it is important to be able to better handle situations where it is slow and be able to convey it to the user.
Rendering async data:
A common pattern for web applications is to display data coming from some data source that is not local. A typical implementation of this pattern is to have the component load the data using fetch or some other request mechanism and then display the results.
There are a number of potential issues with this approach, however. These include:
For any async operation the developer must get the data in both componentWillLoad and componentWillUpdate.
If the async data request is slow then nothing is rendered.
If there are any slow async calls in componentWillLoad or componentWillRender then not only does the component not render but rendering stops up the entire rendering stack. For example a page that includes many components and one of them loads data that happens to take a lot of time then the entire page does not render.
It is important for some components to render even if child components are in progress. For example, an application page should be able to at least display a back button if some of the child content is taking a long time to load. Otherwise the user just sees a blank page and can't do anything (or in the case of ion-nav has no idea that anything is happening).
Because of these issues I have taken the following approach for components that load data.
never load data in componentWillLoad to avoid blocking rendering
never load data in componentWillRender to avoid blocking rendering
keep data state and branch in render to handle appropriately
timeouts forcing update on async completion to update when state changes
this way all components render as quickly as possible (at least from a data standpoint)
it is a bit painful to do this in Stencil without defining a base class because there is lots of repeated code (even when using utility functions) that is hard to maintain
Stencil is in a great position to make this clean and easy
UI for in progress rendering:
It is sometimes important to indicate to the user that something is in progress and will be rendered later rather than jut being blank for slow operations. A typical approach is starting as blank, changing to progress indicator after some delay, changing to eventual result when done.
There are a number of things that could lead to an in progress state including
waiting for the component entry.js file to load
waiting for component import dependencies to load
waiting for async calculations including application data loading
waiting for child components to load
Advanced handling could potentially include
timeouts
ability to cancel loading (e.g. waiting for page to load and want to go back)
In general, the current behavior for things not caught and handled within developer code is:
components do not render anything
modals do nothing if component fails to load
navigation does nothing if component fails to load
Summary:
The intent of this list is not to complain, but rather to illustrate some of the challenges that developers face when building applications. Most frameworks suffer from similar challenges. Stencil is great but there are always things that can make it better. Good handling for async code and errors is an extremely difficult thing to handle well. Hopefully this list will trigger some ideas for future Stencil improvements.
There has been a transition from React style pure functional rendering (big js file and all sync) to Stencil style async code (large number of small js files and everything async). The optimal solution is probably somewhere in between.
Some ideas for improvements:
State information for components. Something like Loading / In Progress / OK / Error. This information would allow rendering logic to display appropriate ui depending on the state. Developer code would be able to alter state (indicate in progress or errors) to reflect things like external database calls. I am doing this currently for data components but it ends up being pretty coplicated to do well and awkward to generalize without being able to implement in a base class.
Support async render function. For example, render(): JSX | Promise<JSX>. This is somewhat supported by componentWillRender and componentDidRender but it seems like it would be simpler to handle it in a single method - especially if any data was being used. For example, error getting async data and wanting to render an error message. There would be no impact on existing code since sync result would still work and only components that needed the feature would use it. Async rendering is especially important if you want to call any methods on other components while rendering. Because they are async there is not way to do that now.
Support render arguments. For example, render(context?: RenderContext): JSX | Promise<JSX>. There is a lot of information that could be useful during rendering and simplify the process. Things like what caused the update (initial render / props changed / state changed / forceUpdate / etc.), the target element, the componenent state, etc. Using an optional param would avoid breaking existing code, avoid complicating components that don't need it, and help components that benefit from it. This might also simplify some of the current lifecycle methods so that fewer are needed.
Intelligent try/catch for all lifecycle methods. While every method could implement it's own try/catch handler in practice very few do this. Even within Ionic Core there is very little try/catch handling
Rendering fallbacks at higher levels. While some ui for errors and async state can be handled directly within a component, others can not be handled there. Things like loading errors (where the component js fails to load) can't use component rendering. Other cases may include parent components that do not want to show partial content while waiting for child rendering. This could be done like in react with a fallback lifecycle method. It also might be useful to have a global hook or event so the developer can take action.
The text was updated successfully, but these errors were encountered:
Here is an example of a library which attempts to improve upon React style JSX with some innovative async rendering. Not directly applicable to Stencil, but pretty interesting to see async rendering as a primary design driver.
never load data in componentWillLoad to avoid blocking rendering
never load data in componentWillRender to avoid blocking rendering
timeouts forcing update on async completion to update when state changes
Loading data in componentWillLoad will not block rendering as long as you don't return a Promise or use async componentWillLoad (same for willUpdate). This way you don't need to use timeouts. I usually load the data from an async method which I call in willLoad (without await) and in the render method I check if this.data is:
null => show loader
an empty array => render "no data"
a non-empty array => render the data
You might want to return a Promise while prerendering though so the prerendered version includes the data.
Stencil version:
I'm submitting a:
[x] feature request
Summary:
The heart of Stencil is its ability to automatically segment code and load things dynamically. With a fast network and no errors this is a great experience for developers and end users and can provide high performance UI coupled with smart code loading.
It is easy, however, for developers to forget that async calls have the potential to be very slow and that errors can occur. Handling the cases where calls are slow or fail is very hard to do and requires handling at multiple places in the stack (Stencil, Ionic Core, and developer code).
Some examples in Ionic Core where slow performance can lead to problems include:
Rather than repeating all of the details here you can refer to these specific write-ups for more details about the issues that arise.
Part of the challenge is that Stencil makes it easy to do things that will not perform well on slow networks and it is extremely difficult to fully handle these situations without architecting it through all the layers (Stencil, Ionic Core and develolper code). The intent here is not to document how to make things faster but rather how to deal with things when they happen to be slow or fail.
Stencil 2.0 provides an opportunity for breaking changes that may not happen again for a while so it seems like a good time to look at potential improvements related to async loading and error handling.
Key areas for potential improvement include:
This document attempts to provide an overview of some the issues and possible approaches vs. providing details about a single potential new feature. I considered splitting out smaller portions of the issue (sorry it is so long) but many things might be interrelated so I tried to create a broader view.
Handling slow network latency:
Consider the case of a Stencil based web application running on a desktop browser and using a slow network. A key issue that impacts the Stencil architecture is the latency even if the download speed is high. I recently had a user with a Comcast cable modem at 50mb download that was experiencing 400ms latency. That can have a huge impact if many round trips are required.
Running a Stencil based website with a simulated
Slow 3G
network in DevTools is an easy way to notice what happens with a slow network. If the code is not already on the machine then the user experience is pretty bad.Part of the promise of Stencil is that it handles all the async loading so that you don't even need to think about it.
I created a relatively simple page in Stencil / Ionic Core that happened to use a number of nested components. Something like
<my-page>
renders<my-comp-a>
which renders<my-comp-b>
which renders<my-comp-c>
. Nothing too fancy (all tiny components) and everything works great during dev.So what happens when I try to display this page with a
Slow 3G
network (with 2s latency)?Yes this case is a bit contrived because components are likely to be in the cache, dependencies are likely to be loaded, etc. But it can and does happen (especially with complex design systems and not one person in control). And it doesn't even include async data requests that would add more time. I had an actual page in an app with lots of component nesting that worked fine during dev. It worked pretty well doing a refresh over the internet with a fast connection, but took 32 seconds with Slow 3G. So the promise of "auto-magic" loading is not quite enough for these situations.
So there are things that can be done to try to parallelize some of the component loading but this is fairly complicated for the developer and hard to handle all permutations of how components are used. Stencil actually does an extremely good job of pre-requesting downstream imports to reduce round trips for code dependencies. An observation here is that no matter what you do there are situations where rendering may be slow. Faster is always better but additionally it is important to be able to better handle situations where it is slow and be able to convey it to the user.
Rendering async data:
A common pattern for web applications is to display data coming from some data source that is not local. A typical implementation of this pattern is to have the component load the data using
fetch
or some other request mechanism and then display the results.There are a number of potential issues with this approach, however. These include:
componentWillLoad
andcomponentWillUpdate
.componentWillLoad
orcomponentWillRender
then not only does the component not render but rendering stops up the entire rendering stack. For example a page that includes many components and one of them loads data that happens to take a lot of time then the entire page does not render.Because of these issues I have taken the following approach for components that load data.
UI for in progress rendering:
It is sometimes important to indicate to the user that something is in progress and will be rendered later rather than jut being blank for slow operations. A typical approach is starting as blank, changing to progress indicator after some delay, changing to eventual result when done.
There are a number of things that could lead to an in progress state including
Advanced handling could potentially include
UI for errors:
Errors can come from a number of sources:
In general, the current behavior for things not caught and handled within developer code is:
Summary:
The intent of this list is not to complain, but rather to illustrate some of the challenges that developers face when building applications. Most frameworks suffer from similar challenges. Stencil is great but there are always things that can make it better. Good handling for async code and errors is an extremely difficult thing to handle well. Hopefully this list will trigger some ideas for future Stencil improvements.
There has been a transition from React style pure functional rendering (big js file and all sync) to Stencil style async code (large number of small js files and everything async). The optimal solution is probably somewhere in between.
Some ideas for improvements:
render(): JSX | Promise<JSX>
. This is somewhat supported bycomponentWillRender
andcomponentDidRender
but it seems like it would be simpler to handle it in a single method - especially if any data was being used. For example, error getting async data and wanting to render an error message. There would be no impact on existing code since sync result would still work and only components that needed the feature would use it. Async rendering is especially important if you want to call any methods on other components while rendering. Because they are async there is not way to do that now.render(context?: RenderContext): JSX | Promise<JSX>
. There is a lot of information that could be useful during rendering and simplify the process. Things like what caused the update (initial render / props changed / state changed / forceUpdate / etc.), the target element, the componenent state, etc. Using an optional param would avoid breaking existing code, avoid complicating components that don't need it, and help components that benefit from it. This might also simplify some of the current lifecycle methods so that fewer are needed.The text was updated successfully, but these errors were encountered: