-
-
Notifications
You must be signed in to change notification settings - Fork 8.7k
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
feat(core): rework client modules lifecycles, officially make API public #6732
Conversation
✅ [V2]
To edit notification comments on pull requests, go to your Netlify site settings. |
⚡️ Lighthouse report for the changes in this PR:
Lighthouse ran on https://deploy-preview-6732--docusaurus-2.netlify.app/ |
Size Change: +399 B (0%) Total Size: 803 kB
ℹ️ View Unchanged
|
890d3ce
to
7b2bad2
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.
Looks like a useful change to do that 👍
See comments: I'd rather really use useEffect
today as most usage is analytics IMHO, should be non-blocking (perf) and should be fail-safe. Maybe the lifecycle should somehow respect React terminology? Like onRouteUpdateEffect
+ eventually onRouteUpdateLayoutEffect
?
Also worth migrating our GA plugins to this new lifecycle?
Docs? or do we dogfood this a bit before writing it?
(considering it impacts the plugin authors we should rather get it right the fist time)
If we are talking about firing analytics, then it doesn't really matter, because the effect doesn't affect UI anyways, it just leaves. If we are talking about manipulating DOM (for example, someone has presented a weird use-case to me where they have an external script that needs to manipulate a particular DOM element on every page transition), then it certainly needs to be synchronous. I can't think of a compelling case where the lifecycle would block render in an unacceptable way. |
Actually it can affect UI:
That's why I think firing in This is also why the React team created
Then we need to understand better this use-case |
I remember it was attaching event listeners. import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
export default (function () {
if (!ExecutionEnvironment.canUseDOM) {
return null;
}
return {
onRouteUpdate({location}) {
if (location.pathname.startsWith('/acs/')) {
const labels = document.getElementsByTagName("label");
for (let a = 0; a < labels.length; a++)
labels[a].addEventListener("click", (e) => e.target.classList.add("foo"));
document.querySelector('input,textarea').attr('autocomplete', 'off');
}
},
};
})(); More complicated than that though, because they are using some 3rd-party script that they don't really want to refactor into their React app, so they just wanted a standalone |
7b2bad2
to
d71d643
Compare
@slorber I implemented what you had in mind in a4e16b9. Tell me what you think and if that's the current direction we should go in. I realized because |
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.
LGTM 👍
Migrated to |
😭 I don't know 😅 It looks like it does have an impact and lead to duplicate events being send on site first load 😓 Before/prod: After: Maybe both lifecycles should never fire on the very first render when previousLocation is null? I feel like having this difference between the 2 lifecycles will be confusing for users, and we should rather make the best choice now because this change later can also be painful for the plugin ecosystem. Note that client module authors can still add some inline JS that will be executed immediately after load: if (ExecutionEnvironment.canUseDOM) {
console.log("on first load, document title is " + document.title);
} Note that Gatsby seems to fire both lifecycles on first load: |
We can fire it as frequently as we want, and ask client module authors to filter out unwanted triggers. For example, here we can add a simple |
I guess it's more powerful to let the users figure out the filtering on their side in case duplicate events are fired on initial load. Some analytics may send events automatically to their endpoint on js load, while others need to be triggered manually with a JS API. Giving the opportunity to trigger this API seems better to me 👍 Now we should probably make both lifecycle consistent, and it's not very clear to me when/where if (previousLocation === null) {
const cleanup = dispatchLifecycleAction('onRouteUpdate', {
previousLocation,
location,
});
cleanup();
}
dispatchLifecycleAction('onRouteDidUpdate', {previousLocation, location}); |
I suggest we do like Gatsby: both lifecycles fire on initial render. Firing from PendingNavigation constructor seems ok 🤷♂️ not exactly like my suggestion above but it probably doesn't matter much https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/cache-dir/navigation.js#L217 Now we need to ignore GA calls when |
|
seems good now 👍 just need to filter first call on gtag/google-analytics plugins |
|
||
export default (function analyticsModule() { | ||
if (!ExecutionEnvironment.canUseDOM) { |
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 check actually isn't necessary before either...
tested locally, analytics work as before 👍 great 🎉 |
Cool! |
## ✅ What's done - [x] Detach zoomObject if already attached. - [x] I used this code as a reference. Dark mode not supported. - https://github.com/gabrielcsapo/docusaurus-plugin-image-zoom - [x] Support the following onRouteUpdate lifecycle specification changes. - facebook/docusaurus#6732 - Not a public distributed plugin, so it does not support backward compatibility.
Breaking changes
Client module lifecycle
onRouteUpdateDelayed
was removed. You can restore a similar feature using the new lifecycles + setTimeout (see our NProgress integration as an example).The existing
onRouteUpdate
lifecycle will now also fire on first browser page render (hydration), similarly to the newonRouteDidUpdate
Documentation: https://deploy-preview-6732--docusaurus-2.netlify.app/docs/advanced/client#client-module-lifecycles
More details here: #3399 (comment)
Motivation
Fix #3399. Client modules suffer from the issue of firing too early—before React even renders. In this case, people who use client modules to modify DOM on every page, or simply reading from it (like the route announcer in #7074) will not be able to do so without using a timeout hack.
This PR also addresses a long-standing issue of hash links not working in development. The problem is that the scroll-to-anchor happens in
shouldComponentUpdate
, so if there's no pre-rendered HTML, there will be no DOM element to read from. This is solved through deferring it to after the initial render as well.Have you read the Contributing Guidelines on pull requests?
Yes
Test Plan
I added a dogfooding client module. When switching between routes, note how the lifecycle now logs the new title instead of the old one: