(energetic upbeat music) - Hi, WebDirections, my name is Mejin Leechor and I'm a developer in Los Angeles, where I currently work for Pivotal Labs.
We just became part of VMWare this year.
The web is a pretty different place in 2020 than it was back in the early years.
Before we go further, I want to clarify this term native modules.
When I say native modules, I'm not talking about some of the more popular module formats out there that you might be using.
So I'm not talking about CommonJS modules and node. I'm also not talking about AMD modules which you might know, from having used RequireJS. When I say native modules, I'm talking about modules that are actually part of the language and written into the ECMAScript specification.
They might even be newer than you think.
So we didn't actually have a working module system at that time, it took some additional work to bring ES module support to the browser and to node. So by mid 2018, we had native module support in all major browsers and no chip to support for modules initially behind an experimental modules flag, so you could play with it, but that flag was actually removed in November, 2019. Please note, it's actually still not considered a stable feature.
So, what's all this fuss about modules? What's so important about them that we couldn't live without them for years? Well, let's get into it.
And we'll start by talking about modularity. I like to think of modularity as providing structure through boundaries, if you don't have boundaries, you don't have structure, you have a morphous systems and you have source code files that are in the tens of thousands of lines long.
And maybe you have that many global variables too. Another way that I like to think about modularity is through the analogy of building blocks.
So you compose programmes of smaller units of data and functionality that we can mix, match and recombine. And these smaller units, which we call modules, hang together through a couple well-defined relationships. So these modules can expose data in functionality via their interfaces, and they can also have dependencies on other modules so that they can use their data and functionality.
To facilitate these module relationships, modules need to have their own private scopes. Now name spacing can get you part of the way to modularity, but to truly have independent modules, you need independent scopes.
And a couple of the benefits of modularity. Well, there are so many benefits to modularity, but two in particular stand out to me.
The first is our ability to reason independently about the component parts of a system, which lightens our cognitive load, we don't have to think about the whole system we can think about one piece at a time.
That also encourages loose coupling and helps us write more maintainable code.
The other big benefit of modularity that I see is the ability to reuse code, whether that's code that you wrote or someone else wrote. Think whenever you use a package from NPM, The bottom line is that modularity is for us, it's for human beings, we're the ones who are gonna suffer when our systems grow beyond our ability to manage them. We're also the ones who have to grok and maintain our own code.
And on the other hand, we're the ones who benefit when our systems are clear and organised, and when we have a community to share our solutions with.
Modularity is what facilitates all of this. One final note, I wanna make a distinction between modules as a programming language construct, and modularity, the more abstract concept.
I personally find that it's helpful to understand where we came from to understand where we are with modules in 2020.
We'll hop through history in roughly five to 10 year increments, based on this little timeline I put together while playing module historian. Quick disclaimer, all the dates are pretty rough, very approximate, but this is just meant to be a teaching tool.
And when we look at the history books, it turns out there's a really good reason.
We didn't have modules back then, which goes back to the original plans that Netscape had for the language.
"To write large code, you don't just want this little snippet language that I made easy for beginners to start buying by the yard.
You want strong APIs ways of saying, this is my module, and this is your module, and you can throw your code over to me, and I can use it safely." I really appreciate that, Brendan Eich recognised that the absence of modules was an inhibitor in maturing the language, and that we would actually need them if we wanted to write large code. Post 2000 we move into the do it yourself module era. And to give you a little bit of context, this was a time of transformation for the web. Gmail came out in 2004, and it was really widely regarded as the quintessential web 2.0 app.
And it really raised the bar about what was possible. It could do so much, and with so few page reloads, the web was becoming a more interactive place, thanks in large part to technologies like Ajax, web apps, not websites were the new Vogue, it's against this backdrop, that we started to see the shortcomings of living in a world without modules. And one of the biggest problems was global variables, which made scripts more prone to variable naming collisions and unexpected state changes.
A large number is bad because the chances of bad interactions with other programmes goes up." Now, I would argue that the chance of bad interactions within a programme goes up too.
And we're starting to see strategies emerge in this period to combat the global variables problem.
The most classic of these patterns is the module pattern, which you might actually be familiar with.
And the essence of the module pattern is to pair a closure within immediately invoked function expression with the effect of shielding private variables, but still allowing indirect access to them through publicly exposed methods.
So we can take a look at an example here.
Now we've got a do it yourself module, and in it, we have defined inside this IIFE function scope, we have defined a private variable, a counter which we initially set to the value of zero. And we've got a couple exposed functions that we're gonna return out of this IIFE, including an increment function that increments counter and a print function that's gonna let you see what the current value of that counter is.
Now, if we want to actually use that module, we can, we can call the print method, and we can see that the counter is initially zero, initially has a value of zero, and then after we increment it, it has a value of one, but if we actually tried to access the counter itself, and this is the most important part, we can't access it. We've achieved data privacy because we've exited out of the IIFE scope, and the variables that were scoped to that function are now gone.
And that's the ingenuity of the module pattern, the use of function scope to create these makeshift module boundaries.
And that's a trick we'll see throughout module history. CommonJS uses a similar mechanism and it's required method by creating a wrapper function that contains the module scope.
Now, there were other problems that started to rear their heads during this period, especially with dependency management.
And a couple others that I didn't include here. Now, just doesn't note this CommonJS group. isn't the same as the CommonJS module specification in node, but when node came out in May, 2009, it delivered on one of those hopes for the CommonJS effort, which was a module specification.
And we now call that module spec, CommonJS, not confusing at all.
And that's what spawned asynchronous module definition or AMD unlike CommonJS and it's synchronous thread blocking require method, AMD provided for asynchronous loading of modules and their dependencies.
Nobody agrees on a solution to this problem." And that's how these became the years of specification hell. It was a dumpster fire.
There's one other major development from the specification era that I'd like to highlight, and that's bundling.
And bundling is when we stitch together modules, and their dependencies into one or more files for browser consumption.
A couple of players of node include Browserify, which came out in 2011, bringing CommonJS modules to the browser.
So you could actually use NPM packages in your browser code, pretty cool.
But I would be remiss if I didn't mention Webpack, which brought static assets into the mix.
And there are a couple other libraries out there, actually, a lot of other libraries out there, but I'll just highlight a couple more, Rollup and Parcel, which is a newer one.
Please make a quick mental bookmark here because bundling ain't over yet.
So we finally arrived in the era of standardisation and it couldn't have come soon enough.
We finally converged on a single module system. Let's see what history has brought us.
Now, since the ES6 pack came out in 2015, we've had this lovely, concise module syntax. We have default exports, you get one of those profile. We have named exports, and you can have as many as your heart desires. On the import side, we can import all the exports from a module in one go, and assign them to a variable. And then we can access the exports by name with dot notation.
We can also import one or more named exports, from a module by using what looks like destructuring syntax, it's not destructuring syntax, it's just named imports.
So those curly braces are common to both destructuring and named imports.
And if we're importing a default export, we can just assign it to a variable name of our choosing. Fun fact, the default export of a module is actually just a named export with the name default. And you can import it by name, if you like, just like in that last line where we're importing default and then aliasing it to suite export.
One important note about ES modules.
What you're exporting or importing is a read-only live binding.
So let's say you import a variable called 'fu', and it has the value two, the exporting module could at some point update fu's value to be seven, and your fu value would also you're importing modules, from fu value would also update to seven.
But the importing module, because this is a read-only binding, can't actually change fu's value directly.
Not unless it had a mutater or something that was exposed. This is not at all like CommonJS.
So please pay attention to that if you aren't familiar with ES modules and are coming from CommonJS, this is one reason why it was actually really difficult to implement ES modules in node.
ES modules also are loaded and connected to each other prior to execution.
And again, this is different from CommonJS modules where the required method is synchronous and loading and evaluation basically happen together in sequence. And ES modules are better adapted to the browser for this reason, because you're not actually blocking the thread of execution on import.
Dynamic imports are supported in all browsers, and that support followed really quickly on the heels of having imports in the static form. And we can invoke the import keyword as a function, give a module specifier, which is basically a path to a module and you get this nice promise-based API to work with, by the way, this is real code from when I was playing around with dynamic imports. And if you're wondering what it does, it does this. You could also use async await given that you're just working with promises here.
And this can be useful if you want to do lazy loading or conditional module loading, or if you need to compute the module specifier on the fly. I mentioned earlier that there's now native support for modules in the browser, and this is how you can actually use that yourself if you'd like.
So you can specify a type attribute on a script tag as a module, and as module, and that tells the browser to parse this file as a module and not as a regular script.
And if you provide a script tag, the nomodule attribute that lets you fall back...
that basically lets you have a fallback script for older browsers that don't yet support ES modules. So that's pretty nifty and all, but are we there yet? Have we arrived? After all these years are we tasting the promise of a native module system with code sharing, and loose coupling and clean perfect modularity? Well, unfortunately not quite, it's still playing out. We do have native modules, but right now they coexist alongside CommonJS primarily and other prior module specs. And we're still working through some of the interoperability issues between module formats. And this in a nutshell is how I describe where we are right now.
You're probably still bundling.
You're using module syntax when you develop, but Webpack or Parcel or some other bundler's still in the picture.
So why is that? Why haven't we all jumped ship already if ES modules are the new hotness? Well, it all comes down to one thing, and that's performance.
A year or two ago, most of the conventional wisdom was to stick with bundling for your production apps, even in a world where native modules and native module support in the browser is real. Now it didn't seem like there was a way for native modules to outperform bundling when the browser has to make a round trip for every module that it needs in a script.
And the Chrome team released a study back in 2018 that did a bottleneck analysis, comparing ES module loading performance to bundled scripts. Their recommendation was that you keep on bundling even with HTTP/2 server push isn't quite there yet. But the jury is still out and there might be optimizations that we haven't fully considered or explored.
I'd like to leave you with a takeaway that can help you think about your options in this present moment in module history, where we're sort of, but not all the way there. And this is what I'm calling the ES Module Maturity Model, inspired by the Richardson Maturity Model.
And if you've seen that or if you haven't, it describes how closely a rest service follows restful principles.
And this ES module maturity module is following a similar sort of look and format, where it's giving you a graded scale.
And the idea is that you have different levels and you can opt in to using ES modules, and reaping their benefits to the extent that you would like.
So at level zero, we don't use native modules at all, but at level one, that's where we actually start to get it, get our hands dirty in this new ES module syntax. We use the new module syntax, but we're still bundling. And that's probably where most of us are right now. At level two, now this is where we're getting into our imagination more, and this is much more aspirational, but I got an idea from Snowpack, which is a library that can serve your application unbundled during development, from the demos I've seen it's actually pretty snappy.
So you actually have unbundled development and ES modules there, but you still bundle for production. And then finally, level three, that's where we're fully bought in using native optimised modules and production. Aspirational? Yeah.
Possible? Well, if not today, eventually.
Well, even though we're not there yet, we've certainly come a long way, and I hope we get there someday.
Judging from our past, I think we will.
That's all I've got for you today.
Thank you so much for listening.
(high energy upbeat music)