modulr 0.1.0 → 0.2.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/README.markdown CHANGED
@@ -21,8 +21,13 @@ Usage
21
21
  `modulr` accepts a singular file as input (the _program_) on which is does static
22
22
  analysis to recursively resolve its dependencies.
23
23
 
24
- The program, its dependencies and a small, namespaced JavaScript library are concatenated into a single `js` file. This
25
- [improves load times by minimizing HTTP requests](http://developer.yahoo.com/performance/rules.html#num_http).
24
+ The program, its dependencies and a small, namespaced JavaScript library are
25
+ concatenated into a single `js` file. This improves load times by
26
+ [minimizing HTTP requests](http://developer.yahoo.com/performance/rules.html#num_http).
27
+ Further load time performance improvements are made possible by the built-in
28
+ [lazy evaluation](http://googlecode.blogspot.com/2009/09/gmail-for-mobile-html5-series-reducing.html)
29
+ option. Modules are delivered as JavaScript strings--instead of functions--and are
30
+ evaluated only when required.
26
31
 
27
32
  The bundled JavaScript library provides each module with the necessary `require`
28
33
  function and `exports` and `module` free variables.
data/Rakefile CHANGED
@@ -23,7 +23,7 @@ task :spec do
23
23
  begin
24
24
  puts File.basename(dir).center(80, "_")
25
25
  File.open(output, 'w') do |f|
26
- f << Modulr.ize(spec)
26
+ f << Modulr.ize(spec, {:lazy_eval => ['a']})
27
27
  end
28
28
  system("js -f #{output}")
29
29
  rescue => e
@@ -31,8 +31,8 @@ task :spec do
31
31
  puts "ERROR while #{phase} (#{e.class}):"
32
32
  puts e.message
33
33
  ensure
34
- FileUtils.rm(output)
35
- FileUtils.rm(system)
34
+ #FileUtils.rm(output)
35
+ #FileUtils.rm(system)
36
36
  puts
37
37
  puts
38
38
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0
data/assets/modulr.js CHANGED
@@ -16,7 +16,8 @@ var modulr = (function(global) {
16
16
 
17
17
  function require(identifier) {
18
18
  var fn, modObj,
19
- id = _references[PREFIX + identifier],
19
+ key = PREFIX + identifier,
20
+ id = _references[key] || key,
20
21
  expts = _exports[id];
21
22
 
22
23
  log('Required module "' + identifier + '".');
@@ -29,20 +30,22 @@ var modulr = (function(global) {
29
30
 
30
31
  fn = _modules[id];
31
32
  if (!fn) { throw 'Can\'t find module "' + identifier + '".'; }
33
+ if (typeof fn === 'string') {
34
+ fn = eval('function(require, exports, module) {' + fn + '}');
35
+ }
32
36
  fn(require, expts, modObj);
33
37
  }
34
38
  return expts;
35
39
  }
36
40
 
37
- function cache(identifier, id, fn) {
38
- log('Cached module "' + identifier + '".');
39
- id = PREFIX + id;
41
+ function cache(id, fn) {
42
+ var key = PREFIX + id;
40
43
 
41
- if (_modules[id]) {
42
- throw 'Can\'t overwrite module "' + identifier + '".';
44
+ log('Cached module "' + id + '".');
45
+ if (_modules[key]) {
46
+ throw 'Can\'t overwrite module "' + id + '".';
43
47
  }
44
- _modules[id] = fn;
45
- _references[PREFIX + identifier] = id;
48
+ _modules[key] = fn;
46
49
  }
47
50
 
48
51
  function alias(identifier, id) {
data/bin/modulrize CHANGED
@@ -7,17 +7,24 @@ options = {
7
7
  }
8
8
 
9
9
  opts = OptionParser.new do |opts|
10
- opts.banner = 'Usage: modulrize [options] FILE'
10
+ opts.banner = 'Usage: modulrize program.js [options] > output.js'
11
11
 
12
- opts.on('-o', '--output=FILE', 'Write the output to FILE. Defaults to stdout') do |output|
12
+ opts.on('-o', '--output=FILE', 'Write the output to FILE. Defaults to stdout.') do |output|
13
13
  options[:output] = File.open(output, 'w')
14
14
  end
15
15
 
16
- opts.on('-r', '--root=DIR', 'Set DIR as root directory. Defaults to the directory containing FILE') do |root|
16
+ opts.on('-r', '--root=DIR', 'Set DIR as root directory. Defaults to the directory containing FILE.') do |root|
17
17
  options[:root] = root
18
18
  end
19
19
 
20
- opts.on_tail('-h', '--help', 'Show this message') do
20
+ opts.on('--lazy-eval [MODULES]', Array,
21
+ 'Enable lazy evaluation of all JS modules or of those specified by MODULES.',
22
+ 'MODULES accepts a comma-separated list of identifiers.') do |modules|
23
+ modules = true unless modules
24
+ options[:lazy_eval] = modules
25
+ end
26
+
27
+ opts.on_tail('-h', '--help', 'Show this message.') do
21
28
  puts opts
22
29
  exit
23
30
  end
@@ -1,60 +1,49 @@
1
- require 'rkelly'
2
-
3
1
  module Modulr
4
2
  class Collector
5
- attr_reader :modules, :aliases, :main
3
+ attr_reader :modules, :main
6
4
 
7
- def initialize(root = nil)
8
- @root = root
9
- @modules = {}
10
- @aliases = {}
5
+ def initialize(options = {})
6
+ @root = options[:root]
7
+ @lazy_eval = options[:lazy_eval]
8
+ @modules = []
11
9
  end
12
10
 
13
11
  def parse_file(path)
14
12
  @src = File.read(path)
15
13
  @root ||= File.dirname(path)
16
14
  @main = JSModule.new(File.basename(path, '.js'), @root, path)
17
- modules[main.id] = main
18
- find_dependencies(@src, path)
15
+ modules << main
16
+ collect_dependencies(main)
19
17
  end
20
18
 
21
- def parser
22
- @parser ||= RKelly::Parser.new
23
- end
24
-
25
- def parse(src)
26
- parser.parse(src)
19
+ def to_js(buffer = '')
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)
24
+ else
25
+ js_module.to_js(buffer)
26
+ end
27
+ end
28
+ buffer << "\nmodulr.require('#{main.identifier}');\n"
27
29
  end
28
30
 
29
- def find_dependencies(src, path)
30
- parse(src).each do |exp|
31
- if is_a_require_expression?(exp)
32
- js_module = JSModule.from_expression(exp, @root, path)
33
- if cached_module = modules[js_module.id]
34
- if cached_module.identifier != js_module.identifier
35
- aliases[js_module.identifier] = js_module.id
36
- end
37
- else
38
- modules[js_module.id] = js_module
39
- find_dependencies(js_module.src, js_module.path)
31
+ private
32
+ def collect_dependencies(js_module)
33
+ js_module.dependencies.each do |dependency|
34
+ unless modules.include?(dependency)
35
+ modules << dependency
36
+ collect_dependencies(dependency)
40
37
  end
41
38
  end
42
39
  end
43
- end
44
-
45
- def is_a_require_expression?(node)
46
- node.is_a?(RKelly::Nodes::FunctionCallNode) &&
47
- node.value.is_a?(RKelly::Nodes::ResolveNode) &&
48
- node.value.value == 'require'
49
- end
50
-
51
- def to_js(buffer = '')
52
- buffer << File.read(PATH_TO_MODULR_JS);
53
- modules.each { |id, js_module| js_module.to_js(buffer) }
54
- aliases.each do |identifier, id|
55
- buffer << "modulr.alias('#{identifier}', '#{id}');\n"
40
+
41
+ def lazy_eval_module?(js_module)
42
+ return false unless @lazy_eval
43
+ return true if @lazy_eval === true
44
+ return true if @lazy_eval.include?(js_module.identifier)
45
+ return true if @lazy_eval.include?(js_module.id)
46
+ false
56
47
  end
57
- buffer << "\nmodulr.require('#{main.identifier}');\n"
58
- end
59
48
  end
60
49
  end
@@ -1,10 +1,28 @@
1
1
  module Modulr
2
2
  class JSModule
3
- def self.from_expression(exp, root, file)
4
- str = exp.arguments.first.value.first
5
- new(str.value[1...-1], root, file, str.line.to_i)
3
+ include Comparable
4
+
5
+ JS_ESCAPE_MAP = {
6
+ '\\' => '\\\\',
7
+ '</' => '<\/',
8
+ "\r\n" => '\n',
9
+ "\n" => '\n',
10
+ "\r" => '\n',
11
+ '"' => '\\"',
12
+ "'" => "\\'"
13
+ }
14
+
15
+ def self.parser
16
+ @dependency_finder ||= Parser.new
6
17
  end
7
-
18
+
19
+ def self.find_dependencies(js_module)
20
+ expressions = parser.get_require_expressions(js_module.src)
21
+ expressions.map do |exp|
22
+ new(exp[:identifier], js_module.root, js_module.path, exp[:line])
23
+ end
24
+ end
25
+
8
26
  attr_reader :identifier, :root, :terms, :file, :line
9
27
 
10
28
  def initialize(identifier, root, file=nil, line=nil)
@@ -15,6 +33,10 @@ module Modulr
15
33
  @terms = identifier.split('/').reject { |term| term == '' }
16
34
  raise ModuleIdentifierError.new(self) unless identifier_valid?
17
35
  end
36
+
37
+ def <=> (other_module)
38
+ id <=> other_module.id
39
+ end
18
40
 
19
41
  def inspect
20
42
  "#<#{self.class.name} \"#{identifier}\">"
@@ -26,8 +48,12 @@ module Modulr
26
48
 
27
49
  def id
28
50
  return @id if @id
29
- @id = File.expand_path(partial_path, directory)
30
- @id.sub!("#{File.expand_path(root)}/", '')
51
+ if top_level?
52
+ @id = identifier
53
+ else
54
+ @id = File.expand_path(partial_path, directory)
55
+ @id.sub!("#{File.expand_path(root)}/", '')
56
+ end
31
57
  end
32
58
 
33
59
  def relative?
@@ -51,9 +77,25 @@ module Modulr
51
77
  end
52
78
  end
53
79
 
80
+ def escaped_src
81
+ @escaped_src ||= src.gsub(/(\\|<\/|\r\n|[\n\r"'])/) {
82
+ JS_ESCAPE_MAP[$1]
83
+ }
84
+ end
85
+
86
+ def dependencies
87
+ @dependencies ||= self.class.find_dependencies(self)
88
+ end
89
+
54
90
  def to_js(buffer = '')
91
+ call_alias_js_function(buffer)
55
92
  fn = "function(require, exports, module) {\n#{src}\n}"
56
- buffer << "modulr.cache('#{identifier}', '#{id}', #{fn});"
93
+ buffer << "\nmodulr.cache('#{id}', #{fn});\n"
94
+ end
95
+
96
+ def to_js_string(buffer = '')
97
+ call_alias_js_function(buffer)
98
+ buffer << "\nmodulr.cache('#{id}', '#{escaped_src}');\n"
57
99
  end
58
100
 
59
101
  protected
@@ -62,10 +104,12 @@ module Modulr
62
104
  end
63
105
 
64
106
  def directory
107
+ relative? ? File.dirname(file) : root
108
+ end
109
+
110
+ def call_alias_js_function(buffer)
65
111
  if relative?
66
- File.dirname(file)
67
- else
68
- root
112
+ buffer << "\nmodulr.alias('#{identifier}', '#{id}');"
69
113
  end
70
114
  end
71
115
  end
@@ -0,0 +1,35 @@
1
+ require 'rkelly'
2
+
3
+ module Modulr
4
+ class Parser
5
+
6
+ def parse(src)
7
+ parser.parse(src)
8
+ end
9
+
10
+ def get_require_expressions(src)
11
+ nodes = parse(src)
12
+ nodes = nodes.select { |node| is_a_require_expression?(node) }
13
+ nodes.map { |node| normalize(node) }
14
+ end
15
+
16
+ private
17
+ def parser
18
+ @parser ||= RKelly::Parser.new
19
+ end
20
+
21
+ def is_a_require_expression?(node)
22
+ node.is_a?(RKelly::Nodes::FunctionCallNode) &&
23
+ node.value.is_a?(RKelly::Nodes::ResolveNode) &&
24
+ node.value.value == 'require'
25
+ end
26
+
27
+ def normalize(node)
28
+ str = node.arguments.first.value.first
29
+ {
30
+ :identifier => str.value[1...-1],
31
+ :line => str.line.to_i
32
+ }
33
+ end
34
+ end
35
+ end
data/lib/modulr.rb CHANGED
@@ -8,13 +8,14 @@ module Modulr
8
8
  end
9
9
 
10
10
  require 'modulr/js_module'
11
+ require 'modulr/parser'
11
12
  require 'modulr/collector'
12
13
  require 'modulr/version'
13
14
 
14
15
  PATH_TO_MODULR_JS = File.join(LIB_DIR, '..', 'assets', 'modulr.js')
15
16
 
16
17
  def self.ize(input_filename, options = {})
17
- collector = Collector.new(options[:root])
18
+ collector = Collector.new(options)
18
19
  collector.parse_file(input_filename)
19
20
  collector.to_js
20
21
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: modulr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobie Langel
@@ -38,6 +38,7 @@ files:
38
38
  - lib/modulr.rb
39
39
  - lib/modulr/collector.rb
40
40
  - lib/modulr/js_module.rb
41
+ - lib/modulr/parser.rb
41
42
  - lib/modulr/version.rb
42
43
  - vendor/rkelly/CHANGELOG.rdoc
43
44
  - vendor/rkelly/Manifest.txt