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

Allow server components in islands architecture to call code behind ssr without need for #[server] #2578

Open
versecafe opened this issue May 7, 2024 · 1 comment

Comments

@versecafe
Copy link

Is your feature request related to a problem? Please describe.
It's a bit annoying to need to worry about the security implications of exposing every server function when some functions should be server accessible only with no exposure for components rendered only on the server, same behaviour as server side code with RSCs not needing to be exposed to clients

Describe the solution you'd like
inside of a #[component] macro when experimental-islands enabled allow all ssr dependencies

#[cfg(features="ssr")]
async fn db_example() -> Result<Vec<String>, ServerFnError> {
    use sqlx::Row; // sqlx is under ssr deps in cargo.toml

    let url: &str = "example-pg-url";
    let pool = sqlx::postgres::PgPool::connect(&url).await?;

    let res = sqlx::query("SELECT `title` FROM `works`").fetch_all(&pool).await?;
    let titles: Vec<String> = res.get()
    return titles;
}


#[component]
fn ServerOnly() -> impl IntoView {
    view! {
      <div>
        <Await future=|| db_example() let:data>
          <For
            each=move || { res.clone().into_iter().enumerate() }
            key=|(index, data)| *index
            // renders each item to a view
            children=move |val| {
                let title = val.to_string();
                view! { <p>{title}</p> }
            }
          />
        </Await>
      </div>
    }
}

Describe alternatives you've considered
Just always making a server action even if it's never used on the client and handling the security for all of them as if they were exposed endpoints rather than internal functions.

Additional context
Async RSC example as a reference over from the JS/TS world

async function getTitles(): Promise<string[]> {
  // drizzle ORM example as comparison to sqlx
   return await db
    .select({
      title: worksTable.title
    })
    .from(worksTable);
}

async function ServerOnly(): Promise<JSX.Element> {
  return (
    <div>
      {titles.map((title) => (
        <p>{title}</p>
      )}
    </div>
  );
}
@gbj
Copy link
Collaborator

gbj commented May 7, 2024

This is not unreasonable. The way it works how it currently works is this: when compiling the frontend in islands node, the compiler actually compiles the whole application, then does dead code elimination to remove anything that's not used in the body of an island.

This is because #[component] does not mean "guaranteed to run on the server"; an island can use a component in its body and that will be included in the WASM binary. We don't have server components guaranteed only to be on the server; we have islands guaranteed to be on the client, and shared components.

A straightforward workaround is to cfg-flag whatever server-only code you want to call in a component, and #[cfg(not(feature= "ssr"))] let foo = unreachable!(); if you are sure you don't use it in an island.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants