Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Tell react-native packager to watch a non-javascript file

I'm using a babel plugin to load environment variables from a .env file into a React Native project, but changes to the .env file are not loaded until the javascript file importing them changes. I'd like a way to tell the react-native packager to recompile in the event that this file changes. I would accept an answer that:

  1. Simply re-transpiles the entire project when a specific file (.env) changes.
  2. Re-transpiles only those files containing a specific string, say foo

Is there a simple way to do this by writing a plugin/middleware? Maybe a separate background script that fires events to watchman that the react-native packager is listening for?

[EDIT in reply to a comment]

My current .babelrc is the following, where babel-plugin-react-native-config is a plugin I wrote to do hot variable swapping in conjunction with the react-native-config package.

{
  "presets": [
    "react-native"
  ],
  "plugins": [
     ["babel-plugin-espower", {
       "sourceRoot": "./App"
     }],
     "transform-flow-strip-types"
  ],
  "env": {
    "production": {
      "plugins": [
        "babel-plugin-unassert",
      ]
    },
    "development": {
      "plugins": [
        ["babel-plugin-react-native-config", { envfile: ".env" }]
      ]
    }
  }  
}

The problem is that the react-native packager only watches javascript files. I don't think changing my babel configuration will help, unless babel can somehow speak upwards to react-native or watchman to inform it that some file needs recompiling...

[EDIT 2]

I determined that the react-native packager uses watchman to watch files. E.g., when I do watchman watch-list after starting the packager (and after doing a watchman watch-del-all), I get

{
    "version": "4.6.0",
    "roots": [
        "/path/to/my/project"
    ]
}

Moreover, when I delete this watch while the packager is running, nothing happens (from its perspective, the js isn't changing because it doesn't receive any updates), but then when I restart the packager it recreates this watch and transpiles everything.

So it seems that, unless there's a better way, I have to create a watchman trigger to both (1) kill the react-packager (2) kill the watch on my app directory (3) restart the node packager. This seems slow and hacky, but I would like to see if it can even work.

I haven't quite gotten this to work in a generic way, but I'm experimenting with various things.

like image 702
JeremyKun Avatar asked Feb 13 '17 19:02

JeremyKun


1 Answers

Since it's been two weeks since I've asked this question, I'm going to post the (kind of terrible) workaround I was able to cobble together. I will leave this question unanswered, and accept any new answer that is better (less hacky) than this one.

The react native packager uses watchman to watch for filesystem changes, and upon getting an event that some JS file has changed, it looks to see if the file has actually changed, and then retranspiles if so. This prevents me from doing something simple like a watchman trigger that touches the relevant JS file, because the react packager thinks it's so smart that it can ignore updates with no diff. Whatever.

So my solution is to create a watchman trigger on .env changing which calls make clear_env_cache, where clear_env_cache is the following (phony) target in a Makefile.

# get the PID of the react packager
pid := $(shell lsof -i:8081 | grep node | awk '{print $$2;}' | head -n 1)

# Kill files that the packager uses to determine whether it needs to 
# re-transpile a js file, then restart the packager
clear_env_cache:
    find ${TMPDIR}/react-native-packager-cache-* -name "my_pattern" | xargs rm
    kill -9 $(pid) || echo "no packager running"
    nohup node node_modules/react-native/local-cli/cli.js start > /dev/null 2>&1 &

Note that my_pattern will change depending on your project layout. For me there's one file importing all the envvars called Settings.js, so the pattern is "*Settings*". Note this target also basically kills and reboots the packager every time the file changes, and it nohups the node packager so you won't be able to see the process anymore. Not a big deal unless you need to view the output of the packager.

The watchman-cli command to trigger this is watchman-make --root . -p .env -t clear_env_cache and for convenience I set up a make target that nohups this command:

# Run `make hotswap_env` to allow envvar changes to show up in the react-native packager.
hotswap_env:
    nohup watchman-make --root . -p .env -t clear_env_cache > /dev/null 2>&1 &

Now I can (once per system boot) run make hotswap_env and it will trigger whenever .env changes and ensure the packager server is continuously running.

Disclaimer: This script is probably not portable, and is definitely brittle. Caveat emptor and YMMV and IANAL and all that. Suggested improvements for portability are welcome.

like image 91
JeremyKun Avatar answered Sep 30 '22 14:09

JeremyKun