Skip to content

Commit

Permalink
Merge pull request #6499 from apollographql/add-react-ssr
Browse files Browse the repository at this point in the history
Migrate all @apollo/react-ssr features to @apollo/client/react/ssr
  • Loading branch information
benjamn authored Jun 30, 2020
2 parents cea0bc9 + 65851ef commit 83e6014
Show file tree
Hide file tree
Showing 9 changed files with 364 additions and 13 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@
```
[@hwillson](https://github.com/hwillson) in [#5357](https://github.com/apollographql/apollo-client/pull/5357)

- React SSR features (previously accessed via `@apollo/react-ssr`) can now be accessed from the separate Apollo Client entry point of `@apollo/client/react/ssr`. These features are not included in the default `@apollo/client` bundle. <br/>
[@hwillson](https://github.com/hwillson) in [#6499](https://github.com/apollographql/apollo-client/pull/6499)

### General

- **[BREAKING]** Removed `graphql-anywhere` since it's no longer used by Apollo Client. <br/>
Expand Down
33 changes: 22 additions & 11 deletions config/prepareDist.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,16 @@ fs.copyFileSync(`${srcDir}/README.md`, `${destDir}/README.md`);
fs.copyFileSync(`${srcDir}/LICENSE`, `${destDir}/LICENSE`);


/* @apollo/client/core, @apollo/client/cache, @apollo/client/utilities */

function buildPackageJson(bundleName) {
/*
* @apollo/client/core
* @apollo/client/cache
* @apollo/client/utilities
* @apollo/client/react/ssr
*/

function buildPackageJson(bundleName, entryPoint) {
return JSON.stringify({
name: `@apollo/client/${bundleName}`,
name: `@apollo/client/${entryPoint || bundleName}`,
main: `${bundleName}.cjs.js`,
module: 'index.js',
types: 'index.d.ts',
Expand Down Expand Up @@ -91,13 +96,14 @@ function writeCjsIndex(bundleName, exportNames, includeNames = true) {
].join('\n'));
}

// Create `core`, `cache` and `utilities` bundle package.json files, storing
// them in their associated dist directory. This helps provide a way for the
// Apollo Client core to be used without React (via `@apollo/client/core`),
// and AC's cache and utilities to be used by themselves
// (`@apollo/client/cache` and `@apollo/client/utilities`), via the
// `core.cjs.js`, `cache.cjs.js` and `utilities.cjs.js` CommonJS entry point
// files that only include the exports needed for each bundle.
// Create `core`, `cache`, `utilities` and `ssr` bundle package.json files,
// storing them in their associated dist directory. This helps provide a way
// for the Apollo Client core to be used without React
// (via `@apollo/client/core`), as well as AC's cache, utilities and SSR to be
// used by themselves (`@apollo/client/cache`, `@apollo/client/utilities`,
// `@apollo/client/react/ssr`), via the `core.cjs.js`, `cache.cjs.js`,
// `utilities.cjs.js` and `ssr.cjs.js` CommonJS entry point files that only
// include the exports needed for each bundle.

fs.writeFileSync(`${distRoot}/core/package.json`, buildPackageJson('core'));
writeCjsIndex('core', loadExportNames('react'), false);
Expand All @@ -109,3 +115,8 @@ fs.writeFileSync(
`${distRoot}/utilities/package.json`,
buildPackageJson('utilities')
);

fs.writeFileSync(
`${distRoot}/react/ssr/package.json`,
buildPackageJson('ssr', 'react/ssr')
);
18 changes: 18 additions & 0 deletions config/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,13 +172,31 @@ function prepareTesting() {
};
}

function prepareReactSSR() {
const ssrDistDir = `${distDir}/react/ssr`;
return {
input: `${ssrDistDir}/index.js`,
external,
output: {
file: `${ssrDistDir}/ssr.cjs.js`,
format: 'cjs',
sourcemap: true,
exports: 'named',
},
plugins: [
nodeResolve(),
],
};
}

function rollup() {
return [
prepareESM(packageJson.module, distDir),
prepareCJS(packageJson.module, packageJson.main),
prepareCJSMinified(packageJson.main),
prepareUtilities(),
prepareTesting(),
prepareReactSSR(),
];
}

Expand Down
13 changes: 11 additions & 2 deletions docs/source/migrating/apollo-client-3-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ import { MockedProvider } from '@apollo/client/testing';

As part of migrating, we recommend removing all `@apollo/react-testing` dependencies.

### @apollo/react-ssr

React Apollo’s SSR utilities (like `getDataFromTree`, `getMarkupFromTree`, and `renderToStringWithData`) are included in the `@apollo/client` package. Access them via `@apollo/client/react/ssr`:

```js
import { renderToStringWithData } from '@apollo/client/react/ssr';
```

As part of migrating, we recommend removing all `@apollo/react-ssr` dependencies.

### react-apollo

`react-apollo` v3 is an umbrella package that re-exports the following packages:
Expand All @@ -72,11 +82,10 @@ As part of migrating, we recommend removing all `@apollo/react-testing` dependen
- `@apollo/react-ssr`
- `@apollo/react-testing`

Because `@apollo/client` includes functionality from `@apollo/react-common`, `@apollo/react-hooks` and `@apollo/react-testing`, we've released a v4 version of `react-apollo` that includes only the following:
Because `@apollo/client` includes functionality from `@apollo/react-common`, `@apollo/react-hooks`, `@apollo/react-ssr` and `@apollo/react-testing`, we've released a v4 version of `react-apollo` that includes only the following:

- `@apollo/react-components`
- `@apollo/react-hoc`
- `@apollo/react-ssr`

This version re-exports the remainder of React functionality directly from `@apollo/client`, so if you upgrade to `react-apollo` v4 you should still have access to everything you had in v3. That being said, we recommend removing all `react-apollo` dependencies and directly installing whichever `@apollo/react-*` packages you need.

Expand Down
72 changes: 72 additions & 0 deletions src/react/ssr/__tests__/useLazyQuery.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React from 'react';
import { DocumentNode } from 'graphql';
import gql from 'graphql-tag';
import { mockSingleLink } from '../../../utilities/testing/mocking/mockLink';
import { ApolloClient } from '../../../ApolloClient';
import { InMemoryCache } from '../../../cache/inmemory/inMemoryCache';
import { ApolloProvider } from '../../context/ApolloProvider';
import { useLazyQuery } from '../../hooks/useLazyQuery';
import { renderToStringWithData } from '..';

describe('useLazyQuery Hook SSR', () => {
const CAR_QUERY: DocumentNode = gql`
query {
cars {
make
model
vin
}
}
`;

const CAR_RESULT_DATA = {
cars: [
{
make: 'Audi',
model: 'RS8',
vin: 'DOLLADOLLABILL',
__typename: 'Car'
}
]
};

it('should run query only after calling the lazy mode execute function', () => {
const link = mockSingleLink({
request: { query: CAR_QUERY },
result: { data: CAR_RESULT_DATA }
});

const client = new ApolloClient({
cache: new InMemoryCache(),
link,
ssrMode: true
});

const Component = () => {
let html = null;
const [execute, { loading, called, data }] = useLazyQuery(CAR_QUERY);

if (!loading && !called) {
execute();
}

if (!loading && called) {
expect(loading).toEqual(false);
expect(data).toEqual(CAR_RESULT_DATA);
html = <p>{data.cars[0].make}</p>;
}

return html;
};

const app = (
<ApolloProvider client={client}>
<Component />
</ApolloProvider>
);

return renderToStringWithData(app).then(markup => {
expect(markup).toMatch(/Audi/);
});
});
});
170 changes: 170 additions & 0 deletions src/react/ssr/__tests__/useQuery.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import React from 'react';
import { DocumentNode } from 'graphql';
import gql from 'graphql-tag';
import { MockedProvider } from '../../../utilities/testing/mocking/MockedProvider';
import { mockSingleLink } from '../../../utilities/testing/mocking/mockLink';
import { ApolloClient } from '../../../ApolloClient';
import { InMemoryCache } from '../../../cache/inmemory/inMemoryCache';
import { ApolloProvider } from '../../context/ApolloProvider';
import { useQuery } from '../../hooks/useQuery';
import { render, wait } from '@testing-library/react';
import { renderToStringWithData } from '..';

describe('useQuery Hook SSR', () => {
const CAR_QUERY: DocumentNode = gql`
query {
cars {
make
model
vin
}
}
`;

const CAR_RESULT_DATA = {
cars: [
{
make: 'Audi',
model: 'RS8',
vin: 'DOLLADOLLABILL',
__typename: 'Car'
}
]
};

const CAR_MOCKS = [
{
request: {
query: CAR_QUERY
},
result: { data: CAR_RESULT_DATA }
}
];

it('should support SSR', () => {
const Component = () => {
const { loading, data } = useQuery(CAR_QUERY);
if (!loading) {
expect(data).toEqual(CAR_RESULT_DATA);
const { make, model, vin } = data.cars[0];
return (
<div>
{make}, {model}, {vin}
</div>
);
}
return null;
};

const app = (
<MockedProvider mocks={CAR_MOCKS}>
<Component />
</MockedProvider>
);

return renderToStringWithData(app).then(markup => {
expect(markup).toMatch(/Audi/);
});
});

it('should initialize data as `undefined` when loading', () => {
const Component = () => {
const { data, loading } = useQuery(CAR_QUERY);
if (loading) {
expect(data).toBeUndefined();
}
return null;
};

const app = (
<MockedProvider mocks={CAR_MOCKS}>
<Component />
</MockedProvider>
);

return renderToStringWithData(app);
});

it('should skip SSR tree rendering if `ssr` option is `false`', async () => {
let renderCount = 0;
const Component = () => {
const { data, loading } = useQuery(CAR_QUERY, { ssr: false });
renderCount += 1;

if (!loading) {
const { make } = data.cars[0];
return <div>{make}</div>;
}
return null;
};

const app = (
<MockedProvider mocks={CAR_MOCKS}>
<Component />
</MockedProvider>
);

return renderToStringWithData(app).then(result => {
expect(renderCount).toBe(1);
expect(result).toEqual('');
});
});

it(
'should skip both SSR tree rendering and SSR component rendering if ' +
'`ssr` option is `false` and `ssrMode` is `true`',
async () => {
const link = mockSingleLink({
request: { query: CAR_QUERY },
result: { data: CAR_RESULT_DATA }
});

const client = new ApolloClient({
cache: new InMemoryCache(),
link,
ssrMode: true
});

let renderCount = 0;
const Component = () => {
const { data, loading } = useQuery(CAR_QUERY, { ssr: false });

let content = null;
switch (renderCount) {
case 0:
expect(loading).toBeTruthy();
expect(data).toBeUndefined();
break;
case 1: // FAIL; should not render a second time
default:
}

renderCount += 1;
return content;
};

const app = (
<ApolloProvider client={client}>
<Component />
</ApolloProvider>
);

await renderToStringWithData(app).then(result => {
expect(renderCount).toBe(1);
expect(result).toEqual('');
});

renderCount = 0;

render(
<ApolloProvider client={client}>
<Component />
</ApolloProvider>
);

await wait(() => {
expect(renderCount).toBe(1);
});
}
);
});
Loading

0 comments on commit 83e6014

Please sign in to comment.