js-routes 2.2.0 → 2.2.3

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.
@@ -0,0 +1,172 @@
1
+ module JsRoutes
2
+ class Route #:nodoc:
3
+ FILTERED_DEFAULT_PARTS = [:controller, :action]
4
+ URL_OPTIONS = [:protocol, :domain, :host, :port, :subdomain]
5
+ NODE_TYPES = {
6
+ GROUP: 1,
7
+ CAT: 2,
8
+ SYMBOL: 3,
9
+ OR: 4,
10
+ STAR: 5,
11
+ LITERAL: 6,
12
+ SLASH: 7,
13
+ DOT: 8
14
+ }
15
+
16
+ attr_reader :configuration, :route, :parent_route
17
+
18
+ def initialize(configuration, route, parent_route = nil)
19
+ @configuration = configuration
20
+ @route = route
21
+ @parent_route = parent_route
22
+ end
23
+
24
+ def helpers
25
+ helper_types.map do |absolute|
26
+ [ documentation, helper_name(absolute), body(absolute) ]
27
+ end
28
+ end
29
+
30
+ def helper_types
31
+ return [] unless match_configuration?
32
+ @configuration[:url_links] ? [true, false] : [false]
33
+ end
34
+
35
+ def body(absolute)
36
+ @configuration.dts? ?
37
+ definition_body : "__jsr.r(#{arguments(absolute).map{|a| json(a)}.join(', ')})"
38
+ end
39
+
40
+ def definition_body
41
+ args = required_parts.map{|p| "#{apply_case(p)}: RequiredRouteParameter"}
42
+ args << "options?: #{optional_parts_type} & RouteOptions"
43
+ "((\n#{args.join(",\n").indent(2)}\n) => string) & RouteHelperExtras"
44
+ end
45
+
46
+ def optional_parts_type
47
+ @optional_parts_type ||=
48
+ "{" + optional_parts.map {|p| "#{p}?: OptionalRouteParameter"}.join(', ') + "}"
49
+ end
50
+
51
+ protected
52
+
53
+ def arguments(absolute)
54
+ absolute ? [*base_arguments, true] : base_arguments
55
+ end
56
+
57
+ def match_configuration?
58
+ !match?(@configuration[:exclude]) && match?(@configuration[:include])
59
+ end
60
+
61
+ def base_name
62
+ @base_name ||= parent_route ?
63
+ [parent_route.name, route.name].join('_') : route.name
64
+ end
65
+
66
+ def parent_spec
67
+ parent_route&.path&.spec
68
+ end
69
+
70
+ def spec
71
+ route.path.spec
72
+ end
73
+
74
+ def json(value)
75
+ JsRoutes.json(value)
76
+ end
77
+
78
+ def helper_name(absolute)
79
+ suffix = absolute ? :url : @configuration[:compact] ? nil : :path
80
+ apply_case(base_name, suffix)
81
+ end
82
+
83
+ def documentation
84
+ return nil unless @configuration[:documentation]
85
+ <<-JS
86
+ /**
87
+ * Generates rails route to
88
+ * #{parent_spec}#{spec}#{documentation_params}
89
+ * @param {object | undefined} options
90
+ * @returns {string} route path
91
+ */
92
+ JS
93
+ end
94
+
95
+ def required_parts
96
+ route.required_parts
97
+ end
98
+
99
+ def optional_parts
100
+ route.path.optional_names
101
+ end
102
+
103
+ def base_arguments
104
+ @base_arguments ||= [parts_table, serialize(spec, parent_spec)]
105
+ end
106
+
107
+ def parts_table
108
+ parts_table = {}
109
+ route.parts.each do |part, hash|
110
+ parts_table[part] ||= {}
111
+ if required_parts.include?(part)
112
+ # Using shortened keys to reduce js file size
113
+ parts_table[part][:r] = true
114
+ end
115
+ end
116
+ route.defaults.each do |part, value|
117
+ if FILTERED_DEFAULT_PARTS.exclude?(part) &&
118
+ URL_OPTIONS.include?(part) || parts_table[part]
119
+ parts_table[part] ||= {}
120
+ # Using shortened keys to reduce js file size
121
+ parts_table[part][:d] = value
122
+ end
123
+ end
124
+ parts_table
125
+ end
126
+
127
+ def documentation_params
128
+ required_parts.map do |param|
129
+ "\n * @param {any} #{apply_case(param)}"
130
+ end.join
131
+ end
132
+
133
+ def match?(matchers)
134
+ Array(matchers).any? { |regex| base_name =~ regex }
135
+ end
136
+
137
+ def apply_case(*values)
138
+ value = values.compact.map(&:to_s).join('_')
139
+ @configuration[:camel_case] ? value.camelize(:lower) : value
140
+ end
141
+
142
+ # This function serializes Journey route into JSON structure
143
+ # We do not use Hash for human readable serialization
144
+ # And preffer Array serialization because it is shorter.
145
+ # Routes.js file will be smaller.
146
+ def serialize(spec, parent_spec=nil)
147
+ return nil unless spec
148
+ # Rails 4 globbing requires * removal
149
+ return spec.tr(':*', '') if spec.is_a?(String)
150
+
151
+ result = serialize_spec(spec, parent_spec)
152
+ if parent_spec && result[1].is_a?(String) && parent_spec.type != :SLASH
153
+ result = [
154
+ # We encode node symbols as integer
155
+ # to reduce the routes.js file size
156
+ NODE_TYPES[:CAT],
157
+ serialize_spec(parent_spec),
158
+ result
159
+ ]
160
+ end
161
+ result
162
+ end
163
+
164
+ def serialize_spec(spec, parent_spec = nil)
165
+ [
166
+ NODE_TYPES[spec.type],
167
+ serialize(spec.left, parent_spec),
168
+ spec.respond_to?(:right) ? serialize(spec.right) : nil
169
+ ].compact
170
+ end
171
+ end
172
+ end
@@ -1,3 +1,3 @@
1
- class JsRoutes
2
- VERSION = "2.2.0"
1
+ module JsRoutes
2
+ VERSION = "2.2.3"
3
3
  end
data/lib/js_routes.rb CHANGED
@@ -1,109 +1,12 @@
1
- require 'uri'
2
- if defined?(::Rails) && defined?(::Sprockets::Railtie)
1
+ if defined?(::Rails)
3
2
  require 'js_routes/engine'
4
3
  end
5
4
  require 'js_routes/version'
5
+ require "js_routes/configuration"
6
+ require "js_routes/instance"
6
7
  require 'active_support/core_ext/string/indent'
7
8
 
8
- class JsRoutes
9
-
10
- class Configuration
11
- DEFAULTS = {
12
- namespace: nil,
13
- exclude: [],
14
- include: //,
15
- file: nil,
16
- prefix: -> { Rails.application.config.relative_url_root || "" },
17
- url_links: false,
18
- camel_case: false,
19
- default_url_options: {},
20
- compact: false,
21
- serializer: nil,
22
- special_options_key: "_options",
23
- application: -> { Rails.application },
24
- module_type: 'ESM',
25
- documentation: true,
26
- } #:nodoc:
27
-
28
- attr_accessor(*DEFAULTS.keys)
29
-
30
- def initialize(attributes = nil)
31
- assign(DEFAULTS)
32
- return unless attributes
33
- assign(attributes)
34
- end
35
-
36
- def assign(attributes)
37
- attributes.each do |attribute, value|
38
- value = value.call if value.is_a?(Proc)
39
- send(:"#{attribute}=", value)
40
- end
41
- normalize_and_verify
42
- self
43
- end
44
-
45
- def [](attribute)
46
- send(attribute)
47
- end
48
-
49
- def merge(attributes)
50
- clone.assign(attributes)
51
- end
52
-
53
- def to_hash
54
- Hash[*members.zip(values).flatten(1)].symbolize_keys
55
- end
56
-
57
- def esm?
58
- module_type === 'ESM'
59
- end
60
-
61
- def dts?
62
- self.module_type === 'DTS'
63
- end
64
-
65
- def modern?
66
- esm? || dts?
67
- end
68
-
69
- def require_esm
70
- raise "ESM module type is required" unless modern?
71
- end
72
-
73
- def source_file
74
- File.dirname(__FILE__) + "/" + default_file_name
75
- end
76
-
77
- def output_file
78
- webpacker_dir = Rails.root.join('app', 'javascript')
79
- sprockets_dir = Rails.root.join('app','assets','javascripts')
80
- file_name = file || default_file_name
81
- sprockets_file = sprockets_dir.join(file_name)
82
- webpacker_file = webpacker_dir.join(file_name)
83
- !Dir.exist?(webpacker_dir) && defined?(::Sprockets) ? sprockets_file : webpacker_file
84
- end
85
-
86
- def normalize_and_verify
87
- normalize
88
- verify
89
- end
90
-
91
- protected
92
-
93
- def default_file_name
94
- dts? ? "routes.d.ts" : "routes.js"
95
- end
96
-
97
- def normalize
98
- self.module_type = module_type&.upcase || 'NIL'
99
- end
100
-
101
- def verify
102
- if module_type != 'NIL' && namespace
103
- raise "JsRoutes namespace option can only be used if module_type is nil"
104
- end
105
- end
106
- end
9
+ module JsRoutes
107
10
 
108
11
  #
109
12
  # API
@@ -111,8 +14,7 @@ class JsRoutes
111
14
 
112
15
  class << self
113
16
  def setup(&block)
114
- configuration.tap(&block) if block
115
- configuration.normalize_and_verify
17
+ configuration.assign(&block)
116
18
  end
117
19
 
118
20
  def configuration
@@ -120,11 +22,11 @@ class JsRoutes
120
22
  end
121
23
 
122
24
  def generate(**opts)
123
- new(opts).generate
25
+ Instance.new(opts).generate
124
26
  end
125
27
 
126
- def generate!(file_name=nil, **opts)
127
- new(file: file_name, **opts).generate!
28
+ def generate!(file_name = configuration.file, **opts)
29
+ Instance.new(file: file_name, **opts).generate!
128
30
  end
129
31
 
130
32
  def definitions(**opts)
@@ -140,323 +42,6 @@ class JsRoutes
140
42
  ActiveSupport::JSON.encode(string)
141
43
  end
142
44
  end
143
-
144
- #
145
- # Implementation
146
- #
147
-
148
- def initialize(options = {})
149
- @configuration = self.class.configuration.merge(options)
150
- end
151
-
152
- def generate
153
- # Ensure routes are loaded. If they're not, load them.
154
- if named_routes.empty? && application.respond_to?(:reload_routes!)
155
- application.reload_routes!
156
- end
157
- content = File.read(@configuration.source_file)
158
-
159
- if !@configuration.dts?
160
- content = js_variables.inject(content) do |js, (key, value)|
161
- js.gsub!("RubyVariables.#{key}", value.to_s) ||
162
- raise("Missing key #{key} in JS template")
163
- end
164
- end
165
- content + routes_export + prevent_types_export
166
- end
167
-
168
- def generate!
169
- # Some libraries like Devise did not load their routes yet
170
- # so we will wait until initialization process finishes
171
- # https://github.com/railsware/js-routes/issues/7
172
- Rails.configuration.after_initialize do
173
- file_path = Rails.root.join(@configuration.output_file)
174
- source_code = generate
175
-
176
- # We don't need to rewrite file if it already exist and have same content.
177
- # It helps asset pipeline or webpack understand that file wasn't changed.
178
- next if File.exist?(file_path) && File.read(file_path) == source_code
179
-
180
- File.open(file_path, 'w') do |f|
181
- f.write source_code
182
- end
183
- end
184
- end
185
-
186
- protected
187
-
188
- def js_variables
189
- {
190
- 'GEM_VERSION' => JsRoutes::VERSION,
191
- 'ROUTES_OBJECT' => routes_object,
192
- 'RAILS_VERSION' => ActionPack.version,
193
- 'DEPRECATED_GLOBBING_BEHAVIOR' => ActionPack::VERSION::MAJOR == 4 && ActionPack::VERSION::MINOR == 0,
194
-
195
- 'APP_CLASS' => application.class.to_s,
196
- 'NAMESPACE' => json(@configuration.namespace),
197
- 'DEFAULT_URL_OPTIONS' => json(@configuration.default_url_options),
198
- 'PREFIX' => json(@configuration.prefix),
199
- 'SPECIAL_OPTIONS_KEY' => json(@configuration.special_options_key),
200
- 'SERIALIZER' => @configuration.serializer || json(nil),
201
- 'MODULE_TYPE' => json(@configuration.module_type),
202
- 'WRAPPER' => @configuration.esm? ? 'const __jsr = ' : '',
203
- }
204
- end
205
-
206
- def application
207
- @configuration.application
208
- end
209
-
210
- def json(string)
211
- self.class.json(string)
212
- end
213
-
214
- def named_routes
215
- application.routes.named_routes.to_a
216
- end
217
-
218
- def routes_object
219
- return json({}) if @configuration.modern?
220
- properties = routes_list.map do |comment, name, body|
221
- "#{comment}#{name}: #{body}".indent(2)
222
- end
223
- "{\n" + properties.join(",\n\n") + "}\n"
224
- end
225
-
226
- def static_exports
227
- [:configure, :config, :serialize].map do |name|
228
- [
229
- "", name,
230
- @configuration.dts? ?
231
- "RouterExposedMethods['#{name}']" :
232
- "__jsr.#{name}"
233
- ]
234
- end
235
- end
236
-
237
- def routes_export
238
- return "" unless @configuration.modern?
239
- [*static_exports, *routes_list].map do |comment, name, body|
240
- "#{comment}export const #{name}#{export_separator}#{body};\n\n"
241
- end.join
242
- end
243
-
244
- def prevent_types_export
245
- return "" unless @configuration.dts?
246
- <<-JS
247
- // By some reason this line prevents all types in a file
248
- // from being automatically exported
249
- export {};
250
- JS
251
- end
252
-
253
- def export_separator
254
- @configuration.dts? ? ': ' : ' = '
255
- end
256
-
257
- def routes_list
258
- named_routes.sort_by(&:first).flat_map do |_, route|
259
- route_helpers_if_match(route) + mounted_app_routes(route)
260
- end
261
- end
262
-
263
- def mounted_app_routes(route)
264
- rails_engine_app = app_from_route(route)
265
- if rails_engine_app.respond_to?(:superclass) &&
266
- rails_engine_app.superclass == Rails::Engine && !route.path.anchored
267
- rails_engine_app.routes.named_routes.flat_map do |_, engine_route|
268
- route_helpers_if_match(engine_route, route)
269
- end
270
- else
271
- []
272
- end
273
- end
274
-
275
- def app_from_route(route)
276
- app = route.app
277
- # rails engine in Rails 4.2 use additional
278
- # ActionDispatch::Routing::Mapper::Constraints, which contain app
279
- if app.respond_to?(:app) && app.respond_to?(:constraints)
280
- app.app
281
- else
282
- app
283
- end
284
- end
285
-
286
- def route_helpers_if_match(route, parent_route = nil)
287
- JsRoute.new(@configuration, route, parent_route).helpers
288
- end
289
-
290
- class JsRoute #:nodoc:
291
- FILTERED_DEFAULT_PARTS = [:controller, :action]
292
- URL_OPTIONS = [:protocol, :domain, :host, :port, :subdomain]
293
- NODE_TYPES = {
294
- GROUP: 1,
295
- CAT: 2,
296
- SYMBOL: 3,
297
- OR: 4,
298
- STAR: 5,
299
- LITERAL: 6,
300
- SLASH: 7,
301
- DOT: 8
302
- }
303
-
304
- attr_reader :configuration, :route, :parent_route
305
-
306
- def initialize(configuration, route, parent_route = nil)
307
- @configuration = configuration
308
- @route = route
309
- @parent_route = parent_route
310
- end
311
-
312
- def helpers
313
- helper_types.map do |absolute|
314
- [ documentation, helper_name(absolute), body(absolute) ]
315
- end
316
- end
317
-
318
- def helper_types
319
- return [] unless match_configuration?
320
- @configuration[:url_links] ? [true, false] : [false]
321
- end
322
-
323
- def body(absolute)
324
- @configuration.dts? ?
325
- definition_body : "__jsr.r(#{arguments(absolute).map{|a| json(a)}.join(', ')})"
326
- end
327
-
328
- def definition_body
329
- args = required_parts.map{|p| "#{apply_case(p)}: RequiredRouteParameter"}
330
- args << "options?: #{optional_parts_type} & RouteOptions"
331
- "((\n#{args.join(",\n").indent(2)}\n) => string) & RouteHelperExtras"
332
- end
333
-
334
- def optional_parts_type
335
- @optional_parts_type ||=
336
- "{" + optional_parts.map {|p| "#{p}?: OptionalRouteParameter"}.join(', ') + "}"
337
- end
338
-
339
- protected
340
-
341
- def arguments(absolute)
342
- absolute ? [*base_arguments, true] : base_arguments
343
- end
344
-
345
- def match_configuration?
346
- !match?(@configuration[:exclude]) && match?(@configuration[:include])
347
- end
348
-
349
- def base_name
350
- @base_name ||= parent_route ?
351
- [parent_route.name, route.name].join('_') : route.name
352
- end
353
-
354
- def parent_spec
355
- parent_route&.path&.spec
356
- end
357
-
358
- def spec
359
- route.path.spec
360
- end
361
-
362
- def json(value)
363
- JsRoutes.json(value)
364
- end
365
-
366
- def helper_name(absolute)
367
- suffix = absolute ? :url : @configuration[:compact] ? nil : :path
368
- apply_case(base_name, suffix)
369
- end
370
-
371
- def documentation
372
- return nil unless @configuration[:documentation]
373
- <<-JS
374
- /**
375
- * Generates rails route to
376
- * #{parent_spec}#{spec}#{documentation_params}
377
- * @param {object | undefined} options
378
- * @returns {string} route path
379
- */
380
- JS
381
- end
382
-
383
- def required_parts
384
- route.required_parts
385
- end
386
-
387
- def optional_parts
388
- route.path.optional_names
389
- end
390
-
391
- def base_arguments
392
- @base_arguments ||= [parts_table, serialize(spec, parent_spec)]
393
- end
394
-
395
- def parts_table
396
- parts_table = {}
397
- route.parts.each do |part, hash|
398
- parts_table[part] ||= {}
399
- if required_parts.include?(part)
400
- # Using shortened keys to reduce js file size
401
- parts_table[part][:r] = true
402
- end
403
- end
404
- route.defaults.each do |part, value|
405
- if FILTERED_DEFAULT_PARTS.exclude?(part) &&
406
- URL_OPTIONS.include?(part) || parts_table[part]
407
- parts_table[part] ||= {}
408
- # Using shortened keys to reduce js file size
409
- parts_table[part][:d] = value
410
- end
411
- end
412
- parts_table
413
- end
414
-
415
- def documentation_params
416
- required_parts.map do |param|
417
- "\n * @param {any} #{apply_case(param)}"
418
- end.join
419
- end
420
-
421
- def match?(matchers)
422
- Array(matchers).any? { |regex| base_name =~ regex }
423
- end
424
-
425
- def apply_case(*values)
426
- value = values.compact.map(&:to_s).join('_')
427
- @configuration[:camel_case] ? value.camelize(:lower) : value
428
- end
429
-
430
- # This function serializes Journey route into JSON structure
431
- # We do not use Hash for human readable serialization
432
- # And preffer Array serialization because it is shorter.
433
- # Routes.js file will be smaller.
434
- def serialize(spec, parent_spec=nil)
435
- return nil unless spec
436
- # Rails 4 globbing requires * removal
437
- return spec.tr(':*', '') if spec.is_a?(String)
438
-
439
- result = serialize_spec(spec, parent_spec)
440
- if parent_spec && result[1].is_a?(String) && parent_spec.type != :SLASH
441
- result = [
442
- # We encode node symbols as integer
443
- # to reduce the routes.js file size
444
- NODE_TYPES[:CAT],
445
- serialize_spec(parent_spec),
446
- result
447
- ]
448
- end
449
- result
450
- end
451
-
452
- def serialize_spec(spec, parent_spec = nil)
453
- [
454
- NODE_TYPES[spec.type],
455
- serialize(spec.left, parent_spec),
456
- spec.respond_to?(:right) ? serialize(spec.right) : nil
457
- ].compact
458
- end
459
- end
460
45
  module Generators
461
46
  end
462
47
  end
data/lib/routes.d.ts CHANGED
@@ -66,7 +66,6 @@ declare const RubyVariables: {
66
66
  SPECIAL_OPTIONS_KEY: string;
67
67
  DEFAULT_URL_OPTIONS: RouteParameters;
68
68
  SERIALIZER: Serializer;
69
- NAMESPACE: string;
70
69
  ROUTES_OBJECT: RouteHelpers;
71
70
  MODULE_TYPE: ModuleType;
72
71
  WRAPPER: <T>(callback: T) => T;