如果你已经熟悉RequireJS的基本配置和使用,可以直接跳到30分钟上手RequireJS打包及优化

本机环境:

  • OS: Yosemite 10.10.2
  • Git: 1.9.5
  • Nodejs: 0.10.32
  • requirejs: 2.1.19

###为什么折腾RequireJS
在2015年8月份以前,我没有理解过什么是AMD,什么是CMD。我也从来没有想过有需要去了解。毕竟library这些东西,上手知道大概怎么用就好了。直到我因为想要调整工作项目框架中的Gruntfile。

它里面RequireJS配置的部分简直就是毒瘤一样的存在,所以激发了我要折腾RequireJS明白的欲望。

作为一个循规蹈矩的人,想要了解一个工具,当然是从官网开始了。

RequireJS is a JavaScript file and module loader. It is optimized for in-browser use, but it can be used in other JavaScript environments, like Rhino and Node. Using a modular script loader like RequireJS will improve the speed and quality of your code.

Yaaayyy!没有看明白!!!

加之所有需要细看的正文字体都好小,不想看怎么办……

看来只能召唤大谷歌了。

所以这篇将是大杂烩式的记录。

###文件夹结构
这次的初始文件夹结构如下,我事先把需要用到的JS文件都复制好了在assets/js/lib下。

1
2
3
4
5
6
7
8
9
10
11
dev
  ├── assets
  │   └── js
  │   ├── lib
  │   │   ├── backbone-1.1.2.min.js
  │   │   ├── jquery-1.11.2.min.js
  │   │   ├── requirejs-2.1.19.min.js
  │   │   └── underscore-1.8.3.min.js
  │   └── requirejsConfig.js
  ├── index-modern.html
  └── index-old-school.html

###瘦身前后快照对比
老式的JS文件引入方法,就是在HTML底部一个一个script地加,而且还一定要理好顺序,否则浏览器就要发你脾气。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<html>
<head>
<meta charset="UTF-8">
<title>Old School Way</title>
</head>
<body>
<p class="lineOne"></p>
<p class="lineTwo"></p>
<p class="lineThree"></p>
</body>
<!--要听妈妈的话,一个个排好队哦-->
<script src="assets/js/lib/jquery-1.11.2.min.js" charset="utf-8"></script>
<script src="assets/js/lib/underscore-1.8.3.min.js" charset="utf-8"></script>
<script src="assets/js/lib/backbone-1.1.2.min.js" charset="utf-8"></script>
<!--报告!都到齐了-->
<script type="text/javascript">
(function () {
$('.lineOne').text('This page is showing you the old school way of including js files');
$('.lineTwo').text('-- including them one after another behind the <body> tag.');
$('.lineThree').text('Check the HTML code!');
})()
</script>
</html>

然后我们来感受一下RequireJS的苗条。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<html>
<head>
<meta charset="UTF-8">
<title>Modern Way</title>
</head>
<body>
<p class="lineOne"></p>
<p class="lineTwo"></p>
<p class="lineThree"></p>
</body>
<!--要听妈妈的话,一个个排好队哦-->
<script data-main="assets/js/requirejsConfig" src="assets/js/lib/requirejs-2.1.19.min.js"></script>
<!--诶?!怎么只有你一个?-->
<script type="text/javascript">
(function () {
$('.lineOne').text('This page is showing you the MODERN way of including js files');
$('.lineTwo').text('-- using REQUIRE JS!!!.');
$('.lineThree').text('Check the HTML code!');
})()
</script>
</html>

看到这里,你一定会说,这不科学,单单引入RequireJS就可以?你是出来骗小学生的吧?想要达到根上面一样的效果,不引入要用的那些库,浏览器根本就是罢工好吗?

这就是即将要说明的:

data-main的魔法

RequireJS苗条版,除了只引入了RequireJS本身,最可疑的地方就是这个scriptdata-main属性了。assets/js/requirejsConfig看上去是个文件路径吧?说!里面写了些什么?

既然你这么机灵,没办法,只能坦白从宽了。

data-main指向的文件,其实可以看成是RequireJS的配置文件。加载完RequireJS以后,第一张会加载的script就是data-main所指。

requirejs.config

assets/js/requirejsConfig里面,使用requirejs.config方法对RequireJS进行配置。接下来会从简说明一下配置文件中常用的参数。

注意: requirejs.config中的文件路径均为相对于index.html的路径。

1
baseUrl: 'assets/js',

baseUrl是本config文件中用到的所有文件的路径base。所有文件最终形式都是baseUrl + path(接下来马上会说明)。

以jquery为例,如下配置以后RequireJS就会知道,jquery的路径为:‘assets/js/lib/jquery-1.11.2.min.js’。

1
2
3
4
5
6
7
8
9
10
11
12
13
paths: {
/* 路径中不需要添加文件后缀 */
jquery : 'lib/jquery-1.11.2.min',
underscore: 'lib/underscore-1.8.3.min',
backbone : 'lib/backbone-1.1.2.min',
requirejs : 'lib/requirejs-2.1.19.min'
backbone : [
/* 如果希望使用CDN地址,可以这样配置 */
'//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.3.3/underscore-min',
/* CDN加载失败时,会自动从下面这条路径加载文件 */
'backbone-1.1.2.min'
]
}

Tips:一个个找文件路径很麻烦?如果你的项目中有使用Bower,用bower --paths命令,就可以轻松列出所有包的路径了。

定义好paths以后,在使用define方法的时候,就可以直接使用paths对象里面的key,不需要写长到爆的JS文件名了。

define方法

在苗条版最下方,你会看到我还是用了老方法来执行jquery方法。下面我们一起来尝试RequireJS的define方法吧。

首先把苗条版最后的jquery方法提取出来,放到assets/js/tipText.js里面:

1
2
3
4
5
define(['jquery'], function ($) {
$('.lineOne').text('This page is showing you the MODERN way of including js files');
$('.lineTwo').text('-- using REQUIRE JS!!!.');
$('.lineThree').text('Check the HTML code!');
});

define方法接受的参数,第一个是需要依赖的模块,以数组形式传入。在接下来的function中,记得一定要对应数组顺序,给它传入参数。

比方说,我的依赖模块是jquery和underscore,function的参数就需要按顺序是$_

1
define(['jquery', 'underscore'], function ($, _) { ...... });

如果你非要给function先传_$,也不是不可以。只要你记得_是jquery,$是underscore就好了。

非要试一下是吧?没问题,直接尝试下面这样写,然后刷新页面。

1
2
3
define(['jquery', 'underscore'], function ($, _) {
$('.lineThree').text('Check the HTML code!');
});

……

相信了吧?

乖乖地按照顺序来吧。

但是如果要依赖的模块超级多,要一个一个写到数组里面然后在function里面对应加,那会烦到爆啊!

对!所以可以像下面这样写:

1
2
3
4
5
define(function (require) {
var $ = require('jquery');
var _ = require('underscore');
......
});

require方法

好了,既然我们把原有的jquery方法提取了出来,接着要怎么使用呢?

很简单,为了方便我们暂时直接在引用完RequireJS之后调用require方法吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html>
<head>
<meta charset="UTF-8">
<title>Modern Way</title>
</head>
<body>
<p class="lineOne"></p>
<p class="lineTwo"></p>
<p class="lineThree"></p>
</body>
<script data-main="assets/js/requirejsConfig" src="assets/js/lib/requirejs-2.1.19.min.js"></script>
<script type="text/javascript">
require(['tipText'], function (tipText) { });
</script>
</html>

由于我们的tipText文件目前只是对html做了一些简单处理,直接这样就可以了。刷新页面可以看到效果。

require方法接受的参数和参数规则与define方法一致。

1
require(['tipText'], function (tipText) { });

数组中依赖的模块,使用相对requirejsConfig.js的路径。试一下在dev下面新建testRequire.js文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
dev
├── assets
│   └── js
│   ├── lib
│   │   ├── backbone-1.1.2.min.js
│   │   ├── jquery-1.11.2.min.js
│   │   ├── requirejs-2.1.19.min.js
│   │   └── underscore-1.8.3.min.js
│   ├── requirejsConfig.js
│   └── tipText.js
├── index-modern.html
├── index-old-school.html
└── testRequire.js /* require方法引用模块路径测试文件 */

如果直接在require方法中这样引入,RequireJS是会报script error的。因为与requirejsConfig.js同级的文件中找不到testRequire.js

1
require(['tipText', 'testRequire'], function (tipText, testRequire) { });

改成下面这样页面就可以正常显示了。

1
require(['tipText', '../../testRequire'], function (tipText, testRequire) { });

###普通js也可以作为模块引入吗?
答案是,让人兴奋的,可以哟~

下面我们直接在js文件夹新建global.js文件,文件里面就这样定义一个简单对象。

1
2
3
var GLOBALVAR = {
amd: 'requirejs'
};

requireConfig.js里面,增加一个参数:

1
2
3
4
5
6
7
/* 非amd的js,可以通过shim object来设置。 */
/* 这样就可以作为依赖模块在require方法中使用。 */
shim: {
'global': {
exports: 'GLOBALVAR'
}
}

在require方法中:

1
2
3
4
require(['tipText', '../../testRequire', 'global'], function (tipText, testRequire, GLOBALVAR) {
/* 获取shim object中的GLOBALVAR */
console.log(GLOBALVAR);
});

基本原理总结

  1. 引入RequireJS, 在data-main属性中指明配置文件
  2. 使用define方法自定义模块。define时声明需要依赖的模块
  3. 通过require方法使用自定义的模块。RequireJS会知道自定义模块依赖了哪些模块并一起加载

如果你已经熟悉RequireJS的基本配置和使用,可以直接跳到30分钟上手RequireJS打包及优化