Seajs学习(7)- seajs.require() require书写约定
使用 sea.js 书写模块代码时,需要遵循一些简单规则。
只是书写和调试时的规范!!!构建后的代码完全不需要遵循下面的约定!!!!!!
1.require 书写约定
(1)模块 factory 构造方法的第一个参数必须命名为require 。
// 错误!
define(function(req) {
// ...
});
// 正确!
define(function(require) {
// ...
});
(2)不要重命名 require 函数,或在任何作用域中给 require 重新赋值。
// 错误 - 重命名 "require"!
var req = require, mod = req("./mod");
// 错误 - 重定义 "require"!
require = function() {};
// 错误 - 重定义 "require" 为函数参数!
function F(require) {}
// 错误 - 在内嵌作用域内重定义了 "require"!
function F() {
var require = function() {};
}
(3)require 的参数值必须是字符串直接量。
// 错误! require(myModule);
// 错误! require("my-" + "module");
// 错误! require("MY-MODULE".toLowerCase());
// 正确! require("my-module");
在书写模块代码时,必须遵循这些规则。其实只要把 require 看做是语法关键字 就好啦。
2.关于动态依赖
有时会希望可以使用 require 来进行条件加载:
if (todayIsWeekend)
require("play");
else
require("work");
但请牢记,从静态分析的角度来看,这个模块同时依赖 play 和 work 两个模块,加载器会把这两个模块文件都下载下来。 这种情况下,推荐使用 require.async 来进行条件加载。
3.Why?
这些约定初看起来会有些小不爽,其实也的确可以通过每次都编译的方式来去掉这些限制。但编译的方式,会给开发调试带来麻烦,代码的实现复杂度也会增加。sea.js 的核心设计原则是保持简单,遵循 New Jersey Approach:
简单性:设计必须简单,这既是对实现的要求,也是对接口的要求。实现的简单要比接口的简单更加重要。简单是设计中需要第一重视的因素。
因为简单,所以可靠!
附:参考文档 —— 为什么要用构建工具来压缩 CMD 模块?
在书写 CMD 模块时,需要遵守 require 书写约定 。
在压缩 CMD 模块时,推荐使用配套的构建工具来压缩。
为什么要这么做呢?
(1)CMD 模块的构建过程
CMD 模块在构建时,有两个基本操作:
第1个:提取操作,用来提取模块的标识 id 和依赖 dependencies。假设模块代码为:
a.js
define(function(require, exports) {
var b = require('./b');
})
经过提取操作后,a.js 的源码会转换成临时文件:
define('xxx/1.0.0/a', ['./b'], function(require, exports) {
var b = require('./b');
})
第2个:压缩操作。经过上面的提取操作后,构建工具就可以调用任何 JS 压缩工具来进行压缩了,require 参数也可以被压缩成任意字符。
可以看出,和普通压缩工具相比,CMD 模块的构建过程中增加了 id 和 dependencies 的提取操作。下面说明为什么需要预先提取这两个信息。
(2)为什么要提取 id
默认情况下,书写 CMD 模块时,不需要手写 id:
a.js
define(function(require, exports) {
...
});
b.js
define(function(require, exports) {
...
});
上面两个模块,如果直接合并,会变成:
a+b.js
define(function(require, exports) {
...
});
define(function(require, exports) {
...
});
这会导致无法区分 define 对应哪个模块。因此在合并前,我们需要通过工具将 id 提取出来。
a+b.js:
define('a', function(require, exports) {
...
});
define('b', function(require, exports) {
...
});
此外,即便不合并,保持一个文件一个模块,如果压缩时不提取 id,那么在 IE6-9 下也有可能会出现问题。这是实现上的困难,具体请看源码。如果要确保上线后在 IE 下没问题,请务必要手写或通过工具提取 id。
(3)为什么 require 要有书写约定
在开发时,Sea.js 是如何知道一个模块的具体依赖呢?
a.js
define(function(require, exports) {
var b = require('./b');
var c = require('./c');
});
Sea.js 在运行 define 时,接受 factory 参数,可以通过 factory.toString() 拿到源码,再通过正则匹配 require 的方式来得到依赖信息。依赖信息是一个数组,比如上面 a.js 的依赖数组是:['./b', './c']
由于 Sea.js 的这个实现原理,使得书写 CMD 模块代码时,必须遵守 require 书写约定,否则获取不到依赖数组,Sea.js 也就无法正确运行。
而且正则匹配取依赖的实现方案并非百分百可靠,除了 require 关键字被压缩的问题以外,对于一些极端情况无法保证正确性,特别对于压缩后的代码。有兴趣的可以看看社区里关于 require 正则提取依赖的有奖挑战。
(4)为什么要提取依赖数组 dependencies
为了保证压缩工具可以随意压缩代码,构建工具在提取 id 字符串时,同时也会提取 dependencies 数组。提取过后的代码变成:
define('xxx/1.0.0/a', ['./b', './c'], function(require, exports) {
var b = require('./b');
var c = require('./c');
});
这样,Sea.js 就不需要通过 factory.toString() 和正则匹配的方式来获取依赖,直接从第二个参数中就可以拿到依赖数组。
这意味着,提取过 id 和 dependencies 的模块代码,就可以用任何压缩工具压缩了。
(5)用普通压缩工具如何压缩 CMD 模块
由于各种原因,暂时无法使用 Sea.js 配套的构建工具来压缩时,需要注意以下几点:
如果项目需要支持 IE,请手写 id,即定义模块时,需要人肉写上第一个参数,比如:
define('a', function(require, exports) {
...
});
如果项目对性能有要求,上线后需要合并文件,也请确保手工写上 id 参数。
压缩时,不要压缩 require 参数,目前 UglifyJS 支持通过参数来指定保留名字:
$ uglifyjs --reserved-names require -o test-min.js test.js
或者自己写工具来保证 id 和 dependencies 的预先提取。
(6)小结
如果使用 Sea.js,强烈推荐采用配套的构建工具来压缩、合并代码。如果不这么做,可能会带来不少额外的工作甚至隐患。
这是一把双刃剑,目前业界还没有『完美』的处理方案,都会在某些地方存在取舍和权衡。
如果这方面你有好的想法,欢迎与我们交流。
原文地址:https://github.com/seajs/seajs/issues/259

浙公网安备 33010602011771号