ReactJS in a Plugin
The default frontend of a Festi project is server-side: plugin templates, HUI components, the DGS, and jimbo.js. Use it for lists, forms, and dashboards.
Use ReactJS only for a page with much state in the browser, for example an editor, a board with drag and drop, or a live monitor.
This page tells you:
- what the framework supplies for ReactJS
- how to add a React application to a plugin
- how the application gets data
- what the framework has for microfrontends
What the framework supplies
The Festi CLI has the command festi-react. Its create mode makes a starter React application. The application is a Vite project. Its build writes main.js and main.css directly into the Festi project.
Optionally, the create mode adds a URL, a method, and a template to a plugin.
The framework has no other React support. There is no React component library, and the HUI components are not React components.
Note: An earlier version of the CLI made the application with Create React App, and the mode
deploycopied the build. The modedeployis only for such an application. See the README of the Festi CLI.
Directories
The source of the application is outside the directories that the web server serves. Only the build is in the static directory of the plugin that owns the application.
<project root>/
|-- react/
| `-- cart/ the React source (package.json, vite.config.js, src/)
`-- plugins/
`-- Shop/
|-- ShopPlugin.php
|-- templates/
| `-- cart.phtml the template with the mount element
`-- static/
`-- cart-static/ the build: main.js, main.css
| Directory | Content | In git | On the production server |
|---|---|---|---|
react/cart/ |
The source of the application. | Yes, without node_modules. |
Not necessary. |
plugins/Shop/static/cart-static/ |
The files that the browser loads. | A decision of the project: commit them, or build them in the pipeline. | Necessary. |
Caution: The web server serves the
staticdirectory of a plugin. Do not put the source ornode_modulesthere. Thecreatemode shows a warning when the path of the application is in a served directory.
Add a React application to a plugin
1. Create the application
Run the command in the root directory of the project.
./vendor/bin/festi-react --mode create --name cart --plugin Shop --config ./config.php
The command does these things:
- It makes the application in
./react/cart/. - It asks for the method name, the URL pattern, the area, and the type. It adds the method to the plugin
Shop, and it copies the template totemplates/cart.phtml. - It sets the build directory of the application to
plugins/Shop/static/cart-static/.
| Option | Use |
|---|---|
--name |
The name of the application. |
--path |
The directory of the application. Default: ./react/<name>. |
--plugin |
The plugin that gets the page. |
--target |
The directory for the build. Default with --plugin: <plugin>/static/<name>-static. |
--config |
The config.php of the project. The command needs it to find the plugin. Default: ./config.php. |
Without --plugin and --target, the command asks: "Do you want append project to plugin?" To make only the application, give the build directory with --target. You then add the method and the template (see step 3).
Caution: Each build removes all files in the build directory. Thus the command refuses a build directory that contains files. Give each application its own build directory.
The application contains:
| File | Content |
|---|---|
package.json |
React, Redux, Vite, and the scripts dev, build, watch, preview. |
vite.config.js |
The build directory, as a path relative to this file, and the file names main.js and main.css. |
src/main.jsx |
Renders the component App into the element with the id root. |
src/components/App.jsx |
The first component. |
src/store.js, src/reducers/ |
The Redux store. |
src/rpc/request.js |
A JSON-RPC client. See Data for the application. |
No file of the application contains a path of your computer. Thus you can commit the application, and a different developer or a pipeline can build it.
2. Build
cd react/cart
npm install
npm run build
npm run build writes main.js and main.css to the build directory. No copy step is necessary.
During development, npm run watch builds again after each change in src.
3. Mount the application in a plugin page
The plugin method gives the URL of the build to a template. The create mode with --plugin adds this method and this template for you.
/**
* @urlRule ~^/shop/cart/$~
* @area backend
* @userType user
*/
public function onDisplayCart(Response &$response): bool
{
$this->assign('path', $this->getStaticUrl('cart-static'));
$response->content = $this->ui->widget()
->setTitle(__('Cart'))
->setContent($this->fetch('cart.phtml'))
->fetch();
return true;
}
templates/cart.phtml:
<?php
/** @var string $path */
?>
<link href="<?php echo $path; ?>/main.css" rel="stylesheet">
<div id="root"></div>
<script type="module" src="<?php echo $path; ?>/main.js"></script>
The build is an ES module. Thus the <script> tag needs type="module".
The page is a normal page of the project. The theme renders the layout, the menu, and the permission checks work. React controls only the content of the mount element.
4. Browser cache
The file names of the build have no hash (main.js). Thus the browser can use an old file after a release. Add a version parameter to the two URLs in the template, and change it with each release. Use the same value as the theme: the setting js_version.
Data for the application
Initial data from the server
Give the first data to the application in the template. The application then needs no request at the start.
<div id="root"
data-id-customer="<?php echo (int) $idCustomer; ?>"
data-url="<?php echo $this->getUrl('/shop/cart/items/'); ?>"></div>
const root = document.getElementById('root');
const itemsUrl = root.dataset.url;
Requests to the server
There are two methods.
onJson methods. The application calls a plugin method with fetch(). The method returns JSON through the Response object. See Response.
public function onJsonCartItems(Response &$response): bool
{
$response->items = $this->object->getCartItems($this->core->user->getID());
return true;
}
const response = await fetch(itemsUrl, {credentials: 'same-origin'});
const data = await response.json();
setItems(data.items);
The reply has the key type only when the response has an action. If the method throws an exception, the reply is an alert: {"messages": [...], "type": "alert"}. Examine data.type in the application, or give the reply to Jimbo.response(data) to show the message in the usual manner. Jimbo is available because the theme loads jimbo.js on the page.
JSON-RPC. The starter application contains src/rpc/request.js, a client for the RPC plugin. The client reads the address of the API and the token of the user from the attributes data-endpoint and data-access-token of the mount element. The method that the create mode adds to the plugin assigns the two values to the template as empty strings. Set them in that method.
Rules for a React application in a Festi project
- One plugin owns one React application. Do not share one application between plugins.
- Keep the server as the owner of the business logic and of the permissions. The application only shows data and sends actions.
- Do not load jQuery, Bootstrap, or a second CSS framework in the application. The theme supplies the look.
- Use the CSS classes of the theme for standard elements, so that the page looks the same as the other pages.
- Use
__()on the server for the texts, and give the translated texts to the application as data. - Do not replace DGS lists and forms with React. Use the DGS.
Microfrontends
The framework has no microfrontend runtime. There is no shell application, no module loader, and no shared React instance between plugins.
What you can do with the current framework:
| Need | Method |
|---|---|
| Each team develops and builds its frontend independently. | Each plugin has its own React application in its static directory, with its own package.json. |
| A page contains more than one application. | Give each application its own mount element with a different id, and load each bundle. Each bundle contains its own copy of React. |
| Applications send messages to each other. | Use the event bus of jimbo.js: Jimbo.fireEvent() and Jimbo.addListener(). See Frontend and backend events. |
| Old pages and new pages work together. | A plugin page with a React application is a normal page. The theme, the menu, and the permissions are the same. |
Limits:
- Two applications cannot share components or state at run time.
- Each bundle makes the page larger, because each contains React.
- The starter application uses the
idroot. Change it when a page has two applications.
What was tested
The procedure on this page was not run after the Festi CLI changed the starter application to Vite. The statements about the create mode and about the starter application come from the source of the Festi CLI (src/react/mode/CreateMode.php and src/react/resources/create/) and from its README. The examples in Data for the application were not run.