RequireJs 初哥
最近在弄前端打包的东西, 发现有些很基础的东西想弄好还是不那么容易的。
序
其实也有蛮多框架可以用的, 随便看了下, 国外现在挺火的 webpack, 或者是国内可能已经有点过气的度娘的 fis, 应该都是挺不错的.
但我是个比较懒的人, 不是太轻的东西, 嚼不透的话还是不敢随随便便就拿来用, 学一样东西啦, 读一遍文档都是挺累人的. 还是稍微花点时间折腾一下 RequireJs
自己的东西好了.
RequireJs
自己就包含了专门的打包工具 r.js
, 代码有两万多行 (还好至少没更多第三方依赖了), 出点小问题的话嚼一下应该还是能嚼的动的. 它的文档组织的不是我喜欢的样子, 没法当手册来用, 大部分的说明都放到一个配置文件的备注上了, 读起来有点累人.
我的需求其实蛮简单的, 就是想把一些文件打一起, 然后另外一些文件要剥离开, 避免一开始就要被迫载入所有的东西, 当然, 哪些文件需要放一起, 最好能够有脚本参与的空间, 毕竟很多原有的代码不是遵循 AMD 规范的依赖约定, 或者是在上面有加了些自己所约定的内容, 有脚本的话一切会简单很多.
无聊
require 已经提供了一些例子, 比如这个
演示了个简单的多页面项目,大致上可以实现
- 每个页面生成一个单独的 js (包括依赖)
- 公共的 js 单独打成一个文件
这是一个很不错的入口, 因为它足够简单, 没有什么累赘和多余的东西, 一目了然.
但是我没找到一个能满足我需求的足够简单的例子. (也许只是我搜索的姿势不太对)
所以后来我撸了下面这个示例项目
希望对一些同样不想用框架(或者想造自己轮子)的后来者提供一些帮助.
折腾
需求前面已经说过了, 我就简单整理一下为了达成预期目标所遇到的一些坑以及所采取了的应对手段
== 首先是 option 模块化的问题 ==
r.js
命令行支持的 -o
参数支持的选项文件既不是一个简单的 json
文件也不是一个可导入的标准 CommonJS
模块, 查了 r.js
的源代码才知道, 好家伙那原来是一个 eval
语句, 虽然有点 dirty, 但至少灵活性是有了
由于主要的打包工具是 grunt
, 我希望这个配置文件是通用的, 既可以在 grunt
或其它同样是基于 NodeJS 的工具 (比如 gulp
) 能够直接以 CommonJS
模块导入, 也可以直接用于 r.js
命令行.
所以就有了下面这些 hack (也不完全是 hack 了)
-
匿名函数有了用武之地 官方例子里面的选项文件是这样子的,后缀用的是 js
{ appDir : "xxx" ... ... }
由于这是不符合 js 语法规范的, 无法通过语法检查, 集成环境也会报告语法错误, 改成下面这样就好多了
module.exports = (function() { return { appDir : "xxx" ... ... }; })();
加了匿名函数, 在里面再添加自己的脚本就容易多了, 至于外面的
module.exports =
完全是为了照顾一些早期的 NodeJS 实现(大概是0.10.?
的吧), 它们如果没有return
语句或者module.exports
的话就无法导出内容, 而新版的 NodeJS 则没有这个限制, 直接写一个表达式就能导出, 其实这个对r.js
执行是有一些副作用的, 因为它使用的是eval
而不是nodeRequire
导入的选项文件, 所以实际上这个module
变量是属于r.js
本身的. 也就是说, 它直接给r.js
导出了个变量, 但对实际运行并没有什么卵影响, 所以也就无所谓了. -
nodeRequire
以及如何定位项目所在目录 由于r.js
在运行过程中需要模拟一个RequireJs
的运行环境, 因此全局的require
已经被替换成了内部的require
函数, 要通过nodeRequire
才能访问到 NodeJs 的require
函数, 所以我们的代码看起来是这样子的var fs = (require.nodeRequire || require)("fs"); var path = (require.nodeRequire || require)("path");
以确保在两种方式下都能访问到 NodeJs 的模块(这里我们主要是需要一些文件操作)
另外一个有点棘手问题就是如何得到当前目录了 查 NodeJs 的文档不难找到
__dirname__
这个全局变量的说明, 但由于r.js
用了eval
(很想再次吐槽这个),__dirname__
实际指向了r.js
自己所在的目录, 因此问题就变得麻烦了, 没法子只能再次求救源码大法, 发现r.js
在调用eval
之前的变量空间是开放的, 其中buildFile
保存的就是选项文件的路径, 于是我们可以使用这样的代码 (已经完全是 hack 了, 以后如果有更好的办法应该要换一下)var buildDir = (typeof buildFile === "string") ? path.dirname(buildFile) : __dirname__;
== modules
配置的生成问题 ==
选项文件模块化之后,这个问题就很好解决了, 在例子里面, modules 配置 (决定最终生成几个 js 文件的) 是 hardcode 的, 也就是说, 即使是无法模块化的 json 文件都能简单的配置.
我们实际的项目是用了点脚本, 递归扫描了下各个模块目录, 生成一个模块包含序列, 有脚本参与的话这些都是很容易做到的
== bundles
config ==
了解这个问题之前建议先读一下 RequireJs
文档的这部分
由于几个模块 (示例里面分别是 modules/1
, modules/2
, modules/3
, modules/4
) 的同名的 js 文件在打包后已经被删除, 在需要(动态)加载这些 AMD 模块的时候, 由于文件还没载入, RequireJs
将触发同名的 js 文件载入来加载模块定义, 于是就产生了久违的 404
了
解决办法就是在配置文件中插入一段 bundles
config, 可以利用 onBuildWrite
回调函数来达成 (r.js
的配置示例文件中有说明这个函数的用法, 每个模块在输出的时候可以做一些额外的修改, 比如我们这里就是插入一点配置信息), 插入的是这么一段代码
require.config({
bundles : {
"m12": ["modules/1", "modules/3"],
"m34": ["modules/3", "modules/4"]
}
});
当然, 这段代码是不需要 hardcode 的, 完全可以从 modules
配置里面反向导出来
结语
所有(配置)源码也只有几十行代码, 其实也没啥好说的, 看两眼然后运行一下应该都不难知道是咋回事, 虽然折腾的时候还是要费不少功夫, 但不折腾一下又怎么知道世界有多美(can)丽(ku)呢?