diff --git a/.travis.yml b/.travis.yml index 1c8c2f7a99..56410ab4ca 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,7 @@ script: - npm test - coveralls < ./coverage/lcov.info || true # ignore coveralls error - npm run compile + - npm run bundle - cd examples/create-react-app && npm test && cd ../../ - npm run filesize - python node_modules/travis-weigh-in/weigh_in.py ./dist/index.min.js.gz || true # ignore size errors diff --git a/Changelog.md b/Changelog.md index ab876eeac7..e455bf7861 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,6 +4,8 @@ Expect active development and potentially significant breaking changes in the `0 ### vNext +### 1.3.0 +- Feature: Support tree shaking and smaller (marginally) bundles via rollup [PR #691](https://github.com/apollographql/react-apollo/pull/691) - Fix: Render full markup on the server when using the `cache-and-network` fetchPolicy [PR #688](https://github.com/apollographql/react-apollo/pull/688) ### 1.2.0 diff --git a/examples/create-react-app/src/App.js b/examples/create-react-app/src/App.js index fdeb8362a0..6448cb8c00 100644 --- a/examples/create-react-app/src/App.js +++ b/examples/create-react-app/src/App.js @@ -1,6 +1,6 @@ import React, { Component } from 'react'; import ApolloClient, { createNetworkInterface } from 'apollo-client'; -import { ApolloProvider } from "../../../lib"; +import { ApolloProvider } from "../../../"; import Pokemon from "./Pokemon"; diff --git a/examples/create-react-app/src/Pokemon.js b/examples/create-react-app/src/Pokemon.js index 506a43754d..3c7757c6b5 100644 --- a/examples/create-react-app/src/Pokemon.js +++ b/examples/create-react-app/src/Pokemon.js @@ -1,6 +1,6 @@ import React from 'react'; import gql from 'graphql-tag'; -import { graphql } from '../../../lib'; +import { graphql } from '../../../'; // The data prop, which is provided by the wrapper below contains, // a `loading` key while the query is in flight and posts when it is ready diff --git a/examples/create-react-app/src/Pokemon.test.js b/examples/create-react-app/src/Pokemon.test.js index 8708af34fe..ef7e0db32e 100644 --- a/examples/create-react-app/src/Pokemon.test.js +++ b/examples/create-react-app/src/Pokemon.test.js @@ -1,37 +1,43 @@ -import React from 'react'; -import renderer from 'react-test-renderer'; -import { MockedProvider } from '../../../lib/test-utils'; -import { print } from 'graphql'; -import { addTypenameToDocument } from 'apollo-client/queries/queryTransform'; +import React from "react"; +import renderer from "react-test-renderer"; +import { MockedProvider } from "../../../lib/react-apollo.test-utils.umd"; +import { print } from "graphql"; +import { addTypenameToDocument } from "apollo-client/queries/queryTransform"; -import PokemonWithData, { POKEMON_QUERY, Pokemon, withPokemon } from './Pokemon'; +import PokemonWithData, { + POKEMON_QUERY, + Pokemon, + withPokemon, +} from "./Pokemon"; const mockedData = { pokemon: { - __typename: 'Pokemon', - name: 'Charmander', - image: 'https://img.pokemondb.net/artwork/charmander.jpg', + __typename: "Pokemon", + name: "Charmander", + image: "https://img.pokemondb.net/artwork/charmander.jpg", }, }; const query = addTypenameToDocument(POKEMON_QUERY); -const variables = { name: 'charmander' }; +const variables = { name: "charmander" }; -describe('default export', () => { - it('renders without crashing', () => { +describe("default export", () => { + it("renders without crashing", () => { const output = renderer.create( - + - ) + ); expect(output.toJSON()).toMatchSnapshot(); }); }); -describe('Pokemon enhancer', () => { - it('renders with loading first', (done) => { +describe("Pokemon enhancer", () => { + it("renders with loading first", done => { class Container extends React.Component { componentWillMount() { expect(this.props.data.loading).toBe(true); @@ -41,18 +47,20 @@ describe('Pokemon enhancer', () => { render() { return null; } - }; + } const ContainerWithData = withPokemon(Container); const output = renderer.create( - + ); }); - it('renders data without crashing', (done) => { + it("renders data without crashing", done => { class Container extends React.Component { componentWillReceiveProps(props) { expect(props.data.loading).toBe(false); @@ -62,18 +70,20 @@ describe('Pokemon enhancer', () => { render() { return null; } - }; + } const ContainerWithData = withPokemon(Container); const output = renderer.create( - + ); }); - it('renders with an error correctly', (done) => { + it("renders with an error correctly", done => { try { class Container extends React.Component { componentWillReceiveProps(props) { @@ -83,12 +93,12 @@ describe('Pokemon enhancer', () => { render() { return null; } - }; + } const ContainerWithData = withPokemon(Container); const output = renderer.create( - + ); @@ -98,33 +108,33 @@ describe('Pokemon enhancer', () => { }); }); -describe('Pokemon query', () => { +describe("Pokemon query", () => { // it('should match expected structure', () => { // expect(POKEMON_QUERY).toMatchSnapshot(); // }); - it('should match expected shape', () => { + it("should match expected shape", () => { expect(print(POKEMON_QUERY)).toMatchSnapshot(); }); }); -describe('Pokemon Component', () => { - it('should render a loading state without data', () => { - const output = renderer.create() +describe("Pokemon Component", () => { + it("should render a loading state without data", () => { + const output = renderer.create(); expect(output.toJSON()).toMatchSnapshot(); }); - it('should render an error', () => { - const output = renderer.create() + it("should render an error", () => { + const output = renderer.create( + + ); expect(output.toJSON()).toMatchSnapshot(); }); - it('should render name and image in order', () => { + it("should render name and image in order", () => { const output = renderer.create( ); expect(output.toJSON()).toMatchSnapshot(); }); }); - - diff --git a/package.json b/package.json index db9ccab248..f20eccb123 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,11 @@ { "name": "react-apollo", - "version": "1.2.0", + "version": "1.3.0", "description": "React data container for Apollo Client", - "main": "lib/index.js", - "browser": "lib/browser.js", + "main": "lib/react-apollo.umd.js", + "module": "./lib/index.js", + "jsnext:main": "./lib/index.js", + "browser": "lib/react-apollo.browser.umd.js", "typings": "lib/index.d.ts", "scripts": { "deploy": "npm run compile && npm test && npm publish --tag next && git push --tags", @@ -13,7 +15,8 @@ "posttest": "npm run lint", "filesize": "npm run compile:browser && ./scripts/filesize.js --file=./dist/index.min.js --maxGzip=20", "compile": "tsc", - "compile:browser": "rm -rf ./dist && mkdir ./dist && browserify ./lib/index.js --i react --i apollo-client -o=./dist/index.js && npm run minify:browser && npm run compress:browser", + "bundle": "rollup -c && rollup -c rollup.browser.config.js && rollup -c rollup.test-utils.config.js", + "compile:browser": "rm -rf ./dist && mkdir ./dist && browserify ./lib/react-apollo.browser.umd.js --i graphql-tag --i react --i apollo-client -o=./dist/index.js && npm run minify:browser && npm run compress:browser", "minify:browser": "uglifyjs --compress --mangle --screw-ie8 -o=./dist/index.min.js -- ./dist/index.js", "compress:browser": "./scripts/gzip.js --file=./dist/index.min.js", "watch": "tsc -w", @@ -119,6 +122,7 @@ "redux-form": "^6.0.5", "redux-immutable": "^4.0.0", "redux-loop": "^2.2.2", + "rollup": "^0.41.6", "source-map-support": "^0.4.0", "swapi-graphql": "0.0.6", "travis-weigh-in": "^1.0.2", @@ -129,7 +133,7 @@ "uglify-js": "^2.6.2" }, "dependencies": { - "apollo-client": "^1.2.1", + "apollo-client": "^1.2.2", "graphql-anywhere": "^3.0.0", "graphql-tag": "^2.0.0", "hoist-non-react-statics": "^1.2.0", diff --git a/rollup.browser.config.js b/rollup.browser.config.js new file mode 100644 index 0000000000..53ce135e17 --- /dev/null +++ b/rollup.browser.config.js @@ -0,0 +1,16 @@ +export default { + entry: "lib/browser.js", + dest: "lib/react-apollo.browser.umd.js", + format: "umd", + sourceMap: true, + moduleName: "react-apollo", + onwarn, +}; + +function onwarn(message) { + const suppressed = ["UNRESOLVED_IMPORT", "THIS_IS_UNDEFINED"]; + + if (!suppressed.find(code => message.code === code)) { + return console.warn(message.message); + } +} diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000000..10188409a1 --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,16 @@ +export default { + entry: "lib/index.js", + dest: "lib/react-apollo.umd.js", + format: "umd", + sourceMap: true, + moduleName: "react-apollo", + onwarn, +}; + +function onwarn(message) { + const suppressed = ["UNRESOLVED_IMPORT", "THIS_IS_UNDEFINED"]; + + if (!suppressed.find(code => message.code === code)) { + return console.warn(message.message); + } +} diff --git a/rollup.test-utils.config.js b/rollup.test-utils.config.js new file mode 100644 index 0000000000..fca94c11bd --- /dev/null +++ b/rollup.test-utils.config.js @@ -0,0 +1,16 @@ +export default { + entry: "lib/test-utils.js", + dest: "lib/react-apollo.test-utils.umd.js", + format: "umd", + sourceMap: true, + moduleName: "react-apollo", + onwarn, +}; + +function onwarn(message) { + const suppressed = ["UNRESOLVED_IMPORT", "THIS_IS_UNDEFINED"]; + + if (!suppressed.find(code => message.code === code)) { + return console.warn(message.message); + } +} diff --git a/src/ApolloProvider.tsx b/src/ApolloProvider.tsx index d185be8f80..43a32158d1 100644 --- a/src/ApolloProvider.tsx +++ b/src/ApolloProvider.tsx @@ -6,7 +6,7 @@ import { Store } from 'redux'; import ApolloClient, { ApolloStore } from 'apollo-client'; -import invariant = require('invariant'); +const invariant = require('invariant'); export declare interface ProviderProps { store?: Store; diff --git a/src/browser.ts b/src/browser.ts index ab310d5fd8..1893bd351f 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -1,5 +1,6 @@ export { default as ApolloProvider } from './ApolloProvider'; -export { default as graphql, withApollo, InjectedGraphQLProps } from './graphql'; +export { default as graphql, InjectedGraphQLProps } from './graphql'; +export { withApollo } from './withApollo'; // expose easy way to join queries from redux export { compose } from 'redux'; diff --git a/src/graphql.tsx b/src/graphql.tsx index b6979f1e00..2c3d911905 100644 --- a/src/graphql.tsx +++ b/src/graphql.tsx @@ -6,14 +6,13 @@ import { } from 'react'; import * as PropTypes from 'prop-types'; -// modules don't export ES6 modules -import pick = require('lodash.pick'); +const pick = require('lodash.pick'); import shallowEqual from './shallowEqual'; -import invariant = require('invariant'); -import assign = require('object-assign'); +const invariant = require('invariant'); +const assign = require('object-assign'); -import hoistNonReactStatics = require('hoist-non-react-statics'); +const hoistNonReactStatics = require('hoist-non-react-statics'); import ApolloClient, { ObservableQuery, @@ -95,54 +94,6 @@ function getDisplayName(WrappedComponent) { // Helps track hot reloading. let nextVersion = 0; -export function withApollo( - WrappedComponent, - operationOptions: OperationOption = {}, -) { - - const withDisplayName = `withApollo(${getDisplayName(WrappedComponent)})`; - - class WithApollo extends Component { - static displayName = withDisplayName; - static WrappedComponent = WrappedComponent; - static contextTypes = { client: PropTypes.object.isRequired }; - - // data storage - private client: ApolloClient; // apollo client - - constructor(props, context) { - super(props, context); - this.client = context.client; - - invariant(!!this.client, - `Could not find "client" in the context of ` + - `"${withDisplayName}". ` + - `Wrap the root component in an `, - ); - - } - - getWrappedInstance() { - invariant(operationOptions.withRef, - `To access the wrapped instance, you need to specify ` + - `{ withRef: true } in the options`, - ); - - return (this.refs as any).wrappedInstance; - } - - render() { - const props = assign({}, this.props); - props.client = this.client; - if (operationOptions.withRef) props.ref = 'wrappedInstance'; - return createElement(WrappedComponent, props); - } - } - - // Make sure we preserve any custom statics on the original component. - return hoistNonReactStatics(WithApollo, WrappedComponent, {}); -} - export interface OperationOption { options?: Object | ((props: any) => QueryOptions | MutationOptions); props?: (props: any) => any; diff --git a/src/parser.ts b/src/parser.ts index 9f382b028c..224cce2763 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -5,7 +5,7 @@ import { OperationDefinitionNode, } from 'graphql'; -import invariant = require('invariant'); +const invariant = require('invariant'); export enum DocumentType { Query, diff --git a/src/server.ts b/src/server.ts index 764fe8a0ce..a272e6612b 100644 --- a/src/server.ts +++ b/src/server.ts @@ -2,7 +2,7 @@ import { Children } from 'react'; import * as ReactDOM from 'react-dom/server'; import ApolloClient from 'apollo-client'; -import assign = require('object-assign'); +const assign = require('object-assign'); declare interface Context { diff --git a/src/withApollo.tsx b/src/withApollo.tsx new file mode 100644 index 0000000000..2145845ab3 --- /dev/null +++ b/src/withApollo.tsx @@ -0,0 +1,93 @@ +import { + Component, + createElement, + ComponentClass, + StatelessComponent, +} from 'react'; +import * as PropTypes from 'prop-types'; + +const invariant = require('invariant'); +const assign = require('object-assign'); + +const hoistNonReactStatics = require('hoist-non-react-statics'); + +import ApolloClient, { + ObservableQuery, + MutationQueryReducersMap, + Subscription, + ApolloStore, + ApolloQueryResult, + ApolloError, + FetchPolicy, + FetchMoreOptions, + UpdateQueryOptions, + FetchMoreQueryOptions, + SubscribeToMoreOptions, +} from 'apollo-client'; + +import { + // GraphQLResult, + DocumentNode, +} from 'graphql'; + +import { parser, DocumentType } from './parser'; +import { + OperationOption, + MutationOptions, + QueryOptions, + GraphQLDataProps, + InjectedGraphQLProps, +} from './graphql'; + +function getDisplayName(WrappedComponent) { + return WrappedComponent.displayName || WrappedComponent.name || 'Component'; +} + +export function withApollo( + WrappedComponent, + operationOptions: OperationOption = {}, +) { + + const withDisplayName = `withApollo(${getDisplayName(WrappedComponent)})`; + + class WithApollo extends Component { + static displayName = withDisplayName; + static WrappedComponent = WrappedComponent; + static contextTypes = { client: PropTypes.object.isRequired }; + + // data storage + private client: ApolloClient; // apollo client + + constructor(props, context) { + super(props, context); + this.client = context.client; + + invariant(!!this.client, + `Could not find "client" in the context of ` + + `"${withDisplayName}". ` + + `Wrap the root component in an `, + ); + + } + + getWrappedInstance() { + invariant(operationOptions.withRef, + `To access the wrapped instance, you need to specify ` + + `{ withRef: true } in the options`, + ); + + return (this.refs as any).wrappedInstance; + } + + render() { + const props = assign({}, this.props); + props.client = this.client; + if (operationOptions.withRef) props.ref = 'wrappedInstance'; + return createElement(WrappedComponent, props); + } + } + + // Make sure we preserve any custom statics on the original component. + return hoistNonReactStatics(WithApollo, WrappedComponent, {}); +} + diff --git a/tsconfig.json b/tsconfig.json index ee94646425..35a5715f28 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "target": "es5", - "module": "commonjs", + "module": "es2015", "lib": ["es6", "dom"], "moduleResolution": "node", "sourceMap": true, diff --git a/tslint.json b/tslint.json index 8f3e8223f7..ab21824a08 100644 --- a/tslint.json +++ b/tslint.json @@ -51,13 +51,14 @@ "no-internal-module": true, "no-null-keyword": false, "no-require-imports": false, + "no-var-requires": false, "no-shadowed-variable": true, "no-switch-case-fall-through": true, "no-trailing-whitespace": true, "no-unused-expression": true, "no-use-before-declare": true, "no-var-keyword": true, - "no-var-requires": true, + "no-var-requires": false, "object-literal-sort-keys": false, "one-line": [ true,