Permalink
Browse files

Add lazy-eval option.

  • Loading branch information...
1 parent ea93c70 commit 460eb96ee188f4e671fe52bc24f30b14f592d36c @tobie committed Mar 10, 2010
Showing with 88 additions and 26 deletions.
  1. +7 −2 README.markdown
  2. +1 −1 VERSION
  3. +3 −0 assets/modulr.js
  4. +11 −4 bin/modulrize
  5. +1 −1 lib/modulr.rb
  6. +31 −15 lib/modulr/collector.rb
  7. +34 −3 lib/modulr/js_module.rb
View
@@ -21,8 +21,13 @@ Usage
`modulr` accepts a singular file as input (the _program_) on which is does static
analysis to recursively resolve its dependencies.
-The program, its dependencies and a small, namespaced JavaScript library are concatenated into a single `js` file. This
-[improves load times by minimizing HTTP requests](http://developer.yahoo.com/performance/rules.html#num_http).
+The program, its dependencies and a small, namespaced JavaScript library are
+concatenated into a single `js` file. This improves load times by
+[minimizing HTTP requests](http://developer.yahoo.com/performance/rules.html#num_http).
+Further load time performance improvements are made possible by the built-in
+[lazy evaluation](http://googlecode.blogspot.com/2009/09/gmail-for-mobile-html5-series-reducing.html)
+option. Modules are delivered as JavaScript strings--instead of functions--and are
+evaluated only when required.
The bundled JavaScript library provides each module with the necessary `require`
function and `exports` and `module` free variables.
View
@@ -1 +1 @@
-0.1.1
+0.2.0
View
@@ -30,6 +30,9 @@ var modulr = (function(global) {
fn = _modules[id];
if (!fn) { throw 'Can\'t find module "' + identifier + '".'; }
+ if (typeof fn === 'string') {
+ fn = eval('function(require, exports, module) {' + fn + '}');
+ }
fn(require, expts, modObj);
}
return expts;
View
@@ -7,17 +7,24 @@ options = {
}
opts = OptionParser.new do |opts|
- opts.banner = 'Usage: modulrize [options] FILE'
+ opts.banner = 'Usage: modulrize program.js [options] > output.js'
- opts.on('-o', '--output=FILE', 'Write the output to FILE. Defaults to stdout') do |output|
+ opts.on('-o', '--output=FILE', 'Write the output to FILE. Defaults to stdout.') do |output|
options[:output] = File.open(output, 'w')
end
- opts.on('-r', '--root=DIR', 'Set DIR as root directory. Defaults to the directory containing FILE') do |root|
+ opts.on('-r', '--root=DIR', 'Set DIR as root directory. Defaults to the directory containing FILE.') do |root|
options[:root] = root
end
- opts.on_tail('-h', '--help', 'Show this message') do
+ opts.on('--lazy-eval [MODULES]', Array,
+ 'Enable lazy evaluation of all JS modules or of those specified by MODULES.',
+ 'MODULES accepts a comma-separated list of identifiers.') do |modules|
+ modules = true unless modules
+ options[:lazy_eval] = modules
+ end
+
+ opts.on_tail('-h', '--help', 'Show this message.') do
puts opts
exit
end
View
@@ -15,7 +15,7 @@ class ModulrError < StandardError
PATH_TO_MODULR_JS = File.join(LIB_DIR, '..', 'assets', 'modulr.js')
def self.ize(input_filename, options = {})
- collector = Collector.new(options[:root])
+ collector = Collector.new(options)
collector.parse_file(input_filename)
collector.to_js
end
@@ -2,32 +2,48 @@ module Modulr
class Collector
attr_reader :modules, :main
- def initialize(root = nil)
- @root = root
- @modules = {}
+ def initialize(options = {})
+ @root = options[:root]
+ @lazy_eval = options[:lazy_eval]
+ @modules = []
end
def parse_file(path)
@src = File.read(path)
@root ||= File.dirname(path)
@main = JSModule.new(File.basename(path, '.js'), @root, path)
- modules[main.id] = main
+ modules << main
collect_dependencies(main)
end
-
- def collect_dependencies(js_module)
- js_module.dependencies.each do |dependency|
- unless modules[dependency.id]
- modules[dependency.id] = dependency
- collect_dependencies(dependency)
- end
- end
- end
def to_js(buffer = '')
- buffer << File.read(PATH_TO_MODULR_JS);
- modules.each { |id, js_module| js_module.to_js(buffer) }
+ buffer << File.read(PATH_TO_MODULR_JS)
+ modules.each do |js_module|
+ if lazy_eval_module?(js_module)
+ js_module.to_js_string(buffer)
+ else
+ js_module.to_js(buffer)
+ end
+ end
buffer << "\nmodulr.require('#{main.identifier}');\n"
end
+
+ private
+ def collect_dependencies(js_module)
+ js_module.dependencies.each do |dependency|
+ unless modules.include?(dependency)
+ modules << dependency
+ collect_dependencies(dependency)
+ end
+ end
+ end
+
+ def lazy_eval_module?(js_module)
+ return false unless @lazy_eval
+ return true if @lazy_eval === true
+ return true if @lazy_eval.include?(js_module.identifier)
+ return true if @lazy_eval.include?(js_module.id)
+ false
+ end
end
end
@@ -1,5 +1,17 @@
module Modulr
class JSModule
+ include Comparable
+
+ JS_ESCAPE_MAP = {
+ '\\' => '\\\\',
+ '</' => '<\/',
+ "\r\n" => '\n',
+ "\n" => '\n',
+ "\r" => '\n',
+ '"' => '\\"',
+ "'" => "\\'"
+ }
+
def self.parser
@dependency_finder ||= Parser.new
end
@@ -21,6 +33,10 @@ def initialize(identifier, root, file=nil, line=nil)
@terms = identifier.split('/').reject { |term| term == '' }
raise ModuleIdentifierError.new(self) unless identifier_valid?
end
+
+ def <=> (other_module)
+ id <=> other_module.id
+ end
def inspect
"#<#{self.class.name} \"#{identifier}\">"
@@ -61,18 +77,27 @@ def src
end
end
+ def escaped_src
+ @escaped_src ||= src.gsub(/(\\|<\/|\r\n|[\n\r"'])/) {
+ JS_ESCAPE_MAP[$1]
+ }
+ end
+
def dependencies
@dependencies ||= self.class.find_dependencies(self)
end
def to_js(buffer = '')
- if relative?
- buffer << "\nmodulr.alias('#{identifier}', '#{id}');"
- end
+ call_alias_js_function(buffer)
fn = "function(require, exports, module) {\n#{src}\n}"
buffer << "\nmodulr.cache('#{id}', #{fn});\n"
end
+ def to_js_string(buffer = '')
+ call_alias_js_function(buffer)
+ buffer << "\nmodulr.cache('#{id}', '#{escaped_src}');\n"
+ end
+
protected
def partial_path
File.join(*terms)
@@ -81,6 +106,12 @@ def partial_path
def directory
relative? ? File.dirname(file) : root
end
+
+ def call_alias_js_function(buffer)
+ if relative?
+ buffer << "\nmodulr.alias('#{identifier}', '#{id}');"
+ end
+ end
end
class ModuleIdentifierError < ModulrError

0 comments on commit 460eb96

Please sign in to comment.