Modifying a controller recompiles my router and all UI layer

Hey there, is it normal that whenever I modify a controller, it recompiles my router, all my controllers and my views?

I tried already ran mix xref but I don’t really know how to investigate further.

Here is my mix xref output for one of my controllers: https://gist.github.com/cblavier/217681e36c546fe7d44d2f0d245860b5

1 Like

Hmm, one big thing of note is that it looks like the Views/Templates use Router helpers, but directly instead of namespaced. Do you use things like some_path(...) instead of Routes.some_path(...)? That would create a hard dependency and force recompilation. Phoenix like 1.3 or so changed from direct accessing to namespaced accessing for that reason, though it does mean you lose compile-time checking on them.

5 Likes

Thank you, it helps! I was able to decrease the count of recompiled files from 208 to 72.
Now, I can decrease it to about 10 files if I disable all custom module plugs I’m using in my controllers. Any idea why?

I tried playing with config :phoenix, :plug_init_mode, :runtime but it doesn’t change anything? Does it also apply to controller plugs or only router plugs?

I suggest watching this video. It’ll give you an idea of what causes recompilation of modules. :plug_init_mode is just a way to lessen recompilations for plug, but you might have other causes of compile time dependencies as well.

2 Likes

Already watched it! I think I tracked down all my “compile” dependencies.
But I can’t understand why, when using a same plug from a few controllers, when I touch one of the controllers, all the other controllers get recompiles (even if my plug code is empty!)

I finally solved my problem :partying_face:
Touching a view now recompiles only 1 file, and touching a controller recompiles 2 files (the controller and the endpoint) instead of 208 … (went from 30sec to 2sec)

What did I do?

  • watched this video twice :wink:
  • ran a gazillion times mix xref graph --source lib/../my_controller.ex and xref graph --sink lib/../router.ex
  • also ran fswatch _build -r | cat | grep .beam to actually see what files are being recompiled
  • aliased RouteHelpers instead of importing them
  • removed any hard reference to LayoutView or Router in any of my plugs. I had to workaround this problem by using Module.concat["MyApp.Router.Helpers"] in my navigation plugs
  • had a weird dependency issue related to Coherence library when coherence_routes were inlined in my app main scope instead of its own dedicated scope
  • the last one still seems weird to me, but I also had to rewrite any defdelegate calls from views to other views into a classic function call

Hope it will help someone, and if you have any comment about my post, feel free!

8 Likes

defdelegate does ensure the function actually exists on the other end, which makes it create a compile time dependency.

3 Likes

Still trying to understand why I didn’t noticed this mess earlier, I figured out that I had no such problem on this project a month ago (ie. touching a controller was only re-compiling a single file)

I finally figured out that I upgraded my phoenix version. And this specific commit is the one that made the issue apparent.

Is it intended? Should I open a ticket on phoenix?

That commit is actually removing the compile time dependencies of plugs if :init_mode is configured to :runtime (in dev). It’s there to prevent the issues of cascading recompiles. If :init_mode is :compile it should act exactly as before the change.

I actually had no plug_init_mode option at this time.
But I just tried to set it to both compile or runtime, it doesn’t change anything
(I even hard changed the init_mode value directly in Endpoint code to discard any configuration mistake)

That’s to be expected as it was only recently added to plug exactly because many people had the same problems as you had. When set to runtime it makes the init/1 function of plugs be executed at runtime instead of compile time, so any compile time dependencies based on those init calls should be gone.