-   Notifications  You must be signed in to change notification settings 
- Fork 471
Description
I was playing around and I realized that, with the current approach to compile inline modules, we produce JS code that cannot be tree-shaked (at least using esbuild 0.18.13).
// Split.res module Nested = Split__Nested// Split__Nested.res let adder = x => { x + 2 } let multiplier = x => { x * 2 }// Inlined.res module Nested = { let adder = x => { x + 2 } let multiplier = x => { x * 2 } }// MainSplit.res Console.log(Split.Nested.adder(2)->Int.toString)// MainInlined.res Console.log(Inlined.Nested.adder(2)->Int.toString)// MainSplit.resi// MainInlined.resiIf you compile this (I am currently using rescript 11.0.0-beta.4), then you can use esbuild to minify and bundle the two main files separatedly (--keep-names is just to make things a bit more readable):
rescript build esbuild --minify --bundle --keep-names src/MainSplit.bs.js esbuild --minify --bundle --keep-names src/MainInlined.bs.jsThis is the formatted output for MainSplit:
(() => { var i = Object.defineProperty; var e = (t, o) => i(t, "name", { value: o, configurable: !0 }); function r(t) { return (t + 2) | 0; } e(r, "adder"); console.log(r(2).toString()); })();This is the output for MainInlined instead:
(() => { var d = Object.defineProperty; var r = (e, n) => d(e, "name", { value: n, configurable: !0 }); function i(e) { return (e + 2) | 0; } r(i, "adder"); function o(e) { return e << 1; } r(o, "multiplier"); var t = { adder: i, multiplier: o }; console.log(t.adder(2).toString()); })();As you can see, we keep the multiplier function around in the inlined version, and the reason is pretty simple: because we represent an inline module as an object, it is impossible for the tree-shaker to only remove multiplier from the object t.
I hope to be wrong, but I do not think there is a nice and easy solution, just because it is not possible to express the concept of an inline module in JS.
We could simply consider to warn the users that inline modules can lead to bundle size increase, but given that in Rescript modules are first citizen elements (and they are pretty pervasives) maybe we should consider exploring better approaches.
An obvious one could be to automatically split inline modules into separated JS files, but the following situation is totally not trivial to handle:
// Test.res let a = x => { x + 10 } module Inner = { let b = x => { a(x) * 2 } }// Test.resi // Note that `a` is not exported module Inner: { let b: int => int }In fact, a should be exported to Inner but not to other modules according to the interface file.
As you can see, I really do not have a good solution to this problem. Probably the situation is made a little worse by the way module files are flat in Rescript. But let me know what you think.