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 +7 -2
- data/Rakefile +3 -3
- data/VERSION +1 -1
- data/assets/modulr.js +11 -8
- data/bin/modulrize +11 -4
- data/lib/modulr/collector.rb +30 -41
- data/lib/modulr/js_module.rb +54 -10
- data/lib/modulr/parser.rb +35 -0
- data/lib/modulr.rb +2 -1
- metadata +2 -1
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
|
25
|
-
|
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.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
|
-
|
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(
|
38
|
-
|
39
|
-
id = PREFIX + id;
|
41
|
+
function cache(id, fn) {
|
42
|
+
var key = PREFIX + id;
|
40
43
|
|
41
|
-
|
42
|
-
|
44
|
+
log('Cached module "' + id + '".');
|
45
|
+
if (_modules[key]) {
|
46
|
+
throw 'Can\'t overwrite module "' + id + '".';
|
43
47
|
}
|
44
|
-
_modules[
|
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]
|
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.
|
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
|
data/lib/modulr/collector.rb
CHANGED
@@ -1,60 +1,49 @@
|
|
1
|
-
require 'rkelly'
|
2
|
-
|
3
1
|
module Modulr
|
4
2
|
class Collector
|
5
|
-
attr_reader :modules, :
|
3
|
+
attr_reader :modules, :main
|
6
4
|
|
7
|
-
def initialize(
|
8
|
-
@root = root
|
9
|
-
@
|
10
|
-
@
|
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
|
18
|
-
|
15
|
+
modules << main
|
16
|
+
collect_dependencies(main)
|
19
17
|
end
|
20
18
|
|
21
|
-
def
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
data/lib/modulr/js_module.rb
CHANGED
@@ -1,10 +1,28 @@
|
|
1
1
|
module Modulr
|
2
2
|
class JSModule
|
3
|
-
|
4
|
-
|
5
|
-
|
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
|
-
|
30
|
-
|
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 << "
|
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
|
-
|
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
|
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.
|
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
|