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

posted @ 2014-12-02 22:45  South Wind  Views(679)  Comments(0)    收藏  举报