js前端的自动构建工具

博主是14年入的程序员大军,当时主java兼具前端开发的活儿,在现在看来的一些流开发框架和新兴思想,早在node.js开始进入大家视野的时候就流行起来了,只是在那时博主并没有关注前端的生态圈(然而java好像也并没有关注,逃),所以还是处在很多人所描述的刀耕火种的阶段,前端代码全部挂载到全局作用域,包括插件导出的变量。那更别提组件化和模块化的编程思想了,甚至代码都不用压缩优化就直接上传到服务器发布了。

后来换了一家公司,没有前端开发这个职位,是从javaer转过去的,因为项目需要,渐渐的也就坐实了这个岗位。项目到现在(2014年8月-2017年7月22日)一共出现了三个阶段

        用着十年前的开发(或者叫整合)技术的简陋期
        经历4、5个月的半模块化改造的准现代期
        到现在能整合全局资源(仅限web静态资源),随意整合新技术的现代期(未实施)

为什么要不断的去折腾,去改造?仅仅是为了跟上“现代”的步伐吗?下面我将讲述每个阶段是如何无痛改造的,为什么要改造。
从简陋期到准现代期

举个例子,我们以前的代码是这样的
html页面部分
    

<html>
<head>
<link href="style.css" rel="stylesheet"/>
</head>
<body>
<!-- 通用的代码 -->
<script src="common.js"></script>
<!-- 第三方的插件代码 -->
<script src="plugin.js"></script>
<!-- 我们的主入口 -->
<script src="individual.js"></script>
</body>
</html>

javascript部分

在common.js里,是我们的定义的通用函数,比如一些特定组件的部分代码如header或footer,或者是字符串处理,日期格式化的函数等等,这些函数都以对象或函数的形式暴露在全局作用域里,非常的冗杂和不安全,随着代码量的增加,容易导致覆盖,出现难以预料的bug,还有一个致命的弱点就是无法按需加载资源,我哪怕只是用到了其中一个小小的常量,都需要引用整个文件,然后从全局作用域里拿。


// common.js
var Header = {
  var1: '',
  var2: {},
  fn1: function() {
    // some code
  },
  fn2: function() {
    // some code
  }
}
function strReplace() {
  // some code
}
...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

    

// individual.js
// 也许我们早已有觉悟 使用了自执行匿名函数来防止全局变量的污染
(function() {
  // 这里我们需要用到commonjs的函数 常量等
  var afterHandleStr = strReplace(str);
  // 也许我们忘记strReplace函数已存在全局作用域又或者换了一个人
  // 来维护这个文件可能又会定义一个函数叫strReplace
  function strReplace() {
    // 那么此时根据javascript特性,原先的函数已经被覆盖了,
    // 上面的调用逻辑优先从最近的作用域开始找,于是会执行到这里
  }
  ...
}());

因为项目是迭代开发的,功能一点点叠加上去,考虑到整个项目的生命周期,不至于到后期完全无法维护,所以我们必须以优雅的姿态去重构整个项目的资源引用方式,那就是模块化,一个模块做一件事情,并暴露它对外提供的接口以供具象化的页面来使用。比如header,footer,nav,sidebar,utils等等。前端的模块化有俩个标准,一个AMD(Asynchronous Module Definition),一个是CMD(Common Module Definition),前者是异步模块定义,推崇依赖前置,后者是通用模块定义,推崇依赖就近,AMD的代表框架有requirejs,CMD的代表框架有seajs,都是很优秀的作品,这里对二者有详细的介绍。最后我选择了requirejs作为本次重构的基础,其实就当是的代码来说,改造起来并没有什么难度,就是需要细心,细心,细心,只需要将common.js这个通用模块进行拆分就好了,页面只需要引入一个js文件,如下面这样  

<html>
<head>
<link href="style.css" rel="stylesheet"/>
</head>
<body>
...
<script type="text/javascript" data-main="/individual.js" src="/require.js"></script>
</body>
</html>

data-main是我们的代码主入口,src是requireJs的源码。从文件引用来说,至少我们不必再关心每次使用一个插件都要手动来加入一个script标签了,如何引用呢?我下面会介绍。
假如我们以前的代码是这样的


    

// common.js
(function(){
  var exportObj = {
    aa: 'aa',
    bb: 'bb'
    ...
  }
  var utils = {
    replaceStr: function() {
    }
    ...
  }
  // 放到全局作用域
  window.exportObj = exportObj;
  window.utils = utils;
}());
// individual。js
(function(){
  var aa = constants.aa;
  var bb = constants.bb;
 
  var tempStr = utils.replaceStr('tempStr');
}());

上面的代码使用了两个全局对象,constants和utils,那么改造后应该是:



// constants.js
// 如果它不依赖于其他模块,就不必声明依赖的数组
define( function() {
  var exportObj = {
    aa: 'aa',
    bb: 'bb'
    ...
  }
  // 返回我们要暴露出来的对象,不用再放到全局作用域
  return exportObj;
} );
// utils.js
define( function() {
  // 返回我们要暴露出来的对象,不用再放到全局作用域
  return  {
    replaceStr: function() {
    }
    ...
  };
} );
// individual
define( [
  'constants',
  'utils'
], function( consts, utils ) {
  var aa = consts.aa;
  var bb = consts.bb;
 
  var tempStr = utils.replaceStr('tempStr');
} );
// 或者
define( [
  'constants',
  'utils'
], function() {
  var consts = require('constants');
  var utils = require('utils');
  var aa = consts.aa;
  var bb = consts.bb;
 
  var tempStr = utils.replaceStr('tempStr');
} );

是不是感觉毫无挑战性,对,这就是一个体力活,细心点就好了

    我们不必担心还需要手动去改动第三方插件,现在的主流插件基本都会UMD方式去适配,也就是兼容了AMD,CMD,所以只需要直接引用第三方插件就行了,不必再去html文件里手动引用script标签了,其他具体实现细节和必备的配置可以参照requirejs官网的例子

等到改造完,也还没有愉快的结束,我们的准现代期增加了一个优化环节,官方提供了r.js这个优化器来帮我们打包压缩代码(毕竟生产环境过多的请求数还是不被允许的),此时的改造才真的做到了模块化,能优化,从简陋期无痛过渡到准现代期。此时的代码,其实已经具备了进入现代期的要求,那就是规范模块化。下面是我们即将进行的改造,顺利过渡到现代期,从而拥抱你想使用的新技术

有话要说