[RFC] RenderingTarget
type and varying props by platform(?)
#11239
Replies: 8 comments 2 replies
-
I would urge you really consider this in depth. When AMP was added to frontend, the web templates were reused and peppered with conditional logic, and that made working on them extremely complicated. It became very hard to find the bit you wanted to change, and to know if you were breaking a platform you were not intending to affect – this was the experience that drove the separation of AMP and web in DCR. I think you would want to really understand exactly how contingent any current similarity across platforms is:
The more child components your component composes the more it matters (e.g. a whole page vs a timestamp span). I totally understand why you'd want to reduce the surface area for edits when you want to push simultaneous changes out across multiple platforms, and why that's hard currently. But binding distinct platforms in code implies a large amount of identity – does that genuinely obtain here or are the platforms just coincidentally similar right now? It's useful to consider how easy any solution will make undoing it/deleting things (if you can't delete code then you're stuck with it). Does forking by platform inside components (of all sizes) make this a lot harder? |
Beta Was this translation helpful? Give feedback.
-
Thanks for your response @sndrs this historical context is super useful! I think fundamentally we should see Apps & Web articles as the same, part of the product decision that went into the organisation of this work is that by moving to one platform, we're committing to this similarity. However, we accept some levels of differences as necessary due to the difference in how each should run:
Our approach for this during the spike we carried out took a balanced approach of duplication & in-component comparisons; we duplicated things like SSR code & article layouts, where significant differences in composition were present. However - as an example - were we to want to have separate Lightbox implementations, and didn't want to go down the route of a I think I see the inclusion of a platform type prop to be more similar to that of While the separation of AMP code and Web code was somewhat successful in reducing complexity there are some factors to consider in this route
In the other hand, I'd argue that multi-platform complexity is desired where differences are required for the Apps/Webs work. Any engineer working on DCR should really be thinking about all platforms when working on their code, and most work that does not interact with a platform specific feature would be written & tested across both platforms. tl;dr: We don't want people to be writing code for the 'apps' or 'webs' but rather just writing rendering code, and complexity which comes from having multiple platforms would (hopefully) be able to be managed in isolated components. In terms of exit strategy, I think early on if we get the vibe that the complexity being added here is too much then we will have ample opportunity to change strategy during this migration - as long as we stay aware of the complexity we're adding :) Also as a final note, if it helps the understanding, I wonder if we should switch the naming of the suggested Platform type (which is overloaded) to |
Beta Was this translation helpful? Give feedback.
-
Thanks for such a clear and detailed discussion Olly! I don't think I currently know enough about the similarities/differences/future of web vs app rendering to comment on that part of the discussion above. But on the issue of the
Having said all that, I think the above probably only applies if we can limit the surface area of the API. If the cases become more complex e.g. sub-types of specific rendering targets) and/or if we find it leaking into our other patterns (e.g. adding new props to patterns like |
Beta Was this translation helpful? Give feedback.
-
This is an excellent and very clear RFC - especially if we consider what problem you're trying to solve here, so well done and thanks! Same as Pete, I can't say I know enough about the similarities / differences between web and apps and how this might evolve in the future. However, my understanding is at this point we're talking about:
For these two cases From what I understand there is a third case which we can see arising in the future, i.e. if we want to have separate implementations of a feature / component. In your Lightbox example I believe |
Beta Was this translation helpful? Give feedback.
-
Hi everyone! Thanks for your excellent feedback on this RFC :) Having taken some to think through this feedback, here is the state of play: I believe we should go ahead with the RFC as defined above, using the During this process we will keep track of both the complexity we are adding to codebase, as well whether the patterns feel maintainable. Furthermore, we should track the overall objectives of how we'd like developers to be able to work on DCR, looking at the cultural element of how we can help people approach work on the codebase. Overall our goal should be to enable developers to work quickly and easily across any product (e.g articles, fronts) and for any rendering target (e.g apps, web). In these early stages, backing out or making changes to either separation patterns, or the file structure (#7174) should be relatively easy, and we should feel confident to change any of these decisions at a later date. I'll now be raising a PR to add in these pattern definitions, so that development work can continue on creating articles which can be rendered on apps. |
Beta Was this translation helpful? Give feedback.
-
Thanks for the update @OllysCoding -- that sounds like a good plan! |
Beta Was this translation helpful? Give feedback.
-
@OllysCoding your last comment would make a splendid ADR! |
Beta Was this translation helpful? Give feedback.
-
I would recommend going for a string union over an enum, as one is natively JavaScript and the other requires transformation by the TypeScript compiler. // If you define a array `as const`…
const renderingTarget = [
"Web",
"Apps",
"Editions",
] as const;
// … you can infer the type from it…
export type RenderingTarget = renderingTarget[number];
// … and create a custom type guard, if you so desire!
export isRenderingTarget = (target: string): target is RenderingTarget =>
renderingTarget.map(String).includes(target) |
Beta Was this translation helpful? Give feedback.
-
Background
DCR is being extended to support rendering for both web & apps, and as a result AR will eventually be decommissioned.
Content pages (articles) differ slightly between web & apps, and DCR must now be updated to support these differences, these include:
For this to work then, our components in DCR will need to be aware of what they're rendering for, and - occasionally - they'll also need different props and data in order to achieve this.
Introducing
RenderingTarget
typeThe
RenderingTarget
type will aim to encapsulate the different targets that DCR can render for. We'd likely start with justWeb
andApps
as options, but in the long term we could includeAMP
(Which DCR already renders for) andEditions
which will come at a later date in the DCAR migration.How can we define it?
There are really two options to define this new
RenderingTarget
type:Option 1 - Enums
Option 2 - String union
While enums would likely make the most sense semantically, they're rarely used in DCR and generally we see string unions used in place.
This
RenderingTarget
type would then likely be heaving drilled in DCR (since we don't use React Context or any similar global state), to allow components to make decisions such asIt's worth noting that as the
RenderingTarget
type expands, these comparisons would become more difficult, leaving us a few options for handling differences between the rendering targetsWe should carefully consider how we want to define RenderingTarget now, and what kinds of solutions we may use in components in the future.
Differing props by the
RenderingTarget
typeWhile working on the original spike, we discovered that we may encounter differences between the required props for each rendering target. As an example, while in DCR we require a
contributionsServiceUrl
string in order to link users to sign up for a digital subscription, this wouldn't be required in the app - where it would likely be activated natively, through bridget.As a consequence of this, we took a look at how we can differ types based on the value of the
RenderingTarget
.Introducing the
CombinedProps
patternDuring the spike, @JamieB-gu came up with the follow pattern to enable specific Apps only & Web only props:
Definition:
Usage:
What are the advantages of this approach?
The key advantage of this approach is type safety - Not only do we know for sure which props are defined & when, but we also add safety that people won't try and develop apps features with web props, or vice versa.
Furthermore, it may help with the longevity of the code, as mixed rendering target props could result in eventual confusion on which props should be used when, making it more difficult for future developers to determine what should be used when.
What are the disadvantages of this approach?
This pattern is fairly complex, adding a new pattern to DCR with type definitions that may take some learning for people not familiar with typescript.
It also does not work if you deconstruct the props in the function definition, meaning we'd have a mix of prop deconstruction techniques in DCR going forward.
Another consideration is that this likely wouldn't be a massively common pattern in DCR, as not a lot of components would require differing props, meaning it could be even more jarring to come across.
Alternatives to the
CombinedProps
patternMake everything optional
In this case, we could just make
contributionServiceUrl
optional, and then do a check such asrenderingTarget === RenderingTarget.Web && contributionServiceUrl !== undefined && <AnotherComponent contributionsServiceUrl={props.contributionsServiceUrl} />
For this to work best, you'd want to make DCR variants of the model optional, but have the FE model where DCR still expects
contributionsServiceUrl
to be defined for web requests. Otherwise we'd compromise the type safety of our model.Have all the data, all the time
The second option would be to make all apps only and web only props required all the time. This relies on the idea that we will have a single data source for both apps & web, and we'll always be able to provide that data no matter what request we're making.
Both of the alternatives have the disadvantage that we lose the ability to discern the difference between apps and webs data at the point of usage, which could make the system feel more complex for developers.
Thanks for reading this long explanation of the two concepts we could bring to DCR! I'd love to hear your thoughts and ideas on how to solve this problem!
The key thing here is that adding the ability to render for apps requires adding some amount of new complexity to DCR, and we need to carefully think about how we manage this complexity, and what the best way to implement it could be!
Beta Was this translation helpful? Give feedback.
All reactions