-
Notifications
You must be signed in to change notification settings - Fork 6
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
Add dotcom-ui-data-embed
#812
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A nice, clean implementation. 👍
And I think the naming decisions ended well as this (to me at least) all reads really clearly.
I have added a few comments.
Is it possible to add a screengrab from Dev Tools of some dummy data being embedded in the DOM?
I am also going to have a go of running this branch locally.
@@ -64,6 +67,7 @@ module.exports = (_, response, next) => { | |||
</section> | |||
</div> | |||
</Layout> | |||
<DataEmbed id={DATA_EMBED_ID} data={{ foo: true, bar: 'qux' }} /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason this is not passed as an argument to Shell
like flags
and appContext
? Would be good to keep the same pattern if possible.
In the Shell
component, flags
and appContext
end up at the bottom of the body
tags so as to be non-blocking: https://github.com/Financial-Times/dotcom-page-kit/blob/master/packages/dotcom-ui-shell/src/components/Shell.tsx#L76-L77. Is there a difference between the DataEmbed
being here rather than there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There can be multiple instances of DataEmbed
if a user wants, so right now it's up to them how to get data to DataEmbed.
In theory we could replicate the middleware and server packages of app-context but that felt more than what was initially agreed in the original issue.
Then DataEmbed would just be a generic AppContext.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In theory we could replicate the middleware and server packages
Feels like over-engineering? 😬 We don't expect many apps to interact with this tool directly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There can be multiple instances of DataEmbed
So using this usage of Page Kit as an example, it would look something like:
const document = React.createElement(
Shell,
{ ...shellProps },
[
React.createElement(Layout, { ...layoutProps, contents: html }),
React.createElement(ClientEmbed, id='custom-context-foobar' data={{ foo: true, bar: 'qux' }}),
React.createElement(ClientEmbed, id='custom-context-barfoo' data={{ foo: false, bar: 'quux' }})
]
);
- This will require the document element created by the consuming app to have that many more arguments (at the moment it is quite clean with just
Shell
andLayout
), but admittedly it is currently only one app that will be usingClientEmbed
so is not a huge consideration. - In keeping to the principle of least surprise, users may well expect for all the client-embedded data (flags, appContext, custom) to sit alongside one another, and that this could be achieved by passing the custom data as part of the props given to the
Shell
component - is this easily possible?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this easily possible?
I think it is, but we'd want to mirror the dotcom-server-app-context
and dotcom-middleware-app-context
package functionality so that it is integrated in a similar way to app context which we discussed above.
So we could go ahead and flesh that functionality out, or intentionally keep this simpler and observe how people use the package before committing to something in page kit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Okay - let's keep it as is and monitor.
@@ -0,0 +1,88 @@ | |||
# @financial-times/dotcom-ui-data-embed | |||
|
|||
This package provides methods for putting data into your server-side rendered pages and safely retrieving it again in the browser. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This package provides methods for putting data into your server-side rendered pages and safely retrieving it again in the browser. | |
This package provides methods for putting data into your server-side rendered pages and retrieving it again in the browser. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the issue of safety factor here? (Honest question - I could have easily missed something.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is me mirroring language in app-context
and flags
, since the extra safety comes from the DataEmbedStore
class rather than returning a regular object.
|
||
const dataEmbedClient = dataEmbed.init({ id: 'data-embed' }) | ||
|
||
if (dataEmbedClient.get('property')) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are flags
and appContext
also frozen, i.e. would this be a breaking change?
Would have deep-freezing been an option (as opposed to just freezing the top-level properties) and offered any benefits for nested objects that may be used in the future?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'm mostly worried about the performance cost of shipping a deep-freeze library and I'm not too sure of the consequences of us not doing this. I suppose we want to encourage people to make copies of the data rather than relying on the ability to mutate it deeply...
Should we investigate the bundle cost of doing a deep-freeze?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In terms of freezing flags and appContext, that should be unchanged as they don't reuse the DataEmbedStore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's keep it just as a .freeze
as flags
and appContext
currently are and worry about it if the nested properties being unfrozen ever becomes an issue.
I asked more for my curiosity.
} | ||
|
||
get(key: string) { | ||
return this.data.hasOwnProperty(key) ? this.data[key] : undefined |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't this.data[key]
return undefined
if the key does not exist as a property on this.data
? I.e. is the conditional check necessary here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll admit a lot of this code is me looking for patterns in flags
and app-context
and consolidating the logic.
So while I didn't make the decision, I can only assume it's to avoid you being able to try to 'get' properties built into the Object.prototype e.g. constructor
or __proto__
.
So I'm not too sure, but perhaps could be simplified alongside flags
and app-context
later on?
https://masteringjs.io/tutorials/fundamentals/hasownproperty
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is always the assumption of good faith that there is a good reason behind any pre-existing code. Also happy to leave it as is for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, definitely should have checked this, thanks for the prompt. :)
import loadDataEmbed from './loadDataEmbed' | ||
import DataEmbedStore from './DataEmbedStore' | ||
|
||
const init = ({ id }: { id: string }) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if it's convention, but are index files usually for just listing the modules by importing and exporting them? I.e. should this function live in its own file and be imported?
I say this when it could easily be possible that we have functions in index
files elsewhere in this repo. Am just thinking out loud.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This follows what app-context
and flags
are doing. I agree it's a bit odd though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough - happy to leave as is.
Validation error: data should NOT have additional properties, received "isNotInTheSchema" | ||
|
||
If you want to share application specific data with the client, consider using @financial-times/dotcom-ui-data-embed. | ||
`.trim() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Backticks can be frustrating. I wonder if there's a way to do multiline without inconsistent formatting and trim()
… 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could consider adding a dependency like https://www.npmjs.com/package/outdent but I felt like that was a bit much for this small usecase. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could always just do something like
'string\n' +
'second-string'
or
[
'string',
'second-string'
].join('\n')
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I agree with your feeling about importing a package just for this.
You could even have one really long line…
None is particularly nice so I will leave it to you to decide (which may as well be how it is currently).
|
||
If you want to share application specific data with the client, consider using @financial-times/dotcom-ui-data-embed. | ||
`.trim() | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same backticks issue as above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I looked at this again I noticed there was an opportunity to improve the error message so we only output the additional information when they specifically encounter the error for adding additional properties. So I've managed to address the odd formatting as well at the same time...
52394dd
to
ed8d922
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent work! I've added a few small comments but it's looking really good
|
||
### Client-side integration | ||
|
||
Once you have you data embedded in your page you can use the [data embed client](#client-side-api) in your client-side code. The client provides methods for safely retrieving the status of individual context properties. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should individual context properties
be individual data properties
? Just wondering if the terminology has shifted since this was written
|
||
Requires an `id` parameter within an options object, this `id` should match your embed `id` within the page. | ||
|
||
## data embed client API |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure it's super obvious what data embed client API
means here. Would something like Component API
work or have I misunderstood the structure?
@@ -64,6 +67,7 @@ module.exports = (_, response, next) => { | |||
</section> | |||
</div> | |||
</Layout> | |||
<DataEmbed id={DATA_EMBED_ID} data={{ foo: true, bar: 'qux' }} /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In theory we could replicate the middleware and server packages
Feels like over-engineering? 😬 We don't expect many apps to interact with this tool directly
The spike in next-video-page looks good 👌 Once these changes are released we should ask the Content Innovation team to test/review the new approach in that app, then we can refine this package if needed before rolling |
3204cf6
to
f34248d
Compare
Since `dotcom-ui-app-context` and `dotcom-ui-flags` share functionality this commit moves JSON output into the embed component which is the same as how it works in app-context. This'll hopefully make it easier to use the new package in the future. May be a breaking change if formatFlagsJSON is part of the public API.
Also rename the file since there's no JSX syntax used in this file anymore.
Also rename the file since there's no JSX syntax used in this file anymore.
This allows users to parse the data embedded in the page.
Mirrors the classes found in flags and app-context. Expose through fleshed out init method.
f34248d
to
ff280ae
Compare
|
||
### `init({ id }:{ id: string })` | ||
|
||
Initialises and returns a new [data embed client](#data-embed-client-api) which can be used to safely access the status of individual contexts. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would 'properties' or 'values' work in place of 'contexts' here? I'd rather not introduce any confusion with appContext, and calling things 'context' in the past has called all manner of confusion 🙈
expect(instance.get('baz')).toBe('qux') | ||
}) | ||
|
||
it('returns undefined for contexts which do not exist', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See pedantic 'contexts' comment above
In order to allow users to share data between the server and client introduce a new
dotcom-ui-data-embed
package.Some parts of this package can be reused and consumed by the
dotcom-ui-flags
anddotcom-ui-app-context
packages.I have spiked this feature in the
next-video-page
application that was blocked without this feature.Screenshot of kitchen sink example output
Closes #799
Closes #761