Skip to content

Dynamic imports in module scripts

Astro ^4.0.0

Scripts with no attributes in Astro are automatically processed into type="module" and all hoisted into one .js file that has static imports to other .js files. It is possible to use dynamic module loading instead of the default static imports, in order to gain more granular control over if/when the imported code is actually imported (downloaded).

For more details on Astro’s script processing, refer to the official documentation.

Examples

If you want to see a more interactive example that might help explain or let you test things out better you can:

Granular Control

Prioritize critical modules by importing less critical modules only after the critical ones are processed.

<script>
import criticalModule from 'criticalModule';
criticalModule();
const lessCriticalModule = await import('lessCriticalModule');
lessCriticalModule();
</script>

Recreate Client Directives in <script>s

Effects similar to client:visible, client:media, and client:idle can all be achieved with this technique.

<script>
const observer = new IntersectionObserver(async (entries, observer) => {
entries.forEach(async entry => {
if (entry.isIntersecting) {
const { module } = await import('module');
module();
observer.disconnect();
}
});
});
observer.observe(document.querySelector('#targetElement'));
</script>

Feature Flags

Dynamically import modules based on feature flags. For example, loading a specific feature only for users who have opted in or who meet certain criteria.

<script>
if (featureFlag) {
const { featureModule } = await import('featureModule');
featureModule();
}
</script>

Device-Specific Code

Import desktop-specific code that doesn’t need to be downloaded on mobile devices.

<script>
(async () => {
if (!('ontouchstart' in window || navigator.maxTouchPoints > 0)) {
const { desktopModule } = await import('desktopModule');
desktopModule();
}
})();
</script>

Rarely Clicked Buttons

A button that might be rarely clicked, and when it is, the module needed to hydrate it is small enough that pre-hydration is unnecessary.

<script>
document.querySelector('#rareButton').addEventListener('click', async () => {
const { buttonModule } = await import('buttonModule');
buttonModule();
});
</script>

Considerations before using this technique

When a user clicks a button they expect it to react instantly. If you dynamically import heavy code that hydrates a button only when it is clicked that could create a delay between interaction and next paint. As a general rule any code that hydrates interactivity shouldn’t be delayed.