-
-
Notifications
You must be signed in to change notification settings - Fork 44
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
Switch to dequal to remove 45 transitive dependencies #497
Conversation
Why can't svelte bundle? |
Also, this isn't equivalent; dequal relies on the runtime (as opposed to "require time") existence of lots of builtins, which makes it brittle in an environment that runs untrusted code (namely, all environments). On the other hand, |
Even with bundling, there's absolutely no reason to add 45 extra dependencies for such a simple piece of functionality. It still adds an extra 91k of output after bundling not to mention opening users up to more supply chain attacks by adding 45 extra chances for a package to be compromised. It also forces users to apply workarounds because some of the dependencies of
You can see the implementation here. It's tiny and the function I'm calling doesn't require any builtins: https://github.com/lukeed/dequal/blob/master/src/lite.js.
I'm afraid I have no idea what you're talking about. How does checking if two javascript objects are equal involve iframes and how would that be relevant to the |
Um, it's calling methods on prototypes, so it does rely on builtins - |
This line will be false if the Date comes from an iframe, as will any lines that compare constructors by identity, since that is a brittle and broken approach. (also, any reliance on |
Ah, I was talking about Node built-in modules. E.g. If users are going around redefining methods on JavaScript objects in ways that break them, they're going to run into trouble irrespective of what we do here and it doesn't seem like the solution should be for projects to avoid calling any methods that JavaScript provides by default |
and yet, deep-equal, like most of my projects, are robust against later modification of builtins. That might not be something you care about, but it's something that's important, since untrusted code often runs in every environment. |
None of this applies here. These are simple objects only containing primitives like strings and numbers (no dates) and they are all under control of this repository. If these checks fail because you patch object prototypes in ways that break it you have bigger problems than a failed comparator function in one of your dependencies. |
@jessebeach any chance you would be able to take a look at this? Nearly all of Svelte's dependencies are coming from the From https://arve0.github.io/npm-download-size/#svelte@4.0.0-next.0: |
@benmccann point made :) This is not a list I want the #1 and #2 spots on. I appreciate you taking the time to show the impact here. What is your ideal timeline? Finding time to code these days is a little difficult with a tiny human demanding my attention at home =D |
Thanks so much for taking the time to respond @jessebeach! We don't have an exact date yet for the Svelte 4 release, but are planning for roughly two weeks from now. This wouldn't be a breaking change so we could always do it in a minor release later on, but it would be fun to tout that https://svelte.dev/repl loads faster in the v4 release announcement. If there's anything you'd like to check feel free to let me know and I'd be happy to do the leg work. I've got a couple little humans running around too, so know how much scarcer time becomes! |
Another alternative is that I could extract some subsets of checks out of deep-equal, and then aria-query could use those - and then the same back compat and robustness would be assured with far fewer deps. If that's palatable, then an exhaustive list of the types of things checked here (plain objects, arrays, strings, numbers?) would be all I'd need. |
Yes, plain objects, arrays, strings and numbers. The usages are here: aria-query/src/elementRoleMap.js Line 27 in 1160138
and here: aria-query/src/elementRoleMap.js Line 70 in 1160138
We're comparing Lines 352 to 355 in 1160138
The Lines 360 to 385 in 1160138
And the Lines 387 to 392 in 1160138
Here are some juicy examples https://github.com/A11yance/aria-query/blob/main/scripts/roles.json#L619-L769 |
Would it still need to handle cycles, or boxed primitives? Would you use loose mode, or strict mode, or both? |
In this case, I need value equality, and not instance equality. I don't know how that translates to loose or strict. But to give an example:
I can't do As for cycles, The objects don't contain references to ancestor nodes in the same tree. A DFS or BFS would work. |
"loose" would mean that |
Aha! No, I have absolute control over the data in this project. I would rather update the data to be a number or string as it should be, than have to rely on loose matching.
|
https://npmjs.com/deep-equal-json @benmccann is this a sufficient improvement for the metrics you care about? |
Thanks for making the effort. It's an improvement, but still has a ways to go in my opinion. https://arve0.github.io/npm-download-size/#dequal https://arve0.github.io/npm-download-size/#deep-equal-json Is there any reason not to just merge this PR as is? I don't see the need to create new libraries when we have a fully working solution right here already |
It's not equivalent because it's not robust against later builtin modification. I can certainly remove |
More to the point, dep count and package size are largely irrelevant metrics when compared to correctness, so I'd hope that the latter wouldn't be sacrificed to appease the former. |
Would you be happier to use Protection against built-in modification seems irrelevant to It would be nearly impossible to write any decently-sized JavaScript project without using any built-in methods. To say that a JavaScript library cannot use any method on an object provided by the language is an incomprehensibly insane position to take. While it's fine if you want to do that for your own projects, it seems absolutely mad to try to force it on the rest of the ecosystem especially when it comes at a cost of making libraries 15x larger. The easier position to take would be that the extremely rare projects that override built-in methods in a breaking way should be the ones to be changed I also question how this policy could even be enforced. Are you regularly auditing every version of each of the 45 dependencies of |
Yes, I am, but that's easy because I maintain all of them :-) Basically all of my 400+ packages follow this principle. |
Please correct me if I've misunderstood the conversation (I've been looking at the source code for The argument is that if (a.getTime() !== b.getTime()) return false; ...it does this: if ($getTime(a) !== $getTime(b)) return false; Here, But as far as I can tell, Date.prototype.getTime = () => 'lol';
const { default: CallBound } = await import('call-bind/callBound');
const $getTime = CallBound('Date.prototype.getTime');
console.log($getTime(new Date()); // 'lol' So — again, please correct me if I'm mistaken — this doesn't protect against built-in modification (and if it did, it wouldn't do so for the rest of your app), it only mitigates it, at signficant cost. This seems like a very bad trade-off that results in slower/more bloated apps in return for a false sense of security. |
I'd like to re-iterate that considering whether built-ins are used for Date and Regex is completely irrelevant anyway. As both @dummdidumm (#497 (comment)) and @jessebeach (#497 (comment)) have pointed out, the comparisons being done here are on plain objects, arrays, strings and numbers. Thus, the fact that |
@Rich-Harris yep, that's right - it's impossible in JS to defend against first-run code, but what my packages do is defend against second-run code - ie, code that's evaluated after mine has cached the method, which is a common security approach used by many environments, including Salesforce, Runkit, and a number of others. |
If guarding against that sort of thing is desirable in those environments, it would be far more effective for them to do something like this before importing anything: Object.freeze(Date.prototype); // and everything else Expecting everyone to avoid built-ins is very silly indeed. |
You're welcome to believe so, but I think it's critical to do so, especially in packages that are heavily transitively used. |
Even if that were true, it's simply not relevant in the case of this library — as articulated above, the only things being compared are numbers, strings, plain objects and arrays. As far as I can tell, the only time return Object.keys(bar).length === len; ...and if someone has overwritten For the purposes of this library, |
Thank you so much @jessebeach!! I really appreciate it! Btw, just so it doesn't fall off the radar, I sent the same change to |
Thank you 🙏 . I landed that one as well. |
@jessebeach just a gentle reminder about a new release. thanks! |
Thank you for the reminder! It's on my mind. Just a few things in my personal life to attend to this weekend. I'll have time this week. |
@benmccann et voilà. aria-query@5.2.0: https://www.npmjs.com/package/aria-query |
Looks like there's a missing call site → #502 |
It's used on this line in the file on npm, but it's been already converted. The only reference to deep-equal I can find in this repo now is in a flow definitions file. Edit: forgot to add the line aria-query/src/elementRoleMap.js Line 27 in ef150a3
Building it locally gives me the correct "dequal" import |
Should be fixed now. I had to run |
thank you so much!! we just updated the Svelte 4 branch with the new version. really appreciate your help! |
Here's our release announcement: https://svelte.dev/blog/svelte-4. Thanks again @jessebeach! |
Turns out this PR was a breaking change (to axobject-query as well) - dequal declares an If you're interested in fixing it, either this PR will need to be reverted, or dequal will need to extend its engines.node support significantly. |
…o CI Specifically, v5.2 of aria-query and v3.2 of axobject-query require node 6 via the dequal dependency, and neither have an engines declaration. This is a breaking change See A11yance/aria-query#497 (comment)
Seeing as how this has been released for over a year and no one has noticed, I don't think there's a need to do anything. Node 6 reached EOL five years ago and is still supported by this PR. That's plenty old enough |
That's certainly a position one can take, but that's not how semver works - a breaking change is a breaking change. |
If we want to be pedantic about semver, the most reasonable solution would be to release a new major. And in that case I'd add an |
Yes, that's exactly what I'd suggest - v6 can keep using dequal and have as aggressive an engines requirement as desired, and v5 can get a revert. However, node's EOL status is irrelevant - old node versions are helpful stand-ins for the older browsers that need accessibility ensured the most. (which, to be fair, doesn’t really apply to linting, but definitely applies to this library and its in-browser usage) |
Which browsers are those? |
@ljharb we chatted in person but can you add some info about in-browser usage? Which tools are using it in-browser, and how? |
@marcysutton in this particular case, the only in-browser usage I'm aware of is svelte's, which presumably only includes modern browsers. My response was about the general case to explain why my packages broadly support super old node. |
@ljharb gotcha, thanks for the update. I think it's worth it to be pragmatic about how the library is used when making those decisions, especially since community feedback has shown that consumers are concerned about bundle size and maintenance. Older browsers are not going to have the same level of ARIA support as modern ones, especially for features in the newer specs. So when considering these trade-offs of maintenance and support, I think you'll have to be more specific about "Node as a helpful stand-in" for an old browser. Which ARIA attributes/APIs are at play in those environments, if at all? It might help to answer the question of relevancy for these libraries in particular. |
Unfortunately these threads tend to conflate general ecosystem-wide concerns with aria-query-specific concerns :-) Specifically, aria-query and axobject-query quite certainly don't need to have support for older browsers or nodes, for the reasons indicated. The only consideration for v3 is that dropping any engine for any reason is semver-major. For v4, it could have been released with |
I don't mean to beat a dead horse here, but transitive deps aside... the I mean, we're talking ~1K ops/sec to ~2M ops/sec |
@jeremy-daley-kr this package now has zero dependencies, so it's quite irrelevant - but also, comparing their speed is nonsensical because they don't do the same thing. deep-equal compares cross-realm, robustly, (dequal does neither) which afaik can't be done any faster than deep-equal does it (in JS, at least - node's C++ implementation is much faster). Additionally, this specific operation is probably done less than 1K times on an entire codebase - it literally doesn't have a practical impact. |
deep-equal
has 45 transitive dependencies whereasdequal
has none. This is important for Svelte which loadsaxobject-query
in the browser as part of https://svelte.dev/replSee also A11yance/axobject-query#311