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

[Next.js][Multi-site] Fetch site info during build #1276

Merged

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import 'dotenv/config';
import chalk from 'chalk';
import { ConfigPlugin, JssConfig } from '..';
import { GraphQLSiteInfoService, SiteInfo } from '@sitecore-jss/sitecore-jss-nextjs';

/**
* This plugin will set the "sites" config prop.
* By default this will attempt to fetch site information directly from Sitecore (using the GraphQLSiteInfoService).
* You could easily modify this to fetch from another source such as a static JSON file instead.
*/
class MultisitePlugin implements ConfigPlugin {
order = 3;

async exec(config: JssConfig) {
let sites: SiteInfo[] = [];
const endpoint = process.env.GRAPH_QL_ENDPOINT || config.graphQLEndpoint;
const apiKey = process.env.SITECORE_API_KEY || config.sitecoreApiKey;

if (!endpoint || !apiKey) {
console.warn(
chalk.yellow('Skipping site information fetch (missing GraphQL connection details)')
);
} else {
console.log(`Fetching site information from ${endpoint}`);
try {
const siteInfoService = new GraphQLSiteInfoService({
endpoint,
apiKey,
});
sites = await siteInfoService.fetchSiteInfo();
} catch (error) {
console.error(chalk.red('Error fetching site information'));
console.error(error);
}
}

return Object.assign({}, config, {
sites: JSON.stringify(sites),
});
}
}

export const multisitePlugin = new MultisitePlugin();
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import 'dotenv/config';
import chalk from 'chalk';
import { constants } from '@sitecore-jss/sitecore-jss-nextjs';
import { ConfigPlugin, JssConfig } from '..';

/**
* This plugin will override the "sitecoreApiHost" config prop
* for disconnected mode (using disconnected server).
*/
class DisconnectedPlugin implements ConfigPlugin {
order = 3;

async exec(config: JssConfig) {
const disconnected = process.env.JSS_MODE === constants.JSS_MODE.DISCONNECTED;

if (!disconnected) return config;

if (process.env.FETCH_WITH === constants.FETCH_WITH.GRAPHQL) {
throw new Error(
chalk.red(
'GraphQL requests to Dictionary and Layout services are not supported in disconnected mode.'
)
);
}

const port = process.env.PORT || 3000;

return Object.assign({}, config, {
sitecoreApiHost: `http://localhost:${port}`,
});
}
}

export const disconnectedPlugin = new DisconnectedPlugin();
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"npm-run-all": "~4.1.5",
"prettier": "^2.1.2",
"ts-node": "^9.0.0",
"tsconfig-paths": "^4.1.1",
"typescript": "~4.3.5",
"yaml-loader": "^0.6.0"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,41 +1,21 @@
import 'dotenv/config';
import { generateConfig } from './generate-config';
import { constants } from '@sitecore-jss/sitecore-jss-nextjs';
import chalk from 'chalk';
/*
BOOTSTRAPPING
The bootstrap process runs before build, and generates JS that needs to be
included into the build - specifically, the component name to component mapping,
and the global config module.
included into the build - specifically, plugins, the global config module,
and the component name to component mapping.
*/

const disconnected = process.env.JSS_MODE === constants.JSS_MODE.DISCONNECTED;
/*
CONFIG GENERATION
Generates the /src/temp/config.js file which contains runtime configuration
that the app can import and use.
PLUGINS GENERATION
*/
const port = process.env.PORT || 3000;
const configOverride: { [key: string]: string } = {};
if (disconnected) {
if (process.env.FETCH_WITH === constants.FETCH_WITH.GRAPHQL) {
throw new Error(
chalk.red(
'GraphQL requests to Dictionary and Layout services are not supported in disconnected mode.'
)
);
}
configOverride.sitecoreApiHost = `http://localhost:${port}`;
}

generateConfig(configOverride);
import './generate-plugins';

/*
COMPONENT FACTORY GENERATION
CONFIG GENERATION
*/
import './generate-component-factory';
import './generate-config';

/*
PLUGINS GENERATION
COMPONENT FACTORY GENERATION
*/
import './generate-plugins';
import './generate-component-factory';
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const plugins = require('scripts/temp/config-plugins');

/**
* JSS configuration object
*/
export interface JssConfig extends Record<string, string | undefined> {
sitecoreApiKey?: string;
sitecoreApiHost?: string;
jssAppName?: string;
graphQLEndpointPath?: string;
defaultLanguage?: string;
graphQLEndpoint?: string;
}

export interface ConfigPlugin {
/**
* Detect order when the plugin should be called, e.g. 0 - will be called first (can be a plugin which data is required for other plugins)
*/
order: number;
/**
* A function which will be called during config generation
* @param {JssConfig} config Current (accumulated) config
*/
exec(config: JssConfig): Promise<JssConfig>;
}

export class JssConfigFactory {
public async create(defaultConfig: JssConfig = {}): Promise<JssConfig> {
return (Object.values(plugins) as ConfigPlugin[])
.sort((p1, p2) => p1.order - p2.order)
.reduce(
(promise, plugin) => promise.then((config) => plugin.exec(config)),
Promise.resolve(defaultConfig)
);
}
}

export const jssConfigFactory = new JssConfigFactory();
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ConfigPlugin, JssConfig } from '..';

/**
* 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 {
order = 2;

async exec(config: JssConfig) {
return Object.assign({}, config, {
graphQLEndpoint: `${config.sitecoreApiHost}${config.graphQLEndpointPath}`,
});
}
}

export const computedPlugin = new ComputedPlugin();
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ConfigPlugin, JssConfig } from '..';
import packageConfig from 'package.json';

/**
* This plugin will set config props based on package.json.
*/
class PackageJsonPlugin implements ConfigPlugin {
order = 1;

async exec(config: JssConfig) {
if (!packageConfig.config) return config;

return Object.assign({}, config, {
jssAppName: packageConfig.config.appName,
graphQLEndpointPath: packageConfig.config.graphQLEndpointPath,
defaultLanguage: packageConfig.config.language,
});
}
}

export const packageJsonPlugin = new PackageJsonPlugin();
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { ConfigPlugin, JssConfig } from '..';

/**
* This plugin will set config props based on scjssconfig.json.
* scjssconfig.json may not exist if you've never run `jss setup` (development)
* or are depending on environment variables instead (production).
*/
class ScJssConfigPlugin implements ConfigPlugin {
order = 1;

async exec(config: JssConfig) {
let scJssConfig;
try {
scJssConfig = require('scjssconfig.json');
} catch (e) {
return config;
}

if (!scJssConfig) return config;

return Object.assign({}, config, {
sitecoreApiKey: scJssConfig.sitecore?.apiKey,
sitecoreApiHost: scJssConfig.sitecore?.layoutServiceHost,
});
}
}

export const scjssconfigPlugin = new ScJssConfigPlugin();
Original file line number Diff line number Diff line change
@@ -1,86 +1,59 @@
import fs from 'fs';
import path from 'path';
import { constantCase } from 'constant-case';
import packageConfig from '../package.json';
import { JssConfig, jssConfigFactory } from './config';

/* eslint-disable no-console */
/*
CONFIG GENERATION
Generates the /src/temp/config.js file which contains runtime configuration
that the app can import and use.
*/

/**
* Generate config
* The object returned from this function will be made available by importing src/temp/config.js.
* This is executed prior to the build running, so it's a way to inject environment or build config-specific
* settings as variables into the JSS app.
* NOTE! Any configs returned here will be written into the client-side JS bundle. DO NOT PUT SECRETS HERE.
* @param {object} configOverrides Keys in this object will override any equivalent global config keys.
*/
export function generateConfig(configOverrides?: { [key: string]: string }): void {
const defaultConfig = {
sitecoreApiKey: 'no-api-key-set',
sitecoreApiHost: '',
jssAppName: 'Unknown',
};
const defaultConfig: JssConfig = {
sitecoreApiKey: 'no-api-key-set',
sitecoreApiHost: '',
jssAppName: 'Unknown',
graphQLEndpointPath: '',
defaultLanguage: 'en',
};

// require + combine config sources
const scjssConfig = transformScJssConfig();
const packageJson = transformPackageConfig();
generateConfig(defaultConfig);

// Object.assign merges the objects in order, so config overrides are performed as:
// default config <-- scjssconfig.json <-- package.json <-- configOverrides
// Optional: add any other dynamic config source (e.g. environment-specific config files).
const config = Object.assign(defaultConfig, scjssConfig, packageJson, configOverrides);

// The GraphQL endpoint is an example of making a _computed_ config setting
// based on other config settings.
const computedConfig: { [key: string]: string } = {};
computedConfig.graphQLEndpoint = '`${config.sitecoreApiHost}${config.graphQLEndpointPath}`';
/**
* Generates the JSS config based on config plugins (under ./config/plugins)
* and then writes the config to disk.
* @param {JssConfig} defaultConfig Default configuration.
*/
function generateConfig(defaultConfig: JssConfig): void {
jssConfigFactory
.create(defaultConfig)
.then((config) => {
writeConfig(config);
})
.catch((e) => {
console.error('Error generating config');
console.error(e);
process.exit(1);
});
}

/**
* Writes the config object to disk with support for environment variables.
* @param {JssConfig} config JSS configuration to write.
*/
function writeConfig(config: JssConfig): void {
let configText = `/* eslint-disable */
// Do not edit this file, it is auto-generated at build time!
// See scripts/bootstrap.ts to modify the generation of this file.
const config = {};\n`;

// Set base configuration values, allowing override with environment variables
// Set configuration values, allowing override with environment variables
Object.keys(config).forEach((prop) => {
configText += `config.${prop} = process.env.${constantCase(prop)} || "${config[prop]}",\n`;
});
// Set computed values, allowing override with environment variables
Object.keys(computedConfig).forEach((prop) => {
configText += `config.${prop} = process.env.${constantCase(prop)} || ${
computedConfig[prop]
};\n`;
configText += `config.${prop} = process.env.${constantCase(prop)} || '${config[prop]}',\n`;
});
configText += `module.exports = config;`;

const configPath = path.resolve('src/temp/config.js');
console.log(`Writing runtime config to ${configPath}`);
fs.writeFileSync(configPath, configText, { encoding: 'utf8' });
}

function transformScJssConfig() {
// scjssconfig.json may not exist if you've never run `jss setup` (development)
// or are depending on environment variables instead (production).
let config;
try {
// eslint-disable-next-line global-require
config = require('../scjssconfig.json');
} catch (e) {
return {};
}

if (!config) return {};

return {
sitecoreApiKey: config.sitecore.apiKey,
sitecoreApiHost: config.sitecore.layoutServiceHost,
};
}

function transformPackageConfig() {
if (!packageConfig.config) return {};

return {
jssAppName: packageConfig.config.appName,
graphQLEndpointPath: packageConfig.config.graphQLEndpointPath,
defaultLanguage: packageConfig.config.language || 'en',
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ interface PluginFile {
}

const pluginDefinitions = [
{
listPath: 'scripts/temp/config-plugins.ts',
rootPath: 'scripts/config/plugins',
moduleType: ModuleType.ESM,
},
{
listPath: 'src/temp/sitemap-fetcher-plugins.ts',
rootPath: 'src/lib/sitemap-fetcher/plugins',
Expand Down Expand Up @@ -75,7 +80,8 @@ function run(definitions: PluginDefinition[]) {
* Modify this function to use a different convention.
*/
function writePlugins(listPath: string, rootPath: string, moduleType: ModuleType) {
const pluginName = rootPath.split('/')[2];
const segments = rootPath.split('/');
const pluginName = segments[segments.length - 2];
const plugins = getPluginList(rootPath, pluginName);
let fileContent = '';

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
Loading