App controller

Contents

The app controller is responsible for bootstrapping all important elements of React4XP.

Site mapping

React4XP runs on top of XP’s site engine. By adding a React4XP application to a site, it is injected into request pipeline - where it hijackes the rendering.

This happes because the application declares a "catch all" pattern mapping in site.xml.

/src/main/resources/site/site.xml
<?xml version="1.0" encoding="UTF-8"?>
<site>
    <form/>
    <mappings>
        <mapping controller="/site/app.js" order="10"> (1)
            <pattern>/.*</pattern>
        </mapping>        
        <mapping controller="/site/component.js" order="10"> (2)
            <service>component</service>
        </mapping>
    </mappings>
</site>
1 Pattern mapping to catch all requests
2 Enables drag’n drop support in the visual page editor

Controller

The controller is by default located /src/main/resources/site/controllers/app.ts. It uses a traditional MVC approach:

  1. Get component props using the dataFetcher

    const content = getContent();
    const {
      componentProps,
      response
    } = dataFetcher.execute({
      content,
      request,
    });
  2. Defines raw HTML with the React4XP app element

    const react4xpId = `react4xp_${content._id}`;
    const htmlBody = `<!DOCTYPE html><html lang="en">
    	<head>
    		<meta charset="UTF-8">
    		<title>${content.displayName}</title>
    	</head>
    	<body>
    		<div id="${react4xpId}"></div>
    	</body>
    </html>`;
  3. Invokes React4XP’s render function

    const output = render(
      'App', // 1
      props, // 2
      request,
      {
        body: htmlBody, // 3
        id: react4xpId, // 4
      }
    );
    
    return output;
1 React entry - React4XP will look for a matching component in /src/main/resources/react4xp/entries
2 Content from dataFetcher
3 The bootstrap HTML
4 Element ID to use for the React app

The render function kicks off React server-side rendering by invoking the App component.

/src/main/resources/react4xp/entries/App.tsx
import type { AppProps } from '/types/AppProps';

import * as React from 'react';
import {BaseComponent} from '@enonic/react-components';
import {componentRegistry} from '../componentRegistry';

const App: React.FC<AppProps> = (props) => {
	return (
		<BaseComponent componentRegistry={componentRegistry} {...props}/>
	);
}

App.displayName = 'App';

export default App;

From here on, it is all React.

Output

If you take a closer look at the page markup produced by the app controller, you should see something like this:

The links have been shortened for better readability
<!DOCTYPE html><html lang="en">
<head>
  <meta charset="UTF-8">
  <script defer src=".../react4xp/globals.js"></script>                            (1)
  <script defer src=".../react4xp/runtime.js"></script>                            (2)
  <script defer src=".../react4xp/hello-react.js"></script> (3)
  <script defer src=".../react4xp/client-QZN5J5VC.global.js"></script>             (4)
  <script data-react4xp-app-name="com.enonic.app.samples_react4xp" data-react4xp-ref="react4xpApp" type="application/json">{"command":"hydrate","devMode":false,"hasRegions":0,"isPage":1,"jsxPath":"site/pages/hello-react/hello-react","props":{}}</script> (5)
</head>
<body class="xp-page">

    <div id="react4xpApp">
        <div data-reactroot="">                                                                       (6)
            <h1>Hello React4XP</h1>
        </div>
    </div>

    <script defer src=".../react4xp/executor-BL4RRDZO.js"></script>                (7)
</body>
</html>

The result is the HTML string we passed to the render request of our initial controller - but a lot of stuff has also been inserted.

1 globals.<contenthash>.js is react and react-dom bundled together. For improved security, they are served from XP instead of a random CDN.
2 runtime.js containst chunked boilerplate code to run entries.
3 hello-react.js is the compiled version of hello-react.tsx
4 client-QZN5J5VC.global.js is the client-wrapper. TODO: What does it do?
5 Properties and props needed for the client-side rendering to work properly (pun intended)
6 The <div id="react4xpApp"> target container, now filled with a server-side rendering of the react component
7 Script that triggers hydration - more on this in the next chapter
Most of these static assets are content-hashed, and optimized for client-side caching - thus running a CDN in front of the application, in production, will speed up the delivery.

Contents

Contents

AI-powered search

Juke AI