A complication in implementing source map support (T47514) is figuring out how to deliver the source map for a localStorage cache hit, where JS text is written to localStorage and then eval'd on a subsequent request.
Currently mediawiki.loader.store.set() does its own module serialization, putting back together all the pieces of the original mw.loader.implement() call, and reconstructing roughly the same mw.loader.implement() call that is returned by PHP's makeLoaderImplementScript().
When we have a source map feature, the result of module serialization in mediawiki.loader.store.set() would have to exactly match the one used in PHP while constructing the source map, character for character. I think that would complicate future maintenance.
My idea is to instead call mw.loader.implement() with a declarator function that returns an array of arguments, instead of directly calling it with the arguments. Then the function can be serialized, recovering the exact JS code generated by PHP.
Using an arrow function and shortening "implement" to "impl" reduces the bundle size impact to a couple of bytes per module. So for example
mw.loader.implement("ext.graph.render@2uh7o", ... );
would become
mw.loader.impl(()=>["ext.graph.render@2uh7o", ... ]);
I asked @Krinkle what he thought about this idea. He was concerned about compilation being deferred when it was previously done up front. So I investigated that. It turns out to cause the opposite problem.
On a force reload of a local VisualEditor page, zooming in on the flame graph of the largest domEval() call:
There is a lot of DOM overhead, but eventually it just does a single compilation, lasting 24ms. The domEval() is 59ms.
With PS1 of the WIP patch:
There is the big compilation, this time lasting 23ms, but then there is another 32ms consisting of many small compilations. Overall, domEval() takes 89ms.
I haven't confirmed whether there is an impact on user-visible latency.
Using plain old functions instead of arrow functions, as in PS2 of the patch, the additional compilation is reduced to 6ms with domEval() taking 74ms.
Rebasing on top of function constructor evaluation (gerrit 945005), with plain declarator functions, the largest globalEval() takes 32ms, which is only 1ms more than what I measured on that patch before touching mw.loader.implement(). So I think the overhead of this variant is acceptable.