Integrate the SPA developer-workflow

Understand how the source code for a Single Page Application (SPA) written in React can be integrated with an Adobe Experience Manager (AEM) Project. Learn to use modern front-end tools, like a webpack dev server, to rapidly develop the SPA against the AEM JSON model API.

Objective

  1. Understand how the SPA project is integrated with AEM with client-side libraries.
  2. Learn how to use a webpack development server for dedicated front-end development.
  3. Explore the use of a proxy and static mock file for developing against the AEM JSON model API.

What you will build

In this chapter you will make several small changes to the SPA in order to understand how it is integrated with AEM.
This chapter will add a simple Header component to the SPA. In the process of building out this static Header component several approaches to AEM SPA development are used.

New Header in AEM

The SPA is extended to add a static Header component

Prerequisites

Review the required tooling and instructions for setting up a local development environment. This chapter is a continuation of the Create Project chapter, however to follow along all you need is a working SPA-enabled AEM project.

Integration approach integration-approach

Two modules were created as part of the AEM project: ui.apps and ui.frontend.

The ui.frontend module is a webpack project that contains all of the SPA source code. A majority of the SPA development and testing is done in the webpack project. When a production build is triggered, the SPA is built and compiled using webpack. The compiled artifacts (CSS and Javascript) are copied into the ui.apps module which is then deployed to the AEM runtime.

ui.frontend high-level architecture

A high-level depiction of the SPA integration.

Additional information about the Front-end build can be found here.

Inspect the SPA integration inspect-spa-integration

Next, inspect the ui.frontend module to understand the SPA that has been auto-generated by the AEM Project archetype.

  1. In the IDE of your choice open your AEM Project. This tutorial will use the Visual Studio Code IDE.

    VSCode - AEM WKND SPA Project

  2. Expand and inspect the ui.frontend folder. Open the file ui.frontend/package.json

  3. Under the dependencies you should see several related to react including react-scripts

    The ui.frontend is a React application based on the Create React App or CRA for short. The react-scripts version indicates which version of CRA is used.

  4. There are also several dependencies prefixed with @adobe:

    code language-json
    "@adobe/aem-react-editable-components": "~1.1.2",
    "@adobe/aem-spa-component-mapping": "~1.1.0",
    "@adobe/aem-spa-page-model-manager": "~1.3.3",
    "@adobe/aem-core-components-react-base": "1.1.8",
    "@adobe/aem-core-components-react-spa": "1.1.7",
    

    The above modules make up the AEM SPA Editor JS SDK and provide the functionality to make it possible to map SPA Components to AEM Components.

    Also included are AEM WCM Components - React Core implementation and AEM WCM Components - Spa editor - React Core implementation. These are a set of re-usable UI components that map to out of the box AEM components. These are designed to be used as is and styled to meet your project’s needs.

  5. In the package.json file there are several scripts defined:

    code language-json
    "scripts": {
        "start": "react-scripts start",
        "build": "react-scripts build && clientlib",
        "test": "react-scripts test",
        "eject": "react-scripts eject",
    }
    

    These are standard build scripts made available by the Create React App.

    The only difference is the addition of && clientlib to the build script. This extra instruction is responsible for copying the compiled SPA into the ui.apps module as a client-side library during a build.

    The npm module aem-clientlib-generator is used to facilitate this.

  6. Inspect the file ui.frontend/clientlib.config.js. This configuration file is used by aem-clientlib-generator to determine how to generate the client library.

  7. Inspect the file ui.frontend/pom.xml. This file transforms the ui.frontend folder into a Maven module. The pom.xml file has been updated to use the frontend-maven-plugin to test and build the SPA during a Maven build.

  8. Inspect the file index.js at ui.frontend/src/index.js:

    code language-js
    //ui.frontend/src/index.js
    ...
    document.addEventListener('DOMContentLoaded', () => {
        ModelManager.initialize().then(pageModel => {
            const history = createBrowserHistory();
            render(
            <Router history={history}>
                <App
                history={history}
                cqChildren={pageModel[Constants.CHILDREN_PROP]}
                cqItems={pageModel[Constants.ITEMS_PROP]}
                cqItemsOrder={pageModel[Constants.ITEMS_ORDER_PROP]}
                cqPath={pageModel[Constants.PATH_PROP]}
                locationPathname={window.location.pathname}
                />
            </Router>,
            document.getElementById('spa-root')
            );
        });
    });
    

    index.js is the entrypoint of the SPA. ModelManager is provided by the AEM SPA Editor JS SDK. It is responsible for calling and injecting the pageModel (the JSON content) into the application.

  9. Inspect the file import-components.js at ui.frontend/src/components/import-components.js. This file imports the out of the box React Core Components and makes them available to the project. We will inspect the mapping of AEM content to SPA components in the next chapter.

Add a static SPA component static-spa-component

Next, add a new component to the SPA and deploy the changes to a local AEM instance. This is a simple change, just to illustrate how the SPA is updated.

  1. In the ui.frontend module, beneath ui.frontend/src/components create a new folder named Header.

  2. Create a file named Header.js beneath the Header folder.

    Header folder and file

  3. Populate Header.js with the following:

    code language-js
    //Header.js
    import React, {Component} from 'react';
    
    export default class Header extends Component {
    
        render() {
            return (
                    <header className="Header">
                        <div className="Header-container">
                            <h1>WKND</h1>
                        </div>
                    </header>
            );
        }
    }
    

    Above is a standard React component that will output a static text string.

  4. Open the file ui.frontend/src/App.js. This is the application entry-point.

  5. Make the following updates to App.js to include the static Header:

    code language-diff
      import { Page, withModel } from '@adobe/aem-react-editable-components';
      import React from 'react';
    + import Header from './components/Header/Header';
    
      // This component is the application entry point
      class App extends Page {
      render() {
          return (
          <div>
    +       <Header />
             {this.childComponents}
             {this.childPages}
         </div>
    
  6. Open a new terminal and navigate into the ui.frontend folder and run the npm run build command:

    code language-shell
    $ cd aem-guides-wknd-spa
    $ cd ui.frontend
    $ npm run build
    ...
    Compiled successfully.
    
    File sizes after gzip:
    
    118.95 KB (-33 B)  build/static/js/2.489f399a.chunk.js
    1.11 KB (+48 B)    build/static/js/main.6cfa5095.chunk.js
    806 B              build/static/js/runtime-main.42b998df.js
    451 B              build/static/css/main.e57bbe8a.chunk.css
    
  7. Navigate to the ui.apps folder. Beneath ui.apps/src/main/content/jcr_root/apps/wknd-spa-react/clientlibs/clientlib-react you should see the compiled SPA files have been copied from theui.frontend/build folder.

    Client library generated in ui.apps

  8. Return to the terminal and navigate into the ui.apps folder. Execute the following Maven command:

    code language-shell
    $ cd ../ui.apps
    $ mvn clean install -PautoInstallPackage
    ...
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time:  9.629 s
    [INFO] Finished at: 2020-05-04T17:48:07-07:00
    [INFO] ------------------------------------------------------------------------
    

    This will deploy the ui.apps package to a local running instance of AEM.

  9. Open a browser tab and navigate to http://localhost:4502/editor.html/content/wknd-spa-react/us/en/home.html. You should now see the contents of the Header component being displayed in the SPA.

    Initial header implementation

    The above steps are executed automatically when triggering a Maven build from the root of the project (i.e mvn clean install -PautoInstallSinglePackage). You should now understand the basics of the integration between the SPA and AEM client-side libraries. Notice that you can still edit and add Text components in AEM beneath the static Header component.

Webpack Dev Server - Proxy the JSON API proxy-json

As seen in the previous exercises, performing a build and syncing the client library to a local instance of AEM takes a few minutes. This is acceptable for final testing, but is not ideal for the majority of the SPA development.

A webpack-dev-server can be used to rapidly develop the SPA. The SPA is driven by a JSON model generated by AEM. In this exercise the JSON content from a running instance of AEM is proxied into the development server.

  1. Return to the IDE and open the file ui.frontend/package.json.

    Look for a line like the following:

    code language-json
    "proxy": "http://localhost:4502",
    

    The Create React App provides an easy mechanism to proxy API requests. All unknown requests are proxied through localhost:4502, the local AEM quickstart.

  2. Open a terminal window and navigate to the ui.frontend folder. Run the command npm start:

    code language-shell
    $ cd ui.frontend
    $ npm start
    ...
    Compiled successfully!
    
    You can now view wknd-spa-react in the browser.
    
    Local:            http://localhost:3000
    On Your Network:  http://192.168.86.136:3000
    
    Note that the development build is not optimized.
    To create a production build, use npm run build.
    
  3. Open a new browser tab (if not already opened) and navigate to http://localhost:3000/content/wknd-spa-react/us/en/home.html.

    Webpack dev server - proxy json

    You should see the same content as in AEM, but without any of the authoring capabilities enabled.

    note note
    NOTE
    Due to the security requirements of AEM, you will need to be logged into the local AEM instance (http://localhost:4502) in the same browser but in a different tab.
  4. Return to the IDE and create a file named Header.css in the src/components/Header folder.

  5. Populate the Header.css with the following:

    code language-css
    .Header {
        background-color: #FFEA00;
        width: 100%;
        position: fixed;
        top: 0;
        left: 0;
        z-index: 99;
        box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.24);
    }
    
    .Header-container {
        display: flex;
        max-width: 1024px;
        margin: 0 auto;
        padding: 12px;
    }
    
    .Header-container h1 {
        letter-spacing: 0;
        font-size: 48px;
    }
    

    VSCode IDE

  6. Re-open Header.js and add the following line to reference Header.css:

    code language-diff
      //Header.js
      import React, {Component} from 'react';
    + require('./Header.css');
    

    Save the changes.

  7. Navigate to http://localhost:3000/content/wknd-spa-react/us/en/home.html to see the style changes automatically reflected.

  8. Open the file Page.css at ui.frontend/src/components/Page. Make the following changes to fix the padding:

    code language-css
    .page {
      max-width: 1024px;
      margin: 0 auto;
      padding: 12px;
      padding-top: 50px;
    }
    
  9. Return to the browser at http://localhost:3000/content/wknd-spa-react/us/en/home.html. You should immediately see the changes to the app reflected.

    Style added to header

    You can continue to make content updates in AEM and see them reflected in webpack-dev-server, since we are proxying the content.

  10. Stop the webpack dev server with ctrl+c in the terminal.

Deploy SPA updates to AEM

The changes made to the Header are currently only visible through the webpack-dev-server. Deploy the updated SPA to AEM to see the changes.

  1. Navigate to the root of the project (aem-guides-wknd-spa) and deploy the project to AEM using Maven:

    code language-shell
    $ cd ..
    $ mvn clean install -PautoInstallSinglePackage
    
  2. Navigate to http://localhost:4502/editor.html/content/wknd-spa-react/us/en/home.html. You should see the updated Header and styles applied.

    Updated Header in AEM

    Now that the updated SPA is in AEM, authoring can continue.

Congratulations! congratulations

Congratulations, you have updated the SPA and explored the integration with AEM! You now know how to develop the SPA against the AEM JSON model API using a webpack-dev-server.

Next Steps next-steps

Map SPA components to AEM components - Learn how to map React components to Adobe Experience Manager (AEM) components with the AEM SPA Editor JS SDK. Component mapping enables users to make dynamic updates to SPA components within the AEM SPA Editor, similar to traditional AEM authoring.

(Bonus) Webpack Dev Server - Mock JSON API mock-json

Another approach to rapid development is to use a static JSON file to act as the JSON model. By “mocking” the JSON, we remove the dependency on a local AEM instance. It also allows a front-end developer to update the JSON model in order to test functionality and drive changes to the JSON API that would then be later implemented by a back-end developer.

The initial set up of the mock JSON does require a local AEM instance.

  1. Return to the IDE and navigate to ui.frontend/public and add a new folder named mock-content.

  2. Create a new file named mock.model.json beneath ui.frontend/public/mock-content.

  3. In the browser navigate to http://localhost:4502/content/wknd-spa-react/us/en.model.json.

    This is the JSON exported by AEM that is driving the application. Copy the JSON output.

  4. Paste the JSON output from the previous step in the file mock.model.json.

    Mock Model Json file

  5. Open the file index.html at ui.frontend/public/index.html. Update the metadata property for the AEM page model to point to a variable %REACT_APP_PAGE_MODEL_PATH%:

    code language-html
        <!-- AEM page model -->
        <meta
           property="cq:pagemodel_root_url"
           content="%REACT_APP_PAGE_MODEL_PATH%"
        />
    

    Using a variable for the value of the cq:pagemodel_root_url will make it easier to toggle between the proxy and mock json model.

  6. Open the file ui.frontend/.env.development and make the following updates to comment out the previous value for REACT_APP_PAGE_MODEL_PATH and REACT_APP_API_HOST:

    code language-diff
    + PUBLIC_URL=/
    - PUBLIC_URL=/etc.clientlibs/wknd-spa-react/clientlibs/clientlib-react/resources
    
    - REACT_APP_PAGE_MODEL_PATH=/content/wknd-spa-react/us/en.model.json
    + REACT_APP_PAGE_MODEL_PATH=/mock-content/mock.model.json
    
    - REACT_APP_API_HOST=http://localhost:4502
    + #REACT_APP_API_HOST=http://localhost:4502
    
    REACT_APP_ROOT=/content/wknd-spa-react/us/en/home.html
    
  7. If currently running, stop the webpack-dev-server. Start the webpack-dev-server from the terminal:

    code language-shell
    $ cd ui.frontend
    $ npm start
    

    Navigate to http://localhost:3000/content/wknd-spa-react/us/en/home.html and you should see the SPA with the same content used in the proxy json.

  8. Make a small change to the mock.model.json file created earlier. You should see the updated content immediately reflected in the webpack-dev-server.

    mock model json update

Being able to manipulate the JSON model and see the effects on a live SPA can help a developer understand the JSON model API. It also allows both front-end and back-end development happen in parallel.

You can now toggle where to consume the JSON content by toggling the entries in the env.development file:

# JSON API via proxy to AEM
#REACT_APP_PAGE_MODEL_PATH=/content/wknd-spa-react/us/en.model.json
#REACT_APP_API_HOST=http://localhost:4502

# JSON API via static mock file
REACT_APP_PAGE_MODEL_PATH=/mock-content/mock.model.json
recommendation-more-help
e25b6834-e87f-4ff3-ba56-4cd16cdfdec4