Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[XMCloud] node-xmcloud-proxy add-on #1863

Merged
merged 12 commits into from
Jul 31, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Our versioning strategy is as follows:

### 🎉 New Features & Improvements

* `[create-sitecore-jss]` Introduced "node-xmcloud-proxy" addon ([#1863](https://github.com/Sitecore/jss/pull/1863))
* `[create-sitecore-jss]` `[template/angular]` `[template/angular-sxp]` `[template/angular-xmcloud]` Introduced "angular-sxp", "angular-xmcloud" addons ([#1838](https://github.com/Sitecore/jss/pull/1838))([#1845](https://github.com/Sitecore/jss/pull/1845)):
* The Angular app should now be initialized by providing both templates (or using CLI prompts):
* SXP-based: 'angular,angular-sxp'
Expand Down
249 changes: 31 additions & 218 deletions docs/upgrades/unreleased.md
Original file line number Diff line number Diff line change
@@ -1,238 +1,51 @@
## Unreleased

* If you are importing any _editing_ utils from `@sitecore-jss/sitecore-jss/utils` in your code, please update the import path to `@sitecore-jss/sitecore-jss/editing`. For now these exports are still available in the old path and marked as deprecated. They will be removed in the next major version release. Specifically for the following utils:
* ExperienceEditor
* HorizonEditor
* isEditorActive
* resetEditorChromes
* handleEditorAnchors
* Metadata
* DefaultEditFrameButton
* DefaultEditFrameButtons
* DefaultEditFrameButtonIds
* EditFrameDataSource
* ChromeCommand
* FieldEditButton
* WebEditButton
* EditButtonTypes
* mapButtonToCommand
# Angular - XMCloud

# react
If you plan to use the Angular SDK with XMCloud, you will need to perform next steps:

* With the simplification of Editing Support work we have added the following breaking changes to the `sitecore-jss-react` package. Please make the necessary updates.
- `ComponentConsumerProps` is removed. You might need to reuse _WithSitecoreContextProps_ type.
* On top of existing Angular sample, apply changes from "angular-xmcloud" add-on.
* Updated package.json "build" script to set "JSS_MODE=production":

### headless-ssr-experience-edge
* Replace `scripts/generate-config.js` if you have not modified it. Otherwise:
* Add a `trim()` call to `config[prop]` and replace comma before a newline (`,`) with semicolon (`;`) in configText prop assignment so it would look like this:

```ts
configText += `config.${prop} = process.env.REACT_APP_${constantCase(prop)} || "${
config[prop]?.trim()
}";\n`;
```

# angular

* Update Angular and core dependencies to ~17.3.1, related dependencies

* Update Typescript to ~5.2.2

* Replace `scripts/generate-config.ts` if you have not modified it. Otherwise:
* Add a `trim()` call to `config[prop]` (use toString() to avoid type conflicts) and replace commas before a newline (`,`) with semicolon (`;`) in configText prop assignments so it would look like this:

```ts
configText += `config.${prop} = process.env.${constantCase(prop)} || "${config[prop]?.toString().trim()}";\n`;
```

* Update import in _src/templates/angular/server.bundle.ts_
Use _'zone.js'_ instead of _'zone.js/dist/zone-node'_

```ts
import 'zone.js';
```
* Update import in _src/templates/angular/src/polyfills.ts_
Use _'zone.js'_ instead of _'zone.js/dist/zone-node'_

```ts
import 'zone.js';
```

# vue

* Replace `scripts/generate-config.js` if you have not modified it. Otherwise:
* Add a `trim()` call to `config[prop]` and replace commas before a newline (`,`) with semicolon (`;`) in configText prop assignments so it would look like this:

```ts
configText += `config.${prop} = process.env.VUE_APP_${constantCase(prop)} || "${
config[prop]?.trim()
}";\n`;
```

# nextjs

* Replace `scripts/generate-config.ts` if you have not modified it. Otherwise:
* Add a `trim()` call to `config[prop]` and replace comma before a newline (`,`) with semicolon (`;`) in configText prop assignment so it would look like this:

```ts
configText += `config.${prop} = process.env.${constantCase(prop)} || '${config[prop]?.trim()}';\n`;
```

* Remove cors header for API endpoints from _lib/next-config/plugins/cors-header_ plugin since cors is handled by API handlers / middlewares:

```ts
{
source: '/api/:path*',
headers: [
{
key: 'Access-Control-Allow-Origin',
value: config.sitecoreApiHost.replace(/\/$/, ''),
},
],
},
```

* Update _pages/api/editing/render.ts_ API handler initialization signature, since _resolvePageUrl_ function now accepts an object and _serverUrl_ now is optional, it's ommited when Pages Metadata Edit Mode is used. Update the handler initialization as follows:

```ts
const handler = new EditingRenderMiddleware({
resolvePageUrl: ({ serverUrl, itemPath }) => `${serverUrl}${itemPath}`,
}).getHandler();
```

* The implementation of 'EditingComponentPlaceholder' has been removed. Its purpose to avoid refreshing the entire page during component editing in Pages had never been fully utilized. The references to it and to `RenderingType` enum in `[[...path]].tsx` of the nextjs app (and in any custom code) should be removed:

```ts
import Layout from 'src/Layout';
import { RenderingType, EditingComponentPlaceholder } from '@sitecore-jss/sitecore-jss-nextjs';
...
const isComponentRendering =
layoutData.sitecore.context.renderingType === RenderingType.Component;
...
{isComponentRendering ? (
<EditingComponentPlaceholder rendering={layoutData.sitecore.route} />
) : (
<Layout layoutData={layoutData} headLinks={headLinks} />
)}
...
```

* It's highly recommended to install `sharp` dependency version `0.32.6` for nextjs apps in order to improve memory usage of Image Optimization feature. Run the `npm` command to install it:
`npm i [email protected]`

# nextjs-sxa

* The implementation for the following SXA components has been updated. Replace the existing files with updated versions.
* `src/components/Image.tsx`
* `src/components/Promo.tsx`
* `src/components/Title.tsx`

# nextjs-xmcloud

* Render a new `EditingScripts` component in your `Scripts.ts` file to support a new Editing Integration feature.

```ts
import { EditingScripts } from '@sitecore-jss/sitecore-jss-nextjs';
...
const Scripts = (): JSX.Element | null => (
<>
<EditingScripts />
...
</>
);
```

* Add a `useSiteQuery` parameter when `GraphQLDictionaryService` is initialized in `/src/lib/dictionary-service-factory.ts` :
```shell
"cross-env-shell JSS_MODE=production \"npm-run-all --serial bootstrap --serial build:client build:server\""
```
new GraphQLDictionaryService({
siteName,
clientFactory,
.....
useSiteQuery: true,
})

* We have introduced a new configuration option, `pagesEditMode`, in the `\src\pages\api\editing\config.ts` file to support the new editing metadata architecture for Pages (XMCloud). This option allows you to specify the editing mode used by Pages. It is set to `metadata` by default. However, if you are not ready to use a new integration and continue using the existing architecture, you can explicitly set the `pagesEditMode` to `chromes`.
* Update "scripts/generate-config.ts" to handle tempalte literals in the output "environment" file:

```ts
import { EditMode } from '@sitecore-jss/sitecore-jss-nextjs';

const handler = new EditingConfigMiddleware({
...
pagesEditMode: EditMode.Chromes,
}).getHandler();
```

* Introduce a new _lib/graphql-editing-service.ts_ file to initialize a _graphQLEditingService_ to support a new Editing Metadata Mode. Can be done by adding this file from the latest version introduced in _nextjs-xmcloud_ base template.
Object.keys(config).forEach((prop) => {
let value = config[prop]?.toString().trim();
const usesTemplateLiteral = /\$\{.*?\}/.test(config[prop].toString());

* Update _lib/page-props-factory/plugins/preview-mode_ plugin to support a new Editing Metadata Mode. Can be done by replacing this file with the latest version introduced in _nextjs-xmcloud_ base template.
value = usesTemplateLiteral ? `\`${value}\`` : `"${value}"`;

* To support editing for fields in Pages, the new editing metadata architecture relies on the new metadata property 'field.metadata' (instead of on 'field.editable', which won't be used in this scenario). If you are using the new editing arhitecture in Pages (EditMode.Metadata) and have custom field component that manipulates or relies on 'field.editable' in some way, it may need to be reworked. Experience Editor still relies on 'field.editable', so it needs to be supported. See example below from SXA's Banner component:

```ts
import { useSitecoreContext, EditMode } from '@sitecore-jss/sitecore-jss-nextjs';
...
export const Banner = (props: ImageProps): JSX.Element => {
const { sitecoreContext } = useSitecoreContext();
const isMetadataMode = sitecoreContext?.editMode === EditMode.Metadata;
...
const modifyImageProps = !isMetadataMode
? {
...props.fields.Image,
editable: props?.fields?.Image?.editable
?.replace(`width="${props?.fields?.Image?.value?.width}"`, 'width="100%"')
.replace(`height="${props?.fields?.Image?.value?.height}"`, 'height="100%"'),
}
: { ...props.fields.Image };
...
}
...
configText += `config.${prop} = process.env.${constantCase(prop)} || ${value};\n`;
});
```

* To enable AB testing and component level personalization support in JSS:
* Ensure `componentVariantIds` are passed to `personalizeLayout` function call in `/lib/page-props-factory/plugins/personalize.ts`:
* Updated "server.bundle.ts" to additionally expose new properties:

```ts
// Get variant(s) for personalization (from path)
const personalizeData = getPersonalizedRewriteData(path);

// Modify layoutData to use specific variant(s) instead of default
// This will also set the variantId on the Sitecore context so that it is accessible here
personalizeLayout(
props.layoutData,
personalizeData.variantId,
personalizeData.componentVariantIds
);
```

* For preview mode, prepare and pass `componentVariantIds` into `personalizeLayout` in `/lib/page-props-factory/plugins/preview-mode.ts`:
import { environment } from './src/environments/environment';
import { clientFactory } from './src/app/lib/graphql-client-factory';
import { dictionaryServiceFactory } from './src/app/lib/dictionary-service-factory';
import { layoutServiceFactory } from './src/app/lib/layout-service-factory';

```ts
import {
SiteInfo,
personalizeLayout,
getGroomedVariantIds,
} from '@sitecore-jss/sitecore-jss-nextjs';
```
```ts
props.headLinks = [];
const personalizeData = getGroomedVariantIds(variantIds);
personalizeLayout(
props.layoutData,
personalizeData.variantId,
personalizeData.componentVariantIds
);
```

* Update _lib/middleware/plugins/personalize.ts_ `PersonalizeMiddleware` constructor signature, moving `scope` from `cdpConfig` to the root. For now this option will continue working but is marked as deprecated. It will be removed in the next major version release.
...
const defaultLanguage = environment.defaultLanguage;
const graphQLEndpointPath = environment.graphQLEndpointPath;
const graphQLEndpoint = environment.graphQLEndpoint;

```ts
this.personalizeMiddleware = new PersonalizeMiddleware({
export {
...
cdpConfig: {
...
scope: process.env.NEXT_PUBLIC_PERSONALIZE_SCOPE, // REMOVE
},
scope: process.env.NEXT_PUBLIC_PERSONALIZE_SCOPE, // ADD
});
clientFactory,
dictionaryServiceFactory,
layoutServiceFactory,
defaultLanguage,
graphQLEndpointPath,
graphQLEndpoint,
};
```

* GraphQL FETCH_WITH method is required to be used, REST is not supported. Updated FETCH_WITH environment variable if needed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

# ========== XM Cloud Proxy ===========

# Your XM Cloud Proxy hostname is needed to build the app and execute the client-side requests against the proxy server.
PROXY_HOST=http://localhost:3001

# Your XM Cloud Proxy server path is needed to build the app. The build output will be copied to the proxy server path.
PROXY_BUILD_PATH=<%- proxyAppDestination %>/dist
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
{
"license": "yo-ho-ho"
}

{
"config": {
"sitecoreDistPath": "/dist"
},
"scripts": {
"postbuild:server": "npm-run-all --serial prepare:build prepare:proxy-build",
"prepare:build": "move-cli ./dist/main.js ./dist/server.bundle.js",
"prepare:proxy-build": "ts-node --project src/tsconfig.webpack-server.json ./scripts/proxy-build.ts"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { JssConfig } from 'lib/config';
import { ConfigPlugin } from '..';
import { constantCase } from 'constant-case';

/**
* This plugin will set XM Cloud related config props.
*/
class XMCloudPlugin implements ConfigPlugin {
// should come after other plugins (but before fallback)
order = 10;

async exec(config: JssConfig) {
const proxyBuildPath = process.env[`${constantCase('proxyBuildPath')}`]?.replace(/\/$/, '');
const proxyHost = process.env[`${constantCase('proxyHost')}`];

return Object.assign({}, config, {
proxyBuildPath,
proxyHost,
});
}
}

export const xmcloudPlugin = new XMCloudPlugin();
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { execSync } from 'child_process';
import { environment } from '../src/environments/environment';

// Executed at the end of the build process (jss build) to move the build output to the proxy build path

try {
console.log('Moving build output to proxy build path:', environment.proxyBuildPath);

execSync(`del-cli ${environment.proxyBuildPath} --force`, { stdio: 'inherit' });
execSync(`move-cli ./dist ${environment.proxyBuildPath}`, { stdio: 'inherit' });

console.log('Proxy build prepared successfully!');
} catch (error) {
console.error('Error preparing proxy build:', error);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Represents the type of config object available within the generated /environments/environment.js
*/
export interface JssConfig extends Record<string, string | boolean | undefined> {
production?: false;
sitecoreApiKey?: string;
sitecoreApiHost?: string;
sitecoreSiteName?: string;
defaultLanguage?: string;
graphQLEndpoint?: string;
graphQLEndpointPath?: string;
defaultServerRoute?: string;
proxyBuildPath?: string;
proxyHost?: string;
}
Loading