In the modern front end, modularity has become a necessity. There are not only libraries of various tool classes, but also written into the specification. Regardless of the size of the company, the code follows the idea of modularity in the development, it has become an experience and ability independent of technology.

In this article, we will talk about this topic.

demand

When the web first appeared, it was just a very simple document, simple style, very little interaction, very few design elements, a page didn’t depend on many files, very little logical code.

However, with the development of Web technology, Web pages become more and more rich, which is an absolute blessing for users, but for developers, the problem is gradually prominent.

  • More code, higher probability of naming conflicts
  • Code redundancy, too many requests slow down
  • As the dependency between files increases, reference errors are easy to occur, leading to errors in code operation
  • Hard to reuse, rewrite? A copy?
  • When you modify or change a version, you have to dig through hundreds of lines of code, which is difficult to maintain

To sum up, solutions are needed from both a development and an experience perspective.

The module

Countless examples have shown that small, well-organized code is far easier to understand and maintain than large code. It is therefore wiser to optimize the structure and organization of programs into small, loosely coupled pieces called modules.

Fortunately, JavaScript has a native way of organizing itself — functions.

function

What does the function do? Encapsulate code with certain functions, which can wrap all types of things inside, and have their own independent scope, and then call where needed. Such as:

function f1(){
  / /...
}
function f2(){
  / /...
}
function f3() {
  f1();
  f2();
}
Copy the code

This is functional and allows for clean organization and separation of code, but they are scattered throughout the file, which can pollute the global namespace and be costly to maintain. Continue exploring.

object

Objects can have properties, and properties can be data or methods, which is good enough to meet the requirements, and the properties of objects are accessed through objects, which is equivalent to setting up a namespace.

let myModule = {
  name: 'Joe',
  getName() {
    console.log(this.name); }}Copy the code

This solves the naming problem to some extent, but its properties are still exposed and can be changed externally. Such as:

myModule.name = 'bill';
myModule.getName() // 'Li Si'
Copy the code

Still not ideal.

Anonymous closure

This approach has been used in a number of projects to take advantage of closures’ proprietary data and shared methods, which we cover in closures.

The code is as follows:

//module.js
(function(window) {
  let name = 'idea'
  // A function that manipulates data
  function getName() {
    console.log(`${name}`)}// Method of exposure
  window.myModule = { getName } 
})(window)
Copy the code

At this point, mymodule.getname () can be used to getName, but not mymodule.name.

myModule.getName() // "idea"
myModule.name  // undefined
Copy the code

That looks good, solving both problems at once, but what if this module needs to depend on another module?

There is also a way, remember, anonymous function is also a function, can pass parameters ~

//module.js
(function(window,$) {
  let name = 'idea'
  // A function that manipulates data
  function getName() {
    console.log(`${name}`);
    $('body').css('color'.'red');
  }
  // Method of exposure
  window.myModule = { getName } 
})(window,jQuery)
Copy the code

Of course, jQuery here is another module defined in another place that can be used inside myModule in this way, and that’s where the idea for modern module implementations comes from, just in a different way.

Having said all that, there have been some changes and optimizations to the module implementation, but one thing remains the same: the way files are organized, like the code above, in a page looks like this:

<script type="text/javascript" SRC ="jquery-1.10.1.js"></script> <script type="text/javascript" SRC ="module.js"></script>  <script type="text/javascript"> myModule.getName() </script>Copy the code

We have addressed naming conflicts, data protection, and introducing dependencies, but not dependencies that are strongly related to file loading order, as well as too many requests.

So, better solutions are still needed to solve these problems.

Modular specification

The above are the results of developers using the characteristics of the language itself, and each has its own uses, but there are shortcomings.

Next, we introduce a few more widely used and desirable modularity specifications.

CommonJS

In the CommonJS specification, each file is a module and has its own scope. The variables, functions, and classes of the module are private and invisible.

// num.js
var a = 5;
var b = 3;
var add = function (a,b) {
  return a + b;
};
Copy the code

Now that your data is protected, what do you do with it? — Expose yourself.

module.exports.a = a;
module.exports.b = b;
module.exports.add = add;
Copy the code

Module. exports is the way to expose it.

Another file needs to be referenced like this:

//index.js
var num = require('./num.js');  //./ represents the relative path
console.log(num.a); / / 5
console.log(num.b); / / 5
console.log(num.add(a,b)); / / 8
Copy the code

The require command is responsible for reading and executing a JavaScript file and returning the exports object of the module. An error is reported if none is found.

CommonJS features can be summarized as follows:

  • All code runs inside the module and does not pollute globally.
  • Modules can be loaded multiple times, but only run once for the first time. The results are cached. Reloading directly reads the cache, and the cache must be cleared for the module to run again.
  • The order in which modules are loaded, in the order in which the code appears.

CommonJS looks just like a regular JS file. What’s the difference?

A brief analysis:

First, like many Web tools, it relies on Node, which provides a Module constructor inside, and all modules are instances of Modules.

function Module(id, parent) {
  this.id = id;
  this.exports = {};
  this.parent = parent;
  // ...
Copy the code

Inside each module, there is a Module object with the following common properties:

  • Module. id Identifier, usually a module file with an absolute path.
  • Module.parent returns an object representing the module that called the module.
  • Module. children returns an array representing other modules used by this module.
  • Module. exports indicates the value that the module exports.

In this way, it should be known that it is different from ordinary JS, but this difference is not perceptive, very friendly to development.

CommonJS does a good job of addressing all of these issues, but it is also synchronous. In Node server applications, modules tend to be native and load quickly, so synchronization is not a problem, so it is not suitable in browsers, so asynchronous modularization is needed.

AMD

AMD (Asynchronous Module Definition) is designed for browser environments and defines a set of asynchronous loading standards to address the issue of synchronization.

The syntax is as follows:

define(id, dependencies, factory);
Copy the code
  • Id is the name of the module. It is an optional string.
  • Dependencies are an optional list of dependencies.
  • Factory, which wraps around the implementation of a module, is a “function” or “object.” If it is a function, the return value is the module’s output interface or value.

Here’s an example:

There are module dependencies

/ / define
define('myModule'['jquery'].function($) {
    // $is the output of jquery module
    $('body').text('hello world');
});
/ / use
require(['myModule'].function(myModule) {});
Copy the code

Output module

define(['jquery'].function($) {
    var HelloWorld = function(selector){
        $(selector).text('hello world');
    };
    // HelloWorld is the output interface of this module
    return HelloWorld;
});
Copy the code

RequireJS

RequireJS is an AMD-compliant library for client-side module management. It defines code as a module through the define method; Through the require method, the implementation of code module loading, use to download and import the project.

// No dependency module
// msg.js
define(function() {
  let msg = 'www.baidu.com'
  function getMsg() {
    return msg.toUpperCase()
  }
  return { getMsg } // Expose the module
})

// There are dependency modules
// showMsg.js
define(['msg'].function(getMsg) {
  let name = 'idea'
  function showMsg() {
    alert(msg.getMsg() + name)
  }
  return { showMsg } // Expose the module
})
Copy the code

To use it, just look like this:

<! - the introduction ofrequire.js and specify the entry to the js main file --><script data-main="js/main" src="js/libs/require.js"></script>
Copy the code

CMD

The CMD specification stands on the shoulders of giants, integrating CommonJS and AMD specifications for asynchronous loading of browser modules.

Look directly at the code:

/ / no dependence
define(function(require, exports, module){
  exports.xxx = value  
  module.exports = value
})
Copy the code

The exports parameter is a reference to the Module. exports object. Providing an interface through the exports parameter alone sometimes does not meet all the needs of developers. For example, a module whose interface is an instance of a class needs to be implemented through module.exports.

/ / a dependency
define(function(require, exports, module){
  // Synchronous import
  var module = require('./module')
  // Async import
  require.async('./module'.function (module) {})// The condition is introduced
  if (status) {
      var x = requie('./x');
  }
  // Expose the module
  exports.xxx = value
})
Copy the code

By contrast, the CMD specification advocates a single code responsibility and no global require, which appears to have lighter headers and requires modules to be brought in nearby.

SeaJS

The epitome of the CMD specification is SeaJS, which my previous group used as a module loader. Its use is similar to RequireJS, requiring downloading and importing pages.

<script type="text/javascript" src="js/libs/sea.js"></script>
<script type="text/javascript">
  seajs.use('./js/modules/main')
</script>
Copy the code

With these scenarios in mind, it’s time to introduce the native implementation.

ES6 module

In order to provide these capabilities, ES6 introduces two keywords.

  • Export — Sets the external interface of the module.
  • Import – Imports functionality from other modules.

It is designed to be as static as possible so that module dependencies and input and output variables can be determined at compile time.

/** math.js **/
var basic = 0;
var add = function (a, b) {
    return a + b;
};
export { basic, add };
/** references the module **/
import { basic, add } from './math';
function test(ele) {
    ele.textContent = add(66 + basic);
}
Copy the code

Both imports and exports can be individual or collections, but only collections are listed here.

Beyond that, it offers something different:

  • as
// name.js
let myName = "idea";
export { myName as exportName }  // Alias export

import { exportName as name } from "./name.js";  // Alias import
console.log(name);  // idea
Copy the code

It provides the ability to change the name of a module’s export or import interface, but the names must be one-to-one.

  • export default
// xxx.js
var a = "My name is idea!";
export default a; // There can only be one

// Can be accepted using any variable
import b from "./xxx.js"; 
Copy the code

This is called a default export, which means it doesn’t matter what the default is, similar to the default task in the build tool, just run.

While it may seem convenient, it can cause some potential problems that are beyond the scope of this article, so you can use them in the best way possible

The above two methods are not just needed, depending on their coding preferences and needs to use.

ES6 modules are similar to CommonJS in form, regardless of syntax, so what’s the difference?

  • The CommonJS module prints a copy of the value, the ES6 module prints a reference to the value. A value change in CommonJS has no effect on modules that reference it. ES6 modules still have an effect.

  • The CommonJS module is loaded at run time, it loads an object, it is generated at run time; ES6 modules are compile-time output interfaces that are generated during the static parsing phase of code.

  • The operating mechanism is different. ES6 modules are dynamic references and do not cache values. Module variables are bound to the module in which they reside.

In any case, modularity has been added to the specification and seems to be simpler and more intuitive, and you can see it in many places. First, you don’t need to use third-party tools anymore, and second, there are tools like Babel to help you use them even if browsers don’t support them yet. If conditions permit, it can still be used happily

conclusion

The content is already long, and many things are still not very detailed, but I believe you can get a general understanding of the development of modularity, and can feel its charm.

By rights, or work for any kind of tool, we don’t have to know about it, just need to know which is best and how to use the present, is ok, but they produce on the background and development history can make us more targeted, in no one to make choices for us, or have a problem, can have their own judgment, and the solution.

Talk here, not guarantee completely correct, welcome to exchange, see you next! ~

Reference:

Front-end modular detailed explanation of AMD specification “JavaScript Ninja Secrets”, etc

Blog link: The history of JavaScript modularization

Looking for a job, base shenzhen, seeking to hook up ~