CMJ (CommonJS specification)

When it comes to CMJ, we can easily think of Node.js because the modularity specification used by Node is CMJ, which is a modularity specification applicable to servers. CMJ has several main methods: Module, exports, require, global. In development we use module.exports to export modules and require to load modules. Let’s look at a simple use case: math.js

// In math.js, we define a variable and a function and expose it
let num = 0;
function add(a, b) {
  return a + b;
}
module.exports = { // Expose functions and variables here
  add: add,
  num: num
}
Copy the code

index.js

// We use the require method here to reference math.js
let math = require('./math');
// We can then call the add method in Math.
math.add(4.9);
Copy the code

Note that when referring to a custom module, the parameter contains the path and can be omitted. Js, but when referring to a core module, the path is not required. Because CommonJS loads modules synchronously. When we load modules synchronously in the browser, the user cannot continue to operate the web page, so the front end needs a modular specification that can load modules asynchronously.

The file is a module, private. Module require (exports = module.exports)

One import and one export constitute the basic structure of communication

Two things to pay attention to

  1. Cache. Require will cache a little bit, so
// a.js
var name = 'morrain'
var age = 18
exports.name = name
exports.getAge = function(){
    return age
}
// b.js
var a = require('a.js')
console.log(a.name) // 'morrain'
a.name = 'rename'
var b = require('a.js')
console.log(b.name) // 'rename'
Copy the code
  1. Reference copy versus value copy
// a.js
var name = 'morrain'
var age = 18
exports.name = name
exports.age = age
exports.setAge = function(a){
    age = a
}
// b.js
var a = require('a.js')
console.log(a.age) / / 18
a.setAge(19)
console.log(a.age) / / 18
Copy the code
  1. Run time load/compile time load (multi-stage, asynchronous) ESM

AMD

AMD is a modular specification for asynchronously loading modules. The AMD specification defines a function define that defines a module through the define method

define(id? , dependencies? , factory);Copy the code
  • Id: Optional parameter used to define the module id. If this parameter is not provided, the module id takes the script name (with the extension removed).
  • Dependencies: An optional parameter to pass in an array of module names that the current module depends on.
  • Factory: The factory method in which the module initializes the function or object to be executed, if it is a function, it is executed only once. If it is an object, this object should be the output value of the module. Examples of defining modules:
define('a'.function () {
  console.log('a load')
  return {
    run: function () { console.log('a run')}}})Copy the code

In require.js there are require.config and require.

require.config({
    paths : {
        "jquery" : ["http://libs.baidu.com/jquery/2.0.3/jquery"]}})// We can use jquery to import the CDN address directly
require(["jquery"].function($){$(function(){
        alert("load finished"); })})Copy the code

Dependency preloading, in other words, before parsing and executing the current module, the module author must specify the module on which the current module depends.

Once the code runs there, it immediately knows about dependencies. Without having to traverse the function body find it, so the performance improved, the downside is that the developer must explicitly specify rely on – it makes the development work quantity is big, such as: when you write about when the function body internal hundreds of thousands of, need to add a dependency, suddenly found that you have to return to the top of the function to add the dependence into an array.

define('a'.function () {
  console.log('a load')
  return {
    run: function () { console.log('a run') }
  }
})

define('b'.function () {
  console.log('b load')
  return {
    run: function () { console.log('b run')}}})require(['a'.'b'].function (a, b) {
  console.log('main run') 
  a.run()
  b.run()
})

// a load
// b load
// main run
// a run
// b run
// ADM loads all modules when require, regardless of whether you use it or not. This is called dependency preloading.
Copy the code

Simple implementation

const def = new Map(a);// AMD mini impl
const defaultOptions = {
  paths: ' '
}

// From CDN loading CDN (System loading library)
const __import = (url) = > {
  return new Promise((resove, reject) = > {
    System.import(url).then(resove, reject)
  })
}

// Normal script read path
const __load = (url) = > {
  return new Promise((resolve, reject) = > {
    const head = document.getElementsByTagName('head') [0];
    const node = document.createElement('script');
    node.type = 'text/javascript';
    node.src = url;
    node.async = true;
    node.onload = resolve;
    node.onerror = reject;
    head.appendChild(node)
  })
}

// let const var
// Do not use this low method in practice 🔥
rj = {};

rj.config = (options) = > Object.assign(defaultOptions, options);

// Define module, trigger time is actually when require, so -> collect
define = (name, deps, factory) = > {
  // Todo argument, swap
  def.set(name, { name, deps, factory });
}

// dep -> a -> a.js -> 'http:xxxx/xx/xx/a.js';
const __getUrl = (dep) = > {
  const p = location.pathname;
  return p.slice(0, p.lastIndexOf('/')) + '/' + dep + '.js';
}

// This is where the loading dependency is triggered
require = (deps, factory) = > {
  return new Promise((resolve, reject) = > {
    Promise.all(deps.map(dep= > {
      / / the CDN
      if (defaultOptions.paths[dep]) return __import(defaultOptions.paths[dep]);

      return __load(__getUrl(dep)).then(() = > {
        const { deps, factory } = def.get(dep);
        if (deps.length === 0) return factory(null);
        return require(deps, factory)
      })
    })).then(resolve, reject)
  })
  .then(instances= >factory(... instances)) }Copy the code

CMD

CMD example of implementing Seajs on behalf of the technology:

// the require argument is used to import other modules, exports and modules are used to export module public interfaces.
define('a'.function (require.exports.module) {
  console.log('a load')
  exports.run = function () { console.log('a run') }
})

define('b'.function (require.exports.module) {
  console.log('b load')
  exports.run = function () { console.log('b run') }
})

define('main'.function (require.exports.module) {
  console.log('main run')
  var a = require('a')
  a.run()
  var b = require('b')
  b.run()
})

seajs.use('main')
Copy the code

We can see that our dependencies are introduced as needed with require. That’s the dependency post-position. And we need to call the seajs.use() method to execute the module.

Simple implementation

const modules = {};
const exports = {};
sj = {};

const toUrl = (dep) = > {
  const p = location.pathname;
  return p.slice(0, p.lastIndexOf('/')) + '/' + dep + '.js';
}

const getDepsFromFn = (fn) = > {
  let matches = [];
  // require('a ')
  / / 1. (? :require\() -> require( -> (? :) non-trapping grouping
  / / 2. (? :['"]) -> require('
  //3. ([^'"]+) -> a -> Avoid backtracking -> Backtracking state machine
  let reg = / (? :require\()(? :['"])([^'"]+)/g; // todo
  let r = null;
  while((r = reg.exec(fn.toString())) ! = =null) {
    reg.lastIndex
    matches.push(r[1])}return matches
}

const __load = (url) = > {
  return new Promise((resolve, reject) = > {
    const head = document.getElementsByTagName('head') [0];
    const node = document.createElement('script');
    node.type = 'text/javascript';
    node.src = url;
    node.async = true;
    node.onload = resolve;
    node.onerror = reject;
    head.appendChild(node)
  })
}

// What about dependencies?
// Extract dependencies: 1. Regular expressions 2. State machines
define = (id, factory) = > {
  const url = toUrl(id);
  const deps = getDepsFromFn(factory);
  if(! modules[id]) { modules[id] = { url, id, factory, deps } } }const __exports = (id) = > exports[id] || (exports[id] = {});
const __module = this;
// This is where the module is loaded
const __require = (id) = > {
  return __load(toUrl(id)).then(() = > {
    // After loading
    const { factory, deps } = modules[id];
    if(! deps || deps.length ===0) {
      factory(__require, __exports(id), __module);
      return __exports(id);
    }

    return sj.use(deps, factory);
  })
}

sj.use = (mods, callback) = > {
  mods = Array.isArray(mods) ? mods : [mods];
  return new Promise((resolve, reject) = > {
    Promise.all(mods.map(mod= > {
      return __load(toUrl(mod)).then(() = > {
        const { factory } = modules[mod];
        return factory(__require, __exports(mod), __module)
      })
    })).then(resolve, reject)
  }).then(instances= >callback && callback(... instances)) }Copy the code

supplement

Sometimes recursive calls can feel very convoluted, so we can try to break it down and assume that it is a single procedure at its most basic

Assume that module A depends on module B, and module B does not

Let’s simplify the model

Assume that only module B is loaded

Define B module

define('B'.function () {
    console.log('B load')
    return {
        run: function () { console.log('B run')}}})Copy the code

Introduce module B

require(["B"].function(B){
    B.run()
})
Copy the code

implementation

require = (deps, factory) = > {
    return new Promise((resolve, reject) = > {
       // Loop over dependencies
      Promise.all(deps.map(dep= > {
        / / the CDN
        if (defaultOptions.paths[dep]) return __import(defaultOptions.paths[dep]);
        // Local reference
        return __load(__getUrl(dep)).then(() = > {
          const { deps, factory } = def.get(dep); // Get the B module stored on define
          return factory(null); // Execute module B to return the result of module B
        })
      })).then(resolve, reject) // Return the result of the ↑ execution back to the value of resolve as promise. all is the array of factory range values after execution
    })
    .then(instances= >factory(... instances))// Execute require's callback factory
  }
Copy the code

When module A references module B

Define module A

define('A'['B'].function (B) {
    console.log('A load')
    return {
        run: function () { B.run() }
    }
})
Copy the code

When module A is introduced, it is necessary to detect the modules that module A needs to rely on. When each module is assumed to have its own dependency, this process is like A tree graph, which constantly calls its own require, and each tree node loads its own node branch modules, meeting the conditions of recursive invocation.

Recursive calls require an execution condition, which is whether the module needs a dependency

return __load(__getUrl(dep)).then(() = > {
const { deps, factory } = def.get(dep); // Remove the module to check whether it needs dependencies
    if (deps.length === 0) return factory(null); // when the length part is 0
    return require(deps, factory) // Otherwise load its dependencies
})
Copy the code