blog image

Dynamic Imports with Adobe AEM & Webpack

  • icons04 November, 2020
  • iconsTushar Mane

Performance of AEM based sites can be greatly improved by leveraging Dynamic Imports using Webpack. Sites built with AEM use components to compose a page. As a best practice, developers should generally split their code at the component level and use clientlibrary feature to combine and minify their JavaScript files with AEM. But often there are scenarios where specific components are only used on certain areas of the site or on specific pages and it doesn’t make sense to combine their JavaScript modules into a single bundle.

The below video show’s a demo of how dynamic import with AEM Components would work

This article walks through using Webpack Dynamic Imports to load JavaScript modules on demand, based on components present on the page as demo’d in the video above. To get a better understanding of dynamic imports check out a previous article in this series about What are Dynamic Imports and how to use them?

Prepare ui.frontend module for Dynamic Imports

The AEM Archetype has an option to generate a ui.frontend module with a dedicated front-end build mechanism based on Webpack to keep front-end resources separate. This below instructions assume you have generated the ui.frontend module as part of your AEM project generation using archetype.

Step 1: Update the clientlib.config.js

We have updated the clientlib.config.js to create a new clientlibrary named clientlib-dynamic-modules for storing shared module files inside /resources folder.

The clientlibrary node module will generate a new clientlib based on this configuration and copy the chunks from dist/clientlib-site-shared-modules/resources into this clientlibrary’s resources folder.

// clientlib.config.js
{
    name: 'clientlib-dynamic-modules',
    categories: ['dynamic-imports-demo.dynamic-modules'],
    serializationFormat: "xml",
    allowProxy: true,
    dependencies: [],
    assets: {
        resources: [
            "dist/clientlib-dynamic-modules/resources/*.js"
        ]
    },
}

The JS chunks will be directly loaded as resources (similar to fonts) via the clientlibrary proxy /etc.clientlibs/ hence we treat the JS files as resources of the AEM clientlibrary.

Step 2: Update your webpack.common.js

// webpack.common.js
output: {
    chunkFilename: 'clientlib-dynamic-modules/resources/[name].js',
    publicPath: "/etc.clientlibs/dynamic-imports-demo/clientlibs/",
}

We have updated the webpack.common.js output object with:

  • chunkFilename: Will be file directory and name of the file given when the file is generated at build/dist folder. ([name] will get the value from the import function where we can specify webpackChunkName (specified in the javascript file).
  • publicPath: Which will be path to the project directory clientlibs in /apps

Step 3: Update .eslint.json (Optional)

If you are using ESlint, then we some additional configuration needs to be performed to avoid ESlint errors & warnings. Add parseOptions to .eslint.json as following:

// .eslint.json

"parserOptions": {
    "ecmaVersion": 11,
    "allowImportExportEverywhere": true
},

Step 4: Add Chunks in your JS (Dynamic Import)

Now finally use dynamic imports in your code.

Below is just an example of a component JavaScript module. This file is fairly small just to show as an example but, generally you want to use dynamic imports for very large modules.

// carousel.js
const CarouselComponent = {
    // Selectors
    selectors : {
        carouselContainer: '.cmp-carousel',
        carouselActions: '.cmp-carousel__actions',
        carouselIndicators: '.cmp-carousel__indicators',
    },
    init: function() {
        const { selectors } = this;
        let carousels = document.querySelectorAll(selectors.carouselContainer);
        carousels.forEach((carousel)=> {
            if(carousel) {
                console.log('Carousel component loaded ...');
                /*
                 *  Custom Carousel code will go here
                */
            }
        });
    }
};

export default CarouselComponent;

Now lets look at how to dynamically include the above carousel JS only if carousel is present on the page.

Create a new file dynamic-imports.js as shown below.

dynamicComponents.js

//register the className of each dynamic component to it's own js chunk, and initialization call
const dynamicComponents = {
    "cmp-carousel": async () => {
        await import(
            /* webpackChunkName: 'cmp-carousel' */
            "../components/content/carousel/js/carousel.js")
            .then(obj => obj.default.init())},

    "helloworld": async () => {
        await import(
            /* webpackChunkName: 'cmp-helloworld' */ 
            "../components/content/helloworld/ts/helloworld.js")
            .then(obj => obj.default.init())},
}

const alreadyLoadedScripts = {};

//  Dynamic Components Loader
//
//  Loops through dynamic components registered, if found on page, loads their chunk dynamically.
const DynamicComponents = {
    init: function () {
        //loop through each registered dynamic component
        Object.keys(dynamicComponents).forEach(function(component){
            const componentContext = document.querySelector(`.${component}`);

            //if component found on page
            if(componentContext) {

                //check if script not already loaded
                if (!alreadyLoadedScripts[component]) {

                    //record script state
                    alreadyLoadedScripts[component] = true;

                    //execute the dynamic import & init script registered
                    dynamicComponents[component]();
                }
            }
        });
    } 
};

export default DynamicComponents;

The above script is an example which you can customize to your needs. At a high level the above script, queries the dom for classname of component and if the component is present, it executes the dynamic import.

Now update your main.ts to load the dynamic-components.js

Advance User Tip

You can use intersection observer in conjunction with the above approach to further improve the performance of your pages by only loading the component script if the user actually scrolls down to the component. This can be used to further improve the performance of your pages.

Demo

Here’s a sample AEM project demonstrating they dynamic import

https://github.com/initialyze/aem-dynamic-imports-demo

Additional Notes

  • Refer to the new Webpack dynamic import instructions for more details about the dynamic import.
  • Also, in your package.json, make sure you are using babel (@babel/eslint-parser) 7.5+ for the dynamic import plugin support.
  • To understand the different options for front-end build mechanisms check out the blog related to front-end builds here.

At Initialyze, we are experienced at using advance techniques to improve performance of websites. Are you looking to improve the performance of your AEM Site? Contact, Call or email us now to schedule a demo and learn how.

About Initialyze

Founded in 2015 in San Francisco, Initialyze specializes in providing software solutions and services to help worlds leading brands deliver transformation digital brand experiences. Our expertise includes Digital Strategy, Technology Implementation, Analytics and Marketing. We strive to form strong partnerships with our clients to envision, design and build innovative digital experiences efficiently and simply. Using an optimized implementation process and a suite of ready to use Initialyzers, we deliver on complex requirements quickly and cost effectively without sacrificing on quality.

Leave A Comment

Your email address will not be published. Required fields are marked *