denvned / isomorphic-relay

Adds server side rendering support to React Relay
BSD 2-Clause "Simplified" License
242 stars 53 forks source link

When webpack built: Invariant Violation: RelayQueryNode: Abstract class cannot be instantiated. #34

Closed prewk closed 8 years ago

prewk commented 8 years ago

Hi, I saw issue #10 but since I'm not using decorators I didn't want to lead anyone astray.

I've started with the basic relay-starter-kit and worked my way from there. isomorphic-relay works fine with rehydration etc, until... I do a webpack build (Both webpack -p and webpack -d causes this problem).

My dev environment consists of running the server with babel-node and WDS as watch, whereas my production build transpiles the server files with babel and the routes file (The React application entry from isomorphic-relays perspective) as a CommonJS module.

It perhaps sounds more complex than it is, I can open the transpiled server files and just console.log the imported routes module and get the same results as in the dev environment so I don't see why there should be a difference.

Anyway, when I run the built server and visit a route which in the dev env will server-render without any problems, the application crashes with this stack trace:

/Users/oskar/src/my-relay-project/node_modules/react-relay/node_modules/fbjs/node_modules/promise/lib/done.js:10
      throw err;
      ^

Invariant Violation: RelayQueryNode: Abstract class cannot be instantiated.
    at invariant (/Users/oskar/src/my-relay-project/node_modules/react-relay/node_modules/fbjs/lib/invariant.js:38:15)
    at new RelayQueryNode (/Users/oskar/src/my-relay-project/node_modules/react-relay/lib/RelayQuery.js:107:93)
    at createNode (/Users/oskar/src/my-relay-project/node_modules/react-relay/lib/RelayQuery.js:1192:12)
    at /Users/oskar/src/my-relay-project/node_modules/react-relay/lib/RelayQuery.js:178:24
    at Array.forEach (native)
    at /Users/oskar/src/my-relay-project/node_modules/react-relay/lib/RelayQuery.js:174:28
    at RelayQueryRoot.getChildren (/Users/oskar/src/my-relay-project/node_modules/react-relay/lib/RelayQuery.js:186:9)
    at RelayQueryRoot.instrumentedCallback [as getChildren] (/Users/oskar/src/my-relay-project/node_modules/react-relay/lib/RelayProfiler.js:137:40)
    at RelayQueryRoot.hasDeferredDescendant (/Users/oskar/src/my-relay-project/node_modules/react-relay/lib/RelayQuery.js:261:67)
    at RelayQueryRoot.instrumentedCallback [as hasDeferredDescendant] (/Users/oskar/src/my-relay-project/node_modules/react-relay/lib/RelayProfiler.js:137:40)
    at RelayQueryRoot.hasDeferredDescendant (/Users/oskar/src/my-relay-project/node_modules/react-relay/lib/RelayQuery.js:482:81)
    at /Users/oskar/src/my-relay-project/node_modules/react-relay/lib/GraphQLQueryRunner.js:96:64
    at Array.forEach (native)
    at splitAndFlattenQueries (/Users/oskar/src/my-relay-project/node_modules/react-relay/lib/GraphQLQueryRunner.js:95:15)
    at /Users/oskar/src/my-relay-project/node_modules/react-relay/lib/GraphQLQueryRunner.js:177:5
    at /Users/oskar/src/my-relay-project/node_modules/react-relay/lib/RelayTaskQueue.js:118:25
    at /Users/oskar/src/my-relay-project/node_modules/react-relay/lib/RelayTaskQueue.js:124:15
    at RelayTaskQueue._invokeWithinScopedQueue (/Users/oskar/src/my-relay-project/node_modules/react-relay/lib/RelayTaskQueue.js:163:7)
    at /Users/oskar/src/my-relay-project/node_modules/react-relay/lib/RelayTaskQueue.js:153:14
    at RelayTaskQueue._scheduleIfNecessary (/Users/oskar/src/my-relay-project/node_modules/react-relay/lib/RelayTaskQueue.js:180:9)
    at RelayTaskQueue.enqueue (/Users/oskar/src/my-relay-project/node_modules/react-relay/lib/RelayTaskQueue.js:130:10)
    at Object.enqueue (/Users/oskar/src/my-relay-project/node_modules/react-relay/lib/RelayTaskScheduler.js:86:37)
    at runQueries (/Users/oskar/src/my-relay-project/node_modules/react-relay/lib/GraphQLQueryRunner.js:159:22)
    at GraphQLQueryRunner.forceFetch (/Users/oskar/src/my-relay-project/node_modules/react-relay/lib/GraphQLQueryRunner.js:82:12)
    at RelayContext.forceFetch (/Users/oskar/src/my-relay-project/node_modules/react-relay/lib/RelayContext.js:98:45)
    at /Users/oskar/src/my-relay-project/node_modules/isomorphic-relay/lib/prepareData.js:47:22
    at new Promise (/Users/oskar/src/my-relay-project/node_modules/babel-polyfill/node_modules/core-js/modules/es6.promise.js:197:7)
    at Promise.exports.(anonymous function).target.(anonymous function).function.target.(anonymous function).F (/Users/oskar/src/my-relay-project/node_modules/isomorphic-relay/node_modules/babel-runtime/node_modules/core-js/library/modules/_export.js:35:28)
    at Object.prepareData (/Users/oskar/src/my-relay-project/node_modules/isomorphic-relay/lib/prepareData.js:31:12)
    at Object.prepareData (/Users/oskar/src/my-relay-project/node_modules/isomorphic-relay-router/lib/prepareData.js:27:38)
    at /Users/oskar/src/my-relay-project/build/server/render.js:37:49
    at /Users/oskar/src/my-relay-project/node_modules/react-router/lib/match.js:65:5
    at /Users/oskar/src/my-relay-project/node_modules/react-router/lib/createTransitionManager.js:117:11
    at done (/Users/oskar/src/my-relay-project/node_modules/react-router/lib/AsyncUtils.js:81:19)

I perform the render from the server through this module:

import React from 'react';
const { routes } = require('../front/routes');
import { renderToString } from 'react-dom/server';
import { match } from 'react-router';
import IsomorphicRouter from 'isomorphic-relay-router';

export default function render(networkLayer, location) {
    return new Promise((resolve, reject) => {
        match({ routes, location }, (error, redirectLocation, renderProps) => {
            if (error) {
                reject(error);
            } else if (redirectLocation) {
                resolve({
                    status: 301,
                    from: location,
                    location: redirectLocation.pathname + redirectLocation.search,
                });
            } else if (renderProps) {
                IsomorphicRouter.prepareData(renderProps, networkLayer)
                    .then(({ data, props }) => {
                        const reactOutput = renderToString(
                            <IsomorphicRouter.RouterContext { ...props } />
                        );

                        resolve({
                            status: 200,
                            reactOutput,
                            preloadedData: JSON.stringify(data),
                        });
                    })
                    .catch(err => {
                        reject(err);
                    });
            } else {
                resolve({
                    status: 404,
                    from: location,
                });
            }
        });
    });
}

I know it's a lot to swallow, but do you have any pointers on where to look? Thanks!

prewk commented 8 years ago

If I, instead of using the webpack built module for my routes.js (target: node and libraryTarget: commonjs), just require my original unbuilt non-minified version with babel-node instead - it works.

Therefore, the error must have something to do with creating a transpiled webpack bundle of routes.js and all of its React stuff. Can you think of something that might disappear in the build step?

My webpack build config doesn't have anything fancy, really:

{
    entry: [path.join(__dirname, '..', 'src', 'front', 'routes')],
    output: {
        path: path.join(__dirname, '..', 'build', 'front'),
        filename: 'routes.js',
        libraryTarget: 'commonjs',
    },
    target: 'node',
    plugins: [
        new webpack.optimize.OccurenceOrderPlugin(),
        new webpack.DefinePlugin({
            'process.env': {
                'NODE_ENV': JSON.stringify('production')
            }
        }),
        new webpack.NormalModuleReplacementPlugin(/\/iconv-loader$/, 'node-noop'),
        new webpack.optimize.DedupePlugin(),
        new webpack.optimize.UglifyJsPlugin({
            minimize: false,
            mangle: false,
            compressor: {
                warnings: false
            }
        })
    ],
    module: {
        loaders: [
            { test: /\.js$/, loaders: ['babel'], include: path.join(__dirname, '..', 'src') },
            { test: /\.json$/, loaders: ['json'] },
        ],
    },
}
denvned commented 8 years ago

Probably it's because externals is missing in your webpack config. See https://github.com/denvned/isomorphic-relay-router/issues/5#issuecomment-156984817.

prewk commented 8 years ago

Thanks, I tried it out externals: /^[^.]/, assuming it would exclude all non-relative imports. It spat out a small module whose only purpose, it seems, is to require the unminified version, by absolute path (wtf) instead:

module.exports=function(r){function e(t){if(o[t])return o[t].exports;var n=o[t]={exports:{},id:t,loaded:!1};return r[t].call(n.exports,n,n.exports,e),n.loaded=!0,n.exports}var o={};return e.m=r,e.c=o,e.p="",e(0)}([function(r,e,o){r.exports=o(1)},function(r,e){r.exports=require("/Users/oskar/src/my-relay-project/src/front/routes")}]);

I guess it's more of a webpack question, but do you know if that exclude rule screws up if you're consistently using import x from '../y' instead of require, because that's what I'm doing?

denvned commented 8 years ago

it seems, is to require the unminified version, by absolute path (wtf)

It must be because you use absolute path for entry. Try the following instead:

context: path.join(__dirname, '..', 'src'),
entry: './front/routes',
prewk commented 8 years ago

Good eye, thank you very much!