From d72bf8c588a009e15b95b76476230bb98d5071c0 Mon Sep 17 00:00:00 2001 From: illiakovalenko Date: Fri, 27 Oct 2023 12:44:15 +0300 Subject: [PATCH 1/3] [sitecore-jss] [FEAAS] Load stylesheets with correct revision staged/published --- .../sitecore-jss/src/feaas/themes.test.ts | 641 ++++++++++-------- packages/sitecore-jss/src/feaas/themes.ts | 25 +- 2 files changed, 368 insertions(+), 298 deletions(-) diff --git a/packages/sitecore-jss/src/feaas/themes.test.ts b/packages/sitecore-jss/src/feaas/themes.test.ts index d83e32e4f1..441e1d14e9 100644 --- a/packages/sitecore-jss/src/feaas/themes.test.ts +++ b/packages/sitecore-jss/src/feaas/themes.test.ts @@ -1,13 +1,18 @@ import { expect } from 'chai'; import { FEAAS_SERVER_URL, getFEAASLibraryStylesheetLinks, getStylesheetUrl } from './themes'; -import { ComponentRendering, HtmlElementRendering } from '../layout'; +import { ComponentRendering, HtmlElementRendering, LayoutServicePageState } from '../layout'; describe('utils', () => { describe('getFEAASLibraryStylesheetLinks', () => { - const setBasicLayoutData = (component: ComponentRendering | HtmlElementRendering) => { + const setBasicLayoutData = ( + component: ComponentRendering | HtmlElementRendering, + pageState?: LayoutServicePageState + ) => { return { sitecore: { - context: {}, + context: { + pageState, + }, route: { name: 'home', placeholders: { @@ -63,370 +68,416 @@ describe('utils', () => { ).to.deep.equal([{ href: getStylesheetUrl('bar'), rel: 'style' }]); }); - describe('normal mode', () => { - it('should return links using CSSStyles param', () => { - expect( - getFEAASLibraryStylesheetLinks( - setBasicLayoutData({ - componentName: 'styled', - params: { - CSSStyles: '-library--foo', - }, - }) - ) - ).to.deep.equal([{ href: getStylesheetUrl('foo'), rel: 'style' }]); - }); + it('should return links using CSSStyles param', () => { + expect( + getFEAASLibraryStylesheetLinks( + setBasicLayoutData({ + componentName: 'styled', + params: { + CSSStyles: '-library--foo', + }, + }) + ) + ).to.deep.equal([{ href: getStylesheetUrl('foo'), rel: 'style' }]); + }); - it('should return links using LibraryId param', () => { - expect( - getFEAASLibraryStylesheetLinks( - setBasicLayoutData({ - componentName: 'styled', - params: { - LibraryId: 'bar', - }, - }) - ) - ).to.deep.equal([{ href: getStylesheetUrl('bar'), rel: 'style' }]); - }); + it('should return links using LibraryId param', () => { + expect( + getFEAASLibraryStylesheetLinks( + setBasicLayoutData({ + componentName: 'styled', + params: { + LibraryId: 'bar', + }, + }) + ) + ).to.deep.equal([{ href: getStylesheetUrl('bar'), rel: 'style' }]); + }); - it('should return prefer params over fields', () => { - expect( - getFEAASLibraryStylesheetLinks( - setBasicLayoutData({ - componentName: 'styled', - params: { - CSSStyles: '-library--foo', - }, - fields: { - CSSStyles: { - value: '-library--not-foo', - }, + it('should return prefer params over fields', () => { + expect( + getFEAASLibraryStylesheetLinks( + setBasicLayoutData({ + componentName: 'styled', + params: { + CSSStyles: '-library--foo', + }, + fields: { + CSSStyles: { + value: '-library--not-foo', }, - }) - ) - ).to.deep.equal([{ href: getStylesheetUrl('foo'), rel: 'style' }]); + }, + }) + ) + ).to.deep.equal([{ href: getStylesheetUrl('foo'), rel: 'style' }]); - expect( - getFEAASLibraryStylesheetLinks( - setBasicLayoutData({ - componentName: 'styled', - params: { - LibraryId: 'bar', - }, - fields: { - LibraryId: { - value: 'not-bar', - }, + expect( + getFEAASLibraryStylesheetLinks( + setBasicLayoutData({ + componentName: 'styled', + params: { + LibraryId: 'bar', + }, + fields: { + LibraryId: { + value: 'not-bar', }, - }) - ) - ).to.deep.equal([{ href: getStylesheetUrl('bar'), rel: 'style' }]); - }); + }, + }) + ) + ).to.deep.equal([{ href: getStylesheetUrl('bar'), rel: 'style' }]); + }); - it('should read LibraryId from class when matching param or field is not found', () => { - expect( - getFEAASLibraryStylesheetLinks( - setBasicLayoutData({ - componentName: 'styled', - params: { - NotCSSStyles: '-library--not-foo', - NotLibraryId: 'not-foo', - }, - fields: { - NotCSSStyles: { - value: '-library--not-foo', - }, - NotLibraryId: { - value: 'not-foo', - }, + it('should read LibraryId from class when matching param or field is not found', () => { + expect( + getFEAASLibraryStylesheetLinks( + setBasicLayoutData({ + componentName: 'styled', + params: { + NotCSSStyles: '-library--not-foo', + NotLibraryId: 'not-foo', + }, + fields: { + NotCSSStyles: { + value: '-library--not-foo', }, - attributes: { - class: '-library--foo', + NotLibraryId: { + value: 'not-foo', }, - }) - ) - ).to.deep.equal([{ href: getStylesheetUrl('foo'), rel: 'style' }]); - }); + }, + attributes: { + class: '-library--foo', + }, + }) + ) + ).to.deep.equal([{ href: getStylesheetUrl('foo'), rel: 'style' }]); + }); - it('should return links using custom server url', () => { - expect( - getFEAASLibraryStylesheetLinks( - setBasicLayoutData({ - componentName: 'test', - fields: { - LibraryId: { - value: 'bar', - }, + it('should return links using custom server url', () => { + expect( + getFEAASLibraryStylesheetLinks( + setBasicLayoutData({ + componentName: 'test', + fields: { + LibraryId: { + value: 'bar', }, - }), - 'https://foo.net' - ) - ).to.deep.equal([{ href: getStylesheetUrl('bar', 'https://foo.net'), rel: 'style' }]); - }); + }, + }), + 'https://foo.net' + ) + ).to.deep.equal([ + { + href: getStylesheetUrl('bar', LayoutServicePageState.Normal, 'https://foo.net'), + rel: 'style', + }, + ]); + }); - it('should return empty links array when required fields are not provided', () => { - expect( - getFEAASLibraryStylesheetLinks({ - sitecore: { - context: {}, - route: { - name: 'home', - fields: {}, - placeholders: {}, - }, + it('should return empty links array when required fields are not provided', () => { + expect( + getFEAASLibraryStylesheetLinks({ + sitecore: { + context: {}, + route: { + name: 'home', + fields: {}, + placeholders: {}, }, - }) - ).to.deep.equal([]); - }); + }, + }) + ).to.deep.equal([]); + }); - it('should return empty links array when required params are not provided', () => { - expect( - getFEAASLibraryStylesheetLinks( - setBasicLayoutData({ - componentName: 'styled', - params: {}, - }) - ) - ).to.deep.equal([]); - }); + it('should return empty links array when required params are not provided', () => { + expect( + getFEAASLibraryStylesheetLinks( + setBasicLayoutData({ + componentName: 'styled', + params: {}, + }) + ) + ).to.deep.equal([]); + }); - it('should traverse nested nodes and return only unique links', () => { - expect( - getFEAASLibraryStylesheetLinks({ - sitecore: { - context: {}, - route: { - name: 'home', - fields: { - CSSStyles: { - value: '-library--foo', - }, - LibraryId: { - value: 'bar', - }, + it('should traverse nested nodes and return only unique links', () => { + expect( + getFEAASLibraryStylesheetLinks({ + sitecore: { + context: {}, + route: { + name: 'home', + fields: { + CSSStyles: { + value: '-library--foo', }, - placeholders: { - x: [ - { - componentName: 'x1-component', - fields: { - LibraryId: { - value: 'foo', - }, + LibraryId: { + value: 'bar', + }, + }, + placeholders: { + x: [ + { + componentName: 'x1-component', + fields: { + LibraryId: { + value: 'foo', }, - placeholders: { - x1: [ - { - componentName: 'x11-component', - fields: { - CSSStyles: { - value: '-library--x11', - }, + }, + placeholders: { + x1: [ + { + componentName: 'x11-component', + fields: { + CSSStyles: { + value: '-library--x11', }, }, - { - componentName: 'x12-component', - fields: { - CSSStyles: { - value: '-library--x12', - }, - LibraryId: { - value: 'x12-id', - }, + }, + { + componentName: 'x12-component', + fields: { + CSSStyles: { + value: '-library--x12', + }, + LibraryId: { + value: 'x12-id', }, }, - ], - x2: [ - { - componentName: 'x21-component', - fields: { - LibraryId: { - value: 'x21', - }, + }, + ], + x2: [ + { + componentName: 'x21-component', + fields: { + LibraryId: { + value: 'x21', }, }, - ], - }, - }, - ], - y: [ - { - componentName: 'y1-component', - fields: { - LibraryId: { - value: 'y1', }, + ], + }, + }, + ], + y: [ + { + componentName: 'y1-component', + fields: { + LibraryId: { + value: 'y1', }, }, - { - componentName: 'y2-component', - fields: { - CSSStyles: { - value: 'custom-style', - }, - LibraryId: { - value: 'y2', - }, + }, + { + componentName: 'y2-component', + fields: { + CSSStyles: { + value: 'custom-style', + }, + LibraryId: { + value: 'y2', }, }, - ], - z: [ - { - componentName: 'z1-component', - fields: { - CSSStyles: { - value: '-library--z1', - }, + }, + ], + z: [ + { + componentName: 'z1-component', + fields: { + CSSStyles: { + value: '-library--z1', }, - placeholders: { - z1: [ - { - componentName: 'z11-component', - fields: { - CSSStyles: { - value: '-library--z11', - }, + }, + placeholders: { + z1: [ + { + componentName: 'z11-component', + fields: { + CSSStyles: { + value: '-library--z11', }, }, - ], - z2: [ - { - componentName: 'z21-component', - fields: { - LibraryId: { - value: 'z21', - }, + }, + ], + z2: [ + { + componentName: 'z21-component', + fields: { + LibraryId: { + value: 'z21', }, }, - ], - }, + }, + ], }, - ], - }, + }, + ], }, }, - }) - ).to.deep.equal( - ['foo', 'x11', 'x12', 'x21', 'y1', 'y2', 'z1', 'z11', 'z21'].map((id) => ({ - href: getStylesheetUrl(id), - rel: 'style', - })) - ); - }); + }, + }) + ).to.deep.equal( + ['foo', 'x11', 'x12', 'x21', 'y1', 'y2', 'z1', 'z11', 'z21'].map((id) => ({ + href: getStylesheetUrl(id), + rel: 'style', + })) + ); }); + }); - describe('editing mode', () => { - it('should return links using class attribute', () => { - expect( - getFEAASLibraryStylesheetLinks( - setBasicLayoutData({ + describe('editing mode', () => { + it('should return links using class attribute', () => { + expect( + getFEAASLibraryStylesheetLinks( + setBasicLayoutData( + { name: 'foo-component', contents: null, attributes: { class: '-library--bar', }, - }) + }, + LayoutServicePageState.Edit ) - ).to.deep.equal([{ href: getStylesheetUrl('bar'), rel: 'style' }]); - }); + ) + ).to.deep.equal([ + { href: getStylesheetUrl('bar', LayoutServicePageState.Edit), rel: 'style' }, + ]); + }); - it('should return links using custom server url', () => { - expect( - getFEAASLibraryStylesheetLinks( - setBasicLayoutData({ + it('should return links using custom server url', () => { + expect( + getFEAASLibraryStylesheetLinks( + setBasicLayoutData( + { name: 'foo-component', contents: null, attributes: { class: '-library--bar', }, - }), - 'https://foo.net' - ) - ).to.deep.equal([{ rel: 'style', href: getStylesheetUrl('bar', 'https://foo.net') }]); - }); + }, + LayoutServicePageState.Edit + ), + 'https://foo.net' + ) + ).to.deep.equal([ + { + rel: 'style', + href: getStylesheetUrl('bar', LayoutServicePageState.Edit, 'https://foo.net'), + }, + ]); + }); - it('should not return id when class does not match pattern', () => { - expect( - getFEAASLibraryStylesheetLinks( - setBasicLayoutData({ + it('should not return id when class does not match pattern', () => { + expect( + getFEAASLibraryStylesheetLinks( + setBasicLayoutData( + { name: 'foo-component', contents: null, attributes: { class: 'bar', }, - }) + }, + LayoutServicePageState.Edit ) - ).to.deep.equal([]); - }); + ) + ).to.deep.equal([]); + }); - it('should return only unique links', () => { - expect( - getFEAASLibraryStylesheetLinks({ - sitecore: { - context: {}, - route: { - name: 'home', - placeholders: { - x: [ - { - name: 'x1-component', - contents: null, - attributes: { - class: '-library--x1', - }, + it('should return only unique links', () => { + expect( + getFEAASLibraryStylesheetLinks({ + sitecore: { + context: { + pageState: LayoutServicePageState.Edit, + }, + route: { + name: 'home', + placeholders: { + x: [ + { + name: 'x1-component', + contents: null, + attributes: { + class: '-library--x1', }, - ], - y: [ - { - name: 'x2-component', - contents: null, - attributes: { - class: '-library--x1', - }, + }, + ], + y: [ + { + name: 'x2-component', + contents: null, + attributes: { + class: '-library--x1', }, - { - name: 'y1-component', - contents: null, - attributes: { - class: '-library--y1', - }, + }, + { + name: 'y1-component', + contents: null, + attributes: { + class: '-library--y1', }, - ], - z: [ - { - name: 'z-component', - contents: null, - attributes: { - class: '-library--z1', - }, + }, + ], + z: [ + { + name: 'z-component', + contents: null, + attributes: { + class: '-library--z1', }, - { - name: 'z-component', - contents: null, - attributes: { - class: '-library--z2', - }, + }, + { + name: 'z-component', + contents: null, + attributes: { + class: '-library--z2', }, - ], - }, + }, + ], }, }, - }) - ).to.deep.equal( - ['x1', 'y1', 'z1', 'z2'].map((id) => ({ rel: 'style', href: getStylesheetUrl(id) })) - ); - }); + }, + }) + ).to.deep.equal( + ['x1', 'y1', 'z1', 'z2'].map((id) => ({ + rel: 'style', + href: getStylesheetUrl(id, LayoutServicePageState.Edit), + })) + ); }); }); describe('getStylesheetUrl', () => { it('should return stylesheet url using default server url', () => { - expect(getStylesheetUrl('foo')).to.equal(`${FEAAS_SERVER_URL}/styles/foo/published.css`); + const pageState = LayoutServicePageState.Normal; + + expect(getStylesheetUrl('foo', pageState)).to.equal( + `${FEAAS_SERVER_URL}/styles/foo/published.css` + ); + }); + + it('should return stylesheet url using default server url in Edit mode', () => { + const pageState = LayoutServicePageState.Edit; + + expect(getStylesheetUrl('foo', pageState)).to.equal( + `${FEAAS_SERVER_URL}/styles/foo/staged.css` + ); + }); + + it('should return stylesheet url using default server url in Preview mode', () => { + const pageState = LayoutServicePageState.Preview; + + expect(getStylesheetUrl('foo', pageState)).to.equal( + `${FEAAS_SERVER_URL}/styles/foo/staged.css` + ); }); it('should return stylesheet url using custom server url', () => { - expect(getStylesheetUrl('foo', 'https://bar.net')).to.equal( + const pageState = LayoutServicePageState.Normal; + + expect(getStylesheetUrl('foo', pageState, 'https://bar.net')).to.equal( 'https://bar.net/styles/foo/published.css' ); }); diff --git a/packages/sitecore-jss/src/feaas/themes.ts b/packages/sitecore-jss/src/feaas/themes.ts index ce27e2ce8e..7314000b12 100644 --- a/packages/sitecore-jss/src/feaas/themes.ts +++ b/packages/sitecore-jss/src/feaas/themes.ts @@ -2,11 +2,19 @@ import { ComponentRendering, HtmlElementRendering, LayoutServiceData, + LayoutServicePageState, RouteData, getFieldValue, } from '../layout'; import { HTMLLink } from '../models'; +/** + * Stylesheets revision type + * 'staged': Editing/Preview + * 'published': Normal + */ +type RevisionType = 'staged' | 'published'; + /** * Pattern for library ids * @example -library--foo @@ -31,11 +39,22 @@ export function getFEAASLibraryStylesheetLinks( traverseComponent(layoutData.sitecore.route, ids); - return [...ids].map((id) => ({ href: getStylesheetUrl(id, serverUrl), rel: 'style' })); + return [...ids].map((id) => ({ + href: getStylesheetUrl(id, layoutData.sitecore.context.pageState, serverUrl), + rel: 'style', + })); } -export const getStylesheetUrl = (id: string, serverUrl?: string) => - `${serverUrl || FEAAS_SERVER_URL}/styles/${id}/published.css`; +export const getStylesheetUrl = ( + id: string, + pageState?: LayoutServicePageState, + serverUrl?: string +) => { + const revision: RevisionType = + pageState && pageState !== LayoutServicePageState.Normal ? 'staged' : 'published'; + + return `${serverUrl || FEAAS_SERVER_URL}/styles/${id}/${revision}.css`; +}; /** * Traverse placeholder and components to add library ids From 946625dee42a55271e37c0e6900f4938a89f937c Mon Sep 17 00:00:00 2001 From: illiakovalenko Date: Fri, 27 Oct 2023 12:50:14 +0300 Subject: [PATCH 2/3] CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c88f1a1ea3..0e2f9d2538 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Our versioning strategy is as follows: ### 🎉 New Features & Improvements +* `[sitecore-jss]` Support for both 'published' and 'staged' revisions of FEAAS stylesheets/themes ([#1644](https://github.com/Sitecore/jss/pull/1644)) * `[templates/nextjs]` Enable client-only BYOC component imports. Client-only components can be imported through src/byoc/index.client.ts. Hybrid (server render with client hydration) components can be imported through src/byoc/index.hybrid.ts. BYOC scaffold logic is also moved from nextjs-sxa addon into base template ([#1628](https://github.com/Sitecore/jss/pull/1628)[#1636](https://github.com/Sitecore/jss/pull/1636)) * `[templates/nextjs]` Import SitecoreForm component into sample nextjs app ([#1628](https://github.com/Sitecore/jss/pull/1628)) * `[sitecore-jss-nextjs]` (Vercel/Sitecore) Deployment Protection Bypass support for editing integration. ([#1634](https://github.com/Sitecore/jss/pull/1634)) From 5d657689485ef7e80d41c265694bd17f4250d829 Mon Sep 17 00:00:00 2001 From: illiakovalenko Date: Fri, 27 Oct 2023 12:51:00 +0300 Subject: [PATCH 3/3] Update --- packages/sitecore-jss/src/feaas/themes.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sitecore-jss/src/feaas/themes.ts b/packages/sitecore-jss/src/feaas/themes.ts index 7314000b12..8d7c822094 100644 --- a/packages/sitecore-jss/src/feaas/themes.ts +++ b/packages/sitecore-jss/src/feaas/themes.ts @@ -10,8 +10,8 @@ import { HTMLLink } from '../models'; /** * Stylesheets revision type - * 'staged': Editing/Preview - * 'published': Normal + * 'staged': Editing/Preview mode + * 'published': Normal mode */ type RevisionType = 'staged' | 'published';