AEM Maven Project Including React SPA Part I
At the time of this writing, the AEM project archetype doesn’t allow you to create a single site project containing both the generic and react frontend modules. Typically, there will be a mix of SPA and non SPA content pages in a site project. This post documents how I was able to set this up for Adobe Experience Manager version 6.5. using aem-project-archetype-23.
Requirements
- Java 1.8 or Java 11 (AEM 6.5+ only)
- Apache Maven (3.3.9+)
- Add the adobe-public profile to user Maven settings, e.g.,
.m2/settings.xml
. - This tutorial is written with the assumption that an instance of AEM is setup for local development.
Setup
Create two projects with identical properties except for the frontendModule
property value. They will need to be generated in different folders since their appId
’s will be the name of the sub folder created by Maven. Skip ahead to Part 2
Target Project “A” (frontendModule=general)
mvn -B archetype:generate \
-D archetypeGroupId=com.adobe.granite.archetypes \
-D archetypeArtifactId=aem-project-archetype \
-D archetypeVersion=23 \
-D aemVersion=6.5.0 \
-D appTitle="myproject" \
-D appId="myproject" \
-D groupId="com.myproject" \
-D frontendModule=general \
-D includeExamples=n
Source Project “B” (frontendModule=react)
mvn -B archetype:generate \
-D archetypeGroupId=com.adobe.granite.archetypes \
-D archetypeArtifactId=aem-project-archetype \
-D archetypeVersion=23 \
-D aemVersion=6.5.0 \
-D appTitle="myproject" \
-D appId="myproject" \
-D groupId="com.myproject" \
-D frontendModule=react \
-D includeExamples=n
Compare & Merge
Compare the two project directories. I used KDiff3 for this.
Inspect each of the files and manually merge from the project in folder B into folder A. For example, here I’m using VS Code’s file comparison utility.
Merge these changes from project-B/myproject/pom.xml
into project-A/myproject/pom.xml
.
Additionally, add the ui.frontend.react
module property node.
pom.xml
<modules>
...
<module>ui.frontend</module>
<module>ui.frontend.react</module>
...
</modules>
<properties>
...
<spa.project.core.version>1.0.6</spa.project.core.version>
</properties>
...
<!-- SPA Project Core (includes hierarchy page model) -->
<dependency>
<groupId>com.adobe.aem</groupId>
<artifactId>spa.project.core.all</artifactId>
<type>zip</type>
<version>${spa.project.core.version}</version>
</dependency>
<dependency>
<groupId>com.adobe.aem</groupId>
<artifactId>spa.project.core.core</artifactId>
<version>${spa.project.core.version}</version>
</dependency>
Note that the ellipsis
...
in the code snippet examples are not a part of the actual code and is there only to denote code that is being skipped and not applicable to the example. Verify all merges with actual file comparisons, these code examples are a guide.
all
Merge all/pom.xml
from project-B
into project-A
.
all/pom.xml
<embeddeds>
...
<embedded>
<groupId>com.adobe.aem</groupId>
<artifactId>spa.project.core.core</artifactId>
<target>/apps/myproject-vendor-packages/install</target>
</embedded>
...
<embeddeds>
<subPackages>
<subPackage>
<groupId>com.adobe.aem</groupId>
<artifactId>spa.project.core.all</artifactId>
<filter>true</filter>
</subPackage>
</subPackages>
...
<dependencies>
...
<!-- SPA Project Core (includes hierarchy page) -->
<dependency>
<groupId>com.adobe.aem</groupId>
<artifactId>spa.project.core.all</artifactId>
<type>zip</type>
</dependency>
<dependency>
<groupId>com.adobe.aem</groupId>
<artifactId>spa.project.core.core</artifactId>
</dependency>
...
<dependencies>
ui.apps
- apps
- myproject
- components
- page-spa
- .content.xml
- customfooterlibs.html
- customheaderlibs.html
- page-spa
- components
- myproject
Copy the page component from project-B
and rename it page-spa
in project-A
. e.g.,
ui.apps/src/main/content/jcr_root/apps/myproject/components/page-spa
Update the title in .content.xml
page-spa/.content.xml
jcr:title="myproject Page SPA"
The clientlib-react
folder linked in customheaderlibs.html
will be created when we build the project after all of the merges are completed using the ui.frontend.react/pom.xml
configuration.
Update the spa component sling:resourceSuperType
to proxy the page-spa component added above.
spa/.content.xml
sling:resourceSuperType="myproject/components/page-spa"
Merge ui.apps/pom.xml
from project-B
into project-A
bringing in the spa specific configuration items.
ui.apps/pom.xml
<embeddeds>
...
<embedded>
<groupId>com.adobe.aem</groupId>
<artifactId>spa.project.core.core</artifactId>
<target>/apps/myproject/install</target>
</embedded>
</embeddeds>
<subPackages>
<subPackage>
<groupId>com.adobe.aem</groupId>
<artifactId>spa.project.core.all</artifactId>
</subPackage>
</subPackages>
...
<!-- SPA Project Core (includes hierarchy page) -->
<dependency>
<groupId>com.adobe.aem</groupId>
<artifactId>spa.project.core.all</artifactId>
<type>zip</type>
</dependency>
<dependency>
<groupId>com.adobe.aem</groupId>
<artifactId>spa.project.core.core</artifactId>
</dependency>
ui.content
- conf
- myproject
- settings
- wcm
- policies
- wcm
- settings
- myproject
Merge .content.xml
from project-B
into project-A
for the policies
folder.
policies/.content.xml
<page jcr:primaryType="nt:unstructured">
...
<policy_spa
jcr:primaryType="nt:unstructured"
jcr:title="myproject SPA Page Policy"
sling:resourceType="wcm/core/components/policy/policy"
clientlibs="[myproject.react]">
<jcr:content jcr:primaryType="nt:unstructured"/>
</policy_spa>
</page>
<spa jcr:primaryType="nt:unstructured">
<default
jcr:primaryType="nt:unstructured"
jcr:title="myproject App Policy"
sling:resourceType="wcm/core/components/policy/policy"
clientlibs="[wcm.foundation.components.page.responsive]"
isRoot="{Boolean}true"
structureDepth="3"
/>
</spa>
- conf
- myproject
- settings
- wcm
- templates
- spa-app-template
- spa-page-template
- .content.xml
- templates
- wcm
- settings
- myproject
Copy the spa-app-template
and spa-page-template
folders and files from project-B
into project-A
.
Update the sling:resourceType
in the spa-page-template/structure
, content to use the page-spa
component. e.g.,
spa-page-template/structure/.content.xml
<jcr:content
cq:deviceGroups="[mobile/groups/responsive]"
cq:template="/conf/myproject/settings/wcm/templates/spa-page-template"
jcr:primaryType="cq:PageContent"
sling:resourceType="myproject/components/page-spa">
...
</jcr:content>
Update the spa-page-template
title.
spa-page-template/.content.xml
jcr:title="myproject Page SPA Template"
Merge the templates
folder content nodes from project-B
into project-A
.
templates/.content.xml
<spa-app-template />
<spa-page-template />
...
</jcr:root>
spa content page
Create a folder named spa
in
ui.content/src/main/content/jcr_root/content/myproject/us/en
- content
- myproject
- us
- en
- spa
- en
- us
- myproject
Copy the content/myproject/us/en/home
folder and files from project-B
and paste into the en
folder of project-A
. Rename the folder spa
and update the .content.xml
document title and sling:resourceType
properties as needed. e.g.,
jcr:title="myproject SPA Page"
sling:resourceType="myproject/components/page-spa
spa/.content.xml
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
jcr:primaryType="cq:Page">
<jcr:content
cq:lastModified="{Date}2018-10-04T09:50:29.650+02:00"
cq:lastModifiedBy="admin"
cq:template="/conf/myproject/settings/wcm/templates/spa-page-template"
jcr:primaryType="cq:PageContent"
jcr:title="myproject SPA Page"
sling:resourceType="myproject/components/page-spa">
<root
jcr:primaryType="nt:unstructured"
sling:resourceType="wcm/foundation/components/responsivegrid">
<responsivegrid
jcr:primaryType="nt:unstructured"
sling:resourceType="wcm/foundation/components/responsivegrid">
<text
jcr:primaryType="nt:unstructured"
sling:resourceType="myproject/components/text"
text="<p>Hello World!</p>"
textIsRich="true">
<cq:responsive jcr:primaryType="nt:unstructured"/>
</text>
</responsivegrid>
</root>
</jcr:content>
</jcr:root>
ui.frontend
Optionally update the name so it’s format is consistent with the other modules.
ui.frontend/pom.xml
<name>myproject - UI Frontend</name>
ui.frontend.react
Copy the ui.frontend
folder from project-B
and rename it ui.frontend.react
in project-A
. e.g., myproject/ui.frontend.react
ui.frontend.react/package.json
Update "react-scripts": "~3.3.0"
to "react-scripts": "^3.4.0"
This fixes
npm start
taskTypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string.
- per https://github.com/facebook/create-react-app/issues/8499
In the ui.frontend.react/pom.xml
, update the artifactId
and name
properties.
Then replace the frontend-maven-plugin with the exec-maven-plugin and its executions so there will not be two copies of node and npm installed into the project.
ui.frontend.react/pom.xml
...
<!-- ====================================================================== -->
<!-- P R O J E C T D E S C R I P T I O N -->
<!-- ====================================================================== -->
<artifactId>myproject.ui.frontend.react</artifactId>
<packaging>pom</packaging>
<name>myproject - UI Frontend.React</name>
<!-- ====================================================================== -->
<!-- B U I L D D E F I N I T I O N -->
<!-- ====================================================================== -->
<build>
<sourceDirectory>src/main/content/jcr_root</sourceDirectory>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>npm install (initialize)</id>
<goals>
<goal>exec</goal>
</goals>
<phase>initialize</phase>
<configuration>
<executable>npm</executable>
<arguments>
<argument>install</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>npm install (clean)</id>
<goals>
<goal>exec</goal>
</goals>
<phase>pre-clean</phase>
<configuration>
<executable>npm</executable>
<arguments>
<argument>install</argument>
</arguments>
</configuration>
</execution>
<!--
<execution>
<id>npm run test (test)</id>
<goals>
<goal>exec</goal>
</goals>
<phase>test</phase>
<configuration>
<executable>npm</executable>
<arguments>
<argument>run</argument>
<argument>test</argument>
</arguments>
</configuration>
</execution>-->
<execution>
<id>npm run build (compile)</id>
<goals>
<goal>exec</goal>
</goals>
<phase>compile</phase>
<configuration>
<executable>npm</executable>
<arguments>
<argument>run</argument>
<argument>build</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>assembly.xml</descriptor>
</descriptors>
<appendAssemblyId>false</appendAssemblyId>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
Install
For frontend builds - If you already have node and npm installed, switching to node version 10 will likely prevent errors due to an environment change.
cd project-A/myproject
mvn -PautoInstallPackage clean install
Source Code
Part 1 of 2 in the AEM Maven Project Including React series.