Skip to content

News Site - Navigation workload - #167

Merged
flashdesignory merged 84 commits into
WebKit:mainfrom
flashdesignory:news-thor-next
May 23, 2023
Merged

flashdesignory merged 84 commits into
WebKit:mainfrom
flashdesignory:news-thor-next

Conversation

@flashdesignory

@flashdesignory flashdesignory commented May 19, 2023 •

Copy link
Copy Markdown
Contributor

News site built with next.js, using its static export feature.

To visualize the test - stepping through breakpoints:
output

@kara

@flashdesignory

Copy link
Copy Markdown
Contributor Author

@rniwa - could you try one more time for me?
I am waiting now for that close button, before running the test.

@rniwa

rniwa commented May 19, 2023 •

Copy link
Copy Markdown
Member

Hm... I'm still hitting this error in Safari midway through the suite:

TypeError: null is not an object (evaluating 'page.querySelector("#close-message-link").click')

@flashdesignory

Copy link
Copy Markdown
Contributor Author

Hm... I'm still hitting this error in Safari midway through the suite:

TypeError: null is not an object (evaluating 'page.querySelector("#close-message-link").click')

ah - I can't repro on my machine.

Comment thread resources/tests.mjs Outdated
Comment thread resources/tests.mjs
},
tests: [
new BenchmarkTestStep("NavigateToUS", (page) => {
page.querySelector("#close-toast-link").click();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We discussed this a bit before, but I have been considering if we should to force a layout in between each of these actions within a step. I believe this would create a more realistic workload for this type of test. While opening and immediately closing the navbar will create React and DOM work there won't be any layout required, while in practice a user discreetly clicking the toggle button twice would cause multiple layouts.

An alternative would be to make each step tiny (with a single action in sync time, then taking the forced layout in async time) but I understand there's a practical problem with noise when steps are so short. So I wonder if having a few big steps that run through a bunch of user actions would be a good compromise that allows them to be heavy enough to be more measurable but also not skipping work that would be happening if they were individual steps.

page.querySelector("#close-toast-link").click();
page.layout();
page.querySelector("#navbar-dropdown-toggle").click();
page.layout();
// and so on, possibly containing actions from multiple of the current BenchmarkTestSteps if there is a logical grouping

I'm curious for thoughts from others on the idea (cc @rniwa @camillobruni @smaug----)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Forcing layouts after each click seems like a reasonable compromise to me since splitting work into multiple pieces can also lead to CPU getting throttled down.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@bgrins - just to ensure I am making the right change - instead of multiple test steps, we'd have just one and calling the page.layout() function in between each action?

@flashdesignory flashdesignory May 20, 2023 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

added page.layout() function, but kept the individual steps for now.

Screenshot 2023-05-19 at 4 55 43 PM

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yes, something like that is what I was thinking

@bgrins

bgrins commented May 22, 2023

Copy link
Copy Markdown
Contributor

I like the shape of this test and think it adds some good coverage for the benchmark, thanks @flashdesignory! Will give @julienw a chance to make any comments on the code or the generated site itself (https://news-thor-next--speedometer-preview.netlify.app/resources/tentative/newssite/news-next/dist/), although I know there are a number of other items from the repo on his plate so we can discuss as a group on Wednesday how to sequence this if he doesn't have a chance to in the meantime (i.e. if we should just land in tentative and then take any issues as follow ups, or keep it as a PR until then).

@julienw julienw left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This looks pretty good to me. I just left a few comments, but it's not clear to me what was generated and what was written by you, so feel free to ignore them..

Also I profiled locally and it doesn't seem to me that we're losing any work in the user timing marks. To be honest I'm a bit surprised because this uses React 18, I was expecting things to be more asynchronous :-)

<div className="article-content">
<ul className={`article-list vertical ${display ?? ""}`}>
{content.map((item, index) =>
<li key={`article-list-item-${index}`} className="article-list-item">

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Normally we shouldn't use the index as the key, rather we should use something that represents this article. Maybe its URL?
Otherwise React will update everything when the order changes for any reason. But maybe that's not a big deal in this context, because the content doesn't change actually? You know better than me.

(ditto in all other similar cases below)

Comment on lines +68 to +69
{item.url ? <a href={item.url}>
<ArticleText className="article-content truncate-multiline truncate-multiline-3" text={item.text} type="h3"/></a>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Indentation looks a bit weird here. I think that prettier+eslint ignores this code currently, it would be good to enable it right now.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'll take a look - thanks for flagging!

return null;

const Tag = type;
return <Tag className={`${className ?? ""}`}>{text}</Tag>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
return <Tag className={`${className ?? ""}`}>{text}</Tag>;
return <Tag className={className ?? ""}>{text}</Tag>;

or maybe even

Suggested change
return <Tag className={`${className ?? ""}`}>{text}</Tag>;
return <Tag className={className}>{text}</Tag>;

(as when it's undefined or null, then I believe that the attribute just isn't added)

Comment on lines +21 to +23
// Since the navbar is supposed to appear below the menu, we can't use position: fixed, height: 100%.
// Therefore we are using 100vh for the height. This function fixes the challenge on mobile, where
// 100vh might include the addressbar, ect.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not following completely, but would one of the newer units (such as dvh) be useful here? The support looks good enough, and I'd be interested in seeing this in the benchmark. What do you think?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, there are newer units, but I think this still represents a larger amount of websites using this approach.
The only downside is that it's using JavaScript to set the custom property in CSS, but that's really common.

It's working and I think it's ok to keep and maybe follow up at a later point with a newer approach.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm fine with keeping the current code as well

Comment on lines +7 to +19
const [navItems, dropdownItems] = keys.reduce(
(result, key) => {
// priority 0 does not show up in nav
if (content[key].priority === 0)
return result;

// priority 1 shows up in nav list
// priority 2 shows up in nav dropdown
result[content[key].priority === 1 ? 0 : 1].push(key);
return result;
},
[[], []]
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

optional nit: I think this would be clearer using an object with explicit properties, rather than a tuple.

@flashdesignory flashdesignory May 23, 2023 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I could do something like this:

const navItems = [];
const dropdownItems = [];

    keys.forEach(key => {
        if (content[key].priority === 1)
            navItems.push(key);
        else if (content[key].priority === 2)
            dropdownItems.push(key);
});

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

pushed above snippet for review

Comment thread resources/tentative/newssite/news-next/src/components/sitemap/sitemap.jsx Outdated
Comment on lines +6 to +8
useEffect(() => {
setIsSelected(checked);
}, [checked]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's not clear to me why we need a local state instead of using directly the prop checked.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The toggle component uses a "checked" prop, which will let us assign the initial state, or change the toggle from a parent component.

Comment on lines +23 to +27
function App({ Component, pageProps }) {
const [render, setRender] = useState(false);
useEffect(() => setRender(true), []);
return render ? <Component {...pageProps} /> : null;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's not clear to me what the intent of this is. Have you written this or is that generated?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ah, yes, this tripped me off too.
This next.js app uses static files, and this piece of code ensures that next.js doesn't try to render anything "on the server". With the useEffect, we ensure this code can only run client-side.

Comment thread resources/tests.mjs
Comment on lines +338 to +340
async prepare(page) {
await page.waitForElement("#close-toast-link");
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can you look whether we can have the initial rendering (including rehydration!) in the tests step instead of in the prepare phase? This would be really good for the benchmark IMO.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

it's static files, no server-side phase is present in this build.
We should treat it the same as other static apps.

@camillobruni

Copy link
Copy Markdown
Contributor

Looking good from my side, module sync-step length.
We've talked separately about this, and I think it will be good enough to just add a for-loop with some 4 iterations to bump the sync step time closer to 10ms.

@flashdesignory

Copy link
Copy Markdown
Contributor Author

Looking good from my side, module sync-step length.
We've talked separately about this, and I think it will be good enough to just add a for-loop with some 4 iterations to bump the sync step time closer to 10ms.

Done in the last commit - thanks @camillobruni

Comment thread resources/tentative/newssite/news-next/README.md Outdated
Co-authored-by: Kara <kara@users.noreply.github.com>
@flashdesignory
flashdesignory merged commit 372bd7a into WebKit:main May 23, 2023
@flashdesignory
flashdesignory deleted the news-thor-next branch May 23, 2023 22:03
bgrins added a commit to mozilla/Speedometer that referenced this pull request May 23, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants