11 ms·
A horrifying globalThis polyfill in JavaScript
- ahmeni 7y agoThis is interesting from both a technical challenge point of view as well as a neat introduction into the various differences for ECMA run-times, especially for the module scope. It's impressive how small this polyfill ends up being, even with the Object.prototype munging avoidance.
- Klathmon 7y agoYeah I've see a lot of people (the author included) call this horrifying, I think it's beautiful! It's a piece of code that is simple on it's surface but has an incredible amount of depth to it. Every single line has a purpose and a backstory. And it solves a very simple to understand problem that many don't even know is a problem until it bites them (most often when trying to use a library in a web worker the first time).
- chii 7y ago> simple on it's surface but has an incredible amount of depth to it that's the definition of bad code: looks simple and innocuous. Does something completely out of the world and "unexpected" (from the point of view of a novice/unknowledgable programmer).
- Klathmon 7y agoBut that's what makes this beautiful in my opinion, it can't really be simplified anymore and still do it's job! It's bad code that has to be bad, it has to be complex, it's inherently difficult and needs to tiptoe around edge cases and avoid pitfalls that 99% of us don't know or have to care about. And yet it's able to do that and still be small, concise, and relatively easy to understand on a basic level, even if you can't quite understand the full reasoning behind why it was created that way on the surface. I have similar feelings about the fast square root function from Quake III. It's horrible and ugly and confusing on the surface, but incredibly powerful, fast, and humbling when you really look at it. And it served a purpose that enabled the game to work!
- daurnimator 7y agoThanks for the new method! In fengari we currently use https://github.com/fengari-lua/fengari-interop/blob/8e59efb7e25469ed1d56dcd065361a1481112e9e/src/js.js#L92-L107 https://github.com/fengari-lua/fengari-interop/blob/8e59efb7... const global_env = (function() { /* global WorkerGlobalScope */ /* see https://github.com/sindresorhus/globals/issues/127 */ if (typeof process !== "undefined") { /* node */ return global; } else if (typeof window !== "undefined") { /* browser window */ return window; } else if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) { /* web worker */ return self; } else { /* unknown global env */ return (0, eval)('this'); /* use non-strict mode to get global env */ } })(); I created https://github.com/fengari-lua/fengari-interop/issues/45 https://github.com/fengari-lua/fengari-interop/issues/45 to track.
- bfred_it 7y agoeval doesn’t work with some CSP settings, the article mentions this and other limits of previous solutions.
- codedokode 7y agoYou use "global_env" only inside luaopen_js(). Why don't you just add it as an argument, for example: function luaopen_js(L, global) { ... } instead of writing this ugly hack?
- daurnimator 7y ago`luaopen_js` is the entry point for the library with a set API. (that follows https://www.lua.org/manual/5.3/manual.html#lua_CFunction https://www.lua.org/manual/5.3/manual.html#lua_CFunction ). The caller cannot pass more arguments.
- Technetium_Hat 7y agoWhy the comma operator with the eval?
- 7y ago
- Arbalest 7y agoI think what this demonstrates is the patch upon patch nature that Javascript has taken on. Something we already knew, but with ES2015+, was kind of hidden from us. That isn't so say the new ES revisions aren't significant improvements, but just that they still rely on this old infrastructure.
- underwater 7y agoAn alternative way of looking at things: the dynamic nature of JavaScript and ability to monkey-patch things like this has let the language move forward. The fact that I can use classes, block scopes, const and let, new array methods, decorators, etc. without having to wait for every client to update to JacaScript N+1 is pretty sweet.
- IgorPartola 7y agoThat’s the job of a compiler/transpiler though, i.e. praising assembly for the benefits that C provides. JavaScript/ES is just now getting features that other languages have enjoyed for one or more decades because JS people have discovered the features and how to build the tooling. So good for JS for finally getting these basics, just wish it happened about 15 years ago.
- Klathmon 7y agoI hate this trope that "js people just discovered this feature and now rush to get it included". Not only is it not true in many cases (I've been using pattern matching for years in other functional languages and I'm anxiously hoping the proposal will get closer to inclusion in JS, but I'm sure if/when it lands I'll be told about how sad it is that I just learned about this feature when others have had it for decades...), but it's also pointlessly "holier than thou". If JS doesn't have a feature, people say that they want it. Once it gets it, another crowd complains about how they should have had it 15 years ago. The fact is that there wasn't much of a need for something like globalThis 15 years ago. We had window, and that's all that we needed. Then we got global with node, and then self in workers, and then JS started getting more usage in embedded contexts with none of that. A problem was found, a proposal created and refined, a polyfill made, and finally it's going to be part of the language. This is progress, this is the maintainers identifying and fixing a deficiency, only to be met with "who cares they should have done it years ago [when nobody was asking for it]". My point being there's just no need to say that. It doesn't contribute anything, it's just belittling the work of some very talented people who are making things that people are asking for in ways that don't break past code. That may not have been your intention, and I'm sorry for coming into this with a lot of baggage here, but it's just something that really gets under my skin and I've seen multiple TC39 people talk about how they avoid places like HN because of those kinds of attitudes, and I feel that everyone is missing out because of it.
- underwater 7y agoThe line: __magic__.globalThis = __magic__; // lolwat Seems needlessly, well, magic. Keeping it at two lines would make it easier to grok: Object.prototype.__defineGetter__('__getValueOfThis__', function() { return this; }); const globalThisValue = __getValueOfThis__; // Invoke getter on "global object" globalThisValue.globalThis = globalThisValue; Just because it magical doesn't mean it has to be confusing.
- chrismorgan 7y agoNow you’ve added a variable declaration, which makes life more painful for keeping your polyfill small. The name of the game in these things is minimising size—though I will allow that a more verbose form can be useful for teaching.
- thrower123 7y agoWorrying about keeping polyfills micro-optimized seems somewhat silly, when a typical website is pulling in megabytes of other scripts and trackers. Not to mention when just the headers and cookies on every request can balloon up into the tens or hundreds of kilobytes.
- lucideer 7y agoLeaving it at one line, but just naming it something more explanatory than `__magic__` would also improve it slightly (without needing an extra var declaration). `__getValueOfThis__` is good. Something like `__globalThisGetter__` or `__globalThisPolyfillGetter__` might be even clearer given the deletion line. e.g.: __globalThisPolyfillGetter__.globalThis = __globalThisPolyfillGetter__; delete Object.prototype.__globalThisPolyfillGetter__;
- IgorPartola 7y ago__getGlobal__()
- lucideer 7y ago
- hardwaresofton 7y agoWhat's next with JS talk @ Google IO where Mathias presents this work: https://www.youtube.com/watch?v=c0oy0vQKEZE https://www.youtube.com/watch?v=c0oy0vQKEZE
- dmitriid 7y agoWell, if TC39 weren’t a bunch of kids running with scissors, you probably wouldn’t have this problem, would you? You wouldn’t have to have to bolt on .meta onto import after the fact. You wouldn’t have to create horrible polyfills for the horribly named globalthis. And so on and so on and so on. Hint: just spend actual time actually designing the language. Hint: Introduce System namespace/module with System.globals. There. Solved. Trivially polyfilled. The BS about “we can’t easily introduce new global objects/modules” is BS after WeakMaps and SharedBuffers and god knows what else.
- realityking 7y agoHow would System.globals be any easier to polyfill? The problem described in this article is how to reliable get access to the global object (or bow globalThis) in currently shopping JS environments in the first place.
- dmitriid 7y agoAh, true. I re-read the article. You’d need the horrible polyfill anyway. So, my other point still stands: instead of any long-term planning and robust design, TC39 keeps introducing features that are badly specified are underspecified, and that require patches and workarounds in the language itself (yup, .meta, globalThis and some others are nothing but poorly thought-out patches) and conplex workarounds for the various JS environments that may or may not support these features.
- thrower123 7y agoIf Javascript needs anything, it is a breaking change to go back and clean out all the cruft that has accumulated over two decades of being bent and banged and globbed onto. I don't think that that can actually happen, so we just keep trying to cake more lipstick on the pig; maybe in five years we can put it out to pasture and use WebAssembly instead.
- colejohnson66 7y ago
- silverwind 7y agoI wonder if it is possibly to access the local scope with similar trickery. It would be useful to get a reference to a local variable via a string: import 'foo' import 'bar' (function (module) { console.info(localThis[module]); })('foo')
- moomin 7y agoConcrete question: could this be used to find the XmlHttpRequest constructor?
- Klathmon 7y agoYup! This can get XmlHttpRequest in any environment that has it. Which means it's extremely easy to write code that works both in the main thread and a worker transparently.
- DonHopkins 7y ago"globalThis" too shall pass. JavaScript transit gloria mundi.
- chii 7y agoAs the comment on the page says: The real WTF is (0, eval)('this') wtf is that syntax??!!one!
- Klathmon 7y agoThat's a really cool trick/abuse of syntax that goes back to the confusing `this` in javascript. calling a method or function directly (when in the global scope) will set `this` to the global object. But calling a method or bound function could have a different `this`, and calling a function directly in another context can have a different `this`. the `(0, funcName)` syntax works by evaluating each item in the parens, but returns the last item. (don't ask me why this is in the syntax, I genuinely don't know and have never seen it used beyond this as far as I know) So that syntax is abused to basically (simplified here) reassign the funcName to strip out the `this` that is bound to it and reassign it to the global context. It then evals that `this`, and returns it! Meaning it will always get the global `this`, or `globalThis`! It would be similar to doing this: var thing = eval thing('this')
- saagarjha 7y ago> the `(0, funcName)` syntax works by evaluating each item in the parens, but returns the last item. This is just how the comma operator works in other languages such as C and C++: https://en.m.wikipedia.org/wiki/Comma_operator https://en.m.wikipedia.org/wiki/Comma_operator
- Klathmon 7y agoit is! And I just now realized that I have seen it used many times before, in for loops and variable declarations. It just seems so foreign and out of place when used outside of those instances though, and for me at least the purpose of it wasn't intuitive at all!
- mturmon 7y agoIn C, the commas are also used in macro definitions where you need multiple statements to be output by the macro, but can’t use semicolons because the macro might expand in a context where only one statement is expected. In both the OP and this one,it comes off as clever but tricky.
- codedokode 7y agoI don't understand why they need access to global variables. If you are writing a portable module using strict mode, you should't need access to "window" or "global". If you are writing non-portable code then you can access "window" or "global" by its name. I mean, they intentionally closed access to global object through "this" in strict mode and now they want it back.
- amitport 7y agowould you prefer the current: (?) let global = typeof window === 'undefined' ? global : window // do something with global.theGlobalProp (this is used to refer to currently available globals which are not going away anytime soon)
- Uehreka 7y agoCan’t you use `self` to refer to the global object in both client and server contexts?
- LocalPCGuy 7y agoThe article goes into why you can't just use `self`
- IgorPartola 7y agoI would rather do: export default foo(theGlobalProp) {...; return ...;} Why should a module reach outside its own scope?
- deleted 7y ago[deleted]
- kozak 7y agoSo, we made the polyfill horribly hideous in order to make it run everywhere JavaScript can run, and yet, it doesn't work on IE10-. Maybe it's not a good way to set globalThis after all :)
- folkrav 7y agoIE>11 is 0.35% of global usage according to caniuse, and even being generous and pushing it to 0.5%, it's horribly low. Maybe we just shouldn't support IE>11 anymore.
- deleted 7y ago[deleted]
- deleted 7y ago[deleted]
- TheCoelacanth 7y agoThat's still much, much higher usage than jsvu[1] which they gave as the justification for needing this polyfill. 0.35% of Internet users is ~14 million people. jsvu has been on npm for about a year and has never had more than 1000 downloads per week, so it has under 50k users, more than two orders of magnitude less than IE<11. [1] https://www.npmjs.com/package/jsvu https://www.npmjs.com/package/jsvu
- mathias 7y agoAuthor of the referenced article here. The article includes a working polyfill that includes old IE support, so I’m not sure what you’re complaining about. Also, saying “jsvu is the justification for the polyfill” doesn’t make much sense. jsvu is just how I happen to test in various standalone JavaScript engines. There are other, non-jsvu ways of getting such binaries, e.g. compiling one yourself, and there are JS engine binaries that jsvu doesn’t provide (Rhino, Ringo, Nashorn). jsvu usage does not correspond to anything you seem to be talking about, and comparing its download count with IE10 usage is one of the more extreme apples-to-oranges comparison I’ve seen. ️
- 7y ago
- josteink 7y ago> TL;DR globalThis is not “the global object”; it’s simply the this from the global scope. Thanks to Domenic for helping me understand this important nuance. Can someone in the JavaScript community please enlighten me and tell me why you would want to be able to obtain a reference to “globalThis” rather than just the global object itself? Maybe I’m just being presumptuous, but I suspect that’s what this object is going to be used for anyway, for storing global state. And then it might as well just be called global, right? Unless of course you want to permeate the chaos that is JS “this” into this new global context, but who would deliberately want to do that?
- deleted 7y ago[deleted]
- bzbarsky 7y agoA few things: 1) In browsers, the actual global (the Window) is _never_ exposed to script. The reasons for that used to do with same-origin policy enforcement, with exposure of the Window to script being considered a security bug in various browsers for a while, but I'm not sure those reasons are still relevant nowadays. At this point, the main reason the WindowProxy is what's exposed is that this is what web authors and web code expect. It's actually quite terrible, because it conflates two objects: "the web page" (corresponding to the non-exposed Window) and "the thing we load web pages in" (corresponding to the WindowProxy). _That_ is a design mistake in the web's object model that dates back to the mid-'90s... In any case, the upshot is that in browser window contexts you can't get a reference to the global; the only thing you can get is the thing that is globalThis. In other contexts (web workers, Node), the two are the same thing. 2) The property naming is ... well, read https://github.com/tc39/proposal-global/blob/master/NAMING.md https://github.com/tc39/proposal-global/blob/master/NAMING.m... for a (longish) summary. Node uses "global", but browsers couldn't do that because it broke some websites. Browsers use "self", but Node wasn't willing to do that, citing concern over the same sort of breakage. And so on, and so forth.