This article is part of a web development series from Microsoft. Thank you for supporting the partners who make SitePoint possible.
ES6 brings the biggest changes to JavaScript in a long time, including several new features for managing large and complex codebases. These features, primarily the import and export keywords, are collectively known as modules.
If you’re new to JavaScript, especially if you come from another language that already had built-in support for modularity (variously named modules, packages, or units), the design of ES6 modules may look strange. Much of the design emerged from solutions the JavaScript community devised over the years to make up for that lack of built-in support.
We’ll look at which challenges the JavaScript community overcame with each solution, and which remained unsolved. Finally, we’ll see how those solutions influenced ES6 module design, and how ES6 modules position themselves with an eye towards the future.
First the <script> Tag, Then Controversy
At first, HTML limited itself to text oriented elements, which got processed in a very static manner. Mosaic, one of the most popular of the early browsers, wouldn’t display anything until all HTML had finished downloading. On an early ‘90s dial up connection, this could leave a user staring at a blank screen for literally minutes.
Netscape Navigator exploded in popularity almost as soon as it appeared in the mid to late ‘90s. Like a lot of current disruptive innovators, Netscape pushed boundaries with changes that weren’t universally liked (read the fascinating email thread where Marc Andreesen pretty much implements the image tag before Tim Berners-Lee can finish saying why he doesn’t like it). One of Navigator’s many innovations was rendering HTML as it downloaded, allowing users to begin reading a page as soon as possible, signaling the end for Mosaic in the process.
In a famous 10 day period in 1995, Brendan Eich created JavaScript for Netscape. They didn’t originate the idea of dynamically scripting a web page - ViolaWWW preceded them by 5 years - but much like Isaac Singer’s sewing machine, their popularity made them synonymous with the concept.
The implementation of the <script> tag went back to blocking HTML download and rendering. The limited communication resources commonly available at the time couldn’t handle fetching two data sources simultaneously, so when the browser saw <script> in the markup, it would pause HTML execution and switch to handling JS. In addition, any JS actions that affected HTML rendering, done via the browser-supplied API called DOM, placed a computational strain on even that day’s cutting edge Pentium CPUs. So when the JavaScript had finished downloading, it would parse and execute, and only after that pick up processing HTML where it had left off.
At first, very few coders did any substantial JS work. Even the name suggested that JavaScript was a lesser citizen compared to its server-side relatives like Java and ASP. Most JavaScript around the turn of the century limited itself to client side conditions the server couldn’t affect - often simple form activities like putting focus into the first field, or validating form input prior to submitting. The most common meaning of AJAX still referred to the caustic household cleaner, and almost all nontrivial actions required the full HTTP round trip to the server and back, so almost all web developers were backenders who looked down on the “toy” language.
Did you catch the gotcha in the last paragraph? Validating one form input might be simple, but validating multiple inputs on multiple forms gets complicated - and sure enough, so did JS codebases. Just as fast as it became apparent that client side scripting had undeniable usability benefits, so too the problems with vanilla script tags emerged: unpredictability with notification of DOM readiness; variable collisions in file concatenation; dependency management; you name it.
JS developers had a very easy time finding jobs, and a very hard time enjoying them. When jQuery appeared in 2006, developers adopted it warmly. Today, 65 to 70% of the top 10 million websites have jQuery installed. But it never intended and could offer little to solve the architectural issues: the “toy” language had made it to the big time, and needed big time skills.
What Exactly Did We Need?
Fortunately, other languages had already hit this complexity barrier, and found a solution: modular programming. Modules encouraged lots of best practices:
- Separation: Code needs to be separated into smaller chunks in order to be understandable. Best practices recommend these chunks should take the form of files.
- Composability: You want code in one file but reused in many others. This promotes flexibility in a codebase.
- Dependency management: 65% of sites might have jQuery installed, but what if your product is a site add-on, and needs a specific version? You want to reuse the installed version if it’s suitable, and load it if it’s not.
- Namespace management: Similar to dependency management - can you move the location of a file without rewriting core code?
- Consistent implementation: Everybody should not come up with their own solution to the same problems.
Early solutions
Each solution to these problems which JavaScript developers came up with had an influence on the structure of ES6 modules. We’ll review the major milestones in their evolution, and what the community learned at each step, finally showing the results in the form of today’s ES6 modules.
- Object Literal pattern
- IIFE/Revealing Module pattern
- CommonJS
- AMD
- UMD
Object Literal pattern
JavaScript already has a structure built in for organization: the object. The object literal syntax got used as an early pattern to organize code:
<!DOCTYPE html>
<html>
<head>
<script src="person.js"></script>
<script src="author.js"></script>
</head>
<body>
<script>
person.author.doJob('ES6 module history');
</script>
</body>
<script>
// shared scope means other code can inadvertently destroy ours
var person ='all gone!';
</script>
</html>
What it offered
This approach’s primary benefit was ease in understanding and implementation. Many other aspects were not at all easy.
What held it back
It relied on a variable in the global scope (person) as its root; if some other JS on the page declared a variable by the same name, your code would disappear without a trace! In addition, there’s no reusability - if we wanted to support a monkey banging on a typewriter, we’d have to duplicate the author.js file:
Last, the order that files load in is critical. All the versions of author will error if person (or monkey) doesn’t exist first.
IIFE/Revealing Module pattern
An IIFE (pronounced “iffy” according to the term’s coiner, Ben Alman) is an Immediately Invoked Function Expression. The function expression is the function keyword and body wrapped in the first set of parentheses. The second set of parens invokes the function, passing it whatever is inside as parameters to the function’s arguments. By returning an object from the function expression, we get the revealing module pattern:
Continue reading %Understanding ES6 Modules via Their History%
by Elias Carlston via SitePoint