shinsenter / defer.js

🥇 A lightweight JavaScript library that helps you lazy load (almost) anything. Defer.js is dependency-free, highly efficient, and optimized for Web Vitals.
https://shinsenter.github.io/defer.js/
MIT License
277 stars 45 forks source link

Defer.js to load the script on the same position where it was declared to avoid dependency errors #109

Closed DahmaniAdame closed 2 years ago

DahmaniAdame commented 2 years ago

Scripts using Defer.js don't load at the exact location where they were declared. Which creates dependency issues.

Here is an example with the Tippy library.

https://jsfiddle.net/DahmaniAdame/mu6dor90/show

The original order is:

<script id="popperjs" src="https://unpkg.com/@popperjs/core@2"></script>
<script id="tippy" src="https://unpkg.com/tippy.js@6"></script></head>
<script>
      tippy('#myButton', {
        content: 'My tooltip!',
      });
</script>

The final order was:

<script>
      tippy('#myButton', {
        content: 'My tooltip!',
      });
</script>
<script id="popperjs" src="https://unpkg.com/@popperjs/core@2"></script>
<script id="tippy" src="https://unpkg.com/tippy.js@6"></script></head>

That creates dependency issues as tippy is not yet available.

shinsenter commented 2 years ago

Hi @DahmaniAdame !

Thank you for pointing out a case I might not have considered when using a combination of Defer.js and Defer.all.

I will test and reproduce this case more thoroughly, if it is indeed a bug and needs correction, I will release it in the nearest minor version.

shinsenter commented 2 years ago

@DahmaniAdame

Thank you again for creating this special usecase. I found two problems in the snippet you created.

Problem 1:

You used Defer.all('script[type="deferjs"]', null, true); in the above example, and you probably thought it would lazy load the tags script has the type="deferjs" attribute when the user starts interacting with your page, and it will execute right after 2 libraries that you have deferred earlier with Defer.js.

However, script tags with the type="deferjs" attribute have already been executed by the library as soon as the web page has finished loading, so the order of the tags in your example is as expected.

To work around the tags being generated in the wrong order, you can try using a name other than deferjs for this, e.g. type="myscript" for the third script tag and call Defer.all( 'script[type="myscript"]', null, true);, then the order of your scripts will be guaranteed.

Problem 2:

Because the download of file using Defer.js function is asynchronous, so even in case the order of tags is guaranteed, we cannot conclude that the tippy library is presented when the third script tag is called.

To avoid dependency error when lazy loading a library using Defer.js, it is highly recommended that a onload callback function be used to make sure that the tippy library you needed is completely defined.

Please check my modified version of your snippets here. https://jsfiddle.net/sueh4y5g/

DahmaniAdame commented 2 years ago

Thank you for checking @shinsenter 🙏

Why I didn't use the onload callback

While it will guarantee the outcome, that option is not practical when dealing with scripts structure that is already managed by a 3rd party, like WordPress themes and plugins.

There is no automated way to figure out dependencies and structure them in a way Defer.js would understand and respect.

It is more fit for custom implementation of Defer.js :)

Custom type

Tested with a custom type, the order is right this time, but the problem is still there.

Just like you said, the issue is the asynchronous aspect of Defer.js.

Mixing Defer.js and Defer.all without the onload callback won't work. That will limit it to manual implementation.

Unless there is a parameter to make Defer.js not asynchronous when needed 😅

shinsenter commented 2 years ago

@DahmaniAdame You also can use type=deferjs for script tags with src attribute

shinsenter commented 2 years ago

@DahmaniAdame Just an example to prove that mixing inline script tags and script tags with src attribute works. https://jsfiddle.net/5tL42a8u/