In recent years, the front end of the rapid development, and the front end of the modular scheme is also constantly updated, here is a record of the work of 8 years to understand some of the front-end modular knowledge, by the way, “take a test of the ancient”, because the content involved more than each is not very understand, can only try to ensure the accuracy of the output content.

Best served with Fe-Module-examples

File module

Before the front-end modularity specification, we implemented code modularity and module sharing by pulling out the common logic and putting it in the common code, and then referencing the desired files directly in different pages.

page1.html

<! DOCTYPEhtml>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
        <title>Demo</title>
        <script src="https://code.jquery.com/jquery-1.1.1.js"></script>
        <script src="/header.js"></script>
        <script src="/footer.js"></script>
        <script src="/menu.js"></script>
        <script src="/page1.js"></script>
    </head>
    <body></body>
</html>
Copy the code

page2.html

<! DOCTYPEhtml>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
        <title>Demo</title>
        <script src="https://code.jquery.com/jquery-1.1.1.js"></script>
        <script src="/header.js"></script>
        <script src="/footer.js"></script>
        <script src="/menu.js"></script>
        <script src="/page2.js"></script>
    </head>
    <body></body>
</html>
Copy the code

The way modules are defined is also evolving

The global variable

The early ones exposed all the variables globally, which was easy to call but severely polluted the global variables.

header.js

window.login = function() {... }window.logout = function() {... }Copy the code

main.js

window.login();
Copy the code

The namespace

Then came the use of namespaces to reduce the impact on the global scope, but because code is executed in the global scope it is easy to accidentally pollute.

header.js

var header = window.header = {};
header.login = function() {... } header.logout =function() {... }Copy the code

main.js

window.header.login();
Copy the code

IIFE package

Then people started using IIFE (self-executing/immediately executing functions) to wrap code around function scopes, further reducing global scope contamination.

header.js

window.header = (function () {
    return {
        login: function () {},
        logout: function () {}
    };
})();
Copy the code

main.js

window.header.login();
Copy the code

Module system

Over time, various modular systems began to appear in the community.

CommonJS (hereinafter called CJS)

The first was CJS, which was called ServerJS in the early days. After the introduction of Modules/1.0 specification, NodeJS gradually developed into a built-in module system. CJS imports and exports modules via require and module.

a.js

module.exports = function (log) {
    console.log(log);
};
Copy the code

demo.js

var a = require('./a');
a('test');
Copy the code

Its implementation is based on IIFE, and CJS modules are wrapped in functions in NodeJS

(function (exports.require.module, __filename, __dirname) {
    module.exports = function (log) {
        console.log(log);
    };
});
Copy the code

Thus realize module isolation and definition.

AMD

Since CJS is not suitable for browsers, the AMD specification for various reasons separated from the CJS camp and became its own, representative of which is require.js.

a.js

define(function () {
    return {
        log: function (log) {
            console.log(log); }}; });Copy the code

b.js

define(['a'].function (a) {
    return {
        loga: a.log,
        logb: function (log) {
            console.log(log); }}; });Copy the code

main.js

require(['a'.'b'].function (a, b) {
    a.log('a');
    b.logb('b');
    b.logb('b');
});
Copy the code
Require.js is widely supported, but some libraries inject global variables, such as Lodash, for compatibility with certain usage cases.Copy the code

CMD

However, THERE are some differences between AMD and CJS that lead to disapproval from some people.

CJS

// CJS will load the module and execute it only when the module is required
var a = reuqire('js');
Copy the code

AMD

define(['a'].function (a) {
    // The code for module A has already been downloaded and executed
});
Copy the code

Due to the limitations of the browser, there is no way to achieve the same synchronous download and execution as CJS (in fact, there is a way, but not very good), so domestic Daniuyu Bo, developed sea-.js and promoted the CMD specification. The biggest difference between sea.js and require.js is the timing of module execution.

CMD

define(function (require) {
    // Module A has been downloaded, but its factory is not executed
    var a = require('a'); // When module A is required for the first time, its factory is executed
});
Copy the code
Many people here have the misconception that CMD is lazy to load, but CMD is lazy to execute rather than lazy to load. Due to browser limitations, this syntax does not allow lazy loading.Copy the code

However, with the require.js update, the subsequent require.js implementation is also lazy and in modules that support CJS (pseudo support).

Specific on the HISTORY of AMD, CMD standards to archaeology students can see here: front-end modular development that point in history

UMD

With the increasing number of module standards, some class libraries have been troubled. In order to be compatible with various module systems, UMD specifications have emerged. The STANDARD UMD is mainly compatible with CJS, AMD and global variables.

(function (global, factory) {
    typeof exports= = ='object' && typeof module! = ='undefined'
        ? factory(exports.require('react'))
        : typeof define === 'function' && define.amd
        ? define(['exports'.'dep'], factory)
        : ((global = global || self), factory((global.module = {}), global.dep)); }) (this.function (exports, dep) {
    // code
});
Copy the code

Depending on the environment, you can adapt the library to various module systems.

Support for CMD can also be added to UMD, but it has been phased out due to the development of the front end.

ES Modules

With the update of ES standard, the native modular syntax ES Modules appears gradually.

Import supports two syntax types

Static declaration import ‘module’

The first is as a static declaration

b.js

import a from 'a';
const b = {};
export default b;
Copy the code

Statically declared import is similar to CJS require, but there are some differences:

  1. Imports must be declared at the top and not wrapped in blocks or functions
  2. Import imported references are Live Bindings (Live references), that is, when the original reference changes, the import reference will also change
  3. Import imports modules on demand, while require is all exported content in the entire import file

Live Bindings can be considered as the first occurrence of pass-by-reference in JS.

Live Bindings are implemented in Webpack (by merging modules or modifying references to module attributes), but when using Babel to escape code directly, import is equivalent to require, so it needs to be used with care, and if exported from lib, When used in other Libs, WebPack doesn't work eitherCopy the code

a.js

let v = 1;
export { v };

setInterval(() = > {
    console.log(`v updated`, ++v);
}, 1000);
Copy the code

main.js

import { v } from './a.js';

setInterval(() = > {
    console.log(v);
}, 1000);
Copy the code

Dynamic import import(‘module’)

Import also supports dynamic import of modules

dep1.js

console.log('dep1 ready to run');
export default {
    log: (. args) = > console.log('this is dep1'. args) };Copy the code

dep2.js

console.log('dep2 ready to run');
export default {
    log: (. args) = > console.log('this is dep2'. args) };Copy the code

main.js

(async() = > {const [dep1, dep2] = await Promise.all([import('./dep1.js'), import('./dep2.js')]);
    console.log(dep1, dep2); }) ();Copy the code

With await it is convenient to import modules asynchronously (really lazy loading and lazy execution), but overall speed will affect the speed more than pre-batch loading, of course it can be optimized with header import ahead. Note that the return value is a module object, not the value of export default (the return value here is similar to the value of require(‘module’)).

SystemJS

In addition to the module loader mentioned above, there is also SystemJS, which is not very common in China, but is still used as a recommended option for Angular2.

SystemJS module definition

System.register(['react'.'react-dom'].function (_export, _context) {
    'use strict';
    var React, ReactDOM;
    return {
        setters: [
            function (_react) {
                React = _react.default;
            },
            function (_reactDom) { ReactDOM = _reactDom.default; }].execute: function () {
            ReactDOM.render(React.createElement('button'.null.'A button created by React'), document.getElementById('react-root'));
            System.register([], function (_export, _context) {
                _export('foo', { name: 'foo'}); }); }}; });Copy the code

SystemJS can be implemented through a variety of plug-ins, AMD, UMD loading, and with the help of the runtime compiler, you can achieve ES Modules and CJS module directly loading, but the official is not recommended in production environment, will affect the page performance, but as a toy is still very convenient.

Module packaging

In addition to the module management system described above, there are many tools for modular management at compile time, such as Webpack.

webpack

CJS

Take a look at the result of webPack packing CJS code

The source code

cjs.js

const { v } = require('./cjsDep');

setInterval(() = > {
    console.log(v);
}, 1000);
Copy the code
let v = 1;

setInterval(() = > {
    v++;
}, 1000);

module.exports = { v };
Copy the code

Webpack after the module part of the code

webpackModuleFactory([
    function (e, t, n) {
        const { v: r } = n(1);
        setInterval(() = > {
            console.log(r);
        }, 1e3);
    },
    function (e, t) {
        let n = 1;
        setInterval(() = > {
            n++;
        }, 1e3),
            (e.exports = { v: n }); }]);Copy the code

Developed in source code using the syntax of CJS or ES Modules, webpack each module wrapped in function for isolation, and then with the module array (or possibly object) ID for module identification, when used to execute module functions to return module values

ES Modules

Take a look at the package from ES Modules code:

The source code

es.js

import { v } from './esDep';

setInterval(() = > {
    console.log(v);
}, 1000);
Copy the code

esDep.js

let v = 1;

setInterval(() = > {
    v++;
}, 1000);

export { v };
Copy the code

Webpack after the module part of the code

webpackModuleFactory({
    2: function (e, t, r) {
        'use strict';
        r.r(t);
        let n = 1;
        setInterval(() = > {
            n++;
        }, 1e3),
            setInterval(() = > {
                console.log(n);
            }, 1e3); }});Copy the code

To implement Live Bindings, WebPack packages DEP and ES together.

When DEP is shared by other entries, Webpack will package the output of DEP into modules and convert imported variables into attributes of module objects to implement Live Bindings.

webpackModuleFactory([
    function (e, t, r) {
        'use strict';
        r.r(t),
            r.d(t, 'v'.function () {
                return n;
            }),
            console.log(123);
        let n = 1;
        setInterval(() = > {
            n++;
        }, 1e3);
    },
    function (e, t, r) {
        'use strict';
        r.r(t);
        var n = r(0);
        setInterval(() = > {
            console.log(n.v);
        }, 1e3); }]);Copy the code

Webpack also supports dynamic import and require syntaxes, which load module files via webpackJsonp after subcontract.

The introduction of Module Federation in WebPack 5 also makes up for webPack’s original cross-project Module references. It is foreseeable that ModuleConcatenation will bring some changes to the gameplay of large front-end projects and front-end microservices.

conclusion

Although most developers now switch to the Webpack camp, some browser-side module loaders still have its use scenarios, such as module sharing between multiple projects, using some common CDN modules, etc., so require. Js, SystemJS and other modular management tools are still useful. And with the popularity of HTTP 2.0, module subcontracting is becoming more and more detailed. There may be other modularity solutions in the near future.

There are many other modularity solutions, such as AngularJS dependency injection, but I won’t mention them here because I’m not familiar with them.

The last

  • The demo code covered in this article is compiled here [

FE – module – examples] (github.com/ZxBing0066/…).

If it is useful, please like it. If it is wrong, please leave a comment