The only change my code makes to that
Dict.size/sizeHelp code is to add the
/*#__PURE__*/ annotation. Without it, Uglify would keep sizeHelp even if size wasn't used, since it didn't understand that F2 doesn't have side effects.
var _elm_lang$core$Dict$sizeHelp = /*#__PURE__*/F2(function (n, dict) { ... truncated ... });
var _elm_lang$core$Dict$size = function (dict) {
return A2(_elm_lang$core$Dict$sizeHelp, 0, dict);
};
The __PURE__ annotation is pretty new and not particularly documented. Here's the pull request where they added it along with links to some of the Uglify/Typescript issues that prompted the change:
https://github.com/mishoo/UglifyJS2/pull/1448
uglify-beautified is my plugin + uglify without the mangle option so you can see what it actually eliminated.
pure_getters and pure_funcs
These options are exactly what I experimented with initially. You can feed a bunch of names into pure_funcs (specifically F2...9) to achieve some of the savings. pure_getters helps a bit as well, but only to eliminate the expressions that refer to the object created by the IIFE. The IIFE itself (and any values referenced by the object it returns) will be retained.
The other problems with using these options is that they get awkward fast, as you need to maintain and feed a whitelist into Uglify. I actually do that in the tool right now as a pragmatic stopgap. When I run it on a new example project, I look for the "Side effects found in unused ..." warnings from Uglify and
whitelist the function if appropriate. This is clearly not scalable. Also, these options apply to the entire input file, so you could potentially break any accompanying Javascript/libraries if they're part of the same payload being minified.
Native code
Much of the savings do come from helping minifiers eliminate unused native code. The JS compiled from actual Elm is already fairly flat, though the F2..9 helpers are still a stumbling block.
Here's a representative partial example from Native.String:
var _elm_lang$core$Native_String = function() {
function isEmpty(str)
{
return str.length === 0;
}
function cons(chr, str)
{
return chr + str;
}
return {
isEmpty: isEmpty,
cons: F2(cons),
}
}();
var _elm_lang$core$String$cons = _elm_lang$core$Native_String.cons;
var _elm_lang$core$String$isEmpty = _elm_lang$core$Native_String.isEmpty;
After
function _elm_lang$core$Native_String$iife_private$cons(chr, str) {
return chr + str;
}
function _elm_lang$core$Native_String$iife_private$isEmpty(str) {
return str.length === 0;
}
var _elm_lang$core$Native_String = {},
_elm_lang$core$Native_String$iife_public$isEmpty = _elm_lang$core$Native_String$iife_private$isEmpty,
_elm_lang$core$Native_String$iife_public$cons = /*#__PURE__*/F2(_elm_lang$core$Native_String$iife_private$cons);
var _elm_lang$core$String$cons = _elm_lang$core$Native_String$iife_public$cons;
var _elm_lang$core$String$isEmpty = _elm_lang$core$Native_String$iife_public$isEmpty;
Your simple strategy should achieve essentially the same thing. Unfortunately, what is ideal for the minifier is much less ergonomic for the writer :)
I believe the `root._elm_lang$core$Native_String$reverse` version would not be effective. All of the values referenced by the root/bundle object will be kept alive by their membership, and any references to them from elsewhere will keep the `root.foo` expressions alive since getters can potentially be functions in JS. I'm not aware of any minifiers that do the flow analysis required to successfully handle this sort of case.
Sorry this is a bit scattered since I need to run, but I'd be happy to post more complete examples this evening. There is a Makefile in the examples dir to generate all of the examples and stats yourself, but I can also pull out any sort of excerpts you're interested in.