stencil icon indicating copy to clipboard operation
stencil copied to clipboard

Question: Handling components changes deployment with lazy loading

Open MatteoFeltrin opened this issue 11 months ago • 8 comments

I'm asking here this question because I cannot find anything similiar on the web. The only similar problem I found is this one: https://www.reddit.com/r/vuejs/comments/x3ge3w/how_to_handle_deployments_when_using_vue_router/?rdt=64644 Here they suggest to catch the error and reload the page but I find this solution really bad at a user-friendly level.

The problem explanation:

We have a component called <my-component> and we make a fresh build of Stencil. Inside the dist folder, the component would result built to something like: p-123abc.entry.js

And inside the stencil lazy-loader we would find something like this: dist/stencil/stencil.esm.js

...
...
[["p-123abc", [[1, "my-component", {
    param1: [1, 'something1'],
    param2: [1, 'something2'],
}]]
...
...

This tells to the lazy loader which is the name of the components, when it is required and thus needs to be loaded.

We now make a change inside this component and build again Stencil, now the component would be called like this: p-999999.entry.js

Now imagine this scenario: In our application we offer a web page where a user can monitor what is happening on the entire system. If he want more details info about an area, he can click on a specific field and a popup appears showing details information. This popup opens via an Ajax call (or how you prefer to call it) and contains <my-component>

Our user open its browser in the morning and navigates to this web page, he just want to monitor the situation and not to open any popup.

Now we deploy our fresh release with the component changed. p-123abc.entry.js will not exist anymore p-999999.entry.js is the new version of <my-component>

When the user loaded our web page, he downloaded dist/stencil/stencil.esm.js which, at that time, was configured with this parameter ...["p-123abc", [[1, "my-component"...

Now the user wants to open a popup... So Stencil tries to dynamically import p-123abc.entry.js resulting in this error: Constructor for "my-component#undefined" was not found

Image

The user can reload the page, restart the browser, restart the computer but he cannot resolve the problem because dist/stencil/stencil.esm.js is cached from the browser and so the web page is broken until the browser decides to refresh its cache...

I cannot find anything related to this problem, am I the only one falling into this? How would you handle this situation with lazy loading?

My temporary solution to this is to put the stencil loader under a "hash" that changes at every release, thus forcing the browser to reload all components at every release. This does not solve the problem at the root but at least resolve it when the user refresh the page.

Not related to this issue

Moreover, while debugging this problem, I found out that when you make a change inside one specific component, stencil changes the hash also of some other compontents that are not related to the changed component at all, thus making this problem bigger.

MatteoFeltrin avatar May 13 '25 08:05 MatteoFeltrin

Ist disabling the hashing an option for you? https://stenciljs.com/docs/config#hashfilenames

k-ruben avatar May 16 '25 12:05 k-ruben

@k-ruben sorry for late reply but had no time to look after this at all... Yes, this seems to be a working solution even if it may lead to having uncompatible versions of components working togheter. For example if in <my-component> i use <my-list> which is also used somewhere else, when I download the new version of <my-component> (because I have disabled name-hashing), I would have the new version of <my-component> using an old version of <my-list> (which, for example, was downloaded because it was used in the static page). I think this is a very uncommon edge case that can be ignored anyway.

What stinks to me is that I would not like to disable Stencil component hashing beucase i feel like I'm disabling a core feature... But I guess I have no other way? 🤷‍♂️

MatteoFeltrin avatar Jun 05 '25 10:06 MatteoFeltrin

What we do is, in the build process, we copy everything into a folder e.g. v1. If we have breaking changes (mainly only necessary in conjunction with breaking changes in the Api) we increase the version so new builds are copied to v2. New users get the new version and old users can still access the old files. If you want to keep the file hashing, your could do this but just for every build,

k-ruben avatar Jun 05 '25 20:06 k-ruben

Yeah, we thought about implementing some sort of component versioning but we were hoping to get a easiset way of solving this problem. Anyway thank you for your help, much appreciated!

MatteoFeltrin avatar Jun 06 '25 09:06 MatteoFeltrin

@christian-bromann I don't know if you followed this topic, but these days i digged into Stencil source code and I came out with a possible working solution (for us). But before proposing a PR i would like to ask your opinion whether Stencil team would be interested in merging this change or not.

The problem in very short: Lazy loading is enabled and in production build components are named something like p-[hash].js. User load the page but never lazy-load a specific component (eg: he never open a popup containing this component). We deploy a new version of this component so its hash changes. User never reloads the page and now tries to open the popup. The cached component-hash now does not exist anymore so the dynamic import fails and the users gets this error Constructor for "my-component#undefined" was not found

We are not developing a PWA, but a classic web page where some clicks redirects and reload the page, while others click triggers ajax calls. I have read about Service Workers used in PWA but I'm not sure whether this is a good approach for us.

The solution that came to my mind is the following: Extract the bundle list to a separate file (now it is hard coded in the entrypoint) and import it in the entrypoint to keep the same logic as before. When we try to load a module with the function loadModule, if the import fails, than dynamic import again this bundle list using cache busting ?v=randomNumber. Extract from the list the new component hash and then try again to re-load the component with the new hash.

The only downside of this approch that I see is that you may download a new version of a component that has props not compatible with the other components interacting with him that are still at the old version because you did not refresh the page. But, at least for us, I think this is an extremely rare edge-case It's better to have a -with an extremelly little chanche- broken component than having a component that is not even rendered because the import failed.

Does this make sense for you? Unfortunately I haven't a wide enough view of other companies use-cases so your opinion would be appreciated 😊

MatteoFeltrin avatar Jun 12 '25 07:06 MatteoFeltrin

Does this make sense for you?

This makes absolutely sense to me. Thanks for the investigation! Do you think this behavior can be determined by a configuration in stencil.config.ts? Or should we just default to your proposed idea? I think this is a great idea and I am happy to support you adding this to Stencil.

christian-bromann avatar Jun 12 '25 17:06 christian-bromann

Hmmm I see no drawback of this so I think we can default to It. Maybe we add a flag to the config that Is enabled by default and you can disabile It? Just to be safe

MatteoFeltrin avatar Jun 15 '25 05:06 MatteoFeltrin

That sounds reasonable to me.

christian-bromann avatar Jun 19 '25 16:06 christian-bromann

thanks all for the valuable question / discussion. Gonna close this one out now. Please feel free to raise a feature request / PR or continue the discussion at https://github.com/stenciljs/core/discussions or discord

johnjenkins avatar Dec 17 '25 09:12 johnjenkins