Reading tip: Many of the links in this article require scientific Internet access

For JavaScript novices, seeing “CommonJS vs. AMD”, “Requirejs vs. Seajs”, “Webpack vs. Browserify”, etc., can be overwhelming.

Especially in today’s days when most browsers have implemented ES6 modular specifications, our newly developed projects are basically ES6 with Webpack. AMD, CMD, UMD, Requirejs and Seajs are all in the past, and many students have not used them.

But modularity is part of the JavaScript development architecture, and it’s important to understand its history, at least so you don’t lose the ability to talk to other developers, like your interviewer, about it.

Foreword

Since the release of JavaScript in 1995, browsers have loaded JS modules using simple script tags. As early as 1996, there were many server-side JavaScript implementations, such as Nodejs, released in 2009. Both browser-side and server-side JavaScript, JavaScript itself had no modular architecture until the ES6 specification was proposed.

So what is a module?

Good writers divide their books into chapters. Good programmers divide their programs into modules. Good modules are highly independent and have specific functions that can be modified, removed or added as needed without breaking the entire system.

What are the benefits of modularity?

These are the main benefits of modularity:

  • The namespace

In JavaScript, the interfaces of every JS file are exposed to the global scope, accessible to everyone, and are prone to namespace contamination. Modularity can create private Spaces for variables to avoid namespace contamination.

  • reusability

Have you ever copied previously written code into a new project at some point? If you modularize this code, you can use it over and over again, and if you need to change it, you only need to change the module, not every piece of code in the project.

  • maintainability

Modules should be independent, and a well-designed module should minimize its dependence on parts of the code base so that it can be cut and modified independently. When modules are separated from other code fragments, it is much easier to update individual modules, and you can do versioning with each change.

Traditional modular development

When multiple JS files have the same name for variables and methods, resulting in naming conflicts, the Java namespace approach can be used.

/ / code from: https://github.com/seajs/seajs/issues/547
var org = {};
org.CoolSite = {};
org.CoolSite.Utils = {};

org.CoolSite.Utils.each = function (arr) {
  // Implementation code
};

org.CoolSite.Utils.log = function (str) {
  // Implementation code
};
Copy the code

Similar to the way classes are used in other programming languages such as Java or Python, public and private methods and variables can be stored in a single object. The problem of exposing variables to the global scope is solved by writing the methods that will be exposed to the global scope outside the closure and wrapping private variables and methods inside the closure scope.

// The global agent is accessible
var global = 'Hello World';
(function() {
  // Only accessible within closures
   var a = 2; }) ()Copy the code

While this approach has its advantages, it also has its disadvantages.

  • The module defined by the Immediately executed factory Function (IIFE: Immediately Invoked Function Expression).
  • References to dependencies are done by global variable names loaded through HTML script tags.
  • Dependencies are very weak: developers need to know the correct order of dependencies. For example, Backbone files cannot precede the jQuery tag.
  • Additional tools are required to replace a set of script tags with a single tag to optimize deployment.

This is difficult to manage on large projects, especially when scripts have many dependencies in an overlapping and nested manner. Handwritten script markup is not scalable, and it does not have the ability to load scripts on demand.

Is there a way to request a dependent module within a module instead of globally requesting a dependent module? CommonJS, AMD, CMD, UMD, etc. These modular specifications tell developers:

  • How to import module dependencies (Imports)
  • How to define modules (code)
  • How to export module interfaces (Exports)

Since the idea of modular development was put forward, whether it is browser side or server side Javascript development, developers have been exploring the modular specification and implementation to meet the actual needs, they have to solve the same problem, namely modular development and module dependence, but they are initiated by different reasons.

CommonJS

Mozilla engineer Kevin Dangoor launched the ServerJS project in January 2009 to standardize the modularity of JavaScript used on the server side, And the standardization of Filesystem API, I/O Streams, Socket IO and other server-side development areas.

He mentions what server-side javascript needs in What-server-side-javascript-needs:

a cross-interpreter standard library

a handful of standard interfaces

a standard way to include other modules

a way to package up code for deployment and distribution and further to install packages

And want these to work on as many operating systems and interpreters as possible, including three major operating systems (Windows, Mac, Linux) and four major interpreters (SpiderMonkey, Rhino, V8, JavaScriptCore), Then there is the “browser “(itself a unique environment).

In August 2009, ServerJS was renamed CommonJS to demonstrate that the API it defines can be widely used. A lot of subsequent development jokes have suggested that the CommonJS module format is browser-unfriendly (it doesn’t support asynchronous writing) and that the browser is a class II citizen, which is better suited to the ServerJS name.

I also feel like CommonJS has treated browser use as a second class citizen, which may have made more sense when it was ServerJS.  As it stands today, the CommonJS module format is unfriendly to the browser.

NodeJS

On May 31 of the same year, Ryan Dahl, an American programmer, implemented node.js (New Server-side JS Project: Node.js was introduced for the first time at the JSConf conference on 8 November 2009 (Ryan Dahl at JSConf EU 2009 Video).

Node.js, which uses the CommonJS specification directly to implement the module architecture, is so popular that most Web developers still refer to the Node.js module architecture as the CommonJS specification:

//math.js
exports.sum = function(. nums){
  return nums.reduce((result, num) = > result + num, 0)}//index.js
var math = require('math')
exports.result = math.sum(1.3);
Copy the code

In fact, the relationship between the two is not what we think of as the role of standard-setter and standard-enforcer. In May 2011, Ryan wrote a q&A post at the request of r/ Node moderators, saying that CommonJS was dead and not worth talking about, and that was 2009:

Consider CommonJS extinct – not worth thinking further about. That was a 2009 thing.

By March 2013, Brettz9 was asking the NodeJS community:

What is the reason for the indifference to CommonJS? I understand you are no longer looking to adhere to it.* Are all contributors abandoning it or just you?

Isaac Schlueter, founder of NPM, responded:

A few good things came out of CommonJS. The module system we have now is basically indistinguishable from the original “securable modules” proposal that Kris Kowal originally came up with. (Of course, we went pretty far off the reservation in v0.4, with the whole node_modules folder thing, and loading packages via their “main” field. Maybe we should remove those features, I’m sure that Node users would appreciate us being more spec-compliant!)

The CommonJS standard has become the documentation hub for niche Server Side JS solutions, and Node.js has won the competition for Server Side JS. As Ryan Dahl, founder of Node.js, put it:

“Forget CommonJS. It’s dead. We are server side JavaScript.”

Node.js is server-side JavaScript. More importantly, Isaac valued the voice of real users rather than the so-called standard-setters, and new standards proposed by the CommonJS working group were more disruptive (such as the so-called Package standard). By 2013, node.js Modules will be in their own right.

Module Loader

Back in 2009, web developers were worried about a bunch of

After CommonJS was introduced, some people wondered why CommmonJS only focuses on the server side. Kevin Dangoor mentioned in his blog that the special bold points are not only for the server side, but also for browser-side JS.

In agreement on the desire to have some standardization around the areas that you’ve bold-ed in your post. One nit though: there’s not really anything server-specific about this stuff.  It applies to browser-based JS usage, and even other JS usage, like folks integrating with Gnome, Cocoa, etc.

CommonJS is dedicated to the server-side ecosystem of JavaScript. Modules are loaded synchronously, and the syntax is very simple and friendly to server-side development. But this is unacceptable on the browser side, as it takes longer to read a module from the network than from disk, and as long as the script to load the module is running, it prevents the browser from running anything else until the module is loaded.

In the CommonJS forum, Kevin Dangoor leads a discussion about asynchronously loading CommonJS modules and solicits ideas for browser-side module loading. There are also a number of forum posts on how to asynchronously load Commonjs modules in browsers.

  • For those who propose a transport scheme, before running it on the browser, the module should be converted into codes that conform to transport specifications through conversion tools.
  • Some propose that XHR load module code text and execute it in the browser using eval or new Function.
  • Some proposed that we should directly improve CommonJS and launch a pure asynchronous module loading scheme;

James Burke, the author of the third solution, believes that the module format of CommonJS does not support asynchronous loading on the browser side, and CommonJS modules need to be loaded through other ways such as XHR, which is very unfriendly to web front-end developers. The authors argue that the best practice for browser-side development is to load only one module per page. Something like this:

<! -- loader.js defines LOADER_ENTRY_FUNCTION --><script src="loader.js"></script>
<script>LOADER_ENTRY_FUNCTION(["page1"]);</script>The page1 module might look like this: LOADER_ENTRY_FUNCTION("page1"["b"."c"].function(b, c) { //
        document.addEventListener("DOMContentLoaded".function() {
                //Do page setup in here, use b and c
        }, false); });Copy the code

In dev mode, each module can be loaded individually to provide the best debugging experience. You can then merge all page1 module dependencies and nested dependencies into it by compilation, or you can load the dependencies as a group at run time through loader.js.

RequireJS in AMD

James Burke wrote at length in CommonJS in the Browser in December 2009 about directly modifying the CommonJS module format for browser-side development. But CommonJS founder Kevin Dangoor didn’t agree with this solution, which led to RequireJS, which was created by a discussion post by James Burke:

  • New amd-implement list  11/5/25
  • Split off AMD? (was Re: [CommonJS] New amd-implement list)
  • Harmony module execution
  • AMD proposal change: define.amd
  • AMD vs Wrappings
  • Function.prototype.toString to discover function dependencies 10/9/16
  • Updated Transport proposals 10/3/31

James Burke wrote the AMD specification and implemented RequireJS, an AMD-compliant module loader, in 2010. I suggest you take a look at WHY AMD?

require.config({
    path: {
        module: './module',}});require(['module/module1.js'.'module/module2.js'].function(module1,module2){
    module1.printModule1FileName();
    module2.printModule2FileName();
});
Copy the code

SeaJS in CMD

Yubo thinks RequireJS is not perfect:

  • There is an objection to the execution time
    • Reqiurejs module is executed immediately after it is loaded. Seajs saves the factory function after the module is loaded, and executes the corresponding factory function of the module when it is executed to require to return the exported result of the module.
  • Module writing style is controversial
    • Amd-style exports that pass in parameters to dependent modules break the nearest declaration principle.

He thinks AMD’s popularity is largely due to the RequireJS authors, but what hits is not always good. He gave many suggestions to the RequireJS team, but none of them were adopted. In the CommonJS Group:

RequireJS is good but I found that its API is annoying me. I dived into reading topics in this group, and began to implement a module loader (SeaJS) from scratch some month ago. My fellow don’t like RequireJS too after using it in two projects.

RequireJS is great, but its API annoys me. After using RequireJS in two projects, my colleagues didn’t like it either. I read the topics discussed in this Group and started implementing a module loader (SeaJS) from scratch a few months ago.

RequireJS author James Burke seems a little annoyed by the fact that their debate about RequireJS and SeaJS is all about APIs for CommonJS Module Loader in this discussion. James Burke believes that the API RequireJS proposed by Yip is supported. Yip lists the problems of RequireJS in detail:

1. In RequireJS, require has multiple uses, which is easy for beginners to make mistakes and not easily understood by users.

  • require(‘a’)  — gets exports of module a
  • require([‘a’]) — fetch module a according to module name scheme
  • require([‘a.js’])  — fetch a.js directly relative to current page
  • require({… }) — set loader config

2. I don’t really care about lazy execution or early execution, but the RequireJS API can easily leave garbage in your code, for example:

define(['a'.'b'].function(a, b) {
  a.xx();
  b.yy();
});
Copy the code

Although he doesn’t care about lazy implementation or early implementation, in the history of modular front-end development, he also mentions the issue of timing

It started out fine, but over time, some of the module code would be maintained by different coders, and the code would look something like this:

define(['a'.'b'.'c'. .'z'].function(a, b, c, ... , z) {
  a.xx();
  // b.yy();
 	z.bar();
});
Copy the code

As you can see, module B is commented out and no longer in use. But the dependency array still contains “B”. This is too bad!

Of course, we can force users to use only another form, for example:

define(function(require, exports) {... });Copy the code

But they will know that it is RequireJS and will spend some time reading the RequireJS documentation. Once it’s started, it’s hard to stop it.

In addition to the disadvantages mentioned above, RequireJS has the following disadvantages:

3. It’s still 16KB compressed, it’s just a module loader, it should be smaller and faster.

4. The RequireJS source code contains many unnecessary functions for our use.

I think the loader should compile to:

  • loader-for-browser.js
  • loader-for-node.js
  • loader-for-xx.js

Loader-for-browser.js should contain only the basic functionality of the module loader in the client browser. But “require.js” contains code for Webworker, jQueryCheck, DOMContentLoaded, packages, etc. These are not necessary in our use case, but we cannot remove the unnecessary code.

For these reasons, Yip has developed a new Module Loader: SeaJS, which was announced in CommonJS Group in November 2011 (Announcing SeaJS: A Module Loader for the Web), sea-.js follows the CMD specification.

define(function(require, exports, module) {

  // exports is a reference to module.exports
  console.log(module.exports === exports); // true

  // re-assign module.exports
  module.exports = new SomeClass();

  // exports no longer equals module.exports
  console.log(module.exports === exports); // false

});
Copy the code

ES Module

Most of this chapter is excerpted from: Chen Yangjian’s blog: The State of the Front-end Module

In June 2015, ECMAScript6 standard was officially released. The ES modular specification was proposed to integrate CommonJS, AMD and other existing module solutions to achieve modularity at the level of language standards and become a common module solution for browsers and servers.

The module functions are completed by export and import commands. Export is used to export modules, and import is used to import modules. Import more uses, export more uses.

// Import a single interface
import {myExport} from '/modules/my-module.js';
// Import multiple interfaces
import {foo, bar} from '/modules/my-module.js';

// Export the function defined earlier
export { myFunction }; 

// Export constants
export const foo = Math.sqrt(2);

Copy the code

ES Module differs from CommonJS and Loaders schemes in the following aspects:

  • Declarative rather than imperative, orimportA Declaration Statement, not an expression, cannot be used in ES ModuleimportDeclare a dependency with a variable, or introduce a dependency dynamically:
  • The CommonJS module prints a copy of the value, the ES6 module prints a reference to the value.
  • importIs pre-parsed and pre-loaded, unlike RequireJS, which executes and then issues a request

For pragmatic Node.js developers, these differences put the massive community code created by NPM in an awkward position, requiring a lot of work to upgrade and integrate. David Herman explains that the benefits of the ES Module far outweigh the inconvenience:

  • Static imports ensure that they are compiled into variable references that can be optimized by the parser (through JIT compilation of the polymorphic inline cache) for more efficient execution at runtime in the current execution environment
  • Static export makes variable detection more accurateIn code checking tools such as JSHint and ESLint, whether a variable is defined is a very popular feature, while staticexportCould make the test more accurate
  • More complete loop dependency processingIn existing CommonJS implementations such as Node.js, circular dependencies are not completed by passingexportsObject, for direct referencesexports.fooOr parent module overwritemodule.exportsBecause ES Module passes references, you don’t have these problems

Others include greater compatibility with possible future additions to standards (macros, type systems, etc.).

ES Module in Browser

Before the ES Module standard came out, although the community implemented a large number of Loader, but the browser itself has not selected the Module scheme, supporting ES Module is relatively less concern for the browser.

Because the ES Module execution environment is different from normal scripts, the browser chooses to add

<script>
import foo from "./foo.js"
</script>

<script type="javascript">
import bar from "./bar.js"
</script>
Copy the code

ES Module is currently supported by several evergreen browsers. The last one is Firefox, which officially supports ES Module in Firefox 60, released on May 8, 2018.

In addition, browsers add

// In browsers, import statements can only be used in tags of scripts that declare type="module".<script type="module" src="./app.js"></script>// Use the nomodule attribute in the script tag to ensure backward compatibility.<script nomodule src="./app.bundle.js"></script>
Copy the code

ES Module in Node.js

But on the Node.js side, ES Module encounters a lot more noise. Former Node.js leader Isaacs Schlutuer even adds nothing to the fact that the ES Module is too utopian and faceless.

The first dilemma is how to support module execution mode, whether to auto-detect, ‘use module’, add module attributes as a special entry in package.json, or simply add a new extension.

Finally node.js chose to add a new extension.mjs:

  • in.mjsCan be used freely inimportexport
  • in.mjsCannot be used inrequire
  • in.jsCan only be used inrequire
  • in.jsCannot be used inimportexport

That is, the two module systems are completely independent. In addition, the dependency lookup method has been changed to require. Extensions are:

{ '.js': [Function].'.json': [Function].'.node': [Function]}Copy the code

Now (with — experimental-Modules on) :

{ '.js': [Function].'.json': [Function].'.node': [Function].'.mjs': [Function]}Copy the code

But two separate sets of module systems also lead to a second twist: how do module systems communicate with each other? This isn’t a problem for browsers, but node.js has to consider the massive CommonJS modules in NPM.

In.mjs, developers can import CommonJS (although they can only import default) :

import 'fs' from 'fs'
import { readFile } from 'fs'
import foo from './foo'
// etc.
Copy the code

In.js, developers naturally cannot import ES Module, but they can import() :

import('./foo').then(foo= > {
  // use foo
})

async function() {
  const bar = await import('./bar')
  // use bar} ()Copy the code

Note that unlike the browser, which determines the running mode by import, the running mode of the script in Node.js is bound to the extension. That is, dependencies are found differently:

  • in.jsrequire('./foo')Looking for the./foo.jsor./foo/index.js
  • in.mjsimport './bar'Looking for the./bar.mjsor./bar/index.mjs

Taking advantage of these features, we can now upgrade existing NPM modules to ES Modules and still support CommonJS.

Dynamic Import

Static imports are the best choice for initializing load dependencies. Using static imports is easier to benefit from code static analysis tools and tree shaking. But when you want to load modules under certain conditions or on demand, you need to introduce dependencies dynamically, for example:

if(process.env.NODE_ENV ! = ='production') {
  require('./cjs/react.development.js')}else {
  require('./cjs/react.production.js')}if (process.env.BROWSER) {
  require('./browser.js')}Copy the code

To this end, Domenic Denicola drafted the import() standard proposal.

// This is a phase 3 proposal.
var promise = import("module-name");
Copy the code

Script tags in HTML do not need to declare type=”module”, except to handle dynamic dependencies.

<script>
import('./foo.js').then(foo= > {
  // use foo
})
</script>
Copy the code

We can also use import() to import ES modules in Node.js (.js files) :

import('./foo.mjs').then(foo= > {
  // use foo
})
Copy the code

Writing modular JavaScript code for browsers and Node.js using ES Modules is completely feasible. Do we need compilation or packaging tools?

Module Bundler

Most of this chapter is excerpted from: Chen Yangjian’s blog: The History of front-end Modules

There are also drawbacks to using a module loader on the browser side. For example, the RequireJS encoding mode is not friendly, it is troublesome to load modules of other specifications, and it is executed in advance. SeaJS rules change all the time, which leads to various upgrade problems. However, CommonJS is convenient and stable to use on the server side, and there are only three simple steps to reference third-party libraries:

  • Configure the module name and version number in package.json
  • NPM install Installs the module
  • Just introduce it using require

Is it possible to introduce modules in the browser using the CommonJS specification and make it easy to call modules from other specifications?

One solution is precompilation, where we write code to define and import modules in the CommonJS specification, and then compile the modules and dependencies into a JS file called BundleJS.

Browserify and WebPack are both pre-compiled modular solutions that end up generating a bundle from a build and resolving dependencies during the build.

Browserify

Substack, an early and active member of the Node.js community, developed Browserify from a simple point of view:

Browsers don’t have the require method defined, but Node.js does. With Browserify you can write code that uses require in the same way that you would use it in Node.

Browserify[Github] allows you to organize browser-side Javascript code in a way similar to Node’s require(), Pre-compilation allows the front-end Javascript to directly use some of the libraries installed on Node NPM, or to introduce non-CommonJS modules using browserify.transform (browserify.transform configuration transform plug-in).

Browserify’s require is consistent with Node.js and does not support asynchronous loading. Browserify’s support for asynchronous loading has been strong in the community, as you can see: Support for asynchronous loading (and not packing everything in one file), but Browserify’s require should be consistent with Node.js:

1: wrapping a whole file in a function block is ugly

2: node modules use synchronous requires

3: browserify’s goal is to let code written for node run in the browser

Webpack

Webpack, released a year after Browserify, combines the strengths and weaknesses of CommonJS and AMD, and can be written as CommonJS, with on-demand and asynchronous loading of all resources after compilation.

One of the most outstanding features of Webpack is its module parsing granularity and powerful packaging capability, and the other is its scalability. Related conversion tools (Babel, PostCSS, CSS Modules) can be quickly accessed into plug-ins and can also be customized Loader. Taken together, these characteristics are all to the detriment.

It also supports ES Module:

import defaultExport from "module-name";
import * as name from "module-name";
import { export } from "module-name";
Copy the code

This is the benefit of the build tool, which is much more scalable than traditional browser loaders and can easily add transpiler support such as Babel and Traceur.

More recommended reading:

  • webpack for browserify users
  • how-is-webpack-different
  • Webpack vs. rival products

Afterword

As Yubo said in his point in the history of front-end modular development, with the W3C and other specifications and the rapid development of browsers, front-end modular development will gradually become the infrastructure. Everything will eventually become history.

We don’t have to worry about which modularity solution to use in our development. ES6 solves this problem at the level of language standards.

Time Line

  • In 2009, Ryan Dahl, an American programmer, created the Node. js project. The module system of Node. js is based on the CommonJS module specification.
  • However, require in the CommonJS specification is synchronous, which is not acceptable on the browser side. So there was an AMD specification, and in 2010 RequireJS was implemented as an AMD specification.
  • Since 2012, Yu Bo felt that RequireJS was not perfect enough and many of her suggestions to the RequireJS team were not accepted, so she wrote Sea-.js herself and formulated CMD specification, and Sea-.js followed CMD specification.
  • ECMAScript6 standard was officially released in June 2015, realizing module functions at the level of language standards, which can completely replace CommonJS and AMD specifications and become a common module solution for browsers and servers. (This is the future)
  • In October 2015, UMD emerged to integrate CommonJS and AMD’s approach to module definition specifications. At this time, the ES6 module standard was just coming out, and many browsers did not support the ES6 modular specification.
  • Browserify was launched in 2016
  • Webpack was released in 2017

Current Situation

  • CommonJS
    • Nodejs Start 67.3K has become a server-side JavaScript standard
  • Module Loader(obsolete)
    • RequireJS [GitHub] Start: 12.4K, no longer maintained
    • Seajs [Github] Start: 8K, no longer maintained. In 2015, the author tweeted that the Sea-js tree should be given a tombstone.
  • ES6 Module
    • Syntax is supported in mainstream browsers and Nodejs8.5 and above. Check browser compatibility.
  • Module Bundler
    • Webpack [GitHub] Star: 52.5K, now the most popular packaging tool
    • Browserify [GitHub] Star: 13k, 11 November 2019

Finally, this article is partially referenced or extracted from the following articles: