awslabs / aws-mobile-appsync-sdk-js

JavaScript library files for Offline, Sync, Sigv4. includes support for React Native
Apache License 2.0
920 stars 266 forks source link

Solution for large bundle size #203

Open barrysteyn opened 6 years ago

barrysteyn commented 6 years ago

Hi There

First, a huge thank you to @manueliglesias - absolutely love appsync.

The problem (as others have pointed out) is the bundle size. It is huge, and the reason it is big is mainly because the entire AWS-SDK is imported. Even if one is not using any feature on the client that needs it. I have two suggestions:

  1. Allow consumers to consume the raw "unprocessed" version, and then in our build process, we can use something like Webpack that would minify and treeshake.
  2. Setup the project in such a way that allows a consumer to configure their options and therefore they can opt out of the expensive (in terms of size) options.

Unfortunately, I could not wait: Using aws-appsync-client in it's current state adds almost 500kb (uncompressed) to our build, which was a blocker. Therefore I stripped things down (and things seem to work). I will provide my solution here for anyone interested, and also @manueliglesias I would appreciate it if you could comment on it.

The following code will create an Apollo client that users openId tokens for auth and subscriptions:

import ApolloClient from 'apollo-client';
import fetch from 'isomorphic-fetch';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import { ApolloLink } from 'apollo-link';
import { getMainDefinition } from 'apollo-utilities';
import { SubscriptionHandshakeLink } from 'aws-appsync/lib/link/subscription-handshake-link'; // importing directly so AWS-SDK does not get introduced
import { NonTerminatingHttpLink } from 'aws-appsync/lib/link/non-terminating-http-link';
import { onError } from 'apollo-link-error';
import { logError } from 'client/common/log';

// The singleton client instance
let apolloClient = null;

// Polyfill fetch() on the server (used by apollo-client)
if (!process.browser) {
  global.fetch = fetch;
}

const createApolloClient = (jwtTokenFunc, initialState) => {

  // Allows for custom logic when a GraphQL or network error occurs
  const onErrorLink = onError(({ graphQLErrors, networkError, response }) => {
    if (graphQLErrors) {
      graphQLErrors.forEach(({ message, locations, path }) => {
        const error = new Error(`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`);
        logError(error);
      });
    }
    if (networkError) logError(new Error(`[Network error]: ${networkError}`));

    response.errors = null;
  });

  // This is very simple auth middleware - if using openId auth token
  // you really only need to provide the token as an authorization field
  // in the header
  const authOpenIdConnectLink = setContext((_, { headers }) => (
    jwtTokenFunc()
      .then(idToken => ({
        headers: {
          ...headers,
          authorization: idToken || '',
        },
      }))
  ));

  // This logic (mostly) copied from appsync's apollo client creation
  const link = ApolloLink.from([
    authOpenIdConnectLink,
    onErrorLink,
    ApolloLink.split(
      (operation) => {
        const { query } = operation;
        const { kind, operation: graphqlOperation } = getMainDefinition(query);
        const isSubscription = kind === 'OperationDefinition' && graphqlOperation === 'subscription';

        return isSubscription;
      },
      ApolloLink.from([
        new NonTerminatingHttpLink('subsInfo', { uri: GRAPHQL_URL }, true),
        new SubscriptionHandshakeLink('subsInfo'),
      ]),
      ApolloLink.from([
        new HttpLink({ uri: GRAPHQL_URL }),
      ]),
    ),
  ]);

  return new ApolloClient({
    connectToDevTools: process.browser,
    ssrMode: !process.browser, // Disables forceFetch on the server (so queries are only run once)
    link,
    cache: new InMemoryCache().restore(initialState),
  });
};
nathanbirrell commented 5 years ago

Any updates here or progress on #290 ?

opeologist commented 5 years ago

this is still an issue and it doesn't seem like any communication here or in the PR linked above has been made as of late. could we please get an update to this, either here or in the PR? will comment on PR as well for visibility

akd-io commented 4 years ago

I would love a comment on this too. I'm looking so much forward to getting to build apps with AppSync. With people calling the aws-appsync library "merely a wrapper of the apollo-client", it surprises me to see the apollo-client only taking up 7% of the bundle size.

Any comments?

akd-io commented 4 years ago

Might I ask how you are doing today @barrysteyn? Still using AppSync with your own custom client?

barrysteyn commented 4 years ago

@akd-io I have not used app sync for sometime. We migrated away from using GraphQL. However, I did this time ago, I really would have thought things would improve by now.

akd-io commented 4 years ago

@barrysteyn I see, thank you for the update. Good luck out there :)

pickfire commented 4 years ago

We use appsync too but we put it under a feature module which save like ~55% (1MB) from the main bundle.