Compatibility with Prototype.js has been broken since 3.2.46
Vue version
3.3.7
Link to minimal reproduction
https://github.com/Sublime1/Vue-Prototype-issue
Steps to reproduce
There are 3 HTML files at the Github repository - just save them locally and open them in your browser.
- working.html uses version 3.2.45 and it works fine.
- broken.html uses versions 3.2.46 and is breaking.
- broken-3.3.7.html uses latest Vue version 3.3.7 and is also breaking
What is expected?
The browser should display "Hello vue!" (as it does on versions 3.2.45 or earlier)
What is actually happening?
Versions from 3.2.46 and later are breaking with console error:
Uncaught ReferenceError: _toDisplayString is not defined
System Info
System:
OS: Linux 5.10 Ubuntu 22.04.2 LTS 22.04.2 LTS (Jammy Jellyfish)
CPU: (24) x64 12th Gen Intel(R) Core(TM) i7-12850HX
Memory: 2.69 GB / 7.77 GB
Container: Yes
Shell: 5.1.16 - /bin/bash
Binaries:
Node: 12.22.9 - /usr/bin/node
npm: 8.5.1 - /usr/bin/npm
Any additional comments?
We have a legacy web app that has a lot of Prototype.js code that we would prefer not to touch. Our Vue code worked great until we recently upgraded to Vue 3.3.7. I traced the problem back to 3.2.46. Hopefully the test case is sufficient to help resolve this. We don't want to get cut off from Vue upgrades, but we are not currently in a position to drop Prototype.js
Thanks for an amazing piece of software.
In 3.2.46 we refactored the ast.helpers
to a Set
and used Array.from
to get the helpers array.
https://github.com/vuejs/core/pull/6774/files#diff-3990584204ce4ce4bd8dcd1e3172f04a8211ee56ec2cdd2dfde626d926610681R211
Prototype.js overwrites the Array.from
method: http://api.prototypejs.org/language/Array/from/index.html
And it doesn't handle the Set
case, which can be expected, as it's such an old library…
My recommendation would be to re-override the override of prototype in that project with something that doesn't break modern JS.
We should not revert our change as we can't guarantee to respect such edge cases in the future, caused by outdated libraries that mess up globals (prototype is not the only one doing that, it was a common practice back then)
In case you need it:
Putting the following lines before the first call to createApp
fixes the issue:
const iframe = document.body.appendChild(document.createElement("iframe"));
const iframeArray = iframe.contentWindow.Array;
document.body.removeChild(iframe);
Array.from = iframeArray.from
Thanks sodatea for the helpful workaround - that seems to have solved my immediate problem.
Your decision not to respect such edge cases is understandable, but I must admit to a little touch of disappointment, even if I completely understand your point of view. There are many legacy apps out there, especially in older companies, where it's not feasible to rewrite codebases to remove libraries like Prototype. While it's not ideal that we still have to support such old code, the world is complex, and full of "edge cases" like mine. Anything you can do to help is appreciated.
I understand your sentiment, and am aware of the multitude of legacy apps out there. And I feel the pain of having to maintain these apps, often with with companies not willing (or arguably not able) to provide funds and/or time to migrate away even 8 years after the support of the library in question has stopped.
And I understand that we could fix this specific problem with a small change (albeit one we would have to ensure not to re-introduce later, elsewhere in the codebase).
I still feel that we need to take this stand as a matter of principle.
- The problem is caused by prototype, not us. So in principle, we should not be the ones fixing it. the fact that no one works on prototype anymore should be motivation for its consumers to move to a more modern, and well supported solutions.
- While we could invest our funds and/or time to work around the problem caused by prototype.js, that means we divert our scarce resources as an Open Source project away from moving the library forward, in favor of artificially prolonging the lifespan of outdated code in legacy apps.
- And that means that these companies running these legacy apps essentially outsource their problems, caused by their legacy code, to OSS projects.
- And also, quite frankly, we don't want to limit ourselves in this way by not using certain JS features in all of our codebase now and in the future, even though the browsers we support do support these language features.
Thanks for the thoughtful reply, Thorsten. I do appreciate your engagement on this. It's a tricky one because there are multiple factors involved, aren't there?
On one hand you could argue that no library needs to have a duty of care towards other libraries, and that you should be able to do whatever you want in isolation, without regard for the status of other libraries and the projects/companies that use them. However the real world is messy and things get complex pretty quickly.
For example, if a Vue release broke compatibility with e.g. jQuery, I have no doubt that you would probably be able to come to a resolution there pretty quickly. That, of course, is due to jQuery still being widely used, and actively developed, which is reason enough to maintain compatibility. And that picture naturally changes for other libraries with smaller usage.
In fact, when I went to find usage statistics for Prototype.js, I was surprised to see how widely used it still is. I don't know how accurate these links are, maybe you have a better source. This link claims that Prototype has a 0.5% market share, compared to Vue's 1.0% - not that different! This link looks at more popular sites, which would presumably not include legacy apps such as ours. This has Vue at 8.01% and Prototype at 1.66%. Quite respectable and definitely surprising.
The point is: there is an arbitrary line at which duty of care towards other libraries stops, and that line is a little messy and not always easy to distinguish, although usage statistics help. If I were the only person on the planet still running Prototype.js in my codebase, that would be pretty clear. But when there are larger numbers, at what point do you say, maybe we have a duty of care here?
Regarding the point about companies not being able or willing to migrate away from old libraries: if I imagine the extreme case, where one would have to rewrite currently stable code that relies on Prototype.js, it might spell significant effort and risk, which in turn could make the effort not financially feasible, which in the worst case could spell the possible demise of the product. Of course that's the extreme case, and in practice, people will probably fork their own version of Prototype and keep going as long as possible with that. There seems to be plenty of PRs over on their github with quite some activity, even if the maintainers aren't going to release new versions. But the point stands that these are often not trivial things to do, and eventually you run out of road and have to make very difficult decisions.
The other factor, of course, is how much effort it is for your team to revert. If there were an equally good solution to the one that required Array.from that meant little pain for you, that could be worth considering. If on the other hand, the previous solution you had before 3.2.46 had was a real pain for you to maintain, well then OK, that would obviously not be a runner.
Again, thanks for the engagement, and I really just want to plant a seed not to forget us legacy people. We are often in a world of intense compromise, trying to drag ourselves into the future against strong forces of inertia and messy reality. We don't want to get left behind!
Thanks for your thoughtful response, as well!
I agree about the kind of arbitrary line of duty of care, in general.
There is something special about the problem with Protoype though: It pollutes the global namespace, overriding existing Javascript functionality in the process. That was, in my recollection, one of the reasons why Prototype lost to jQuery, which does not do this.
This pollution of the global namespaces has consequences for you as a user of Prototype that goes beyond the individual problem you found here:
Say we fix this problem for you in Vue, by no longer using Array.from()
on Sets in this specific case, plus we add some kind of eslint rule to not use it again in other places in the future.
In a couple of months, the same problem can arise for you when you use i.e. nuxt, or vuetify, or vue-query, or any other 3rd party lib from the Vue ecosystem, when they introduce a change that uses Array.from
on a Set (or write other code that is incompatible with one of the globals changes done by Prototype).
So I think the better solution for you as a user of Prototype is to fix these problems on your end with workarounds like the one soda shared, unless you want to continue living with this risk, findig yourself opening issues in the repos of other projects that you rely on each time that prototype's global pollution breaks their code, and thus your project - each time hoping the maintainers will help you out and revert their change.
As that is a long and well-known problem with Prototype, I imagine there should be literature out there about possible workarounds.
Yes, you've convinced me of the correctness of your approach. In fact, I'm going to fork Prototype, and take a few security things that I saw in their PRs too. Then I won't even need sodatea's clever workaround. It will buy me a little more time before I inevitably have to migrate all my Prototype code to something modern.
I appreciate the engagement and look forward to continued usage of Vue in our app!
Those who are migrating away from Prototype might be interested in a blog post I wrote about this topic. It took the Jenkins project months to remove all usages of Prototype.
Just adding a link for anyone interested to an ongoing discussion over on the Prototype.js github about the future of Prototype. Feel free to join in!
@basil thanks very much for that. It will come in handy to me one day, I'm sure!