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 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