JSJ 434: Understanding and Using ES Modules in Node with Gil Tayar
Gil Tayar gave a presentation recently on ES modules in Node. He joins the panel to discuss how to use and think about ES modules. With considerable pushback from AJ, Gil explains how to start using modules and what the tradeoffs are between modules, script tags, and build
Special Guests:
Gil Tayar
Show Notes
Gil Tayar gave a presentation recently on ES modules in Node. He joins the panel to discuss how to use and think about ES modules. With considerable pushback from AJ, Gil explains how to start using modules and what the tradeoffs are between modules, script tags, and build tools.
Panel
- AJ O’Neal
- Aimee Knight
- Charles Max Wood
- Steve Edwards
- Dan Shappir
Guest
- Gil Tayar
Sponsors
"The MaxCoders Guide to Finding Your Dream Developer Job" by Charles Max Wood is now available on Amazon. Get Your Copy Today!
Links
Picks
AJ O’Neal:
Aimee Knight:
Charles Max Wood:
Steve Edwards:
Dan Shappir:
- webinstall.dev
- Visit Israel
Gil Tayar:
- Follow Gil on Twitter > @giltayar
- Sunset Blvd
- Node v14.0.0
Follow JavaScript Jabber on Twitter > @JSJabber
Special Guest: Gil Tayar.
Transcript
CHARLES MAX_WOOD: Hey everybody and welcome to another episode of JavaScript Jabber. This week on our panel, we have AJ O'Neill.
AJ_O’NEAL: Yo, yo, yo. Coming at you live from sunny, pleasant, pleasant Grove.
CHARLES MAX_WOOD: Amy Knight.
AIMEE_KNIGHT: Hey, hey from a gloomy Nashville.
CHARLES MAX_WOOD: Steve Edwards.
STEVE_EDWARDS: Hello from sunny Portland.
CHARLES MAX_WOOD: Dan Shapir.
DAN_SHAPPIR: Hey, coming at you from Tel Aviv with was sunny. Now it's night still under lockdown. Hopefully not for too long though.
CHARLES MAX_WOOD: I'm Charles Maxwood from devchat.tv. And this week we have a special guest, it's Gil Tayar.
GIL_TAYAR: Hi everybody, also from Tel Aviv, starting our set Independence Day celebrations here.
CHARLES MAX_WOOD: Oh, congrats.
If you're a front-end developer looking for remote work, then I recommend G2i, a React and React Native focused hiring platform that will connect you directly with their clients that need your skillset. What makes G2i a unique hiring experience is that they spend the time marketing you to their clients of your choice. G2i is a team of engineers that technically vets you upfront. If you pass their vetting, their clients have agreed to skip their initial interview process, saving you time and energy getting your next gig. They take care of all the hard work for you so you can get focused on development. To join G2i, go to g2i.co and apply.
CHARLES MAX_WOOD: So Dan set this up and I'm gonna let him introduce what's going on. Gil, we've had you on before though, I'm pretty sure. Do you wanna just remind people who you are?
GIL_TAYAR: Yep, I'm senior architect, whatever that means, at Applitools. I designed and architected and built a visual grid. I'm also a developer advocate, go to conferences, blog posts, Twitter, whatever, like part-time-ish thing and basically 30, 35 years in the industry. So, did a lot.
CHARLES MAX_WOOD: Cool. All right, so Dan, you were giving us kind of an intro before the intro, and all I really heard was the AJ's are contrarian. So do you want to tell us what the deal is here?
DAN_SHAPPIR: So yeah, for sure. So I think right before we went into lockdown, there was this conference in Tel Aviv called No TLV. I actually missed that conference because I already put myself in lockdown. That being said, I did watch all the videos and one of the great videos from that conference was Gil speaking about ESM modules and how great they are in general and how great they are in was an excellent topic on conversation. So I wanted to have Gil here on the show.
AJ_O’NEAL: Blessing.
CHARLES MAX_WOOD: you know, what the ESM modules are and how they work in general. I think, I think some people are going to be familiar and some people kind of take for granted how they work, may have used them and not realized it. So yeah, just give us a rundown and then we can talk about what it means in Node.
GIL_TAYAR: Sure. ES modules are like first things first, they are a feature of JavaScript since ES6 is 2015. So they're with us for five or six years already. Basically, they haven't been implemented. They're the last ES6 feature that's been implemented. They've started to become implemented on the browser, like natively, and now in Node.js natively. But people have been using them for five years, not natively, but rather by transpiling them. And we'll talk a bit later about, I mean, what does transpiling ES modules do in Node.js and in the browsers? There's a whole history there and a lot of stuff that's really, really confusing because of that history and because everything is so late in the game. Hopefully this episode will remove a lot of that confusion.
DAN_SHAPPIR: Just to add to that, just to maybe to clarify, when we say ES modules, what we practically mean is the import keyword and the export keyword and the export default keyword combination and so forth, right?
GIL_TAYAR: Exactly. Let's think pre-ES modules days. Back in the days, I mean, really back in the days, JavaScript didn't even have a module system. It never did. There was one big script source and people worked with that and that was fine back in the MySpace days and GeoCity days. But then things started to become bigger and bigger, the source code became bigger and bigger, and people wanted to separate them into separate files, which makes sense. What people did in the JavaScript, well, in JavaScript was because there was no module system, they invented one. And then we had things like, people don't remember that today, but Dan probably does, AMD and RequiredJS were the two more popular ones. They were these weird stuff where you wrote the files and then there was this thing that combined them all into one and like hooked them with various means. I think Google Closure also did sort of a module thing. But in the end, the browser, and we're talking browser days, pre-Node.js, the browser had to have just one script source. So basically all the files were combined into one. That was pre-ESM. Now, even pre-ESM, Node.js comes to the scene. Node.js had to have a module system because writing server-side code with one big file doesn't make sense. So they borrowed one, something that wasn't really used back then, but Node.js made very popular, it was Common.js. Common.js is what is now used in Node.js for a module system. It's that require load dash or require foo or whatever, where you write a file, what is called a module. You do a module.exports and export whatever you want on that exports object. And when you require that file, Node.js basically runs the file, takes the module.exports object and returns it as a return value of the require. So it's a really, really cool and simple module system. Love it. I mean, when I saw it, it was like, oh, this is so simple. Makes total sense. And that was Node.js. That was pre-ES6. And then in the ES6, people came and said, no, we need a real module system something that is part of the language.
AJ_O’NEAL: Something that breaks the language, you mean?
GIL_TAYAR: Well, that's, I mean, conditional, a lot of things break the language. Object.assign breaks the language, for, out breaks the language, async, away breaks the language, promise breaks the language.
AJ_O’NEAL: Wait, how does object.assign break the language?
GIL_TAYAR: I mean, it's not backward compatible.
AJ_O’NEAL: How is it not backwards compatible?
GIL_TAYAR: No, not object.assign, okay, but it's for, away.
DAN_SHAPPIR: No, actually object.assign as well. If you happen to have, if somebody happened to have, to put and assign method on object on the global object instance. And then you get object to sign as part of the language. Then all of a sudden you have a problem and there's that famous smoosh gate because what was it? Muthools or somebody put a flat or flatten on arrays and suddenly you couldn't do array.flat because that broke existing code.
GIL_TAYAR: Right, but sorry, but that's an edge case. That's definitely an edge case, but I mean, any feature, any syntactic feature in ES6, 7, 8, whatever, breaks previous languages. I mean, that's why we transpiled to ES5, because all those language features don't work in previous JavaScript syntax. A lot of new JavaScript breaks old JavaScript. That's what new JavaScript features are. They're basically additions too. So yeah, any addition breaks previous language. That's fine. That's how languages evolve. All languages evolve that way. Good or bad, I try not to. Not having a module system, but rather, cludging something from require and everything is fine and it works really well for Node.js, but it's not part of the language and a lot of tooling has problems with that and everything. I think making modules a part of I mean, is there any language other than JavaScript that doesn't have a module system? Well, C++ in some ways doesn't have a module system, C and C++, because they have this weird combination of just include, hash include and whatever. But other than that, all languages have a module system and like JavaScript wanted one. So they designed one. It's ESM, ES modules, which is as Dan Sappir said import something from whatever and export default and export const x equals whatever. It's a land and it actually, I mean, definitely broke the language, but it's become immensely popular in the frontend world. I mean, most code today, whether it's Angular, React or Vue, uses ES modules in the language. They're transpiling it and we can talk a lot about. They're definitely using ES modules.
AJ_O’NEAL: I would say ES modules are definitely a native feature of React script. I've definitely seen that. I find it to be confusing because it's, it came from Ruby and C sharp. Those were the people on the committee that wanted to break the language rather than continue with the existing standard common JS, which is a module system and does work pretty darn well, both in the browser and in node, which the import stuff has not without.
GIL_TAYAR: What, I, I, I didn't. Common test doesn't work in the browser. What, what, what happens is that bundlers like webpack, browserify, rollup, et cetera, what they do is they transpile it into one big file back again. They have no choice.
AJ_O’NEAL: No, require can work in the browser just fine.
GIL_TAYAR: Not really because require is in a synchronous, it's a synchronous feature and browser can't do HTTP in a synchronous manner.
AJ_O’NEAL: They can actually can. XML HTTP request is synchronous.
GIL_TAYAR: And it's deprecated. If you think of stretch, stretch is async only. And as far as I know, all uses of require whether webpack rollup, et cetera, I mean like a hundred percent are by bundling and not by HTTP using XML HTTP. So basically. Require and import are being transpiled. Now, I think Require is amazing. I mean, CommonJS is amazing, but it's not part of the syntax. And I think the fact that...
AJ_O’NEAL: It is part of the syntax. But I grant you the point that like predominantly in the browser, no, people are not using Require synchronously. They're using some sort of tool that gives it the ability to load asynchronously by wrapping it or perhaps converting it over to the very oddly named require.js syntax, which is actually defined, not require.
GIL_TAYAR: Yes.
AJ_O’NEAL: Yeah.
GIL_TAYAR: It was confusing back in the days.
DAN_SHAPPIR: The AMD syntax.
GIL_TAYAR: The AMD syntax, yes.
DAN_SHAPPIR: Yeah, which is a total different thing from common.js, though.
GIL_TAYAR: Not necessarily, by the way. Rollup and Parcel don't do the Webpack.
DAN_SHAPPIR: No, they do UMD, which works both ways does introduce some issues. But the way that you then import that stuff is you either, you import it either using a CommonJS Require or using RequireJS. And one of them is synchronous by definition, and the other one is asynchronous by definition. And using them both within the same project is an invitation for problems.
GIL_TAYAR: So the ESPREC people, rightly or wrongly define the import-export syntax, what is now called ES modules. And by the way, I think what the nice thing is, they took the best module system of the day, which is CommonJS, and they basically turned it into syntax. If you look at CommonJS and you look at ESM, they're basically the same, but different syntax. The idea of default export wasn't in ESM originally, wasn't in ESM modules but they took it from CommonJS, now we have export default. And named exports obviously comes from CommonJS because in CommonJS, the exports object can be like an object with like properties in it. So that's basically like named exports. So when you do import a call it a B from whatever, you're basically doing a named exports, which is similar to const a be equals require whatever. So the syntax is like amazingly similar and I think that's a good thing. I think, you know, there are quibbles on syntax and yak shaving on that bike shedding, but basically it's taking require, taking common JS and turning it into a language feature, which is great.
AJ_O’NEAL: So I...Of course, disagree on this. The primary thing that comes to mind, and a lot of people are not concerned with performance anymore. A lot of people don't care about how weighty something is, you know, bundle up six megabytes of JavaScript in a file, you know, they're cool with it. But with require, you have the option, both in node and in the browser, depending on implementation, though I grant very few implementations worked in such a way. But you have the option of lazy loading. For example, you know, it's very common to have a gigabyte of dependencies in your node modules folder. And if you try to load all of those at once, it's very common that a server might take 30 seconds, 45 seconds to fully boot up. Now granted, if somebody's writing that kind of server, they're not, you know, caring much for conservation in the first place. So kind of a bad example, but I've run into that type of issue on the Raspberry Pi. That's where even normal apps can start to become under performant or take long load times. And you could do tricks with require to, you know, put a require on the first line for something you need right away, have some sort of a knit method, let things get started, let the server get going, and then start requiring other things that are needed for background tasks and et cetera, et cetera. And the import syntax does require that you have to have everything load all at once. Now granted, you can still use require with import. There's nothing stopping you from having a require in code that has an import in it because require is well defined, it's easy to understand, it can easily be implemented and transpiled and all that. But I think, although people make the argument of tree shaking and all that with import, I don't understand why you can't do that with require, but import does have some disadvantages over require and the syntax breaking and also in that it functions, everything has to be eager loaded to work.
GIL_TAYAR: I totally agree in ES2015. In ES2018 or 2019, I don't remember, we have a way import. So you can dynamically import lazily or dynamically import whichever module you want. And you were talking about cold starting a server. If you think about it, require is totally, I mean, Node.js is a totally async environment and yet the module loading system is synchronous. That's basically crazy.
AJ_O’NEAL: Well, it's not as crazy as you think because the time... So I did performance tests on this because like I said, I had this as a real-world problem. The where you lose the time is actually when you hit VM.compile. You do not lose the time on the FS read. The FS read time is sub-millisecond. The VM compile time is where you get into multiple milliseconds.
GIL_TAYAR: Yes. And so, okay, we're going deep into the rabbit hole. Maybe we can, you know, come back to that later. Because I have answers to all that.
AJ_O’NEAL: But hey, I'm super excited to hear about this. I did not know what you said about the 2018.
GIL_TAYAR: Okay, so a weight is crazily important. Not only that, in 2020, yes, 2020, we have top level of weight, which means we can a weight import or a weight of file read on the top level and not only in an async function. But this works only if the module is an ESM module, an ES module. It cannot work in a CJS module because CJS is basically synchronous. We're getting a little bit forward here. Let's go back to the history. I really want to go back to the history. So, you know, we're in ES6 world and we have the two, you know, we have common JS and we have all those AMDs and everything and then up comes ES6 with import export, right? Now what happened are two amazingly interesting things in parallel. One is browse and they basically have nothing to do with one another ESM, natively. Okay. So today in all browsers, except IE, I don't care about IE, you can do an import from to a file that is on your server. So you can do import from dot slash my module dot JS. Okay. And what the browser will do is will HTTP get to and bring that file and use it as an ESM module. No bundling, no nothing.
DAN_SHAPPIR: Just a quick comment before you continue, because people might be running out to try it if they're not familiar with it. You do need to put scripts type="module", for that to work.
GIL_TAYAR: Yes, yes. The entry point in the HTML has to have type="module", on it. Otherwise, the script itself will be like non-module, so it can't import. Yes. Thank you, Dan.
AJ_O’NEAL: Like a red-black thing where...Red modules can import other red modules and black modules, but black modules cannot import red modules.
GIL_TAYAR: Exactly. And it's basically, and they define names for those red and black. So in ES6, you have script and module, okay? Script cannot import a module and it goes, once you're in module land, you're in module land forever. And once you're in script land, you're in script land basically forever, unless you do an awaken.
AJ_O’NEAL: But from module land you can still go to script land. You can import script from a module.
GIL_TAYAR: Not that I know. Not in the browser. In Node.js, yes. Not in the browser.
AJ_O’NEAL: Okay, so in the browser, red modules only support red modules, black modules only support black modules, and there is no intermingle.
GIL_TAYAR: Yes. And in Node.js, common.js is script and es-modules are modules.
DAN_SHAPPIR: Okay, again, to interject a quick point about no mingle. Yes, they cannot mingle, but they can obviously access each other because they both see the global browser namespace. So obviously if, if both of them put a function on window, for example, they can definitely call each other. They cannot import each other. That's the, that's the important point.
AJ_O’NEAL: So I couldn't from a Java module. No, wait, that's not right. But if we're replacing JavaScript with Java module, no? Okay, anyway. So if I were to do one of those documents, like window.document.body.addEvent or no, addElement script, blah, blah, blah, you know, that little trick you do sometimes to load a script in the background the same way that JSONP used to work, that type of trick, no worky in a JavaScript module? Sure.
DAN_SHAPPIR: That would work because both of them see both of them see the DOM. It's not that they each have any restriction with regard to access to the DOM or DOM functionality. Like I said, they even can call each other's functions because you can just put the function on the window object or whatever. What you cannot do is you cannot use ES6 import or ESM import from within a regular JavaScript code that doesn't have type equals module or was originally imported from something recursively from something that had a type of module. Likewise, from within type of module, you don't do a common JS sort of a thing.
AIMEE_KNIGHT: I'll give like a practical example maybe because we were talking about this before the call. So I'm trying to get a really old app over to at least using some modules so that I can test it more, so I can use it to test more easily. And right now, everything is just in script tags and the HTML. So what I've done is just to try to do this piecemeal is I created a couple just regular JavaScript modules like we're used to, but then what I had to do so that I could do this iteratively and not just do it all in one big fell swoop is I'm having to import some of those modules in a script tag in the HTML. And of course I had to, so that script tag, that's actually like, there's JavaScript within that script tag, and that JavaScript within the script tag, hopefully this is making some sense for people if they can like imagine this in their heads. There's actually JavaScript in that script tag inside the HTML, and that JavaScript is, it was previously just using a script tag to import the file, but I've changed that file to export whatever, I'm doing a terrible job of explaining this. I had to update the script tag from the type JavaScript to type module. But then once I did that, I'm able to import the helper module that JavaScript in my HTML is relying on the functions for. God, that was a horrible... I feel like I did a terrible job explaining that.
GIL_TAYAR: That's beautiful because you're basically using ESM the way it was meant to be as like a replacement for script. So instead of having a lot of script sources, you have one script which imports a lot of modules. This is perfect.
STEVE_EDWARDS: It made sense to me, Amy. I got it.
AIMEE_KNIGHT: Okay, and I'm trying to like, I mean, what I'm trying to do is to try to minimize, because this application really doesn't need a lot of JavaScript, but because I like testing so much, I wanna be able to test it. I don't wanna like overcomplicate things by introducing like a Bundler and all that stuff. I just wanna use like whatever I can get that comes native in browsers, which we don't have to support IE, so I was able to do this.
GIL_TAYAR: Perfect. The nice thing is that, well, if the code uses DOM, then it won't work obviously, but you can run that code today in Node.js and the import-export will work. And if you use a library like JS DOM to simulate the DOM, then the same code will work both in Node.js and in the browser. That's, for me, that's beautiful.
AIMEE_KNIGHT: One other thing I want to throw in super quickly. So, I mean, one of the issues that, you know, there's not a lot of, there's really nobody else on the team who knows JavaScript, but, you know, they were having to like fight issues of like the order of their script tags and by doing this, that's no longer a problem. So there's some benefits there as well.
GIL_TAYAR: Yep. Okay. So, uh, we were in browser land, right? And browsers got what Amy was using, which is basic module support for ES6, including a way to import and everything. But the interesting thing is, if we go back to Node.js and require, when you do a require like.slash.poo,.slash.amodule, it doesn't look for a module. It looks for a module.js and a module.json. And when you do a require loadash, then it doesn't look for the file load-. It looks for node module slash load- slash package.json and slash index.js. It has this whole algorithm where it looks for files based on your import string, on the string you pass to require. So if it's a relative file, it will look relatively. And if it's not a relative file, if it's a bare specifier, like load-dash, that's called a bare specifier, then it looks upwards in the tree for node modules, for a node modules folder with a load-dash directory.
DAN_SHAPPIR: So basically what you're saying is that even though they look like paths. What they actually are, are let's call them some sort of indices into some sort of, they're used by some sort of an algorithm that translates them into a path, but they aren't really paths in and of themselves.
GIL_TAYAR: Exactly. And whenever you do or require something, you're basically doing about five to ten file operations just to get at that file. Now, it works! in a Node.js environment because files are cheap. But in browser land, you can't do that. You can't look for a file and then look for another file because each one of those is HTTP and HTTP is not cheap. Definitely not back in the days of HTTP one, but even in HTTP two, those things are not cheap. So browsers said, okay, we're not gonna do any lookup. We're not gonna support bare specifiers at all. So you can't import from low dash. And when you're specifying a relative path, you can't do. slash foo. You have to say. slash foo.js because we're going to do an HTTP GET and get that foo.js. And we're not going to look for foo and foo.js and foo.json, whatever. Just give us the path. So browser land, and this is important for... We'll see a node. For browser land, you have to specify the whole path, including the extension. That is incredibly important. And this is why because browsers can't do HTTP like they do fast.
AJ_O’NEAL: And I'd also argue that it is best to do it that way in Node as well, because if you haven't hit an ambiguous case where it works wrong, you will eventually. And I mean, I know a lot of people are like, well, I'd rather save 10,000 keystrokes over the next five years. I'd rather write the extra 10,000 keystrokes on the.js and the.json and always be completely clear what happened with the module loading.
GIL_TAYAR: Totally agree. And if you remember back then, and when we talked about Cold Start, a lot of the time in Cold Start is wasted on looking for all those files in the node modules and in whatever.
AJ_O’NEAL: Well, not really, because you're usually gonna get it on the first hit and even on a block device, even if it's not SSD. What do you mean? No, you're not going to get it.
GIL_TAYAR: When I was at Wix, but by the way, Dan Shappir knows me at Wix. We worked on this project called Wix code and then now it's called wait for it. Wix Corvid. Yes, I know that's, uh,
DAN_SHAPPIR: Corvid, not COVID.
GIL_TAYAR: Yeah.
DAN_SHAPPIR: Important to make that distinction. Marketing is going to kill me.
CHARLES MAX_WOOD: Unfortunate at all.
DAN_SHAPPIR: Marketing is going to kill me when they hear me say that, but please go on.
GIL_TAYAR: Cold start was. Cold start was a big issue because we're basic Wix Corvid or Wix code back in the days what is the serverless solution basically. And cold start was a big thing. And what one of our developers would did was whenever we do a require, our whole thing doesn't require, what they do is they cash all the decisions by Node.js. And the next time that module runs, the next time the application runs we just patch the require to just know where the files are. That saved about 30% of the cold start time, which is a lot. Definitely not like it's not 50%, but a big part of the cold start time in a Node.js application is spent on all those little searches. But definitely, you and the Node.js modules team are on the same page in terms of let's be more specific instead of what the file path is. Okay, so that's what happened in browser. On the node land, something interesting happened. Node started getting more and more NPM modules, NPM started to grow. And the browser, the front end people remember that's before bundling. They looked at the NPM registry and said, oh, there's a really nice things we can use there. But they couldn't use it because they were using AMD and require JS, which are like these old module systems and not common JS. So then this guy came along and wrote Browzerify. That was the first bundler. What Browzerify did is it took an npm package with all those requires transpiled all those requires and bundled them into one big file just like Webpack does. That was before Webpack. So now you can use those npm packages that were meant for NodeJS you can use them in browser land. You can use CommonJS in browser land. And Browserify basically did a revolution in front-end land. We don't remember that today because everybody's using bundlers.
DAN_SHAPPIR: Yeah, that's when we went from having kilobytes of JavaScript download to megabytes of JavaScript download. Thank you Browserify and Webpack.
AJ_O’NEAL: Well, a lot of that is because the way that it bundled it was not very smart. So say for example, you did something that used buffer, which is everything in Node. Rather than using an implementation that would take, you know, like four or five lines of code, which you can do using A to B and B to A and escape and URI and code and...Like you can, you can get this to happen using things that are natively available in the browser, but that's not the way that it got packed and transpiled. I don't know why they made the choices that they did, but they kind of, instead of using browser functions, they kind of use like pure JavaScript to do some of these implementations. So they, they re-implemented the wheel and then some on a lot of stuff and buffer is one of those modules where it was a little overzealous, I believe, and that it kind of like even copied the bugs of buffer, which are not, I mean, from my perspective, if you're trying to get something to run in the browser, it would be acceptable that it fail if you're using like an obscure feature that is deprecated or whatever. But like everything about buffer. Every little nuance detail about buffer was replicated in like this pure JavaScript without using any of the native browser things that had been there since like, you know 1998 and
DAN_SHAPPIR: I but I think you're going down down I You're right, but I think you're going down the wrong rabbit hole here I think that the main issue is the developers tend to just use the path of least resistance whenever they're developing anything. And if I know that Moment.js happens to do what I need to do in terms of working with date and time, and it's so easy now thanks to first Browserify and now Webpack to bundle in Moment.js, then I will bundle in Moment.js. And because I'm not a specialist about how to configure NPM, I end up bundling five different versions of Moment.js into my application and before I know it my download size is 600 kilobytes. Even though my own code...
AJ_O’NEAL: That's a pretty lean sight by today's standards.
DAN_SHAPPIR: Yeah, even though my own code is only, I don't know, 20 kilobytes of minified code because, you know, I've never actually wrote more than that. But again, I think we're kind of straying away from the main topic here.
GIL_TAYAR: Right, because browser-ified tried to do two things emulate node and bundle. Webpack came and basically killed Browserify because it tried to do only one thing, bundle. It didn't care about node. Which makes sense historically because back in the days when Browserify started, all the NPM packages were node. Now in the Webpack days and roll up in parcel, like I'm guessing that 90% are front end in NPM. So I think what you're saying is totally true, but the reason that happened was historic. Browserify is basically not really used anymore, so it's a more point. But the interesting thing is that the front-end people started using CommonJS. And not only that, they used the CommonJS node resolution module. So they didn't write foo.js like the browsers. They just wrote foo and load- and use bare specifiers, because that is how NodeJS works. This is how CommonJS works. And then came the webpacks of the world and the babels of the world, and they didn't want to use require they wanted to use ES module syntax. So the web packs of the world and the webels of the world just said, hey, why not ditch require and just use the import-export syntax and still bundle. And this is what we have in the browser land, in front end land today, not in browser land. Front-end land uses the import syntax, but uses the resolution module of common JS. So when you're doing import load dash, the bundler goes and looks at node modules and all that package JSON and looks at everything to find it just like Node.js would. And when you're writing import from foo,.slash foo, it goes and looks for.slash foo.js. So we have frontend land using his modules, but with node resolution, with finding modules the way node. And we have the browser land not doing that. So Frontend land is using bundlers but cannot migrate to browser land and they can only bundle because their module resolution is different. So this is like ironical in that basically the frontend people implemented ES modules a bit too quickly in some ways. They did it before browsers did and when browsers did it like the right way. There's no going back. Well, not yet. We'll talk about how this will happen. But we basically now have three module systems, Common.js and Node.js, transpiled ES6 modules that work a bit like Common.js, and browser-landed ES modules, which just use relative paths.
Right now, many of you are stuck at home, but eventually everything will open up. All right, listeners, let me ask you a question. Wouldn't you rather work from home instead of a cubicle or a noisy open office? Need to negotiate with your team and convince them to let you do it? Well, I have the perfect book for you. It's by my friend, Will Gantt. It's called Remote Work, Get a Job or Make a Career Working from Home. He's a proven author, software developer and professional consultant with over 20 years of experience in a variety of roles. And now he wants to share his trade secrets with you. In remote work, you'll discover how to save more time, money, and mental energy each year. How working remotely can give you and your company a competitive edge in the market managing your physical health, mental health, career goals and relationships. You'll also get the ultimate list of tools and resources to help you transition into working remotely and much more. This is the perfect time to test out if working remotely is for you. And if you enjoy the freedom of working anywhere you want, then you can pick up your copy of remote work on Amazon today, or click the link below in the show description.
GIL_TAYAR: Now comes Node.js native modules and introduces a fourth option. Remember we have browser language that uses relative paths, but you have to specify the whole path. We have require, we have common.js which uses require and where you don't have to specify the whole path and we have transpiled ES6 which is used front end lines in bundlers which uses the syntax of ES6 but not the module resolution so you don't have to specify the whole path. Now come node-esm. One of the prime directives of the designers of node-es modules, when I'm talking node-es modules. They wanted to be as browser compatible as they could. And that is an important thing. What did that mean? They mean that just like CJ said, AJ, sorry, they did not accept Foo. They had to have Foo.js,.slash Foo.js. So in ESM land, in Node, you have to, when you're importing, you have to import.slash Foo.js. and not. slash full. So that's one big difference. And a prime directive, that is one of the main reasons Node.SM is so different from CommonJS in Node.js. So that's like a bit of a history of the order of things and why things happen. I have a lot more to talk about on the Node.SM side, but if you have any questions up to now.
DAN_SHAPPIR: Aside from your describing some things that I also discovered for myself, some of them the hard way, other than that, it's pretty clear to me.
GIL_TAYAR: Great. So basically, that was Node's prime directive. Let us be as browser compatible as possible. But they had a problem. They had a big problem because browsers, remember we have script and module, browsers have that distinction between script and module. So we talked about the fact that a script can't do an import, but there are other differences between script and module. For example, a module is always strict mode. It can't be sloppy. If you know the distinction between strict mode and sloppy mode in JavaScript.
DAN_SHAPPIR: You're referring to use strict, right?
GIL_TAYAR: Yes, use strict, exactly. So a module is always use strict, and I think you can't go back. So you can't do use sloppy.
DAN_SHAPPIR: You cannot, there is no way to to undo a use strict within a block.
GIL_TAYAR: Moreover, this means that for example, global this, if you use this in a global scope, it's undefined in a module. Whereas if you use this in a global scope in a script, it points to something in the window object and in browsers and in Node.js, I don't even remember what.
AJ_O’NEAL: So do modules actually define the global object or is that still something where you have to do like, window.function, eval, whatever, whatever.
GIL_TAYAR: ES2020, which is implemented in, I think, all the browsers and in Node.js, defined this horribly named thing called global this. Global this is the new global window. It works both in Node and in all the browsers.
DAN_SHAPPIR: It's not a global window, it's the global object or the global context.
GIL_TAYAR: Yeah, but it's basically just like window in the browser and global in the node.
DAN_SHAPPIR: In the browser it's window, exactly.
AJ_O’NEAL: So now we've got three globals in node. We've got global, uppercase global, and global this. And I bet they all have different properties on them.
GIL_TAYAR: No, I think they're the same. I didn't know about uppercase global. Wow, that's new, interesting. But yeah, just like in browser land, you have window and you have global this. But they wanted to use global. They really wanted to use nodes global, but it didn't work for other reasons.
AJ_O’NEAL: So they invented this horribly named global this because like one out of every thousand websites had a variable called global, but only one out of 10 million websites had a variable called global this
GIL_TAYAR: exactly something like that. Probably. So the browser's know understand the description between script and module and the ESM designers and no just wanted the same thing because they wanted to be as browser compatible as possible in node land. So they defined CJ as a script, which it is. So it's sloppy by default. And they decided that ES modules will be, well, modules. But that's a problem because now when node has to parse a file, it needs to know whether it's parsing a module or a script because the parse is different between the two. Now comes the controversial part, the node-y assembly.
DAN_SHAPPIR: Just to give an example of where the parse is different for people who don't remember, the width keyword is supported in script and is not supported in modules. So that's just an additional example. So yes, parsing needs to be different between the two.
GIL_TAYAR: Wow, I didn't know that about width, but yes.
AJ_O’NEAL: And there's certain errors that in sloppy mode. It'll do it PHP style where it just encounters the error, says nothing and keeps going to the next line. And in strict mode, those errors become hard errors that actually get thrown. And I think that may originate from like ES1 or so back before try catch was defined. I'm not sure about that, don't quote me on it.
GIL_TAYAR: We'll send Brendan and Ike along your way. So they needed a way to figure out when importing or requiring a file, whether it's like module or script. And they decided on a weird way. I think it's the right way, but it is very controversial. They decided that it's defined by extension. So JS is script, it's CJS, and MJS, what I call Michael Jackson script, or module JavaScript is ESM. That is the birth of the MJS extension. And people got crazy about this on the internet. I mean, it was a very controversial decision. People...
JSJ 434: Understanding and Using ES Modules in Node with Gil Tayar
0:00
Playback Speed: