Article objective: the combination of code or classic source code to explain, roughly introduce the development of front-end modularization.

0x01 – All natural global function mode without processing

By exposing function names directly to the global scope, without modularization at all, we can clearly see some of the problems that are bound to arise when large teams work together:

  • Module dependencies are difficult to determine quickly;
  • Easy to cause naming conflicts;

But we can’t write everything in the global scope.

You’re really cool when you write code, but you’re really awkward when you fix bugs.

<! DOCTYPEhtml>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
    <title>Wild Age - Global function mode</title>
  </head>
  <body></body>
  <script src="./A.js"></script>
  <script src="./B.js"></script>
  <script src="./C.js"></script>
  <script>
    function globalFunctionA() {
      // ...
    }
    function globalFunctionB() {
      // ...
    }
    function globalFucntionA() {
      // ...
      console.log("Do you like me?");
    }
  </script>
</html>
Copy the code
0x02 – Global function pattern using namespaces

A namespace is set up with an object, and properties and methods in the namespace are called by the object name.

  • namespace.property
  • namespace.function()

Namespaces are really doing their best. What’s Brendan doing? Select * from Brendan;

Here you can see the source of jQuery in this period code.jquery.com/jquery-1.2…. That’s the way it’s used.

// namespaceA.js
let namespaceA = {
    namespaceName: "Ava".functionA: function () {
        // ...
    },
    functionB: function () {
        // ...}}// namespaceB.js
let namespaceB = {
    namespaceName: "Diana".functionA: function () {
        // ...
    },
    functionB: function () {
        // ...
        namespaceA.functionA();
        console.log(`${namespaceA.namespaceName}`); }}// You can use it this way
namespaceB.functionB(); 
// Ava
Copy the code

From the above code, we can see that the introduction of namespaces solves the problem of naming conflicts in the global scope to some extent, but it also has its limitations:

  • Because a namespace is essentially an object, and its properties and methods are directly exposed to the outside world, there is a systemic risk that external modification operations will affect the internal state.
  • Dependencies between modules remain difficult to determine.
// namespaceA.js
let namespaceA = {
    namespaceName: "Ava".functionA: function () {
        // ...
        console.log('Not very muchThe ${this.namespaceName}! Hum ~ `)},functionB: function () {
        // ...}}// namespaceB.js
let namespaceB = {
    namespaceName: "Diana".functionA: function () {
        // ...
    },
    functionB: function () {
        // ...
        namespaceA.namespaceName = "Diana";
        namespaceA.functionA();
        console.log('But it was already there${namespaceA.namespaceName}The shape of jia dinner win hemp. `); }}// You can use it this way
namespaceB.functionB();
/* I don't like Diana very much! But it was already in Diana's shape. Jia won the dinner. * /
Copy the code
0x03-iife — Achilles in global function mode

IIFE uses closures to privatize internal attributes, which is a nice design and is still used in webPack packaging, but the modularity of the implementation of this pattern still has its Achilles heel.

After all, you can’t always expect a mass production environment to have strict control over the loading order of JS files or the writing order of code. It’s too difficult and unrealistic.

Take jQuery as an example code.jquery.com/jquery-1.4…. IIFE is already being used instead of namespaces.

// Whiteblowfish.js Let's look at the strengths of IIFE and then explore its weaknesses.
const otherModuleA =(function() {
    function fn () {
        console.log(`fn from otherModuleA`);
    }
    return {
        fn
    }
})();

const otherModuleB =(function() {
    function fn () {
        console.log(`fn from otherModuleB`);
    }
    return {
        fn
    }
})();
const Beller = (function () {
    let times = 55;
    let name = "Beller";
    let darling = "Eileen";

    otherModuleA.fn();
    otherModuleB.fn();

    function getName() {
        return name;
    }
    function getTimes() {
        return times;
    }
    function setDarling(moreDarling) {
        darling = moreDarling;
    }
    function getDarling() {
        return darling;
    }
    function onemoreTime() {
        times = times + 1;
    }
    function findSpider() {
        console.log(`${name}Spiders were found in dance practice rooms,${name}I almost passed out. `);
    }
    return {
        getTimes,
        getName,
        onemoreTime,
        setDarling,
        getDarling,
        findSpider
    }
})(otherModuleA, otherModuleB);

console.log('Eileen already touched it${Beller.getName()}The thigh${Beller.getTimes()}Times! Check the motives for joining the league! `);
Beller.onemoreTime();
console.log('After one more fair touch, Eileen had touched it${Beller.getName()}The thigh${Beller.getTimes()}Times! Check the motives for joining the league! `);
Beller.times = 0; // Eileen tried to tamper with the data
console.log(Eileen tried to tamper with the data, but it was impossible${ Beller.getTimes()}Times. `); 
// Why?
console.log(Beller);
// This is the magic scope. The private variables implemented by closures cannot be changed directly.
/* { getTimes: [Function: getTimes], getName: [Function: getName], onemoreTime: [Function: onemoreTime], setDarling: [Function: setDarling], getDarling: [Function: getDarling], findSpider: [Function: findSpider], times: 0 } */
// Of course, it is not impossible to stew the large triangle.
Beller.setDarling(`${Beller.getDarling() + ", Carol"}`);
console.log('What is the Grand Triangle:${Beller.getName()}.${Beller.getDarling()}`);

// The mighty Captain Beller is also afraid of spiders.
Beller.findSpider();
// Polaris tried to fix this weakness with Captain Beller.
Beller.findSpider = function() {
    console.log(Small captain `${Beller.getName()}Spotted a spider, but she wasn't scared at all. `)};// Try again, good, Polaris made it.
Beller.findSpider();

Copy the code

Polaris successfully fixed captain Beller’s Achilles heel in the code above, but IIFE did not fix his Achilles heel. Although IIFE uses closures to privatize its internal attributes, as you can see from the code above, the methods exposed in this way can still be tampered with externally.

// The code to verify the first and second points
var moduleA = (function () {
    let moduleName = "moduleA";
    function getModuleName() {
        console.log(moduleName);
    };
    function fn() {
        getModuleName()
        console.log(`fn from moduleA`);
    };
    return{ fn, getModuleName }; }) ();var moduleC = (function (A, B) {
    let moduleName = "moduleC";
    function getModuleName() {
        console.log(moduleName);
    };
    function fn() {
        A.fn();
        A.fn = function () {
            console.log('a.fein () has been tampered with')}; B.fn(); };return {
        fn,
        getModuleName
    };
})(moduleA, moduleB);

var moduleB = (function (A) {
    let moduleName = "moduleB";
    function getModuleName() {
        console.log(moduleName);
    };
    function fn() {
        A.fn();
        getModuleName()
        console.log(`fn from moduleB`);
    };
    return {
        fn,
        getModuleName
    };
})(moduleA);
moduleC.fn();

/* moduleA FN from moduleA A.FEIN () Has been tampered with moduleB FN from moduleB */

// The code used to verify point 3
var moduleA = (function () {
    let moduleName = "moduleA";
    function getModuleName() {
        console.log(moduleName);
    };
    function fn() {
        getModuleName()
        console.log(`fn from moduleA`);
    };
    return{ fn, getModuleName }; }) ();var moduleC = (function (A, B) {
    let moduleName = "moduleC";
    function getModuleName() {
        console.log(moduleName);
    };
    function fn() {
        A.fn();
        A.fn = function () {
            console.log('a.fein () has been tampered with')}; B.fn(); };return {
        fn,
        getModuleName
    };
})(moduleA, moduleB);

var moduleB = (function (A) {
    let moduleName = "moduleB";
    function getModuleName() {
        console.log(moduleName);
    };
    function fn() {
        A.fn();
        getModuleName()
        console.log(`fn from moduleB`);
    };
    return {
        fn,
        getModuleName
    };
})(moduleA);
moduleC.fn();

/* B.fn(); ^ TypeError: Cannot read property 'fn' of undefined // moduleB was initialized after moduleA was initialized, although the var keyword raises variables to the top of the scope. It is true that a few small pieces of JS code can achieve strict control over the writing order of modules, but it is undoubtedly challenging to achieve this in collaborative development. * /

Copy the code

We can see from the above code that IIFE has privatized its own internal properties using closures, but there is still room for improvement in the modularity of its implementation. (To verify the third point, the var keyword is used to define modules.) :

  • Dependencies need to be collected manually;
  • There is no guarantee that writing the current module will not affect the operation of other modules;
  • It is necessary to strictly control the loading sequence of JS files or the writing sequence of codes;
0x04-CommonJS — An important step in the development of front-end modularization

Require + module.exports NodeJS treats each file as a module, and the underlying implementation wraps a layer of module information around these files with module constructors. Nonsense not to say, we directly on the source.

// index.js
console.log(module); // See what nodejs prints for us in the console.
/* Module { id: '.', path: 'D:\\project\\src', exports: { }, parent: null, filename: 'D:\\project\\src\\index.js', loaded: false, children: [], paths: [ 'D:\\project\\src\\node_modules', 'D:\\project\\node_modules', 'D:\\node_modules' ] } */
Module. exports refers to an empty object, which is a complex type of data stored in the heap. This means that module.exports only records the memory address of the heap.
console.log(module.exports === exports); Module. exports and exports refer to the same address when we do not modify them in the file.
// One thing that beginners get wrong here is that it is easy to assign values directly to exports, or to mix module.exports and exports in the same JS file, resulting in a discrepancy between the internal members that are supposed to be exposed to other modules and the internal members that are actually exposed.
exports = function() {
    console.log('wrong')};console.log(module); // Print it out.
/* Module { id: '.', path: 'D:\\project\\src', exports: Module. Exports {}, // module. Exports is not exported. parent: null, filename: 'D:\\project\\src\\index.js', loaded: false, children: [], paths: [ 'D:\\project\\src\\node_modules', 'D:\\project\\node_modules', 'D:\\node_modules' ] } */
module.exports = {
    A: function() {
        console.log('yes')},B: "B"
};
console.log(module); // Print it out again.
/ * Module {id: '. ', path: 'D: \ \ project \ \ SRC', exports: {A: / Function: A, B: 'B'}, / / modify. parent: null, filename: 'D:\\project\\src\\index.js', loaded: false, children: [], paths: [ 'D:\\project\\src\\node_modules', 'D:\\project\\node_modules', 'D:\\node_modules' ] } */
Copy the code

Don’t mix module.exports and exports on the same file

  • Use onlymodule.exports.xxx = xxxormodule.exports = { xxx, yyy, zzz } Such module exposure mode;
  • Or just useexports.xxx = xxx, do not use itexports = { xxx, yyy, zzz }Exports, after all, is just a copy of module.exports, which ultimately affects the exposed interfaceexports.exports;

Returning to the topic of modularity, where was the Module created here?

See NodeJS source making warehouse _node/lib/internal/modules/CJS/loader. Js_L808

// node/lib/internal/modules/cjs/loader.js L808
  const cachedModule = Module._cache[filename];
  if(cachedModule ! = =undefined) { // If the cache is not empty, the component has already been loaded.
    updateChildren(parent, cachedModule, true);
    if(! cachedModule.loaded) {const parseCachedModule = cjsParseCache.get(cachedModule);
      if(! parseCachedModule || parseCachedModule.loaded)return getExportsForCircularRequire(cachedModule);
      parseCachedModule.loaded = true;
    } else {
      returncachedModule.exports; }}const mod = loadNativeModule(filename, request);
  if(mod? .canBeRequiredByUsers)return mod.exports;

  // Don't call updateChildren(), Module constructor already does.
 const module = cachedModule || new Module(filename, parent);  // Let's take a look at what's in the Module constructor.

  if (isMain) {
    process.mainModule = module;
    module.id = '. '; // The id in the code above is '.'.
  }

  Module._cache[filename] = module; // This is also important, where nodejs caches modules loaded for the first time.
// The above paragraph of source code also has a useful knowledge point, next we expand to talk about.


// carol.js
let bobo = 2;
let willBoBoWho = "Beller";
let addBoBo = function () {
    bobo = bobo + 1;
}
module.exports = {
    bobo, willBoBoWho, addBoBo
};


// index.js
let carol = require('./carol.js');

console.log(`Carol will bobo ${carol.willBoBoWho} ${carol.bobo} times.`); 
// Carol will bobo Beller 2 times. The first output, as a control.

carol.addBoBo(); // Call the method to try to modify the value in the original module
console.log(`Carol will bobo ${carol.willBoBoWho} ${carol.bobo} times.`); 
// Carol will bobo Beller 2 times. It failed spectacularly.
// Since require is A copy of the value in the exported module, when the value is exported by module A, call the method in module A to modify the value of module A, will not change to the copy of module A under the current module B.

carol.willBoBoWho = carol.willBoBoWho + "Royal Knight."; // What about direct modification?
carol = require('./carol.js'); 
// Carol Will Bobo Beller, Royal Knights 2 times
// Why is that? Since Carol.js was already loaded and there was a cache of it in memory, when reintroduced, it was fetched directly from memory. So the changes are not overwritten by the data in the original module.
console.log(`Carol will bobo ${carol.willBoBoWho} ${carol.bobo} times.`);

Copy the code

Tips:

  • When the value is exported by module A, the method in module A is called to modify the value of module A, which will not change to the copy of module A under the current module B.
  • The module is cached in memory the first time it is loaded, and the second require is a cache read directly from memory.

NodeJS source making warehouse _node/lib/internal/modules/CJS/loader. Js_L172

// node/lib/internal/modules/cjs/loader.js L172

function Module(id = ' ', parent) {
  this.id = id;
  this.path = path.dirname(id);
  this.exports = {};
  moduleParentCache.set(this, parent);
  updateChildren(parent, this.false);
  this.filename = null;
  this.loaded = false;
  this.children = [];
}
// That's right.
Copy the code

NodeJS source making warehouse _node/lib/internal/modules/CJS/loader. Js_L1025

// node/lib/internal/modules/cjs/loader.js L1025

try {  // The variables and methods that can be used on NodeJS without us declaring or importing them are the ones that NodeJS adds to every file.
    return vm.compileFunction(content, [ 
      'exports'.'require'.'module'.'__filename'.'__dirname',
    ], {
      filename,
      importModuleDynamically(specifier, _, importAssertions) {
        const loader = asyncESM.esmLoader;
        returnloader.import(specifier, normalizeReferrerURL(filename), importAssertions); }}); }catch (err) {
    if (process.mainModule === cjsModuleInstance)
      enrichCJSError(err, content);
    throw err;
  }
Copy the code

So how does commonJS achieve modularity?

I wrote a version of nodeJS, but it is still too rough, so I will not bring it up at this stage. I will make it up later after I read the implementation of nodeJS built-in module.

Those of you interested in this implementation can read the article written by these two guys, which I think is quite detailed.

Take a look at how CommonJs works

Front-end Engineering tetralogy “The Past life of Modularization (I)

0x05 – AMD

AMD module features:

  • Module loading by asynchronous: the browser module loading needs to depend on the network environment, AMD specifications use asynchronous mode to load the module, the module loading does not affect the operation of its following statements. All statements that depend on this module are defined in a callback function that will not run until the load is complete.
  • Dependency precollection: AMD modules specify which modules they need to depend on in the second parameter of define(). When you define a module, you declare the module it depends on
Define (id = name of the script required by the module loader, dependencies = ["require"."exports"."module"], factory);
Copy the code

define (

id? : String, // Optional

dependencies? : String[], // Optional

Factory: Function | Object / / will be selected

);

The first argument, ID, is a string. It refers to the name of the module in the definition. This parameter is optional. If this parameter is not provided, the name of the module should default to the name of the specified script requested by the module loader.

The second parameter Dependencies is an array of strings that act as a dependency array for the AMD module.

  • If the value of “require”, “exports”, or “module” appears in the dependency list, the parameter should be resolved to the corresponding free variable defined by the CommonJS module specification.
  • The dependency argument is optional. If omitted, it defaults to [“require”, “exports”, “module”]. However, if the arity (length attribute) of the factory method is less than 3, the loader can choose to call only the factory method that has the number of arguments corresponding to the arity or length of the function.

The third argument factory is the factory method that must be passed in.

define('module1'.function() {
    let fn = function () {
        console.log('module1 ... ');
    };
    return fn;
});
define('module2'['module1'].function(module1) {
    let fn = function() {
        module1.fn();
    };
    return fn;
});
define('module3'['module1, module2'].function(module1, module2) {
    let fn1 = function() {
        module1.fn();
    };
    let fn2 = function() {
        module2.fn();
    };
    return {
    	fn1, 
        fn2
    };
});
// weather.js when factory is an object.
define({
    weather: [{city: 'hangzhou'.weather: 'rainy'}, {city: 'huzhou'.weather: 'rainy',},/ /...]}); define(['weather'].function(weather) {
    // Use weather here
});
Copy the code

The AMD specification also uses the require keyword to load modules, but unlike CommonJS, the AMD specification requires two parameters for require.

require(

​ modules: String [],

​ callback: Function

);

When the modules that make up modules are successfully loaded, the callback function is executed

require([module1, module2, ...] .function callback (module1, module2, ...) {
     module1.fn();
     module2.fn();
     // ...
     // module1.fn() and module2.fn() are executed in a manner that is not synchronized with the loading of modules such as module1 and module2, so the browser does not appear to be dead and is suitable for the browser environment.
 });
Copy the code

Here is an example of jQuery code.jquery.com/jquery-1.8…. , here the jQuery already use AMD specification, the specific time may be in bugs.jquery.com/ticket/1411… Before and after.

// jquery-1.8.3.js goes directly to the end of the file to look at this section.

	// Expose jQuery as an AMD module, but only for AMD loaders that
	// understand the issues with loading multiple versions of jQuery
	// in a page that all might call define(). The loader will indicate
	// they have special allowances for multiple jQuery versions by
	// specifying define.amd.jQuery = true. Register as a named module,
	// since jQuery can be concatenated with other files that may use define,
	// but not use a proper concatenation script that understands anonymous
	// AMD modules. A named AMD is safest and most robust way to register.
	// Lowercase jquery is used because AMD module names are derived from
	// file names, and jQuery is normally delivered in a lowercase file name.
	// Do this after creating the global so that if an AMD module wants to call
	// noConflict to hide this version of jQuery, it will work.
	if (typeof define === "function" && define.amd && define.amd.jQuery) {
		define("jquery"[],function () { return jQuery; });
	}
Copy the code

You can also read some of require.js’s introduction to modularity.

Why Web Modules

Why AMD

How do you modify existing CommonJS code to be COMPATIBLE with AMD specifications?

define(function (require.exports.module) {
    const a = require('a'),
        b = require('b'),
        c = require('./c.js');
    const fn = function() {
    	a.fn();
        b.fn();
    };
    return {
        fn,
        ... // Other possible functions
    };
};
Copy the code
0x06 – CMD

CMD is the normalized output of SeaJS module definitions during the roll-out process. CMD module features:

  • Dependency proximity: uses are introduced wherever they are used in the code block, without being written to define arguments.
  • Load on demand: If the current module depends on multiple external modules, an external module is not introduced when it is not needed.

CMD specification on Github

define (

callback: Function | String | Array | Object| OtherType

);

// carol.js
define(function (require.exports.module) = >{
   	exports.fn = function () {
    	// ...
	};
});
// eileen.js
define(function (require.exports.module) = >{
    exports.fn = function() {
    	// ...
	};
});
// beller.js
define(function (require.exports.module) = >{
   let carol = require('carol'); // Rely on proximity: introduce where it is used
	carol.fn(); // Load on demand: When running here, the following Eileen will not be introduced because it has not been executed there yet.
	/ /... Some logic
	let eileen = require('eileen');
	moduleB.fn();
	/ /... Some logic
});

Copy the code

When factory is not of type Function:

// This code uses an example directly from the CMD specification
// object-data.js
define({
    foo: "bar"
});

// array-data.js
define([
    'foo'.'bar'
]);

// string-data.js
define('foo bar');
Copy the code
0x06 – UMD

The running anywhere UMD module makes various specification conversions to allow modular code to run in a variety of environments.

Well, one of the best libraries to illustrate is axios, which can be used by both the browser and the server.

Axios using the UMD specification

(function webpackUniversalModuleDefinition(root, factory) {
	if(typeof exports= = ='object' && typeof module= = ='object') 
		module.exports = factory(); // Exporst and Module of CommonJS implementation in NodeJS are both objects
	else if(typeof define === 'function' && define.amd)
		define([], factory); // Define is an AMD module
	else if(typeof exports= = ='object')
		exports["axios"] = factory();  // Other implementations of CommonJS
	else 
		root["axios"] = factory();   // No module environment is attached directly to the global object.}) (this.function() {
	/ / axios code
});
// However, the AXIos UMD module does not include the CMD specification.
Copy the code

Let’s write a UMD module that is compatible with CommonJS, AMD, CMD, etc.

// This code is modified on the webpack5 output.library.type = 'umd' output
(function (root, factory) {
    if (typeof module= = ='object' && typeof exports= = ='object') {
        module.exports = factory(require('depModule')); // Exporst and Module of CommonJS implementation in NodeJS are both objects
    } else if (typeof define === 'function' && define.amd) {
        define(['depModule'], factory);  // Define is an AMD module
    } else if (typeof define === 'function' && define.cmd) { 
         // Define is a CMD module
        There is no implementation of the CMD specification in the Webpack package, so we need to hand-write a conversion method
        define(function(require.exports.module) {
            const depModule = require('depModule');
            module.exports = factory(depModule);
        });
    } else if(typeof exports= = ='object') {  // Other implementations of CommonJS
		exports["axios"] = factory();
    } else { // No module environment is attached directly to the global object.
        root.moduleName = factory(root.depModule)
    }
}(this.function(depModule) {  / /... I'm just going to call it factory
    let fn = function () {
        console.log('fn... ');
    }
    return {
        fn
    }
}));
Copy the code
0x07 – ES Module – Modularity becomes ECMAScript standard

Import, export these should be very familiar, no more nonsense.

// test.mjs
export default function() {
    console.log('Knowing is easy.');
}
// index.js
import test from './test.mjs';

test();
// It is not difficult to grasp
//console.log(module); // ReferenceError: module is not defined in ES module scope
//console.log(exports); // ReferenceError: exports is not defined in ES module scope
Copy the code

It was followed by dynamic introduction of new features in ES11(released in June 2020).

import('test.mjs').then(dynamicEsModule= > {
    dynamicEsModule.fn();
})
Copy the code

Next comes front-end engineering, which I will post as soon as possible.

This is the front end rookie _ Sinan, if my article can bring you harvest, that is the best thing.

Writing is not good, talent and learning shallow, but also hope you big guy not stingy advice.