diff --git a/CHANGELOG.md b/CHANGELOG.md index b2cc3578e2..5faaa8cb21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,12 +13,12 @@ Our versioning strategy is as follows: ### 🎉 New Features & Improvements -* `[templates/nextjs-sxa]` `[sitecore-jss-react]` "Bring Your Own Code" (BYOC) feature is introduced. This allows developers and editors more flexibility when developing and working with new components, i.e.: +* `[templates/nextjs-sxa]` `[sitecore-jss-react]` `[sitecore-jss-nextjs]` "Bring Your Own Code" (BYOC) feature is introduced. This allows developers and editors more flexibility when developing and working with new components, i.e.: * Avoid the jss deploy process for components, and use FEAAS registration instead * Put components anywhere in the project, * Use any prop type, without dependence on Layout Service data - Check the BYOC documentation for more info. ([#1568](https://github.com/Sitecore/jss/pull/1568)) ([#1603](https://github.com/Sitecore/jss/pull/1603)) + Check the BYOC documentation for more info. ([#1568](https://github.com/Sitecore/jss/pull/1568)) ([#1603](https://github.com/Sitecore/jss/pull/1603))([#1605](https://github.com/Sitecore/jss/pull/1605)) * `[templates/nextjs-sxa]` Scaffolding components for BYOC is added. Use '--byoc' flag at the end of `jss scaffold` command to create a boilerplate component for BYOC ([#1572](https://github.com/Sitecore/jss/pull/1572)) * `[sitecore-jss-nextjs]` Stylesheet loading via page head links for FEAAS and BYOC is implemented. This allows stylesheets to be loaded during SSR and avoid extra calls on client. ([#1587](https://github.com/Sitecore/jss/pull/1587)) * `[templates/nextjs]` Scaffold new components outside of 'src/components' folder by specifying a path with src in it, i.e. `jss scaffold src/new-folder/NewComponent` ([#1572](https://github.com/Sitecore/jss/pull/1572)) diff --git a/packages/create-sitecore-jss/src/templates/nextjs-sxa/package.json b/packages/create-sitecore-jss/src/templates/nextjs-sxa/package.json index 547d68f94a..b27c6cdcf7 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-sxa/package.json +++ b/packages/create-sitecore-jss/src/templates/nextjs-sxa/package.json @@ -1,6 +1,5 @@ { "dependencies": { - "@sitecore-feaas/clientside": "^0.3.12", "bootstrap": "^5.1.3", "font-awesome": "^4.7.0", "sass": "^1.52.3", diff --git a/packages/create-sitecore-jss/src/templates/nextjs/package.json b/packages/create-sitecore-jss/src/templates/nextjs/package.json index a8f7ddc277..4a7b3aa0e6 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/package.json +++ b/packages/create-sitecore-jss/src/templates/nextjs/package.json @@ -29,6 +29,7 @@ }, "license": "Apache-2.0", "dependencies": { + "@sitecore-feaas/clientside": "^0.3.14", "@sitecore-jss/sitecore-jss-nextjs": "~21.3.0-canary", "graphql": "~15.8.0", "graphql-tag": "^2.12.6", 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 99707b8a9c..2fd63ecb01 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 @@ -4,8 +4,8 @@ import { PackageDefinition, ComponentFile } from '@sitecore-jss/sitecore-jss-dev export interface ComponentBuilderPluginConfig { watch?: boolean; - packages?: PackageDefinition[]; - components?: ComponentFile[]; + packages: PackageDefinition[]; + components: ComponentFile[]; } export interface ComponentBuilderPlugin { @@ -38,6 +38,8 @@ export interface ComponentBuilderPlugin { const defaultConfig: ComponentBuilderPluginConfig = { watch: process.argv.some(arg => arg === '--watch'), + packages: [], + components: [], }; (Object.values(plugins) as ComponentBuilderPlugin[]) diff --git a/packages/create-sitecore-jss/src/templates/nextjs/scripts/generate-component-builder/plugins/feaas.ts b/packages/create-sitecore-jss/src/templates/nextjs/scripts/generate-component-builder/plugins/feaas.ts new file mode 100644 index 0000000000..bdbce27569 --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/nextjs/scripts/generate-component-builder/plugins/feaas.ts @@ -0,0 +1,28 @@ +import { ComponentBuilderPlugin, ComponentBuilderPluginConfig } from '..'; + +/** + * Provides Sitecore Components (FEaaS) packages configuration + */ +class FEaaSPlugin implements ComponentBuilderPlugin { + order = 1; + + exec(config: ComponentBuilderPluginConfig) { + config.packages.push({ + name: '@sitecore-jss/sitecore-jss-nextjs', + components: [ + { + componentName: 'BYOCWrapper', + moduleName: 'BYOCWrapper', + }, + { + componentName: 'FEaaSWrapper', + moduleName: 'FEaaSWrapper', + }, + ], + }); + + return config; + } +} + +export const feaasPlugin = new FEaaSPlugin(); diff --git a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/next-config/plugins/feaas.js b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/next-config/plugins/feaas.js new file mode 100644 index 0000000000..1cb6b91c3f --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/next-config/plugins/feaas.js @@ -0,0 +1,24 @@ +/** + * @param {import('next').NextConfig} nextConfig + */ +const feaasPlugin = (nextConfig = {}) => { + return Object.assign({}, nextConfig, { + webpack: (config, options) => { + if (options.isServer) { + // Force use of CommonJS on the server for FEAAS SDK since JSS also uses CommonJS entrypoint to FEAAS SDK. + // This prevents issues arising due to FEAAS SDK's dual CommonJS/ES module support on the server (via conditional exports). + // See https://nodejs.org/api/packages.html#dual-package-hazard. + config.externals = [{ '@sitecore-feaas/clientside/react': 'commonjs @sitecore-feaas/clientside/react' }, ...config.externals]; + } + + // Overload the Webpack config if it was already overloaded + if (typeof nextConfig.webpack === 'function') { + return nextConfig.webpack(config, options); + } + + return config; + } + }); +}; + +module.exports = feaasPlugin; diff --git a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/next-config/plugins/monorepo.js b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/next-config/plugins/monorepo.js index f6f54147ea..f1ce7c9a26 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/next-config/plugins/monorepo.js +++ b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/next-config/plugins/monorepo.js @@ -1,3 +1,6 @@ +const path = require('path'); +const CWD = process.cwd(); + /** * @param {import('next').NextConfig} nextConfig */ @@ -7,6 +10,12 @@ const monorepoPlugin = (nextConfig = {}) => { if (options.isServer) { config.externals = ['react', 'vertx', ...config.externals]; } + // Monorepo support for @sitecore-feaas/clientside/react + config.resolve.alias['@sitecore-feaas/clientside/react'] = path.resolve( + CWD, options.isServer ? + './node_modules/@sitecore-feaas/clientside/dist/node/react.cjs' : + './node_modules/@sitecore-feaas/clientside/dist/browser/react.esm.js' + ); // Overload the Webpack config if it was already overloaded if (typeof nextConfig.webpack === 'function') { diff --git a/packages/sitecore-jss-nextjs/src/ComponentBuilder.ts b/packages/sitecore-jss-nextjs/src/ComponentBuilder.ts index aeb8b725d4..3d3b976030 100644 --- a/packages/sitecore-jss-nextjs/src/ComponentBuilder.ts +++ b/packages/sitecore-jss-nextjs/src/ComponentBuilder.ts @@ -13,7 +13,7 @@ export type LazyModule = { /** * Component is a module or a lazy module */ -type Component = Module | LazyModule; +type Component = Module | LazyModule | ComponentType; /** * Configuration for ComponentBuilder @@ -97,7 +97,11 @@ export class ComponentBuilder { return (component as Module)[exportName]; } - return (component as Module).Default || (component as Module).default || null; + return ( + (component as Module).Default || + (component as Module).default || + (component as ComponentType) + ); }; } } diff --git a/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/components/FEaaSWrapper.tsx b/packages/sitecore-jss-nextjs/src/components/FEaaSWrapper.tsx similarity index 53% rename from packages/create-sitecore-jss/src/templates/nextjs-sxa/src/components/FEaaSWrapper.tsx rename to packages/sitecore-jss-nextjs/src/components/FEaaSWrapper.tsx index 27e4536fe5..e4f0330511 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/components/FEaaSWrapper.tsx +++ b/packages/sitecore-jss-nextjs/src/components/FEaaSWrapper.tsx @@ -1,38 +1,34 @@ import { - GetServerSideComponentProps, - GetStaticComponentProps, - FEaaSComponent, - FEaaSComponentProps, + FEaaSWrapper, FEaaSComponentParams, fetchFEaaSComponentServerProps, - constants, -} from '@sitecore-jss/sitecore-jss-nextjs'; -import React from 'react'; +} from '@sitecore-jss/sitecore-jss-react'; +import { + GetStaticComponentProps, + GetServerSideComponentProps, +} from '../sharedTypes/component-props'; +import { constants } from '@sitecore-jss/sitecore-jss'; -export const Default = (props: FEaaSComponentProps): JSX.Element => { - const styles = `component feaas ${props.params?.styles}`.trimEnd(); - const id = props.params?.RenderingIdentifier; - return ( -
-
- -
-
- ); -}; +/** + * This is a repackaged version of the React FEaaSWrapper component with support for + * server rendering in Next.js (using component-level data-fetching feature of JSS). + */ /** * Will be called during SSG * @param {ComponentRendering} rendering * @param {LayoutServiceData} layoutData - * @param {GetStaticPropsContext} context + * @returns {GetStaticPropsContext} context */ export const getStaticProps: GetStaticComponentProps = async (rendering, layoutData) => { if (process.env.JSS_MODE === constants.JSS_MODE.DISCONNECTED) { return null; } const params: FEaaSComponentParams = rendering.params || {}; - const result = await fetchFEaaSComponentServerProps(params, layoutData.sitecore.context.pageState); + const result = await fetchFEaaSComponentServerProps( + params, + layoutData.sitecore.context.pageState + ); return result; }; @@ -40,13 +36,18 @@ export const getStaticProps: GetStaticComponentProps = async (rendering, layoutD * Will be called during SSR * @param {ComponentRendering} rendering * @param {LayoutServiceData} layoutData - * @param {GetStaticPropsContext} context + * @returns {GetStaticPropsContext} context */ export const getServerSideProps: GetServerSideComponentProps = async (rendering, layoutData) => { if (process.env.JSS_MODE === constants.JSS_MODE.DISCONNECTED) { return null; } const params: FEaaSComponentParams = rendering.params || {}; - const result = await fetchFEaaSComponentServerProps(params, layoutData.sitecore.context.pageState); + const result = await fetchFEaaSComponentServerProps( + params, + layoutData.sitecore.context.pageState + ); return result; }; + +export default FEaaSWrapper; diff --git a/packages/sitecore-jss-nextjs/src/index.ts b/packages/sitecore-jss-nextjs/src/index.ts index 90ab5a8f42..b34d35bf37 100644 --- a/packages/sitecore-jss-nextjs/src/index.ts +++ b/packages/sitecore-jss-nextjs/src/index.ts @@ -152,6 +152,8 @@ export { RichText, RichTextProps } from './components/RichText'; export { Placeholder } from './components/Placeholder'; export { EditingComponentPlaceholder } from './components/EditingComponentPlaceholder'; export { NextImage } from './components/NextImage'; +import * as FEaaSWrapper from './components/FEaaSWrapper'; +export { FEaaSWrapper }; export { ComponentBuilder, ComponentBuilderConfig } from './ComponentBuilder'; @@ -175,6 +177,7 @@ export { BYOCComponent, BYOCComponentProps, getFEAASLibraryStylesheetLinks, + BYOCWrapper, File, FileField, RichTextField, diff --git a/packages/sitecore-jss-react/package.json b/packages/sitecore-jss-react/package.json index c78c743ad6..f121cae91c 100644 --- a/packages/sitecore-jss-react/package.json +++ b/packages/sitecore-jss-react/package.json @@ -27,6 +27,7 @@ "url": "https://github.com/sitecore/jss/issues" }, "devDependencies": { + "@sitecore-feaas/clientside": "^0.3.14", "@types/chai": "^4.3.4", "@types/chai-string": "^1.4.2", "@types/deep-equal": "^1.0.1", @@ -57,11 +58,11 @@ "typescript": "~4.9.3" }, "peerDependencies": { + "@sitecore-feaas/clientside": "^0.3.14", "react": "^18.2.0", "react-dom": "^18.2.0" }, "dependencies": { - "@sitecore-feaas/clientside": "^0.3.12", "@sitecore-jss/sitecore-jss": "21.3.0-canary.51", "deep-equal": "^2.1.0", "prop-types": "^15.8.1", diff --git a/packages/sitecore-jss-react/src/components/BYOCComponent.test.tsx b/packages/sitecore-jss-react/src/components/BYOCComponent.test.tsx index 5006543a82..791a6e9570 100644 --- a/packages/sitecore-jss-react/src/components/BYOCComponent.test.tsx +++ b/packages/sitecore-jss-react/src/components/BYOCComponent.test.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { expect } from 'chai'; import { mount } from 'enzyme'; +import * as FEAAS from '@sitecore-feaas/clientside/react'; import { BYOCComponent } from './BYOCComponent'; import { MissingComponent, MissingComponentProps } from './MissingComponent'; @@ -8,15 +9,25 @@ describe('BYOCComponent', () => { it('should render with props when ComponentProps is provided', () => { const mockProps = { params: { - ComponentName: 'ExternalComponent', + ComponentName: 'Foo', ComponentProps: JSON.stringify({ prop1: 'value1' }), }, }; + const Foo = () =>

Test

; + FEAAS.External.registerComponent(Foo, { + name: 'Foo', + properties: { + prop1: { + type: 'string', + }, + }, + }); const wrapper = mount(); - const externalComponent = wrapper.find('Se'); - expect(externalComponent).to.have.lengthOf(1); - expect(externalComponent.prop('componentName')).to.equal('ExternalComponent'); - expect(externalComponent.prop('prop1')).to.equal('value1'); + const fooComponent = wrapper.find('feaas-external'); + expect(fooComponent).to.have.lengthOf(1); + expect(fooComponent.prop('prop1')).to.equal('value1'); + expect(fooComponent.prop('data-external-id')).to.equal('Foo'); + expect(fooComponent.find('#foo-content')).to.have.length(1); }); }); diff --git a/packages/sitecore-jss-react/src/components/BYOCComponent.tsx b/packages/sitecore-jss-react/src/components/BYOCComponent.tsx index a1d3d33ac1..afcdf7268b 100644 --- a/packages/sitecore-jss-react/src/components/BYOCComponent.tsx +++ b/packages/sitecore-jss-react/src/components/BYOCComponent.tsx @@ -4,6 +4,8 @@ import { getDataFromFields } from '../utils'; import { MissingComponent, MissingComponentProps } from './MissingComponent'; import * as FEAAS from '@sitecore-feaas/clientside/react'; +export const BYOC_COMPONENT_RENDERING_NAME = 'BYOCComponent'; + /** * Data from rendering params on Sitecore's BYOC rendering */ diff --git a/packages/sitecore-jss-react/src/components/BYOCWrapper.test.tsx b/packages/sitecore-jss-react/src/components/BYOCWrapper.test.tsx new file mode 100644 index 0000000000..cd2a2579ac --- /dev/null +++ b/packages/sitecore-jss-react/src/components/BYOCWrapper.test.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { stub } from 'sinon'; +import { expect } from 'chai'; +import { mount } from 'enzyme'; +import { BYOCWrapper } from './BYOCWrapper'; +import * as BYOCComponent from './BYOCComponent'; + +describe('', () => { + it('should render', () => { + const byocComponentStub = stub(BYOCComponent, 'BYOCComponent').callsFake(() =>

Foo

); + const mockProps = { + params: { + ComponentName: 'xxx', + ComponentProps: JSON.stringify({ prop1: 'value1' }), + RenderingIdentifier: 'foo-id', + styles: 'bar car ', + }, + }; + const wrapper = mount(); + + const byocComponent = wrapper.find('BYOCComponent'); + expect(byocComponent).to.have.lengthOf(1); + const props = byocComponent.props() as BYOCComponent.BYOCComponentProps; + expect(props.params).to.deep.equal({ + ComponentName: 'xxx', + ComponentProps: JSON.stringify({ prop1: 'value1' }), + RenderingIdentifier: 'foo-id', + styles: 'bar car ', + }); + + const root = wrapper.find('.bar'); + expect(root).to.have.lengthOf(1); + expect(root.props().className).to.equal('bar car'); + expect(root.props().id).to.equal('foo-id'); + + byocComponentStub.restore(); + }); +}); diff --git a/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/components/BYOCWrapper.tsx b/packages/sitecore-jss-react/src/components/BYOCWrapper.tsx similarity index 61% rename from packages/create-sitecore-jss/src/templates/nextjs-sxa/src/components/BYOCWrapper.tsx rename to packages/sitecore-jss-react/src/components/BYOCWrapper.tsx index 89f3cf9885..7bca3572aa 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/components/BYOCWrapper.tsx +++ b/packages/sitecore-jss-react/src/components/BYOCWrapper.tsx @@ -1,7 +1,9 @@ -import { BYOCComponentProps, BYOCComponent } from '@sitecore-jss/sitecore-jss-nextjs'; +import { BYOCComponentProps, BYOCComponent } from './BYOCComponent'; import React from 'react'; -export const Default = (props: BYOCComponentProps): JSX.Element => { +export const BYOC_WRAPPER_RENDERING_NAME = 'BYOCWrapper'; + +export const BYOCWrapper = (props: BYOCComponentProps): JSX.Element => { const styles = props.params?.styles?.trimEnd(); const id = props.params?.RenderingIdentifier; diff --git a/packages/sitecore-jss-react/src/components/FEaaSWrapper.test.tsx b/packages/sitecore-jss-react/src/components/FEaaSWrapper.test.tsx new file mode 100644 index 0000000000..e96057ffac --- /dev/null +++ b/packages/sitecore-jss-react/src/components/FEaaSWrapper.test.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { stub } from 'sinon'; +import { expect } from 'chai'; +import { mount } from 'enzyme'; +import { ComponentFields } from '@sitecore-jss/sitecore-jss/layout'; +import { FEaaSWrapper } from './FEaaSWrapper'; +import * as FEaaSComponent from './FEaaSComponent'; + +describe('', () => { + const params: FEaaSComponent.FEaaSComponentParams = { + LibraryId: 'library123', + ComponentId: 'component123', + ComponentVersion: 'version123', + ComponentRevision: 'staged', + ComponentHostName: 'host123', + RenderingIdentifier: 'foo-id', + styles: 'foo bar ', + }; + + const fields: ComponentFields = { + sampleText: { + value: 'Welcome-to-Sitecore-JSS', + }, + }; + + const fetchedData = { + foo: 'bar', + baz: 42, + }; + + it('should render', () => { + const feaasComponentStub = stub(FEaaSComponent, 'FEaaSComponent').callsFake(() =>

Foo

); + + const mockProps: FEaaSComponent.FEaaSComponentProps = { + params, + fields, + fetchedData, + }; + const wrapper = mount(); + + const feaasComponent = wrapper.find('FEaaSComponent'); + expect(feaasComponent).to.have.lengthOf(1); + const props = feaasComponent.props() as FEaaSComponent.FEaaSComponentProps; + expect(props.params).to.deep.equal({ + LibraryId: 'library123', + ComponentId: 'component123', + ComponentVersion: 'version123', + ComponentRevision: 'staged', + ComponentHostName: 'host123', + RenderingIdentifier: 'foo-id', + styles: 'foo bar ', + }); + expect(props.fields).to.deep.equal({ + sampleText: { + value: 'Welcome-to-Sitecore-JSS', + }, + }); + expect(props.fetchedData).to.deep.equal({ + foo: 'bar', + baz: 42, + }); + + const root = wrapper.find('.bar'); + expect(root).to.have.lengthOf(1); + expect(root.props().className).to.equal('component feaas foo bar'); + expect(root.props().id).to.equal('foo-id'); + + feaasComponentStub.restore(); + }); +}); diff --git a/packages/sitecore-jss-react/src/components/FEaaSWrapper.tsx b/packages/sitecore-jss-react/src/components/FEaaSWrapper.tsx new file mode 100644 index 0000000000..267b22c95d --- /dev/null +++ b/packages/sitecore-jss-react/src/components/FEaaSWrapper.tsx @@ -0,0 +1,16 @@ +import { FEaaSComponent, FEaaSComponentProps } from './FEaaSComponent'; +import React from 'react'; + +export const FEAAS_WRAPPER_RENDERING_NAME = 'FEaaSWrapper'; + +export const FEaaSWrapper = (props: FEaaSComponentProps): JSX.Element => { + const styles = `component feaas ${props.params?.styles}`.trimEnd(); + const id = props.params?.RenderingIdentifier; + return ( +
+
+ +
+
+ ); +}; diff --git a/packages/sitecore-jss-react/src/components/Placeholder.test.tsx b/packages/sitecore-jss-react/src/components/Placeholder.test.tsx index ffc3ddaf0f..00351e7b36 100644 --- a/packages/sitecore-jss-react/src/components/Placeholder.test.tsx +++ b/packages/sitecore-jss-react/src/components/Placeholder.test.tsx @@ -4,6 +4,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import React from 'react'; import PropTypes from 'prop-types'; +import { stub } from 'sinon'; import { expect } from 'chai'; import { shallow, mount } from 'enzyme'; import { ComponentRendering, RouteData } from '@sitecore-jss/sitecore-jss/layout'; @@ -18,11 +19,17 @@ import { sxaRenderingVariantData, sxaRenderingVariantDataWithCommonContainerName as sxaRenderingCommonContainerName, sxaRenderingVariantDataWithoutCommonContainerName as sxaRenderingWithoutContainerName, + byocWrapperData, + feaasWrapperData, } from '../test-data/non-ee-data'; import { convertedData as eeData, emptyPlaceholderData } from '../test-data/ee-data'; import * as SxaRichText from '../test-data/sxa-rich-text'; import { MissingComponent, MissingComponentProps } from './MissingComponent'; import { HiddenRendering } from './HiddenRendering'; +import * as BYOCComponent from './BYOCComponent'; +import * as BYOCWrapper from './BYOCWrapper'; +import * as FEAASComponent from './FEaaSComponent'; +import * as FEAASWrapper from './FEaaSWrapper'; const componentFactory: ComponentFactory = (componentName: string) => { const components = new Map(); @@ -364,6 +371,72 @@ describe('', () => { }); }); + describe('BYOC fallback', () => { + let byocComponentStub; + let byocWrapperStub; + + const componentFactory: ComponentFactory = (_componentName: string, _exportName?: string) => + null; + + it('should render', () => { + const component = byocWrapperData.sitecore.route as RouteData; + const phKey = 'main'; + + byocComponentStub = stub(BYOCComponent, 'BYOCComponent').callsFake(() => ( +

Foo

+ )); + + byocWrapperStub = stub(BYOCWrapper, 'BYOCWrapper').callsFake(() => ( +
+ +
+ )); + + const renderedComponent = mount( + + ); + + expect(renderedComponent.find('.byoc-component').length).to.equal(2); + expect(renderedComponent.find('.byoc-wrapper').length).to.equal(1); + + byocComponentStub.restore(); + byocWrapperStub.restore(); + }); + }); + + describe('FEaaS fallback', () => { + let feaasComponentStub; + let feaasWrapperStub; + + const componentFactory: ComponentFactory = (_componentName: string, _exportName?: string) => + null; + + it('should render', () => { + const component = feaasWrapperData.sitecore.route as RouteData; + const phKey = 'main'; + + feaasComponentStub = stub(FEAASComponent, 'FEaaSComponent').callsFake(() => ( +

Foo

+ )); + + feaasWrapperStub = stub(FEAASWrapper, 'FEaaSWrapper').callsFake(() => ( +
+ +
+ )); + + const renderedComponent = mount( + + ); + + expect(renderedComponent.find('.feaas-component').length).to.equal(2); + expect(renderedComponent.find('.feaas-wrapper').length).to.equal(1); + + feaasComponentStub.restore(); + feaasWrapperStub.restore(); + }); + }); + it('should populate the "key" attribute of placeholder chrome', () => { const component: any = eeData.sitecore.route; const phKey = 'main'; diff --git a/packages/sitecore-jss-react/src/components/PlaceholderCommon.tsx b/packages/sitecore-jss-react/src/components/PlaceholderCommon.tsx index 3626e080a4..a7f2bd0390 100644 --- a/packages/sitecore-jss-react/src/components/PlaceholderCommon.tsx +++ b/packages/sitecore-jss-react/src/components/PlaceholderCommon.tsx @@ -12,6 +12,9 @@ import { import { convertAttributesToReactProps } from '../utils'; import { HiddenRendering, HIDDEN_RENDERING_NAME } from './HiddenRendering'; import { FEaaSComponent, FEAAS_COMPONENT_RENDERING_NAME } from './FEaaSComponent'; +import { FEaaSWrapper, FEAAS_WRAPPER_RENDERING_NAME } from './FEaaSWrapper'; +import { BYOCComponent, BYOC_COMPONENT_RENDERING_NAME } from './BYOCComponent'; +import { BYOCWrapper, BYOC_WRAPPER_RENDERING_NAME } from './BYOCWrapper'; /** * These patterns need for right rendering Dynamic placeholders. @@ -217,8 +220,17 @@ export class PlaceholderCommon extends React.Compone component = this.getComponentForRendering(componentRendering); } - if (componentRendering.componentName === FEAAS_COMPONENT_RENDERING_NAME) { - component = FEaaSComponent; + // Fallback/defaults for Sitecore Component renderings (in case not defined in component factory) + if (!component) { + if (componentRendering.componentName === FEAAS_COMPONENT_RENDERING_NAME) { + component = FEaaSComponent; + } else if (componentRendering.componentName === FEAAS_WRAPPER_RENDERING_NAME) { + component = FEaaSWrapper; + } else if (componentRendering.componentName === BYOC_COMPONENT_RENDERING_NAME) { + component = BYOCComponent; + } else if (componentRendering.componentName === BYOC_WRAPPER_RENDERING_NAME) { + component = BYOCWrapper; + } } if (!component) { diff --git a/packages/sitecore-jss-react/src/index.ts b/packages/sitecore-jss-react/src/index.ts index 4e166e2bdc..e3d3dbaffe 100644 --- a/packages/sitecore-jss-react/src/index.ts +++ b/packages/sitecore-jss-react/src/index.ts @@ -63,7 +63,9 @@ export { FEaaSComponentParams, fetchFEaaSComponentServerProps, } from './components/FEaaSComponent'; +export { FEaaSWrapper } from './components/FEaaSWrapper'; export { BYOCComponent, BYOCComponentParams, BYOCComponentProps } from './components/BYOCComponent'; +export { BYOCWrapper } from './components/BYOCWrapper'; export { Link, LinkField, LinkFieldValue, LinkProps, LinkPropTypes } from './components/Link'; export { File, FileField } from './components/File'; export { VisitorIdentification } from './components/VisitorIdentification'; diff --git a/packages/sitecore-jss-react/src/test-data/non-ee-data.ts b/packages/sitecore-jss-react/src/test-data/non-ee-data.ts index 7e49ac785b..b82de9796f 100644 --- a/packages/sitecore-jss-react/src/test-data/non-ee-data.ts +++ b/packages/sitecore-jss-react/src/test-data/non-ee-data.ts @@ -369,3 +369,105 @@ export const sxaRenderingColumnSplitterVariant = { }, }, }; + +export const byocWrapperData = { + sitecore: { + context: { + pageEditing: false, + }, + route: { + name: 'Home', + displayName: 'Home', + fields: { + key: { + value: 'This is a some sample <p>field data</p> o'boy! "wow"', + }, + }, + placeholders: { + main: [ + { + uid: '278b99a7-8d73-4362-ac05-53e7c35154d5', + componentName: 'BYOCWrapper', + dataSource: '', + params: { + ComponentName: 'Foo', + ComponentProps: '{ "columns": 7 }', + GridParameters: 'col-12', + DynamicPlaceholderId: '1', + FieldNames: 'Default', + }, + }, + { + uid: '278b99a7-8d73-4362-ac05-53e7c35154d5', + componentName: 'BYOCComponent', + dataSource: '', + params: { + ComponentName: 'Bar', + ComponentProps: '{ "columns": 5 }', + GridParameters: 'col-12', + DynamicPlaceholderId: '1', + FieldNames: 'Default', + }, + }, + ], + }, + }, + }, +}; + +export const feaasWrapperData = { + sitecore: { + context: { + pageEditing: false, + }, + route: { + name: 'Home', + displayName: 'Home', + fields: { + key: { + value: 'This is a some sample <p>field data</p> o'boy! "wow"', + }, + }, + placeholders: { + main: [ + { + uid: 'd07d1832-f2a1-4f56-a949-5d1ab263a1d5', + componentName: 'FEaaSWrapper', + dataSource: '', + params: { + ComponentName: 'Two product teaser', + LibraryId: '4lcTPh6h5L4soeuM0WkXtf', + ComponentId: 'bkpRNHFB2v', + ComponentVersion: 'responsive', + ComponentRevision: 'staged', + ComponentHostName: 'https://feaas.windows.net', + ComponentInstanceId: 'bSE5grxVRMCVB0K', + GridParameters: 'col-12', + CacheClearingBehavior: 'Clear on publish', + DynamicPlaceholderId: '2', + FieldNames: 'Default', + }, + }, + { + uid: 'd07d1832-f2a1-4f56-a949-5d1ab263a1d5', + componentName: 'FEaaSComponent', + dataSource: '', + params: { + ComponentName: 'One product teaser', + LibraryId: '4lcTPh6h5L4soeuM0WkXtf', + ComponentId: 'bkpRNHFB2v', + ComponentVersion: 'responsive', + ComponentRevision: 'staged', + ComponentHostName: 'https://feaas.windows.net', + ComponentInstanceId: 'bSE5grxVRMCVB0K', + GridParameters: 'col-9', + CacheClearingBehavior: 'Clear on publish', + DynamicPlaceholderId: '2', + FieldNames: 'Default', + }, + }, + ], + }, + }, + }, +}; diff --git a/yarn.lock b/yarn.lock index 894fe47c52..5167d25b19 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6446,12 +6446,12 @@ __metadata: languageName: node linkType: hard -"@sitecore-feaas/clientside@npm:^0.3.12": - version: 0.3.12 - resolution: "@sitecore-feaas/clientside@npm:0.3.12" +"@sitecore-feaas/clientside@npm:^0.3.14": + version: 0.3.14 + resolution: "@sitecore-feaas/clientside@npm:0.3.14" peerDependencies: react-dom: ">=16.8.0" - checksum: de095e6dd0d38456789a913c0443c626f206b05e376d8803b8ac3595fc5f2e67b7a4184fcadeeb0987e194e05eb0626bc13c80e5d2b40882db12cc658cdac334 + checksum: e793a475862dfb4d9aaeff1e74745578c8eb2fe168ea85ac133a1f7b20f63c225ad2e82b536377e1b63dfc8f03e6a5d83f24217e89b03d035d329a0356f492aa languageName: node linkType: hard @@ -6779,7 +6779,7 @@ __metadata: version: 0.0.0-use.local resolution: "@sitecore-jss/sitecore-jss-react@workspace:packages/sitecore-jss-react" dependencies: - "@sitecore-feaas/clientside": ^0.3.12 + "@sitecore-feaas/clientside": ^0.3.14 "@sitecore-jss/sitecore-jss": 21.3.0-canary.51 "@types/chai": ^4.3.4 "@types/chai-string": ^1.4.2 @@ -6813,6 +6813,7 @@ __metadata: ts-node: ^10.9.1 typescript: ~4.9.3 peerDependencies: + "@sitecore-feaas/clientside": ^0.3.14 react: ^18.2.0 react-dom: ^18.2.0 languageName: unknown