Ionic v3 Production Builds & AWS Amplify (GraphQL)

Today, I had a very unusual problem. An Ionic v3 app that we are developing worked fine in development, but when compiled in production mode (ionic build --prod) would fail to launch. When connected to a web inspector, the error thrown was

Error: Cannot use e "__Schema" from another module or realm.

Ensure that there is only one instance of "graphql" in the node_modules
directory. If different versions of "graphql" are the dependencies of other
relied on modules, use "resolutions" to ensure only one version is installed.

https://yarnpkg.com/en/docs/selective-version-resolutions

Duplicate "graphql" modules cannot be used at the same time since different
versions may have different capabilities and behavior. The data from one
version used in the function from another could produce confusing and
spurious results.

The GraphQL library is throwing a fatal error as it believes there are multiple versions loaded. We aren't using GraphQL, but we are using AWS Amplify which includes GraphQL as part of its API module.

This error has been reported in both the AWS-Amplify aws-amplify/issue#2275 aws-amplify/issue#1445 (and others) GraphQL repos graphql-js/issues#1182

In our case, the error is caused by uglification of the app that causes GraphQL to mistakenly believe there are multiple versions installed. It is possible to disable this check in production mode by setting NODE_NEV=production. However as Ionic is built using Webpack, setting NODE_ENV=production isn't as simple as setting the env variable when building the app.

Custom Ionic v3 Webpack

The solution in our case is to create a custom webpack.config.js for our project. And use this custom config to inject NODE_ENV=production into the built app.

To do this, here are the steps.

1. Create a custom webpack.config.js in [app root]/config/webpack.config.js

const merge = require("webpack-merge");
const common = require("@ionic/app-scripts/config/webpack.config.js");
const webpack = require("webpack");

module.exports = {
  dev: common.dev,
  prod: merge(common.prod, {
    plugins: [
      new webpack.DefinePlugin({
        "process.env.NODE_ENV": "production"
      })
    ]
  })
};

There are a couple of things happening here.

  1. I get the default ionic webpack config from the @ionic/app-scripts node module
  2. I am using webpack-merge library to help merge our custom config with the default
  3. Ionic expects webpack.config.js to export 1 object with valid webpack configs for each environment. In this case, we export an object with the default dev environment, and export an updated prod config, that takes the ionic default config and updates it with a new plugin.
  4. I am using the Webpack Define Plugin to define process.env.NODE_ENV in the resulting bundle. Setting this to 'production' disables the GraphQL check that throws the error.

2. Update package.json to tell ionic about the custom webpack.config.js

{
    ...,
    "config": {
        "ionic_webpack": "./config/webpack.config.js"
    }
}

Adding config.ionic_webpack to package.json tells the ionic build system to use the custom webpack.config.js when building the app.
And after all that, our production build works again.

Notes

Please note that this only applies to ionic v3 apps. I haven't experienced this problem with our Ionic v4 apps.