This is the third day of my participation in Gwen Challenge. For details, see Gwen Challenge.

As front-end applications grew more complex, our projects grew to the point where we had to spend a lot of time managing them. Modularization is the most mainstream way of project organization. It divides complex codes into different modules according to their functions and maintains them independently, thus improving development efficiency and reducing maintenance costs.

Webpack is used to compile JavaScript modules. In essence, Webpack is a modular packaging tool, which skillfully realizes the modularization of the whole front-end project through the design idea of “everything module”. In the Webpack concept, any resource in a front-end project can be used as a module, and any module can be processed by the Loader mechanism and eventually packaged together.

The evolution of modularity

  • File division mode

Put each function and its related state data into a separate JS file, with the convention that each file is an independent module. Use a module to introduce the module to the page, one script tag for each module, and then directly call the members (variables/functions) in the module.

SRC Exercises ── a.exercises ─ b.exercises ─ index.htmlCopy the code
// a.js
let a = 'hello';
let b = 'webpack! ';
Copy the code
// b.js
function jump() {
    location.replace(`www.baidu.com`);
}
Copy the code
<! DOCTYPEhtml>
<html>
    <head>  
        <meta charset="UTF-8" />
        <title>webpack</title>
    </head>
    <body>
        <script src="a.js"></script>
        <script src="b.js"></script>
        <script>
            // Use global members directly
            jump(); // There may be a naming conflict
            console.log(a + b);
            a = 'other'; // Data may be modified
        </script>
    </body>
</html>
Copy the code

Disadvantages:

  • Modules work directly in the global scope, and a large number of module members pollute the global scope.
  • There is no private space, and all module members can be accessed or modified outside the module.
  • Once the number of modules increases, naming conflicts tend to occur.
  • Unable to manage dependencies between modules
  • It is also difficult to identify which module each member belongs to during maintenance.

This original “modular” approach to implementation relies entirely on conventions, which are very unreliable as projects get bigger, so we need to fix as many of the problems as possible.

  • Namespace mode

Agreed each module only expose a global object, all the members of the module is mounted to the global object, particular way is on the basis of the first stage, through the “package” for each module in the form of a global object implementation, this way is like as a member of the module added “namespace”, so we call it a namespace.

// a.js
window.moduleA = {
    method1: function () {
        console.log('moduleA#method1'); }};Copy the code
// b.js
window.moduleB = {
    data'something'
    method1function ({
        console.log('moduleB#method1')}}Copy the code
<! DOCTYPE html><html>
<head>
  <meta charset="UTF-8">
  <title>Stage 2</title>
</head>
<body>
    <script src="a.js"></script>
    <script src="b.js"></script>
    <script>
        moduleA.method1()
        moduleB.method1()
        // Module members can still be modified
        moduleA.data = 'foo'
    </script>
</body>
</html>
Copy the code

This approach to namespaces only solves the problem of naming conflicts, but other problems remain.

  • IIFE (Immediately-Invoked Function Expression)

The IIFE (Immediately-Invoked Function Expression) provides private space for the module. This is done by placing each module member in a private scope created by a function that executes immediately, and by hanging onto global objects for members that need to be exposed externally.

// a.js! (function () {
    let name = 'module-a';

    function method1() {
        console.log(name + '#method1');
    }

    window.moduleA = {
        method1: method1, }; }) ();Copy the code
// b.js! (function () {
    let name = 'module-b';

    function method1() {
        console.log(name + '#method1');
    }

    window.moduleB = {
        method1: method1, }; }) ();Copy the code

This approach introduces the concept of private members that can only be accessed through closures within module members, which solves the problems of global scope contamination and naming conflicts mentioned earlier.

  • IIFE depends on parameters

In addition to IIFE, we can use IIFE parameters as dependency declarations, which makes the dependencies between each module more obvious.

// a.js! (function ($) {
    // Clearly indicate the module's dependencies with parameters
    let name = 'module-a';

    function method1() {
        console.log(name + '#method1');
        $('body').animate({ margin: '200px' });
    }

    window.moduleA = {
        method1: method1,
    };
})(jQuery);
Copy the code

The above four phases are ways that early developers landed on modularity without tools or specifications, and they did solve many of the problems of implementing modularity in the front end, but there were still some unanswered questions. For example: module loading;

Budding modular specifications

Besides module loading problem, by convention modular ways at this time, the different developers will appear in the process of implementation of some subtle differences, therefore, in order to unify the difference between different developers and project, we need to set an industry standard to regulate the realization of the modular approach;

  • commonJS

It is the module specification that node.js follows. According to the convention, a file is a module, each module has its own scope, exports members through module.exports, and loads modules through the require function. The CommonJS convention is to load modules synchronously. Node.js implementations load modules at startup and use only modules during execution, so this is not a problem. However, if you want to use synchronous loading mode on the browser side, it will cause a large number of synchronous mode requests, resulting in low application efficiency

  • AMD (Asynchronous Module Definition)

The asynchronous module definition specification was redesigned specifically for the browser side. Each module is defined by the define() function, which accepts two parameters by default. The first parameter is an array used to declare the module’s dependencies. The second argument is a function that corresponds to the preceding dependencies, each of which corresponds to the exported member of the dependency module. The function provides a private space for the current module. If you need to export members from the current module, you can return them. At the same time, a very famous library called require.js was released, which, in addition to implementing the AMD modularization specification, is itself a very powerful module loader.

define(['jquery'.'./module.js'].function ($, module){
    return {
        start(){$('body).animate({margin: '200px'}); module() } } })Copy the code
  • CMD (Common Module Definition)

His specification describes how modules can be written to interoperate in browser-based environments, similar to CommonJS, which is basically the same in usage as require.js, and can be considered a duplicate wheel, Taobao’s Seajs.

  • Universal Module Definition (UMD) link

This pattern is designed to address the common CommonJS and AMD code issues, while also supporting the old global variable specification.

  • ES Modules ECMA-262

A mechanism for breaking up JavaScript programs into separate modules that can be imported on demand. The idea is to be as static as possible, so that module dependencies, as well as input and output variables, can be determined at compile time.