Say Goodbye to liferay-npm-bundler: A Smoother Path with Standard JS Tooling

As of LPD-48372, the amd-loader has officially been deprecated in Liferay DXP. That means it’s no longer enabled by default—and with that, so ends the era of liferay-npm-bundler and amd-loader.

But don’t worry. Taking advantage of Browser modules and migrating to standard JavaScript tooling like esbuildwebpack, or vite is not only doable, it’s refreshingly simple. In this post, I’ll walk you through exactly how to modernize an existing Liferay project using one of these tools (in our case, we’ll use esbuild).

Let’s jump in!


The Starting Point

For this guide, we'll assume you're working with a project created using the npm-react-portlet Blade template. This template was originally set up to use liferay-npm-bundler, but we’ll swap that out for something leaner and more modern.


Clean Up the Old Setup

First things first, let’s remove files we no longer need:

  • .babelrc
  • .npmbundlerrc

These were used by Babel and the liferay-npm-bundler, but since we're switching tools, they’re no longer needed.


Update Your package.json

Here’s what the old package.json might look like:

{
  "dependencies": {
    "react": "18.2.0",
    "react-dom": "18.2.0"
  },
  "devDependencies": {
    "@babel/cli": "^7.0.0",
    "@babel/core": "^7.0.0",
    "@babel/preset-env": "^7.0.0",
    "@babel/preset-react": "^7.0.0",
    "liferay-npm-bundler": "2.30.0"
  },
  "main": "js/index.js",
  "name": "react-portlet",
  "scripts": {
    "build": "babel --source-maps -d build/resources/main/META-INF/resources src/main/resources/META-INF/resources && liferay-npm-bundler"
  },
  "version": "1.0.0"
}

Now, let’s simplify things with esbuild:

{
  "dependencies": {
    "react": "18.2.0",
    "react-dom": "18.2.0"
  },
  "devDependencies": {
    "esbuild": "^0.20.2"
  },
  "main": "js/index.js",
  "name": "react-portlet",
  "scripts": {
    "build": "esbuild ./src/main/resources/META-INF/resources/js/index.js --bundle --outfile=./build/resources/main/META-INF/resources/js/index.js --loader:.js=jsx --format=esm --external:react --external:react-dom --external:react-dom/client"
  },
  "version": "1.0.0"
}

What changed?

  • Removed Babel and liferay-npm-bundler (goodbye clutter 👋)
  • Added esbuild to bundle code (you can use the tool of your choice)
    • input: ./src/main/resources/META-INF/resources/js/index.js
    • output: ./build/resources/main/META-INF/resources/js/index.js
  • Updated the build script to bundle your code using ESM format
    • Additionally declared react and react-dom as externals to use Liferay DXP’s provided versions.

Update the Java Class (ReactPortlet.java)

Here’s a look at the old implementation:

@Override
public void doView(RenderRequest renderRequest, RenderResponse renderResponse)
  throws IOException, PortletException {

  renderRequest.setAttribute(
    "mainRequire",
    _npmResolver.resolveModuleName("react-portlet") + " as main");

  super.doView(renderRequest, renderResponse);
}

@Reference
private NPMResolver _npmResolver;

We can now simplify it drastically:

public class ReactPortlet extends MVCPortlet {}

What changed?

  • Removed the need for NPMResolver
  • No longer setting mainRequire manually

Update Your View (view.jsp)

Here’s what the old view file looked like:

<%@ include file="/init.jsp" %>

<div id="<portlet:namespace />-root"></div>

<aui:script require="<%= mainRequire %>">
  main.default('<portlet:namespace />-root');
</aui:script>

And here’s the modern version using native ES Modules:

<%@ include file="/init.jsp" %>

<div id="<portlet:namespace />-root"></div>

<aui:script type="module">
  import App from '<%= request.getContextPath() %>/js/index.js';

  App('<portlet:namespace />-root');
</aui:script>

What changed?

  • Switched from custom aui:script require to native import syntax

Wrapping Up

And that’s it! With just a few small updates, your project is now running on modern JavaScript tooling. You’re free to choose your favorite build tool, whether it’s esbuildvite, or webpack or some other tool, and take full advantage of faster builds, better plugin ecosystems, and a more intuitive developer experience.

Happy coding, and welcome to the modern web! 🎉


Further Reading