The creators of GWT faced the same problem, and they have addressed it by making some shrewd choices.
The first such choice was to support a strictly static subset of Java - one that eliminates reflection - so that the compiler can prove exactly which lines of code will be used in the application, and aggressively eliminate those that are not. The compiler does this by building a graph rooted at the entry point and following method calls. For example, if your GWT application uses the ArrayList class, but it never uses its remove() method, then the code for remove() is not included in the resulting JavaScript output. They refer to this as the "pay as you go" design philosophy.
Other minification techniques are used in addition to this, but the most impact comes from choosing a strict static subset of the source language.
The next choice that the creators of GWT made was to make the compiler aware of a special method GWT.runAsync(). When the compiler encounters this method in the aforementioned graph, it knows that it can break the javascript into pieces, so that code on the far side of that call is loaded lazily when it is encountered, as opposed to in the initial bundle. This "developer guided code splitting" saves the user from having to wait for a 5 megabyte download when they only need to wait for 1 megabyte, etc.
There's no reason that Emscripten cannot use these techniques, other than an unwillingness to make the hard choice of sacrificing some expressiveness in the source language.