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

add RSC and RCC for turbopack to benchmarks #2620

Merged
merged 2 commits into from Nov 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/next-core/src/app_source.rs
Expand Up @@ -500,7 +500,7 @@ impl NodeEntry for AppRenderer {

for (_, import) in segments.iter() {
if let Some((p, identifier, chunks_identifier)) = import {
result += r#"("TURBOPACK {{ transition: next-layout-entry; chunking-type: parallel }}");
result += r#"("TURBOPACK { transition: next-layout-entry; chunking-type: parallel }");
"#;
writeln!(
result,
Expand Down
25 changes: 23 additions & 2 deletions crates/next-dev/benches/bundlers/mod.rs
Expand Up @@ -29,6 +29,10 @@ pub trait Bundler {
fn has_server_rendered_html(&self) -> bool {
false
}
/// There is a hydration done event emitted by client side JavaScript
fn has_interactivity(&self) -> bool {
true
Copy link
Contributor

Choose a reason for hiding this comment

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

You could change has_interactivity and has_server_rendered_html to return an enum.

enum InteractivityMode {
    None,
    HydrationDone,
}

impl InteractivityMode {
    pub fn is_interactive(&self) -> bool {
        match self { ... }
    }
}

enum RenderMode {
    Client,
    Server,
}

// etc.

This will make the following code clearer to understand, without having to know the order of arguments to Turbopack::new.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll do

}
fn prepare(&self, _template_dir: &Path) -> Result<()> {
Ok(())
}
Expand All @@ -55,8 +59,25 @@ pub fn get_bundlers() -> Vec<Box<dyn Bundler>> {
}
let mut bundlers: Vec<Box<dyn Bundler>> = Vec::new();
if turbopack {
bundlers.push(Box::new(Turbopack::new("Turbopack CSR", "/", false)));
bundlers.push(Box::new(Turbopack::new("Turbopack SSR", "/page", true)));
bundlers.push(Box::new(Turbopack::new("Turbopack CSR", "/", false, true)));
bundlers.push(Box::new(Turbopack::new(
"Turbopack SSR",
"/page",
true,
true,
)));
bundlers.push(Box::new(Turbopack::new(
"Turbopack RSC",
"/app",
true,
false,
)));
bundlers.push(Box::new(Turbopack::new(
"Turbopack RCC",
"/client",
true,
true,
)));
}

if others {
Expand Down
13 changes: 12 additions & 1 deletion crates/next-dev/benches/bundlers/turbopack.rs
Expand Up @@ -18,14 +18,21 @@ pub struct Turbopack {
name: String,
path: String,
has_server_rendered_html: bool,
has_interactivity: bool,
}

impl Turbopack {
pub fn new(name: &str, path: &str, has_server_rendered_html: bool) -> Self {
pub fn new(
name: &str,
path: &str,
has_server_rendered_html: bool,
has_interactivity: bool,
) -> Self {
Turbopack {
name: name.to_owned(),
path: path.to_owned(),
has_server_rendered_html,
has_interactivity,
}
}
}
Expand All @@ -43,6 +50,10 @@ impl Bundler for Turbopack {
self.has_server_rendered_html
}

fn has_interactivity(&self) -> bool {
self.has_interactivity
}

fn prepare(&self, install_dir: &Path) -> Result<()> {
npm::install(
install_dir,
Expand Down
36 changes: 33 additions & 3 deletions crates/next-dev/benches/mod.rs
Expand Up @@ -56,6 +56,13 @@ fn bench_startup_internal(mut g: BenchmarkGroup<WallTime>, hydration: bool) {
} else {
true
}
} else if !bundler.has_interactivity() {
// For bundlers without interactivity there is no hydration event to wait for
if hydration {
continue;
} else {
false
}
} else {
hydration
};
Expand Down Expand Up @@ -119,6 +126,10 @@ fn bench_hmr_internal(mut g: BenchmarkGroup<WallTime>, location: CodeLocation) {
let browser = &runtime.block_on(create_browser());

for bundler in get_bundlers() {
// TODO HMR for RSC is broken, fix it and enable it here
if !bundler.has_interactivity() {
continue;
}
for module_count in get_module_counts() {
let test_app = Lazy::new(|| build_test(module_count, bundler.as_ref()));
let input = (bundler.as_ref(), &test_app);
Expand Down Expand Up @@ -206,11 +217,19 @@ fn bench_hmr_internal(mut g: BenchmarkGroup<WallTime>, location: CodeLocation) {
.await?;
app.start_server()?;
let mut guard = app.with_page(browser).await?;
guard.wait_for_hydration().await?;
if bundler.has_interactivity() {
guard.wait_for_hydration().await?;
} else {
guard.page().wait_for_navigation().await?;
}
guard
.page()
.evaluate_expression("globalThis.HMR_IS_HAPPENING = true")
.await?;
.await
.context(
"Unable to evaluate JavaScript in the page for HMR check \
flag",
)?;
Ok(guard)
},
|mut guard| async move {
Expand Down Expand Up @@ -283,6 +302,13 @@ fn bench_startup_cached_internal(mut g: BenchmarkGroup<WallTime>, hydration: boo
} else {
true
}
} else if !bundler.has_interactivity() {
// For bundlers without interactivity there is no hydration event to wait for
if hydration {
continue;
} else {
false
}
} else {
hydration
};
Expand All @@ -303,7 +329,11 @@ fn bench_startup_cached_internal(mut g: BenchmarkGroup<WallTime>, hydration: boo
.await?;
app.start_server()?;
let mut guard = app.with_page(browser).await?;
guard.wait_for_hydration().await?;
if bundler.has_interactivity() {
guard.wait_for_hydration().await?;
} else {
guard.page().wait_for_navigation().await?;
}

let mut app = guard.close_page().await?;

Expand Down
3 changes: 2 additions & 1 deletion crates/next-dev/benches/util/page_guard.rs
Expand Up @@ -100,7 +100,8 @@ impl<'a> PageGuard<'a> {
self.wait_for_binding(TEST_APP_HYDRATION_DONE),
)
.await
.context("Timeout happened while waiting for hydration")??;
.context("Timeout happened while waiting for hydration")?
.context("Error happened while waiting for hydration")?;
Ok(())
}
}
Expand Down
29 changes: 22 additions & 7 deletions crates/next-dev/benches/util/prepared_app.rs
Expand Up @@ -95,13 +95,27 @@ impl<'a> PreparedApp<'a> {

pub async fn with_page(self, browser: &Browser) -> Result<PageGuard<'a>> {
let server = self.server.as_ref().context("Server must be started")?;
let page = browser.new_page("about:blank").await?;
let page = browser
.new_page("about:blank")
.await
.context("Unable to open about:blank")?;
// Bindings survive page reloads. Set them up as early as possible.
add_binding(&page).await?;

let mut errors = page.event_listener::<EventExceptionThrown>().await?;
let binding_events = page.event_listener::<EventBindingCalled>().await?;
let mut network_response_events = page.event_listener::<EventResponseReceived>().await?;
add_binding(&page)
.await
.context("Failed to add bindings to the browser tab")?;

let mut errors = page
.event_listener::<EventExceptionThrown>()
.await
.context("Unable to listen to exception events")?;
let binding_events = page
.event_listener::<EventBindingCalled>()
.await
.context("Unable to listen to binding events")?;
let mut network_response_events = page
.event_listener::<EventResponseReceived>()
.await
.context("Unable to listen to response received events")?;

let destination = Url::parse(&server.1)?.join(self.bundler.get_path())?;
// We can't use page.goto() here since this will wait for the naviation to be
Expand All @@ -111,7 +125,8 @@ impl<'a> PreparedApp<'a> {
// So instead we navigate via JavaScript and wait only for the HTML response to
// be completed.
page.evaluate_expression(format!("window.location='{destination}'"))
.await?;
.await
.context("Unable to evaluate javascript to naviagate to target page")?;

// Wait for HTML response completed
loop {
Expand Down
59 changes: 59 additions & 0 deletions crates/turbopack-create-test-app/src/test_app_builder.rs
Expand Up @@ -253,6 +253,65 @@ export function getStaticProps() {
.write_all(bootstrap_static_page.as_bytes())
.context("writing bootstrap static page")?;

let app_dir = src.join("app");
create_dir_all(app_dir.join("app"))?;
create_dir_all(app_dir.join("client"))?;

// The page is e. g. used by Next.js
let bootstrap_app_page = r#"import React from "react";
import Triangle from "../../triangle.jsx";

export default function Page() {
return <svg height="100%" viewBox="-5 -4.33 10 8.66" style={{ backgroundColor: "black" }}>
<Triangle style={{ fill: "white" }}/>
</svg>
}
"#;
File::create(app_dir.join("app/page.jsx"))
.context("creating bootstrap app page")?
.write_all(bootstrap_app_page.as_bytes())
.context("writing bootstrap app page")?;

// The page is e. g. used by Next.js
let bootstrap_app_client_page = r#""use client";
import React from "react";
import Triangle from "../../triangle.jsx";

export default function Page() {
React.useEffect(() => {
globalThis.__turbopackBenchBinding && globalThis.__turbopackBenchBinding("Hydration done");
})
return <svg height="100%" viewBox="-5 -4.33 10 8.66" style={{ backgroundColor: "black" }}>
<Triangle style={{ fill: "white" }}/>
</svg>
}
"#;
File::create(app_dir.join("client/page.jsx"))
.context("creating bootstrap app client page")?
.write_all(bootstrap_app_client_page.as_bytes())
.context("writing bootstrap app client page")?;

// This root layout is e. g. used by Next.js
let bootstrap_layout = r#"export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<meta charSet="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Turbopack Test App</title>
</head>
<body>
{children}
</body>
</html>
);
}
"#;
File::create(app_dir.join("layout.jsx"))
.context("creating bootstrap html in root")?
.write_all(bootstrap_layout.as_bytes())
.context("writing bootstrap html in root")?;

// This HTML is used e. g. by Vite
let bootstrap_html = r#"<!DOCTYPE html>
<html lang="en">
Expand Down