Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[Angular] Edge Proxy / Context Id support #1875

Merged
merged 8 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Our versioning strategy is as follows:
### 🐛 Bug Fixes

* `[templates/nextjs-sxa]` The caption of image component has been fixed. ([#1874](https://github.com/Sitecore/jss/pull/1874))
* `[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))

### 🎉 New Features & Improvements

Expand All @@ -24,6 +25,7 @@ Our versioning strategy is as follows:
* XMCloud-based: 'angular,angular-xmcloud'
* Rework Angular initializer to support XMCloud and SXP journeys;
* Add SXA styles to xmcloud addon
* `[create-sitecore-jss]` `[template/angular]` `[template/angular-xmcloud]` `[template/node-xmcloud-proxy]` Edge Proxy / Context Id support ([#1875](https://github.com/Sitecore/jss/pull/1875))

* `[create-sitecore-jss]` Rework Angular initializer to support XMCloud and SXP journeys ([#1845](https://github.com/Sitecore/jss/pull/1845))([#1858](https://github.com/Sitecore/jss/pull/1858))([#1868](https://github.com/Sitecore/jss/pull/1868))
* `[create-sitecore-jss]` Allow node-xmcloud-proxy app to be installed alongside Angular SPA application
Expand Down
74 changes: 68 additions & 6 deletions docs/upgrades/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,90 @@ If you plan to use the Angular SDK with XMCloud, you will need to perform next s
"build:client": "cross-env-shell ng build --configuration=production --base-href $npm_package_config_sitecoreDistPath/browser/ --output-path=$npm_package_config_buildArtifactsPath/browser/"
```

* Update "server.bundle.ts" to additionally expose new properties:
* 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 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';

...
const defaultLanguage = environment.defaultLanguage;
const graphQLEndpointPath = environment.graphQLEndpointPath;
const graphQLEndpoint = environment.graphQLEndpoint;
const getClientFactoryConfig = getGraphQLClientFactoryConfig;

export {
...
clientFactory,
getClientFactoryConfig,
dictionaryServiceFactory,
layoutServiceFactory,
defaultLanguage,
graphQLEndpointPath,
graphQLEndpoint,
};
```

* 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,
});
```
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const chalk = require('chalk');
*/
class DisconnectedPlugin implements ConfigPlugin {
// should come before other plugins
order = 0;
order = 2;

async exec(config: JssConfig) {
const disconnected = process.env.JSS_MODE === constants.JSS_MODE.DISCONNECTED;
Expand All @@ -20,7 +20,9 @@ class DisconnectedPlugin implements ConfigPlugin {
);
}

return config;
return Object.assign({}, config, {
sitecoreApiKey: config.sitecoreApiKey || 'no-api-key-set',
});
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@

# ========== Sitecore Edge Platform ===========

# Your unified Sitecore Edge Context Id.
# This will be used over any Sitecore Preview / Delivery Edge variables (above).
SITECORE_EDGE_CONTEXT_ID=

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

# Your XM Cloud Proxy hostname is needed to build the app and execute the client-side requests against the proxy server.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { JssConfig } from 'lib/config';
import { ConfigPlugin } from '..';
import { constantCase } from 'constant-case';
import chalk from 'chalk';

/**
* This plugin will set XM Cloud related config props.
Expand All @@ -13,9 +14,24 @@ class XMCloudPlugin implements ConfigPlugin {
const proxyBuildPath = process.env[`${constantCase('proxyBuildPath')}`]?.replace(/\/$/, '');
const proxyHost = process.env[`${constantCase('proxyHost')}`];

const sitecoreEdgeUrl =
process.env[`${constantCase('sitecoreEdgeUrl')}`]?.replace(/\/$/, '') ||
'https://edge-platform.sitecorecloud.io';
const sitecoreEdgeContextId = process.env[`${constantCase('sitecoreEdgeContextId')}`];

if (config.sitecoreApiKey && sitecoreEdgeContextId) {
console.log(
chalk.yellow(
"You have configured both 'sitecoreApiKey' and 'sitecoreEdgeContextId' values. The 'sitecoreEdgeContextId' is used instead."
)
);
}

return Object.assign({}, config, {
proxyBuildPath,
proxyHost,
sitecoreEdgeUrl,
sitecoreEdgeContextId,
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ export interface JssConfig extends Record<string, string | boolean | undefined>
defaultServerRoute?: string;
proxyBuildPath?: string;
proxyHost?: string;
sitecoreEdgeUrl?: string;
sitecoreEdgeContextId?: string;
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {
GraphQLRequestClientFactoryConfig,
getEdgeProxyContentUrl,
} from '@sitecore-jss/sitecore-jss-angular/cjs';
import { environment as env } from '../../../environments/environment';

/**
* Gets the configuration for the GraphQLRequestClientFactory
* @returns GraphQLRequestClientFactoryConfig
*/
export const getGraphQLClientFactoryConfig = () => {
let clientConfig: GraphQLRequestClientFactoryConfig;

// Server side requests should go directly to the Sitecore, browser requests should go through the proxy.
const isServer = typeof window === 'undefined';
// If we are in a production environment we are going to use Node XM Cloud proxy
const isProduction = env.production === 'true';

if (isProduction) {
if (!env.proxyHost) {
throw new Error('Please configure your proxyHost.');
}

if (env.sitecoreEdgeContextId) {
clientConfig = {
endpoint: isServer
? getEdgeProxyContentUrl(env.sitecoreEdgeContextId, env.sitecoreEdgeUrl)
: getEdgeProxyContentUrl(env.sitecoreEdgeContextId, env.proxyHost),
};
} else if (env.graphQLEndpoint && env.sitecoreApiKey) {
const graphQLEndpointPath = new URL(env.graphQLEndpoint).pathname;

clientConfig = {
endpoint: isServer ? env.graphQLEndpoint : `${env.proxyHost}${graphQLEndpointPath}`,
apiKey: env.sitecoreApiKey,
};
}
} else {
if (env.sitecoreEdgeContextId) {
clientConfig = {
endpoint: getEdgeProxyContentUrl(env.sitecoreEdgeContextId, env.sitecoreEdgeUrl),
};
} else if (env.graphQLEndpoint && env.sitecoreApiKey) {
clientConfig = {
endpoint: env.graphQLEndpoint,
apiKey: env.sitecoreApiKey,
};
}
}

if (!clientConfig.endpoint) {
throw new Error(
'Please configure either your sitecoreEdgeContextId, or your graphQLEndpoint and sitecoreApiKey.'
);
}

return clientConfig;
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Subscription } from 'rxjs';
import { JssState } from '../../JssState';
import { JssMetaService } from '../../jss-meta.service';
import { JssLinkService } from '../../jss-link.service';
import { environment as env } from '../../../environments/environment';

enum LayoutState {
Layout,
Expand Down Expand Up @@ -58,13 +59,10 @@ export class LayoutComponent implements OnInit, OnDestroy {
? 'editing-mode'
: 'prod-mode';

/** TODO: get contextId and edgeUrl properly **/
const sitecoreEdgeContextId = '';
const sitecoreEdgeUrl = '';
const contentStyles = getContentStylesheetLink(
{ sitecore: data.jssState.sitecore },
sitecoreEdgeContextId,
sitecoreEdgeUrl
env.sitecoreEdgeContextId,
env.sitecoreEdgeUrl
);

// Clear existing stylesheets
Expand Down
4 changes: 4 additions & 0 deletions packages/create-sitecore-jss/src/templates/angular/.env
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# ====== Sitecore Preview / Delivery Edge ======

# Your Sitecore API key is needed to build the app. Typically, the API key is
# defined in `scjssconfig.json` (as `sitecore.apiKey`). This file may not exist
# when building locally (if you've never run `jss setup`), or when building in a
Expand All @@ -17,6 +19,8 @@ SITECORE_API_HOST=
# the resolved Sitecore API hostname + the `graphQLEndpointPath` defined in your `package.json`.
GRAPH_QL_ENDPOINT=

# ==============================================

# Your Sitecore site name.
# Uses your `package.json` config `appName` if empty.
SITECORE_SITE_NAME=
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@
"@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",
"@types/isomorphic-fetch": "0.0.35",
"@types/jasmine": "~3.6.7",
"@types/jasminewd2": "~2.0.8",
"@types/node": "~20.14.10",
Expand All @@ -99,7 +98,6 @@
"eslint-plugin-import": "2.29.1",
"eslint-plugin-jsdoc": "48.7.0",
"eslint-plugin-prefer-arrow": "1.2.3",
"isomorphic-fetch": "^3.0.0",
"jasmine-core": "~3.7.1",
"jasmine-spec-reporter": "~6.0.0",
"karma": "^6.3.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ class FallbackPlugin implements ConfigPlugin {
async exec(config: JssConfig) {
return Object.assign({}, config, {
defaultLanguage: config.defaultLanguage || 'en',
sitecoreApiKey: config.sitecoreApiKey || 'no-api-key-set',
<% if (!locals.xmcloud) { -%>
layoutServiceConfigurationName: config.layoutServiceConfigurationName || 'default',
<% } -%>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dotenv/config';
import * as fs from 'fs';
import * as path from 'path';
import { constantCase } from 'constant-case';
Expand Down Expand Up @@ -38,6 +39,14 @@ export function generateConfig(
defaultConfig: JssConfig = defaultConfigValue,
configOverrides?: { [key: string]: unknown }
) {
// Handle undefined values
defaultConfig = Object.keys(defaultConfig).reduce((acc, key) => {
return {
...acc,
[key]: defaultConfig[key] || '',
};
}, {});

jssConfigFactory
.create(defaultConfig)
.then((config) => {
Expand Down Expand Up @@ -67,9 +76,11 @@ 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`;
// Handle undefined values
const value = config[prop] || '';
configText += `config.${prop} = process.env.${constantCase(
prop
)} || "${value.toString().trim()}";\n`;
});

configText += `module.exports.environment = config;`;
Expand Down
Loading