目录

CommonJS

JS模块化需要解决两个问题:

  • 使用何种模块规范对代码进行组织。
  • 如何将分散的模块有机地结合起来,使浏览器接受所使用的模块规范。

在浏览器执行JS时,需要解决命名冲突的问题。这通常是通过遵循一定的规范来实现的,譬如CommonJSAMD。 这两种规范的实现代表分别有Node.jsrequirejs。 考虑到服务器端大量运行Node.js,为了使浏览器中运行的代码规范与其保持一致,reduce-js默认支持的是CommonJS规范。

Node.js中,通过require函数和module对象来实现模块划分、组织。

// salute.js

// module.exports是暴露给外面的接口
module.exports = 'Hello, '
// world.js

// require的返回结果是对应的module.exports
var salute = require('./salute.js')
module.exports = salute + 'world!'

可见,require是依赖声明,module是接口输出。 更多内容请参考Node.js#modules

模块打包

在服务器端,所有的模块都已经在本机上,直接可读取,因此require通过文件系统读取内容直接解析。 但在浏览器端,除了实现requiremodule外,还需要考虑模块下载的问题。

假定某个页面的初始化脚本如下:

// 入口模块

// 功能一
require('./ga')

// 功能二
require('./dialog')

// 更多入口

从入口模块出发,分析依赖,可得一棵依赖关系树。 只要将整棵树中各节点对应的模块均下载下来,那么入口模块便可以被顺利执行。 所以,上面的初始化脚本便确定了该页面所需要的模块集合,将其封装成一个包下载即可。 这个打包问题便是webpackbrowserify解决的基本问题。

CSS规范

对于CSS而言,模块化还需要解决的主要问题有:

  • 如何声明依赖
  • 如何避免命名冲突

命名冲突的解决方案请参见CSS命名空间

由于CSS中可以使用自定义的At-rule,因此Reduce支持在CSS通过@external "deps.css";来声明依赖。

/* a.css */

/* 声明a.css依赖b.css */
@external "./b.css";

a {}

“依赖”表示两个模块在最终打包生成的CSS中的先后顺序。 在上面的例子中,Reduce会确保最终b.css的内容会出现在a.css前。

CSS中的`@import`可以引入另一个CSS的内容,即直接在当前位置插入目标文件的内容。 这与`@external`的含义是有区别的。

通常,在一些预编译工具中,@import可以引入一些辅助模块,如变量定义、mixins等。 这些模块本身不产生任何具体的CSS内容,但预处理生成CSS时需要它们。

下面是一个使用@import的例子。

/* a.css */
@import "./vars.css";
a {
  color: $red;
}
/* vars.css */
$red: #ff0000;
/* 打包结果 */
a {
  color: #ff0000;
}

如果上面的例子使用@external替代@import,打包结果将是:

/* 使用@external替代@import的打包结果 */

/* vars.css编译后内容为空 */

a {
  /* 没有引入变量的定义 */
  color: $red;
}

总结一下两者的区别:

  • @external在预处理后起作用,而@import在预处理过程中起作用。 因此,任何辅助预处理的模块都必须使用@import来引入。
  • @external声明预处理后CSS模块的依赖关系,主要影响打包结果。 只要有可能,对于具体的CSS模块,都使用@external来连接。

在下面的例子中,需要以a.css和b.css为入口,分别生成两个CSS文件,同时生成一个公共的common.css文件,供跨页面共享。

输入

/* a.css */

/* 在文件中的顺序不重要 */
@external "reset";

/* 必须在使用之前 */
@import "./vars";

a {
  color: $red;
}
/* b.css */

/* 在文件中的顺序不重要 */
@external "reset";

/* 必须在使用之前 */
@import "./vars";

b {
  color: $green;
}
/* reset.css */
html {}

输出

/* a.css打包结果 */
a {
  color: #ff0000;
}
/* b.css打包结果 */
b {
  color: #00ff00;
}
/* common.css */
html {}

如果这个例子中的@external都用@import替换,则无法提取公共包,即common.css内容为空。

@external与@import的路径解析规则与Node.js中的require保持一致。 具体请参考官方文档

与Node.js一样,Reduce在解析依赖时,也会参考package.json的信息,JS是main字段,CSS是style字段。

组件

在组件中,常常同时包含JS、CSS。

假设有组件calendar(包含calendar.js和calendar.css),和schedule(包含schedule.js和schedule.css)。 现在要在组件schedule中使用calendar,按一般的做法, 需要在schedule.js中去require('calendar'),同时在schedule.css中@external "calendar";

但在Reduce中,可以只写require('calendar'),打包时便会自动补上schedule.css对calendar.css的依赖。 如此,就只需要在一处声明,便确定了组件之间的依赖关系。

为了能做到这点,需要calendar组件通过某种方式声明自己的JS与CSS的绑定关系。 譬如,在package.json中指定style字段,便表示该组件的入口JS模块应当与指定的样式文件同时使用。

在0.6中,是通过在配置中指定`cssFile = getStyle(jsFile)`函数来做到的。 后续版本将支持在package.json中指定组件的JS与CSS之间的绑定关系。