js-routes-zigexn 1.3.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.gitignore +60 -0
- data/.rspec +1 -0
- data/.travis.yml +60 -0
- data/Appraisals +16 -0
- data/CHANGELOG.md +112 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +20 -0
- data/Rakefile +33 -0
- data/Readme.md +234 -0
- data/app/assets/javascripts/js-routes.js.erb +2 -0
- data/gemfiles/rails32.gemfile +8 -0
- data/gemfiles/rails40.gemfile +8 -0
- data/gemfiles/rails40_sprockets3.gemfile +8 -0
- data/gemfiles/rails41.gemfile +8 -0
- data/gemfiles/rails41_sprockets3.gemfile +8 -0
- data/gemfiles/rails42.gemfile +8 -0
- data/gemfiles/rails42_sprockets3.gemfile +8 -0
- data/gemfiles/rails50.gemfile +8 -0
- data/gemfiles/rails50_sprockets3.gemfile +8 -0
- data/js-routes-zigexn.gemspec +36 -0
- data/lib/js-routes.rb +1 -0
- data/lib/js_routes/engine.rb +73 -0
- data/lib/js_routes/version.rb +3 -0
- data/lib/js_routes.rb +293 -0
- data/lib/routes.js +470 -0
- data/lib/routes.js.coffee +368 -0
- data/lib/tasks/js_routes.rake +8 -0
- data/spec/dummy/app/assets/javascripts/.gitkeep +0 -0
- data/spec/dummy/config/routes.rb +55 -0
- data/spec/js_routes/amd_compatibility_spec.rb +42 -0
- data/spec/js_routes/default_serializer_spec.rb +15 -0
- data/spec/js_routes/generated_javascript_spec.rb +83 -0
- data/spec/js_routes/options_spec.rb +450 -0
- data/spec/js_routes/rails_routes_compatibility_spec.rb +414 -0
- data/spec/js_routes/zzz_last_post_rails_init_spec.rb +135 -0
- data/spec/spec_helper.rb +121 -0
- data/spec/support/routes.rb +76 -0
- metadata +209 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'js_routes/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{js-routes-zigexn}
|
8
|
+
s.version = JsRoutes::VERSION
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["VietTH"]
|
12
|
+
s.description = %q{Generates javascript file that defines all Rails named routes as javascript helpers}
|
13
|
+
s.email = %q{vietth@zigexn.vn}
|
14
|
+
s.extra_rdoc_files = [
|
15
|
+
"LICENSE.txt"
|
16
|
+
]
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.homepage = %q{https://github.com/vietthventura/js-routes}
|
19
|
+
s.licenses = ["MIT"]
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
s.summary = %q{Brings Rails named routes to javascript}
|
22
|
+
|
23
|
+
s.add_runtime_dependency(%q<railties>, [">= 3.2"])
|
24
|
+
s.add_runtime_dependency(%q<sprockets-rails>)
|
25
|
+
s.add_development_dependency(%q<rspec>, [">= 3.0.0"])
|
26
|
+
s.add_development_dependency(%q<bundler>, [">= 1.1.0"])
|
27
|
+
s.add_development_dependency(%q<coffee-script>, [">= 0"])
|
28
|
+
s.add_development_dependency(%q<appraisal>, [">= 0.5.2"])
|
29
|
+
if defined?(JRUBY_VERSION)
|
30
|
+
s.add_development_dependency(%q<therubyrhino>, [">= 2.0.4"])
|
31
|
+
else
|
32
|
+
s.add_development_dependency(%q<byebug>)
|
33
|
+
s.add_development_dependency(%q<pry-byebug>)
|
34
|
+
s.add_development_dependency(%q<therubyracer>, [">= 0.12.3"])
|
35
|
+
end
|
36
|
+
end
|
data/lib/js-routes.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'js_routes'
|
@@ -0,0 +1,73 @@
|
|
1
|
+
class JsRoutesSprocketsExtension
|
2
|
+
def initialize(filename, &block)
|
3
|
+
@filename = filename
|
4
|
+
@source = block.call
|
5
|
+
end
|
6
|
+
|
7
|
+
def render(context, empty_hash_wtf)
|
8
|
+
self.class.run(@filename, @source, context)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.run(filename, source, context)
|
12
|
+
if context.logical_path == 'js-routes'
|
13
|
+
routes = Rails.root.join('config', 'routes.rb').to_s
|
14
|
+
context.depend_on(routes)
|
15
|
+
end
|
16
|
+
source
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.call(input)
|
20
|
+
filename = input[:filename]
|
21
|
+
source = input[:data]
|
22
|
+
context = input[:environment].context_class.new(input)
|
23
|
+
|
24
|
+
result = run(filename, source, context)
|
25
|
+
context.metadata.merge(data: result)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
class Engine < ::Rails::Engine
|
31
|
+
require 'sprockets/version'
|
32
|
+
v2 = Gem::Dependency.new('', ' ~> 2')
|
33
|
+
vgte3 = Gem::Dependency.new('', ' >= 3')
|
34
|
+
sprockets_version = Gem::Version.new(::Sprockets::VERSION).release
|
35
|
+
initializer_args = case sprockets_version
|
36
|
+
when -> (v) { v2.match?('', v) }
|
37
|
+
{ after: "sprockets.environment" }
|
38
|
+
when -> (v) { vgte3.match?('', v) }
|
39
|
+
{ after: :engines_blank_point, before: :finisher_hook }
|
40
|
+
else
|
41
|
+
raise StandardError, "Sprockets version #{sprockets_version} is not supported"
|
42
|
+
end
|
43
|
+
|
44
|
+
is_running_rails = defined?(Rails) && Rails.respond_to?(:version)
|
45
|
+
is_running_rails_32 = is_running_rails && Rails.version.match(/3\.2/)
|
46
|
+
|
47
|
+
initializer 'js-routes.dependent_on_routes', initializer_args do
|
48
|
+
case sprockets_version
|
49
|
+
when -> (v) { v2.match?('', v) },
|
50
|
+
-> (v) { vgte3.match?('', v) }
|
51
|
+
|
52
|
+
# It seems rails 3.2 is not working if
|
53
|
+
# `Rails.application.config.assets.configure` is used for
|
54
|
+
# registering preprocessor
|
55
|
+
if is_running_rails_32
|
56
|
+
Rails.application.assets.register_preprocessor(
|
57
|
+
"application/javascript",
|
58
|
+
JsRoutesSprocketsExtension,
|
59
|
+
)
|
60
|
+
else
|
61
|
+
# Other rails version, assumed newer
|
62
|
+
Rails.application.config.assets.configure do |config|
|
63
|
+
config.register_preprocessor(
|
64
|
+
"application/javascript",
|
65
|
+
JsRoutesSprocketsExtension,
|
66
|
+
)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
else
|
70
|
+
raise StandardError, "Sprockets version #{sprockets_version} is not supported"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/js_routes.rb
ADDED
@@ -0,0 +1,293 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'js_routes/engine' if defined?(Rails)
|
3
|
+
require 'js_routes/version'
|
4
|
+
|
5
|
+
class JsRoutes
|
6
|
+
|
7
|
+
#
|
8
|
+
# OPTIONS
|
9
|
+
#
|
10
|
+
|
11
|
+
DEFAULT_PATH = File.join('app','assets','javascripts','routes.js')
|
12
|
+
|
13
|
+
DEFAULTS = {
|
14
|
+
namespace: "Routes",
|
15
|
+
exclude: [],
|
16
|
+
include: //,
|
17
|
+
file: DEFAULT_PATH,
|
18
|
+
prefix: -> { Rails.application.config.relative_url_root || "" },
|
19
|
+
url_links: false,
|
20
|
+
camel_case: false,
|
21
|
+
default_url_options: {},
|
22
|
+
compact: false,
|
23
|
+
serializer: nil,
|
24
|
+
special_options_key: "_options",
|
25
|
+
application: -> { Rails.application }
|
26
|
+
}
|
27
|
+
|
28
|
+
NODE_TYPES = {
|
29
|
+
GROUP: 1,
|
30
|
+
CAT: 2,
|
31
|
+
SYMBOL: 3,
|
32
|
+
OR: 4,
|
33
|
+
STAR: 5,
|
34
|
+
LITERAL: 6,
|
35
|
+
SLASH: 7,
|
36
|
+
DOT: 8
|
37
|
+
}
|
38
|
+
|
39
|
+
LAST_OPTIONS_KEY = "options".freeze
|
40
|
+
FILTERED_DEFAULT_PARTS = [:controller, :action, :subdomain]
|
41
|
+
URL_OPTIONS = [:protocol, :domain, :host, :port, :subdomain]
|
42
|
+
|
43
|
+
class Configuration < Struct.new(*DEFAULTS.keys)
|
44
|
+
def initialize(attributes = nil)
|
45
|
+
assign(DEFAULTS)
|
46
|
+
return unless attributes
|
47
|
+
assign(attributes)
|
48
|
+
end
|
49
|
+
|
50
|
+
def assign(attributes)
|
51
|
+
attributes.each do |attribute, value|
|
52
|
+
value = value.call if value.is_a?(Proc)
|
53
|
+
send(:"#{attribute}=", value)
|
54
|
+
end
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
def [](attribute)
|
59
|
+
send(attribute)
|
60
|
+
end
|
61
|
+
|
62
|
+
def merge(attributes)
|
63
|
+
clone.assign(attributes)
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_hash
|
67
|
+
Hash[*members.zip(values).flatten(1)].symbolize_keys
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
# API
|
73
|
+
#
|
74
|
+
|
75
|
+
class << self
|
76
|
+
def setup(&block)
|
77
|
+
configuration.tap(&block) if block
|
78
|
+
end
|
79
|
+
|
80
|
+
def options
|
81
|
+
ActiveSupport::Deprecation.warn('JsRoutes.options method is deprecated use JsRoutes.configuration instead')
|
82
|
+
configuration
|
83
|
+
end
|
84
|
+
|
85
|
+
def configuration
|
86
|
+
@configuration ||= Configuration.new
|
87
|
+
end
|
88
|
+
|
89
|
+
def generate(opts = {})
|
90
|
+
new(opts).generate
|
91
|
+
end
|
92
|
+
|
93
|
+
def generate!(file_name=nil, opts = {})
|
94
|
+
if file_name.is_a?(Hash)
|
95
|
+
opts = file_name
|
96
|
+
file_name = opts[:file]
|
97
|
+
end
|
98
|
+
new(opts).generate!(file_name)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Under rails 3.1.1 and higher, perform a check to ensure that the
|
102
|
+
# full environment will be available during asset compilation.
|
103
|
+
# This is required to ensure routes are loaded.
|
104
|
+
def assert_usable_configuration!
|
105
|
+
if 3 == Rails::VERSION::MAJOR && !Rails.application.config.assets.initialize_on_precompile
|
106
|
+
raise("Cannot precompile js-routes unless environment is initialized. Please set config.assets.initialize_on_precompile to true.")
|
107
|
+
end
|
108
|
+
true
|
109
|
+
end
|
110
|
+
|
111
|
+
def json(string)
|
112
|
+
ActiveSupport::JSON.encode(string)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
#
|
117
|
+
# Implementation
|
118
|
+
#
|
119
|
+
|
120
|
+
def initialize(options = {})
|
121
|
+
@configuration = self.class.configuration.merge(options)
|
122
|
+
end
|
123
|
+
|
124
|
+
def generate
|
125
|
+
# Ensure routes are loaded. If they're not, load them.
|
126
|
+
if named_routes.to_a.empty? && application.respond_to?(:reload_routes!)
|
127
|
+
application.reload_routes!
|
128
|
+
end
|
129
|
+
|
130
|
+
{
|
131
|
+
"GEM_VERSION" => JsRoutes::VERSION,
|
132
|
+
"ROUTES" => js_routes,
|
133
|
+
"DEPRECATED_BEHAVIOR" => Rails.version < "4",
|
134
|
+
"NODE_TYPES" => json(NODE_TYPES),
|
135
|
+
|
136
|
+
"APP_CLASS" => application.class.to_s,
|
137
|
+
"NAMESPACE" => json(@configuration.namespace),
|
138
|
+
"DEFAULT_URL_OPTIONS" => json(@configuration.default_url_options),
|
139
|
+
"PREFIX" => json(@configuration.prefix),
|
140
|
+
"SPECIAL_OPTIONS_KEY" => json(@configuration.special_options_key),
|
141
|
+
"SERIALIZER" => @configuration.serializer || json(nil),
|
142
|
+
}.inject(File.read(File.dirname(__FILE__) + "/routes.js")) do |js, (key, value)|
|
143
|
+
js.gsub!(key, value.to_s)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def generate!(file_name = nil)
|
148
|
+
# Some libraries like Devise do not yet loaded their routes so we will wait
|
149
|
+
# until initialization process finish
|
150
|
+
# https://github.com/railsware/js-routes/issues/7
|
151
|
+
Rails.configuration.after_initialize do
|
152
|
+
file_name ||= self.class.configuration['file']
|
153
|
+
File.open(Rails.root.join(file_name), 'w') do |f|
|
154
|
+
f.write generate
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
protected
|
160
|
+
|
161
|
+
def application
|
162
|
+
@configuration.application
|
163
|
+
end
|
164
|
+
|
165
|
+
def named_routes
|
166
|
+
application.routes.named_routes.to_a
|
167
|
+
end
|
168
|
+
|
169
|
+
def js_routes
|
170
|
+
js_routes = named_routes.sort_by(&:first).flat_map do |_, route|
|
171
|
+
[build_route_if_match(route)] + mounted_app_routes(route)
|
172
|
+
end.compact
|
173
|
+
"{\n" + js_routes.join(",\n") + "}\n"
|
174
|
+
end
|
175
|
+
|
176
|
+
def mounted_app_routes(route)
|
177
|
+
rails_engine_app = get_app_from_route(route)
|
178
|
+
if rails_engine_app.respond_to?(:superclass) && rails_engine_app.superclass == Rails::Engine && !route.path.anchored
|
179
|
+
rails_engine_app.routes.named_routes.map do |_, engine_route|
|
180
|
+
build_route_if_match(engine_route, route)
|
181
|
+
end
|
182
|
+
else
|
183
|
+
[]
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def get_app_from_route(route)
|
188
|
+
# rails engine in Rails 4.2 use additional ActionDispatch::Routing::Mapper::Constraints, which contain app
|
189
|
+
if route.app.respond_to?(:app) && route.app.respond_to?(:constraints)
|
190
|
+
route.app.app
|
191
|
+
else
|
192
|
+
route.app
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def build_route_if_match(route, parent_route=nil)
|
197
|
+
if any_match?(route, parent_route, @configuration[:exclude]) || !any_match?(route, parent_route, @configuration[:include])
|
198
|
+
nil
|
199
|
+
else
|
200
|
+
build_js(route, parent_route)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def any_match?(route, parent_route, matchers)
|
205
|
+
full_route = [parent_route.try(:name), route.name].compact.join('_')
|
206
|
+
|
207
|
+
matchers = Array(matchers)
|
208
|
+
matchers.any? {|regex| full_route =~ regex}
|
209
|
+
end
|
210
|
+
|
211
|
+
def build_js(route, parent_route)
|
212
|
+
name = [parent_route.try(:name), route.name].compact
|
213
|
+
route_name = generate_route_name(name, (:path unless @configuration[:compact]))
|
214
|
+
parent_spec = parent_route.try(:path).try(:spec)
|
215
|
+
route_arguments = route_js_arguments(route, parent_spec)
|
216
|
+
url_link = generate_url_link(name, route_name, route_arguments, route)
|
217
|
+
_ = <<-JS.strip!
|
218
|
+
// #{name.join('.')} => #{parent_spec}#{route.path.spec}
|
219
|
+
// function(#{build_params(route.required_parts)})
|
220
|
+
#{route_name}: Utils.route(#{route_arguments})#{",\n" + url_link if url_link.length > 0}
|
221
|
+
JS
|
222
|
+
end
|
223
|
+
|
224
|
+
def route_js_arguments(route, parent_spec)
|
225
|
+
required_parts = route.required_parts
|
226
|
+
parts_table = route.parts.each_with_object({}) do |part, hash|
|
227
|
+
hash[part] = required_parts.include?(part)
|
228
|
+
end
|
229
|
+
default_options = route.defaults.select do |part, _|
|
230
|
+
FILTERED_DEFAULT_PARTS.exclude?(part) && URL_OPTIONS.include?(part) || parts_table[part]
|
231
|
+
end
|
232
|
+
[
|
233
|
+
# JS objects don't preserve the order of properties which is crucial,
|
234
|
+
# so array is a better choice.
|
235
|
+
parts_table.to_a,
|
236
|
+
default_options,
|
237
|
+
serialize(route.path.spec, parent_spec)
|
238
|
+
].map do |argument|
|
239
|
+
json(argument)
|
240
|
+
end.join(", ")
|
241
|
+
end
|
242
|
+
|
243
|
+
def generate_url_link(name, route_name, route_arguments, route)
|
244
|
+
return "" unless @configuration[:url_links]
|
245
|
+
<<-JS.strip!
|
246
|
+
#{generate_route_name(name, :url)}: Utils.route(#{route_arguments}, true)
|
247
|
+
JS
|
248
|
+
end
|
249
|
+
|
250
|
+
def generate_route_name(name, suffix)
|
251
|
+
route_name = name.join('_')
|
252
|
+
route_name << "_#{ suffix }" if suffix
|
253
|
+
@configuration[:camel_case] ? route_name.camelize(:lower) : route_name
|
254
|
+
end
|
255
|
+
|
256
|
+
def json(string)
|
257
|
+
self.class.json(string)
|
258
|
+
end
|
259
|
+
|
260
|
+
def build_params(required_parts)
|
261
|
+
params = required_parts + [LAST_OPTIONS_KEY]
|
262
|
+
params.join(", ")
|
263
|
+
end
|
264
|
+
|
265
|
+
# This function serializes Journey route into JSON structure
|
266
|
+
# We do not use Hash for human readable serialization
|
267
|
+
# And preffer Array serialization because it is shorter.
|
268
|
+
# Routes.js file will be smaller.
|
269
|
+
def serialize(spec, parent_spec=nil)
|
270
|
+
return nil unless spec
|
271
|
+
return spec.tr(':', '') if spec.is_a?(String)
|
272
|
+
result = serialize_spec(spec, parent_spec)
|
273
|
+
if parent_spec && result[1].is_a?(String)
|
274
|
+
result = [
|
275
|
+
# We encode node symbols as integer
|
276
|
+
# to reduce the routes.js file size
|
277
|
+
NODE_TYPES[:CAT],
|
278
|
+
serialize_spec(parent_spec),
|
279
|
+
result
|
280
|
+
]
|
281
|
+
end
|
282
|
+
result
|
283
|
+
end
|
284
|
+
|
285
|
+
def serialize_spec(spec, parent_spec=nil)
|
286
|
+
[
|
287
|
+
NODE_TYPES[spec.type],
|
288
|
+
serialize(spec.left, parent_spec),
|
289
|
+
spec.respond_to?(:right) && serialize(spec.right)
|
290
|
+
]
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|