Legacy mode upgrade

Contents

React4XP 7 (for Enonic XP 8) is still compatible with how parts are rendered in version 5 and 6, where each part is a separate entry.

This means you may perform a partial upgrade of your application, where you will not be using the Component Registry.

Follow the steps below:

Upgrade XP

Set the XP version of your app to 8.0.0 or newer.

gradle.properties
xpVersion=8.0.3

The XP 8 app plugin requires Gradle 9. Bump the wrapper:

./gradlew wrapper --gradle-version 9.4.1
XP 8 also moves the app plugin version and the XP dependency catalog into a Gradle settings plugin - see Upgrade the build below.

Upgrade package.json

  • Use latest React4XP types

    "@enonic-types/lib-react4xp": "^7.0.0",
  • Use latest React4XP runtime library

    "@enonic/react4xp": "7.0.0",
  • Add global Enonic types for XP 8 (includes core transitively)

    "@enonic-types/global": "^8.0.3",
    this brings in @enonic-types/core:
    ├─┬ @enonic-types/global@8.0.3
    │ └── @enonic-types/core@8.0.3
  • Add React component library

    "@enonic/react-components": "^7.0.0",
  • Add CSS-Modules typing & plugin

    "@types/css-modules":            "^1",
    "typescript-plugin-css-modules": "^5",
  • Server code is now compiled with tsdown. Add it and its SWC transformer

    "tsdown":     "^0.22.2",
    "@swc/core":  "^1.15.33",
  • Remove the obsolete CSS extractor (React4XP 7 uses Rspack’s native CSS support)

    // delete this line:
    "mini-css-extract-plugin": "^2",
  • Update the npm scripts. The build now runs tsdown (server code + client assets) and React4XP (the .tsx entries), and the type checks are split into the React4XP and server projects:

    "scripts": {
      "build":               "tsdown",
      "build:react4xp":      "npm explore @enonic/react4xp -- npm run build:react4xp",
      "check":               "concurrently -c auto -g --timings npm:check:types:*",
      "check:types:react4xp":"npx tsc --noEmit -p src/main/resources/react4xp/tsconfig.json",
      "check:types:server":  "npx tsc --noEmit -p src/main/resources/tsconfig.json"
    }
The easiest way to get the exact, up-to-date config files (tsdown.config.ts, react4xp.config.js, webpack.config.react4xp.js, and the tsconfig.json files) is to copy them from the React4XP starter.

Folder structure

Your React components stay under react4xp/, and static files and shared TS types keep their place.

In XP 8, CMS schemas and controllers move from site/ to cms/ (see Site mapping). Rename the folder:

git mv src/main/resources/site src/main/resources/cms

and convert its descriptors from XML to the YAML format (kind: "Site", kind: "Part", etc.). The recommended upgrade shows the converted descriptors.

Upgrade the build

XP 8 introduces a Gradle settings plugin that supplies the XP app plugin version and a dependency catalog (xplibs).

  • Add the settings plugin to settings.gradle:

    settings.gradle
    plugins {
        id 'com.enonic.xp.settings' version '4.1.0'
    }
    
    rootProject.name = projectName
  • In build.gradle, drop the version from the com.enonic.xp.app plugin - the settings plugin now supplies it:

    build.gradle
    plugins {
        id 'maven-publish'
        id 'com.enonic.xp.app'
        id 'com.enonic.defaults' version '2.1.6'
        id 'com.github.node-gradle.node' version '7.1.0'
    }
  • Migrate XP library dependencies to the xplibs catalog, and update the React4XP libraries:

    dependencies {
        include xplibs.content
        include xplibs.context
        include xplibs.portal
        include xplibs.auth
    
        include 'com.enonic.lib:lib-asset:2.0.1'
        include 'com.enonic.lib:lib-react4xp:7.0.0'
    }
    XP libraries (com.enonic.xp:lib-) are referenced through the xplibs. catalog aliases (lib-content becomes xplibs.content, and so on). Third-party libraries such as lib-asset and lib-react4xp keep their full coordinates.
  • Keep TS/TSX/SASS source out of the JAR - tsdown and React4XP produce the compiled output:

    processResources {
        exclude '**/.gitkeep'
        exclude '**/tsconfig.json'
        exclude '**/*.ts'  // handled by tsdown
        exclude '**/*.tsx' // handled by @enonic/react4xp
        exclude '**/*.jsx' // handled by @enonic/react4xp
        exclude '**/*.scss'
        exclude '**/*.sass'
    }
  • Inject npm-based type-checks:

    tasks.register('npmCheck', NpmTask) {
        dependsOn npmInstall
        args = ['run', 'check']
        environment = ['FORCE_COLOR': 'true']
    }
    
    check.dependsOn npmCheck
  • Register the tsdown (server + assets) and React4XP (.tsx entries) build tasks, and wire them into the JAR:

    tasks.register('npmBuild', NpmTask) {
        args = ['run', '--silent', 'build']
        dependsOn npmInstall
        environment = ['FORCE_COLOR'          : 'true',
                       'LOG_LEVEL_FROM_GRADLE': gradle.startParameter.logLevel.toString(),
                       'NODE_ENV'             : project.hasProperty('dev') || project.hasProperty('development') ? 'development' : 'production']
        inputs.dir 'src/main/resources'
        outputs.dir 'build/resources/main'
        outputs.upToDateWhen { false }
    }
    
    tasks.register('react4xp', NpmTask) {
        def envMode = project.hasProperty('env') ? (project.property('env') == 'prod' ? 'production' : 'development') : 'production'
        args = ['run', 'build:react4xp']
        dependsOn(npmInstall)
        description 'Compile react4xp resources'
        environment = [
            'R4X_APP_NAME'                 : "${appName}",
            'R4X_BUILD_LOG_LEVEL'          : gradle.startParameter.logLevel.toString(),
            'R4X_DIR_PATH_ABSOLUTE_PROJECT': project.projectDir.toString(),
            'NODE_ENV'                     : envMode
        ]
        group 'react4xp'
        inputs.dir 'node_modules/@enonic/react4xp'
        inputs.dir 'src/main/resources'
        outputs.dir 'build/resources/main'
        outputs.upToDateWhen { false }
    }
    
    jar.dependsOn(npmBuild, react4xp)

Upgrade webpack

React4XP 7 compiles React with Rspack and uses its native CSS support, so the old mini-css-extract-plugin / css-loader setup is gone.

webpack.config.react4xp.js
  • Enable Rspack’s CSS experiment and disable CSS named exports:

    config.experiments = {
        ...config.experiments,
        css: true
    };
    
    config.module.parser = {
        ...config.module.parser,
        'css/auto': {
            ...(config.module.parser && config.module.parser['css/auto']),
            namedExports: false
        }
    };
  • Replace the loader rules with css/auto rules for CSS and SCSS, plus a font-asset rule:

    config.module.rules = [
        ...(config.module.rules || []),
        {
            test: /\.css$/i,
            type: 'css/auto'
        },
        {
            test: /\.s[ac]ss$/i,
            type: 'css/auto',
            use: [
                {
                    loader: 'sass-loader',
                    options: {
                        sassOptions: {
                            outputStyle: 'compressed'
                        }
                    }
                }
            ]
        },
        {
            test: /\.(woff|woff2|eot|ttf|otf)$/i,
            type: 'asset/resource'
        }
    ];
    
    return config;
    type: 'css/auto' enables CSS Modules for .module.css/.module.scss files and plain global styling for the rest.

Upgrade to new types

  • React4XP 7 uses @enonic-types/core, so you need to replace the old js-utils Request type with the new one.

  • Replace Enonic.Xp.Http.Request from @enonic/js-utils/types/Request with Request from @enonic-types/core.

Make production build and deploy

enonic project build
enonic project deploy

You can also run the project in development mode, which watches for changes and automatically deploys them to the sandbox:

enonic dev

Migrating parts

In react4xp v5 the example controller looks like this:

src/main/resources/cms/parts/example/example.ts
import {render} from '/lib/enonic/react4xp';
import {getComponent} from '/lib/xp/portal';
import type {Enonic} from '@enonic/js-utils/types/Request';
import {toStr} from './toStr';


export function get(request: Enonic.Xp.Http.Request) {

    const component = getComponent();
    log.debug('component:%s', toStr(component));

    const props = {};

    const response = render(
        component,
        props,
        request,
        {
            ...
        }
    );

    return response;
}

React4XP 7 uses @enonic-types/core, so you need to replace the old js-utils Request with the new one.

  • Replace Enonic.Xp.Http.Request from @enonic/js-utils/types/Request with Request from @enonic-types/core.

This is how the upgraded component looks:

src/main/resources/cms/parts/example/example.ts
import {render} from '/lib/enonic/react4xp';
import {getComponent} from '/lib/xp/portal';
import {Request} from '@enonic-types/core';
import {toStr} from './toStr';


export function get(request: Request) {

    const component = getComponent();
    log.debug('component:%s', toStr(component));

    const props = {};

    const response = render(
        component,
        props,
        request,
        {
            ...
        }
    );

    return response;
}

So far in this tutorial we have used componentRegistry to render and dataFetcher to invoke the processor for all components.

Since our old component does not have any processor and is rendered outside componentRegistry, we need to exclude it so it becomes available for XP. We do this by updating cms/site.yaml, adding an inverted pattern mapping that lets XP serve the legacy route directly:

src/main/resources/cms/site.yaml
kind: "Site"
mappings:
  - controller: "/cms/app.js"
    order: 10
    pattern: "/r4xp5(/.*)?"
    invertPattern: true
  - controller: "/cms/component.js"
    service: "component"
    order: 10
apis:
  - "asset"
  - "react4xp"

Now we can use the v5 component with react4xp v7 - we just need to make sure the url contains /r4xp5.

Migrating pages and layouts

As this is a breaking change you need to follow the recommended guide for migrating pages and layouts, as these depend on the component registry.


Contents

Contents