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 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(output, 'w') do |f|
26
- f << Modulr.ize(spec)
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/tobie/modulr"
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.3.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 _modules = {},
8
- _moduleObjects = {},
7
+ var _dependencyGraph = {},
8
+ _loadingFactories = {},
9
+ _incompleteFactories = {},
10
+ _factories = {},
11
+ _modules = {},
9
12
  _exports = {},
10
- _oldDir = '',
11
- _currentDir = '',
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
- function log(str) {
16
- if (global.console && console.log) { console.log(str); }
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, modObj,
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
- _moduleObjects[key] = modObj = { id: id };
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, modObj);
43
- _currentDir = _oldDir;
89
+ fn(require, expts, mod);
90
+ _dirStack.pop();
44
91
  } catch(e) {
45
- _currentDir = _oldDir;
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 = (_currentDir + '/' + identifier).split('/');
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 cache(id, fn) {
79
- var key = PREFIX + id;
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
- log('Cached module "' + id + '".');
82
- if (_modules[key]) {
83
- throw 'Can\'t overwrite module "' + id + '".';
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.on_tail('-h', '--help', 'Show this message.') do
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
- result = Modulr.ize(ARGV.first, options)
43
- output.print(result)
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
@@ -18,14 +18,22 @@ module Modulr
18
18
 
19
19
  def to_js(buffer = '')
20
20
  buffer << File.read(PATH_TO_MODULR_JS)
21
- modules.each do |js_module|
22
- if lazy_eval_module?(js_module)
23
- js_module.to_js_string(buffer)
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
- js_module.to_js(buffer)
32
+ value = m.factory
26
33
  end
34
+ "\n'#{m.id}': #{value}"
27
35
  end
28
- buffer << "\nmodulr.require('#{main.identifier}');\n"
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
@@ -17,9 +17,17 @@ module Modulr
17
17
  end
18
18
 
19
19
  def self.find_dependencies(js_module)
20
- expressions = parser.get_require_expressions(js_module.src)
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
- new(exp[:identifier], js_module.root, js_module.path, exp[:line])
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 to_js_string(buffer = '')
96
- buffer << "\nmodulr.cache('#{id}', '#{escaped_src}');\n"
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
- parser.parse(src)
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
- str = node.arguments.first.value.first
34
+ arg = node.arguments.value.first
35
+ valid = arg.is_a?(RKelly::Nodes::StringNode)
29
36
  {
30
- :identifier => str.value[1...-1],
31
- :line => str.line.to_i
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
- collector = Collector.new(options)
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
- version: 0.3.0
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-03-11 00:00:00 +01:00
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/tobie/modulr
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.5
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