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

Move SlotBodyEnd, StickyBottomBanner & ReaderRevenueLinks to Islands #4126

Merged
merged 38 commits into from
Mar 15, 2022

Conversation

mxdvl
Copy link
Contributor

@mxdvl mxdvl commented Mar 1, 2022

What does this change?

Migrates StickyBottomBanner , SlotBodyEnd & ReaderRevenueLinks into their own respective islands. These three components need moved together as they had shared dependencies that could not run in both Islands and App.tsx.

To support the move to the Islands pattern, we introduce useBraze, a hook which makes use of useSWRImmutable to ensure we initialise braze only once per page.

This PR also introduces the BrazeMessaging Island, a canonical usage of useBraze to ensure that braze is properly initialised on every page.

Small improvements

  • fix: remove getCmp - CMP is now server safe!
  • fix: import type explicitly

Why?

Islands are the new way: #3629 🏝️

@mxdvl mxdvl requested a review from OllysCoding March 1, 2022 17:07
@mxdvl mxdvl requested review from a team, JamieB-gu and marsavar as code owners March 1, 2022 17:07
@github-actions github-actions bot added the dotcom label Mar 1, 2022
@github-actions
Copy link

github-actions bot commented Mar 1, 2022

Size Change: -204 kB (-12%) 👏

Total Size: 1.51 MB

Filename Size Change
dotcom-rendering/dist/1065.legacy.js 7.49 kB -1.92 kB (-20%) 🎉
dotcom-rendering/dist/1214.js 3.15 kB -2.41 kB (-43%) 🎉
dotcom-rendering/dist/1214.legacy.js 3.21 kB -2.46 kB (-43%) 🎉
dotcom-rendering/dist/1289.js 0 B -19.2 kB (removed) 🏆
dotcom-rendering/dist/1289.legacy.js 0 B -19.8 kB (removed) 🏆
dotcom-rendering/dist/1376.js 1.65 kB -246 B (-13%) 👏
dotcom-rendering/dist/1376.legacy.js 1.75 kB -260 B (-13%) 👏
dotcom-rendering/dist/1561.legacy.js 0 B -3.93 kB (removed) 🏆
dotcom-rendering/dist/1578.js 2.41 kB -2.4 kB (-50%) 🏆
dotcom-rendering/dist/1578.legacy.js 2.43 kB -2.46 kB (-50%) 🏆
dotcom-rendering/dist/1624.js 0 B -5.54 kB (removed) 🏆
dotcom-rendering/dist/1624.legacy.js 0 B -5.67 kB (removed) 🏆
dotcom-rendering/dist/1924.js 6.08 kB -2.39 kB (-28%) 🎉
dotcom-rendering/dist/1924.legacy.js 6.45 kB -2.44 kB (-27%) 🎉
dotcom-rendering/dist/2.js 4.87 kB +1.18 kB (+32%) 🚨
dotcom-rendering/dist/2.legacy.js 4.98 kB +1.24 kB (+33%) 🚨
dotcom-rendering/dist/2058.js 8.94 kB +1.15 kB (+15%) ⚠️
dotcom-rendering/dist/2058.legacy.js 9.21 kB +1.17 kB (+15%) ⚠️
dotcom-rendering/dist/23.js 5.84 kB -1 B (0%)
dotcom-rendering/dist/23.legacy.js 6.13 kB -1 B (0%)
dotcom-rendering/dist/2737.js 6.16 kB +131 B (+2%)
dotcom-rendering/dist/2737.legacy.js 6.51 kB +165 B (+3%)
dotcom-rendering/dist/2879.js 6.97 kB -1.81 kB (-21%) 🎉
dotcom-rendering/dist/293.js 0 B -4.51 kB (removed) 🏆
dotcom-rendering/dist/2949.js 5.66 kB +1.16 kB (+26%) 🚨
dotcom-rendering/dist/2949.legacy.js 5.86 kB +1.2 kB (+26%) 🚨
dotcom-rendering/dist/3213.js 14.2 kB +1.35 kB (+11%) ⚠️
dotcom-rendering/dist/3213.legacy.js 14.5 kB +1.31 kB (+10%) ⚠️
dotcom-rendering/dist/3215.js 6.11 kB +1.96 kB (+47%) 🚨
dotcom-rendering/dist/3215.legacy.js 6.26 kB +2.02 kB (+48%) 🚨
dotcom-rendering/dist/3270.js 7.64 kB +2.72 kB (+55%) 🆘
dotcom-rendering/dist/3270.legacy.js 7.83 kB +2.75 kB (+54%) 🆘
dotcom-rendering/dist/3475.js 0 B -4.25 kB (removed) 🏆
dotcom-rendering/dist/3475.legacy.js 0 B -4.36 kB (removed) 🏆
dotcom-rendering/dist/4025.js 2.02 kB +1 B (0%)
dotcom-rendering/dist/4206.js 0 B -4.56 kB (removed) 🏆
dotcom-rendering/dist/4206.legacy.js 0 B -4.66 kB (removed) 🏆
dotcom-rendering/dist/4211.js 1.65 kB -352 B (-18%) 👏
dotcom-rendering/dist/4211.legacy.js 1.74 kB -363 B (-17%) 👏
dotcom-rendering/dist/4510.js 0 B -3.49 kB (removed) 🏆
dotcom-rendering/dist/4813.js 0 B -5.24 kB (removed) 🏆
dotcom-rendering/dist/4813.legacy.js 0 B -5.37 kB (removed) 🏆
dotcom-rendering/dist/4846.js 0 B -4.71 kB (removed) 🏆
dotcom-rendering/dist/5095.legacy.js 0 B -4.68 kB (removed) 🏆
dotcom-rendering/dist/5096.js 4.73 kB +54 B (+1%)
dotcom-rendering/dist/5096.legacy.js 4.79 kB +55 B (+1%)
dotcom-rendering/dist/53.js 4.12 kB +1 B (0%)
dotcom-rendering/dist/53.legacy.js 4.12 kB +1 B (0%)
dotcom-rendering/dist/5356.js 33.5 kB -541 B (-2%)
dotcom-rendering/dist/5356.legacy.js 31.4 kB -581 B (-2%)
dotcom-rendering/dist/5442.legacy.js 0 B -4.63 kB (removed) 🏆
dotcom-rendering/dist/5585.js 6.35 kB -352 B (-5%)
dotcom-rendering/dist/5585.legacy.js 6.61 kB -360 B (-5%)
dotcom-rendering/dist/5690.js 6.23 kB +1.92 kB (+45%) 🚨
dotcom-rendering/dist/5690.legacy.js 6.4 kB +2 kB (+45%) 🚨
dotcom-rendering/dist/6045.js 3.6 kB +1 B (0%)
dotcom-rendering/dist/6074.legacy.js 0 B -4.81 kB (removed) 🏆
dotcom-rendering/dist/6292.legacy.js 4.78 kB +126 B (+3%)
dotcom-rendering/dist/6348.js 5.84 kB +1.8 kB (+45%) 🚨
dotcom-rendering/dist/6348.legacy.js 6.03 kB +1.87 kB (+45%) 🚨
dotcom-rendering/dist/6684.legacy.js 253 B +1 B (0%)
dotcom-rendering/dist/682.js 0 B -5.04 kB (removed) 🏆
dotcom-rendering/dist/682.legacy.js 0 B -5.45 kB (removed) 🏆
dotcom-rendering/dist/6965.js 3.66 kB -2.4 kB (-40%) 🎉
dotcom-rendering/dist/6965.legacy.js 3.72 kB -2.46 kB (-40%) 🎉
dotcom-rendering/dist/6992.js 1.67 kB -349 B (-17%) 👏
dotcom-rendering/dist/6992.legacy.js 1.76 kB -365 B (-17%) 👏
dotcom-rendering/dist/7051.js 8.29 kB +1.93 kB (+30%) 🚨
dotcom-rendering/dist/7051.legacy.js 8.49 kB +2.01 kB (+31%) 🚨
dotcom-rendering/dist/7236.js 0 B -4.35 kB (removed) 🏆
dotcom-rendering/dist/7269.legacy.js 0 B -4.87 kB (removed) 🏆
dotcom-rendering/dist/7393.js 0 B -3.78 kB (removed) 🏆
dotcom-rendering/dist/7401.js 0 B -4.67 kB (removed) 🏆
dotcom-rendering/dist/750.js 5.47 kB +104 B (+2%)
dotcom-rendering/dist/750.legacy.js 5.62 kB +138 B (+3%)
dotcom-rendering/dist/7583.js 6.56 kB +1.97 kB (+43%) 🚨
dotcom-rendering/dist/7583.legacy.js 6.73 kB +2.04 kB (+44%) 🚨
dotcom-rendering/dist/7631.js 0 B -6.64 kB (removed) 🏆
dotcom-rendering/dist/7631.legacy.js 0 B -6.82 kB (removed) 🏆
dotcom-rendering/dist/7749.js 4.43 kB +99 B (+2%)
dotcom-rendering/dist/7793.js 6.54 kB -1.6 kB (-20%) 🎉
dotcom-rendering/dist/7793.legacy.js 6.81 kB -1.66 kB (-20%) 🎉
dotcom-rendering/dist/7800.js 11.9 kB +680 B (+6%) 🔍
dotcom-rendering/dist/8080.js 1.46 kB -344 B (-19%) 👏
dotcom-rendering/dist/8080.legacy.js 1.54 kB -359 B (-19%) 👏
dotcom-rendering/dist/8129.legacy.js 12.4 kB +670 B (+6%) 🔍
dotcom-rendering/dist/824.legacy.js 483 B -1 B (0%)
dotcom-rendering/dist/8261.legacy.js 0 B -4.84 kB (removed) 🏆
dotcom-rendering/dist/8344.js 6.92 kB +1 B (0%)
dotcom-rendering/dist/8344.legacy.js 7.27 kB +1 B (0%)
dotcom-rendering/dist/8366.js 0 B -4.65 kB (removed) 🏆
dotcom-rendering/dist/8575.legacy.js 0 B -4.81 kB (removed) 🏆
dotcom-rendering/dist/8583.js 16.8 kB -1.01 kB (-6%)
dotcom-rendering/dist/8583.legacy.js 17.4 kB -1.01 kB (-5%)
dotcom-rendering/dist/8605.legacy.js 0 B -3.59 kB (removed) 🏆
dotcom-rendering/dist/8748.js 232 B -1 B (0%)
dotcom-rendering/dist/8748.legacy.js 243 B -1 B (0%)
dotcom-rendering/dist/8839.js 1.63 kB +1 B (0%)
dotcom-rendering/dist/8839.legacy.js 1.73 kB +1 B (0%)
dotcom-rendering/dist/9177.js 4.61 kB +60 B (+1%)
dotcom-rendering/dist/9177.legacy.js 4.63 kB +60 B (+1%)
dotcom-rendering/dist/927.js 0 B -4.65 kB (removed) 🏆
dotcom-rendering/dist/9327.js 1.51 kB -349 B (-19%) 👏
dotcom-rendering/dist/9327.legacy.js 1.58 kB -375 B (-19%) 👏
dotcom-rendering/dist/9641.js 6.33 kB -349 B (-5%)
dotcom-rendering/dist/9641.legacy.js 6.6 kB -360 B (-5%)
dotcom-rendering/dist/9776.js 6.25 kB -357 B (-5%)
dotcom-rendering/dist/9776.legacy.js 6.52 kB -370 B (-5%)
dotcom-rendering/dist/9817.js 11.8 kB -2.02 kB (-15%) 👏
dotcom-rendering/dist/9817.legacy.js 12.1 kB -2.25 kB (-16%) 👏
dotcom-rendering/dist/frontend.server.js 400 kB +18 kB (+5%) 🔍
dotcom-rendering/dist/guardian-braze-components-banner.js 13 kB +602 B (+5%) 🔍
dotcom-rendering/dist/guardian-braze-components-banner.legacy.js 13.1 kB +609 B (+5%) 🔍
dotcom-rendering/dist/guardian-braze-components-end-of-article.js 9.33 kB +605 B (+7%) 🔍
dotcom-rendering/dist/guardian-braze-components-end-of-article.legacy.js 9.39 kB +612 B (+7%) 🔍
dotcom-rendering/dist/initDiscussion.js 8.35 kB +319 B (+4%)
dotcom-rendering/dist/initDiscussion.legacy.js 8.63 kB +318 B (+4%)
dotcom-rendering/dist/islands.js 8.51 kB +316 B (+4%)
dotcom-rendering/dist/islands.legacy.js 9.27 kB +319 B (+4%)
dotcom-rendering/dist/react.js 21.2 kB -24.1 kB (-53%) 🏆
dotcom-rendering/dist/react.legacy.js 21.9 kB -29.1 kB (-57%) 🏆
dotcom-rendering/dist/readerRevenueDevUtils.js 3.19 kB +788 B (+33%) 🚨
dotcom-rendering/dist/readerRevenueDevUtils.legacy.js 4.07 kB +836 B (+26%) 🚨
dotcom-rendering/dist/SignInGateMain.js 2.98 kB +686 B (+30%) 🚨
dotcom-rendering/dist/SignInGateMain.legacy.js 3.05 kB +710 B (+30%) 🚨
ℹ️ View Unchanged
Filename Size
dotcom-rendering/dist/1451.js 19.1 kB
dotcom-rendering/dist/1451.legacy.js 19.8 kB
dotcom-rendering/dist/1576.legacy.js 3.2 kB
dotcom-rendering/dist/1956.js 4.13 kB
dotcom-rendering/dist/2299.js 3.51 kB
dotcom-rendering/dist/2299.legacy.js 3.59 kB
dotcom-rendering/dist/2550.legacy.js 4.35 kB
dotcom-rendering/dist/288.js 2.7 kB
dotcom-rendering/dist/2947.js 2.58 kB
dotcom-rendering/dist/2947.legacy.js 2.66 kB
dotcom-rendering/dist/2981.legacy.js 4.73 kB
dotcom-rendering/dist/3397.js 241 B
dotcom-rendering/dist/3397.legacy.js 252 B
dotcom-rendering/dist/3622.js 4.99 kB
dotcom-rendering/dist/3730.js 4.99 kB
dotcom-rendering/dist/3730.legacy.js 5.39 kB
dotcom-rendering/dist/4109.legacy.js 2.91 kB
dotcom-rendering/dist/4204.legacy.js 2.35 kB
dotcom-rendering/dist/4576.js 236 B
dotcom-rendering/dist/4576.legacy.js 247 B
dotcom-rendering/dist/4850.js 235 B
dotcom-rendering/dist/4850.legacy.js 246 B
dotcom-rendering/dist/4977.legacy.js 3.15 kB
dotcom-rendering/dist/5217.js 233 B
dotcom-rendering/dist/5217.legacy.js 245 B
dotcom-rendering/dist/5226.js 5.5 kB
dotcom-rendering/dist/5226.legacy.js 5.91 kB
dotcom-rendering/dist/5294.js 4.42 kB
dotcom-rendering/dist/5294.legacy.js 4.49 kB
dotcom-rendering/dist/6004.legacy.js 3.68 kB
dotcom-rendering/dist/6021.js 3.95 kB
dotcom-rendering/dist/6021.legacy.js 4.19 kB
dotcom-rendering/dist/6398.js 3.07 kB
dotcom-rendering/dist/6400.js 21.5 kB
dotcom-rendering/dist/6400.legacy.js 21.5 kB
dotcom-rendering/dist/6684.js 241 B
dotcom-rendering/dist/6892.js 5.39 kB
dotcom-rendering/dist/6892.legacy.js 5.81 kB
dotcom-rendering/dist/7057.js 4.48 kB
dotcom-rendering/dist/7318.js 3.83 kB
dotcom-rendering/dist/7318.legacy.js 3.93 kB
dotcom-rendering/dist/7388.legacy.js 2.64 kB
dotcom-rendering/dist/7576.js 4.21 kB
dotcom-rendering/dist/7576.legacy.js 4.38 kB
dotcom-rendering/dist/7809.legacy.js 2.69 kB
dotcom-rendering/dist/7826.legacy.js 4.82 kB
dotcom-rendering/dist/7916.js 9.63 kB
dotcom-rendering/dist/7992.js 4.66 kB
dotcom-rendering/dist/8184.js 12 kB
dotcom-rendering/dist/8184.legacy.js 13.3 kB
dotcom-rendering/dist/824.js 426 B
dotcom-rendering/dist/8282.js 3.65 kB
dotcom-rendering/dist/8282.legacy.js 3.75 kB
dotcom-rendering/dist/8294.js 233 B
dotcom-rendering/dist/8294.legacy.js 244 B
dotcom-rendering/dist/8785.legacy.js 10.4 kB
dotcom-rendering/dist/9007.js 4.7 kB
dotcom-rendering/dist/9007.legacy.js 4.76 kB
dotcom-rendering/dist/9512.js 10.2 kB
dotcom-rendering/dist/9512.legacy.js 12.1 kB
dotcom-rendering/dist/atomIframe.js 1.75 kB
dotcom-rendering/dist/atomIframe.legacy.js 2.02 kB
dotcom-rendering/dist/bootCmp.js 7.76 kB
dotcom-rendering/dist/bootCmp.legacy.js 11.3 kB
dotcom-rendering/dist/braze-web-sdk-core.js 36.1 kB
dotcom-rendering/dist/braze-web-sdk-core.legacy.js 36.1 kB
dotcom-rendering/dist/coreVitals.js 3.91 kB
dotcom-rendering/dist/coreVitals.legacy.js 4.21 kB
dotcom-rendering/dist/dynamicImport.js 2.87 kB
dotcom-rendering/dist/dynamicImport.legacy.js 3.16 kB
dotcom-rendering/dist/embedIframe.js 1.75 kB
dotcom-rendering/dist/embedIframe.legacy.js 2.03 kB
dotcom-rendering/dist/ga.js 3.75 kB
dotcom-rendering/dist/ga.legacy.js 4.02 kB
dotcom-rendering/dist/newsletterEmbedIframe.js 1.91 kB
dotcom-rendering/dist/newsletterEmbedIframe.legacy.js 2.17 kB
dotcom-rendering/dist/ophan.js 7.05 kB
dotcom-rendering/dist/ophan.legacy.js 7.27 kB
dotcom-rendering/dist/relativeTime.js 2.29 kB
dotcom-rendering/dist/relativeTime.legacy.js 2.57 kB
dotcom-rendering/dist/sentry.js 715 B
dotcom-rendering/dist/sentry.legacy.js 727 B
dotcom-rendering/dist/sentryLoader.js 4.74 kB
dotcom-rendering/dist/sentryLoader.legacy.js 7.71 kB
dotcom-rendering/dist/shimport.js 2.75 kB
dotcom-rendering/dist/shimport.legacy.js 2.76 kB

compressed-size-action

@tjmw
Copy link
Member

tjmw commented Mar 2, 2022

Hi @OllysCoding @mxdvl this is on my TODO list to review, looks really interesting! Just wanted to point out that because this affects StickBottomBanner and SlotBodyEnd this PR affects all systems which are vying for the opportunity to show something in those slots (so RRCP and also the CMP banner I think), not just Braze.

Copy link
Contributor

@oliverlloyd oliverlloyd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re @tjmw 's comment, shall we rename this PR to Move Slots to Islands?

Such great work! I made an attempt to do this and failed but I really like the gentle approach you've taken here - this makes a lot of sense to me.

One thing, and I know I keep harping on about this but I would like to keep some of these more advanced Typescript features out of DCR. I do fully appreciate that there are some things that we could do that do add value but it's also true that, in isolation, they all add value. The problem is the costs of each of them compound and compound until the DCR codebase becomes intimidating and inaccessible which is very much not what we want for our core platform. This is code that is designed to be used for years to come by a wide range of developers with different skills and experience so we should be aiming for the lowest common denominator rather than choosing to write code that requires specific knowledge.

idApiUrl,
stage,
pageId,
keywordsId,
}: Props) => {
}: Omit<Props, 'isDev' | 'switches' | 'isSensitive'>) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we omit Omit please? I don't know what it does and would prefer to not spend my time reading the Typescript docs and would rather focus on reviewing this code. This cost is multiplied over time, every time a developer reaches this code we are now either expecting them to know this feature or we are expecting them to learn it which raises the bar to entry on DCR which is not what we want to do

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We did try to avoid it, but you then need two Props types which are nearly identical. Either that or you have to use PropsA & PropsB, which I wouldn't say is any clearer.

Any idea on how we can tackle this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest, I can't suggest an alternative because I (genuinely) don't understand the code or what problem it is trying to solve. My guess is though that if DCR has been able to manage this long without this feature there probably is a way to make it work

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it helps, Omit<Object, Keys> means this Object without these Keys. So in this case it's Props minus the keys isDev, switches and isSensitive.

In my experience it doesn't come up that often, but there may be certain use cases where it's convenient (like perhaps this one?) 🤷.

Docs are here: https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mxdvl on intersection types, we could add clarity and maybe have something to share elsewhere.

e.g.

// AB.ts
export type Props = {
  switches: Switches;
  isSensitive: boolean;
  isDev: boolean;
}

// SlotBodyEnd.tsx
import { ABProps } from './AB.ts';
type Props = {
  contentType: string;
  // ...
}

const SlotBodyEndWithAB = (props: Props & ABProps) = {}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@oliverlloyd in this case I think we’re coming up against some of the limitations of the Island pattern. The reason DCR managed this long without this feature is because we could confidently use composition. We found ourselves more comfortable with a single child to an Island, opting for a pattern where SlotBodyEnd contains its own ABProvider:

// Layout.tsx
<Island clientOnly={true}>
  <SlotBodyEnd
    propA="A"
    propB="B"
    propC="C"
    propD="D"
    /* … */
    propM="M"
    propN="N"
  />
</Island>

// SlotBodyEnd.tsx
export const SlotBodyEnd = ({ /* … */ }: Props) => {
	return (
		<WithABProvider
			propA={A}
			propF={F}
			propN={N}
		>
			<SlotBodyEndWithAB
				propA="A"
				propB="B"
				propC="C"
				propD="D"
				/* … */
				propM="M"
			/>
		</WithABProvider>
	);
};

The alternative to this would be to allow ABProvider to be importable, but that seems odd.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the problem here that you don't want to have two sets of props for two different components? And want to connect the different types together and create one from the other? Asking because I am still a bit on the edge of understanding the goal with Omit.

If my assumption here is right then I can see the benefit. You don't have to create a new type and have instead got a sort of abstraction that creates it for you. Another benefit is you're coupling the types together so any changes to the root type will be carried down into the child version of it.

The counter to that is we're adding complexity which we could avoid by having duplication and that the benefit of coupling is also a constraint.

limitations of the Island pattern

I don't think this is entirely true. The reason we're using composition is not really related to the islands pattern but is instead caused by the constraint that hooks cannot be called conditionally. The islands pattern really only means we need to add an ABProvider, composition is how we would need to add that no matter if we were using an island or not.

In general though, I am happy to adopt these sorts of features if there were team consensus around them. You can have these sorts of discussions endlessly and there's really no right or wrong here.

Copy link
Contributor Author

@mxdvl mxdvl Mar 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If my assumption here is right then I can see the benefit. You don't have to create a new type and have instead got a sort of abstraction that creates it for you. Another benefit is you're coupling the types together so any changes to the root type will be carried down into the child version of it.

The counter to that is we're adding complexity which we could avoid by having duplication and that the benefit of coupling is also a constraint.

By placing the ABProvider inside this component, we’re fully coupled, so the types might as well reflect it. There might be something I’m not seeing here. I’ll have a go at making WithABProvider an importable.

I think, however, that @jamesgorrie’s suggestion is much better, and will have a go at implementing that instead!

EDIT: The new version does away with Omit, and as a result is also more verbose 64e989f2e.

Copy link
Contributor

@jamesgorrie jamesgorrie Mar 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think some of the complexity lies in how we have decided Islands should be implemented if the need access to the useAB hook.

For components to work, with AB hooks, in islands, we need to re-include the WithABProvider. We have chosen to make this the responsibility of the component rather than the island (can't find the documentation for this).

This means we have the pattern:

BootReact
├─ WithABProvider
  ├─ Island
    ├─ ComponentWithABProvider
      ├─ WithABProvider
        ├─ Component

That means ComponentWithABProvider require both the types for WithABProvider and the Component.

To this end, ComponentWithABProvider would have the type of WithABProvider & ComponentProps. This to me is quite easy to understand, or by using the Omit pattern, although for clarity I might use Omit<Props, ABProps>.

The parent component would always have to implement the <Island><ComponentWithABProvider> if the component uses the useAB hook.

Maybe there is something we can do to simplify this?

Copy link
Contributor Author

@mxdvl mxdvl Mar 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m going to have a look at implementing the following approach:

Island
 ├─ WithABProvider
 │  ├─ Component

There’s more discussion about this here: #3955

*
* @deprecated prefer refactoring the code to use SWR natively
*/
export const useSWRPromise = <Data extends unknown, Key extends string>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only use this code in two places so can we just duplicate it and not have the abstraction? If this code is already deprecated and we want to discourage it's use then we would achieve that much better by simply not creating it?

Also, inlining things is going to make it easier to read the code without the need to jump around files, which is always nice

And, if we don't need to pass things around so much we will need fewer types, it's all good stuff.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the ideal would be to not create it… however this would mean rewriting the slots’s braze logic completely, which we felt would be best achieved in a subsequent PR.

When pairing on it, using generics helped us confirm that the compiler was in agreement with what we were trying to achieve. We definitely did not want to inline this code because while we strongly believe it needs to go, having two copies of a code that does something we don’t want is worse than a single copy.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we know we don't want people to use this abstraction then not creating it is probably the best way to stop that happening. Creating an abstraction that we don't want used is metaphorically asking for trouble.

Alternatively, you could rename it to a name that is less inviting? useSWRPromise_DEPRECATED? Although I feel it is weird to create something which is immediately deprecated

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible here to use a plain TS function which memoizes the returned Promise? I think we have that pattern elsewhere in DCR. (I guess you'd need to combine with useEffect/useState to ensure this happens at the right time. This also might be relevant to Max's comment below, if we want to ensure buildBrazeMessages is always called:

When it comes to making sure something is always called on a page, the current approach is having a script in the browser folder, like we do for bootCmp #3661, initDiscussion #3873 and coreVitals

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually prefer this being broken out in and purposely deprecated, and I'd support oliver's idea of naming it useSWRPromise_DEPRECATED

Why?

copy-pasting code is not a rare thing in DCR, so many people work on this codebase it's commonplace to go look for patterns in other places and use them for your own component/function. This is fine, most of this code will be abstracted when and if it's needed, so it's no big deal to go looking for an example of what you're trying to do.

The advantage of abstracting this pattern is that we can mark it globally as something not to use, so if in a years time this pattern still exists for some reason, but we're all gone, if somebody were to copy and paste the way we were using SWR to generate a promise, there's a decent chance nobody would even realise it was a deprecated pattern.

Having a function, clearly named useSWRPromise_DEPRECATED would make it explicitly clear for any future developers that this is not a pattern we want to support. There's no chance it accidentally ends up in another place, at least not without the developers explicitly understanding why it was their only option.

Therefor I feel like it's the safer way to introduce this code, without risking it evolving or being assumed 'okay' in the future.

dotcom-rendering/src/web/lib/getCmp.ts Show resolved Hide resolved
This was referenced Mar 2, 2022
}: Props) => {
}: Omit<Props, 'isDev' | 'switches' | 'isSensitive'>) => {
const brazeMessages = useSWRPromise('braze-messages', () =>
buildBrazeMessages(idApiUrl),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make sure I understand correctly - does the use of useSWRPromise here effectively memoize the async work which buildBrazeMessages does? Under the hood an instance of BrazeMessages is returned which wraps the Braze SDK and I think we only ever want to have one of these on the page at a time. This used to happen because the instance was created in App and was passed down to the relevant "slots" as a prop.

I think a difference I can see with the new version of this code is that we'll only call buildBrazeMessages if one of (or both of) SlotBodyEnd and StickyBottomBanner exist in the page hierarchy? That's slightly different from before in App where we would always call buildBrazeMessages. The reason that distinction is important is because even if the slots/islands don't exist on the page, we still want to call buildBrazeMessage currently as it does work to clear up after the SDK if the user has logged out or removed permissions for Braze to load. Any thoughts on how we could maintain that with this new approach? It's maybe not super relevant at the moment because StickBottomBanner probably exists in every page layout? Even so it'd be nice to settle on an approach which works if that ever isn't true (now or in future).

Copy link
Contributor Author

@mxdvl mxdvl Mar 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, useSWRPromise memoize the async work of buildBrazeMessages. We might be able to use useSWRImmutable under the hood to make sure there’s only ever one instance of the BrazeMEssages.

When it comes to making sure something is always called on a page, the current approach is having a script in the browser folder, like we do for other scripts that run on every page:

Copy link
Contributor Author

@mxdvl mxdvl Mar 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth noting that there's no standard way for code inside the Island to talk to code running through the script tags. If the fact that we have a Slot island on every page is not sufficient and we go the script way, implementing a mediator is a requirement.

One proposal for such a mediator is @ashishpuliyel's guardian/commercial#486.

Copy link
Contributor

@oliverlloyd oliverlloyd Mar 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason that distinction is important is because even if the slots/islands don't exist on the page, we still want to call buildBrazeMessage currently as it does work to clear up after the SDK if the user has logged out or removed permissions for Braze to load. Any thoughts on how we could maintain that with this new approach?

This is a really interesting and useful point!

So the thing that SWR brings to the party for us is request deduplication. It takes any promised based function along with a key value and wraps the call to it in some dedupe logic such that any subsequent call will return the same result.

The fact that SWR takes any function, not just a fetch call, is powerful. It means we can kind of dedupe anything. This is potentially very powerful for our islands architecture. In the situation we have static global state shared between islands, if we wrap the call to get this state using SWR then we effectively share it. Now, I used the words 'static' and 'effectively' there because the limitation to this SWR trick is we don't share dynamic changes to the state. If one island mutates it's state value the other islands are not aware of this.

To solve the issue you raised where we ideally always want to call this function, not just when a slot appears on the page, we could have a third island, one that only makes the call to buildBrazeMessages. This will ensure that the call is always made but because of SWR deduplication it will not mean we make any additional actual executions of this function.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To solve the issue you raised where we ideally always want to call this function, not just when a slot appears on the page, we could have a third island, one that only makes the call to buildBrazeMessages

Ah cool, yeah that makes sense. If that fits with the way you (as in dotcom) are thinking about islands then I'd be in favour of adding that as part of this PR. That way even if it's true now that StickBottomBanner appears on ever page, it guards against that changing in future and going un-noticed. I wonder what a good name is? Would this be the first island where there's no visual/UI element to it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this be the first island where there's no visual/UI element to it?

Not the first, no. Liveness sometimes returns a component but is mostly just a vehicle for code and CommercialMetrics never returns any html. There might be others.

@mxdvl mxdvl changed the title feat: Move Braze to Islands feat: Move Slots to Islands Mar 2, 2022
rejecter = reject;
});

useSWRImmutable(key, fetcher, {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. I really like how this communicates intent!

Copy link
Contributor

@oliverlloyd oliverlloyd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pending Tom's point about needed to ensure we always clear us (eg. we should always run the buildBrazeMessages function) this look's great!

I appreciate there's a lot going on here though and I know you've got some other sub PRs but I'm happy for you to decide what's still needed and when best to merge things.

Co-authored-by: Olly <9575458+OllysCoding@users.noreply.github.com>
@OllysCoding OllysCoding changed the title feat: Move Slots to Islands Move ReaderRevenueLinks , SlotBodyEnd & ReaderRevenueLinks to Islands Mar 14, 2022
Copy link
Contributor

@oliverlloyd oliverlloyd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm approving parts of this work but pending eyes from others

dotcom-rendering/src/web/components/Footer.tsx Outdated Show resolved Hide resolved
dotcom-rendering/src/web/layouts/InteractiveLayout.tsx Outdated Show resolved Hide resolved
Copy link
Contributor

@OllysCoding OllysCoding left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall this looks good to me! Think there are a few small improvements that could be made around islands idle/when visible/etc but otherwise I think this looks good!

Copy link
Member

@AshCorr AshCorr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks A Ok to me!

@OllysCoding OllysCoding changed the title Move ReaderRevenueLinks , SlotBodyEnd & ReaderRevenueLinks to Islands Move ReaderRevenueLinks , SlotBodyEnd, StickyBottomBanner & ReaderRevenueLinks to Islands Mar 14, 2022
@oliverlloyd oliverlloyd changed the title Move ReaderRevenueLinks , SlotBodyEnd, StickyBottomBanner & ReaderRevenueLinks to Islands Move SlotBodyEnd, StickyBottomBanner & ReaderRevenueLinks to Islands Mar 14, 2022
</Portal>
</React.StrictMode>
);
return null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

@oliverlloyd
Copy link
Contributor

@jamesgorrie @mxdvl @tjmw This PR looks like it's ready to merge!

I think this part of the PR is especially nice

We've all contributed to this work recently and @AshCorr , @OllysCoding and I have been working today to finalise the changes. We're planning on making a final deployment to code in the morning and then merging. Let us know if you want to be involved.

@oliverlloyd oliverlloyd mentioned this pull request Mar 14, 2022
@tjmw
Copy link
Member

tjmw commented Mar 15, 2022

Hi @oliverlloyd thanks for the heads up! Yep would love to take a final glance on code before this gets merged. Would you mind pinging me when it's there?

@oliverlloyd
Copy link
Contributor

I've deployed this branch to CODE for verification

@OllysCoding OllysCoding merged commit 968d35a into main Mar 15, 2022
@OllysCoding OllysCoding deleted the mxdvl/braze-swr-islands branch March 15, 2022 11:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants