The ESP32 has a bootloader that uses serial embedded in its internal ROM. Whether that bootloader runs or not depends on the state of certain IO pins when the ESP32 starts up.
Those states are controlled by a small circuit that is triggered by the serial control lines (DTR and RTS).
On the Arduino there isn't that requirement, and the bootloader always runs when the chip resets, so a much simpler circuit is used to trigger a reset using just the DTR signal.
So for an Arduino whenever the serial port is opened the DTR signal resets the board. But for the ESP32 the RTS signal also has to be asserted along with the DTR signal in the right sequence to both set the correct IO pins and to reset the chip.
Whether the target device has internal flash or an external flash chip makes no difference. It is the bootloader's job to get the data into that flash. The AVR chips have a way of interfacing with the internal flash (ISP) to program it without a bootloader, but the ESP32, as it has external flash, doesn't need that (you can interface directly with the flash chip if you so wish).
The software on the PC that communicates with the bootloader has to speak the "language" the bootloader expects. Each family of chips has its own bootloader (some don't even have a bootloader, but that's another story) which speaks its own language. On the AVR chips that's usually a variant of the STK500 protocol, and the program
avrdude is what communicates with it to send the program to flash. On the ESP32 (and the ESP8266 too) it's
esptool.py that does the job. On chipKIT boards it's
pic32prog. On Digipark it's
micronucleus. There's as many upload programs as there are families of boards. Some programs, like
pic32prog are more flexible and can also talk to hardware programmers (USBASP, PICkit2 etc), but in general the upload program is written specifically for a particular bootloader.