modulr 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|