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

Getting Stuck with Apollo #8

Closed
LydiaF opened this issue Mar 14, 2021 · 12 comments
Closed

Getting Stuck with Apollo #8

LydiaF opened this issue Mar 14, 2021 · 12 comments

Comments

@LydiaF
Copy link

LydiaF commented Mar 14, 2021

Dear @brillout,

Thank you for this plugin, it looks very promising.

update: seems to be working perfectly now!

If you have time, would you mind creating a very basic example using apollo client? I am probably doing something stupid but I don't think I can figure it out on my own.

// apollo/index.ts
import 'cross-fetch/polyfill';
import { ApolloClient } from '@apollo/client'
import { InvalidationPolicyCache } from 'apollo-invalidation-policies';

export const client = new ApolloClient({
  cache: new InvalidationPolicyCache({}),
  uri: 'https://countries.trevorblades.com'
})
// pages/tests/index.page.server.ts
import { gql } from '@apollo/client'
import {client} from '../../apollo'

export { addContextProps }
export { setPageProps }

export const LIST_COUNTRIES = gql`
  {
    countries {
      name
      code
    }
  }
`;

async function addContextProps({ contextProps }) {
  const response = await client.query({query: LIST_COUNTRIES})

  return { countries: response.data.countries }
}

function setPageProps({ contextProps: { countries } }) {
  return { countries }
}
// pages/tests/index.page.tsx
import React from "react";
import { useQuery } from '@apollo/client'
import "./index.css";
import {client} from '../../apollo'
import {LIST_COUNTRIES} from './index.page.server'

export { Page };

function Page({countries}) {
  const {data} = useQuery(LIST_COUNTRIES, {client});
  console.log('countries', countries) // This is not working
  console.log('data', data) // This works 
  return (
    <>
      <h1>TEsts</h1>
      <p>A okay text.</p>
    </>
  );
}

Thank you very much,

Lydia

@brillout
Copy link
Member

Hello Lydia,

I'm glad it works now. Let me know if you have any questions.

I'd gladly accept a PR adding an minimalistic /examples/react-apollo :-).

Rom

cc @chriscalo who has been curious about Apollo support.

@LydiaF
Copy link
Author

LydiaF commented Mar 14, 2021

Hi @brillout,

Thank you for such a quick response. I will do a PR if we move to Vite... which is looking likely!

Since you offered, I do have two more questions... :)

(I apologise for the lack of proper terminology, I don't really understand this stuff at all.)

In our current next repo, we are using this package for apollo. As far as I understand it, it means you can just useQuery() everywhere, but it magically still works with ssr.

Is this how apollo roughly works in your package:

  • if I were to not use client.query in addContextProps, and instead just use useQuery in the page component, is it correct that the data would not be 'sent down' initially, and the data would only be fetched on render, thus not benefitting from ssr?
  • so what we need to do is use client.query as above, and use useQuery() in the components, to get all the bells and whistles?

Q2. I'm trying to get import module mapping working. I am perplexed because I was able to get this solution working in a git clone of the official vite starter repo for react-ts. However I cannot get it working in your starter and I believe I am doing the exact same thing! Is it at all possible that this is not possible for some reason in ssr? There is also no red line in the editor under the import... Apologies if this is another silly q, I've just been going round in circles about it :S

src/components/Divvy.tsx
import React from 'react'

const Divvy = () => <div>hiii</div>

export default Divvy
import React from "react";
import Divvy from '~/components/Divvy'

export { Page };

function Page({countries}) {
  return (
    <>
    <Divvy />
      <p className="bg-blue-200">A okay text.</p>
      {countries.map(c => <div key={c.code}>{c.name}</div>)}
    </>
  );
}
//tsconfig.json
"baseUrl": "./src",
    "paths": {
      "~/*": ["./*"]
    }
// vite.config.ts
import reactRefresh from "@vitejs/plugin-react-refresh";
import ssr from "vite-plugin-ssr";
import { defineConfig } from "vite";
import { resolve } from "path";

export default defineConfig({
  resolve: {
    alias: {
      "~": resolve(__dirname, "src"),
    },
  },
  plugins: [reactRefresh(), ssr()],
})

Error: Failed to resolve import "/components/Divvy". Does the file exist?
at formatError (/Users/home1/code/vite-ssr-project/node_modules/vite/dist/node/chunks/dep-e0f09032.js:45829:46)
at TransformContext.error (/Users/home1/code/vite-ssr-project/node_modules/vite/dist/node/chunks/dep-e0f09032.js:45825:19)
at normalizeUrl (/Users/home1/code/vite-ssr-project/node_modules/vite/dist/node/chunks/dep-e0f09032.js:47321:26)
at processTicksAndRejections (internal/process/task_queues.js:93:5)
at TransformContext.transform (/Users/home1/code/vite-ssr-project/node_modules/vite/dist/node/chunks/dep-e0f09032.js:47450:57)
at Object.transform (/Users/home1/code/vite-ssr-project/node_modules/vite/dist/node/chunks/dep-e0f09032.js:46027:30)
at transformRequest (/Users/home1/code/vite-ssr-project/node_modules/vite/dist/node/chunks/dep-e0f09032.js:61631:29)
at instantiateModule (/Users/home1/code/vite-ssr-project/node_modules/vite/dist/node/chunks/dep-e0f09032.js:67994:10) {
plugin: 'vite:import-analysis',
id: '/Users/home1/code/vite-ssr-project/src/pages/tests/index.page.tsx',
pluginCode: 'import React from "react";\n' +
'import Divvy from "
/components/Divvy";\n' +
'export {Page};\n' +
'function Page({countries}) {\n' +
' return /* @PURE / React.createElement(React.Fragment, null, / @PURE / React.createElement(Divvy, null), / @PURE / React.createElement("p", {\n' +
' className: "bg-blue-200"\n' +
' }, "A okay text."), countries.map((c) => /
@PURE / React.createElement("div", {\n' +
' key: c.code\n' +
' }, c.name)));\n' +
'}\n',
loc: {
file: '/Users/home1/code/vite-ssr-project/src/pages/tests/index.page.tsx',
line: 2,
column: 20
},
frame: '1 | import React from "react";\n' +
'2 | import Divvy from "/components/Divvy";\n' +
' | ^\n' +
'3 | export {Page};\n' +
'4 | function Page({countries}) {'
}
Error: Failed to resolve import "
/components/Divvy". Does the file exist?
at formatError (/Users/home1/code/vite-ssr-project/node_modules/vite/dist/node/chunks/dep-e0f09032.js:45829:46)
at TransformContext.error (/Users/home1/code/vite-ssr-project/node_modules/vite/dist/node/chunks/dep-e0f09032.js:45825:19)
at normalizeUrl (/Users/home1/code/vite-ssr-project/node_modules/vite/dist/node/chunks/dep-e0f09032.js:47321:26)
at processTicksAndRejections (internal/process/task_queues.js:93:5)
at TransformContext.transform (/Users/home1/code/vite-ssr-project/node_modules/vite/dist/node/chunks/dep-e0f09032.js:47450:57)
at Object.transform (/Users/home1/code/vite-ssr-project/node_modules/vite/dist/node/chunks/dep-e0f09032.js:46027:30)
at transformRequest (/Users/home1/code/vite-ssr-project/node_modules/vite/dist/node/chunks/dep-e0f09032.js:61631:29)
at instantiateModule (/Users/home1/code/vite-ssr-project/node_modules/vite/dist/node/chunks/dep-e0f09032.js:67994:10) {
plugin: 'vite:import-analysis',
id: '/Users/home1/code/vite-ssr-project/src/pages/tests/index.page.tsx',
pluginCode: 'import React from "react";\n' +
'import Divvy from "~/components/Divvy";\n' +
'export {Page};\n' +
'function Page({countries}) {\n' +
' return /
@PURE / React.createElement(React.Fragment, null, / @PURE / React.createElement(Divvy, null), / @PURE / React.createElement("p", {\n' +
' className: "bg-blue-200"\n' +
' }, "A okay text."), countries.map((c) => /
@PURE */ React.createElement("div", {\n' +
' key: c.code\n' +
' }, c.name)));\n' +
'}\n',
loc: {
file: '/Users/home1/code/vite-ssr-project/src/pages/tests/index.page.tsx',
line: 2,
column: 20
},
frame: '1 | import React from "react";\n' +
'2 | import Divvy from "~/components/Divvy";\n' +
' | ^\n' +
'3 | export {Page};\n' +
'4 | function Page({countries}) {'
}


Thank you so much for any pointers!

Lydia

@brillout
Copy link
Member

if I were to not use client.query in addContextProps, and instead just use useQuery in the page component, is it correct that the data would not be 'sent down' initially, and the data would only be fetched on render, thus not benefitting from ssr?

Correct.

so what we need to do is use client.query as above, and use useQuery() in the components, to get all the bells and whistles?

While it would work, it's better if you don't do that: because otherwise you end up fetching your data twice which is wasteful (and can lead to hydration mismatch if the data changes in-between the two requests).

Apollo's SSR doc looks good at first glance, check it out https://www.apollographql.com/docs/react/performance/server-side-rendering/.

Note that instead of doing

const client = new ApolloClient({
  cache: new InMemoryCache().restore(JSON.parse(window.__APOLLO_STATE__)),
  uri: 'https://example.com/graphql'
});

you can do

// pages/tests/index.page.client.js

const client = new ApolloClient({
  cache: new InMemoryCache().restore(pageProps.initialApolloState),
  uri: 'https://example.com/graphql'
});
// pages/tests/index.page.server.js

import { html } from 'vite-plugin-ssr'
import { getDataFromTree } from "@apollo/client/react/ssr";

export { addContextProps }
export { setPageProps }
export { render }

// Note that `Page` is as well available to `addContextProps()`
async function addContextProps({ Page, contextProps }) {
  let pageHtml;
  let initialApolloState;
  getDataFromTree(Page).then((_pageHtml) => {
    pageHtml = _pageHtml
    initialApolloState = client.extract();
  });

  return { pageHtml, initialApolloState }
}

async function render({ contextProps }) {
  // Note how `pageHtml` is *not* computed in `render()` but provided by `addContextProps()`
  const { pageHtml } = contextProps
  return html`<!DOCTYPE html>
    <html>
      <body>
        <div id="page-root">${html.dangerouslySetHtml(pageHtml)}</div>
      </body>
    </html>`
}

function setPageProps({ contextProps: { initialApolloState } }) {
  // We pass `initialApolloState` to the browser
  return { initialApolloState }
}

About Q2, vite-plugin-ssr doesn't intefere with any of that. Very unlikely that the problem comes from vite-plugin-ssr. (But it may very well come from the fact that you are doing SSR and hence your code is being consumed by the browser as well as by Node.js.)

@LydiaF
Copy link
Author

LydiaF commented Mar 15, 2021

Dear Romuald,

I am so, so, so grateful for your help. Thank you. I apologise if this is taking too much of your time, please reply whenever you have time, if at all.

I have tried to implement what you are saying but I have failed. I am getting
Error: [vite-plugin-ssr][Wrong Usage] [html.dangerouslySetHtml(str)] Argument `str` should be a string but we got `typeof str === "undefined".

In this part of your code, you are using a client...

 getDataFromTree(Page).then((_pageHtml) => {
    pageHtml = _pageHtml
    initialApolloState = client.extract();
  });

As you are using extract(), I am guessing that this client is equivalent to the ssr: true client in the apollo docs you linked. server/index.ts seems to be equivalent to what is in the apollo docs, and I thought that I could put the client on the context there so I could access it in pages/tests/index.page.server.js, so I changed server/index.ts to:

// server/index.ts
import express from "express";
import { createPageRender } from "vite-plugin-ssr";
import * as vite from "vite";
import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client'
import fetch from 'cross-fetch'

const isProduction = process.env.NODE_ENV === "production";
const root = `${__dirname}/..`;

startServer();

async function startServer() {
  const app = express();

  let viteDevServer;
  if (isProduction) {
    app.use(express.static(`${root}/dist/client`, { index: false }));
  } else {
    viteDevServer = await vite.createServer({
      root,
      server: { middlewareMode: true },
    });
    app.use(viteDevServer.middlewares);
  }

  const renderPage = createPageRender({ viteDevServer, isProduction, root });
  app.get("*", async (req, res, next) => {
    const client = new ApolloClient({
      ssrMode: true,
      link: createHttpLink({
        uri: 'https://countries.trevorblades.com',
        credentials: 'same-origin',
        headers: {
          cookie: req.header('Cookie'),
        },
        fetch
      }),
      cache: new InMemoryCache(),
    })

    const url = req.originalUrl;
    const contextProps = { client };
    const result = await renderPage({ url, contextProps });
    if (result.nothingRendered) return next();
    res.status(result.statusCode).send(result.renderResult);
  });

  const port = 3000;
  app.listen(port);
  console.log(`Server running at http://localhost:${port}`);
}

I am confident that this client and query are working as this is working:

//pages/tests/index.page.server.ts
async function addContextProps({ contextProps }) {
  const response = await contextProps.client.query({query: LIST_COUNTRIES})

  return { countries: response.data.countries }
}

There are my other files...

// pages/tests/index.page/server.ts
import { html } from 'vite-plugin-ssr'
import { getDataFromTree } from "@apollo/client/react/ssr";

export { addContextProps }
export { setPageProps }
export { render }

async function addContextProps({ Page, contextProps }) {
  let pageHtml;
  let initialApolloState;
  getDataFromTree(Page).then((_pageHtml) => {
    pageHtml = _pageHtml
    initialApolloState = contextProps.client.extract(); // <- ssr: true client
  });

  return { pageHtml, initialApolloState }
}

async function render({ contextProps }) {
  const { pageHtml } = contextProps
  return html`<!DOCTYPE html>
    <html>
      <body>
        <div id="page-root">${html.dangerouslySetHtml(pageHtml)}</div>
      </body>
    </html>`
}

function setPageProps({ contextProps: { initialApolloState } }) {
  return { initialApolloState }
}

These are my other files:

//pages/tests2/index.page.server.ts
import { html } from 'vite-plugin-ssr'
import { getDataFromTree } from "@apollo/client/react/ssr";

export { addContextProps }
export { setPageProps }
export { render }

async function addContextProps({ Page, contextProps }) {
  let pageHtml;
  let initialApolloState;
  getDataFromTree(Page).then((_pageHtml) => {
    pageHtml = _pageHtml
    initialApolloState = contextProps.client.extract();
  });

  return { pageHtml, initialApolloState }
}

async function render({ contextProps }) {
  const { pageHtml } = contextProps
  return html`<!DOCTYPE html>
    <html>
      <body>
        <div id="page-root">${html.dangerouslySetHtml(pageHtml)}</div>
      </body>
    </html>`
}

function setPageProps({ contextProps: { initialApolloState } }) {
  return { initialApolloState }
}
//pages/tests2/index.page.client.ts

import {ApolloClient, InMemoryCache, ApolloProvider} from '@apollo/client'
import ReactDOM from "react-dom";
import React from "react";
import { getPage } from "vite-plugin-ssr/client";

hydrate();

async function hydrate() {
 const { Page, pageProps } = await getPage();

 const client = new ApolloClient({
   cache: new InMemoryCache().restore(pageProps.initialApolloState),
   uri: 'https://countries.trevorblades.com'
 });

 ReactDOM.hydrate(
   <ApolloProvider client={client}><Page {...pageProps} /></ApolloProvider>,
   document.getElementById("page-root")
 );
}
// pages/tests2/index.page.tsx
import { gql, useQuery } from '@apollo/client'
import React from "react";

export { Page };

export const LIST_COUNTRIES = gql`
  {
    countries {
      name
      code
    }
  }
`;

function Page() {
  const {data} = useQuery(LIST_COUNTRIES)

  console.log('data', data)

  return (
    <>
      <p className="bg-blue-200">Tests2 </p>
    </>
  );
}

@brillout
Copy link
Member

Make sure to use await:

await getDataFromTree(Page).then((_pageHtml)

Otherwise pageHtml won't get set.

Also you should initialize the Apollo client only once at startup time, instead of initializing it on every HTTP request.

@LydiaF
Copy link
Author

LydiaF commented Mar 17, 2021

Thanks so much for your help with this, when we are up and running we will submit a PR :)

@LydiaF LydiaF closed this as completed Mar 17, 2021
@brillout
Copy link
Member

So did it work?

Looking foward to your PR.

@LydiaF
Copy link
Author

LydiaF commented Mar 17, 2021

Well... no!

But our best guess at the moment is that apollo's getDataFromTree() is not working for some reason. It's not returning anything. We also tried renderToStringWithData() but that also didn't work :S

If you're interested...
https://github.com/LydiaF/vite-plugin-ssr-apollo

@LydiaF
Copy link
Author

LydiaF commented Mar 19, 2021

Hey, I got it working :)

Will send a PR example tomorrow.

Thanks so much for your help!

@brillout
Copy link
Member

Love it :-). Let me know if you need further help.

@felixakiragreen
Copy link

@LydiaF did you figure out the import mapping issue you ran into? I'm encountering the same one. Any help would be incredibly appreciated.

@LydiaF
Copy link
Author

LydiaF commented Jun 4, 2021

Hey @felixakiragreen, I deleted the last comment mistakenly if you read it, if you didnt: no good news from me. We couldn't fix it in the end and I had spent ages refactoring so I didn't get the circ dep warnings, but it still didn't work. I can't remember what exact the issue was but we figured it was probably the ssr circular dep issue in the vite repo and we decided, with heavy hearts, to let ssr go until it was fixed!

vitejs/vite#2258

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

No branches or pull requests

3 participants