我决定采用 ljfranklin's advice,并完全取消 RequireJS。我个人认为 AMD 做错了,而 CommonJS(具有同步行为)是要走的路;但这是另一个讨论。
我看到的一件事是转移到Browserify,但在开发过程中,每个编译(因为它会扫描您的所有文件并追捕require() 调用)花费了太长时间,以至于我认为可以接受。
最后,我推出了自己的定制解决方案。它基本上是 Browserify,但它要求您指定所有依赖项,而不是让 Browserify 自己解决。这意味着编译只需几秒钟而不是 30 秒。
这就是 TL;DR。下面,我将详细介绍我是如何做到的。对不起,长度。希望这可以帮助某人……或者至少给某人一些灵感!
首先,我有我的 JavaScript 文件。它们是用 CommonJS 编写的,其限制是 exports 不能作为“全局”变量使用(您必须改用 module.exports)。例如:
var anotherModule = require('./another-module');
module.exports.foo = function () {
console.log(anotherModule.saySomething());
};
然后,我在配置文件中指定依序排列的依赖项列表(注意js/support.js,它会在以后保存):
{
"js": [
"js/support.js",
"js/jquery.js",
"js/jquery-ui.js",
"js/handlebars.js",
// ...
"js/editor/manager.js",
"js/editor.js"
]
}
然后,在编译过程中,我将我所有的 JavaScript 文件(在 js/ 目录中)映射到表单;
define('/path/to/js_file.js', function (require, module) {
// The contents of the JavaScript file
});
这对于原始 JavaScript 文件完全是透明的;下面我们提供对define、require 和module 等的所有支持,以便对原始JavaScript 文件正常工作。
我使用 grunt 进行映射;首先将文件复制到build 目录中(所以我不会弄乱原件)然后重写文件。
// files were previous in public/js/*, move to build/js/*
grunt.initConfig({
copy: {
dist: {
files: [{
expand: true,
cwd: 'public',
src: '**/*',
dest: 'build/'
}]
}
}
});
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.registerTask('buildjs', function () {
var path = require('path');
grunt.file.expand('build/**/*.js').forEach(function (file) {
grunt.file.copy(file, file, {
process: function (contents, folder) {
return 'define(\'' + folder + '\', function (require, module) {\n' + contents + '\n});'
},
noProcess: 'build/js/support.js'
});
});
});
我有一个文件/js/support.js,它定义了我包装每个文件的define() 函数;这就是神奇之处,因为它在不到 40 行的时间内增加了对 module.exports 和 require() 的支持!
(function () {
var cache = {};
this.define = function (path, func) {
func(function (module) {
var other = module.split('/');
var curr = path.split('/');
var target;
other.push(other.pop() + '.js');
curr.pop();
while (other.length) {
var next = other.shift();
switch (next) {
case '.':
break;
case '..':
curr.pop();
break;
default:
curr.push(next);
}
}
target = curr.join('/');
if (!cache[target]) {
throw new Error(target + ' required by ' + path + ' before it is defined.');
} else {
return cache[target].exports;
}
}, cache[path] = {
exports: {}
});
};
}.call(this));
然后,在开发中,我逐字逐句地遍历配置文件中的每个文件,并将其输出为单独的<script /> 标记;一切同步,没有缩小,一切都很快。
{{#iter scripts}}<script src="{{this}}"></script>
{{/iter}}
这给了我;
<script src="js/support.js"></script>
<script src="js/jquery.js"></script>
<script src="js/jquery-ui.js"></script>
<script src="js/handlebars.js"></script>
<!-- ... -->
<script src="js/editor/manager.js"></script>
<script src="js/editor.js"></script>
在生产中,我使用UglifyJs 缩小和合并 JS 文件。好吧,从技术上讲,我使用 UglifyJs 的包装器; mini-fier.
grunt.registerTask('compilejs', function () {
var minifier = require('mini-fier').create();
if (config.production) {
var async = this.async();
var files = bundles.js || [];
minifier.js({
srcPath: __dirname + '/build/',
filesIn: files,
destination: __dirname + '/build/js/all.js'
}).on('error', function () {
console.log(arguments);
async(false);
}).on('complete', function () {
async();
});
}
});
...然后在应用程序代码中,我将scripts(我用来存放要在视图中输出的脚本的变量)更改为['/build/js/all.js'],而不是实际文件的数组。这给了我一个单一的
<script src="/js/all.js"></script>
... 输出。同步、缩小、相当快。