The answer is actually a bit common-sense after a moment: the difference between injecting a <script> and a eval() is that in the former case the code is in a file on some server, whereas in the latter case it's in a string. To hack up a solution with eval requires converting the file into a string, but to do that you need an XMLHttpRequest and to deal with origin policies and it rapidly explodes into a pointless mess because you're explicitly requesting that the loaded script have the power to interact with the context you're in, even if you're not using this power by boxing it into a context. (For example when you eval() in `function () { var secret = "this string is totally private!"; eval(code) }` that code has a power which no script tag has.)
[1] The sins of eval include ugly metaprogramming where it's not needed like `eval("someObject." + property + ".otherThing")` rather than `someObject[property].otherThing`, and accidental remote code exploits where none was intended like `var json = eval(thing_i_dont_actually_trust)`: whoops, someone passed `(function() { ... }())` instead of JSON and it went straight through my parser.