Skip to content
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

Replace lodash.assign with vanilla JS. #21054

Merged
merged 3 commits into from
Mar 27, 2020

Conversation

ZebulanStanphill
Copy link
Member

Description

This PR replaces all uses of lodash.assign in the codebase with the standard JS Object.assign. The only exception to this are some ES5 documentation examples which I left unchanged since Object.assign wasn't introduced until ES6.

This is the first of several PRs I plan to make in order to reduce our usage of lodash whenever there is a simple vanilla JS equivalent, in the hopes of removing it as a dependency from several WP packages.

@ZebulanStanphill ZebulanStanphill added [Type] Code Quality Issues or PRs that relate to code quality [Package] Block editor /packages/block-editor labels Mar 20, 2020
@github-actions
Copy link

github-actions bot commented Mar 20, 2020

Size Change: +68 B (0%)

Total Size: 856 kB

Filename Size Change
build/block-editor/index.js 101 kB +68 B (0%)
ℹ️ View Unchanged
Filename Size Change
build/a11y/index.js 998 B 0 B
build/annotations/index.js 3.43 kB 0 B
build/api-fetch/index.js 3.39 kB 0 B
build/autop/index.js 2.58 kB 0 B
build/blob/index.js 620 B 0 B
build/block-directory/index.js 6.02 kB 0 B
build/block-directory/style-rtl.css 760 B 0 B
build/block-directory/style.css 760 B 0 B
build/block-editor/style-rtl.css 10.9 kB 0 B
build/block-editor/style.css 10.9 kB 0 B
build/block-library/editor-rtl.css 7.22 kB 0 B
build/block-library/editor.css 7.23 kB 0 B
build/block-library/index.js 110 kB 0 B
build/block-library/style-rtl.css 7.43 kB 0 B
build/block-library/style.css 7.44 kB 0 B
build/block-library/theme-rtl.css 669 B 0 B
build/block-library/theme.css 671 B 0 B
build/block-serialization-default-parser/index.js 1.65 kB 0 B
build/block-serialization-spec-parser/index.js 3.1 kB 0 B
build/blocks/index.js 57.5 kB 0 B
build/components/index.js 190 kB 0 B
build/components/style-rtl.css 15.8 kB 0 B
build/components/style.css 15.7 kB 0 B
build/compose/index.js 6.21 kB 0 B
build/core-data/index.js 10.6 kB 0 B
build/data-controls/index.js 1.04 kB 0 B
build/data/index.js 8.25 kB 0 B
build/date/index.js 5.37 kB 0 B
build/deprecated/index.js 771 B 0 B
build/dom-ready/index.js 568 B 0 B
build/dom/index.js 3.06 kB 0 B
build/edit-post/index.js 91.2 kB 0 B
build/edit-post/style-rtl.css 8.43 kB 0 B
build/edit-post/style.css 8.43 kB 0 B
build/edit-site/index.js 6.73 kB 0 B
build/edit-site/style-rtl.css 2.91 kB 0 B
build/edit-site/style.css 2.9 kB 0 B
build/edit-widgets/index.js 4.43 kB 0 B
build/edit-widgets/style-rtl.css 2.57 kB 0 B
build/edit-widgets/style.css 2.57 kB 0 B
build/editor/editor-styles-rtl.css 428 B 0 B
build/editor/editor-styles.css 431 B 0 B
build/editor/index.js 42.8 kB 0 B
build/editor/style-rtl.css 3.38 kB 0 B
build/editor/style.css 3.38 kB 0 B
build/element/index.js 4.44 kB 0 B
build/escape-html/index.js 733 B 0 B
build/format-library/index.js 6.95 kB 0 B
build/format-library/style-rtl.css 502 B 0 B
build/format-library/style.css 502 B 0 B
build/hooks/index.js 1.93 kB 0 B
build/html-entities/index.js 622 B 0 B
build/i18n/index.js 3.49 kB 0 B
build/is-shallow-equal/index.js 710 B 0 B
build/keyboard-shortcuts/index.js 2.3 kB 0 B
build/keycodes/index.js 1.69 kB 0 B
build/list-reusable-blocks/index.js 2.99 kB 0 B
build/list-reusable-blocks/style-rtl.css 226 B 0 B
build/list-reusable-blocks/style.css 226 B 0 B
build/media-utils/index.js 4.84 kB 0 B
build/notices/index.js 1.57 kB 0 B
build/nux/index.js 3.01 kB 0 B
build/nux/style-rtl.css 616 B 0 B
build/nux/style.css 613 B 0 B
build/plugins/index.js 2.54 kB 0 B
build/primitives/index.js 1.5 kB 0 B
build/priority-queue/index.js 781 B 0 B
build/redux-routine/index.js 2.84 kB 0 B
build/rich-text/index.js 14.5 kB 0 B
build/server-side-render/index.js 2.55 kB 0 B
build/shortcode/index.js 1.7 kB 0 B
build/token-list/index.js 1.27 kB 0 B
build/url/index.js 4.01 kB 0 B
build/viewport/index.js 1.61 kB 0 B
build/warning/index.js 1.14 kB 0 B
build/wordcount/index.js 1.18 kB 0 B

compressed-size-action

@aduth
Copy link
Member

aduth commented Mar 20, 2020

The failing unit test illustrates one additional benefit of Lodash 😄 (It tolerates nullish values better than native Object.assign)

  ● align › addAttribute() › should assign a new align attribute
    TypeError: Cannot convert undefined or null to object
        at Function.assign (<anonymous>)
      91 | 	if ( hasBlockSupport( settings, 'align' ) ) {
      92 | 		// Gracefully handle if attributes are undefined.
    > 93 | 		Object.assign( settings.attributes, {
         | 		       ^
      94 | 			align: {
      95 | 				type: 'string',
      96 | 			},
      at assign (packages/block-editor/src/hooks/align.js:93:10)
      at apply (packages/hooks/src/createRunHook.js:55:36)
      at Object.filterRegisterBlockType (packages/block-editor/src/hooks/test/align.js:54:21)

But in general, I can get on board with this effort.

A couple things to consider:

  1. In WordPress, all of Lodash will always be loaded anyways (and it's referenced as an external), so there will be no immediate network bundle size benefit for WordPress as long as Lodash is used anywhere. Therefore, it would mostly be for the benefit of third-party package consumers.
  2. The performance characteristics of Lodash vs. native would be good to keep in mind. Historically, Lodash's _.filter and friends would often perform better than the native equivalents Array#filter (example). Due to runtime improvements, this isn't as true today as it was previously, but it may vary function by function. I was curious, so I ran some benchmarks. The native Object.assign does perform better than Lodash's 👍 But it also depends if we're actually using native Object.assign, or if this is being shimmed through core-js.

@ZebulanStanphill ZebulanStanphill force-pushed the update/replace-lodash-assign-with-vanilla-js branch from a80435a to 389d8d2 Compare March 20, 2020 20:47
@ZebulanStanphill
Copy link
Member Author

(Maybe I should start running the tests before pushing... but then again, they all end up having to get run again anyway, so what's the point? 😛)

All good points, @aduth! Yeah, I'm mainly doing this for the sake of 3rd party package usage and speed improvements. I'll make sure to check the performance comparisons. 👍

I have now switched to an approach using object spread syntax. In my testing, this appears to be even faster than Object.assign!

Unfortunately, there are still some test failures: e2e failures this time. But as far as I can tell, they are completely unrelated. Any ideas?

@aduth
Copy link
Member

aduth commented Mar 21, 2020

I have now switched to an approach using object spread syntax. In my testing, this appears to be even faster than Object.assign!

Huh, that's Interesting, and quite unexpected to me. As the one who originally implemented the original code, I remember explicitly doing it this way in mind of the fact that an object spread is effectively the same as:

Object.assign( {}, a, b );

...contrasted with as it was chosen to be implemented:

Object.assign( a, b );

To me, I would think the fact that assigning the values from sources of both b and a would make it not as efficient as assigning only the values of b.

Then again, since it's all processed by some combination of Babel and core-js (not actually native), it's hard to accurately measure.

@ZebulanStanphill
Copy link
Member Author

Actually, I wasn't even testing through Babel compilation; I was comparing self-made tests on https://jsbench.me/. Specifically, I was comparing these two:

const settings = {};
settings.attributes = {};
Object.assign(settings.attributes, {thing: {donut: 'cake'}});
const settings = {};
settings.attributes = {
  ...settings.attributes,
  thing: {donut: 'cake'}
};

I checked, and if settings.attributes was set from the start in the first example, it would be faster. But I was trying to simulate the case where settings.attributes would be undefined, so you have to set it prior to running Object.assign, which apparently turns out to be slower than the second approach.

@ZebulanStanphill
Copy link
Member Author

After restarting e2e tests several times, the same ones keep failing, but I haven't got a clue as to why. I wonder if my optimizations have revealed a race condition or something... 😕 Any help would be greatly appreciated.

@aduth
Copy link
Member

aduth commented Mar 23, 2020

Is it the same error each time? From the current one, it appears to be one which has been frequently failing lately: #21052.

It might just be bad luck combined with a particularly flakey test. I'll restart the build. Separately, I'd also like to find some time this week to properly debug / fix these current intermittent failures.

@ZebulanStanphill ZebulanStanphill force-pushed the update/replace-lodash-assign-with-vanilla-js branch from 389d8d2 to f9ea821 Compare March 24, 2020 15:31
@ZebulanStanphill
Copy link
Member Author

I did some further testing, and I discovered that the second test I made is only faster in Firefox; it is slower in Chrome. I have now discovered an approach that is more verbose, but it is the fastest I've tried on both Chrome and Firefox. It also happens to use very basic JavaScript that probably won't end up using any core-js polyfills.

I have rebased this branch and updated it to use this new approach.

The tests that were failing before are no longer failing, but now a different test is failing. 😕

@aduth
Copy link
Member

aduth commented Mar 24, 2020

While I think it's always a good idea to have performance in mind, I also think it's important to balance the benefit in mind of (a) the human time-cost of benchmarking and (b) the maintainability impact of a more complex implementation. And by benefit, we should consider what the real-world impact is. If we're talking about code which will run a dozen times during the average lifecycle of a page session, I don't personally think it's worth getting too caught up in the minutia of ops/sec of various implementations†, especially if there's one option which is more obviously simple.

Which is to say that, between the options:

const alignAttribute = {
    type: 'string',
};

if (
    typeof settings.attributes === 'object' &&
    settings.attributes !== null
) {
    settings.attributes.align = alignAttribute;
} else {
    settings.attributes = {
        align: alignAttribute,
    };
}

...and:

settings.attributes = {
    ...settings.attributes,
    align: alignAttribute,
};

I'd generally be more in favor of the second, given the circumstances.

† To be clear, I don't mean to say it's wasted time. I think it can be useful knowledge to keep in mind for any implementation, especially when working in more "hot" code paths than what we're dealing with here.

@ZebulanStanphill ZebulanStanphill force-pushed the update/replace-lodash-assign-with-vanilla-js branch from fa6559b to 22d88e0 Compare March 24, 2020 20:49
@ZebulanStanphill
Copy link
Member Author

ZebulanStanphill commented Mar 24, 2020

Good point; I've reverted to the spread approach. (I've also squashed a few commits and rebased the branch again.)

@ZebulanStanphill ZebulanStanphill force-pushed the update/replace-lodash-assign-with-vanilla-js branch from 22d88e0 to ef5ee3d Compare March 25, 2020 17:54
@ZebulanStanphill ZebulanStanphill force-pushed the update/replace-lodash-assign-with-vanilla-js branch from ef5ee3d to fe86a1e Compare March 26, 2020 20:16
Copy link
Member

@aduth aduth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good 👍

@ZebulanStanphill ZebulanStanphill merged commit 4c53528 into master Mar 27, 2020
@ZebulanStanphill ZebulanStanphill deleted the update/replace-lodash-assign-with-vanilla-js branch March 27, 2020 19:30
@github-actions github-actions bot added this to the Gutenberg 7.9 milestone Mar 27, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Package] Block editor /packages/block-editor [Type] Code Quality Issues or PRs that relate to code quality
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants