Reactotron: Production builds in React Native

Created on 20 May 2017  路  31Comments  路  Source: infinitered/reactotron

Seems to be a topic with a lot of questions around this, so I'd love to track this here.

Problem

What do we do with Reactotron and production builds in React Native?

Currently we're recommending people install as a devDependency. If you then use import Reactotron from 'reactotron-react-native', with only the production dependencies, it breaks.

This is complicated a bit due to how require statements work in React Native's packager. This environment is not webpack, cjs, or node. This is haste. So the normal tricks like using if statements and require are not available. Haste preprocesses your JS files with an AST looking for require calls and doesn't take into account dead code paths like if (0).

So what do we do?

Solution: Make it a dependency

Move it to a dependency and never call connect().

  • 馃憤 You can test in production!
  • 馃挬 An extra 150KB of dependencies that aren't used. Blech.
  • 馃挬 If you ship with connect() on, Apple will not let you in the AppStore.

Solution: Comment it out

Use // to comment out Reactotron.

  • 馃挬 Manual step.
  • 馃挬 Doesn't play well with CI.
  • 馃挬 If you write scripts to strip this, well, you have to do work. Let's not cause work for people.

Solution: Babel Transforms

This seems like it should be the right way.

  • 馃憤 Use these things (this | this | this) or similar to transform out the code!
  • 馃挬 I've never done something like this before. Halp!

Anyone else have comments or suggestions?

discussion help wanted

Most helpful comment

This is what I'm using (planning to at least, as I haven't gotten around to doing a full test):

# src/state/config.js
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable import/no-mutable-exports */
/* eslint-disable global-require */

import { compose as reduxCompose, createStore as reduxCreatStore } from 'redux';

let Reactotron = null;
let reactotronRedux = null;
let composeWithDevTools = null;

if (__DEV__) {
  require('reactotron-react-native');
  Reactotron = require('reactotron-react-native').default;
  reactotronRedux = require('reactotron-redux').reactotronRedux;
  composeWithDevTools = require('remote-redux-devtools').composeWithDevTools;
}

let compose = reduxCompose;
let createStore = reduxCreatStore;

console.log('outside DEV');
if (__DEV__) {
  console.log('inside DEV');
  Reactotron.configure()
    .useReactNative()
    .use(reactotronRedux())
    .connect();

  compose = composeWithDevTools;
  createStore = Reactotron.createStore;
}

export { compose, createStore };

Then in my src/state/store.js I can simply use:

import { createStore, compose } from './config';

Which should pull the correct set of compose and createStore.

All 31 comments

@skellock Using this with wrapping reactotron code inside if (__DEV__) seems legit, but I would like to hear other opinions too. Will react-native/packager remove dead code, e.g. if (false) {...} statements in production?

No, not as I understand it. It just won't execute it. So runtime-safe, but not compile-time safe (if that makes any sense). We'd still need to have reactotron-react-native and friends as a dependency.

It seems that it should eliminate dead code: https://github.com/facebook/react-native/issues/462

If this works, then things just got much easier. =)

Yes, I like the dead code elimination thing much better.

Has anyone tried it already?

I haven't yet.

Ok, so I took a run at this tonight.

I think it's just a matter of trying to identify all the scenarios of people using Reactotron and then strip them out. The goal is to have people not change anything about their codebase and let this plugin do the heavy lifting.

https://github.com/infinitered/babel-plugin-ignite-ignore-reactotron

I started with an ignite-based plugin because it's a great test-bed and I've never created a babel plugin before.

Feedback welcome.

@skellock Thanks! Will definitely try your plugin at some point. But now I am using this shell script to remove all Reactotron references from my code:

rm ./src/utils/reactotronConfig.js
find ./src -type f -name "*.js" -exec sed -i '' -e "s/console.tron.log/console.log/g;s/import Reactotron from 'reactotron-react-native';//g;s/return Reactotron.createStore(rootReducer, initialState, applyMiddleware(thunk));//g" '{}' \;
find . -maxdepth 1 -type f -name "index.*.js" -exec sed -i '' -e "s/import '.\\/src\\/utils\\/reactotronConfig';//g" '{}' \; 
  1. detelete reactotronConfig.js
  2. change all console.tron.log to console.log (later I'll use babel "transform-remove-console" to delete all console.log)
  3. detelete import Reactotron from 'reactotron-react-native';
  4. detelete return Reactotron.createStore(rootReducer, initialState, applyMiddleware(thunk));
  5. detelete import './src/utils/reactotronConfig';

Maybe it is not the best script, but it does it's job for me :)

This is what I'm using (planning to at least, as I haven't gotten around to doing a full test):

# src/state/config.js
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable import/no-mutable-exports */
/* eslint-disable global-require */

import { compose as reduxCompose, createStore as reduxCreatStore } from 'redux';

let Reactotron = null;
let reactotronRedux = null;
let composeWithDevTools = null;

if (__DEV__) {
  require('reactotron-react-native');
  Reactotron = require('reactotron-react-native').default;
  reactotronRedux = require('reactotron-redux').reactotronRedux;
  composeWithDevTools = require('remote-redux-devtools').composeWithDevTools;
}

let compose = reduxCompose;
let createStore = reduxCreatStore;

console.log('outside DEV');
if (__DEV__) {
  console.log('inside DEV');
  Reactotron.configure()
    .useReactNative()
    .use(reactotronRedux())
    .connect();

  compose = composeWithDevTools;
  createStore = Reactotron.createStore;
}

export { compose, createStore };

Then in my src/state/store.js I can simply use:

import { createStore, compose } from './config';

Which should pull the correct set of compose and createStore.

Ooh, that's a good idea @kevinjalbert !

Thank you @kevinjalbert for your code snippet!
But I think you meant if (__DEV__) {} not if (!__DEV__) {} or am I missing something?
I am using

"reactotron-react-native": "^1.12.1",
"reactotron-redux": "^1.12.0"

and this configuration for me is the only way to solve #289 (createStore is not a function)

@fabriziomoscon good catch! Those extra ! was for when I was testing it locally in development haha. I've updated the code snippet.

Minor issue, for /* eslint-disable global-require */ part, I think only applying it on the necessary line with // eslint-disalbe-line global-require looks better for me.

@skellock Can you check my PR for that babel plugin? :) I can post more PRs too :)

Merged in @henrikra . Thx.

I鈥檓 going to close this up now that we have a Babel transform.

@skellock I think you should mention this babel plugin in the readme :D

I was holding off until people tried it. Seems to be working well though.

thanks @skellock. The plug in worked for.

Hi, so do I really need to do this?

if (__DEV__) {
    require('../configs/reactotronConfig.js');
}

Or is this OK (i.e. you strip it during the build phase of the 'release' type)?

import '../configs/reactotronConfig.js'

Version: "reactotron-react-native": "^1.13.2"

Yes you don't need conditional import if you use ignite-ignore-reactotron babel plugin

@henrikra Thanks! By the way, where is this documented? May I assume that the app breaks if I kept it with just the import? (in 'release')

You can use __DEV__ guards and the react-native packager will not include those files. This is important because it's a dev dependency.

babel-plugin-ignite-ignore-reactotron is undocumented because it's kinda experimental. I haven't heard from anyone if they're using it successfully or not. It worked for my ignite-based projects tho. =)

@skellock We have used the babel plugin to remove reactotron from the code and our app is in the App store :) So I can vouche for it

@skellock currently babel-plugin-ignite-ignore-reactotron only works for Ignite based app? or is there any chance I can use it in a non Ignite based app?

It is possible. Full disclosure, I did have some issues with it on a non-ignite project. You'll find this out pretty quickly if you do a production build and it crashes immediately. =) But for ignite stuff it works well.

Also, if you just put __DEV__ guards in front of everytime you use it, the metro bundler won't actually package up the reactotron-* dependencies. =)

We have non Ignite project and we are using that babel plugin and our app is in app store :)

I confirm that plugin work fine for me too. @henrikra @ivansnek @skellock

here my .babelrc

{
  "presets": ["react-native"],
  "plugins": ["transform-decorators-legacy"],
  "env": {
    "production": {
      "plugins": [
        "transform-remove-console",
        "ignite-ignore-reactotron"
      ]
    }
  }
}

So far so good with the babel plugin. Had to add these __DEV__ conditionals:

  const sagaMiddleware = __DEV__ === true
    ? createSagaMiddleware({ sagaMonitor: Reactotron.createSagaMonitor() })
    : createSagaMiddleware()
  const store = __DEV__ === true
    ? Reactotron.createStore(reducers, enchancers(applyMiddleware(sagaMiddleware)))
    : createStore(reducers, enchancers(applyMiddleware(sagaMiddleware)))

What I did just take make things easy, while I was testing my app on testflight etc was...(In ReactotronConfig.js)

if (Config.useReactotron) {

  // EXISTING CODE 

  // Totally hacky, but this allows you to not both importing reactotron-react-native
  // on every file.  This is just DEV mode, so no big deal.
  console.tron = Reactotron
} else { // ADDED AN ELSE

  console.tron = console.log
}

Voila, obviously later on I can go through and strip out the tron stuff when it's actually released!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

skellock picture skellock  路  4Comments

davidjb picture davidjb  路  5Comments

lndgalante picture lndgalante  路  4Comments

felipemillhouse picture felipemillhouse  路  4Comments

wilr picture wilr  路  3Comments