I work on React Native app and it’s important to do as little as possible at app start to reduce the time user has to wait before he can interact with the app. One of the things that helped to reduce wait time was switching from importing JavaScript modules at the top of the file to requiring modules in the functions that actully use them. But the challenge now is to prevent unwanted imports from appearing again.
My immediate reaction was “Let’s use tests for that”. While the goal is simple, finding a solution was not trivial. I tried using Jest machinery on the require function, but could not find an approach that would allow to inspect which modules are loaded.
|
|
Next I asked a question on StackOverflow, but did not get an answer. I did
get a comment pointing to require-in-the-middle
package, which seemed promising. However I couldn’t get it to work in Jest.
Apparently the magic that Jest does with require
clashes with magic that
require-in-the-middle
performs resulting in test case that depending on
Jest version crashes with stack overflow, never completes or just provides
no info about loaded modules.
|
|
Now was the time to dig into undocumented features of Jest. There’s a
configuration option moduleLoader
that seems like a good fit for this
purpose. A quick test shows that we can indeed observe modules getting
loaded.
|
|
|
|
A stream of module names appears in console proving that it is a viable
way to achieve our goal. Now we have a challenge to assert on the data that
passes through InspectableModuleLoader
in our test case. I’m not sure how
and why, but module loader and test runners don’t share global objects.
However we can provide arguments to module loader by importing special paths
and module loader can return data to test case through return value of
requireModule
function.
|
|
|
|
The result is failing test case.
> Cannot find module 'inspect(begin)' from 'imports.js'
.
That’s a bummer: module finder runs first and if module cannot be found,
module loader is not even called. I chose an ugly solution: actually adding
files ./inspect(begin)
and ./inspect(end)
to the project. This allows to
sidestep the need to deal with more undocumented APIs to customize Jest
module finder.
Now the test
|
|
gives us
- Expected
+ Received- Array []
+ Array [
+ “App”,
+ “app.json”,
+ “index.js”,
+ “react”,
+ “react-native”,
+ ]
Let’s change how we import App component.
|
|
And we have a passing test.
|
|
Getting rid of import for App.js
and react
gives no performance benefits
in this sample app.
In larger apps that register multiple components this can give significant
performance boost. The test case looks a bit ugly and I would like to find a
more elegant way to express intent here, but for now I have a solution that
gets the job done.