Blogs
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 esbuild, webpack, 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
- input:
- Updated the
build
script to bundle your code using ESM format- Additionally
declared
react
andreact-dom
as externals to use Liferay DXP’s provided versions.
- Additionally
declared
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 nativeimport
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 esbuild
, vite
,
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! 🎉