modulr 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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