Javascript modular development, the tragic history of the front end.

Foreword

 
Javascript module evolution to some extent represents the history of the development of the front end. From the early 对象字面量, IIFEto the later commonjs, AMDand so on, to today's ES Module. These modular solutions innovation in Internet technology development needs, evolution.

This article describes four stages JS modular development, designed to let everyone know how JS is a modular development today, each module in the program was also to solve the problem.

Cognitive Revolution

 
1

The purpose of early birth of Javascript for client authentication forms, improve the user experience. Standing a solution perspective today to look back, in that no style, no interaction, simple no longer a simple Web page, where JS is difficult to think of a modular significance.

If you have reached a certain degree of code reuse, 对象字面量fully meet the needs of the early Internet Web.
 

//Person.js
var Person = {
    say: function(words) {
        //code
    },
    run: function() {
        //code
    }
};

Person.say('say somthing');
Person.run();

History will progress, Web pages on the Internet more and more diverse, and good people will continue to adjust in a modular fashion according to changing needs.
 
When the team cooperate with each other to complete a particular project, 对象字面量shortcomings glance. Naming conflicts, the scope of isolation and other issues will inevitably just a matter of time early and late can occur.
 
javascript function, the owner of a natural local scope, access to the outside world is less than the internal function scope. Naturally transition to IIFEmodular.
 


(function(global){
    var Person = global.Person || {};
    var pritiveFn = function(){
        //other code
    };
    var pritiveName = 'Tom';
    Person.say = function(words) {
        pritiveFn();
        console.log( pritiveName + 'say: ' + words);
        //other code
    }
    Person.run = function() {
        pritiveFn();
        //other code
    }
})(window);

Person.say();
Person.run();

This model can not be accessed outside the definition of any local variable, it will not pollute the global scope, while some of the access to the global variables. Namespace by parameter passing, modules can be hung on to the global namespace Person.

IIEFThe modular approach to gene *** already front-end development. Even today, in our daily development, could be seen or used in this way.

Agricultural Revolution

The arrival of the era of Web2.0, web applications more emphasis on two-way users interact with services, front-end development has gradually assume more responsibility. A Web site, there may be hundreds of pages, and, javascrpt not limited to the client.

commonjs

推崇 commonjs 模块化规范的 Nodejs ,将模块化推向了一个新的高度。

// path/ModuleA.js
var ModuleA = function(){
    //code
}
module.exports = ModuleA;

//-------------------------

// path/ModuleB.js
var ModuleB = function(){
   //code
}

module.exports = ModuleB;

//------------------------

// path/index.js
var ModuleA = require('./path/ModuleA');
var ModuleB = require('./path/ModuleB');

ModuleA();
ModuleB();

commonjs规范提供 module.exports(或者 exports)接口用于对外暴露模块。require加载模块。
仔细想想,日常开发中我们理所应当只关心模块的自由导出和加载。而加载速度、依赖顺序、作用域隔离等问题应该交给框架或者其他科学技术来系统解决,让我们无感知。
但,nodejs 毕竟是运行在服务端的 javascript。
nodejs 中每个文件具有独立的作用域,所以每个文件可认为是一个模块。除非你显示的定义在全局 global 对象上,否则其他文件是访问不到该作用域的定义的任何数据。
在 nodejs 中,一个 js 文件拥有访问其他模块(文件)能力,这就很好的解决模块间相互依赖的问题。并且所有文件都是在服务器本地加载,速度极快。
但浏览器客户端的现状是残酷的。看下面例子,如果某个页面依赖Slider, Dialog, Tab模块,而这三个模块又有一些自身的依赖。

<!-- 模块自身的依赖 -->
<script src="./util/Animation.js"></script>
<script src="./util/Mask.js"></script>

<!-- 模块依赖 -->
<script src="./Slider/index.js"></script>
<script src="./Dialog/index.js"></script>
<script src="./Tab/index.js"></script>

<script>
    Slider();
    Dialog();
    Tab();
</script>

上面的例子可以看出:

  1. 全局作用域被污染
  2. 开发人员必须手动解决模块依赖关系(顺序)。
  3. 同步远程加载过多的文件,也会造成严重的页面性能问题。
  4. 在大型,多人合作项目中,会导致整体架构混乱。
    而通过工具browserify,可将commonjs规范移植到浏览器端,本质上。browserify 是将所有被依赖commonjs的模块,打包到当前业务代码中。
    AMD

    浏览器中的 js,本身并无加载其他文件(模块)的接口。聪明的人们用动态创建 script 节点实现了动态加载模块。
    AMD, 异步模块定义,采用的是异步加载模块方式。依赖模块是异步加载,不会阻塞页面的渲染。
    AMD规范中最核心的接口是definerequire,顾名思义:定义和加载模块。
    其中以requirejs代表,是AMD规范的实现。

// 定义模块
define(['path/util/Animation'], function(Animation){
    // Slider code
    return Slider;
});

// 加载执行模块
require(['path/Slider'], function(Slider){
    Slider();
})

可以看出,接口的第一个参数,代表模块的依赖路径。模块或业务的代码,放在 callback 中,其中 callback 参数提供暴露出了各依赖模块的接口。

UMD

此时,模块规范分成了commonjsAMD两大阵营。天下大势分久必合,需要一种解决方案同时兼容这两种规范。而UMD规范的诞生就是解决该问题。

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD 规范
        define([], factory);
    } else if (typeof exports === 'object') {
        // commonjs 规范
        module.exports = factory();
    } else {
        // 挂载到全局
        root.globalVar = factory();
  }
}(this, function () {
    return {};
}));

从上面可以看出 UMD 是通过判断运行环境中是否存在各模块化的接口来实现的。

ES2015 Module

不管是 commonjs, AMD, UMD,都是毕竟是为了弥补 javascript 模块缺陷而衍生出的民间解决方案。2015年es6的发布,让javascript终于在语言层面上实现了模块化。
 

// path/ModuleA.js
var ModuleA = function(){
    //code
}
exports default ModuleA;

//-------------------------

// path/ModuleB.js
var ModuleB = function(){
   //code
}

exports default ModuleB;

//------------------------

// path/index.js
import ModuleA from './path/ModuleA';
import ModuleB from './path/ModuleB';

ModuleA();
ModuleB();

commonjs 已经发展很成熟,也能满足日常需求。初略看,es module “就像是语法糖”,我们为何还要去使用它呢,换句话说,我们是用它能为我们带来哪些收益?
不管是 commonjs, AMD,他们的模块架构是 “动态架构”,换句话说,模块依赖是在程序运行时才能确定。而es module“静态架构”,也就是模块依赖在代码编译时就获取到。所以在 commonjs 里能进行 “动态引入” 模块。

if ( Math.random() > 0.5 ) {
    require('./ModuleA');
} else {
    require('./ModuleB');
}

而在 es module 中是无法进行类似操作的。从这个角度来看,es6 module 灵活性还不如 commonjs。但事物具有两面性。es6 module 其实能为我们带来以下几个收益。
tree shaking
在我们部署项目时,常常需要将各个模块文件打包成单个文件,以便浏览器一次性加载所有模块,减少 reqeust 数量。因为在 HTTP/1 中,浏览器 request 并发数量有限制。不过随之带来的问题是,多个模块打包成单文件,会造成文件 size 过大。
如果我们能在编译期时确定好模块依赖,就可以消除没有用到的模块,以便达到一定程度的优化,来看看下面例子。

// moduleA.js
export function moduleX(){
    //some code
}

export function moduleY(){
     //some code
}

// index.js
import { moduleX,  moduleY } from './moduleA';

moduleX();

通过工具 Rollup, 可将 index.js 打包成如下代码:

'use strict';

function moduleX(){
    //some code
}

moduleX();

可以看出,打包的代码只包含 moduleX,最大限度的减少了打包文件 size,这就是所谓的 'tree shaking', 读者可以好好品味下这个词,很传神。
模块变量静态检查
es6 module由于是“静态架构”,在编译时就能确定模块的依赖树以及确保模块一定是被正确的 import/export ,这就为项目质量带来很大的保障。看下面例子:

// module1.mjs
export function moduleX(){
    console.log(1);
}

// index.mjs
// 注意:module1.mjs 中并没有 export 出 moduleY
import { moduleX, moduleY } from './module1.mjs';

moduleX();

//注意 
let randomNum = Math.random();
if (randomNum) > 0.3 && randomNum < 0.4 ) {
    moduleY();
}

 

如果没有静态检查,在上面代码中的条件判断得出,代码运行期间,执行 moduleY() 函数报错的概率是10%,这种风险在线上环境就是一个非常大的隐患,一旦命中条件判断,你一整年的绩效可能就都没了。
那如果有编译期间静态检查,会是怎样的结果?
运行 node --experimental-modules index.mjs 命令时,控制台会报错:

import { moduleX, moduleY } from './module1.mjs';
                  ^^^^^^^
SyntaxError: The requested module does not provide an export named 'moduleY'
    at ModuleJob._instantiate (internal/loader/ModuleJob.js:88:21)
    at <anonymous>

Static control is useful for checking the quality of the project this compilation.
But es6 module sometimes makes me very sad. Because it is "flexible", it brought me trouble.
Take a look at import syntax:
Javascript modular development, the tragic history of the front end.
Look at the grammar export
Javascript modular development, the tragic history of the front end.
turnover, in fact, I wanted a simple import / export only, "even more than less" .
Agriculture is a major revolution in the history of progress in front-end, modular solution as well as a variety of community de facto standard, on the other hand also contributed to the modular Javascript to be supported from the language level. This large-scale infrastructure projects for us to ensure the quality of projects provided the opportunity.

Three: Industrial Revolution

Module compatibility problems and duplication of effort should be given to tools to do it, we should leave more time to enjoy the "good life." Therefore, the emergence of a large number of peripheral modules and modular tool management tool. As Browserify , r.js , Webpack , Rollup , JSPM , npm , the Yarn and so on.
Tools greatly improves the efficiency of our work, we also have more choices for modular.
Happy but also bring a lot of pain, because the alternative is too many tools, configuration too much, so you caught them can not extricate themselves. Either busy writing bug, or busy writing configuration.

Epilogue

Scientific Revolution era, yet to come. Perhaps by then, the use of modular like var m = 1; the same syntax, it should have in our minds is a matter of course there is, and not need to rely on others to build, run and other tools.

Guess you like

Origin blog.51cto.com/14622575/2452869