Sharing Libraries Between Custom Elements

In Liferay, you can have multiple custom element client extensions rendered across different pages, and all of them might use the same library.

Sharing global JavaScript libraries in Liferay requires only the creation of an additional client extension.

Some of the biggest benefits of sharing JavaScript resources include:

  • Performance: The library is downloaded once and cached by the browser.
  • Consistency: All applications use the same library version, avoiding “version drift” issues.
  • Maintainability: You update the library in the import map, and the change applies everywhere instantly.
  • No Build Hacks: The browser understands the mapping natively, without having to manually adjust Webpack, Vite, or Rollup configurations for global dependencies.

In Liferay, there are two ways to make JavaScript broadly available: Global JS client extensions and JS Import Map client extensions. Depending on the level of control required and how you need to make the resource available, both approaches can prevent duplicated code and improve load times.

Global JS Client Extension

The provided JavaScript from a client extension of the type globalJS is added to applicable pages via <script/> tags. By default it uses a page scope: when registered in Liferay, you can add it to pages and certain types of page templates, like master pages. You can choose the company scope to apply the JS resource automatically to all pages in the instance.

The global JS client extension is useful for injecting polyfills, custom scripts, or global variables. The scripts are injected into the browser environment before the page is rendered, making everything available to the page elements as they are being interpreted.

If your application needs a utility library that won’t be used anywhere else, it’s best to include it as a static asset in the application. The globalJS type, on the other hand, is really meant for adding a script to every page at the site or instance scope, like a polyfill or a third-party tracking script.

JS Import Map Client Extension

A client extension of the type jsImportMapsEntry adds JS import maps to Liferay, which control how JavaScript module specifiers (the strings you put inside import statements) are resolved to actual file URLs by the browser.

Under the hood, there’s a mapping table that tells the browser to take an import statement like

import ‘some-library’

and load it from a specific URL where it’s available (e.g., https://somewhere.com/path/to/some-library.js).

This capability allows more than one application to share the same library. Consider the following diagram.

image-20250926-031845.png

The first version shows every custom element bundling its own version of the same library. This leads to larger bundles and longer load times. With the second version, JS import maps define the dependency at the global level, so any extension can import the resource without bundling its own copy.

In Liferay, JS import maps are a great way to make JavaScript broadly available, and they’re the technique we’ll focus on here.

The JavaScript Import Maps client extension taps into the module loader, making exports available in the page so other page elements can use ES6 import statements to reference and use them in their code. These exports are not scoped, so anything that is added to the import map is made available system-wide.

Using a JS Import Map Client Extension

As with other client extensions, the metadata for the JS import map is defined in the client-extension.yaml file:

assemble: 
    - from: build/static 
      into: static 
liferay-sample-js-import-maps-entry: 
    bareSpecifier: jquery 
    name: Liferay Sample JS Import Maps Entry 
    type: jsImportMapsEntry url: jquery.*.js

There are two blocks: one describes the assembly of the source code, and the other describes the metadata for the client extension itself, so Liferay knows how to work with it. The directory structure for the sample looks like this:

Screenshot 2025-08-11 at 19.25.05.png

After the YAML file, the next most important file is index.js, found in the assets folder.

import jquery from 'jquery'; 

export default jquery; 

Here the jQuery library is first imported, then exported with the name jquery. Looking back at the client-extension.yaml file, you can see that there's an attribute called bareSpecifier. This acts as the key to access the entry in the map, which means the named export and the bareSpecifier values must match.

Once everything is packaged and deployed, the module can be imported by different components in the system. Remember that scoping these items is not possible, so jsImportMaps should only be used for truly global items that need to be available and consistent everywhere (e.g., React, core utilities, animation libraries, etc). While you can use the global JS client extension to achieve a similar result of making a JS resource available throughout your instance, using the import map is typically more efficient. With the import map client extension, a resource will only be loaded for a page if its map entry is explicitly referenced by an element on that page.

Clarity’s JavaScript Needs

Clarity has multiple custom elements that use the React framework. All three of these client extensions are being used to render some aspect of distributor information. The information they are pulling is centrally stored in Liferay and accessed via the headless API. The Clarity team has realized that some code for connecting to the API and working with distributor records is repeated more than once. To avoid duplication and create a reusable element for future features or applications that need to access the same data, they plan to create a JS library. This JS library will be shared by all three custom elements, so they have decided to put it into a JS import map rather than bundle it separately with each custom element.

Conclusion

The global JS client extension is useful for injecting scripts, polyfills, or variables to your Liferay instance or individual pages. The JS import map client extension is Liferay's recommended approach for sharing common libraries across multiple custom elements. This technique avoids code duplication by allowing the browser to download and cache a single version of the library for all components to use. It not only reduces bundle sizes, but also improves performance and simplifies dependency management.

Next, you’ll implement a JS import map for Clarity’s distributor locator app.

Loading Knowledge