Skip to content

Commit

Permalink
[Proposal] Lazy load views with suspense (#749)
Browse files Browse the repository at this point in the history
* Add lazy loading stuff to field-views-loader

* More stuff

* Use render prop for target in withModalHandlers

I'm working on code splitting things with suspense and things are breaking because of the findDOMNode calls in react-node-resolver

Slightly related to this: @jossmac, i think you'll like this, reactjs/rfcs#97

* Update types

* Fix linting error

* Fix a thing

* Fix thing

* Works in some cases

* Fix a thing

* Add a Suspense component around page content

* Update CreateItemModal in Relationship field type

* Update UpdateManyItemsModal

* Everything is _mostly_ working now

* Fix a thing

* Fix some things

* forwardRef Pill

* Fix things

* Fix a thing

* Add changeset

* Add changeset

* Add changeset for @arch-ui/layout changes

* Add `compression` and change webpack config stuff so it works how it will work when it's on npm as a test

* Change webpack stuff back

* Try a thing to fix the access control tests

* Only do gzipping in prod
  • Loading branch information
emmatown authored Mar 25, 2019
1 parent df9ec5c commit 3f075eb
Show file tree
Hide file tree
Showing 27 changed files with 537 additions and 301 deletions.
40 changes: 40 additions & 0 deletions .changeset/1d357d19/changes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"releases": [
{ "name": "@voussoir/admin-ui", "type": "major" },
{ "name": "@voussoir/fields", "type": "major" },
{ "name": "@voussoir/field-views-loader", "type": "minor" }
],
"dependents": [
{
"name": "keystone_demo_blog",
"type": "patch",
"dependencies": ["@voussoir/core", "@voussoir/admin-ui", "@voussoir/fields"]
},
{
"name": "@voussoir/demo-project-todo",
"type": "patch",
"dependencies": ["@voussoir/core", "@voussoir/admin-ui", "@voussoir/fields"]
},
{
"name": "@voussoir/cypress-project-access-control",
"type": "patch",
"dependencies": ["@voussoir/core", "@voussoir/admin-ui", "@voussoir/fields"]
},
{
"name": "@voussoir/cypress-project-basic",
"type": "patch",
"dependencies": ["@voussoir/core", "@voussoir/admin-ui", "@voussoir/fields"]
},
{
"name": "@voussoir/cypress-project-login",
"type": "patch",
"dependencies": ["@voussoir/core", "@voussoir/admin-ui", "@voussoir/fields"]
},
{
"name": "@voussoir/cypress-project-twitter-login",
"type": "patch",
"dependencies": ["@voussoir/core", "@voussoir/admin-ui", "@voussoir/fields"]
},
{ "name": "@voussoir/core", "type": "patch", "dependencies": ["@voussoir/fields"] }
]
}
1 change: 1 addition & 0 deletions .changeset/1d357d19/changes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Lazy load field type views with suspense
16 changes: 16 additions & 0 deletions .changeset/2ae667cf/changes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"releases": [{ "name": "@arch-ui/layout", "type": "minor" }],
"dependents": [
{
"name": "@voussoir/admin-ui",
"type": "patch",
"dependencies": ["@voussoir/fields", "@arch-ui/filters", "@arch-ui/layout"]
},
{
"name": "@voussoir/fields",
"type": "patch",
"dependencies": ["@arch-ui/filters", "@arch-ui/layout"]
},
{ "name": "@arch-ui/filters", "type": "patch", "dependencies": ["@arch-ui/layout"] }
]
}
1 change: 1 addition & 0 deletions .changeset/2ae667cf/changes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Forward refs passed to FlexGroup to the container element
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"body-parser": "^1.18.2",
"chrono-node": "^1.3.5",
"cloudinary": "^1.13.2",
"compression": "^1.7.4",
"cookie": "^0.3.1",
"cookie-signature": "^1.1.0",
"cors": "^2.8.4",
Expand Down
9 changes: 2 additions & 7 deletions packages/admin-ui/client/apolloClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ import { onError } from 'apollo-link-error';
import { InMemoryCache } from 'apollo-cache-inmemory';
import ApolloClient from 'apollo-client';

import { adminMeta } from './providers/AdminMeta';
const { apiPath } = adminMeta;

const fetch = require('cross-fetch');

// Ejected from apollo-boost v0.1.4:
Expand All @@ -17,7 +14,7 @@ const fetch = require('cross-fetch');
// Then modified to replace apollo-link-http with apollo-upload-client:
// https://github.com/jaydenseric/apollo-upload-client

class BoostClientWithUplaod extends ApolloClient {
class BoostClientWithUpload extends ApolloClient {
constructor(config) {
const cache =
config && config.cacheRedirects
Expand Down Expand Up @@ -83,6 +80,4 @@ class BoostClientWithUplaod extends ApolloClient {
}
}

export default new BoostClientWithUplaod({
uri: apiPath,
});
export default BoostClientWithUpload;
3 changes: 2 additions & 1 deletion packages/admin-ui/client/classes/List.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ export const gqlCountQueries = lists => gql`{
export default class List {
constructor(config, adminMeta, views) {
this.config = config;
this.adminMeta = adminMeta;

// TODO: undo this
Object.assign(this, config);

this.fields = config.fields.map(fieldConfig => {
const { Controller } = views[fieldConfig.path];
const [Controller] = adminMeta.readViews([views[fieldConfig.path].Controller]);
return new Controller(fieldConfig, this, adminMeta, views[fieldConfig.path]);
});

Expand Down
30 changes: 12 additions & 18 deletions packages/admin-ui/client/components/AnimateHeight.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@

/** @jsx jsx */
import { jsx } from '@emotion/core';
import { Component, type Element, type Ref, type Node, useMemo } from 'react';
import { Component, type Ref, type Node, useMemo } from 'react';
import ResizeObserver from 'resize-observer-polyfill';
import NodeResolver from 'react-node-resolver';

type Height = number | string;
type Props = {
autoScroll: boolean | HTMLElement,
children?: Element<*>,
initialHeight: Height,
onChange?: Height => any,
render?: ({ ref: Ref<*> }) => Node,
render: ({ ref: Ref<*> }) => Node,
};
type State = { height: Height, isTransitioning: boolean };

Expand Down Expand Up @@ -88,7 +86,7 @@ export default class AnimateHeight extends Component<Props, State> {
this.calculateHeight();
};
render() {
const { autoScroll, children, initialHeight, render, ...props } = this.props;
const { autoScroll, initialHeight, render, ...props } = this.props;
const { height, isTransitioning } = this.state;
const overflow = isTransitioning ? 'hidden' : null;

Expand All @@ -109,19 +107,15 @@ export default class AnimateHeight extends Component<Props, State> {
}}
{...props}
>
{render ? (
<Memoize
// this.getNode will never change so i'm not including it in the deps
// render will probably change a bunch but that's fine, the reason for
// memoizing this is so that state updates inside of AnimateHeight don't
// cause a bunch of rerenders of children
deps={[render]}
>
{() => render({ ref: this.getNode })}
</Memoize>
) : (
<NodeResolver innerRef={this.getNode}>{children}</NodeResolver>
)}
<Memoize
// this.getNode will never change so i'm not including it in the deps
// render will probably change a bunch but that's fine, the reason for
// memoizing this is so that state updates inside of AnimateHeight don't
// cause a bunch of rerenders of children
deps={[render]}
>
{() => render({ ref: this.getNode })}
</Memoize>
</div>
);
}
Expand Down
26 changes: 19 additions & 7 deletions packages/admin-ui/client/components/CreateItemModal.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/** @jsx jsx */
import { jsx } from '@emotion/core';
import { Component, Fragment, useCallback, useMemo } from 'react';
import { Component, Fragment, useCallback, useMemo, Suspense } from 'react';
import { Mutation } from 'react-apollo';

import { Button } from '@arch-ui/button';
Expand Down Expand Up @@ -61,6 +61,16 @@ class CreateItemModal extends Component {
render() {
const { isLoading, isOpen, list } = this.props;
const { item } = this.state;
// we want to read all of the fields before reading the views individually
// note we want to _read_ before not just preload because the important thing
// isn't doing all the requests in parallel, that already happens
// what we're doing here is making sure there aren't a bunch of rerenders as
// each of the promises resolve
// this probably won't be necessary with concurrent mode/maybe just other react changes
// also, note that this is just an optimisation, it's not strictly necessary and it should
// probably be removed in the future because i'm guessing this will make performance _worse_ in concurrent mode
list.adminMeta.readViews(list.fields.map(({ views }) => views.Field));

return (
<Drawer
closeOnBlanketClick
Expand Down Expand Up @@ -89,10 +99,10 @@ class CreateItemModal extends Component {
>
<AutocompleteCaptor />
{list.fields.map((field, i) => {
const { Field } = field.views;
return (
<Render key={field.path}>
{() => {
let [Field] = field.adminMeta.readViews([field.views.Field]);
let onChange = useCallback(value => {
this.setState(({ item }) => ({
item: {
Expand Down Expand Up @@ -129,11 +139,13 @@ export default class CreateItemModalWithMutation extends Component {
render() {
const { list } = this.props;
return (
<Mutation mutation={list.createMutation}>
{(createItem, { loading }) => (
<CreateItemModal createItem={createItem} isLoading={loading} {...this.props} />
)}
</Mutation>
<Suspense fallback={null}>
<Mutation mutation={list.createMutation}>
{(createItem, { loading }) => (
<CreateItemModal createItem={createItem} isLoading={loading} {...this.props} />
)}
</Mutation>
</Suspense>
);
}
}
4 changes: 2 additions & 2 deletions packages/admin-ui/client/components/ListTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,9 +231,9 @@ class ListRow extends Component {

let content;

const { Cell } = field.views;
if (field.views.Cell) {
const [Cell] = field.adminMeta.readViews([field.views.Cell]);

if (Cell) {
// TODO
// fix this later, creating a react component on every render is really bad
// react will rerender into the DOM on every react render
Expand Down
16 changes: 9 additions & 7 deletions packages/admin-ui/client/components/UpdateManyItemsModal.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { Component, Fragment, useMemo, useCallback } from 'react';
import React, { Component, Fragment, useMemo, useCallback, Suspense } from 'react';
import { Mutation } from 'react-apollo';
import { Button } from '@arch-ui/button';
import Drawer from '@arch-ui/drawer';
Expand Down Expand Up @@ -99,10 +99,10 @@ class UpdateManyModal extends Component {
</FieldInput>
</FieldContainer>
{selectedFields.map((field, i) => {
const { Field } = field.views;
return (
<Render key={field.path}>
{() => {
let [Field] = field.adminMeta.readViews([field.views.Field]);
let onChange = useCallback(value => {
this.setState(({ item }) => ({
item: {
Expand Down Expand Up @@ -140,11 +140,13 @@ export default class UpdateManyModalWithMutation extends Component {
// to update many things all at once. This doesn't appear to be common pattern
// across the board.
return (
<Mutation mutation={list.updateMutation}>
{(updateItem, { loading }) => (
<UpdateManyModal updateItem={updateItem} isLoading={loading} {...this.props} />
)}
</Mutation>
<Suspense fallback={null}>
<Mutation mutation={list.updateMutation}>
{(updateItem, { loading }) => (
<UpdateManyModal updateItem={updateItem} isLoading={loading} {...this.props} />
)}
</Mutation>
</Suspense>
);
}
}
Loading

0 comments on commit 3f075eb

Please sign in to comment.