From a6f678c68a43e88e4ed1f19541718980827d846b Mon Sep 17 00:00:00 2001 From: illiakovalenko Date: Tue, 30 Jul 2024 01:04:46 +0300 Subject: [PATCH 01/11] Refactoring, debug, proxy graphql requests --- .../src/templates/angular-xmcloud/.env | 8 + .../templates/angular-xmcloud/package.json | 10 ++ .../scripts/config/plugins/computed.ts | 43 ++++++ .../angular-xmcloud/scripts/proxy-build.ts | 13 ++ .../src/templates/angular/package.json | 2 +- .../angular/scripts/generate-config.ts | 9 +- .../src/templates/angular/server.bundle.ts | 20 ++- .../src/app/lib/dictionary-service-factory.ts | 2 + .../src/app/lib/layout-service-factory.ts | 2 + .../src/templates/node-xmcloud-proxy/.env | 8 + .../templates/node-xmcloud-proxy/README.md | 41 +++++ .../templates/node-xmcloud-proxy/package.json | 25 +++ .../node-xmcloud-proxy/src/config.ts | 19 +++ .../templates/node-xmcloud-proxy/src/index.ts | 142 ++++++++++++++++++ .../templates/node-xmcloud-proxy/src/types.ts | 58 +++++++ .../node-xmcloud-proxy/tsconfig.json | 22 +++ packages/sitecore-jss/src/constants.ts | 1 + packages/sitecore-jss/src/debug.ts | 1 + 18 files changed, 421 insertions(+), 5 deletions(-) create mode 100644 packages/create-sitecore-jss/src/templates/angular-xmcloud/.env create mode 100644 packages/create-sitecore-jss/src/templates/angular-xmcloud/package.json create mode 100644 packages/create-sitecore-jss/src/templates/angular-xmcloud/scripts/config/plugins/computed.ts create mode 100644 packages/create-sitecore-jss/src/templates/angular-xmcloud/scripts/proxy-build.ts create mode 100644 packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/.env create mode 100644 packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/README.md create mode 100644 packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/package.json create mode 100644 packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/src/config.ts create mode 100644 packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/src/index.ts create mode 100644 packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/src/types.ts create mode 100644 packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/tsconfig.json diff --git a/packages/create-sitecore-jss/src/templates/angular-xmcloud/.env b/packages/create-sitecore-jss/src/templates/angular-xmcloud/.env new file mode 100644 index 0000000000..5abe7c0b90 --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/angular-xmcloud/.env @@ -0,0 +1,8 @@ + +# ========== XMCloud Proxy =========== + +# Your XMCloud Proxy hostname is needed to build the app. +PROXY_HOST=http://localhost:3001 + +# Your XMCloud Proxy server path is needed to build the app. The build output will be copied to the proxy server path. +PROXY_BUILD_PATH=<%- proxyAppDestination %>/dist diff --git a/packages/create-sitecore-jss/src/templates/angular-xmcloud/package.json b/packages/create-sitecore-jss/src/templates/angular-xmcloud/package.json new file mode 100644 index 0000000000..8e9c7e8544 --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/angular-xmcloud/package.json @@ -0,0 +1,10 @@ +{ + "config": { + "sitecoreDistPath": "/dist" + }, + "scripts": { + "postbuild:server": "npm-run-all --serial prepare:build prepare:proxy-build", + "prepare:build": "move-cli ./dist/main.js ./dist/server.bundle.js", + "prepare:proxy-build": "ts-node --project src/tsconfig.webpack-server.json ./scripts/proxy-build.ts" + } +} \ No newline at end of file diff --git a/packages/create-sitecore-jss/src/templates/angular-xmcloud/scripts/config/plugins/computed.ts b/packages/create-sitecore-jss/src/templates/angular-xmcloud/scripts/config/plugins/computed.ts new file mode 100644 index 0000000000..c3e47745e6 --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/angular-xmcloud/scripts/config/plugins/computed.ts @@ -0,0 +1,43 @@ +import { JssConfig } from 'lib/config'; +import { ConfigPlugin } from '..'; +import path from 'path'; +import { constantCase } from 'constant-case'; +import { constants } from '@sitecore-jss/sitecore-jss-angular/cjs'; + +/** + * This plugin will set computed config props. + * The "graphQLEndpoint" is an example of making a _computed_ config setting + * based on other config settings. + */ +class ComputedPlugin implements ConfigPlugin { + // should come after other plugins (but before fallback) + order = 10; + + async exec(config: JssConfig) { + const proxyBuildPath = + process.env[`${constantCase('proxyBuildPath')}`]?.replace(/\/$/, '') || + path.join(__dirname, '..', 'C:/Work/jss/samples/node-xmc-proxy/dist'); + + const proxyHost = process.env[`${constantCase('proxyHost')}`]; + + const production = process.env.JSS_MODE === constants.JSS_MODE.PRODUCTION; + + let graphQLEndpoint; + let computed = `${config.graphQLEndpoint || config.sitecoreApiHost}${ + config.graphQLEndpointPath + }`; + + if (production) { + graphQLEndpoint = `\${typeof window === 'undefined' ? '${computed}' : '${proxyHost}${config.graphQLEndpointPath}'}`; + } else { + graphQLEndpoint = computed; + } + + return Object.assign({}, config, { + proxyBuildPath, + graphQLEndpoint, + }); + } +} + +export const computedPlugin = new ComputedPlugin(); 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 new file mode 100644 index 0000000000..d355e23838 --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/angular-xmcloud/scripts/proxy-build.ts @@ -0,0 +1,13 @@ +import { execSync } from 'child_process'; +import { environment } from '../src/environments/environment'; + +try { + console.log('Moving build output to proxy build path:', environment.proxyBuildPath); + + execSync(`del-cli ${environment.proxyBuildPath} --force`, { stdio: 'inherit' }); + execSync(`move-cli ./dist ${environment.proxyBuildPath}`, { stdio: 'inherit' }); + + console.log('Proxy build prepared successfully!'); +} catch (error) { + console.error('Error preparing proxy build:', error); +} diff --git a/packages/create-sitecore-jss/src/templates/angular/package.json b/packages/create-sitecore-jss/src/templates/angular/package.json index c9effd53b7..109f1087dd 100644 --- a/packages/create-sitecore-jss/src/templates/angular/package.json +++ b/packages/create-sitecore-jss/src/templates/angular/package.json @@ -19,7 +19,7 @@ "e2e": "ng e2e", "jss": "jss", "start:connected": "npm-run-all --serial bootstrap start:angular start:watch-components", - "build": "npm-run-all --serial bootstrap --serial build:client build:server", + "build": "cross-env-shell JSS_MODE=production \"npm-run-all --serial bootstrap --serial build:client build:server\"", "scaffold": "ng generate @sitecore-jss/sitecore-jss-angular-schematics:jss-component --no-manifest", "start:angular": "ng serve -o", "start:watch-components": "ts-node --project src/tsconfig.webpack-server.json scripts/generate-component-factory.ts --watch", diff --git a/packages/create-sitecore-jss/src/templates/angular/scripts/generate-config.ts b/packages/create-sitecore-jss/src/templates/angular/scripts/generate-config.ts index 12ab4e5d0c..ac43bcfeba 100644 --- a/packages/create-sitecore-jss/src/templates/angular/scripts/generate-config.ts +++ b/packages/create-sitecore-jss/src/templates/angular/scripts/generate-config.ts @@ -67,9 +67,12 @@ export function writeConfig(config: JssConfig, outputPath?: string) { // Set base configuration values, allowing override with environment variables Object.keys(config).forEach((prop) => { - configText += `config.${prop} = process.env.${constantCase(prop)} || "${config[prop] - ?.toString() - .trim()}";\n`; + let value = config[prop]?.toString().trim(); + const usesTemplateLiteral = /\$\{.*?\}/.test(config[prop].toString()); + + value = usesTemplateLiteral ? `\`${value}\`` : `"${value}"`; + + configText += `config.${prop} = process.env.${constantCase(prop)} || ${value};\n`; }); configText += `module.exports.environment = config;`; diff --git a/packages/create-sitecore-jss/src/templates/angular/server.bundle.ts b/packages/create-sitecore-jss/src/templates/angular/server.bundle.ts index 1f8275a886..5eefab4fb1 100644 --- a/packages/create-sitecore-jss/src/templates/angular/server.bundle.ts +++ b/packages/create-sitecore-jss/src/templates/angular/server.bundle.ts @@ -6,6 +6,9 @@ import 'zone.js'; import { JssRouteBuilderService } from './src/app/routing/jss-route-builder.service'; import { environment } from './src/environments/environment'; import { AppServerModule, renderModule } from './src/main.server'; +import { clientFactory } from './src/app/lib/graphql-client-factory'; +import { dictionaryServiceFactory } from './src/app/lib/dictionary-service-factory'; +import { layoutServiceFactory } from './src/app/lib/layout-service-factory'; export * from './src/main.server'; @@ -99,5 +102,20 @@ function parseRouteUrl(url: string) { const apiKey = environment.sitecoreApiKey; const siteName = environment.sitecoreSiteName; +const defaultLanguage = environment.defaultLanguage; +const graphQLEndpointPath = environment.graphQLEndpointPath; +const graphQLEndpoint = environment.graphQLEndpoint; -export { renderView, parseRouteUrl, setUpDefaultAgents, apiKey, siteName }; +export { + renderView, + parseRouteUrl, + setUpDefaultAgents, + apiKey, + siteName, + clientFactory, + dictionaryServiceFactory, + layoutServiceFactory, + defaultLanguage, + graphQLEndpointPath, + graphQLEndpoint, +}; diff --git a/packages/create-sitecore-jss/src/templates/angular/src/app/lib/dictionary-service-factory.ts b/packages/create-sitecore-jss/src/templates/angular/src/app/lib/dictionary-service-factory.ts index bacf34180a..5123ad8060 100644 --- a/packages/create-sitecore-jss/src/templates/angular/src/app/lib/dictionary-service-factory.ts +++ b/packages/create-sitecore-jss/src/templates/angular/src/app/lib/dictionary-service-factory.ts @@ -1,8 +1,10 @@ import { DictionaryService, GraphQLDictionaryService, + <% if (!locals.xmcloud) { -%> RestDictionaryService, constants, + <% } -%> } from '@sitecore-jss/sitecore-jss-angular'; import { environment as env } from '../../environments/environment'; import { clientFactory } from './graphql-client-factory'; diff --git a/packages/create-sitecore-jss/src/templates/angular/src/app/lib/layout-service-factory.ts b/packages/create-sitecore-jss/src/templates/angular/src/app/lib/layout-service-factory.ts index 0e958f3060..4b2eac1190 100644 --- a/packages/create-sitecore-jss/src/templates/angular/src/app/lib/layout-service-factory.ts +++ b/packages/create-sitecore-jss/src/templates/angular/src/app/lib/layout-service-factory.ts @@ -1,8 +1,10 @@ import { LayoutService, GraphQLLayoutService, + <% if (!locals.xmcloud) { -%> RestLayoutService, constants, + <% } -%> } from '@sitecore-jss/sitecore-jss-angular'; import { environment } from '../../environments/environment'; import { clientFactory } from './graphql-client-factory'; diff --git a/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/.env b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/.env new file mode 100644 index 0000000000..37d1f32625 --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/.env @@ -0,0 +1,8 @@ +# Your proxy port (default: 3001) +PROXY_PORT= + +# Your proxy server bundle path +PROXY_BUNDLE_PATH= + +# Set the DEBUG environment variable to 'sitecore-jss:*,proxy*,http-proxy-middleware*' to see all logs: +#DEBUG=sitecore-jss:*,proxy*,http-proxy-middleware* 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 new file mode 100644 index 0000000000..0abd150da8 --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/README.md @@ -0,0 +1,41 @@ +# node/express scaffolding for XMCloud using Sitecore Experience Edge + +> Sitecore JSS Proxy for XMCloud is considered highly experimental. + + +[Documentation (Experience Platform)]() + + +This is a sample setup showing one of how you can configure XMCloud rendering server on top of node.js and Express using Experience Edge. + +## Pre-requisites + +1. Angular sample supports Experience Edge out of the box. + +1. Build your SPA app bundle with `jss build`. + + > You can use JSS sample apps which support XMCloud to operate with this project. + +## Setup + +Open `config.js` and specify your application settings. + +### Environment Variables + +The following environment variables can be set to configure the SSR 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 app's `server.bundle.js` file. | +| `PROXY_PORT` | Optional. Port which will be used when start sample. Default can be seen in [config.js](./config.js). | + +## Build & run + +1. Run `npm install` + +1. Run `npm run start` + +You should be able to see the following message: +`server listening on port 3000!`. 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 new file mode 100644 index 0000000000..019ea4dc91 --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/package.json @@ -0,0 +1,25 @@ +{ + "name": "node-xmcloud-proxy-sample", + "version": "22.1.0-canary", + "description": "Node server-side-rendering proxy sample for running XMCloud JSS apps under Node hosting", + "author": { + "name": "Sitecore Corporation", + "url": "https://jss.sitecore.com" + }, + "scripts": { + "start": "ts-node ./src/index.ts" + }, + "dependencies": { + "compression": "^1.7.4", + "express": "^4.18.2", + "dotenv": "^16.0.3", + "http-proxy-middleware": "^3.0.0" + }, + "devDependencies": { + "@sitecore-jss/sitecore-jss": "~22.1.0-canary", + "@types/compression": "^1.7.2", + "@types/express": "^4.17.17", + "ts-node": "^10.9.1", + "typescript": "~4.9.5" + } +} 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 new file mode 100644 index 0000000000..005276ee41 --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/src/config.ts @@ -0,0 +1,19 @@ +import { Config, ServerBundle } from './types'; + +/** + * The server.bundle.js file from your pre-built SPA app. + */ +const bundlePath = process.env.PROXY_BUNDLE_PATH || `../dist/server.bundle`; + +const serverBundle: ServerBundle = require(bundlePath); + +export const config: Config = { + /** + * The require'd server.bundle.js file from your pre-built SPA app. + */ + serverBundle, + /** + * Port which will be used when start the proxy + */ + port: process.env.PROXY_PORT || 3001, +}; 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 new file mode 100644 index 0000000000..923d56fbdb --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/src/index.ts @@ -0,0 +1,142 @@ +import 'dotenv/config'; +import express, { Response } from 'express'; +import compression from 'compression'; +import { createProxyMiddleware } from 'http-proxy-middleware'; +import { debug } from '@sitecore-jss/sitecore-jss'; +import { config } from './config'; + +const server = express(); + +const { + renderView, + parseRouteUrl, + dictionaryServiceFactory, + layoutServiceFactory, +} = config.serverBundle; + +if (!renderView || !parseRouteUrl) { + throw new Error( + 'ERROR: The serverBundle should export `renderView` and `parseRouteUrl`, please check your server bundle.' + ); +} + +const layoutService = layoutServiceFactory.create(); + +const dictionaryService = dictionaryServiceFactory.create(); + +/** + * Parse requested url in order to detect current route and language + * @param {string} reqRoute requested route + */ +const getRouteParams = (reqRoute: string) => { + const routeParams = parseRouteUrl(reqRoute); + let lang; + let route; + + if (routeParams) { + route = routeParams.sitecoreRoute || '/'; + + if (!route.startsWith('/')) { + route = `/${route}`; + } + + lang = routeParams.lang || ''; + } + + return { route, lang }; +}; + +/** + * Handle unexpected error + * @param {Response} res server response + * @param {Error} err error + */ +const handleError = (res: Response, err: unknown) => { + debug.proxy('response error %o', err); + + res.status(500).send('Internal Server Error'); +}; + +// enable gzip compression for appropriate file types +server.use(compression()); + +// turn off x-powered-by http header +server.settings['x-powered-by'] = false; + +// Serve static app assets from local /dist folder +server.use( + '/dist', + express.static('dist', { + fallthrough: false, // force 404 for unknown assets under /dist + }) +); + +/** + * Proxy GraphQL requests to the Sitecore GraphQL endpoint + */ +server.use( + config.serverBundle.graphQLEndpointPath, + createProxyMiddleware({ + target: config.serverBundle.graphQLEndpoint, + changeOrigin: true, + }) +); + +server.use(async (req, res) => { + debug.proxy(`performing SSR for ${req.originalUrl}`); + + try { + const { route, lang } = getRouteParams(req.originalUrl); + + if (!route) { + debug.proxy('no route detected, returning 404'); + + res.sendStatus(404); + + return; + } + + // 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( + route, + lang || config.serverBundle.defaultLanguage + ); + + const viewBag = { dictionary: {} }; + + viewBag.dictionary = await dictionaryService.fetchDictionaryData( + layoutData.sitecore.context.language || config.serverBundle.defaultLanguage + ); + + renderView( + (err, result) => { + if (err) { + handleError(res, err); + return; + } + + if (!result) { + debug.proxy('no result returned from renderView, returning 204'); + + res.status(204).send(); + return; + } + + const statusCode = layoutData.sitecore.route ? 200 : 404; + + debug.proxy('sending response with status %s', statusCode); + + res.status(statusCode).send(result.html); + }, + route, + layoutData, + viewBag + ); + } catch (err) { + handleError(res, err); + } +}); + +server.listen(config.port, () => { + console.log(`server listening on port ${config.port}!`); +}); 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 new file mode 100644 index 0000000000..585d24b559 --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/src/types.ts @@ -0,0 +1,58 @@ +import { GraphQLRequestClientFactory } from '@sitecore-jss/sitecore-jss'; +import { DictionaryService } from '@sitecore-jss/sitecore-jss/i18n'; +import { LayoutService, LayoutServiceData } from '@sitecore-jss/sitecore-jss/layout'; + +interface ServerResponse { + /** + * The rendered HTML to return to the client + */ + html: string; + /** + * Set the HTTP status code. If not set, the status code returned from Layout Service is returned. + */ + status?: number; + /** + * Sets a redirect URL, causing the reply to send a HTTP redirect instead of the HTML content. + * Note: when using this you must set the status code to 301 or 302. + */ + redirect?: string; +} + +declare type AppRenderer = ( + callback: (error: Error | null, result: ServerResponse | null) => void, + path: string, + /** + * Data returned by Layout Service. If the route does not exist, null. + */ + layoutData: LayoutServiceData, + viewBag: { + [key: string]: unknown; + dictionary: { [key: string]: string }; + } +) => void; + +declare type RouteUrlParser = ( + url: string +) => { + sitecoreRoute?: string; + lang?: string; + qsParams?: string; +}; + +export interface ServerBundle { + renderView: AppRenderer; + parseRouteUrl: RouteUrlParser; + clientFactory: GraphQLRequestClientFactory; + siteName: string; + defaultLanguage: string; + layoutServiceFactory: { create: () => LayoutService }; + dictionaryServiceFactory: { create: () => DictionaryService }; + graphQLEndpointPath: string; + graphQLEndpoint: string; +} + +export interface Config { + [key: string]: unknown; + port: string | number; + serverBundle: ServerBundle; +} diff --git a/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/tsconfig.json b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/tsconfig.json new file mode 100644 index 0000000000..aa0f5bcc2c --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "newLine": "LF", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "noFallthroughCasesInSwitch": true, + "rootDir": ".", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitAny": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "strictFunctionTypes": false, + "downlevelIteration": true, + "moduleResolution": "node", + "isolatedModules": true, + "allowSyntheticDefaultImports": true + }, + "exclude": ["node_modules", "dist"] +} diff --git a/packages/sitecore-jss/src/constants.ts b/packages/sitecore-jss/src/constants.ts index 7a974f39c7..2f000bed81 100644 --- a/packages/sitecore-jss/src/constants.ts +++ b/packages/sitecore-jss/src/constants.ts @@ -14,6 +14,7 @@ export const FETCH_WITH = { export const JSS_MODE = { CONNECTED: 'connected', DISCONNECTED: 'disconnected', + PRODUCTION: 'production', }; export const siteNameError = 'The siteName cannot be empty'; diff --git a/packages/sitecore-jss/src/debug.ts b/packages/sitecore-jss/src/debug.ts index 83e87a34b9..692c342be8 100644 --- a/packages/sitecore-jss/src/debug.ts +++ b/packages/sitecore-jss/src/debug.ts @@ -39,4 +39,5 @@ export default { redirects: debug(`${rootNamespace}:redirects`), personalize: debug(`${rootNamespace}:personalize`), errorpages: debug(`${rootNamespace}:errorpages`), + proxy: debug(`${rootNamespace}:proxy`), }; From 68cda9dd64e239da3f91aa8b2b1805c2e210e0fe Mon Sep 17 00:00:00 2001 From: illiakovalenko Date: Tue, 30 Jul 2024 01:47:14 +0300 Subject: [PATCH 02/11] Removed extra import --- .../angular-xmcloud/scripts/config/plugins/computed.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/create-sitecore-jss/src/templates/angular-xmcloud/scripts/config/plugins/computed.ts b/packages/create-sitecore-jss/src/templates/angular-xmcloud/scripts/config/plugins/computed.ts index c3e47745e6..efa5f42fcf 100644 --- a/packages/create-sitecore-jss/src/templates/angular-xmcloud/scripts/config/plugins/computed.ts +++ b/packages/create-sitecore-jss/src/templates/angular-xmcloud/scripts/config/plugins/computed.ts @@ -1,6 +1,5 @@ import { JssConfig } from 'lib/config'; import { ConfigPlugin } from '..'; -import path from 'path'; import { constantCase } from 'constant-case'; import { constants } from '@sitecore-jss/sitecore-jss-angular/cjs'; @@ -14,9 +13,7 @@ class ComputedPlugin implements ConfigPlugin { order = 10; async exec(config: JssConfig) { - const proxyBuildPath = - process.env[`${constantCase('proxyBuildPath')}`]?.replace(/\/$/, '') || - path.join(__dirname, '..', 'C:/Work/jss/samples/node-xmc-proxy/dist'); + const proxyBuildPath = process.env[`${constantCase('proxyBuildPath')}`]?.replace(/\/$/, ''); const proxyHost = process.env[`${constantCase('proxyHost')}`]; From 7136e847d4602c88eeea45359e0e1ede598f965d Mon Sep 17 00:00:00 2001 From: illiakovalenko Date: Tue, 30 Jul 2024 01:59:14 +0300 Subject: [PATCH 03/11] Updated README.md --- .../templates/node-xmcloud-proxy/README.md | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) 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 0abd150da8..48b34a11e3 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 @@ -1,22 +1,16 @@ -# node/express scaffolding for XMCloud using Sitecore Experience Edge +# Node XMCloud Proxy -> Sitecore JSS Proxy for XMCloud is considered highly experimental. - +> Sitecore JSS Proxy for XMCloud is considered experimental. -[Documentation (Experience Platform)]() +[Documentation]() - -This is a sample setup showing one of how you can configure XMCloud rendering server on top of node.js and Express using Experience Edge. +This is a sample setup showing one of how you can configure XMCloud rendering server. ## Pre-requisites -1. Angular sample supports Experience Edge out of the box. - -1. Build your SPA app bundle with `jss build`. +1. SPA sample supports XMCloud out of the box. - > You can use JSS sample apps which support XMCloud to operate with this project. +1. Build your SPA app bundle with `jss build`. The build output should be placed in the `dist` folder. ## Setup @@ -24,12 +18,13 @@ Open `config.js` and specify your application settings. ### Environment Variables -The following environment variables can be set to configure the SSR 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). +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 app's `server.bundle.js` file. | -| `PROXY_PORT` | Optional. Port which will be used when start sample. Default can be seen in [config.js](./config.js). | +| `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. | ## Build & run @@ -38,4 +33,4 @@ The following environment variables can be set to configure the SSR sample inste 1. Run `npm run start` You should be able to see the following message: -`server listening on port 3000!`. +`server listening on port 3001!`. From 188a6740f9ff47a88273496c1ba8b2e673de519d Mon Sep 17 00:00:00 2001 From: illiakovalenko Date: Tue, 30 Jul 2024 02:07:03 +0300 Subject: [PATCH 04/11] Updated comment --- .../angular-xmcloud/scripts/config/plugins/computed.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/create-sitecore-jss/src/templates/angular-xmcloud/scripts/config/plugins/computed.ts b/packages/create-sitecore-jss/src/templates/angular-xmcloud/scripts/config/plugins/computed.ts index efa5f42fcf..4450aba6e1 100644 --- a/packages/create-sitecore-jss/src/templates/angular-xmcloud/scripts/config/plugins/computed.ts +++ b/packages/create-sitecore-jss/src/templates/angular-xmcloud/scripts/config/plugins/computed.ts @@ -14,7 +14,6 @@ class ComputedPlugin implements ConfigPlugin { async exec(config: JssConfig) { const proxyBuildPath = process.env[`${constantCase('proxyBuildPath')}`]?.replace(/\/$/, ''); - const proxyHost = process.env[`${constantCase('proxyHost')}`]; const production = process.env.JSS_MODE === constants.JSS_MODE.PRODUCTION; @@ -25,8 +24,12 @@ class ComputedPlugin implements ConfigPlugin { }`; if (production) { + // When build for production (using `jss build`) + // The client side requests will be proxied through the node-xmcloud-proxy and passed to the Sitecore instance + // The server side requests will be executed against the Sitecore instance directly graphQLEndpoint = `\${typeof window === 'undefined' ? '${computed}' : '${proxyHost}${config.graphQLEndpointPath}'}`; } else { + // When working in development mode (`jss start:connected`) graphQLEndpoint = computed; } From 0e4c68b4634a71fca132fa7d1e038b87de4a689e Mon Sep 17 00:00:00 2001 From: illiakovalenko Date: Tue, 30 Jul 2024 02:08:57 +0300 Subject: [PATCH 05/11] Updated env variables description --- packages/create-sitecore-jss/src/templates/angular-xmcloud/.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/create-sitecore-jss/src/templates/angular-xmcloud/.env b/packages/create-sitecore-jss/src/templates/angular-xmcloud/.env index 5abe7c0b90..1c394b55d3 100644 --- a/packages/create-sitecore-jss/src/templates/angular-xmcloud/.env +++ b/packages/create-sitecore-jss/src/templates/angular-xmcloud/.env @@ -1,7 +1,7 @@ # ========== XMCloud Proxy =========== -# Your XMCloud Proxy hostname is needed to build the app. +# Your XMCloud Proxy hostname is needed to build the app and execute the client-side requests against the proxy server. PROXY_HOST=http://localhost:3001 # Your XMCloud Proxy server path is needed to build the app. The build output will be copied to the proxy server path. From 58528f2d9034d1ef7f7f3d87beabf79b6577d233 Mon Sep 17 00:00:00 2001 From: illiakovalenko Date: Tue, 30 Jul 2024 02:09:55 +0300 Subject: [PATCH 06/11] Updated comment --- .../src/templates/angular-xmcloud/scripts/proxy-build.ts | 2 ++ 1 file changed, 2 insertions(+) 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 d355e23838..13b3343895 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,6 +1,8 @@ import { execSync } from 'child_process'; import { environment } from '../src/environments/environment'; +// Executed at the end of the build process (jss build) to move the build output to the proxy build path + try { console.log('Moving build output to proxy build path:', environment.proxyBuildPath); From 5e5fc7b8c20b6511ff6c5ebc23e33f6181c2e9ca Mon Sep 17 00:00:00 2001 From: illiakovalenko Date: Tue, 30 Jul 2024 02:11:31 +0300 Subject: [PATCH 07/11] Updated title --- .../src/templates/node-xmcloud-proxy/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 165187a83b..fdbb3a761c 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,7 +1,7 @@ { "name": "node-xmcloud-sample", "version": "22.1.0-canary", - "description": "Node server-side-rendering proxy sample for running XMCloud JSS apps under Node hosting", + "description": "Node XMCloud Proxy sample for running XMCloud JSS SPA apps", "author": { "name": "Sitecore Corporation", "url": "https://jss.sitecore.com" From 39005f5c08f3cfcbdda048627aa0aebc81c5d6ad Mon Sep 17 00:00:00 2001 From: illiakovalenko Date: Tue, 30 Jul 2024 02:25:13 +0300 Subject: [PATCH 08/11] Updated CHANGELOG, Upgrade guide --- CHANGELOG.md | 1 + docs/upgrades/unreleased.md | 249 +++++------------------------------- 2 files changed, 32 insertions(+), 218 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03d5ac0faf..777038b7e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ Our versioning strategy is as follows: ### 🎉 New Features & Improvements +* `[create-sitecore-jss]` Introduced "node-xmcloud-proxy" addon ([#1863](https://github.com/Sitecore/jss/pull/1863)) * `[create-sitecore-jss]` `[template/angular]` `[template/angular-sxp]` `[template/angular-xmcloud]` Introduced "angular-sxp", "angular-xmcloud" addons ([#1838](https://github.com/Sitecore/jss/pull/1838))([#1845](https://github.com/Sitecore/jss/pull/1845)): * The Angular app should now be initialized by providing both templates (or using CLI prompts): * SXP-based: 'angular,angular-sxp' diff --git a/docs/upgrades/unreleased.md b/docs/upgrades/unreleased.md index 0b5d373c88..fed67177cc 100644 --- a/docs/upgrades/unreleased.md +++ b/docs/upgrades/unreleased.md @@ -1,238 +1,51 @@ ## Unreleased -* If you are importing any _editing_ utils from `@sitecore-jss/sitecore-jss/utils` in your code, please update the import path to `@sitecore-jss/sitecore-jss/editing`. For now these exports are still available in the old path and marked as deprecated. They will be removed in the next major version release. Specifically for the following utils: - * ExperienceEditor - * HorizonEditor - * isEditorActive - * resetEditorChromes - * handleEditorAnchors - * Metadata - * DefaultEditFrameButton - * DefaultEditFrameButtons - * DefaultEditFrameButtonIds - * EditFrameDataSource - * ChromeCommand - * FieldEditButton - * WebEditButton - * EditButtonTypes - * mapButtonToCommand +# Angular - XMCloud -# react +If you plan to use the Angular SDK with XMCloud, you will need to perform next steps: -* With the simplification of Editing Support work we have added the following breaking changes to the `sitecore-jss-react` package. Please make the necessary updates. - - `ComponentConsumerProps` is removed. You might need to reuse _WithSitecoreContextProps_ type. +* On top of existing Angular sample, apply changes from "angular-xmcloud" add-on. +* Updated package.json "build" script to set "JSS_MODE=production": -### headless-ssr-experience-edge -* Replace `scripts/generate-config.js` if you have not modified it. Otherwise: - * Add a `trim()` call to `config[prop]` and replace comma before a newline (`,`) with semicolon (`;`) in configText prop assignment so it would look like this: - - ```ts - configText += `config.${prop} = process.env.REACT_APP_${constantCase(prop)} || "${ - config[prop]?.trim() - }";\n`; - ``` - -# angular - -* Update Angular and core dependencies to ~17.3.1, related dependencies - -* Update Typescript to ~5.2.2 - -* Replace `scripts/generate-config.ts` if you have not modified it. Otherwise: - * Add a `trim()` call to `config[prop]` (use toString() to avoid type conflicts) and replace commas before a newline (`,`) with semicolon (`;`) in configText prop assignments so it would look like this: - - ```ts - configText += `config.${prop} = process.env.${constantCase(prop)} || "${config[prop]?.toString().trim()}";\n`; - ``` - -* Update import in _src/templates/angular/server.bundle.ts_ - Use _'zone.js'_ instead of _'zone.js/dist/zone-node'_ - - ```ts - import 'zone.js'; - ``` -* Update import in _src/templates/angular/src/polyfills.ts_ - Use _'zone.js'_ instead of _'zone.js/dist/zone-node'_ - - ```ts - import 'zone.js'; - ``` - -# vue - -* Replace `scripts/generate-config.js` if you have not modified it. Otherwise: - * Add a `trim()` call to `config[prop]` and replace commas before a newline (`,`) with semicolon (`;`) in configText prop assignments so it would look like this: - - ```ts - configText += `config.${prop} = process.env.VUE_APP_${constantCase(prop)} || "${ - config[prop]?.trim() - }";\n`; - ``` - -# nextjs - -* Replace `scripts/generate-config.ts` if you have not modified it. Otherwise: - * Add a `trim()` call to `config[prop]` and replace comma before a newline (`,`) with semicolon (`;`) in configText prop assignment so it would look like this: - - ```ts - configText += `config.${prop} = process.env.${constantCase(prop)} || '${config[prop]?.trim()}';\n`; - ``` - -* Remove cors header for API endpoints from _lib/next-config/plugins/cors-header_ plugin since cors is handled by API handlers / middlewares: - - ```ts - { - source: '/api/:path*', - headers: [ - { - key: 'Access-Control-Allow-Origin', - value: config.sitecoreApiHost.replace(/\/$/, ''), - }, - ], - }, - ``` - -* Update _pages/api/editing/render.ts_ API handler initialization signature, since _resolvePageUrl_ function now accepts an object and _serverUrl_ now is optional, it's ommited when Pages Metadata Edit Mode is used. Update the handler initialization as follows: - - ```ts - const handler = new EditingRenderMiddleware({ - resolvePageUrl: ({ serverUrl, itemPath }) => `${serverUrl}${itemPath}`, - }).getHandler(); - ``` - -* The implementation of 'EditingComponentPlaceholder' has been removed. Its purpose to avoid refreshing the entire page during component editing in Pages had never been fully utilized. The references to it and to `RenderingType` enum in `[[...path]].tsx` of the nextjs app (and in any custom code) should be removed: - - ```ts - import Layout from 'src/Layout'; - import { RenderingType, EditingComponentPlaceholder } from '@sitecore-jss/sitecore-jss-nextjs'; - ... - const isComponentRendering = - layoutData.sitecore.context.renderingType === RenderingType.Component; - ... - {isComponentRendering ? ( - - ) : ( - - )} - ... - ``` - -* 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` : + ```shell + "cross-env-shell JSS_MODE=production \"npm-run-all --serial bootstrap --serial build:client build:server\"" ``` - new GraphQLDictionaryService({ - siteName, - clientFactory, - ..... - useSiteQuery: true, - }) -* We have introduced a new configuration option, `pagesEditMode`, in the `\src\pages\api\editing\config.ts` file to support the new editing metadata architecture for Pages (XMCloud). This option allows you to specify the editing mode used by Pages. It is set to `metadata` by default. However, if you are not ready to use a new integration and continue using the existing architecture, you can explicitly set the `pagesEditMode` to `chromes`. +* Update "scripts/generate-config.ts" to handle tempalte literals in the output "environment" file: ```ts - import { EditMode } from '@sitecore-jss/sitecore-jss-nextjs'; - - const handler = new EditingConfigMiddleware({ - ... - pagesEditMode: EditMode.Chromes, - }).getHandler(); - ``` - -* Introduce a new _lib/graphql-editing-service.ts_ file to initialize a _graphQLEditingService_ to support a new Editing Metadata Mode. Can be done by adding this file from the latest version introduced in _nextjs-xmcloud_ base template. + Object.keys(config).forEach((prop) => { + let value = config[prop]?.toString().trim(); + const usesTemplateLiteral = /\$\{.*?\}/.test(config[prop].toString()); -* Update _lib/page-props-factory/plugins/preview-mode_ plugin to support a new Editing Metadata Mode. Can be done by replacing this file with the latest version introduced in _nextjs-xmcloud_ base template. + value = usesTemplateLiteral ? `\`${value}\`` : `"${value}"`; -* To support editing for fields in Pages, the new editing metadata architecture relies on the new metadata property 'field.metadata' (instead of on 'field.editable', which won't be used in this scenario). If you are using the new editing arhitecture in Pages (EditMode.Metadata) and have custom field component that manipulates or relies on 'field.editable' in some way, it may need to be reworked. Experience Editor still relies on 'field.editable', so it needs to be supported. See example below from SXA's Banner component: - - ```ts - import { useSitecoreContext, EditMode } from '@sitecore-jss/sitecore-jss-nextjs'; - ... - export const Banner = (props: ImageProps): JSX.Element => { - const { sitecoreContext } = useSitecoreContext(); - const isMetadataMode = sitecoreContext?.editMode === EditMode.Metadata; - ... - const modifyImageProps = !isMetadataMode - ? { - ...props.fields.Image, - editable: props?.fields?.Image?.editable - ?.replace(`width="${props?.fields?.Image?.value?.width}"`, 'width="100%"') - .replace(`height="${props?.fields?.Image?.value?.height}"`, 'height="100%"'), - } - : { ...props.fields.Image }; - ... - } - ... + configText += `config.${prop} = process.env.${constantCase(prop)} || ${value};\n`; + }); ``` -* To enable AB testing and component level personalization support in JSS: - * Ensure `componentVariantIds` are passed to `personalizeLayout` function call in `/lib/page-props-factory/plugins/personalize.ts`: +* Updated "server.bundle.ts" to additionally expose new properties: ```ts - // Get variant(s) for personalization (from path) - const personalizeData = getPersonalizedRewriteData(path); - - // Modify layoutData to use specific variant(s) instead of default - // This will also set the variantId on the Sitecore context so that it is accessible here - personalizeLayout( - props.layoutData, - personalizeData.variantId, - personalizeData.componentVariantIds - ); - ``` - - * For preview mode, prepare and pass `componentVariantIds` into `personalizeLayout` in `/lib/page-props-factory/plugins/preview-mode.ts`: + import { environment } from './src/environments/environment'; + import { clientFactory } from './src/app/lib/graphql-client-factory'; + import { dictionaryServiceFactory } from './src/app/lib/dictionary-service-factory'; + import { layoutServiceFactory } from './src/app/lib/layout-service-factory'; - ```ts - import { - SiteInfo, - personalizeLayout, - getGroomedVariantIds, - } from '@sitecore-jss/sitecore-jss-nextjs'; - ``` - ```ts - props.headLinks = []; - const personalizeData = getGroomedVariantIds(variantIds); - personalizeLayout( - props.layoutData, - personalizeData.variantId, - personalizeData.componentVariantIds - ); - ``` - -* Update _lib/middleware/plugins/personalize.ts_ `PersonalizeMiddleware` constructor signature, moving `scope` from `cdpConfig` to the root. For now this option will continue working but is marked as deprecated. It will be removed in the next major version release. + ... + const defaultLanguage = environment.defaultLanguage; + const graphQLEndpointPath = environment.graphQLEndpointPath; + const graphQLEndpoint = environment.graphQLEndpoint; - ```ts - this.personalizeMiddleware = new PersonalizeMiddleware({ + export { ... - cdpConfig: { - ... - scope: process.env.NEXT_PUBLIC_PERSONALIZE_SCOPE, // REMOVE - }, - scope: process.env.NEXT_PUBLIC_PERSONALIZE_SCOPE, // ADD - }); + clientFactory, + dictionaryServiceFactory, + layoutServiceFactory, + defaultLanguage, + graphQLEndpointPath, + graphQLEndpoint, + }; ``` +* GraphQL FETCH_WITH method is required to be used, REST is not supported. Updated FETCH_WITH environment variable if needed. From 1b4ccd03ed00c83293987b610eefd248bd97d715 Mon Sep 17 00:00:00 2001 From: illiakovalenko Date: Tue, 30 Jul 2024 02:40:40 +0300 Subject: [PATCH 09/11] Updated comment --- .../src/templates/node-xmcloud-proxy/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 923d56fbdb..6ad53355db 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 @@ -72,7 +72,7 @@ server.use( ); /** - * Proxy GraphQL requests to the Sitecore GraphQL endpoint + * Proxy browser GraphQL requests to the Sitecore GraphQL endpoint */ server.use( config.serverBundle.graphQLEndpointPath, From 3559281763956cb19d64129a798072ce72eb5cd7 Mon Sep 17 00:00:00 2001 From: illiakovalenko Date: Tue, 30 Jul 2024 18:37:46 +0300 Subject: [PATCH 10/11] Updated .env comment --- .../create-sitecore-jss/src/templates/node-xmcloud-proxy/.env | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 37d1f32625..519a159f39 100644 --- a/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/.env +++ b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/.env @@ -4,5 +4,5 @@ PROXY_PORT= # Your proxy server bundle path PROXY_BUNDLE_PATH= -# Set the DEBUG environment variable to 'sitecore-jss:*,proxy*,http-proxy-middleware*' to see all logs: -#DEBUG=sitecore-jss:*,proxy*,http-proxy-middleware* +# Set the DEBUG environment variable to 'sitecore-jss:*,sitecore-jss:proxy,http-proxy-middleware*' to see all logs: +#DEBUG=sitecore-jss:*,http-proxy-middleware* From ef09ad7907982962245412e240e846a801cc0bd1 Mon Sep 17 00:00:00 2001 From: illiakovalenko Date: Wed, 31 Jul 2024 12:02:27 +0300 Subject: [PATCH 11/11] Resolved PR comments --- .../src/templates/angular-xmcloud/.env | 6 +-- .../scripts/config/plugins/computed.ts | 43 ------------------ .../scripts/config/plugins/xmcloud.ts | 23 ++++++++++ .../angular-xmcloud/src/app/lib/config.ts | 15 +++++++ .../src/app/lib/graphql-client-factory.ts | 44 +++++++++++++++++++ .../src/templates/angular/angular.json | 7 +-- .../src/templates/angular/package.json | 4 +- .../angular/scripts/generate-config.ts | 9 ++-- .../app/routing/layout/layout.component.ts | 3 +- .../templates/node-xmcloud-proxy/README.md | 8 ++-- .../templates/node-xmcloud-proxy/package.json | 2 +- .../templates/node-xmcloud-proxy/src/index.ts | 23 +++++++++- .../templates/node-xmcloud-proxy/src/types.ts | 1 + packages/sitecore-jss/src/constants.ts | 1 - 14 files changed, 119 insertions(+), 70 deletions(-) delete mode 100644 packages/create-sitecore-jss/src/templates/angular-xmcloud/scripts/config/plugins/computed.ts create mode 100644 packages/create-sitecore-jss/src/templates/angular-xmcloud/scripts/config/plugins/xmcloud.ts create mode 100644 packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/lib/config.ts create mode 100644 packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/lib/graphql-client-factory.ts diff --git a/packages/create-sitecore-jss/src/templates/angular-xmcloud/.env b/packages/create-sitecore-jss/src/templates/angular-xmcloud/.env index 1c394b55d3..bf26d7788a 100644 --- a/packages/create-sitecore-jss/src/templates/angular-xmcloud/.env +++ b/packages/create-sitecore-jss/src/templates/angular-xmcloud/.env @@ -1,8 +1,8 @@ -# ========== XMCloud Proxy =========== +# ========== XM Cloud Proxy =========== -# Your XMCloud Proxy hostname is needed to build the app and execute the client-side requests against the proxy server. +# Your XM Cloud Proxy hostname is needed to build the app and execute the client-side requests against the proxy server. PROXY_HOST=http://localhost:3001 -# Your XMCloud Proxy server path is needed to build the app. The build output will be copied to the proxy server path. +# Your XM Cloud Proxy server path is needed to build the app. The build output will be copied to the proxy server path. PROXY_BUILD_PATH=<%- proxyAppDestination %>/dist diff --git a/packages/create-sitecore-jss/src/templates/angular-xmcloud/scripts/config/plugins/computed.ts b/packages/create-sitecore-jss/src/templates/angular-xmcloud/scripts/config/plugins/computed.ts deleted file mode 100644 index 4450aba6e1..0000000000 --- a/packages/create-sitecore-jss/src/templates/angular-xmcloud/scripts/config/plugins/computed.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { JssConfig } from 'lib/config'; -import { ConfigPlugin } from '..'; -import { constantCase } from 'constant-case'; -import { constants } from '@sitecore-jss/sitecore-jss-angular/cjs'; - -/** - * This plugin will set computed config props. - * The "graphQLEndpoint" is an example of making a _computed_ config setting - * based on other config settings. - */ -class ComputedPlugin implements ConfigPlugin { - // should come after other plugins (but before fallback) - order = 10; - - async exec(config: JssConfig) { - const proxyBuildPath = process.env[`${constantCase('proxyBuildPath')}`]?.replace(/\/$/, ''); - const proxyHost = process.env[`${constantCase('proxyHost')}`]; - - const production = process.env.JSS_MODE === constants.JSS_MODE.PRODUCTION; - - let graphQLEndpoint; - let computed = `${config.graphQLEndpoint || config.sitecoreApiHost}${ - config.graphQLEndpointPath - }`; - - if (production) { - // When build for production (using `jss build`) - // The client side requests will be proxied through the node-xmcloud-proxy and passed to the Sitecore instance - // The server side requests will be executed against the Sitecore instance directly - graphQLEndpoint = `\${typeof window === 'undefined' ? '${computed}' : '${proxyHost}${config.graphQLEndpointPath}'}`; - } else { - // When working in development mode (`jss start:connected`) - graphQLEndpoint = computed; - } - - return Object.assign({}, config, { - proxyBuildPath, - graphQLEndpoint, - }); - } -} - -export const computedPlugin = new ComputedPlugin(); 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 new file mode 100644 index 0000000000..eb7e5a3332 --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/angular-xmcloud/scripts/config/plugins/xmcloud.ts @@ -0,0 +1,23 @@ +import { JssConfig } from 'lib/config'; +import { ConfigPlugin } from '..'; +import { constantCase } from 'constant-case'; + +/** + * This plugin will set XM Cloud related config props. + */ +class XMCloudPlugin implements ConfigPlugin { + // should come after other plugins (but before fallback) + order = 10; + + async exec(config: JssConfig) { + const proxyBuildPath = process.env[`${constantCase('proxyBuildPath')}`]?.replace(/\/$/, ''); + const proxyHost = process.env[`${constantCase('proxyHost')}`]; + + return Object.assign({}, config, { + proxyBuildPath, + proxyHost, + }); + } +} + +export const xmcloudPlugin = new XMCloudPlugin(); diff --git a/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/lib/config.ts b/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/lib/config.ts new file mode 100644 index 0000000000..86b4f2f29f --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/lib/config.ts @@ -0,0 +1,15 @@ +/* + * Represents the type of config object available within the generated /environments/environment.js + */ +export interface JssConfig extends Record { + production?: false; + sitecoreApiKey?: string; + sitecoreApiHost?: string; + sitecoreSiteName?: string; + defaultLanguage?: string; + graphQLEndpoint?: string; + graphQLEndpointPath?: string; + defaultServerRoute?: string; + proxyBuildPath?: string; + proxyHost?: string; +} diff --git a/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/lib/graphql-client-factory.ts b/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/lib/graphql-client-factory.ts new file mode 100644 index 0000000000..d2687ef558 --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/angular-xmcloud/src/app/lib/graphql-client-factory.ts @@ -0,0 +1,44 @@ +import { + GraphQLRequestClientFactoryConfig, + GraphQLRequestClient, +} from '@sitecore-jss/sitecore-jss-angular'; +import { environment as env } from '../../environments/environment'; + +// The GraphQLRequestClientFactory serves as the central hub for executing GraphQL requests within the application + +/** + * Creates a new GraphQLRequestClientFactory instance + * @returns GraphQLRequestClientFactory instance + */ +export const createGraphQLClientFactory = () => { + let clientConfig: GraphQLRequestClientFactoryConfig; + + // If we are in a production environment we are going to use Node XM Cloud proxy + if (env.production === 'true') { + if (env.proxyHost && env.sitecoreApiKey && env.graphQLEndpoint) { + // Server side requests should go directly to the Sitecore, browser requests should go through the proxy. + clientConfig = { + endpoint: + typeof window === 'undefined' + ? env.graphQLEndpoint + : `${env.proxyHost}${env.graphQLEndpointPath}`, + apiKey: env.sitecoreApiKey, + }; + } else { + throw new Error('Please configure your proxyHost, sitecoreApiKey, graphQLEndpoint.'); + } + } else { + if (env.graphQLEndpoint && env.sitecoreApiKey) { + clientConfig = { + endpoint: env.graphQLEndpoint, + apiKey: env.sitecoreApiKey, + }; + } else { + throw new Error('Please configure your graphQLEndpoint and sitecoreApiKey.'); + } + } + + return GraphQLRequestClient.createClientFactory(clientConfig); +}; + +export const clientFactory = createGraphQLClientFactory(); diff --git a/packages/create-sitecore-jss/src/templates/angular/angular.json b/packages/create-sitecore-jss/src/templates/angular/angular.json index d8d2e92c59..59005fc14a 100644 --- a/packages/create-sitecore-jss/src/templates/angular/angular.json +++ b/packages/create-sitecore-jss/src/templates/angular/angular.json @@ -49,12 +49,7 @@ ], "baseHref": "/", "aot": true, - "//": [ - "You might want to adjust the optimization entry to workaround a bug with bootstrap warnings caused by Angular", - "The warnings may occur during build and when running connected mode. Check below for possible fixes.", - "https://github.com/ng-bootstrap/ng-bootstrap/issues/4306" - ], - "optimization": "true", + "optimization": true, "outputHashing": "all", "sourceMap": false, "namedChunks": false, diff --git a/packages/create-sitecore-jss/src/templates/angular/package.json b/packages/create-sitecore-jss/src/templates/angular/package.json index 109f1087dd..3d6f1e912e 100644 --- a/packages/create-sitecore-jss/src/templates/angular/package.json +++ b/packages/create-sitecore-jss/src/templates/angular/package.json @@ -19,11 +19,11 @@ "e2e": "ng e2e", "jss": "jss", "start:connected": "npm-run-all --serial bootstrap start:angular start:watch-components", - "build": "cross-env-shell JSS_MODE=production \"npm-run-all --serial bootstrap --serial build:client build:server\"", + "build": "npm-run-all --serial bootstrap build:client build:server", "scaffold": "ng generate @sitecore-jss/sitecore-jss-angular-schematics:jss-component --no-manifest", "start:angular": "ng serve -o", "start:watch-components": "ts-node --project src/tsconfig.webpack-server.json scripts/generate-component-factory.ts --watch", - "build:client": "cross-env-shell ng build --base-href $npm_package_config_sitecoreDistPath/browser/ --output-path=$npm_package_config_buildArtifactsPath/browser/", + "build:client": "cross-env-shell ng build --configuration=production --base-href $npm_package_config_sitecoreDistPath/browser/ --output-path=$npm_package_config_buildArtifactsPath/browser/", "build:server": "cross-env-shell ng run <%- appName %>:server:production --output-path=$npm_package_config_buildArtifactsPath", "postbuild:server": "move-cli ./dist/main.js ./dist/server.bundle.js", "bootstrap": "ts-node --project src/tsconfig.webpack-server.json scripts/bootstrap.ts", diff --git a/packages/create-sitecore-jss/src/templates/angular/scripts/generate-config.ts b/packages/create-sitecore-jss/src/templates/angular/scripts/generate-config.ts index ac43bcfeba..12ab4e5d0c 100644 --- a/packages/create-sitecore-jss/src/templates/angular/scripts/generate-config.ts +++ b/packages/create-sitecore-jss/src/templates/angular/scripts/generate-config.ts @@ -67,12 +67,9 @@ export function writeConfig(config: JssConfig, outputPath?: string) { // Set base configuration values, allowing override with environment variables Object.keys(config).forEach((prop) => { - let value = config[prop]?.toString().trim(); - const usesTemplateLiteral = /\$\{.*?\}/.test(config[prop].toString()); - - value = usesTemplateLiteral ? `\`${value}\`` : `"${value}"`; - - configText += `config.${prop} = process.env.${constantCase(prop)} || ${value};\n`; + configText += `config.${prop} = process.env.${constantCase(prop)} || "${config[prop] + ?.toString() + .trim()}";\n`; }); configText += `module.exports.environment = config;`; diff --git a/packages/create-sitecore-jss/src/templates/angular/src/app/routing/layout/layout.component.ts b/packages/create-sitecore-jss/src/templates/angular/src/app/routing/layout/layout.component.ts index 3db96a98a1..a518f31d7e 100644 --- a/packages/create-sitecore-jss/src/templates/angular/src/app/routing/layout/layout.component.ts +++ b/packages/create-sitecore-jss/src/templates/angular/src/app/routing/layout/layout.component.ts @@ -71,9 +71,8 @@ export class LayoutComponent implements OnInit, OnDestroy { } } - onPlaceholderLoaded(placeholderName: string) { + onPlaceholderLoaded(_placeholderName: string) { // you may optionally hook to the loaded event for a placeholder, // which can be useful for analytics and other DOM-based things that need to know when a placeholder's content is available. - console.debug(`layout.component.ts: placeholder component fired loaded event for the ${placeholderName} placeholder`); } } 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 48b34a11e3..602e68934b 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 @@ -1,14 +1,14 @@ -# Node XMCloud Proxy +# Node XM Cloud Proxy -> Sitecore JSS Proxy for XMCloud is considered experimental. +> Sitecore JSS Proxy for XM Cloud is considered experimental. [Documentation]() -This is a sample setup showing one of how you can configure XMCloud rendering server. +This is a sample setup showing one of how you can configure XM Cloud rendering server. ## Pre-requisites -1. SPA sample supports XMCloud out of the box. +1. SPA sample supports XM Cloud out of the box. 1. Build your SPA app bundle with `jss build`. The build output should be placed in the `dist` folder. 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 fdbb3a761c..4e760334a8 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,7 +1,7 @@ { "name": "node-xmcloud-sample", "version": "22.1.0-canary", - "description": "Node XMCloud Proxy sample for running XMCloud JSS SPA apps", + "description": "Node XM Cloud Proxy sample for running XM Cloud JSS SPA apps", "author": { "name": "Sitecore Corporation", "url": "https://jss.sitecore.com" 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 6ad53355db..d14ad29410 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 @@ -14,9 +14,28 @@ const { layoutServiceFactory, } = config.serverBundle; -if (!renderView || !parseRouteUrl) { +/** + * Required server bundle properties + */ +const requiredProperties = [ + 'renderView', + 'parseRouteUrl', + 'clientFactory', + 'siteName', + 'defaultLanguage', + 'layoutServiceFactory', + 'dictionaryServiceFactory', + 'graphQLEndpointPath', + 'graphQLEndpoint', +]; + +const missingProperties = requiredProperties.filter((property) => !config.serverBundle[property]); + +if (missingProperties.length > 0) { throw new Error( - 'ERROR: The serverBundle should export `renderView` and `parseRouteUrl`, please check your server bundle.' + `ERROR: The serverBundle should export the following properties: ${missingProperties.join( + ', ' + )}. Please check your server bundle.` ); } 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 585d24b559..dc19d648e8 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 @@ -40,6 +40,7 @@ declare type RouteUrlParser = ( }; export interface ServerBundle { + [key: string]: unknown; renderView: AppRenderer; parseRouteUrl: RouteUrlParser; clientFactory: GraphQLRequestClientFactory; diff --git a/packages/sitecore-jss/src/constants.ts b/packages/sitecore-jss/src/constants.ts index 2f000bed81..7a974f39c7 100644 --- a/packages/sitecore-jss/src/constants.ts +++ b/packages/sitecore-jss/src/constants.ts @@ -14,7 +14,6 @@ export const FETCH_WITH = { export const JSS_MODE = { CONNECTED: 'connected', DISCONNECTED: 'disconnected', - PRODUCTION: 'production', }; export const siteNameError = 'The siteName cannot be empty';