-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
remove circular dependencies #2105
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 6.x
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this hard work ! It's really a nice to have to enable tree-shaking ! 🙏
Before reviewing this huge PR, I'd like to understand some things:
You did "move" some functions, like isList
→ probeIsList
. Why did you do that ?
It seems that you did sort imports too. It's a nice to have, but it doesn't seems related to this PR.
Because it make the PR pretty hard to review (+7000 -6000 😨).
Could you undo some of those things to make the PR reviewable, to keep only the minimal changes ? (or for the sorting, open a PR that sort all imports and add an eslint rule that enforce sorted imports).
ping @bdurrer as you did work on that subject too. |
do we have a test that verifies tree-shaken code still works? I remember that our first (failed) attempt looked good until webpack removed the magic. |
Grouping things into a 'probe' module with prefixed 'probe'-naming enables one to intuit where probe-related behaviours are defined and to grep where they are imported. These things helped me to understand and change the sources.
Okay. Things were moved around without any awareness of order of imports. A separate PR could be made for it. |
Feel free to leave this PR idle and use as inspiration or reference to solve the issue in some other way. |
Changes continued here to export a functional interface similar to erlang's "map" https://www.erlang.org/docs/23/man/maps and the main benefit is smaller bundle size. Using esbuild to bundle separate files that create and manipulate map-like data with immer, mutative, immutable.js and my own immutable-derived 'im' this is the result,
My modified version of immutable is smallest. It could be smaller if time were spent further reducing size. Additionally, no functionality is lost; a file still exists which exports a chainable-methods-variant of Considerations for immutablejs based on my experience.
Using function calls rather than method calls internally would make refactoring and moving things around easier. For example the patch below would decouple diff --git a/src/Map.js b/src/Map.js
index dd9faf5e4..2c1b411a9 100644
--- a/src/Map.js
+++ b/src/Map.js
@@ -37,11 +37,9 @@ export const Map = (value) =>
? emptyMap()
: isMap(value) && !isOrdered(value)
? value
- : emptyMap().withMutations((map) => {
- const iter = KeyedCollection(value);
- assertNotInfinite(iter.size);
- iter.forEach((v, k) => map.set(k, v));
- });
+ : opWithMutations(emptyMap(), m => {
+ Object.keys(value).forEach(k => opSet(m, k, value[k]))
+ })
export class MapImpl extends KeyedCollectionImpl {
create(value) { Related to the above diff; consider exporting separate versions of constructors for different value types. eg, Also consider removing or moving-out "atattransducer"-related things like this, MapPrototype['@@transducer/init'] = MapPrototype.asMutable = asMutable;
MapPrototype['@@transducer/step'] = function (result, arr) {
return result.set(arr[0], arr[1]);
};
MapPrototype['@@transducer/result'] = function (obj) {
return obj.asImmutable();
}; Remove duplicated methods like Basically these are the useful things to do now imo that would make immutablejs smaller and easier to refactor. Other changes would be useful as well, but those are the immediate things. |
That's quite impressive dividing the size by 10! 🤯 About your suggestions: Reducing the APIIt might be interesting to reduce the size, but I don't want to reduce the API for now: I have no statistics about what methods are used or not. List and Map as first classI think that you are right, List and Map are the two most useful classes here, but if tree-shaking works, it will automatically remove unused classes, don't you think ? decouple
|
Thanks for the thoughtful response. Seems agreeable. I will reply to some of those points only to clarify thoughts and not from disagreement.
Consider removing the deep methods in a separate future release, after they have been supported with separately importable modules like this. These would be kept with core immutablejs sources and unit-tested with them, import mergeMapsDeep from 'immutable/mergeMapsDeep' The 'deep' manipulation functions are an area of challenge when refactoring immutablejs. Mainly this is because they create KeyedSequences, IndexedSequences and Maps and so lend themslves to circular conditions. If manipulations like 'mergeDeep' are imported separately from core immutablejs, the circular coupling issues go away and things easier to change.
Yes true. Each collection-type must be defined through its own mostly-separate import-tree, regardless of where it is defined.
Yes. If there were a const mapFromList = list => {
utilAssertTupleShape(list)
return opWithMutations(mapCreateEmpty(), map => {
list.reduce((acc, kv) => opSet(acc, kv[0], kv[1]), map)
})
}
It may be impractical to review it. Large PRs are not great, but truly many layers of difficult changes are needed to remove circular dependencies here.
Removing the circular dependencies in controlled steps is not easy. The current immutablejs does not cooperate well with changes for various and surprising reasons. It is good to pace oneself. |
Removes circular dependencies by lazy-assigning some definitions in module "Universe.js"
Running the below benchmark many times, sometimes the newer version is faster and sometimes the older version is faster and its unclear if there is performance loss or gain...
This benchmark can be used with node's official benchmark.js scripts The last number is the rate of operations measured in ops/sec (higher is better).
bench.immutable.js