modulr 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +18 -5
- data/VERSION +1 -1
- data/assets/modulr.js +200 -32
- data/assets/modulr.sync.js +116 -0
- data/bin/modulrize +48 -3
- data/lib/modulr/collector.rb +13 -5
- data/lib/modulr/dependency_graph.rb +86 -0
- data/lib/modulr/global_export_collector.rb +32 -0
- data/lib/modulr/js_module.rb +39 -9
- data/lib/modulr/minifier.rb +41 -0
- data/lib/modulr/parser.rb +15 -4
- data/lib/modulr.rb +30 -2
- metadata +30 -8
data/Rakefile
CHANGED
@@ -11,20 +11,31 @@ task :build_example do
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
+
desc "Concatenate synchronous example file"
|
15
|
+
task :build_sync_example do
|
16
|
+
File.open(File.join('output', 'example.js'), 'w') do |f|
|
17
|
+
f << Modulr.ize(File.join('example', 'program.js'), { :global => 'foo' })
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
desc "Concatenate synchronous example file"
|
22
|
+
task :test do
|
23
|
+
Modulr.ize(File.join('example', 'program.js'))
|
24
|
+
end
|
25
|
+
|
14
26
|
desc "Run CommonJS Module 1.0 specs"
|
15
27
|
task :spec do
|
16
28
|
specs = ENV["SPECS"] || "**"
|
17
|
-
|
18
29
|
FileList["#{COMMONJS_SPEC_DIR}/{#{specs}}/program.js"].each do |spec|
|
19
30
|
dir = File.dirname(spec)
|
20
31
|
output = File.join(dir, 'output.js')
|
32
|
+
input = File.join(dir, 'input.js')
|
21
33
|
system = File.join(dir, 'system.js')
|
22
34
|
FileUtils.touch(system)
|
23
35
|
begin
|
24
36
|
puts File.basename(dir).center(80, "_")
|
25
|
-
File.open(
|
26
|
-
|
27
|
-
end
|
37
|
+
File.open(input, 'w') { |f| f << "require('program');" }
|
38
|
+
File.open(output, 'w') { |f| f << Modulr.ize(input) }
|
28
39
|
system("js -f #{output}")
|
29
40
|
rescue => e
|
30
41
|
phase = e.is_a?(Modulr::ModulrError) ? "building" : "running"
|
@@ -32,6 +43,7 @@ task :spec do
|
|
32
43
|
puts e.message
|
33
44
|
ensure
|
34
45
|
FileUtils.rm(output)
|
46
|
+
FileUtils.rm(input)
|
35
47
|
FileUtils.rm(system)
|
36
48
|
puts
|
37
49
|
puts
|
@@ -46,9 +58,10 @@ begin
|
|
46
58
|
gemspec.summary = "A CommonJS module implementation in Ruby for client-side JavaScript"
|
47
59
|
gemspec.author = "Tobie Langel"
|
48
60
|
gemspec.email = "tobie.langel@gmail.com"
|
49
|
-
gemspec.homepage = "http://github.com/
|
61
|
+
gemspec.homepage = "http://github.com/codespeaks/modulr"
|
50
62
|
gemspec.files = FileList["Rakefile", "README.markdown", "LICENSE", "VERSION", "{lib,bin,assets,example}/**/*", "vendor/rkelly/**/*"]
|
51
63
|
gemspec.executable = "modulrize"
|
64
|
+
gemspec.add_dependency("coffee_machine")
|
52
65
|
end
|
53
66
|
rescue LoadError
|
54
67
|
puts "Jeweler not available. Install it with: sudo gem install jeweler"
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
data/assets/modulr.js
CHANGED
@@ -4,45 +4,92 @@
|
|
4
4
|
// http://github.com/codespeaks/modulr/blob/master/LICENSE
|
5
5
|
|
6
6
|
var modulr = (function(global) {
|
7
|
-
var
|
8
|
-
|
7
|
+
var _dependencyGraph = {},
|
8
|
+
_loadingFactories = {},
|
9
|
+
_incompleteFactories = {},
|
10
|
+
_factories = {},
|
11
|
+
_modules = {},
|
9
12
|
_exports = {},
|
10
|
-
|
11
|
-
|
13
|
+
_handlers = [],
|
14
|
+
_dirStack = [''],
|
12
15
|
PREFIX = '__module__', // Prefix identifiers to avoid issues in IE.
|
13
|
-
RELATIVE_IDENTIFIER_PATTERN =
|
16
|
+
RELATIVE_IDENTIFIER_PATTERN = /^\.\.?\//,
|
17
|
+
_forEach,
|
18
|
+
_indexOf;
|
19
|
+
|
20
|
+
_forEach = (function() {
|
21
|
+
var hasOwnProp = Object.prototype.hasOwnProperty,
|
22
|
+
DONT_ENUM_PROPERTIES = [
|
23
|
+
'constructor', 'toString', 'toLocaleString', 'valueOf',
|
24
|
+
'hasOwnProperty','isPrototypeOf', 'propertyIsEnumerable'
|
25
|
+
],
|
26
|
+
LENGTH = DONT_ENUM_PROPERTIES.length,
|
27
|
+
DONT_ENUM_BUG = true;
|
28
|
+
|
29
|
+
function _forEach(obj, callback) {
|
30
|
+
for(var prop in obj) {
|
31
|
+
if (hasOwnProp.call(obj, prop)) {
|
32
|
+
callback(prop, obj[prop]);
|
33
|
+
}
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
for(var prop in { toString: true }) {
|
38
|
+
DONT_ENUM_BUG = false
|
39
|
+
}
|
40
|
+
|
41
|
+
if (DONT_ENUM_BUG) {
|
42
|
+
return function(obj, callback) {
|
43
|
+
_forEach(obj, callback);
|
44
|
+
for (var i = 0; i < LENGTH; i++) {
|
45
|
+
var prop = DONT_ENUM_PROPERTIES[i];
|
46
|
+
if (hasOwnProp.call(obj, prop)) {
|
47
|
+
callback(prop, obj[prop]);
|
48
|
+
}
|
49
|
+
}
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
return _forEach;
|
54
|
+
})();
|
14
55
|
|
15
|
-
|
16
|
-
|
17
|
-
|
56
|
+
_indexOf = (function() {
|
57
|
+
var nativeIndexOf = Array.prototype.indexOf;
|
58
|
+
if (typeof nativeIndexOf === 'function') {
|
59
|
+
return function(array, item) {
|
60
|
+
return nativeIndexOf.call(array, item);
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
return function(array, item) {
|
65
|
+
for (var i = 0, length = array.length; i < length; i++) {
|
66
|
+
if (item === array[i]) { return i; }
|
67
|
+
}
|
68
|
+
return -1;
|
69
|
+
}
|
70
|
+
})();
|
18
71
|
|
19
72
|
function require(identifier) {
|
20
|
-
var fn,
|
73
|
+
var fn, mod,
|
21
74
|
id = resolveIdentifier(identifier),
|
22
75
|
key = PREFIX + id,
|
23
76
|
expts = _exports[key];
|
24
77
|
|
25
|
-
log('Required module "' + identifier + '".');
|
26
|
-
|
27
78
|
if (!expts) {
|
28
79
|
_exports[key] = expts = {};
|
29
|
-
|
30
|
-
|
31
|
-
if (!require.main) { require.main = modObj; }
|
32
|
-
|
33
|
-
fn = _modules[key];
|
34
|
-
_oldDir = _currentDir;
|
35
|
-
_currentDir = id.slice(0, id.lastIndexOf('/'));
|
80
|
+
_modules[key] = mod = { id: id };
|
36
81
|
|
82
|
+
fn = _factories[key];
|
83
|
+
_dirStack.push(id.substring(0, id.lastIndexOf('/') + 1))
|
37
84
|
try {
|
38
85
|
if (!fn) { throw 'Can\'t find module "' + identifier + '".'; }
|
39
86
|
if (typeof fn === 'string') {
|
40
87
|
fn = new Function('require', 'exports', 'module', fn);
|
41
88
|
}
|
42
|
-
fn(require, expts,
|
43
|
-
|
89
|
+
fn(require, expts, mod);
|
90
|
+
_dirStack.pop();
|
44
91
|
} catch(e) {
|
45
|
-
|
92
|
+
_dirStack.pop();
|
46
93
|
// We'd use a finally statement here if it wasn't for IE.
|
47
94
|
throw e;
|
48
95
|
}
|
@@ -51,13 +98,13 @@ var modulr = (function(global) {
|
|
51
98
|
}
|
52
99
|
|
53
100
|
function resolveIdentifier(identifier) {
|
54
|
-
var parts, part, path;
|
101
|
+
var dir, parts, part, path;
|
55
102
|
|
56
103
|
if (!RELATIVE_IDENTIFIER_PATTERN.test(identifier)) {
|
57
104
|
return identifier;
|
58
105
|
}
|
59
|
-
|
60
|
-
parts = (
|
106
|
+
dir = _dirStack[_dirStack.length - 1];
|
107
|
+
parts = (dir + identifier).split('/');
|
61
108
|
path = [];
|
62
109
|
for (var i = 0, length = parts.length; i < length; i++) {
|
63
110
|
part = parts[i];
|
@@ -75,18 +122,139 @@ var modulr = (function(global) {
|
|
75
122
|
return path.join('/');
|
76
123
|
}
|
77
124
|
|
78
|
-
function
|
79
|
-
var
|
125
|
+
function define(descriptors, dependencies) {
|
126
|
+
var missingDependencies;
|
127
|
+
if (dependencies) {
|
128
|
+
// Check to see if any of the required dependencies
|
129
|
+
// weren't previously loaded.
|
130
|
+
// Build an array of missing dependencies with those which weren't.
|
131
|
+
for (var i = 0, length = dependencies.length; i < length; i++) {
|
132
|
+
var key = PREFIX + dependencies[i];
|
133
|
+
if (!(key in _factories) && !(key in _incompleteFactories)) {
|
134
|
+
missingDependencies = missingDependencies || [];
|
135
|
+
missingDependencies.push(key);
|
136
|
+
}
|
137
|
+
}
|
138
|
+
}
|
139
|
+
|
140
|
+
if (missingDependencies) {
|
141
|
+
// Add each newly defined descriptor to our list of
|
142
|
+
// factories missing dependencies.
|
143
|
+
// Build a dependency graph so we can handle subsequent
|
144
|
+
// require.define calls easily.
|
145
|
+
_forEach(descriptors, function(id, factory) {
|
146
|
+
var key = PREFIX + id;
|
147
|
+
_dependencyGraph[key] = missingDependencies; // TODO clone?
|
148
|
+
_incompleteFactories[key] = factory;
|
149
|
+
});
|
150
|
+
// load the missing modules.
|
151
|
+
loadModules(missingDependencies);
|
152
|
+
} else {
|
153
|
+
// There aren't any missing dependencies in the factories
|
154
|
+
// which were just defined. Lets move them to a list of
|
155
|
+
// synchronously requirable factories.
|
156
|
+
prepare(descriptors);
|
157
|
+
// While we're at it, let's call all async handlers whose
|
158
|
+
// dependencies are now available.
|
159
|
+
callRipeHandlers();
|
160
|
+
}
|
161
|
+
}
|
162
|
+
|
163
|
+
function prepare(descriptors) {
|
164
|
+
// Handles factories for which all dependencies are
|
165
|
+
// available.
|
166
|
+
_forEach(descriptors, function(id, factory) {
|
167
|
+
var key = PREFIX + id;
|
168
|
+
// Move the factory from the list of factories missing
|
169
|
+
// dependencies to the list of synchronously requirable
|
170
|
+
// factories.
|
171
|
+
_factories[key] = factory;
|
172
|
+
delete _incompleteFactories[key];
|
173
|
+
// Go through the dependency graph and remove the factory
|
174
|
+
// from all of the missing dependencies lists.
|
175
|
+
_forEach(_dependencyGraph, function(unused, dependencies) {
|
176
|
+
var i = _indexOf(i, key);
|
177
|
+
if (i > -1) { dependencies.splice(i, 1); }
|
178
|
+
});
|
179
|
+
});
|
180
|
+
|
181
|
+
// Find all the factories which no longer have missing dependencies.
|
182
|
+
var newFactories;
|
183
|
+
_forEach(_dependencyGraph, function(key, dependencies) {
|
184
|
+
if (dependencies.length === 0) {
|
185
|
+
newFactories = newFactories || {};
|
186
|
+
newFactories[key] = _incompleteFactories[key];
|
187
|
+
delete _dependencyGraph[key];
|
188
|
+
}
|
189
|
+
});
|
190
|
+
// recurse!
|
191
|
+
if (newFactories) { prepare(newFactories); }
|
192
|
+
}
|
193
|
+
|
194
|
+
function ensure(dependencies, callback, errorCallback) {
|
195
|
+
// Cache this new handler.
|
196
|
+
_handlers.push({
|
197
|
+
dependencies: dependencies,
|
198
|
+
callback: callback,
|
199
|
+
errorCallback: errorCallback
|
200
|
+
});
|
201
|
+
|
202
|
+
// Immediately callRipeHandlers(): you never know,
|
203
|
+
// all of the required dependencies might be already
|
204
|
+
// available.
|
205
|
+
callRipeHandlers();
|
206
|
+
}
|
207
|
+
|
208
|
+
function callRipeHandlers() {
|
209
|
+
var missingFactories;
|
80
210
|
|
81
|
-
|
82
|
-
|
83
|
-
|
211
|
+
for (var i = 0, length = _handlers.length; i < length; i++) {
|
212
|
+
// Go through all of the stored handlers.
|
213
|
+
var handler = _handlers[i],
|
214
|
+
dependencies = handler.dependencies,
|
215
|
+
isRipe = true;
|
216
|
+
for (var j = 0, reqLength = dependencies.length; j < reqLength; j++) {
|
217
|
+
var id = dependencies[j];
|
218
|
+
// If any dependency is missing, the handler isn't ready to be called.
|
219
|
+
// Store those missing so we can later inform the loader.
|
220
|
+
if (!_factories[PREFIX + id]) {
|
221
|
+
missingFactories = missingFactories || [];
|
222
|
+
if (_indexOf(missingFactories, id) < 0) {
|
223
|
+
missingFactories.push(id);
|
224
|
+
}
|
225
|
+
isRipe = false;
|
226
|
+
}
|
227
|
+
}
|
228
|
+
|
229
|
+
if (isRipe) {
|
230
|
+
handler.callback(); // TODO error handling
|
231
|
+
}
|
232
|
+
}
|
233
|
+
|
234
|
+
if (missingFactories) {
|
235
|
+
loadModules(missingFactories);
|
84
236
|
}
|
85
|
-
_modules[key] = fn;
|
86
237
|
}
|
87
238
|
|
239
|
+
function loadModules(factories) {
|
240
|
+
var missingFactories;
|
241
|
+
for (var i = 0, length = factories.length; i < length; i++) {
|
242
|
+
var factory = factories[i];
|
243
|
+
if (!(factory in _loadingFactories)) {
|
244
|
+
missingFactories = missingFactories || [];
|
245
|
+
missingFactories.push(factory);
|
246
|
+
}
|
247
|
+
}
|
248
|
+
if (missingFactories) {
|
249
|
+
console.log(missingFactories);
|
250
|
+
}
|
251
|
+
}
|
252
|
+
|
253
|
+
require.define = define;
|
254
|
+
require.ensure = ensure;
|
255
|
+
require.main = {};
|
256
|
+
|
88
257
|
return {
|
89
|
-
require: require
|
90
|
-
cache: cache
|
258
|
+
require: require
|
91
259
|
};
|
92
260
|
})(this);
|
@@ -0,0 +1,116 @@
|
|
1
|
+
// modulr.sync.js (c) 2010 codespeaks sàrl
|
2
|
+
// Freely distributable under the terms of the MIT license.
|
3
|
+
// For details, see:
|
4
|
+
// http://github.com/codespeaks/modulr/blob/master/LICENSE
|
5
|
+
|
6
|
+
var require = (function() {
|
7
|
+
var _factories = {},
|
8
|
+
_modules = {},
|
9
|
+
_exports = {},
|
10
|
+
_handlers = [],
|
11
|
+
_dirStack = [''],
|
12
|
+
PREFIX = '__module__', // Prefix identifiers to avoid issues in IE.
|
13
|
+
RELATIVE_IDENTIFIER_PATTERN = /^\.\.?\//,
|
14
|
+
_forEach;
|
15
|
+
|
16
|
+
_forEach = (function() {
|
17
|
+
var hasOwnProp = Object.prototype.hasOwnProperty,
|
18
|
+
DONT_ENUM_PROPERTIES = [
|
19
|
+
'constructor', 'toString', 'toLocaleString', 'valueOf',
|
20
|
+
'hasOwnProperty','isPrototypeOf', 'propertyIsEnumerable'
|
21
|
+
],
|
22
|
+
LENGTH = DONT_ENUM_PROPERTIES.length,
|
23
|
+
DONT_ENUM_BUG = true;
|
24
|
+
|
25
|
+
function _forEach(obj, callback) {
|
26
|
+
for(var prop in obj) {
|
27
|
+
if (hasOwnProp.call(obj, prop)) {
|
28
|
+
callback(prop, obj[prop]);
|
29
|
+
}
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
for(var prop in { toString: true }) {
|
34
|
+
DONT_ENUM_BUG = false
|
35
|
+
}
|
36
|
+
|
37
|
+
if (DONT_ENUM_BUG) {
|
38
|
+
return function(obj, callback) {
|
39
|
+
_forEach(obj, callback);
|
40
|
+
for (var i = 0; i < LENGTH; i++) {
|
41
|
+
var prop = DONT_ENUM_PROPERTIES[i];
|
42
|
+
if (hasOwnProp.call(obj, prop)) {
|
43
|
+
callback(prop, obj[prop]);
|
44
|
+
}
|
45
|
+
}
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
return _forEach;
|
50
|
+
})();
|
51
|
+
|
52
|
+
function require(identifier) {
|
53
|
+
var fn, mod,
|
54
|
+
id = resolveIdentifier(identifier),
|
55
|
+
key = PREFIX + id,
|
56
|
+
expts = _exports[key];
|
57
|
+
|
58
|
+
if (!expts) {
|
59
|
+
_exports[key] = expts = {};
|
60
|
+
_modules[key] = mod = { id: id };
|
61
|
+
|
62
|
+
fn = _factories[key];
|
63
|
+
_dirStack.push(id.substring(0, id.lastIndexOf('/') + 1))
|
64
|
+
|
65
|
+
try {
|
66
|
+
if (!fn) { throw 'Can\'t find module "' + identifier + '".'; }
|
67
|
+
if (typeof fn === 'string') {
|
68
|
+
fn = new Function('require', 'exports', 'module', fn);
|
69
|
+
}
|
70
|
+
if (!require.main) { require.main = mod; }
|
71
|
+
fn(require, expts, mod);
|
72
|
+
_dirStack.pop();
|
73
|
+
} catch(e) {
|
74
|
+
_dirStack.pop();
|
75
|
+
// We'd use a finally statement here if it wasn't for IE.
|
76
|
+
throw e;
|
77
|
+
}
|
78
|
+
}
|
79
|
+
return expts;
|
80
|
+
}
|
81
|
+
|
82
|
+
function resolveIdentifier(identifier) {
|
83
|
+
var dir, parts, part, path;
|
84
|
+
|
85
|
+
if (!RELATIVE_IDENTIFIER_PATTERN.test(identifier)) {
|
86
|
+
return identifier;
|
87
|
+
}
|
88
|
+
dir = _dirStack[_dirStack.length - 1];
|
89
|
+
parts = (dir + identifier).split('/');
|
90
|
+
path = [];
|
91
|
+
for (var i = 0, length = parts.length; i < length; i++) {
|
92
|
+
part = parts[i];
|
93
|
+
switch (part) {
|
94
|
+
case '':
|
95
|
+
case '.':
|
96
|
+
continue;
|
97
|
+
case '..':
|
98
|
+
path.pop();
|
99
|
+
break;
|
100
|
+
default:
|
101
|
+
path.push(part);
|
102
|
+
}
|
103
|
+
}
|
104
|
+
return path.join('/');
|
105
|
+
}
|
106
|
+
|
107
|
+
function define(descriptors) {
|
108
|
+
_forEach(descriptors, function(id, factory) {
|
109
|
+
_factories[PREFIX + id] = factory;
|
110
|
+
});
|
111
|
+
}
|
112
|
+
|
113
|
+
require.define = define;
|
114
|
+
|
115
|
+
return require;
|
116
|
+
})();
|
data/bin/modulrize
CHANGED
@@ -9,6 +9,9 @@ options = {
|
|
9
9
|
opts = OptionParser.new do |opts|
|
10
10
|
opts.banner = 'Usage: modulrize program.js [options] > output.js'
|
11
11
|
|
12
|
+
opts.separator ''
|
13
|
+
opts.separator 'Options:'
|
14
|
+
|
12
15
|
opts.on('-o', '--output=FILE', 'Write the output to FILE. Defaults to stdout.') do |output|
|
13
16
|
options[:output] = File.open(output, 'w')
|
14
17
|
end
|
@@ -24,10 +27,48 @@ opts = OptionParser.new do |opts|
|
|
24
27
|
options[:lazy_eval] = modules
|
25
28
|
end
|
26
29
|
|
27
|
-
opts.
|
30
|
+
opts.on('--minify', 'Minify output using YUI Compressor.') do |minify|
|
31
|
+
options[:minify] = minify
|
32
|
+
end
|
33
|
+
|
34
|
+
opts.on('--global-export=GLOBAL_VAR', 'Export main module\'s exports to the GLOBAL_VAR global variable.') do |global|
|
35
|
+
options[:global] = global
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on('--dependency-graph[=OUTPUT]', 'Create a dependency graph of the module.') do |output|
|
39
|
+
options[:dependency_graph] = true
|
40
|
+
options[:output] = output
|
41
|
+
end
|
42
|
+
|
43
|
+
opts.on('-h', '--help', 'Show this message.') do
|
28
44
|
puts opts
|
29
45
|
exit
|
30
46
|
end
|
47
|
+
|
48
|
+
opts.separator ''
|
49
|
+
opts.separator 'Minification options (these are forwarded to YUI Compressor without the "minify-" prefix):'
|
50
|
+
|
51
|
+
{
|
52
|
+
'line-break COLUMN' => 'Insert a line break after the specified column number.',
|
53
|
+
'verbose' => 'Display informational messages and warnings.',
|
54
|
+
'nomunge' => 'Minify only, do not obfuscate.',
|
55
|
+
'preserve-semi' => 'Preserve all semicolons.',
|
56
|
+
'disable-optimizations' => 'Disable all micro optimizations.'
|
57
|
+
}.each do |option, message|
|
58
|
+
prefixed_option = option.sub(/\A(\[no-\])?/, '--\\1minify-')
|
59
|
+
|
60
|
+
def normalize_option(option)
|
61
|
+
option.gsub('-', '_').to_sym
|
62
|
+
end
|
63
|
+
|
64
|
+
opts.on(prefixed_option, message) do |value|
|
65
|
+
if !options[:minify] || options[:minify] == true
|
66
|
+
options[:minify] = {}
|
67
|
+
end
|
68
|
+
options[:minify][normalize_option(option)] = value
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
31
72
|
end
|
32
73
|
|
33
74
|
opts.parse!
|
@@ -39,8 +80,12 @@ begin
|
|
39
80
|
puts opts
|
40
81
|
exit 1
|
41
82
|
end
|
42
|
-
|
43
|
-
|
83
|
+
if options.delete(:dependency_graph)
|
84
|
+
result = Modulr.graph(ARGV.first, options)
|
85
|
+
else
|
86
|
+
result = Modulr.ize(ARGV.first, options)
|
87
|
+
output.print(result)
|
88
|
+
end
|
44
89
|
ensure
|
45
90
|
output.close
|
46
91
|
end
|
data/lib/modulr/collector.rb
CHANGED
@@ -18,14 +18,22 @@ module Modulr
|
|
18
18
|
|
19
19
|
def to_js(buffer = '')
|
20
20
|
buffer << File.read(PATH_TO_MODULR_JS)
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
buffer << "\n(function(require, module) {"
|
22
|
+
buffer << transport
|
23
|
+
buffer << main.ensure
|
24
|
+
buffer << "})(modulr.require, modulr.require.main);\n"
|
25
|
+
end
|
26
|
+
|
27
|
+
def transport
|
28
|
+
pairs = modules.map do |m|
|
29
|
+
if lazy_eval_module?(m)
|
30
|
+
value = m.escaped_src
|
24
31
|
else
|
25
|
-
|
32
|
+
value = m.factory
|
26
33
|
end
|
34
|
+
"\n'#{m.id}': #{value}"
|
27
35
|
end
|
28
|
-
|
36
|
+
"require.define({#{pairs.join(', ')}\n});"
|
29
37
|
end
|
30
38
|
|
31
39
|
private
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'uri'
|
2
|
+
module Modulr
|
3
|
+
class DependencyGraph
|
4
|
+
def initialize(js_modules)
|
5
|
+
if js_modules.is_a?(Array)
|
6
|
+
@js_modules = js_modules
|
7
|
+
else
|
8
|
+
@js_modules = [js_modules]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_tree
|
13
|
+
return @tree if @tree
|
14
|
+
@tree = {}
|
15
|
+
@stack = []
|
16
|
+
build_branch(@js_modules, @tree)
|
17
|
+
@tree
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_list
|
21
|
+
return @list if @list
|
22
|
+
@list = {}
|
23
|
+
@js_modules.each { |m| build_list(m) }
|
24
|
+
@list
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_yuml(options = {})
|
28
|
+
options = {
|
29
|
+
:ext => 'png',
|
30
|
+
:dir => 'lr',
|
31
|
+
:scale => 100,
|
32
|
+
:scruffy => true
|
33
|
+
}.merge(options)
|
34
|
+
dep = @js_modules.map { |m| "[#{m.id}]" }
|
35
|
+
to_list.map do |k, v|
|
36
|
+
if v
|
37
|
+
v.each do |i|
|
38
|
+
if @list[i]
|
39
|
+
dep << "[#{k}]->[#{i}]"
|
40
|
+
else
|
41
|
+
dep << "[#{k}]-.-Missing>[#{i}{bg:red}]"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
opts = []
|
47
|
+
opts << "scruffy" if options[:scruffy]
|
48
|
+
opts << "dir:#{options[:dir]}"
|
49
|
+
opts << "scale:#{options[:scale]}"
|
50
|
+
|
51
|
+
uri = "http://yuml.me/diagram/"
|
52
|
+
uri << opts.join(';')
|
53
|
+
uri << "/class/"
|
54
|
+
uri << dep.join(',')
|
55
|
+
uri << ".#{options[:ext]}"
|
56
|
+
URI.encode(uri).gsub('[', '%5B').gsub(']', '%5D')
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
def build_branch(js_modules, branch)
|
61
|
+
js_modules.each do |m|
|
62
|
+
id = m.id
|
63
|
+
branch[id] = {}
|
64
|
+
unless @stack.include?(id)
|
65
|
+
@stack << id
|
66
|
+
build_branch(m.dependencies, branch[id])
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def build_list(js_module)
|
72
|
+
begin
|
73
|
+
list = @list[js_module.id] ||= []
|
74
|
+
js_module.dependencies.each do |m|
|
75
|
+
id = m.id
|
76
|
+
unless list.include?(id)
|
77
|
+
list << id
|
78
|
+
build_list(m)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
rescue
|
82
|
+
@list[js_module.id] = false
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Modulr
|
2
|
+
class GlobalExportCollector < Collector
|
3
|
+
|
4
|
+
def initialize(options = {})
|
5
|
+
@global = options[:global]
|
6
|
+
super
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_js(buffer = '')
|
10
|
+
buffer << "#{define_global} = (function() {\n"
|
11
|
+
buffer << File.read(PATH_TO_MODULR_SYNC_JS)
|
12
|
+
buffer << transport
|
13
|
+
buffer << "\n return require('#{main.id}');\n"
|
14
|
+
buffer << "})();\n"
|
15
|
+
end
|
16
|
+
|
17
|
+
def define_global
|
18
|
+
if @global.include?('.')
|
19
|
+
props = @global.split('.')
|
20
|
+
str = props.shift
|
21
|
+
results = "var #{str};"
|
22
|
+
props.each do |prop|
|
23
|
+
results << "\n#{str} = #{str} || {};"
|
24
|
+
str << ".#{prop}"
|
25
|
+
end
|
26
|
+
"#{results}\n#{str}"
|
27
|
+
else
|
28
|
+
"var #{@global}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/modulr/js_module.rb
CHANGED
@@ -17,9 +17,17 @@ module Modulr
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def self.find_dependencies(js_module)
|
20
|
-
|
20
|
+
begin
|
21
|
+
expressions = parser.get_require_expressions(js_module.src)
|
22
|
+
rescue ParserError
|
23
|
+
raise JavaScriptSyntaxError, js_module
|
24
|
+
end
|
21
25
|
expressions.map do |exp|
|
22
|
-
|
26
|
+
if exp[:identifier]
|
27
|
+
new(exp[:identifier], js_module.root, js_module.path, exp[:line])
|
28
|
+
else
|
29
|
+
raise DynamicModuleIdentifierError.new(exp[:src_code], js_module.path, exp[:line])
|
30
|
+
end
|
23
31
|
end
|
24
32
|
end
|
25
33
|
|
@@ -83,19 +91,23 @@ module Modulr
|
|
83
91
|
}
|
84
92
|
end
|
85
93
|
|
94
|
+
def factory
|
95
|
+
"function(require, exports, module) {\n#{src}\n}"
|
96
|
+
end
|
97
|
+
|
86
98
|
def dependencies
|
87
99
|
@dependencies ||= self.class.find_dependencies(self)
|
88
100
|
end
|
89
|
-
|
90
|
-
def to_js(buffer = '')
|
91
|
-
fn = "function(require, exports, module) {\n#{src}\n}"
|
92
|
-
buffer << "\nmodulr.cache('#{id}', #{fn});\n"
|
93
|
-
end
|
94
101
|
|
95
|
-
def
|
96
|
-
|
102
|
+
def dependency_array
|
103
|
+
'[' << dependencies.map { |d| "'#{d.id}'" }.join(', ') << ']'
|
97
104
|
end
|
98
105
|
|
106
|
+
def ensure(buffer = '')
|
107
|
+
fn = "function() {\n#{src}\n}"
|
108
|
+
buffer << "\nrequire.ensure(#{dependency_array}, #{fn});\n"
|
109
|
+
end
|
110
|
+
|
99
111
|
protected
|
100
112
|
def partial_path
|
101
113
|
File.join(*terms)
|
@@ -121,4 +133,22 @@ module Modulr
|
|
121
133
|
super("Cannot load module '#{js_module.identifier}' in #{js_module.file} at line #{js_module.line}.\nMissing file #{js_module.path}.")
|
122
134
|
end
|
123
135
|
end
|
136
|
+
|
137
|
+
class DynamicModuleIdentifierError < ModulrError
|
138
|
+
attr_reader :src, :file, :line
|
139
|
+
def initialize(src, file, line)
|
140
|
+
@src = src
|
141
|
+
@file = file
|
142
|
+
@line = line
|
143
|
+
super("Cannot do a static analysis of dynamic module identifier '#{src}' in #{file} at line #{line}.")
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
class JavaScriptSyntaxError < ModulrError
|
148
|
+
attr_reader :js_module
|
149
|
+
def initialize(js_module)
|
150
|
+
@js_module = js_module
|
151
|
+
super("JavaScript Syntax Error in #{js_module.file}.")
|
152
|
+
end
|
153
|
+
end
|
124
154
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Modulr
|
2
|
+
class MinifierError < ModulrError
|
3
|
+
end
|
4
|
+
|
5
|
+
class Minifier
|
6
|
+
YUI_COMPRESSOR_PATH = File.join(File.dirname(__FILE__), '..', '..', 'vendor', 'yuicompressor-2.4.2.jar').freeze
|
7
|
+
|
8
|
+
def self.minify(input, options = {})
|
9
|
+
new(options).minify(input)
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(options = {})
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def minify(input)
|
17
|
+
run_yui_compressor do |pipe, stderr|
|
18
|
+
pipe.write(input)
|
19
|
+
pipe.close_write
|
20
|
+
output, error = pipe.read, stderr.read
|
21
|
+
raise MinifierError, error unless error.empty?
|
22
|
+
output
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
def run_yui_compressor(&block)
|
28
|
+
require 'coffee_machine'
|
29
|
+
CoffeeMachine.run_jar(YUI_COMPRESSOR_PATH, :args => yui_compressor_args, &block)
|
30
|
+
end
|
31
|
+
|
32
|
+
def yui_compressor_args
|
33
|
+
args = ['--type js']
|
34
|
+
@options.each do |option, value|
|
35
|
+
args << "--#{option.to_s.gsub('_', '-')}"
|
36
|
+
args << value unless value == true || value == false
|
37
|
+
end
|
38
|
+
args.join(' ')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/modulr/parser.rb
CHANGED
@@ -4,7 +4,13 @@ module Modulr
|
|
4
4
|
class Parser
|
5
5
|
|
6
6
|
def parse(src)
|
7
|
-
|
7
|
+
begin
|
8
|
+
ast = parser.parse(src)
|
9
|
+
rescue RKelly::SyntaxError
|
10
|
+
raise ParserError
|
11
|
+
end
|
12
|
+
raise ParserError unless ast
|
13
|
+
ast
|
8
14
|
end
|
9
15
|
|
10
16
|
def get_require_expressions(src)
|
@@ -25,11 +31,16 @@ module Modulr
|
|
25
31
|
end
|
26
32
|
|
27
33
|
def normalize(node)
|
28
|
-
|
34
|
+
arg = node.arguments.value.first
|
35
|
+
valid = arg.is_a?(RKelly::Nodes::StringNode)
|
29
36
|
{
|
30
|
-
:identifier =>
|
31
|
-
:
|
37
|
+
:identifier => valid ? arg.value[1...-1] : nil,
|
38
|
+
:src_code => arg.to_ecma,
|
39
|
+
:line => arg.line.to_i
|
32
40
|
}
|
33
41
|
end
|
34
42
|
end
|
43
|
+
|
44
|
+
class ParserError < ModulrError
|
45
|
+
end
|
35
46
|
end
|
data/lib/modulr.rb
CHANGED
@@ -10,13 +10,41 @@ module Modulr
|
|
10
10
|
require 'modulr/js_module'
|
11
11
|
require 'modulr/parser'
|
12
12
|
require 'modulr/collector'
|
13
|
+
require 'modulr/global_export_collector'
|
14
|
+
require 'modulr/minifier'
|
15
|
+
require 'modulr/dependency_graph'
|
16
|
+
require 'open-uri'
|
13
17
|
require 'modulr/version'
|
14
18
|
|
15
19
|
PATH_TO_MODULR_JS = File.join(LIB_DIR, '..', 'assets', 'modulr.js')
|
20
|
+
PATH_TO_MODULR_SYNC_JS = File.join(LIB_DIR, '..', 'assets', 'modulr.sync.js')
|
16
21
|
|
17
22
|
def self.ize(input_filename, options = {})
|
18
|
-
|
23
|
+
if options[:global]
|
24
|
+
collector = GlobalExportCollector.new(options)
|
25
|
+
else
|
26
|
+
collector = Collector.new(options)
|
27
|
+
end
|
19
28
|
collector.parse_file(input_filename)
|
20
|
-
collector.to_js
|
29
|
+
minify(collector.to_js, options[:minify])
|
21
30
|
end
|
31
|
+
|
32
|
+
def self.graph(file, options = {})
|
33
|
+
dir = File.dirname(file)
|
34
|
+
mod_name = File.basename(file, '.js')
|
35
|
+
mod = JSModule.new(mod_name, dir, file)
|
36
|
+
output = options.delete(:output)
|
37
|
+
output = "#{dir}/#{mod_name}.png" unless output
|
38
|
+
uri = DependencyGraph.new(mod).to_yuml(options)
|
39
|
+
File.open(output, 'w').write(open(uri).read)
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
def self.minify(output, options)
|
44
|
+
if options
|
45
|
+
Minifier.minify(output, options == true ? {} : options)
|
46
|
+
else
|
47
|
+
output
|
48
|
+
end
|
49
|
+
end
|
22
50
|
end
|
metadata
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: modulr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 4
|
8
|
+
- 0
|
9
|
+
version: 0.4.0
|
5
10
|
platform: ruby
|
6
11
|
authors:
|
7
12
|
- Tobie Langel
|
@@ -9,10 +14,21 @@ autorequire:
|
|
9
14
|
bindir: bin
|
10
15
|
cert_chain: []
|
11
16
|
|
12
|
-
date: 2010-
|
17
|
+
date: 2010-05-13 00:00:00 +02:00
|
13
18
|
default_executable: modulrize
|
14
|
-
dependencies:
|
15
|
-
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: coffee_machine
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
version: "0"
|
30
|
+
type: :runtime
|
31
|
+
version_requirements: *id001
|
16
32
|
description:
|
17
33
|
email: tobie.langel@gmail.com
|
18
34
|
executables:
|
@@ -28,6 +44,7 @@ files:
|
|
28
44
|
- Rakefile
|
29
45
|
- VERSION
|
30
46
|
- assets/modulr.js
|
47
|
+
- assets/modulr.sync.js
|
31
48
|
- bin/modulrize
|
32
49
|
- example/foo/bar.js
|
33
50
|
- example/foo/foo.js
|
@@ -37,7 +54,10 @@ files:
|
|
37
54
|
- example/program.js
|
38
55
|
- lib/modulr.rb
|
39
56
|
- lib/modulr/collector.rb
|
57
|
+
- lib/modulr/dependency_graph.rb
|
58
|
+
- lib/modulr/global_export_collector.rb
|
40
59
|
- lib/modulr/js_module.rb
|
60
|
+
- lib/modulr/minifier.rb
|
41
61
|
- lib/modulr/parser.rb
|
42
62
|
- lib/modulr/version.rb
|
43
63
|
- vendor/rkelly/CHANGELOG.rdoc
|
@@ -241,7 +261,7 @@ files:
|
|
241
261
|
- vendor/rkelly/test/test_while_node.rb
|
242
262
|
- vendor/rkelly/test/test_with_node.rb
|
243
263
|
has_rdoc: true
|
244
|
-
homepage: http://github.com/
|
264
|
+
homepage: http://github.com/codespeaks/modulr
|
245
265
|
licenses: []
|
246
266
|
|
247
267
|
post_install_message:
|
@@ -253,18 +273,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
253
273
|
requirements:
|
254
274
|
- - ">="
|
255
275
|
- !ruby/object:Gem::Version
|
276
|
+
segments:
|
277
|
+
- 0
|
256
278
|
version: "0"
|
257
|
-
version:
|
258
279
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
259
280
|
requirements:
|
260
281
|
- - ">="
|
261
282
|
- !ruby/object:Gem::Version
|
283
|
+
segments:
|
284
|
+
- 0
|
262
285
|
version: "0"
|
263
|
-
version:
|
264
286
|
requirements: []
|
265
287
|
|
266
288
|
rubyforge_project:
|
267
|
-
rubygems_version: 1.3.
|
289
|
+
rubygems_version: 1.3.6
|
268
290
|
signing_key:
|
269
291
|
specification_version: 3
|
270
292
|
summary: A CommonJS module implementation in Ruby for client-side JavaScript
|