diff --git a/.eslintrc b/.eslintrc
index 58533d103a..b2272ce457 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -16,6 +16,7 @@
},
"plugins": [
"@typescript-eslint",
+ "@stylistic/ts",
"prettier",
"jsdoc"
],
@@ -63,6 +64,9 @@
"allowTernary": true
}
],
+ "no-unused-vars": [
+ "error"
+ ],
"brace-style": "error",
"quotes": [
"error",
diff --git a/.gitignore b/.gitignore
index 613a49c80c..4360ebf15a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,3 +26,5 @@ samples/
.idea
.next
+
+.npmrc
diff --git a/CHANGELOG.md b/CHANGELOG.md
index aa5b7db9ef..6b65a92134 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@ Our versioning strategy is as follows:
### ๐ Bug Fixes
+* `[templates/angular-xmcloud]` Navigation component link forces full page reload ([#1958](https://github.com/Sitecore/jss/pull/1958))
* `[sitecore-jss-nextjs]` Link component prefetches files ([#1956](https://github.com/Sitecore/jss/pull/1956))
* `[templates/nextjs]` `[templates/react]` `[templates/angular]` `[templates/vue]` Fixed an issue when environment variable is undefined (not present in .env), that produced an "undefined" value in generated config file ([#1875](https://github.com/Sitecore/jss/pull/1875))
* `[templates/nextjs]` Fix embedded personalization not rendering correctly after navigation through router links. ([#1911](https://github.com/Sitecore/jss/pull/1911))
@@ -22,6 +23,8 @@ Our versioning strategy is as follows:
* `[sitecore-jss-angular]` Fix nested dynamic placeholders not being displayed in Pages ([#1947](https://github.com/Sitecore/jss/pull/1947))
* `[sitecore-jss-dev-tools]` getMetadata() now uses `npm query` command to get the list and exact versions of packages. this solution works for monorepo setups ([#1949](https://github.com/Sitecore/jss/pull/1949))
* `[templates/nextjs-sxa]` Fix an alignment issue where components using both `me-auto` and `ms-md-auto` classes resulted in inconsistent alignment of elements. ([#1946](https://github.com/Sitecore/jss/pull/1946)) ([#1950](https://github.com/Sitecore/jss/pull/1950)) ([#1955](https://github.com/Sitecore/jss/pull/1955))
+* `[sitecore-jss-proxy]``[sitecore-jss-nextjs]` Fix for getCSPHeader so that it returns proper value for the CSP header when JSS_ALLOWED_ORIGINS lists multiple origins delimited with comma. ([#1972](https://github.com/Sitecore/jss/pull/1972))
+* `[sitecore-jss-proxy]` Support Editing Host protection by handling OPTIONS preflight requests ([#1976](https://github.com/Sitecore/jss/pull/1976))
### ๐ New Features & Improvements
@@ -38,6 +41,7 @@ Our versioning strategy is as follows:
* `proxyAppDestination` arg can be passed into `create-sitecore-jss` command to define path for proxy to be installed in
* `[templates/angular]` `[templates/angular-xmcloud]` `[template/node-xmcloud-proxy]` `[sitecore-jss-proxy]` Introduced /api/editing/config endpoint ([#1903](https://github.com/Sitecore/jss/pull/1903))
* `[templates/angular]` `[templates/angular-xmcloud]` `[template/node-xmcloud-proxy]` `[sitecore-jss-proxy]` Introduced /api/editing/render endpoint ([#1908](https://github.com/Sitecore/jss/pull/1908))
+* `[templates/angular-xmcloud]` `[template/node-xmcloud-proxy]` Personalization support ([#1964](https://github.com/Sitecore/jss/pull/1964)[#1971](https://github.com/Sitecore/jss/pull/1971)[#1973](https://github.com/Sitecore/jss/pull/1973))
* `[create-sitecore-jss]``[sitecore-jss-angular]``[template/angular-xmcloud]` Angular SXA components
* Angular placeholder now supports SXA components ([#1870](https://github.com/Sitecore/jss/pull/1870))
* Component styles ([#1917](https://github.com/Sitecore/jss/pull/1917))
@@ -71,8 +75,12 @@ Our versioning strategy is as follows:
* `[template/node-xmcloud-proxy]` `[sitecore-jss-proxy]` Introduced /api/healthz endpoint ([#1928](https://github.com/Sitecore/jss/pull/1928))
* `[sitecore-jss]` `[sitecore-jss-angular]` Render field metdata chromes in editMode metadata - in edit mode metadata in Pages, angular package field directives will render wrapping `code` elements with field metadata required for editing; ([#1926](https://github.com/Sitecore/jss/pull/1926))
* `[angular-xmcloud]``[sitecore-jss-angular]` Analytics and CloudSDK integration
-* `[angular-xmcloud]` Add CloudSDK initialization on client side ([#1952](https://github.com/Sitecore/jss/pull/1952))
-
+ * `[angular-xmcloud]` Add CloudSDK initialization on client side ([#1952](https://github.com/Sitecore/jss/pull/1952))([#1957](https://github.com/Sitecore/jss/pull/1957))([#1961](https://github.com/Sitecore/jss/pull/1961))
+ * `[angular-xmcloud]``[sitecore-jss-angular]` Add CDP Page View component to Angular XM Cloud add-on ([#1957](https://github.com/Sitecore/jss/pull/1957))
+* `[templates/vue]``[sitecore-jss-vue]` Vue version has been updated to 3.5
+* `[templates/angular]` Update dependencies and proxy build path value to be unix style path to support xmcloud deployment and monorepo starter kit in xmcloud foundation head ([#1977](https://github.com/Sitecore/jss/pull/1977))
+* `[templates/angular]``[templates/angular-sxp]``[templates/angular-xmcloud]` Updates for Angular XMC sample to work well with local containers and spa-starters monorepo in xmcloud-foundation ([#1983](https://github.com/Sitecore/jss/pull/1983))
+* `[sitecore-jss-angular]``[templates/angular]` Integrate CloudSDK events firing ([#1984](https://github.com/Sitecore/jss/pull/1984))
### ๐ Breaking Change
@@ -82,10 +90,46 @@ Our versioning strategy is as follows:
* `[sitecore-jss-proxy]` Updated exports of the module for better extensibility ([#1903](https://github.com/Sitecore/jss/pull/1903))
* `express@^4.19.2` dependency is marked as a peer dependency
* Default `scProxy` middleware export is replaced by `headlessProxy` object that contains the `middleware`, `ProxyConfig`, `ServerBundle` properties
+* `[all packages][all samples]` Updated packages and apps to use nodejs 22.
+* `[nextjs]`,`[react]``[angular]``[node-headless-ssr-proxy]` Updated `typescript` to latest version
+* `[nextjs]` Updated `@typescript-eslint` to version 8
### ๐งน Chores
* `[templates/angular]``[templates/node-xmcloud-proxy]``[templates/node-headless-ssr-proxy]``[templates/node-headless-ssr-experience-edge]` Adjust out of box .gitignore rules
+* New Angular add-on's are not scaffolded within build pipeline ([#1962](https://github.com/Sitecore/jss/pull/1962))
+* Release process, maintenance simplification ([#1960](https://github.com/Sitecore/jss/pull/1960))
+* `[template/angular-xmcloud]``[template/xmcloud-proxy]` Add README.md ([#1965](https://github.com/Sitecore/jss/pull/1965))
+
+## 22.2.2
+
+### ๐ Bug Fixes
+
+* `[sitecore-jss-nextjs]` Support Editing Host protection by handling OPTIONS preflight requests (#[TBD](TBD))
+
+## 22.2.1
+
+### ๐ Bug Fixes
+
+* `[sitecore-jss-react]` `[templates/nextjs-xmcloud]` [BYOC] Form's submission is failing ([#1966](https://github.com/Sitecore/jss/pull/1966)):
+ * Updated @sitecore-feaas/clientside to v0.5.19.
+ * Updated @sitecore/components to v2.0.1.
+ * Passed rendering data to FEAAS.ExternalComponent.
+
+ Make sure to update the relevant dependencies to be able to use the latest fixes.
+
+* `[sitecore-jss-react]` `[templates/nextjs-xmcloud]` Updated @sitecore-cloudsdk to v0.4.1 ([#1966](https://github.com/Sitecore/jss/pull/1966))
+
+## 22.2.0
+
+### ๐ Breaking Change
+
+* `[templates/nextjs-xmcloud]` CloudSDK dependencies have been updated to 0.4.0 ([#1933](https://github.com/Sitecore/jss/pull/1933))
+* `[templates/nextjs-xmcloud]` `@sitecore/components` dependency has been updated to 2.0.0 ([#1933](https://github.com/Sitecore/jss/pull/1933))
+* `[templates/nextjs-xmcloud]` `lib/context` import has been removed. Values from `temp/config` can be used instead. ([#1933](https://github.com/Sitecore/jss/pull/1933))
+* `[sitecore-jss-nextjs]` `Context` import and `@sitecore-jss/sitecore-jss-nextjs/context` submodule have been removed. ([#1933](https://github.com/Sitecore/jss/pull/1933))
+* `[sitecore-jss-nextjs]` update personalize-middleware for CloudSDK 0.4.0 - pass `enablePersonalizeCookie` to CloudSDK.addPersonalize() function ([#1963](https://github.com/Sitecore/jss/pull/1963))
+
## 22.1.4
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 9d188a4482..e994b2e64e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -4,7 +4,7 @@ Want to contribute to Sitecore JavaScript Services? There are a few things you n
## Pre-requisites:
-- `node.js` (Use version `>= 18` or [Active LTS](https://nodejs.org/en/about/releases/)) installed (cmd `node -v` to test).
+- `node.js` (Use an [Active LTS](https://nodejs.org/en/about/releases/) version (cmd `node -v` to test)).
- `npm` (`>= 9`) installed (cmd `npm -v` to test).
Install yarn globally:
diff --git a/README.md b/README.md
index 7dfe83de0c..ec69068713 100644
--- a/README.md
+++ b/README.md
@@ -81,7 +81,9 @@ To create a JSS project for an older version of JSS and Sitecore:
## Documentation and community resources
-- [Official JSS documentation](https://doc.sitecore.com/xp/en/developers/hd/200/sitecore-headless-development/sitecore-javascript-rendering-sdks--jss-.html)
+- Official JSS documentation:
+ - [XM Cloud](https://doc.sitecore.com/xmc/en/developers/jss/latest/jss-xmc/index-en.html)
+ - [XP](https://doc.sitecore.com/xp/en/developers/hd/latest/sitecore-headless-development/sitecore-javascript-rendering-sdks--jss-.html)
- [StackExchange](https://sitecore.stackexchange.com/)
- [Community Slack](https://sitecorechat.slack.com/messages/jss)
- [Sitecore Community Forum](https://community.sitecore.net/developers/f/40)
diff --git a/UPGRADING.md b/UPGRADING.md
deleted file mode 100644
index 856d66d161..0000000000
--- a/UPGRADING.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# Upgrade guides
-
-This document includes the links to upgrade guides for different versions of JSS apps.
-Each version contains the list of files, dependencies and environment variables that need to be added, updated or removed. Changes are grouped by templates and add-ons in each corresponding page.
-Work-in-progress items are listed in [Unreleased](./docs/upgrades/unreleased.md).
-
-## Upgrade paths for 21.x versions
-- [Upgrade from JSS 21.6 to 21.7](./docs/upgrades/21.x/21.7.md)
-- [Upgrade from JSS 21.5 to 21.6](./docs/upgrades/21.x/21.6.md)
-
-## Upgrade paths for 22.x versions
-- [Upgrade from JSS 22.0 to 22.1]((./docs/upgrades/22.x/22.1.md))
-- [Upgrade from JSS 21.7 to 22.0]((./docs/upgrades/22.x/22.0.md))
\ No newline at end of file
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index c28b40aa6c..d0e2f15815 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -43,7 +43,7 @@ variables:
steps:
- task: NodeTool@0
inputs:
- versionSpec: '20.x'
+ versionSpec: '22.x'
- script: |
yarn cache clean --all && yarn install --immutable
displayName: 'yarn install - initial'
diff --git a/docs/upgrades/21.x/21.6.md b/docs/upgrades/21.x/21.6.md
deleted file mode 100644
index dfc1f33f4d..0000000000
--- a/docs/upgrades/21.x/21.6.md
+++ /dev/null
@@ -1,6 +0,0 @@
-## Upgrade to JSS 21.6 from 21.5
-
-* Find the list of changes in Sitecore documentation:
- * [XM Cloud](https://doc.sitecore.com/xmc/en/developers/jss/216/jss-xmc/upgrade-jss-21-5-next-js-apps-to-version-21-6.html)
- * [SXP / OnPrem](https://doc.sitecore.com/xp/en/developers/hd/21/sitecore-headless-development/upgrade-jss-21-5-next-js-apps-to-version-21-6.html)
-
\ No newline at end of file
diff --git a/docs/upgrades/21.x/21.7.md b/docs/upgrades/21.x/21.7.md
deleted file mode 100644
index d26e155d47..0000000000
--- a/docs/upgrades/21.x/21.7.md
+++ /dev/null
@@ -1,6 +0,0 @@
-## Upgrade to JSS 21.7 from 21.6
-
-* Find the list of changes in Sitecore documentation:
- * [XM Cloud (to be added when published)](https://doc.sitecore.com/xmc/en/developers/jss/latest/jss-xmc/upgrade-jss-21-6-4-next-js-apps-to-version-21-7.html)
- * [SXP / OnPrem](https://doc.sitecore.com/xp/en/developers/hd/21/sitecore-headless-development/upgrade-jss-apps-to-jss-21-7.html)
-
\ No newline at end of file
diff --git a/docs/upgrades/21.x/21.8.md b/docs/upgrades/21.x/21.8.md
deleted file mode 100644
index 46a654502a..0000000000
--- a/docs/upgrades/21.x/21.8.md
+++ /dev/null
@@ -1,4 +0,0 @@
-๏ปฟ## Upgrade to JSS 21.8 from 21.7
-
-* Find the list of changes in Sitecore documentation:
- * [SXP / OnPrem](https://doc.sitecore.com/xp/en/developers/hd/21/sitecore-headless-development/upgrade-jss-apps-to-jss-21-8.html)
diff --git a/docs/upgrades/22.x/22.0.md b/docs/upgrades/22.x/22.0.md
deleted file mode 100644
index 7750509d73..0000000000
--- a/docs/upgrades/22.x/22.0.md
+++ /dev/null
@@ -1,6 +0,0 @@
-## Upgrade to JSS 22.0 from 21.7
-
-* Find the list of changes in Sitecore documentation:
- * [XM Cloud](https://doc.sitecore.com/xmc/en/developers/jss/220/jss-xmc/upgrade-jss-21-7-next-js-apps-to-version-22-0-0.html)
- * [SXP / OnPrem](https://doc.sitecore.com/xp/en/developers/hd/22/sitecore-headless-development/upgrade-jss-apps-to-jss-22-0-0.html)
-
\ No newline at end of file
diff --git a/docs/upgrades/22.x/22.1.md b/docs/upgrades/22.x/22.1.md
deleted file mode 100644
index 398aa067be..0000000000
--- a/docs/upgrades/22.x/22.1.md
+++ /dev/null
@@ -1,237 +0,0 @@
-## Upgrade to JSS 22.1 from 22.0
-
-* 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
-
-# react
-
-* 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.
-
-### 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 ? (
-
- ) : (
-
- )}
- ...
- ```
-
-* 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 sharp@0.32.6`
-
-# 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 => (
- <>
-
- ...
- >
- );
- ```
-
-* Add a `useSiteQuery` parameter when `GraphQLDictionaryService` is initialized in `/src/lib/dictionary-service-factory.ts` :
- ```
- 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`.
-
- ```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.
-
-* 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.
-
-* 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 };
- ...
- }
- ...
- ```
-
-* To prepare your JSS app for AB testing and component level personalization support in Pages:
- * Ensure `componentVariantIds` are passed to `personalizeLayout` function call in `/lib/page-props-factory/plugins/personalize.ts`:
-
- ```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`:
-
- ```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.
-
- ```ts
- this.personalizeMiddleware = new PersonalizeMiddleware({
- ...
- cdpConfig: {
- ...
- scope: process.env.NEXT_PUBLIC_PERSONALIZE_SCOPE, // REMOVE
- },
- scope: process.env.NEXT_PUBLIC_PERSONALIZE_SCOPE, // ADD
- });
- ```
\ No newline at end of file
diff --git a/docs/upgrades/unreleased.md b/docs/upgrades/unreleased.md
deleted file mode 100644
index ee86877c1d..0000000000
--- a/docs/upgrades/unreleased.md
+++ /dev/null
@@ -1,322 +0,0 @@
-## Unreleased
-
-* `FETCH_WITH.REST` constant string value from '@sitecore-jss/sitecore-jss' package now is set to 'REST' instead of 'Rest'. If you are using this constant in your code, please update your FETCH_WITH env variable to 'REST'.
-
-# Angular
-
-* Update the JssContextService and all the references to it, since some of the sitecore-jss-angular components now rely on the application state:
- * In `\src\app\jss-context.service.ts`:
- * Replace the import from `sitecore-jss-angular`:
- ```
- import { LayoutServiceData } from '@sitecore-jss/sitecore-jss-angular';
- ```
- with
- ```
- import { LayoutServiceData, JssStateService } from '@sitecore-jss/sitecore-jss-angular';
- ```
- * Remove `BehaviorSubject` import
- * Remove the `state` field (`state: BehaviorSubject;`)
- * Add two getters and replace constuctor as below:
- ```
- get state() {
- return this.stateService.state;
- }
- get stateValue() {
- return this.stateService.getStateValue();
- }
- constructor(protected transferState: TransferState, protected layoutService: JssLayoutService, protected stateService: JssStateService) {
- }
- ```
- * Replace all `this.state.next` calls with `this.stateService.setState`
- * Replace all `this.state.value` calls with `this.stateService.getStateValue()`
- * In `\src\app\jss-context.server-side.service.ts`:
- * Add import for `JssStateService`:
- ```
- import { JssStateService } from '@sitecore-jss/sitecore-jss-angular';
- ```
- * Modify the constructor. Pass `JssStateService` and propagate it to base class. Your constructor should look like this:
- ```
- constructor(
- protected transferState: TransferState,
- protected layoutService: JssLayoutService,
- protected stateService: JssStateService,
- // this initial state from sitecore is injected by server.bundle for "integrated" mode
- @Inject('JSS_SERVER_LAYOUT_DATA') private serverToSsrState: JssState
- ) {
- super(transferState, layoutService, stateService);
- }
- ```
-
-* Update i18n initialization to gain the performance improvement during fetching Dictionary Data for using SSR:
- * Inject _TransferState_ both on the server and client side:
-
- `app.module.ts`:
-
- ```ts
- TranslateModule.forRoot({
- loader: {
- provide: TranslateLoader,
- useFactory: (transferState: TransferState) =>
- new JssTranslationClientLoaderService(new JssTranslationLoaderService(), transferState),
- deps: [TransferState],
- },
- }),
- ```
-
- `app.server.module.ts`:
-
- ```ts
- TranslateModule.forRoot({
- loader: {
- provide: TranslateLoader,
- useFactory: (
- ssrViewBag: {
- [key: string]: unknown;
- dictionary: { [key: string]: string };
- },
- transferState: TransferState
- ) => new JssTranslationServerLoaderService(ssrViewBag, transferState),
- deps: ['JSS_SERVER_VIEWBAG', TransferState],
- },
- }),
- ```
-
- * In `app\i18n\jss-translation-server-loader.service.ts`:
- * Inject _TransferState_.
- * Make sure to set _dictionary_ data in _transferState_.
-
- ```ts
- export const dictionaryStateKey: StateKey = makeStateKey(
- 'dictionary'
- );
-
- ...
-
- getTranslation(_lang: string) {
- const dictionary = this.serverViewBag.dictionary;
-
- this.transferState.set(dictionaryStateKey, dictionary);
- ...
- }
- ```
-
- * In `app\i18n\jss-translation-client-loader.service.ts`:
- * Inject _TransferState_.
- * Make sure to check for _dictionary_ data in _transferState_ and use it if available and provided by the server.
-
- ```ts
- import { dictionaryStateKey } from './jss-translation-server-loader.service';
-
- ...
-
- getTranslation(lang: string): Observable {
- const dictionary = this.transferState.get(dictionaryStateKey, null);
-
- if (dictionary) {
- return of(dictionary);
- }
- ...
- }
- ```
- * In root folder .gitingore, add the rule for `.angular` if not already present
- * Replace the contents of `src/environments/.gitingore` with the following, if not done so already:
- ```
- *
- !.gitignore
- ```
-
-# Angular - XMCloud
-
-If you plan to use the Angular SDK with XMCloud, you will need to perform next steps:
-
-* On top of existing Angular sample, apply changes from "angular-xmcloud" add-on.
-* Update package.json "build:client" script to use explicit "production" configuration:
-
- ```shell
- "build:client": "cross-env-shell ng build --configuration=production --base-href $npm_package_config_sitecoreDistPath/browser/ --output-path=$npm_package_config_buildArtifactsPath/browser/"
- ```
-
-* Update /scripts/bootstrap.ts file to generate a metadata for editing integration:
- Assuming that you have a `generate-metadata.ts` file pulled from the "angular-xmcloud" add-on:
-
- ```ts
- ...
- /*
- METADATA GENERATION
- */
- require('./generate-metadata');
- ...
- ```
-
-* Update /scripts/generate-component-factory.ts to generate a list of component names for the editing integration, see implementation in the the "angular" template:
-
- ```ts
- import { AppComponentsSharedModule } from './app-components.shared.module';
- ${imports.join('\n')}
-
- export const components = [
- ${components.map((c) => `'${c}'`).join(',\n ')}
- ];
-
- @NgModule({
- ```
-
-* Restructure /src/app/lib/client-factory.ts. This is needed in order to separate the GraphQL client factory configuration from the client factory itself, so we have a single source of GraphQL endpoint resolution that can be used in different places. For example node-xmcloud-proxy, scripts/update-graphql-fragment-data.ts, etc.
- * Introduce /src/app/lib/graphql-client-factory/config.ts. It should expose the _getGraphQLClientFactoryConfig_ that returns the configuration object for the GraphQL client factory, for example (full code snippet you can find in the "angular-xmcloud" add-on):
-
- ```ts
- import { GraphQLRequestClientFactoryConfig } from '@sitecore-jss/sitecore-jss-angular/cjs';
- import { environment as env } from '../../../environments/environment';
-
- export const getGraphQLClientFactoryConfig = () => {
- let clientConfig: GraphQLRequestClientFactoryConfig;
-
- if (env.graphQLEndpoint && env.sitecoreApiKey) {
- clientConfig = {
- endpoint: env.graphQLEndpoint,
- apiKey: env.sitecoreApiKey,
- };
- }
-
- ...
-
- return clientConfig;
- };
- ```
-
- * Introduce /src/app/lib/graphql-client-factory/index.ts. It should contain the _default_ export that returns the GraphQL client factory, for example:
-
- ```ts
- import { GraphQLRequestClient } from '@sitecore-jss/sitecore-jss-angular/cjs';
- import { getGraphQLClientFactoryConfig } from './config';
-
- const createGraphQLClientFactory = () => {
- const clientConfig = getGraphQLClientFactoryConfig();
-
- return GraphQLRequestClient.createClientFactory(clientConfig);
- };
-
- export default createGraphQLClientFactory();
- ```
-
- * Make sure to import variables from @sitecore-jss/sitecore-jss-angular/cjs, not from @sitecore-jss/sitecore-jss-angular, since graphql-client-factory is used in the server bundle.
-
- * Update all the references to the GraphQL client factory in the application to use the new structure.
-
-* Update /scripts/update-graphql-fragment-data.ts to utilize the GraphQL client factory and client factory configuration. The implementation you can find in the "angular" template (/scripts/update-graphql-fragment-data.ts
-)
- * Remove "isomorphic-fetch" npm dependency
-
-* Update /scripts/generate-config.ts, it's needed since generate-config can be called outside of /scripts/bootstrap.ts file. Add dotenv import:
-
- ```ts
- import 'dotenv/config';
- ```
-
-
-* Update /server.bundle.ts to additionally expose new properties:
-
- ```ts
- import { environment } from './src/environments/environment';
- import clientFactory from './src/app/lib/graphql-client-factory';
- import { getGraphQLClientFactoryConfig } from './src/app/lib/graphql-client-factory/config';
- import { dictionaryServiceFactory } from './src/app/lib/dictionary-service-factory';
- import { layoutServiceFactory } from './src/app/lib/layout-service-factory';
- import { components } from './src/app/components/app-components.module';
- import metadata from './src/environments/metadata.json';
-
- ...
- const defaultLanguage = environment.defaultLanguage;
- const getClientFactoryConfig = getGraphQLClientFactoryConfig;
-
- export {
- ...
- clientFactory,
- getClientFactoryConfig,
- dictionaryServiceFactory,
- layoutServiceFactory,
- defaultLanguage,
- components,
- metadata
- };
- ```
-
- * Optionally, you can follow our new approach and create a separate file `server.exports.ts` where you can import/export all the necessary properties and then re-export them from `server.bundle.ts`. See the "angular-xmcloud" add-on for more details.
-
-* GraphQL FETCH_WITH method is required to be used, REST is not supported. Update FETCH_WITH environment variable if needed.
-
-* Update /src/app/lib/dictionary-service-factory.ts to use new `useSiteQuery` property. You will be able to use a Site query to get the dictionary data:
-
- ```ts
- new GraphQLDictionaryService({
- clientFactory,
- siteName: env.sitecoreSiteName,
- useSiteQuery: true,
- });
- ```
-
-* Make sure to render new `sc-editing-scripts` component (exposed by JssModule) in your Layout, since it's required for the editing integration. You might need to introduce a separate _Scripts_ module for that, see example in _angular-xmcloud_ add-on _angular-xmcloud/src/app/routing/scripts/scripts.module.ts_:
-
- ```html
-
- ```
-
-* In order to be able to start using Forms in XMCloud you need to register a Form component and add required configuration:
- * Update your _scripts/generate-component-builder_ template::
- * Register Form component
-
- ```ts
- const packages = [
- {
- name: '@sitecore-jss/sitecore-jss-angular',
- components: [{ componentName: 'Form', moduleName: 'FormComponent' }],
- },
- ]
- ```
-
- Make sure to don't push such components to the "declarations" list, since the related module is a part of "imports" list.
- * Add to "providers" list a new InjectionToken, EDGE_CONFIG token is needed for Form component to be able to fetch the form from Sitecore Edge:
-
- ```ts
- import { EDGE_CONFIG } from '@sitecore-jss/sitecore-jss-angular';
- import { environment } from '../../environments/environment';
- ...
- providers: [
- {
- // This configuration is used to be able to integrate sitecore-jss-angular SDK with Sitecore Edge
- provide: EDGE_CONFIG,
- useValue: {
- sitecoreEdgeUrl: environment.sitecoreEdgeUrl,
- sitecoreEdgeContextId: environment.sitecoreEdgeContextId,
- },
- },
- ],
- ```
-
-
-
-# @sitecore-jss/sitecore-jss-proxy
-
-* Update the import statement
-
- ```ts
- // from
- import scProxy, { ProxyConfig, ServerBundle } from '@sitecore-jss/sitecore-jss-proxy';
- // to
- import { headlessProxy } from '@sitecore-jss/sitecore-jss-proxy';
- ...
- server.use(
- '*',
- headlessProxy.middleware(
- config.serverBundle.renderView,
- config,
- config.serverBundle.parseRouteUrl
- )
- );
- ```
-
- Now `middleware`, `ProxyConfig`, `ServerBundle` properties are available on the "headlessProxy" object.
-
-* `express` dependency is marked as a peer dependency, so you need to match the required version "^4.19.2".
-
-* Copy the `.gitignore` file from fresh latest version proxy app into your existing proxy app, if not already present.
diff --git a/eslint-configs/typescript.js b/eslint-configs/typescript.js
index b618433b6a..1ee1525d6e 100644
--- a/eslint-configs/typescript.js
+++ b/eslint-configs/typescript.js
@@ -15,10 +15,12 @@ module.exports = {
'@typescript-eslint/member-ordering': 'error',
'@typescript-eslint/no-use-before-define': ['error', { functions: false, variables: false }],
'@typescript-eslint/typedef': 'error',
- '@typescript-eslint/type-annotation-spacing': 'error',
- '@typescript-eslint/semi': 'error',
- '@typescript-eslint/no-var-requires': 'off',
+ '@stylistic/ts/type-annotation-spacing': 'error',
+ '@stylistic/ts/semi': 'error',
+ '@typescript-eslint/no-require-imports': 'off',
'@typescript-eslint/no-explicit-any': 'off',
+ '@typescript-eslint/no-unused-expressions': 'off',
+ '@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
},
diff --git a/lerna.json b/lerna.json
index 162b56fa78..51381ff00c 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,7 +1,7 @@
{
"lerna": "3.22.1",
"packages": ["packages/*", "samples/*"],
- "version": "22.2.0-canary.77",
+ "version": "22.3.1-canary.6",
"npmClient": "yarn",
"useWorkspaces": true
}
diff --git a/package.json b/package.json
index 1f275c34d0..73aaf498d7 100644
--- a/package.json
+++ b/package.json
@@ -22,19 +22,21 @@
"url": "https://github.com/sitecore/jss/issues"
},
"devDependencies": {
- "@typescript-eslint/eslint-plugin": "^4.10.0",
- "@typescript-eslint/parser": "^4.10.0",
- "eslint": "^7.16.0",
+ "@stylistic/eslint-plugin-ts": "^2.10.1",
+ "@typescript-eslint/eslint-plugin": "^8.14.0",
+ "@typescript-eslint/parser": "^8.14.0",
+ "eslint": "^8.56.0",
"eslint-config-prettier": "^6.15.0",
- "eslint-plugin-jsdoc": "^30.7.9",
+ "eslint-plugin-jsdoc": "^50.5.0",
"eslint-plugin-prettier": "^3.3.0",
"lerna": "^5.6.2",
"prettier": "^1.14.3",
- "typedoc": "^0.24.0",
- "typedoc-plugin-markdown": "^3.11.3",
- "typescript": "~4.7.4"
+ "typedoc": "^0.26.0",
+ "typedoc-plugin-markdown": "^4.2.10",
+ "typescript": "~5.6.3"
},
"resolutions": {
+ "cheerio": "1.0.0-rc.3",
"eslint-plugin-jsx-a11y": "6.7.1",
"@types/react-native/@types/react": "17.0.34",
"@types/react-native-htmlview/@types/react": "17.0.34"
diff --git a/packages/create-sitecore-jss/package.json b/packages/create-sitecore-jss/package.json
index 78cc588838..2fd32a3b09 100644
--- a/packages/create-sitecore-jss/package.json
+++ b/packages/create-sitecore-jss/package.json
@@ -1,6 +1,6 @@
{
"name": "create-sitecore-jss",
- "version": "22.2.0-canary.77",
+ "version": "22.3.1-canary.6",
"description": "Sitecore JSS initializer",
"bin": "./dist/index.js",
"scripts": {
@@ -12,7 +12,7 @@
"coverage": "nyc npm test"
},
"engines": {
- "node": ">=20"
+ "node": ">=22"
},
"repository": {
"type": "git",
@@ -49,18 +49,20 @@
"@types/inquirer": "^9.0.3",
"@types/minimist": "^1.2.2",
"@types/mocha": "^10.0.1",
- "@types/node": "^20.14.2",
- "@types/sinon": "10.0.6",
- "@types/sinon-chai": "^3.2.9",
+ "@types/node": "^22.9.0",
+ "@types/proxyquire": "^1.3.31",
+ "@types/sinon": "17.0.3",
+ "@types/sinon-chai": "^4.0.0",
"chai": "^4.3.7",
"chokidar": "^3.5.3",
"del-cli": "^5.0.0",
- "eslint": "^8.32.0",
+ "eslint": "^8.56.0",
"mocha": "^10.2.0",
"nyc": "^15.1.0",
- "sinon": "^15.0.1",
+ "proxyquire": "^2.1.3",
+ "sinon": "^18.0.0",
"sinon-chai": "^3.7.0",
"ts-node": "^10.9.1",
- "typescript": "~4.9.5"
+ "typescript": "~5.6.3"
}
}
diff --git a/packages/create-sitecore-jss/src/InitializerFactory.ts b/packages/create-sitecore-jss/src/InitializerFactory.ts
index ee256f1682..bcd999e46e 100644
--- a/packages/create-sitecore-jss/src/InitializerFactory.ts
+++ b/packages/create-sitecore-jss/src/InitializerFactory.ts
@@ -13,6 +13,7 @@ export class InitializerFactory {
path.resolve(this.rootPath, 'initializers', name, 'index')
);
return new Initializer();
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
return undefined;
}
diff --git a/packages/create-sitecore-jss/src/bin.test.ts b/packages/create-sitecore-jss/src/bin.test.ts
index f395bd538c..57f9deec07 100644
--- a/packages/create-sitecore-jss/src/bin.test.ts
+++ b/packages/create-sitecore-jss/src/bin.test.ts
@@ -453,7 +453,7 @@ describe('bin', () => {
getBaseTemplatesStub.returns(['foo']);
fsExistsSyncStub.returns(false);
fsReaddirSyncStub.returns([]);
- const error = 'nope';
+ const error = new Error('nope');
initRunnerStub.throws(error);
inquirerPromptStub.returns({
continue: false,
diff --git a/packages/create-sitecore-jss/src/common/processes/transform.test.ts b/packages/create-sitecore-jss/src/common/processes/transform.test.ts
index 346c603704..474edbad5b 100644
--- a/packages/create-sitecore-jss/src/common/processes/transform.test.ts
+++ b/packages/create-sitecore-jss/src/common/processes/transform.test.ts
@@ -10,6 +10,7 @@ import sinon, { SinonStub } from 'sinon';
import { currentPkg, partialPkg } from '../test-data/pkg';
import * as transform from './transform';
import * as helpers from '../utils/helpers';
+import proxyquire from 'proxyquire';
const {
transformFilename,
@@ -507,7 +508,6 @@ describe('transform', () => {
globSyncStub = sinon.stub(glob, 'sync').returns([file]);
ejsRenderFileStub = sinon.stub(ejs, 'renderFile').returns(Promise.resolve(renderFileOutput));
- diffAndWriteFilesStub = sinon.stub(transform, 'diffAndWriteFiles');
const answers = {
destination: destinationPath,
@@ -516,10 +516,17 @@ describe('transform', () => {
force: false,
};
- await transformFunc(templatePath, answers);
+ const transformModule = proxyquire('./transform', {
+ '../../../package.json': { version: '22.2.1-canary.33' },
+ });
+
+ diffAndWriteFilesStub = sinon.stub(transformModule, 'diffAndWriteFiles');
+
+ await transformModule.transform(templatePath, answers);
expect(ejsRenderFileStub).to.have.been.calledOnceWith(path.join(templatePath, file), {
...answers,
+ version: '22.2.1-canary',
helper: {
isDev: false,
getPascalCaseName: helpers.getPascalCaseName,
@@ -629,8 +636,6 @@ describe('transform', () => {
fsExistsSyncStub = sinon.stub(fs, 'existsSync').returns(true);
openJsonFileStub = sinon.stub(helpers, 'openJsonFile').returns(currentPkg);
ejsRenderFileStub = sinon.stub(ejs, 'renderFile').returns(Promise.resolve(renderFileOutput));
- mergeStub = sinon.stub(transform, 'merge').returns(mergedPkg);
- diffAndWriteFilesStub = sinon.stub(transform, 'diffAndWriteFiles');
const answers = {
destination: destinationPath,
@@ -639,10 +644,18 @@ describe('transform', () => {
force: false,
};
- await transformFunc(templatePath, answers);
+ const transformModule = proxyquire('./transform', {
+ '../../../package.json': { version: '22.2.1-canary.33' },
+ });
+
+ diffAndWriteFilesStub = sinon.stub(transformModule, 'diffAndWriteFiles');
+ mergeStub = sinon.stub(transformModule, 'merge').returns(mergedPkg);
+
+ await transformModule.transform(templatePath, answers);
expect(ejsRenderFileStub).to.have.been.calledOnceWith(path.join(templatePath, file), {
...answers,
+ version: '22.2.1-canary',
helper: {
isDev: false,
getPascalCaseName: helpers.getPascalCaseName,
@@ -670,8 +683,6 @@ describe('transform', () => {
fsExistsSyncStub = sinon.stub(fs, 'existsSync').returns(true);
openJsonFileStub = sinon.stub(helpers, 'openJsonFile').returns(currentJson);
ejsRenderFileStub = sinon.stub(ejs, 'renderFile').returns(Promise.resolve(renderFileOutput));
- mergeStub = sinon.stub(transform, 'merge').returns(mergedPkg);
- diffAndWriteFilesStub = sinon.stub(transform, 'diffAndWriteFiles');
const answers = {
destination: destinationPath,
@@ -680,10 +691,18 @@ describe('transform', () => {
force: false,
};
- await transformFunc(templatePath, answers);
+ const transformModule = proxyquire('./transform', {
+ '../../../package.json': { version: '22.2.1-canary.33' },
+ });
+
+ mergeStub = sinon.stub(transformModule, 'merge').returns(mergedPkg);
+ diffAndWriteFilesStub = sinon.stub(transformModule, 'diffAndWriteFiles');
+
+ await transformModule.transform(templatePath, answers);
expect(ejsRenderFileStub).to.have.been.calledOnceWith(path.join(templatePath, file), {
...answers,
+ version: '22.2.1-canary',
helper: {
isDev: false,
getPascalCaseName: helpers.getPascalCaseName,
@@ -711,8 +730,13 @@ describe('transform', () => {
fsExistsSyncStub = sinon.stub(fs, 'existsSync').returns(true);
fsReadFileSunc = sinon.stub(fs, 'readFileSync').returns(currentDotEnv);
ejsRenderFileStub = sinon.stub(ejs, 'renderFile').returns(Promise.resolve(templateDotEnv));
- mergeEnvStub = sinon.stub(transform, 'mergeEnv').returns(concatDotEnv);
- diffAndWriteFilesStub = sinon.stub(transform, 'diffAndWriteFiles');
+
+ const transformModule = proxyquire('./transform', {
+ '../../../package.json': { version: '22.2.1-canary.33' },
+ });
+
+ mergeEnvStub = sinon.stub(transformModule, 'mergeEnv').returns(concatDotEnv);
+ diffAndWriteFilesStub = sinon.stub(transformModule, 'diffAndWriteFiles');
const answers = {
destination: destinationPath,
@@ -721,10 +745,11 @@ describe('transform', () => {
force: false,
};
- await transformFunc(templatePath, answers);
+ await transformModule.transform(templatePath, answers);
expect(ejsRenderFileStub).to.have.been.calledOnceWith(path.join(templatePath, file), {
...answers,
+ version: '22.2.1-canary',
helper: {
isDev: false,
getPascalCaseName: helpers.getPascalCaseName,
@@ -795,7 +820,7 @@ describe('transform', () => {
const templatePath = path.resolve('templates/next');
const destinationPath = path.resolve('samples/next');
const file = 'file.ts';
- const error = 'Nope!';
+ const error = new Error('Nope!');
globSyncStub = sinon.stub(glob, 'sync').returns([file]);
ejsRenderFileStub = sinon.stub(ejs, 'renderFile').throws(error);
diff --git a/packages/create-sitecore-jss/src/common/processes/transform.ts b/packages/create-sitecore-jss/src/common/processes/transform.ts
index d6412c599f..c4a4a656cb 100644
--- a/packages/create-sitecore-jss/src/common/processes/transform.ts
+++ b/packages/create-sitecore-jss/src/common/processes/transform.ts
@@ -15,6 +15,7 @@ import {
} from '../utils/helpers';
import { diffLines, diffJson, Change } from 'diff';
import { BaseArgs } from '../args/base';
+const { version } = require('../../../package.json');
const FILE_FOR_COPY_REGEXP = /(index\.html)$|\.(gif|jpg|jpeg|tiff|png|svg|ashx|ico|pdf|jar|eot|woff|ttf|woff2)$/;
@@ -194,8 +195,15 @@ export const diffAndWriteFiles = async ({
export const populateEjsData = (answers: BaseArgs, destination?: string) => {
// pass in helper to answers object
+
+ // Don't expose canary build number in the generated app
+ const jssVersion = version.includes('canary')
+ ? version.replace(/(-canary\.\d+)$/, '-canary')
+ : version;
+
const ejsData: Data = {
...answers,
+ version: jssVersion,
helper: {
isDev: isDevEnvironment(destination || answers.destination),
getPascalCaseName: getPascalCaseName,
@@ -227,12 +235,12 @@ type TransformOptions = {
/**
* Handles each template file and applies ejs renderer, also:
- * * determines files for copy
- * * determines files for skip
- * * if some files already exist:
- * * merges package.json files
- * * concatenates .env files
- * * compares diffs
+ * - Determines files for copy.
+ * - Determines files for skip.
+ * if some files already exist:
+ * - merges package.json files
+ * - concatenates .env files
+ * - compares diffs
* @param {string} templatePath path to the template
* @param {BaseArgs} answers CLI arguments
* @param {TransformOptions} options custom options
diff --git a/packages/create-sitecore-jss/src/common/test-data/test.package.json b/packages/create-sitecore-jss/src/common/test-data/test.package.json
index 8b5109fc56..49ce87e202 100644
--- a/packages/create-sitecore-jss/src/common/test-data/test.package.json
+++ b/packages/create-sitecore-jss/src/common/test-data/test.package.json
@@ -13,7 +13,7 @@
"chalk": "^4.1.2"
},
"devDependencies": {
- "@types/node": "^20.14.2",
- "typescript": "~4.3.5"
+ "@types/node": "^22.9.0",
+ "typescript": "~5.6.3"
}
}
\ No newline at end of file
diff --git a/packages/create-sitecore-jss/src/common/utils/helpers.ts b/packages/create-sitecore-jss/src/common/utils/helpers.ts
index 1a7815da8f..1e27319005 100644
--- a/packages/create-sitecore-jss/src/common/utils/helpers.ts
+++ b/packages/create-sitecore-jss/src/common/utils/helpers.ts
@@ -57,7 +57,7 @@ export const openJsonFile = (jsonFilePath: string) => {
/**
* Creates a .json file and inserts provided data
- * @param {Object} data data to be written into the .json file
+ * @param {object} data data to be written into the .json file
* @param {string} jsonFilePath a path to a file.
*/
export const writeJsonFile = (data: { [key: string]: unknown }, jsonFilePath: string) => {
diff --git a/packages/create-sitecore-jss/src/templates/angular-sxp/angular.json b/packages/create-sitecore-jss/src/templates/angular-sxp/angular.json
new file mode 100644
index 0000000000..5769d6037d
--- /dev/null
+++ b/packages/create-sitecore-jss/src/templates/angular-sxp/angular.json
@@ -0,0 +1,13 @@
+{
+ "projects": {
+ "<%- appName %>": {
+ "architect": {
+ "build": {
+ "options": {
+ "styles": ["node_modules/bootstrap/dist/css/bootstrap.min.css"]
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/create-sitecore-jss/src/templates/angular-xmcloud/.env b/packages/create-sitecore-jss/src/templates/angular-xmcloud/.env
index d76ee32482..a9dc589b94 100644
--- a/packages/create-sitecore-jss/src/templates/angular-xmcloud/.env
+++ b/packages/create-sitecore-jss/src/templates/angular-xmcloud/.env
@@ -11,4 +11,12 @@ SITECORE_EDGE_CONTEXT_ID=
PROXY_HOST=http://localhost:3000
# 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=<%- locals.relativeProxyAppDestination.replace(/\\/g, '\\\\') %>dist
+# Use a relative unix style path to ensure compatibility when deployment runs on Windows or Unix based systems.
+PROXY_BUILD_PATH=<%- locals.relativeProxyAppDestination.replace(/\\/g, '\/') %>dist
+
+# ==============================================
+
+# An optional Sitecore Personalize scope identifier.
+# This can be used to isolate personalization data when multiple XM Cloud Environments share a Personalize tenant.
+# This should match the PAGES_PERSONALIZE_SCOPE environment variable for your connected XM Cloud Environment.
+PERSONALIZE_SCOPE=
diff --git a/packages/create-sitecore-jss/src/templates/angular-xmcloud/README.md b/packages/create-sitecore-jss/src/templates/angular-xmcloud/README.md
new file mode 100644
index 0000000000..15368454a6
--- /dev/null
+++ b/packages/create-sitecore-jss/src/templates/angular-xmcloud/README.md
@@ -0,0 +1,60 @@
+๏ปฟ# Angular for XMCloud
+
+> Sitecore JSS Angular App for XM Cloud. For the current release this feature is experimental.
+
+[Documentation]()
+
+This Single Page Application (SPA) built with Angular is designed to be fully compatible with XM Cloud, incorporating several key add-ons and features to streamline the development process and enable seamless integration. The supported key features are as follows:
+
+- `Context ID`: The Context ID environment variable simplifies setting up and configuring XM Cloud solutions. It's a unified identifier that maps to all your configured resources, such as content, sites, files, forms, and integration settings.
+
+- `XM Cloud Pages editing integration`: full integration with Pages - the dynamic visual page editor of XM Cloud.
+
+- `XM Cloud proxy personalization` with embedded personalization and A/B Component Test support.
+
+- `Forms support`: provides the capability to consume and post Sitecore Forms from JSS apps. Sitecore Forms is a form-authoring framework that enables marketers to author their own forms, collect data, and analyze form performance.
+
+This SPA is tailored to enhance development workflows and enable full utilization of XM Cloudโs capabilities, providing a seamless and efficient foundation for developers.
+
+## Components and Supporting Applications
+
+The following components and supporting applications have been added to the Angular base app to ensure compatibility with XM Cloud:
+
+- `XM Cloud Angular`: Adds support for the Sitecore Context data, which simplifies connecting the application to XM Cloud and configuring the integration of multiple composable Sitecore products. Angular app provides components and can be used during development. Once the app is built and its build artifacts are copied into the proxy, the proxy app can be used to connect to an XMCloud instance and render its content (including personalization etc).
+
+- `XM Cloud Proxy`: Adds integration with XMCloud for the JSS SPA applications and enables editing, personalization and A/B component testing support.
+
+## Environment Variables
+
+The following environment variables can be set to configure the angular app. You can use the `.env` file located in the root of the app or set these directly in the environment (for example, in containers).
+
+| Parameter | Description |
+| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
+| `PROXY_HOST` | Your XM Cloud Proxy hostname is needed to build the app and execute the client-side requests against the proxy server. Default value `http://localhost:3000` |
+| `PROXY_BUILD_PATH` | Your XM Cloud Proxy server path is needed to build the app. The build output will be copied to the XMCloud Proxy application path. Default value `\dist`.
+| `SITECORE_EDGE_CONTEXT_ID` | The Context ID, which covers many system configurations, required for connecting to the XM Cloud back end. This is an XM Cloud system environment variable. When the application runs on the XM Cloud rendering host, this value is always set to the preview Context ID. |
+| `SITECORE_API_KEY` | The API key for GRAPH_QL_ENDPOINT authentication. For Experience Edge, you can find the API key in the Sites dashboard by opening the actions menu for a site and navigating to Settings > Developer settings. Copy the value for SITECORE_API_KEY. For a preview endpoint (a CM instance either on XM Cloud or locally), use your preview API Key from the CM instance.
+| `SITECORE_API_HOST` | The API hostname, needed to build the application. This should be used in combination with SITECORE_API_KEY for local development or local container setup. For example, https://.sitecorecloud.io. |
+| `GRAPH_QL_ENDPOINT` | Your GraphQL Edge endpoint. This is typically optional. By default, the endpoint is calculated using the resolved Sitecore API hostname + the `graphQLEndpointPath` defined in your `package.json`. For a preview endpoint (a CM instance on XM Cloud or a local one), the value is /sitecore/api/graph/edge. |
+| `SITECORE_SITE_NAME` | The name of your site. This variable overrides the config.appName defined in the package.json file. You can find this value in the Sites dashboard by opening the actions menu for a site and navigating to Settings > Developer settings. Default value ``,
+` |
+| `DEFAULT_LANGUAGE` | The default language of your app. Default value `en` |
+| `DEBUG` | Optional. Debug level for the proxy. Set the DEBUG environment variable to 'sitecore-jss:*,proxy*,http-proxy-middleware*' to see all logs. Refer to the [official docs](https://doc.sitecore.com/xp/en/developers/hd/latest/sitecore-headless-development/debug-logging-in-jss-apps.html#namespaces) for all the available namespaces.
+
+## Build & run
+
+### Running the application in production mode
+
+To build and run in production mode you need to have your angular app side by side with the [Node XM CLoud proxy](https://github.com/Sitecore/jss/tree/dev/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy). For additional information on how to set up and run SPA app in production mode against XM CLoud instance you can check the [spa-starters](https://github.com/sitecorelabs/xmcloud-foundation-head/tree/main/headapps/spa-starter) monoreopo readme.
+
+1. Run `npm install` for both angular and proxy apps
+2. Run `npm run build` in the angular app root; this will build the angular app and copy the the resulting `/dist` folder into the specified proxy app folder
+3. Run `npm run start` in the proxy app root
+
+### Running the application in connected mode
+
+When building and running your app in connected mode the proxy application is not needed. However, please note that certain features, such as personalization, server-side rendering, and editing, will not be available.
+
+1. Run `npm install`
+2. Run `npm run build`
+3. Run `npm run start:connected`
diff --git a/packages/create-sitecore-jss/src/templates/angular-xmcloud/package.json b/packages/create-sitecore-jss/src/templates/angular-xmcloud/package.json
index c7ffff1bba..06176d39ec 100644
--- a/packages/create-sitecore-jss/src/templates/angular-xmcloud/package.json
+++ b/packages/create-sitecore-jss/src/templates/angular-xmcloud/package.json
@@ -8,8 +8,8 @@
"prepare:proxy-build": "ts-node --project src/tsconfig.webpack-server.json ./scripts/proxy-build.ts"
},
"dependencies": {
- "@sitecore-cloudsdk/core": "^0.4.0-rc.0",
- "@sitecore-cloudsdk/events": "^0.4.0-rc.0",
+ "@sitecore-cloudsdk/core": "^0.4.1",
+ "@sitecore-cloudsdk/events": "^0.4.1",
"font-awesome": "^4.7.0",
"sass": "^1.52.3",
"sass-alias": "^1.0.5"
diff --git a/packages/create-sitecore-jss/src/templates/angular-xmcloud/scripts/config/plugins/xmcloud.ts b/packages/create-sitecore-jss/src/templates/angular-xmcloud/scripts/config/plugins/xmcloud.ts
index 5126ddcfe5..9c9f0201ec 100644
--- a/packages/create-sitecore-jss/src/templates/angular-xmcloud/scripts/config/plugins/xmcloud.ts
+++ b/packages/create-sitecore-jss/src/templates/angular-xmcloud/scripts/config/plugins/xmcloud.ts
@@ -18,6 +18,7 @@ class XMCloudPlugin implements ConfigPlugin {
process.env[`${constantCase('sitecoreEdgeUrl')}`]?.replace(/\/$/, '') ||
'https://edge-platform.sitecorecloud.io';
const sitecoreEdgeContextId = process.env[`${constantCase('sitecoreEdgeContextId')}`];
+ const personalizeScope = process.env[`${constantCase('personalizeScope')}`]
if (config.sitecoreApiKey && sitecoreEdgeContextId) {
console.log(
@@ -32,6 +33,7 @@ class XMCloudPlugin implements ConfigPlugin {
proxyHost,
sitecoreEdgeUrl,
sitecoreEdgeContextId,
+ personalizeScope,
});
}
}
diff --git a/packages/create-sitecore-jss/src/templates/angular-xmcloud/scripts/proxy-build.ts b/packages/create-sitecore-jss/src/templates/angular-xmcloud/scripts/proxy-build.ts
index 13b3343895..51c289581b 100644
--- a/packages/create-sitecore-jss/src/templates/angular-xmcloud/scripts/proxy-build.ts
+++ b/packages/create-sitecore-jss/src/templates/angular-xmcloud/scripts/proxy-build.ts
@@ -1,13 +1,13 @@
-import { execSync } from 'child_process';
import { environment } from '../src/environments/environment';
+import { cpSync, rmSync } from 'fs';
// 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' });
+ rmSync(environment.proxyBuildPath, { recursive: true, force: true });
+ cpSync('./dist', environment.proxyBuildPath, { recursive: true });
console.log('Proxy build prepared successfully!');
} catch (error) {
diff --git a/packages/create-sitecore-jss/src/templates/angular-xmcloud/server.exports.ts b/packages/create-sitecore-jss/src/templates/angular-xmcloud/server.exports.ts
index 5d2c0eb237..da0c739d01 100644
--- a/packages/create-sitecore-jss/src/templates/angular-xmcloud/server.exports.ts
+++ b/packages/create-sitecore-jss/src/templates/angular-xmcloud/server.exports.ts
@@ -5,12 +5,13 @@ import { layoutServiceFactory } from './src/app/lib/layout-service-factory';
import { environment } from './src/environments/environment';
import { components } from './src/app/components/app-components.module';
import metadata from './src/environments/metadata.json';
-
/**
* Define the required configuration values to be exported from the server.bundle.ts.
*/
const defaultLanguage = environment.defaultLanguage;
+const sitecoreSiteName = environment.sitecoreSiteName;
+const personalizeScope = environment.personalizeScope;
const getClientFactoryConfig = getGraphQLClientFactoryConfig;
export {
@@ -19,6 +20,8 @@ export {
dictionaryServiceFactory,
layoutServiceFactory,
defaultLanguage,
+ sitecoreSiteName,
+ personalizeScope,
components,
metadata,
};
diff --git a/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/components/image/image.component.html b/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/components/image/image.component.html
index f25621e0f4..177c0df84d 100644
--- a/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/components/image/image.component.html
+++ b/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/components/image/image.component.html
@@ -22,7 +22,7 @@
-
+
diff --git a/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/components/link-list/link-list.component.html b/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/components/link-list/link-list.component.html
index a977dc0125..2421121117 100644
--- a/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/components/link-list/link-list.component.html
+++ b/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/components/link-list/link-list.component.html
@@ -5,7 +5,7 @@
diff --git a/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/components/navigation/navigation-item.component.html b/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/components/navigation/navigation-item.component.html
index a975400ac6..e8d1f227a8 100644
--- a/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/components/navigation/navigation-item.component.html
+++ b/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/components/navigation/navigation-item.component.html
@@ -1,6 +1,6 @@
diff --git a/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/components/title/title.component.html b/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/components/title/title.component.html
index 1a9f1eba69..89fa1f00d5 100644
--- a/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/components/title/title.component.html
+++ b/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/components/title/title.component.html
@@ -1,7 +1,7 @@
diff --git a/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/lib/graphql-client-factory/config.ts b/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/lib/graphql-client-factory/config.ts
index f7453fbb3a..6cc44823c3 100644
--- a/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/lib/graphql-client-factory/config.ts
+++ b/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/lib/graphql-client-factory/config.ts
@@ -28,10 +28,9 @@ export const getGraphQLClientFactoryConfig = () => {
: getEdgeProxyContentUrl(env.sitecoreEdgeContextId, env.proxyHost),
};
} else if (env.graphQLEndpoint && env.sitecoreApiKey) {
- const graphQLEndpointPath = new URL(env.graphQLEndpoint).pathname;
-
+ // we ignore ssr-proxy and query CM directly in case apiKey is used (i.e. in dev docker deployments)
clientConfig = {
- endpoint: isServer ? env.graphQLEndpoint : `${env.proxyHost}${graphQLEndpointPath}`,
+ endpoint: env.graphQLEndpoint,
apiKey: env.sitecoreApiKey,
};
}
diff --git a/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/routing/scripts/cdp-page-view.component.ts b/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/routing/scripts/cdp-page-view.component.ts
new file mode 100644
index 0000000000..b30a270559
--- /dev/null
+++ b/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/routing/scripts/cdp-page-view.component.ts
@@ -0,0 +1,77 @@
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import { Subscription } from 'rxjs';
+import { isServer, CdpHelper, LayoutServicePageState } from '@sitecore-jss/sitecore-jss-angular';
+import { pageView, PageViewData } from '@sitecore-cloudsdk/events/browser';
+import { JssContextService } from '../../jss-context.service';
+import { JssState } from '../../JssState';
+import { environment } from '../../../environments/environment';
+
+/**
+ * This is the CDP page view component.
+ * It uses the Sitecore Cloud SDK to enable page view events on the client-side.
+ * See Sitecore Cloud SDK documentation for details.
+ * https://www.npmjs.com/package/@sitecore-cloudsdk/events
+ */
+@Component({
+ selector: 'app-cdp-page-view',
+ template: '',
+})
+export class CdpPageViewComponent implements OnInit, OnDestroy {
+ private contextSubscription: Subscription;
+
+ constructor(private jssContext: JssContextService) {}
+
+ ngOnInit(): void {
+ if (!isServer()) {
+ this.contextSubscription = this.jssContext.state.subscribe((newState: JssState) => {
+ const {
+ route,
+ context: { pageState, language, variantId },
+ } = newState.sitecore;
+
+ // Do not create events in editing or preview mode or if missing route data
+ if (pageState !== LayoutServicePageState.Normal || !route?.itemId) {
+ return;
+ }
+
+ // Do not create events if disabled (e.g. we don't have consent)
+ if (this.disabled()) {
+ return;
+ }
+
+ const scope = process.env.PERSONALIZE_SCOPE;
+ const pageVariantId = CdpHelper.getPageVariantId(
+ route.itemId,
+ language || environment.defaultLanguage,
+ variantId as string,
+ scope
+ );
+
+ const pageViewData: PageViewData = {
+ channel: 'WEB',
+ currency: 'USD',
+ page: route.name,
+ pageVariantId,
+ language,
+ };
+
+ pageView(pageViewData).catch((err) => console.debug(err));
+ });
+ }
+ }
+
+ ngOnDestroy() {
+ if (this.contextSubscription) {
+ this.contextSubscription.unsubscribe();
+ }
+ }
+
+ /**
+ * Determines if the page view events should be turned off.
+ * IMPORTANT: You should implement based on your cookie consent management solution of choice.
+ * By default it is disabled if not in production mode
+ */
+ disabled = () => {
+ return !environment.production;
+ };
+}
diff --git a/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/routing/scripts/cloud-sdk-init.component.ts b/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/routing/scripts/cloud-sdk-init.component.ts
index 8a99ae3c06..035d58f751 100644
--- a/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/routing/scripts/cloud-sdk-init.component.ts
+++ b/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/routing/scripts/cloud-sdk-init.component.ts
@@ -1,8 +1,11 @@
import { Component, OnInit } from '@angular/core';
+import { take } from 'rxjs/operators';
import { CloudSDK } from '@sitecore-cloudsdk/core/browser';
import '@sitecore-cloudsdk/events/browser';
+import { isServer, LayoutServicePageState } from '@sitecore-jss/sitecore-jss-angular';
import { environment } from '../../../environments/environment';
-import { isServer } from '@sitecore-jss/sitecore-jss-angular';
+import { JssContextService } from '../../jss-context.service';
+import { JssState } from '../../JssState';
/**
* Component to init CloudSDK logic - to allow events throughout the site
@@ -12,21 +15,34 @@ import { isServer } from '@sitecore-jss/sitecore-jss-angular';
template: '',
})
export class CloudSdkInitComponent implements OnInit {
- constructor() {}
+ constructor(private jssContext: JssContextService) {}
ngOnInit(): void {
- if (!isServer) {
- CloudSDK({
- siteName: environment.sitecoreSiteName,
- sitecoreEdgeUrl: environment.sitecoreEdgeUrl,
- sitecoreEdgeContextId: environment.sitecoreEdgeContextId,
- // Replace with the top level cookie domain of the website that is being integrated e.g ".example.com" and not "www.example.com"
- cookieDomain: window.location.hostname.replace(/^www\./, ''),
- // Cookie may be created in personalize middleware (server), but if not we should create it here
- enableBrowserCookie: true,
- })
- .addEvents()
- .initialize();
+ if (!isServer() && environment.production) {
+ // to ensure that CloudSDK initialization logic runs only once in the browser, take only the first emitted value of state
+ this.jssContext.state.pipe(take(1)).subscribe((newState: JssState) => {
+ const {
+ route,
+ context: { pageState },
+ } = newState.sitecore;
+
+ // Do not initialize CloudSDK in editing or preview mode or if missing route data
+ if (pageState !== LayoutServicePageState.Normal || !route?.itemId) {
+ return;
+ }
+
+ CloudSDK({
+ siteName: environment.sitecoreSiteName,
+ sitecoreEdgeUrl: environment.sitecoreEdgeUrl,
+ sitecoreEdgeContextId: environment.sitecoreEdgeContextId,
+ // Replace with the top level cookie domain of the website that is being integrated e.g ".example.com" and not "www.example.com"
+ cookieDomain: window.location.hostname.replace(/^www\./, ''),
+ // Cookie may be created in personalize middleware (server), but if not we should create it here
+ enableBrowserCookie: true,
+ })
+ .addEvents()
+ .initialize();
+ });
}
}
}
diff --git a/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/routing/scripts/scripts.component.html b/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/routing/scripts/scripts.component.html
index c39b8b6e85..caed1a4cf6 100644
--- a/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/routing/scripts/scripts.component.html
+++ b/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/routing/scripts/scripts.component.html
@@ -1,4 +1,5 @@
+
diff --git a/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/routing/scripts/scripts.module.ts b/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/routing/scripts/scripts.module.ts
index 1b25306864..26d09c93c1 100644
--- a/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/routing/scripts/scripts.module.ts
+++ b/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/routing/scripts/scripts.module.ts
@@ -2,10 +2,11 @@ import { NgModule } from '@angular/core';
import { ScriptsComponent } from './scripts.component';
import { JssModule } from '@sitecore-jss/sitecore-jss-angular';
import { CloudSdkInitComponent } from './cloud-sdk-init.component';
+import { CdpPageViewComponent } from './cdp-page-view.component';
@NgModule({
exports: [ScriptsComponent],
imports: [JssModule],
- declarations: [ScriptsComponent, CloudSdkInitComponent],
+ declarations: [ScriptsComponent, CloudSdkInitComponent, CdpPageViewComponent],
})
export class ScriptsModule {}
diff --git a/packages/create-sitecore-jss/src/templates/angular/angular.json b/packages/create-sitecore-jss/src/templates/angular/angular.json
index 59005fc14a..3797b6a3ad 100644
--- a/packages/create-sitecore-jss/src/templates/angular/angular.json
+++ b/packages/create-sitecore-jss/src/templates/angular/angular.json
@@ -24,7 +24,6 @@
"src/favicon.ico"
],
"styles": [
- "node_modules/bootstrap/dist/css/bootstrap.min.css",
"src/styles.css"
],
"scripts": [],
diff --git a/packages/create-sitecore-jss/src/templates/angular/package.json b/packages/create-sitecore-jss/src/templates/angular/package.json
index 0aaf5d313d..049587bd16 100644
--- a/packages/create-sitecore-jss/src/templates/angular/package.json
+++ b/packages/create-sitecore-jss/src/templates/angular/package.json
@@ -1,6 +1,6 @@
{
"name": "<%- appName %>",
- "version": "22.2.0-canary",
+ "version": "<%- version %>",
"description": "Application utilizing Sitecore JavaScript Services and Angular (angular-cli).",
"config": {
"appName": "<%- appName %>",
@@ -57,7 +57,7 @@
"@apollo/client": "^3.3.12",
"@ngx-translate/core": "~15.0.0",
"@ngx-translate/http-loader": "~8.0.0",
- "@sitecore-jss/sitecore-jss-angular": "~22.2.0-canary",
+ "@sitecore-jss/sitecore-jss-angular": "~<%- version %>",
"apollo-angular": "~6.0.0",
"bootstrap": "^5.3.3",
"core-js": "~3.37.1",
@@ -65,7 +65,8 @@
"graphql-tag": "~2.11.0",
"rxjs": "~7.8.1",
"tslib": "^2.6.3",
- "zone.js": "~0.14.7"
+ "zone.js": "~0.14.7",
+ "reflect-metadata": "^0.2.2"
},
"devDependencies": {
"@angular-builders/custom-webpack": "^17.0.2",
@@ -78,14 +79,14 @@
"@angular/cli": "~17.3.8",
"@angular/compiler-cli": "~17.3.11",
"@angular/language-service": "~17.3.11",
- "@sitecore-jss/sitecore-jss-angular-schematics": "~22.2.0-canary",
- "@sitecore-jss/sitecore-jss-cli": "~22.2.0-canary",
- "@sitecore-jss/sitecore-jss-dev-tools": "~22.2.0-canary",
+ "@sitecore-jss/sitecore-jss-angular-schematics": "~<%- version %>",
+ "@sitecore-jss/sitecore-jss-cli": "~<%- version %>",
+ "@sitecore-jss/sitecore-jss-dev-tools": "~<%- version %>",
"@types/jasmine": "~3.6.7",
"@types/jasminewd2": "~2.0.8",
- "@types/node": "~20.14.10",
- "@typescript-eslint/eslint-plugin": "^7.16.0",
- "@typescript-eslint/parser": "^7.16.0",
+ "@types/node": "~22.9.0",
+ "@typescript-eslint/eslint-plugin": "^8.14.0",
+ "@typescript-eslint/parser": "^8.14.0",
"chalk": "~4.1.0",
"chokidar": "^3.5.2",
"codelyzer": "~6.0.1",
@@ -107,9 +108,9 @@
"karma-jasmine": "~4.0.1",
"karma-jasmine-html-reporter": "~1.5.4",
"move-cli": "^2.0.0",
- "npm-run-all": "~4.1.5",
+ "npm-run-all2": "^7.0.1",
"protractor": "^7.0.0",
"ts-node": "~10.9.2",
- "typescript": "~5.2.2"
+ "typescript": "~5.4.0"
}
}
diff --git a/packages/create-sitecore-jss/src/templates/angular/src/tsconfig.app.json b/packages/create-sitecore-jss/src/templates/angular/src/tsconfig.app.json
index 12be690b66..f65e0423fa 100644
--- a/packages/create-sitecore-jss/src/templates/angular/src/tsconfig.app.json
+++ b/packages/create-sitecore-jss/src/templates/angular/src/tsconfig.app.json
@@ -3,8 +3,7 @@
"compilerOptions": {
"outDir": "../out-tsc/app",
"baseUrl": "./",
- "newLine": "LF",
- "types": []
+ "newLine": "LF"
},
"lib": [
"esnext.asynciterable"
diff --git a/packages/create-sitecore-jss/src/templates/angular/src/tsconfig.server.json b/packages/create-sitecore-jss/src/templates/angular/src/tsconfig.server.json
index e800d3cbcb..8583196bf9 100644
--- a/packages/create-sitecore-jss/src/templates/angular/src/tsconfig.server.json
+++ b/packages/create-sitecore-jss/src/templates/angular/src/tsconfig.server.json
@@ -5,8 +5,7 @@
"baseUrl": "./",
"target": "ES2022",
"module": "commonjs",
- "newLine": "LF",
- "types": []
+ "newLine": "LF"
},
"lib": [
"esnext.asynciterable"
diff --git a/packages/create-sitecore-jss/src/templates/angular/tsconfig.json b/packages/create-sitecore-jss/src/templates/angular/tsconfig.json
index d0459b7357..c74391d5f5 100644
--- a/packages/create-sitecore-jss/src/templates/angular/tsconfig.json
+++ b/packages/create-sitecore-jss/src/templates/angular/tsconfig.json
@@ -20,8 +20,10 @@
"noUnusedLocals": true,
"noUnusedParameters": true,
"newLine": "LF",
- "typeRoots": ["node_modules/@types"],
- "lib": ["es2018", "dom", "esnext.asynciterable"],
+ "types": ["node"],
+ // add additional type root in case of monorepo where dependencies are hoisted to root node_modules
+ "typeRoots": ["node_modules/@types", "../node_modules/@types"],
+ "lib": ["es2019", "dom", "esnext.asynciterable"],
"paths": {
"@angular/*": ["../node_modules/@angular/*"],
"rxjs": ["node_modules/rxjs", "../node_modules/rxjs"],
diff --git a/packages/create-sitecore-jss/src/templates/angular/webpack.config.js b/packages/create-sitecore-jss/src/templates/angular/webpack.config.js
index 3515f06fcd..6b2a02effa 100644
--- a/packages/create-sitecore-jss/src/templates/angular/webpack.config.js
+++ b/packages/create-sitecore-jss/src/templates/angular/webpack.config.js
@@ -1,7 +1,11 @@
-const Dotenv = require("dotenv-webpack");
+const Dotenv = require('dotenv-webpack');
+const path = require('path');
module.exports = {
- plugins: [
- new Dotenv(),
- ],
-}
+ plugins: [new Dotenv()],
+ resolve: {
+ alias: {
+ '@sitecore-cloudsdk': path.resolve(__dirname, 'node_modules/@sitecore-cloudsdk'),
+ },
+ },
+};
diff --git a/packages/create-sitecore-jss/src/templates/nextjs-styleguide/package.json b/packages/create-sitecore-jss/src/templates/nextjs-styleguide/package.json
index cc2658a5d5..f1723a9537 100644
--- a/packages/create-sitecore-jss/src/templates/nextjs-styleguide/package.json
+++ b/packages/create-sitecore-jss/src/templates/nextjs-styleguide/package.json
@@ -4,7 +4,7 @@
"nprogress": "~0.2.0"
},
"devDependencies": {
- "@sitecore-jss/sitecore-jss-dev-tools": "~22.2.0-canary",
+ "@sitecore-jss/sitecore-jss-dev-tools": "~<%- version %>",
"@types/nprogress": "^0.2.0"
},
"scripts": {
diff --git a/packages/create-sitecore-jss/src/templates/nextjs-styleguide/src/lib/sitemap-fetcher/plugins/disconnected-sitemap-service.ts b/packages/create-sitecore-jss/src/templates/nextjs-styleguide/src/lib/sitemap-fetcher/plugins/disconnected-sitemap-service.ts
index 38f8c49df0..6635b57e05 100644
--- a/packages/create-sitecore-jss/src/templates/nextjs-styleguide/src/lib/sitemap-fetcher/plugins/disconnected-sitemap-service.ts
+++ b/packages/create-sitecore-jss/src/templates/nextjs-styleguide/src/lib/sitemap-fetcher/plugins/disconnected-sitemap-service.ts
@@ -19,7 +19,7 @@ class DisconnectedSitemapServicePlugin implements SitemapFetcherPlugin {
if (process.env.JSS_MODE !== constants.JSS_MODE.DISCONNECTED) return null;
try {
- // eslint-disable-next-line @typescript-eslint/no-var-requires
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
const manifest = require('sitecore/manifest/sitecore-import.json');
return manifest;
diff --git a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/package.json b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/package.json
index c2d7a5e3aa..d2731a5503 100644
--- a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/package.json
+++ b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/package.json
@@ -1,7 +1,8 @@
{
"dependencies": {
- "@sitecore/components": "^1.1.10",
- "@sitecore-cloudsdk/events": "^0.3.1",
- "@sitecore-feaas/clientside": "^0.5.17"
+ "@sitecore/components": "~2.0.1",
+ "@sitecore-cloudsdk/core": "^0.4.1",
+ "@sitecore-cloudsdk/events": "^0.4.1",
+ "@sitecore-feaas/clientside": "^0.5.19"
}
}
diff --git a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/Bootstrap.tsx b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/Bootstrap.tsx
index e7343b9bc3..c69834458b 100644
--- a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/Bootstrap.tsx
+++ b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/Bootstrap.tsx
@@ -1,21 +1,36 @@
+import { useEffect } from 'react';
import { SitecorePageProps } from 'lib/page-props';
-import { context } from 'src/lib/context';
+import { CloudSDK } from '@sitecore-cloudsdk/core/browser';
+import '@sitecore-cloudsdk/events/browser';
import config from 'temp/config';
+import { LayoutServicePageState } from '@sitecore-jss/sitecore-jss-nextjs';
/**
* The Bootstrap component is the entry point for performing any initialization logic
* that needs to happen early in the application's lifecycle.
*/
const Bootstrap = (props: SitecorePageProps): JSX.Element | null => {
- /**
- * Initializes the application Context and associated Software Development Kits (SDKs).
- * This function is the entry point for setting up the application's context and any SDKs that are required for its proper functioning.
- * It prepares the resources needed to interact with various services and features within the application.
- */
- context.init({
- siteName: props.site?.name || config.sitecoreSiteName,
- pageState: props.layoutData?.sitecore?.context?.pageState,
- });
+ // Browser ClientSDK init allows for page view events to be tracked
+ useEffect(() => {
+ const pageState = props.layoutData?.sitecore?.context.pageState;
+ if (process.env.NODE_ENV === 'development')
+ console.debug('Browser Events SDK is not initialized in development environment');
+ else if (pageState !== LayoutServicePageState.Normal)
+ console.debug('Browser Events SDK is not initialized in edit and preview modes');
+ else {
+ CloudSDK({
+ sitecoreEdgeUrl: config.sitecoreEdgeUrl,
+ sitecoreEdgeContextId: config.sitecoreEdgeContextId,
+ siteName: props.site?.name || config.sitecoreSiteName,
+ enableBrowserCookie: true,
+ // Replace with the top level cookie domain of the website that is being integrated e.g ".example.com" and not "www.example.com"
+ cookieDomain: window.location.hostname.replace(/^www\./, ''),
+ })
+ .addEvents()
+ .initialize();
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [props.site?.name]);
return null;
};
diff --git a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/byoc/index.ts b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/byoc/index.tsx
similarity index 57%
rename from packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/byoc/index.ts
rename to packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/byoc/index.tsx
index 3339b869b2..311324be4f 100644
--- a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/byoc/index.ts
+++ b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/byoc/index.tsx
@@ -1,26 +1,43 @@
+import React from 'react';
import * as FEAAS from '@sitecore-feaas/clientside/react';
+import * as Events from '@sitecore-cloudsdk/events/browser';
import '@sitecore/components/context';
import dynamic from 'next/dynamic';
-import { context } from 'lib/context';
+import config from 'temp/config';
+import {
+ LayoutServicePageState,
+ SitecoreContextReactContext,
+} from '@sitecore-jss/sitecore-jss-nextjs';
/**
* This is an out-of-box bundler for External components (BYOC) (see Sitecore documentation for more details)
* It enables registering components in client-only or SSR/hybrid contexts
* It's recommended to not modify this file - please add BYOC imports in corresponding index.*.ts files instead
*/
-// Set context properties to be available within BYOC components
-FEAAS.setContextProperties(context);
-
// Import your client-only components via client-bundle. Nextjs's dynamic() call will ensure they are only rendered client-side
const ClientBundle = dynamic(() => import('./index.client'), {
ssr: false,
});
-// Import your hybrid (server rendering with client hydration) components via index.hybrid.ts
-import './index.hybrid';
-
// As long as component bundle is exported and rendered on page (as an empty element), client-only BYOC components are registered and become available
// The rest of components will be regsitered in both server and client-side contexts when this module is imported into Layout
FEAAS.enableNextClientsideComponents(dynamic, ClientBundle);
-export default FEAAS.ExternalComponentBundle;
+// Import your hybrid (server rendering with client hydration) components via index.hybrid.ts
+import './index.hybrid';
+
+const BYOCInit = (): JSX.Element | null => {
+ const sitecoreContext = React.useContext(SitecoreContextReactContext).context;
+ // Set context properties to be available within BYOC components
+ FEAAS.setContextProperties({
+ sitecoreEdgeUrl: config.sitecoreEdgeUrl,
+ sitecoreEdgeContextId: config.sitecoreEdgeContextId,
+ pageState: sitecoreContext?.pageState || LayoutServicePageState.Normal,
+ siteName: sitecoreContext?.site?.name || config.sitecoreSiteName,
+ eventsSDK: Events,
+ });
+
+ return ;
+};
+
+export default BYOCInit;
diff --git a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/components/CdpPageView.tsx b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/components/CdpPageView.tsx
index c02f64d33b..5ae9ac0f9d 100644
--- a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/components/CdpPageView.tsx
+++ b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/components/CdpPageView.tsx
@@ -4,8 +4,8 @@ import {
useSitecoreContext,
} from '@sitecore-jss/sitecore-jss-nextjs';
import { useEffect } from 'react';
+import { pageView } from '@sitecore-cloudsdk/events/browser';
import config from 'temp/config';
-import { context } from 'lib/context';
/**
* This is the CDP page view component.
@@ -46,19 +46,14 @@ const CdpPageView = (): JSX.Element => {
variantId as string,
scope
);
- // there are cases where Events SDK will be absent which are expected to reject
- context
- .getSDK('Events')
- .then((Events) =>
- Events.pageView({
- channel: 'WEB',
- currency: 'USD',
- page: route.name,
- pageVariantId,
- language,
- })
- )
- .catch((e) => console.debug(e));
+ // there can be cases where Events are not initialized which are expected to reject
+ pageView({
+ channel: 'WEB',
+ currency: 'USD',
+ page: route.name,
+ pageVariantId,
+ language,
+ }).catch((e) => console.debug(e));
}, [pageState, route, variantId, site]);
return <>>;
diff --git a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/context/index.ts b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/context/index.ts
deleted file mode 100644
index 4323c5ff53..0000000000
--- a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/context/index.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { Context } from '@sitecore-jss/sitecore-jss-nextjs/context';
-import config from 'temp/config';
-
-import Events from './sdk/events';
-
-/**
- * List of SDKs to be initialized.
- * Each SDK is defined as a module with the @type {SDK} type.
- */
-const sdks = {
- Events,
-};
-
-/**
- * Context instance that is used to initialize the application Context and associated Software Development Kits (SDKs).
- */
-export const context = new Context({
- sitecoreEdgeUrl: config.sitecoreEdgeUrl,
- sitecoreEdgeContextId: config.sitecoreEdgeContextId,
- siteName: config.sitecoreSiteName,
- sdks,
-});
diff --git a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/context/sdk/events.ts b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/context/sdk/events.ts
deleted file mode 100644
index a9faf3d12a..0000000000
--- a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/context/sdk/events.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import * as Events from '@sitecore-cloudsdk/events/browser';
-import { SDK } from '@sitecore-jss/sitecore-jss-nextjs/context';
-
-const sdkModule: SDK = {
- sdk: Events,
- init: async (props) => {
- // Events module can't be initialized on the server side
- // We also don't want to initialize it in development mode
- if (typeof window === 'undefined')
- throw 'Browser Events SDK is not initialized in server context';
- if (process.env.NODE_ENV === 'development')
- throw 'Browser Events SDK is not initialized in development environment';
-
- await Events.init({
- siteName: props.siteName,
- sitecoreEdgeUrl: props.sitecoreEdgeUrl,
- sitecoreEdgeContextId: props.sitecoreEdgeContextId,
- // Replace with the top level cookie domain of the website that is being integrated e.g ".example.com" and not "www.example.com"
- cookieDomain: window.location.hostname.replace(/^www\./, ''),
- // Cookie may be created in personalize middleware (server), but if not we should create it here
- enableBrowserCookie: true,
- });
- },
-};
-
-export default sdkModule;
diff --git a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/middleware/plugins/personalize.ts b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/middleware/plugins/personalize.ts
index 56df575774..b41aafefa5 100644
--- a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/middleware/plugins/personalize.ts
+++ b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/middleware/plugins/personalize.ts
@@ -21,7 +21,6 @@ class PersonalizePlugin implements MiddlewarePlugin {
order = 1;
constructor() {
-
this.personalizeMiddleware = new PersonalizeMiddleware({
// Configuration for your Sitecore Experience Edge endpoint
edgeConfig: {
diff --git a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/page-props-factory/plugins/content-styles.ts b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/page-props-factory/plugins/content-styles.ts
index 8935e335c0..95572edb24 100644
--- a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/page-props-factory/plugins/content-styles.ts
+++ b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/page-props-factory/plugins/content-styles.ts
@@ -14,7 +14,7 @@ class ContentStylesPlugin implements Plugin {
config.sitecoreEdgeUrl
);
- contentStyles && props.headLinks.push(contentStyles);
+ if (contentStyles) props.headLinks.push(contentStyles);
return props;
}
diff --git a/packages/create-sitecore-jss/src/templates/nextjs/.eslintrc b/packages/create-sitecore-jss/src/templates/nextjs/.eslintrc
index b49939ea40..48b6b42bc9 100644
--- a/packages/create-sitecore-jss/src/templates/nextjs/.eslintrc
+++ b/packages/create-sitecore-jss/src/templates/nextjs/.eslintrc
@@ -19,7 +19,12 @@
"@next/next/no-img-element": "off", // Don't force next/image
"jsx-a11y/alt-text": ["warn", { "elements": ["img"] }], // Don't force alt for (sourced from Sitecore media)
"no-unused-vars": "off",
- "@typescript-eslint/no-unused-vars": "error",
+ "@typescript-eslint/no-unused-vars": [
+ "error",
+ {
+ "caughtErrorsIgnorePattern": "."
+ }
+ ],
"@typescript-eslint/no-explicit-any": "error",
"jsx-quotes": ["error", "prefer-double"]
}
diff --git a/packages/create-sitecore-jss/src/templates/nextjs/package.json b/packages/create-sitecore-jss/src/templates/nextjs/package.json
index 7665d0d39a..67c0c89702 100644
--- a/packages/create-sitecore-jss/src/templates/nextjs/package.json
+++ b/packages/create-sitecore-jss/src/templates/nextjs/package.json
@@ -1,7 +1,7 @@
{
"name": "<%- appName %>",
"description": "Application utilizing Sitecore JavaScript Services and Next.js",
- "version": "22.2.0-canary",
+ "version": "<%- version %>",
"private": true,
"config": {
"appName": "<%- appName %>",
@@ -25,10 +25,10 @@
},
"license": "Apache-2.0",
"dependencies": {
- "@sitecore-jss/sitecore-jss-nextjs": "~22.2.0-canary",
+ "@sitecore-jss/sitecore-jss-nextjs": "~<%- version %>",
"graphql": "~15.8.0",
"graphql-tag": "^2.12.6",
- "next": "^14.2.7",
+ "next": "^14.2.18",
"next-localization": "^0.12.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@@ -43,19 +43,19 @@
"@graphql-codegen/typescript-operations": "^4.0.1",
"@graphql-codegen/typescript-resolvers": "^4.0.1",
"@graphql-typed-document-node/core": "^3.2.0",
- "@sitecore-jss/sitecore-jss-cli": "~22.2.0-canary",
- "@sitecore-jss/sitecore-jss-dev-tools": "~22.2.0-canary",
- "@types/node": "^20.14.2",
+ "@sitecore-jss/sitecore-jss-cli": "~<%- version %>",
+ "@sitecore-jss/sitecore-jss-dev-tools": "~<%- version %>",
+ "@types/node": "^22.9.0",
"@types/react": "^18.2.22",
"@types/react-dom": "^18.0.5",
- "@typescript-eslint/eslint-plugin": "^5.49.0",
- "@typescript-eslint/parser": "^5.49.0",
+ "@typescript-eslint/eslint-plugin": "^8.14.0",
+ "@typescript-eslint/parser": "^8.14.0",
"chalk": "~4.1.2",
"chokidar": "~3.5.3",
"constant-case": "^3.0.4",
"cross-env": "~7.0.3",
"dotenv-flow": "^4.1.0",
- "eslint": "^8.32.0",
+ "eslint": "^8.56.0",
"eslint-config-next": "^13.1.5",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-prettier": "^4.2.1",
@@ -66,7 +66,7 @@
"prettier": "^2.8.3",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.1.2",
- "typescript": "~4.9.4",
+ "typescript": "~5.4.0",
"yaml-loader": "^0.8.0"
},
"scripts": {
diff --git a/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/index.ts b/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/index.ts
index 683c6df2e1..9564e1a52f 100644
--- a/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/index.ts
+++ b/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/index.ts
@@ -1,4 +1,4 @@
-// eslint-disable-next-line @typescript-eslint/no-var-requires
+// eslint-disable-next-line @typescript-eslint/no-require-imports
const plugins = require('scripts/temp/config-plugins');
import { JssConfig } from 'lib/config';
diff --git a/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/plugins/scjssconfig.ts b/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/plugins/scjssconfig.ts
index d1368da595..18aa6479ed 100644
--- a/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/plugins/scjssconfig.ts
+++ b/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/plugins/scjssconfig.ts
@@ -12,6 +12,7 @@ class ScJssConfigPlugin implements ConfigPlugin {
async exec(config: JssConfig) {
let scJssConfig;
try {
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
scJssConfig = require('scjssconfig.json');
} catch (e) {
return config;
diff --git a/packages/create-sitecore-jss/src/templates/nextjs/scripts/generate-component-builder/index.ts b/packages/create-sitecore-jss/src/templates/nextjs/scripts/generate-component-builder/index.ts
index 2fd63ecb01..8bdc44606a 100644
--- a/packages/create-sitecore-jss/src/templates/nextjs/scripts/generate-component-builder/index.ts
+++ b/packages/create-sitecore-jss/src/templates/nextjs/scripts/generate-component-builder/index.ts
@@ -1,4 +1,4 @@
-// eslint-disable-next-line @typescript-eslint/no-var-requires
+// eslint-disable-next-line @typescript-eslint/no-require-imports
const plugins = require('scripts/temp/generate-component-builder-plugins');
import { PackageDefinition, ComponentFile } from '@sitecore-jss/sitecore-jss-dev-tools';
diff --git a/packages/create-sitecore-jss/src/templates/nextjs/scripts/scaffold-component/index.ts b/packages/create-sitecore-jss/src/templates/nextjs/scripts/scaffold-component/index.ts
index d182ae1503..d42f65c7d5 100644
--- a/packages/create-sitecore-jss/src/templates/nextjs/scripts/scaffold-component/index.ts
+++ b/packages/create-sitecore-jss/src/templates/nextjs/scripts/scaffold-component/index.ts
@@ -23,7 +23,7 @@ generatePlugins({
Edit this script if you wish to use your own conventions for component storage in your JSS app.
*/
-// eslint-disable-next-line @typescript-eslint/no-var-requires
+// eslint-disable-next-line @typescript-eslint/no-require-imports
const plugins = require('scripts/temp/scaffold-component-plugins');
export interface ScaffoldComponentPluginConfig {
diff --git a/packages/create-sitecore-jss/src/templates/nextjs/scripts/utils.ts b/packages/create-sitecore-jss/src/templates/nextjs/scripts/utils.ts
index 7391a957bb..91781714ff 100644
--- a/packages/create-sitecore-jss/src/templates/nextjs/scripts/utils.ts
+++ b/packages/create-sitecore-jss/src/templates/nextjs/scripts/utils.ts
@@ -40,7 +40,7 @@ export function getItems- (settings: {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const name = item.name.match(fileFormat)![1];
items.push(resolveItem(path, name));
- cb && cb(name);
+ if (cb) cb(name);
}
});
diff --git a/packages/create-sitecore-jss/src/templates/nextjs/tsconfig.json b/packages/create-sitecore-jss/src/templates/nextjs/tsconfig.json
index 223ec264e1..c1c4deac9c 100644
--- a/packages/create-sitecore-jss/src/templates/nextjs/tsconfig.json
+++ b/packages/create-sitecore-jss/src/templates/nextjs/tsconfig.json
@@ -10,6 +10,7 @@
"react": ["node_modules/react"]
},
"target": "es5",
+ "types": ["node"],
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
diff --git a/packages/create-sitecore-jss/src/templates/node-headless-ssr-experience-edge/package.json b/packages/create-sitecore-jss/src/templates/node-headless-ssr-experience-edge/package.json
index 3017c0be2b..8303493185 100644
--- a/packages/create-sitecore-jss/src/templates/node-headless-ssr-experience-edge/package.json
+++ b/packages/create-sitecore-jss/src/templates/node-headless-ssr-experience-edge/package.json
@@ -1,6 +1,6 @@
{
"name": "node-headless-ssr-experience-edge-sample",
- "version": "22.2.0-canary",
+ "version": "<%- version %>",
"description": "Node server-side-rendering sample for running JSS apps under Node hosting using Experience Edge",
"scripts": {
"start": "ts-node ./src/index.ts"
@@ -19,7 +19,7 @@
"homepage": "https://jss.sitecore.com",
"license": "Apache-2.0",
"dependencies": {
- "@sitecore-jss/sitecore-jss": "~22.2.0-canary",
+ "@sitecore-jss/sitecore-jss": "~<%- version %>",
"compression": "^1.7.4",
"express": "^4.18.2",
"dotenv": "^16.0.3"
@@ -28,7 +28,7 @@
"@types/compression": "^1.7.2",
"@types/express": "^4.17.17",
"ts-node": "^10.9.1",
- "typescript": "~4.9.5"
+ "typescript": "~5.6.3"
},
"private": true
}
diff --git a/packages/create-sitecore-jss/src/templates/node-headless-ssr-proxy/package.json b/packages/create-sitecore-jss/src/templates/node-headless-ssr-proxy/package.json
index 5286e053b1..c7ac736c66 100644
--- a/packages/create-sitecore-jss/src/templates/node-headless-ssr-proxy/package.json
+++ b/packages/create-sitecore-jss/src/templates/node-headless-ssr-proxy/package.json
@@ -1,6 +1,6 @@
{
"name": "node-headless-ssr-proxy-sample",
- "version": "22.2.0-canary",
+ "version": "<%- version %>",
"description": "Node server-side-rendering proxy sample for running JSS apps under Node hosting",
"scripts": {
"start": "ts-node ./src/index.ts"
@@ -19,8 +19,8 @@
"homepage": "https://jss.sitecore.com",
"license": "Apache-2.0",
"dependencies": {
- "@sitecore-jss/sitecore-jss": "~22.2.0-canary",
- "@sitecore-jss/sitecore-jss-proxy": "~22.2.0-canary",
+ "@sitecore-jss/sitecore-jss": "~<%- version %>",
+ "@sitecore-jss/sitecore-jss-proxy": "~<%- version %>",
"agentkeepalive": "^4.2.1",
"compression": "~1.7.4",
"express": "~4.19.2",
@@ -31,9 +31,9 @@
"@types/compression": "^1.7.2",
"@types/express": "^4.17.17",
"@types/memory-cache": "^0.2.2",
- "@types/node": "^20.14.2",
+ "@types/node": "^22.9.0",
"ts-node": "^10.9.1",
- "typescript": "~4.9.5"
+ "typescript": "~5.6.3"
},
"private": true
}
diff --git a/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/.env b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/.env
index a92a843a95..42393ebc47 100644
--- a/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/.env
+++ b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/.env
@@ -12,3 +12,9 @@ PROXY_BUNDLE_PATH=
# Set the DEBUG environment variable to 'sitecore-jss:*,sitecore-jss:proxy,http-proxy-middleware*' to see all logs:
#DEBUG=sitecore-jss:*,http-proxy-middleware*
+
+# Timeout (ms) for Sitecore CDP requests to respond within. Default is 400.
+PERSONALIZE_MIDDLEWARE_CDP_TIMEOUT=
+
+# Timeout (ms) for Sitecore Experience Edge requests to respond within. Default is 400.
+PERSONALIZE_MIDDLEWARE_EDGE_TIMEOUT=
diff --git a/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/README.md b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/README.md
index f64f04bf33..abde7f6945 100644
--- a/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/README.md
+++ b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/README.md
@@ -2,35 +2,129 @@
> Sitecore JSS Proxy for XM Cloud is considered experimental.
-[Documentation]()
+[Documentation](TODO)
+
+This proxy will serve as the backbone for supporting various SPA frameworks by handling server-side rendering (SSR), data queries, and middleware functionalities.
This is a sample setup showing one of how you can configure XM Cloud rendering server.
-## Pre-requisites
+## Features Supported
-1. SPA sample supports XM Cloud out of the box.
+- `Context ID`: The Context ID environment variable simplifies setting up and configuring XM Cloud solutions. It's a unified identifier that maps to all your configured resources, such as content, sites, files, forms, and integration settings.
+
+- `XM Cloud Pages editing integration`: full integration with Pages - the dynamic visual page editor of XM Cloud.
+
+- `XM Cloud proxy personalization` with embedded personalization and A/B Component Test support.
+
+- `Forms support`: provides the capability to consume and post Sitecore Forms from JSS apps. Sitecore Forms is a form-authoring framework that enables marketers to author their own forms, collect data, and analyze form performance.
+
+## Configuration Setup
+
+The config.ts file in this proxy app handles essential configurations for server-side rendering, data queries, and middleware functionalities. Here are the main configurations defined:
+
+- Server Bundle Configuration:
+
+ - The app loads a server.bundle.js file, pre-built from your SPA app, for SSR support.
+ - This file contains the configuration and factory functions essential for rendering and data querying.
+
+- GraphQL Endpoint Setup:
-1. Build your SPA app bundle with `jss build`. The build output should be placed in the `dist` folder.
+ - Defines a graphQLEndpoint for handling Sitecore GraphQL requests. It differentiates between production (Sitecore Edge) and development (Sitecore CM) endpoints.
+ - Constructs the target URL and path for proxy requests, ensuring compliance with http-proxy-middleware requirements.
-## Setup
+- Port Configuration:
-Open `config.js` and specify your application settings.
+ - Configures the port for running the proxy, with a default of 3000 or an environment-specified port.
+
+- Personalization Configuration (personalizeConfig):
+
+ - Sets up Sitecore personalization through PersonalizeConfig, defining settings for both Sitecore Experience Edge and CDP endpoints.
+ - Contains options to control personalization features, including:
+ - Timeouts for Edge and CDP endpoints (default 400ms, configurable via environment variables).
+ - Scope and site name used for Sitecore Personalize.
+ - Enable/Disable Switch: Functions that allow you to conditionally disable personalization based on the environment (e.g., disabled in development mode) and cookie consent policy.
+ - Language Configuration: defaultLanguage serves as a fallback if language data is unavailable in layout data.
+
+This configuration is designed to be flexible and secure, with dynamic settings managed via environment variables where appropriate.
### Environment Variables
The following environment variables can be set to configure the Proxy sample instead of modifying `config.js`. You can use the `.env` file located in the root of the app or set these directly in the environment (for example, in containers).
-| Parameter | Description |
-| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
-| `PROXY_BUNDLE_PATH` | Path to the JSS SPA app's `server.bundle.js`. Default can be seen in [config.js](./config.js) file. |
-| `PROXY_PORT` | Optional. Port which will be used when start sample. Default can be seen in [config.js](./config.js) file. |
-| `DEBUG` | Optional. Debug level for the proxy. Set the DEBUG environment variable to 'sitecore-jss:*,proxy*,http-proxy-middleware*' to see all logs. |
+| Parameter | Description |
+| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `PROXY_BUNDLE_PATH` | Path to the JSS SPA app's `server.bundle.js`. Default can be seen in [config.js](./config.js) file. |
+| `PROXY_PORT` | Optional. Port which will be used when start sample. Default can be seen in [config.js](./config.js) file. |
+| `DEBUG` | Optional. Debug level for the proxy. Set the DEBUG environment variable to 'sitecore-jss:_,proxy_,http-proxy-middleware\*, 'sitecore-jss:layout','sitecore-jss:personalize' to see all logs. |
+
+## Pre-requisites
+
+1. SPA sample supports XM Cloud out of the box.
+
+2. Build your SPA app bundle with `jss build` or `npm run build`. The build output should be placed in the `dist` folder. |
## Build & run
1. Run `npm install`
-1. Run `npm run start`
+2. Run `npm run start`
You should be able to see the following message:
`server listening on port 3000!`.
+
+## Deploy to Netlify
+
+`NOTE: If you are using the Angular starter from the XM-Cloud Foundation repository within a monorepo, please skip to Step 3.`
+
+1. Run `npm init` in the root directory and add the following scripts to package.json:
+ ```
+ "build": "cd ./ && npm run build-all && cd ..",
+ "install": "cd ./ && npm install && npm run install-all && cd ..",
+ ```
+2. Ensure that `/package.json` includes the following commands to handle the build and install steps properly::
+ ```
+ "build-all": "cd ../angular && npm run build && cd ../ && tsc",
+ "install-all": "cd ../angular && npm i && cd ../"
+ ```
+3. Add `serverless-http` to the list of dependencies in `/package.json` and then add the following variable to your ``/src/index.ts` file.
+ ```
+ export const handler = serverless(server);
+ ```
+4. Create a `netlfiy.toml` file if not already created and ensure that the following Netlify configuration is added there:
+ - Following functions lets the proxy app to treated as netlify functions. [Functions Overview](https://docs.netlify.com/functions/overview/)
+ ```
+ [functions]
+ directory = "/src"
+ node_bundler = "esbuild"
+ included_files = ["/dist/**"]
+ ```
+ - To ensure that static assets are accessed properly we may need to add redirects statement for them to the toml file:
+ ```
+ [[redirects]]
+ from = "/dist/browser/*"
+ status = 200
+ to = "/browser/:splat"
+ ```
+ - To ensure that static files under /dist are not accessible via browser add `force=true` to the above
+ ```
+ [[redirects]]
+ from = "/*"
+ status = 200
+ to = "/.netlify/functions/index/:splat"
+ force = true
+ ```
+ - Build command
+ ```
+ [build]
+ command = "npm run build"
+ publish = "/dist"
+ ```
+5. Create your netlify deployment: [A Step-by-Step Guide: Deploying on Netlify | Netlify](https://www.netlify.com/blog/2016/09/29/a-step-by-step-guide-deploying-on-netlify/)
+ a. Set up all your necessary environment variables like SITECORE_EDGE_CONTEXT_ID, SITECORE_SITE_NAME etc.
+ b. Set up your build settings in Site configuration --> Build and Deploy tab.
+ sample configuration:
+ Base Directory: /
+ Build command: npm run build
+ Publish directory: /proxy/dist
+ Functions directory: /proxy/src
+ NOTE: If proxy/dist folder is not picked up properly by Netlify make sure that the `PROXY_BUILD_PATH` env variable is unix style path e.g. `../proxy/dist`
diff --git a/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/package.json b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/package.json
index b05cf0f709..9aa81ab91d 100644
--- a/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/package.json
+++ b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/package.json
@@ -1,6 +1,6 @@
{
"name": "node-xmcloud-sample",
- "version": "22.2.0-canary",
+ "version": "<%- version %>",
"description": "Node XM Cloud Proxy sample for running XM Cloud JSS SPA apps",
"author": {
"name": "Sitecore Corporation",
@@ -10,17 +10,17 @@
"start": "ts-node ./src/index.ts"
},
"dependencies": {
- "@sitecore-jss/sitecore-jss-proxy": "~22.2.0-canary",
+ "@sitecore-jss/sitecore-jss-proxy": "~<%- version %>",
"compression": "^1.7.4",
"express": "^4.18.2",
"dotenv": "^16.0.3",
"http-proxy-middleware": "^3.0.0"
},
"devDependencies": {
- "@sitecore-jss/sitecore-jss": "~22.2.0-canary",
+ "@sitecore-jss/sitecore-jss": "~<%- version %>",
"@types/compression": "^1.7.2",
"@types/express": "^4.17.17",
"ts-node": "^10.9.1",
- "typescript": "~4.9.5"
+ "typescript": "~5.6.3"
}
}
diff --git a/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/src/config.ts b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/src/config.ts
index 2c3f8d2c79..830f063804 100644
--- a/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/src/config.ts
+++ b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/src/config.ts
@@ -1,5 +1,5 @@
import { Config, ServerBundle } from './types';
-
+import { PersonalizeConfig } from '@sitecore-jss/sitecore-jss-proxy';
/**
* The server.bundle.js file from your pre-built SPA app.
*/
@@ -13,6 +13,39 @@ try {
throw new Error(`ERROR: The server.bundle.js error. ${error}`);
}
+const clientFactoryConfig = serverBundle.getClientFactoryConfig();
+
+/**
+ * GraphQL endpoint resolution to meet the requirements of the http-proxy-middleware
+ */
+export const graphQLEndpoint = (() => {
+ try {
+ const graphQLEndpoint = new URL(clientFactoryConfig.endpoint);
+ // GraphQL endpoint URL (Edge endpoint for production and GraphQL Sitecore CM endpoint for dev)
+ const graphQLEndpointUrl = `${graphQLEndpoint.protocol}//${graphQLEndpoint.hostname}`;
+ // Sitecore Edge Context ID - will only be present for production
+ const sitecoreEdgeContextId = graphQLEndpoint.searchParams.get('sitecoreContextId');
+ // Browser request path to the proxy. Includes only the pathname.
+ const pathname = graphQLEndpoint.pathname;
+ // Target URL for the proxy. Can't include the query string.
+ const target = `${graphQLEndpointUrl}${pathname}`;
+
+ return {
+ target,
+ path: pathname,
+ graphQLEndpointUrl,
+ sitecoreEdgeContextId,
+ };
+ } catch (error) {
+ throw new Error(
+ `ERROR: The serverBundle should export a getClientFactoryConfig function with valid GraphQL endpoint URL returned, current value is ${clientFactoryConfig.endpoint}. ` +
+ 'Please check your server bundle.'
+ );
+ }
+})();
+
+const { clientFactory } = serverBundle;
+
export const config: Config = {
/**
* The require'd server.bundle.js file from your pre-built SPA app.
@@ -23,3 +56,36 @@ export const config: Config = {
*/
port: process.env.PROXY_PORT || 3000,
};
+
+export const personalizeConfig: PersonalizeConfig = {
+ // Configuration for your Sitecore Experience Edge endpoint
+ edgeConfig: {
+ clientFactory,
+ timeout:
+ (process.env.PERSONALIZE_MIDDLEWARE_EDGE_TIMEOUT &&
+ parseInt(process.env.PERSONALIZE_MIDDLEWARE_EDGE_TIMEOUT)) ||
+ 400,
+ },
+ // Configuration for your Sitecore CDP endpoint
+ // Edge URL and ID can be taken from proxy env, or the base SPA app
+ cdpConfig: {
+ sitecoreEdgeUrl: graphQLEndpoint.graphQLEndpointUrl,
+ sitecoreEdgeContextId: graphQLEndpoint.sitecoreEdgeContextId || '',
+ timeout:
+ (process.env.PERSONALIZE_MIDDLEWARE_CDP_TIMEOUT &&
+ parseInt(process.env.PERSONALIZE_MIDDLEWARE_CDP_TIMEOUT)) ||
+ 400,
+ },
+ // Optional Sitecore Personalize scope identifier.
+ scope: serverBundle.personalizeScope,
+ // This function determines if the personalization should be turned off.
+ // IMPORTANT: You should implement based on your cookie consent management solution of choice.
+ // You may wish to keep it disabled while in development mode.
+ // Personalization will also be disabled when edge context id is missing
+ disabled: () => process.env.NODE_ENV === 'development' || !graphQLEndpoint.sitecoreEdgeContextId,
+ // This function determines if a route should be excluded from personalization.
+ excludeRoute: () => false,
+ sitecoreSiteName: serverBundle.sitecoreSiteName || '',
+ // defaultLanguage will be used as fallback for personalization, if language cannot be read from layout service data
+ defaultLanguage: serverBundle.defaultLanguage,
+};
diff --git a/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/src/index.ts b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/src/index.ts
index 65d1f3f8cf..85e2390ea5 100644
--- a/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/src/index.ts
+++ b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/src/index.ts
@@ -1,11 +1,11 @@
import 'dotenv/config';
import express, { Response } from 'express';
import compression from 'compression';
-import { createProxyMiddleware } from 'http-proxy-middleware';
+import { createProxyMiddleware, fixRequestBody } from 'http-proxy-middleware';
import { debug } from '@sitecore-jss/sitecore-jss';
-import { editingRouter } from '@sitecore-jss/sitecore-jss-proxy';
-import { healthCheck } from '@sitecore-jss/sitecore-jss-proxy';
-import { config } from './config';
+import { editingRouter, healthCheck } from '@sitecore-jss/sitecore-jss-proxy';
+import { config, graphQLEndpoint } from './config';
+import { personalizeHelper, personalizePlugin } from './personalize';
const server = express();
@@ -45,31 +45,6 @@ const layoutService = layoutServiceFactory.create();
const dictionaryService = dictionaryServiceFactory.create();
-const clientFactoryConfig = config.serverBundle.getClientFactoryConfig();
-
-/**
- * GraphQL endpoint resolution to meet the requirements of the http-proxy-middleware
- */
-const graphQLEndpoint = (() => {
- try {
- const graphQLEndpoint = new URL(clientFactoryConfig.endpoint);
- // Browser request path to the proxy. Includes only the pathname.
- const pathname = graphQLEndpoint.pathname;
- // Target URL for the proxy. Can't include the query string.
- const target = `${graphQLEndpoint.protocol}//${graphQLEndpoint.hostname}${pathname}`;
-
- return {
- target,
- path: pathname,
- };
- } catch (error) {
- throw new Error(
- `ERROR: The serverBundle should export a getClientFactoryConfig function with valid GraphQL endpoint URL returned, current value is ${clientFactoryConfig.endpoint}. ` +
- 'Please check your server bundle.'
- );
- }
-})();
-
/**
* Parse requested url in order to detect current route and language
* @param {string} reqRoute requested route
@@ -105,6 +80,8 @@ const handleError = (res: Response, err: unknown) => {
// enable gzip compression for appropriate file types
server.use(compression());
+// enable access to req.body
+server.use(graphQLEndpoint.path, express.json());
// turn off x-powered-by http header
server.settings['x-powered-by'] = false;
@@ -125,6 +102,12 @@ server.use(
createProxyMiddleware({
target: graphQLEndpoint.target,
changeOrigin: true,
+ selfHandleResponse: true,
+ on: {
+ proxyReq: fixRequestBody,
+ },
+ // for client-side routing, personalization is performed by modifying layout service response
+ plugins: [personalizePlugin],
})
);
@@ -165,11 +148,17 @@ server.use(async (req, res) => {
}
// Language is required. In case it's not specified in the requested URL, fallback to the default language from the app configuration.
- const layoutData = await layoutService.fetchLayoutData(
+ let layoutData = await layoutService.fetchLayoutData(
route,
lang || config.serverBundle.defaultLanguage
);
-
+ // for SSR loading routing, personalization is performed by modifying layoutData directly
+ const personalizedLayoutData = await personalizeHelper.personalizeLayoutData(
+ req,
+ res,
+ layoutData
+ );
+ layoutData = personalizedLayoutData;
const viewBag = { dictionary: {} };
viewBag.dictionary = await dictionaryService.fetchDictionaryData(
diff --git a/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/src/personalize.ts b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/src/personalize.ts
new file mode 100644
index 0000000000..6aae1db9c6
--- /dev/null
+++ b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/src/personalize.ts
@@ -0,0 +1,34 @@
+import { GRAPHQL_LAYOUT_QUERY_NAME, PersonalizeHelper } from '@sitecore-jss/sitecore-jss-proxy';
+import { personalizeConfig } from './config';
+import { responseInterceptor } from 'http-proxy-middleware';
+import { Plugin } from 'http-proxy-middleware/dist/types';
+import { IncomingMessageWithBody } from './types';
+
+export const personalizeHelper = new PersonalizeHelper(personalizeConfig);
+
+// personalize plugin to modify intercepted Layout Service request data
+export const personalizePlugin: Plugin = (proxyServer) => {
+ proxyServer.on(
+ 'proxyRes',
+ responseInterceptor(async (responseBuffer, _, req, res) => {
+ let responseText = responseBuffer.toString('utf8');
+ const payload = JSON.stringify((req as IncomingMessageWithBody).body);
+
+ // only apply personalization onto JSS layout service results
+ if (payload.includes(GRAPHQL_LAYOUT_QUERY_NAME)) {
+ let layoutDataRaw = JSON.parse(responseText);
+ if (!layoutDataRaw?.data?.layout?.item?.rendered?.sitecore) {
+ return responseText;
+ }
+ const personalizedLayout = await personalizeHelper.personalizeLayoutData(
+ req as IncomingMessageWithBody,
+ res,
+ layoutDataRaw?.data?.layout?.item?.rendered
+ );
+ layoutDataRaw.data.layout.item.rendered = personalizedLayout;
+ responseText = JSON.stringify(layoutDataRaw);
+ }
+ return responseText;
+ })
+ );
+};
diff --git a/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/src/types.ts b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/src/types.ts
index 43d34f4ec9..8aeebf7438 100644
--- a/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/src/types.ts
+++ b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/src/types.ts
@@ -1,3 +1,4 @@
+/* eslint-disable no-unused-vars */
import {
GraphQLRequestClientFactory,
GraphQLRequestClientFactoryConfig,
@@ -6,6 +7,7 @@ import { DictionaryService } from '@sitecore-jss/sitecore-jss/i18n';
import { Metadata } from '@sitecore-jss/sitecore-jss/utils';
import { LayoutService } from '@sitecore-jss/sitecore-jss/layout';
import { AppRenderer, RouteUrlParser } from '@sitecore-jss/sitecore-jss-proxy';
+import { IncomingMessage } from 'http';
export interface ServerBundle {
[key: string]: unknown;
@@ -14,6 +16,8 @@ export interface ServerBundle {
clientFactory: GraphQLRequestClientFactory;
getClientFactoryConfig: () => GraphQLRequestClientFactoryConfig;
defaultLanguage: string;
+ sitecoreSiteName: string;
+ personalizeScope?: string;
layoutServiceFactory: { create: () => LayoutService };
dictionaryServiceFactory: { create: () => DictionaryService };
components: string[] | Map;
@@ -25,3 +29,10 @@ export interface Config {
port: string | number;
serverBundle: ServerBundle;
}
+
+/**
+ * IncomingMessage type modified with exporess.json() call to include request body
+ */
+export type IncomingMessageWithBody = IncomingMessage & {
+ body: ReadableStream | null;
+};
diff --git a/packages/create-sitecore-jss/src/templates/react-native/package.json b/packages/create-sitecore-jss/src/templates/react-native/package.json
index 3735cfba46..ca352662ec 100644
--- a/packages/create-sitecore-jss/src/templates/react-native/package.json
+++ b/packages/create-sitecore-jss/src/templates/react-native/package.json
@@ -1,6 +1,6 @@
{
"name": "<%- appName %>",
- "version": "22.2.0-canary",
+ "version": "<%- version %>",
"description": "A basic React Native app utilizing Sitecore JavaScript Services",
"config": {
"appName": "<%- appName %>",
@@ -23,7 +23,7 @@
},
"dependencies": {
"@react-native-community/masked-view": "^0.1.10",
- "@sitecore-jss/sitecore-jss-react-native": "~22.2.0-canary",
+ "@sitecore-jss/sitecore-jss-react-native": "~<%- version %>",
"prop-types": "^15.6.0",
"react": "16.13.1",
"react-native": "^0.63.4",
@@ -37,8 +37,8 @@
},
"private": true,
"devDependencies": {
- "@sitecore-jss/sitecore-jss-cli": "~22.2.0-canary",
- "@sitecore-jss/sitecore-jss-dev-tools": "~22.2.0-canary",
+ "@sitecore-jss/sitecore-jss-cli": "~<%- version %>",
+ "@sitecore-jss/sitecore-jss-dev-tools": "~<%- version %>",
"babel-core": "^6.26.0",
"babel-eslint": "^8.2.1",
"babel-plugin-inline-replace-variables": "^1.3.1",
diff --git a/packages/create-sitecore-jss/src/templates/react/.eslintrc b/packages/create-sitecore-jss/src/templates/react/.eslintrc
index 4ae1040451..0dea742ef1 100644
--- a/packages/create-sitecore-jss/src/templates/react/.eslintrc
+++ b/packages/create-sitecore-jss/src/templates/react/.eslintrc
@@ -14,6 +14,8 @@
"ecmaFeatures": {
"jsx": true
},
+ //prevent 'ImportDeclaration should appear when the mode is ES6' error when running lint
+ "ecmaVersion": 13,
"babelOptions": {
"presets": ["@babel/preset-react"]
}
diff --git a/packages/create-sitecore-jss/src/templates/react/package.json b/packages/create-sitecore-jss/src/templates/react/package.json
index cab94ed384..e27ffef885 100644
--- a/packages/create-sitecore-jss/src/templates/react/package.json
+++ b/packages/create-sitecore-jss/src/templates/react/package.json
@@ -1,7 +1,7 @@
{
"name": "<%- appName %>",
"description": "Application utilizing Sitecore JavaScript Services and React (create-react-app).",
- "version": "22.2.0-canary",
+ "version": "<%- version %>",
"private": true,
"config": {
"appName": "<%- appName %>",
@@ -28,7 +28,7 @@
"license": "Apache-2.0",
"dependencies": {
"@apollo/client": "^3.7.1",
- "@sitecore-jss/sitecore-jss-react": "~22.2.0-canary",
+ "@sitecore-jss/sitecore-jss-react": "~<%- version %>",
"axios": "^1.2.0",
"bootstrap": "^5.2.3",
"cross-fetch": "^3.1.5",
@@ -53,9 +53,9 @@
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@babel/register": "~7.18.9",
- "@sitecore-jss/sitecore-jss-cli": "~22.2.0-canary",
- "@sitecore-jss/sitecore-jss-dev-tools": "~22.2.0-canary",
- "@sitecore-jss/sitecore-jss-rendering-host": "~22.2.0-canary",
+ "@sitecore-jss/sitecore-jss-cli": "~<%- version %>",
+ "@sitecore-jss/sitecore-jss-dev-tools": "~<%- version %>",
+ "@sitecore-jss/sitecore-jss-rendering-host": "~<%- version %>",
"babel-loader": "~9.1.0",
"babel-preset-react-app": "~10.0.1",
"chalk": "~4.1.2",
@@ -64,7 +64,7 @@
"cross-spawn": "^7.0.3",
"del-cli": "^5.0.0",
"dotenv": "^16.0.3",
- "eslint": "^8.28.0",
+ "eslint": "^8.56.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.2.1",
diff --git a/packages/create-sitecore-jss/src/templates/vue/package.json b/packages/create-sitecore-jss/src/templates/vue/package.json
index 833e2b9b30..a90eb40841 100644
--- a/packages/create-sitecore-jss/src/templates/vue/package.json
+++ b/packages/create-sitecore-jss/src/templates/vue/package.json
@@ -1,6 +1,6 @@
{
"name": "<%- appName %>",
- "version": "22.2.0-canary",
+ "version": "<%- version %>",
"description": "Application utilizing Sitecore JavaScript Services and Vue (vue-cli).",
"private": true,
"config": {
@@ -45,18 +45,17 @@
"dependencies": {
"@apollo/client": "^3.7.4",
"@panter/vue-i18next": "~0.15.2",
- "@sitecore-jss/sitecore-jss-vue": "~22.2.0-canary",
- "@vue/apollo-composable": "4.0.0-beta.2",
- "@vue/apollo-option": "^4.0.0-alpha.20",
- "@vue/apollo-ssr": "^4.0.0-alpha.18",
- "@vue/server-renderer": "^3.2.45",
+ "@sitecore-jss/sitecore-jss-vue": "~<%- version %>",
+ "@vue/apollo-composable": "4.2.1",
+ "@vue/apollo-option": "^4.0.0",
+ "@vue/apollo-ssr": "^4.0.0",
"axios": "^1.2.3",
"bootstrap": "^5.2.3",
"cross-fetch": "~3.1.5",
"graphql": "^16.6.0",
"register-service-worker": "~1.7.2",
"serialize-javascript": "^6.0.1",
- "vue": "^3.2.45",
+ "vue": "^3.5.12",
"vue-i18n": "9.1.10",
"vue-meta": "3.0.0-alpha.10",
"vue-router": "4.0.16"
@@ -64,19 +63,18 @@
"devDependencies": {
"@babel/eslint-parser": "^7.19.1",
"@babel/register": "7.18.9",
- "@sitecore-jss/sitecore-jss-cli": "~22.2.0-canary",
- "@sitecore-jss/sitecore-jss-dev-tools": "~22.2.0-canary",
+ "@sitecore-jss/sitecore-jss-cli": "~<%- version %>",
+ "@sitecore-jss/sitecore-jss-dev-tools": "~<%- version %>",
"@vue/cli-plugin-babel": "~5.0.8",
"@vue/cli-plugin-eslint": "~5.0.8",
"@vue/cli-service": "~5.0.8",
- "@vue/compiler-sfc": "^3.2.45",
"@vue/eslint-config-prettier": "~7.0.0",
"chalk": "~4.1.2",
"chokidar": "~3.5.3",
"constant-case": "^3.0.4",
"cross-env": "~7.0.3",
"dotenv": "^16.0.3",
- "eslint": "^8.32.0",
+ "eslint": "^8.56.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-vue": "~9.9.0",
"eslint-plugin-yaml": "^0.5.0",
diff --git a/packages/sitecore-jss-angular-schematics/package.json b/packages/sitecore-jss-angular-schematics/package.json
index c80b1613ba..6b1c857dba 100644
--- a/packages/sitecore-jss-angular-schematics/package.json
+++ b/packages/sitecore-jss-angular-schematics/package.json
@@ -1,16 +1,16 @@
{
"name": "@sitecore-jss/sitecore-jss-angular-schematics",
- "version": "22.2.0-canary.77",
+ "version": "22.3.1-canary.6",
"description": "Scaffolding schematics for Sitecore JSS Angular apps",
"scripts": {
"build": "tsc -p tsconfig.json",
"test-package": "jasmine src/**/*_spec.js",
"test": "npm run build && npm run test-package",
"coverage": "nyc npm run test-package",
- "generate-docs": "npx typedoc --plugin typedoc-plugin-markdown --readme none --out ../../ref-docs/sitecore-jss-angular-schematics src/jss-component/index.ts --githubPages false"
+ "generate-docs": "npx typedoc --plugin typedoc-plugin-markdown --outputFileStrategy Members --parametersFormat table --readme none --out ../../ref-docs/sitecore-jss-angular-schematics src/jss-component/index.ts --githubPages false"
},
"engines": {
- "node": ">=20"
+ "node": ">=22"
},
"author": {
"name": "Sitecore Corporation",
@@ -30,10 +30,10 @@
},
"devDependencies": {
"@types/jasmine": "^2.8.0",
- "@types/node": "^20.14.2",
+ "@types/node": "^22.9.0",
"jasmine": "^2.99.0",
"nyc": "^15.1.0",
- "typescript": "~5.2.2"
+ "typescript": "~5.4.0"
},
"gitHead": "2f4820efddf4454eeee58ed1b2cc251969efdf5b"
}
diff --git a/packages/sitecore-jss-angular/karma.conf.js b/packages/sitecore-jss-angular/karma.conf.js
index e188b1ae78..43ffa6a5c0 100644
--- a/packages/sitecore-jss-angular/karma.conf.js
+++ b/packages/sitecore-jss-angular/karma.conf.js
@@ -17,7 +17,7 @@ module.exports = function(config) {
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
- 'src/**/*.ts': 'coverage'
+ 'src/**/*.ts': 'coverage',
},
// list of files / patterns to load in the browser
// list of files to exclude
@@ -26,7 +26,7 @@ module.exports = function(config) {
reporters: [
{ type: 'cobertura', subdir: './', dir: './coverage', file: 'cobertura-coverage.xml' },
{ type: 'text' },
- ]
+ ],
},
// test results reporter to use
// possible values: 'dots', 'progress'
@@ -50,6 +50,7 @@ module.exports = function(config) {
// Concurrency level
// how many browser should be started simultaneous
concurrency: Infinity,
+ files: ['setup.js'],
};
if (process.env.testEnv && process.env.testEnv === 'ci') {
diff --git a/packages/sitecore-jss-angular/package.json b/packages/sitecore-jss-angular/package.json
index 9622cd44fd..9e84391ed6 100644
--- a/packages/sitecore-jss-angular/package.json
+++ b/packages/sitecore-jss-angular/package.json
@@ -1,6 +1,6 @@
{
"name": "@sitecore-jss/sitecore-jss-angular",
- "version": "22.2.0-canary.77",
+ "version": "22.3.1-canary.6",
"description": "",
"scripts": {
"build": "ng-packagr -p ng-package.json",
@@ -8,10 +8,10 @@
"test": "ng test",
"test:watch": "ng test --watch --browsers Chrome",
"coverage": "ng test --code-coverage",
- "generate-docs": "npx typedoc --plugin typedoc-plugin-markdown --readme none --out ../../ref-docs/sitecore-jss-angular src/public_api.ts --githubPages false"
+ "generate-docs": "npx typedoc --plugin typedoc-plugin-markdown --outputFileStrategy Members --parametersFormat table --readme none --out ../../ref-docs/sitecore-jss-angular src/public_api.ts --githubPages false"
},
"engines": {
- "node": ">=20"
+ "node": ">=22"
},
"author": {
"name": "Sitecore Corporation",
@@ -33,8 +33,9 @@
"@angular/platform-browser": "~17.3.11",
"@angular/platform-browser-dynamic": "~17.3.11",
"@angular/router": "~17.3.11",
+ "@sitecore-cloudsdk/events": "^0.4.1",
"@types/jasmine": "^5.1.4",
- "@types/node": "^20.14.10",
+ "@types/node": "^22.9.0",
"codelyzer": "^6.0.1",
"eslint": "~8.57.0",
"eslint-plugin-deprecation": "^1.5.0",
@@ -50,15 +51,16 @@
"reflect-metadata": "^0.1.13",
"rxjs": "~7.8.1",
"tslib": "^2.3.1",
- "typescript": "~5.2.2",
+ "typescript": "~5.4.0",
"zone.js": "~0.14.7"
},
"peerDependencies": {
+ "@sitecore-cloudsdk/events": "^0.4.1",
"@types/debug": "^4.1.12",
"rxjs": "~7.8.1"
},
"dependencies": {
- "@sitecore-jss/sitecore-jss": "22.2.0-canary.77"
+ "@sitecore-jss/sitecore-jss": "22.3.1-canary.6"
},
"main": "dist/esm2022/sitecore-jss-sitecore-jss-angular.mjs",
"module": "dist/esm2022/sitecore-jss-sitecore-jss-angular.js",
diff --git a/packages/sitecore-jss-angular/setup.js b/packages/sitecore-jss-angular/setup.js
new file mode 100644
index 0000000000..eb93560b24
--- /dev/null
+++ b/packages/sitecore-jss-angular/setup.js
@@ -0,0 +1,9 @@
+๏ปฟ// Following is workaround by defining process globally so that the code that accesses it doesn't fail.
+// Refer to the following open issue for more info:
+// https://github.com/testing-library/dom-testing-library/issues/1180
+
+window.process = {
+ env: {
+ NODE_ENV: 'test',
+ },
+};
diff --git a/packages/sitecore-jss-angular/src/components/field-metadata-marker.component.ts b/packages/sitecore-jss-angular/src/components/field-metadata-marker.component.ts
index 315f9d7640..eac986892e 100644
--- a/packages/sitecore-jss-angular/src/components/field-metadata-marker.component.ts
+++ b/packages/sitecore-jss-angular/src/components/field-metadata-marker.component.ts
@@ -18,11 +18,11 @@ export class FieldMetadataMarkerComponent {
@Input()
metadata?: any;
- get metadataString(): string {
- return this.metadata ? JSON.stringify(this.metadata) : '';
- }
-
@HostBinding('attr.kind')
@Input()
kind: MetadataKind = MetadataKind.Open;
+
+ get metadataString(): string {
+ return this.metadata ? JSON.stringify(this.metadata) : '';
+ }
}
diff --git a/packages/sitecore-jss-angular/src/components/form.component.spec.ts b/packages/sitecore-jss-angular/src/components/form.component.spec.ts
index 6763df21ea..d621336bea 100644
--- a/packages/sitecore-jss-angular/src/components/form.component.spec.ts
+++ b/packages/sitecore-jss-angular/src/components/form.component.spec.ts
@@ -64,7 +64,7 @@ describe('FormComponent', () => {
const mockResponse = {
text: () =>
Promise.resolve(
- '
Form Content
\n' +
+ '\n' +
'\n' +
''
),
@@ -72,10 +72,10 @@ describe('FormComponent', () => {
};
spyOn(window, 'fetch').and.returnValue(Promise.resolve(mockResponse as Response));
spyOn(component, 'executeScriptElements').and.callThrough();
+ spyOn(component, 'subscribeToFormSubmitEvent').and.callThrough();
const createElementSpy = spyOn(document, 'createElement').and.callThrough();
const replaceChildSpy = spyOn(elementRef.nativeElement, 'replaceChild').and.callThrough();
-
fixture.detectChanges();
tick();
@@ -91,11 +91,16 @@ describe('FormComponent', () => {
);
expect(elementRef.nativeElement.innerHTML).toBe(
- 'Form Content
\n' +
+ '\n' +
'\n' +
''
);
expect(component.executeScriptElements).toHaveBeenCalled();
+ expect(component.subscribeToFormSubmitEvent).toHaveBeenCalled();
+
+ const formElement = elementRef.nativeElement.querySelector('form');
+
+ expect(formElement).not.toBeNull();
expect(createElementSpy).toHaveBeenCalledTimes(2);
expect(createElementSpy.calls.allArgs()).toEqual([['script'], ['script']]);
@@ -133,6 +138,7 @@ describe('FormComponent', () => {
};
spyOn(window, 'fetch').and.returnValue(Promise.resolve(mockResponse as Response));
spyOn(component, 'executeScriptElements').and.callThrough();
+ spyOn(component, 'subscribeToFormSubmitEvent').and.callThrough();
const createElementSpy = spyOn(document, 'createElement').and.callThrough();
const replaceChildSpy = spyOn(elementRef.nativeElement, 'replaceChild').and.callThrough();
@@ -156,6 +162,7 @@ describe('FormComponent', () => {
''
);
expect(component.executeScriptElements).toHaveBeenCalled();
+ expect(component.subscribeToFormSubmitEvent).toHaveBeenCalled();
expect(createElementSpy).toHaveBeenCalledTimes(2);
expect(createElementSpy.calls.allArgs()).toEqual([['script'], ['script']]);
@@ -173,6 +180,41 @@ describe('FormComponent', () => {
);
}));
+ it('should log a warning if no form element is found', () => {
+ init();
+
+ const consoleWarnSpy = spyOn(console, 'warn').and.callThrough();
+
+ component.subscribeToFormSubmitEvent();
+
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
+ 'No form element found to subscribe to submit event.'
+ );
+ });
+
+ it('should not trigger the form event if the component is in editing or preview mode', () => {
+ init();
+
+ component.isEditing = true;
+
+ const mockFormElement = document.createElement('form');
+ mockElementRef.nativeElement.appendChild(mockFormElement);
+
+ const consoleSpy = spyOn(console, 'log');
+ const formSpy = jasmine.createSpy('form');
+
+ (window as any).form = formSpy;
+
+ component.subscribeToFormSubmitEvent();
+
+ const validEvent = new CustomEvent('form:engage', {
+ detail: { formId: 'test-form-id', name: 'SUBMITTED' },
+ });
+ mockFormElement.dispatchEvent(validEvent);
+
+ expect(consoleSpy).not.toHaveBeenCalled();
+ });
+
describe('when FormId is not provided', () => {
it('editing mode - should log warning and render error', fakeAsync(() => {
const mockRendering = {
diff --git a/packages/sitecore-jss-angular/src/components/form.component.ts b/packages/sitecore-jss-angular/src/components/form.component.ts
index 29de0eab14..682270dcda 100644
--- a/packages/sitecore-jss-angular/src/components/form.component.ts
+++ b/packages/sitecore-jss-angular/src/components/form.component.ts
@@ -13,6 +13,7 @@ import { EDGE_CONFIG, EdgeConfigToken } from '../services/shared.token';
import { JssStateService } from '../services/jss-state.service';
import { isPlatformBrowser } from '@angular/common';
import { Subscription } from 'rxjs';
+import { form } from '@sitecore-cloudsdk/events/browser';
/**
* Shape of the Form component rendering data.
@@ -125,6 +126,7 @@ export class FormComponent implements OnInit, OnDestroy {
this.elRef.nativeElement.innerHTML = content;
this.executeScriptElements();
+ this.subscribeToFormSubmitEvent();
} catch (error) {
console.warn(
`Form '${this.rendering.params.FormId}' was not able to render with the current rendering data`,
@@ -136,6 +138,33 @@ export class FormComponent implements OnInit, OnDestroy {
}
}
+ /**
+ * Subscribes to the custom "form:engage" event and sends data to CloudSDK.
+ * This listener captures interactions such as form views or submissions
+ */
+ subscribeToFormSubmitEvent() {
+ const formElement = this.elRef.nativeElement.querySelector('form');
+
+ if (formElement) {
+ formElement.addEventListener('form:engage', ((
+ e: CustomEvent<{ formId: string; name: 'VIEWED' | 'SUBMITTED' }>
+ ) => {
+ if (this.isEditing) {
+ return;
+ }
+
+ const { formId, name } = e.detail;
+
+ if (formId && name) {
+ console.log(`Sending form event: ${name} for FormId: ${formId}`);
+ form(formId, name, this.rendering.uid?.replace(/-/g, '') || '');
+ }
+ }) as EventListener);
+ } else {
+ console.warn('No form element found to subscribe to submit event.');
+ }
+ }
+
/**
* When you set the innerHTML property of an element, the browser does not execute any