Sample LXC Workspace & Client Extensions Quick Start Guide
Follow the steps in this readme to build a sample lxc workspace and build a client extension using React. This repo also contains the completed code from following the steps below along with the git commit history to show the step by step process.
- If you haven't already, install Blade CLI.
- Update Blade to the latest snapshot by running:
$ blade update -s
.- You can check that you're on the latest version by running
$ blade version
. While creating this guide I'm onblade version 4.1.0.SNAPSHOT202207161538
- To create an LXC workspace run
$ blade init sample-lxc-workspace
.- You will be prompted to select the "Liferay product to target". Select the latest one.
$ cd sample-lxc-workspace
$ mkdir client-extensions
This should result in the following structure:
➜ sample-lxc-workspace tree
.
├── Dockerfile.ext
├── GETTING_STARTED.markdown
├── build.gradle
├── client-extensions
│ └── ...
├── configs
│ └── ...
├── gradle
│ └── ...
├── gradle-local.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── modules
├── platform.bndrun
├── settings.gradle
└── themes
Creating a Custom Element Client Extension
$ cd client-extensions
- Create a new Custom Element Client Extension by running
$ blade create -t client-extension sample-custom-element
- Select the
customElement
extension type. - Type
Sample Custom Element
for the name.
- Select the
$ cd sample-custom-element
➜ sample-custom-element tree
.
├── client-extension.yaml
└── src
├── index.js
└── style.css
// client-extension.yaml
sample-custom-element:
cssURLs:
- style.css
friendlyURLMapping: vanilla-counter
htmlElementName: vanilla-counter
instanceable: false
name: Sample Custom Element
portletCategoryName: category.remote-apps
type: customElement
urls:
- index.js
useESM: false
- Set your
liferay.workspace.home.dir
in yourgradle-local.properties
.
liferay.workspace.home.dir=/path/to/liferay/home
- From the
sample-custom-element
directory run$ blade gw deploy
to deploy your module to your running Liferay instance. - In the browser, navigate to your Liferay instance, edit the page and add the
Sample Custom Element
widget to the page. You have now built and deployed a Custom Element Client Extension.
Adding React
Now that you have a working Client Extension you likely want to add a JavaScript Library that will let you build something more exciting. In this guide we will use React. In Liferay you have 2 main options for creating a custom element using React:
- Liferay CLI Tool
- create_remote_app.sh
The main differences between these 2 is that
create_remote_app.sh
uses Create React App where as theLiferay CLI Tool
does not and has a special feature to let you use the version of React running in your instance of Liferay instead of bundling it's own. Not including React can do quite a bit to reduce the size of your compiled JS. For this guide we will use theLiferay CLI Tool
.
- If you haven't already install/update the Liferay CLI Tool.
- In the
sample-custom-element
directory run$ liferay new react-custom-element
to create a custom element. - Select
Liferay Remote App Project
from the list. - Enter the project description.
- Select
Liferay DXP 7.4
as your target platform in order to be able to share Liferay's version of React and other packages. - Type enter to select
React
as your project type. - Type
sample-custom-element
as your custom element's HTML tag name. - Type enter to disable using shadow dom. (This will let the theme css also style your custom element.) Your directories should now look like this:
➜ sample-custom-element tree
.
├── build
│ └── ...
├── client-extension.yaml
├── dist
│ └── ...
├── react-custom-element
│ ├── README.md
│ ├── assets
│ │ └── css
│ │ └── styles.scss
│ ├── liferay.json
│ ├── package.json
│ └── src
│ ├── AppComponent.js
│ └── index.js
└── src
├── index.js
└── style.css
- Run
$ cd react-custom-element
. - Run
$ yarn && yarn build
to install dependencies and build your React custom element.
➜ react-custom-element tree
├── build
│ ├── css
│ │ └── styles.css
│ ├── index.js
│ └── manifest.json
- Copy the
react-custom-element/build/css/styles.css
andreact-custom-element/build/index.js
to thesample-custom-element/src
folder. - Update the
client-extension.yaml
file.
sample-custom-element:
cssURLs:
- styles.css
friendlyURLMapping: sample-custom-element
htmlElementName: sample-custom-element # Must match the html element name in index.js.
instanceable: false
name: Sample Custom Element
portletCategoryName: category.remote-apps
type: customElement
urls:
- index.js
useESM: true # Must be set to true in order to use Liferay's version of React.
- Deploy your client extensions by running:
$ blade gw deploy
from thesample-custom-element
directory. - In order for Liferay to be able to provide a version of React for your custom element to use, you must enable Import Maps via Liferay Portal’s Control Panel → Configuration → System Settings → Infrastructure → JavaScript Import Maps. Now that you've enabled Import Maps and deployed your custom element remote app, you can see it's deployed by checking Liferay Portal’s Applications → Custom Apps → Remote Apps. You can also add the widget to a page.
Simplifying the Deployment Process
We have successfully built and deployed a React Custom Element Remote App Client Extension, but the deployment process was not very smooth. We had to build the React app, manually copy files, and then deploy our client extension. We can use gradle to automate this process.
- Add the following to
sample-lxc-workspace/build.gradle
:
allprojects {
plugins.withId("com.liferay.node") {
node.global = true
node.npmVersion = '8.1.2'
node.nodeVersion = "16.15.1"
node.useNpm = false
node.yarnVersion = "1.22.5"
}
}
- Add the following to
sample-lxc-workspace/client-extensions/sample-custom-element/build.gradle
:
import com.liferay.gradle.util.OSDetector
import com.liferay.gradle.util.StringUtil
apply plugin: "com.liferay.node"
task buildExtraRemoteApp
File remoteAppDir = new File(projectDir, "react-custom-element")
File remoteAppCSSDir = new File(remoteAppDir, "build/css")
File remoteAppJSDir = new File(remoteAppDir, "build")
task copyfiles(type: Copy) {
println "Copying Files"
dependsOn buildExtraRemoteApp
from(remoteAppCSSDir) {
include "*.css"
}
from(remoteAppJSDir) {
include "*.js"
}
includeEmptyDirs = false
into file("src")
rename(/([0-9a-zA-Z-]+).*\.(css|js)$/, '$1.$2')
}
_createExtraTasks([remoteAppDir])
private String _camelCase(String dirName) {
String suffix = dirName.replaceAll(/\-(\w)/) {
String s = it[1]
s.toUpperCase()
}
return StringUtil.capitalize(suffix)
}
private void _createExtraTasks(List<File> dirs) {
dirs.each {
File dir ->
File packageJSONFile = new File(dir, "package.json")
if (!packageJSONFile.exists()) {
return
}
println "Adding install task for " + packageJSONFile
Task yarnInstallTask = tasks.create(name: "yarnInstall" + _camelCase(dir.name), type: Exec) {
println 'Running Yarn Install'
if (OSDetector.windows) {
executable "cmd.exe"
args "/c"
args new File(node.nodeDir, "node.exe")
args new File(node.nodeDir, "node_modules/yarn/yarn-" + node.yarnVersion + ".js")
}
else {
executable new File(node.nodeDir, "bin/node")
args new File(node.nodeDir, "lib/node_modules/yarn/yarn-" + node.yarnVersion + ".js")
}
args "install"
dependsOn downloadNode
workingDir dir
}
println "Adding build task for " + packageJSONFile
Task yarnBuildTask = tasks.create(name: "yarnBuild" + _camelCase(dir.name), type: Exec) {
println 'Running Yarn Build'
if (OSDetector.windows) {
executable "cmd.exe"
args "/c"
args new File(node.nodeDir, "node.exe")
args new File(node.nodeDir, "node_modules/yarn/yarn-" + node.yarnVersion + ".js")
}
else {
executable new File(node.nodeDir, "bin/node")
args new File(node.nodeDir, "lib/node_modules/yarn/yarn-" + node.yarnVersion + ".js")
}
args "build"
dependsOn yarnInstallTask
workingDir dir
}
buildExtraRemoteApp.dependsOn yarnBuildTask
}
}
build.dependsOn copyfiles
These build scripts will allow the build
gradle task to:
- Install npm modules.
- Build the React project.
- Copy the built files to the correct directory.
This script is written so that building the React project and copying over the files will only be done when
$ blade gw build
is run, NOT when$ blade gw deploy
.
- You can now build and deploy your Client Extension by running
$ blade gw build deploy
.
Speeding up Development
Adding a gradle build script to handle the building of our React project is great for CI/CD, but for local development this takes too long. When developing locally we can run our project using the dev server that was included by Liferay CLI when we initialized our project and then point our client-extension.yaml
file at the resources that are hosted by that server. This will let us make changes to our a React app and automatically see them updated in Liferay without needing to constantly redeploy.
- From the
/sample-lxc-workspace/client-extensions/sample-custom-element/react-custom-element
directory run$ yarn start
to start the dev server. - Update your
client-extension.yaml
to point to css and js resources being served by the dev server.
sample-custom-element:
cssURLs:
- http://localhost:8081/css/styles.css
friendlyURLMapping: sample-custom-element
htmlElementName: sample-custom-element
instanceable: false
name: Sample Custom Element
portletCategoryName: category.remote-apps
type: customElement
urls:
- http://localhost:8081/index.js
useESM: true
- Save and run
$ blade gw deploy
from the/sample-lxc-workspace/client-extensions/sample-custom-element
directory. You can now make any changes you want to your React app and automatically see the result in Liferay without needing to refresh the page. When you're done with development don't forget to undo the changes you made toclient-extension.yaml
and to run the gradle build command before trying to deploy it locally or to the cloud.