IMHO you should put as less functionality in the bootloader as possible (definitely no communication!). It should detect/verify a different firmware image has been loaded which then needs to be programmed into the flash.
Going to disagree.
The bootloader in my case, should have all of the routines to update the system if the main flash is entirely corrupted. This means I have copies of the communication functions in the bootloader. If the flash is missing (I ship with no code on the module) or it's really corrupted, I can still have users log in via bluetooth and update. An RMA is expensive. "Wasting" a 2-6k of ROM on making sure the bootloader can recover flash on it's own, pretty cheap.
In this case, I usually hold inside of bootloader to do any flashing, since all the code is there, I like it to live there and just reboot into bootloader from main app when an update is available. Handle it there when
So in this case, a LOT of my comm stuff is in the bootloader. The majority of it, for sure. But I'm not using a simple comm like UART. I have a lot of bluetooth and other routines going on.
For what it's worth... I am trying to not develop my main application as a "function" but rather as it's own program. So the bootloader and main application are entirely separate. Just by changing rom locations I can test both separately or together. ie; Both bootloader and application have main()s that run. Once bootloader jumps, everything is reset to the application's main.