js-routes 2.1.1 → 2.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.1.1"
1
+ module JsRoutes
2
+ VERSION = "2.2.2"
3
3
  end
data/lib/js_routes.rb CHANGED
@@ -1,109 +1,12 @@
1
- require 'uri'
2
1
  if defined?(::Rails) && defined?(::Sprockets::Railtie)
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)
@@ -132,7 +34,7 @@ class JsRoutes
132
34
  end
133
35
 
134
36
  def definitions!(file_name = nil, **opts)
135
- file_name ||= configuration.file&.sub!(%r{\.(j|t)s\Z}, ".d.ts")
37
+ file_name ||= configuration.file&.sub(%r{(\.d)?\.(j|t)s\Z}, ".d.ts")
136
38
  generate!(file_name, module_type: 'DTS', **opts)
137
39
  end
138
40
 
@@ -140,322 +42,10 @@ 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.to_a.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
- # rails engine in Rails 4.2 use additional
277
- # ActionDispatch::Routing::Mapper::Constraints, which contain app
278
- if route.app.respond_to?(:app) && route.app.respond_to?(:constraints)
279
- route.app.app
280
- else
281
- route.app
282
- end
283
- end
284
-
285
- def route_helpers_if_match(route, parent_route = nil)
286
- JsRoute.new(@configuration, route, parent_route).helpers
287
- end
288
-
289
- class JsRoute #:nodoc:
290
- FILTERED_DEFAULT_PARTS = [:controller, :action]
291
- URL_OPTIONS = [:protocol, :domain, :host, :port, :subdomain]
292
- NODE_TYPES = {
293
- GROUP: 1,
294
- CAT: 2,
295
- SYMBOL: 3,
296
- OR: 4,
297
- STAR: 5,
298
- LITERAL: 6,
299
- SLASH: 7,
300
- DOT: 8
301
- }
302
-
303
- attr_reader :configuration, :route, :parent_route
304
-
305
- def initialize(configuration, route, parent_route = nil)
306
- @configuration = configuration
307
- @route = route
308
- @parent_route = parent_route
309
- end
310
-
311
- def helpers
312
- helper_types.map do |absolute|
313
- [ documentation, helper_name(absolute), body(absolute) ]
314
- end
315
- end
316
-
317
- def helper_types
318
- return [] unless match_configuration?
319
- @configuration[:url_links] ? [true, false] : [false]
320
- end
321
-
322
- def body(absolute)
323
- @configuration.dts? ?
324
- definition_body : "__jsr.r(#{arguments(absolute).map{|a| json(a)}.join(', ')})"
325
- end
326
-
327
- def definition_body
328
- args = required_parts.map{|p| "#{apply_case(p)}: RequiredRouteParameter"}
329
- args << "options?: #{optional_parts_type} & RouteOptions"
330
- "((\n#{args.join(",\n").indent(2)}\n) => string) & RouteHelperExtras"
331
- end
332
-
333
- def optional_parts_type
334
- @optional_parts_type ||=
335
- "{" + optional_parts.map {|p| "#{p}?: OptionalRouteParameter"}.join(', ') + "}"
336
- end
337
-
338
- protected
339
-
340
- def arguments(absolute)
341
- absolute ? [*base_arguments, true] : base_arguments
342
- end
343
-
344
- def match_configuration?
345
- !match?(@configuration[:exclude]) && match?(@configuration[:include])
346
- end
347
-
348
- def base_name
349
- @base_name ||= parent_route ?
350
- [parent_route.name, route.name].join('_') : route.name
351
- end
352
-
353
- def parent_spec
354
- parent_route&.path&.spec
355
- end
356
-
357
- def spec
358
- route.path.spec
359
- end
360
-
361
- def json(value)
362
- JsRoutes.json(value)
363
- end
364
-
365
- def helper_name(absolute)
366
- suffix = absolute ? :url : @configuration[:compact] ? nil : :path
367
- apply_case(base_name, suffix)
368
- end
369
-
370
- def documentation
371
- return nil unless @configuration[:documentation]
372
- <<-JS
373
- /**
374
- * Generates rails route to
375
- * #{parent_spec}#{spec}#{documentation_params}
376
- * @param {object | undefined} options
377
- * @returns {string} route path
378
- */
379
- JS
380
- end
381
-
382
- def required_parts
383
- route.required_parts
384
- end
385
-
386
- def optional_parts
387
- route.path.optional_names
388
- end
389
-
390
- def base_arguments
391
- @base_arguments ||= [parts_table, serialize(spec, parent_spec)]
392
- end
393
-
394
- def parts_table
395
- parts_table = {}
396
- route.parts.each do |part, hash|
397
- parts_table[part] ||= {}
398
- if required_parts.include?(part)
399
- # Using shortened keys to reduce js file size
400
- parts_table[part][:r] = true
401
- end
402
- end
403
- route.defaults.each do |part, value|
404
- if FILTERED_DEFAULT_PARTS.exclude?(part) &&
405
- URL_OPTIONS.include?(part) || parts_table[part]
406
- parts_table[part] ||= {}
407
- # Using shortened keys to reduce js file size
408
- parts_table[part][:d] = value
409
- end
410
- end
411
- parts_table
412
- end
413
-
414
- def documentation_params
415
- required_parts.map do |param|
416
- "\n * @param {any} #{apply_case(param)}"
417
- end.join
418
- end
419
-
420
- def match?(matchers)
421
- Array(matchers).any? { |regex| base_name =~ regex }
422
- end
423
-
424
- def apply_case(*values)
425
- value = values.compact.map(&:to_s).join('_')
426
- @configuration[:camel_case] ? value.camelize(:lower) : value
427
- end
428
-
429
- # This function serializes Journey route into JSON structure
430
- # We do not use Hash for human readable serialization
431
- # And preffer Array serialization because it is shorter.
432
- # Routes.js file will be smaller.
433
- def serialize(spec, parent_spec=nil)
434
- return nil unless spec
435
- # Rails 4 globbing requires * removal
436
- return spec.tr(':*', '') if spec.is_a?(String)
437
-
438
- result = serialize_spec(spec, parent_spec)
439
- if parent_spec && result[1].is_a?(String) && parent_spec.type != :SLASH
440
- result = [
441
- # We encode node symbols as integer
442
- # to reduce the routes.js file size
443
- NODE_TYPES[:CAT],
444
- serialize_spec(parent_spec),
445
- result
446
- ]
447
- end
448
- result
449
- end
450
-
451
- def serialize_spec(spec, parent_spec = nil)
452
- [
453
- NODE_TYPES[spec.type],
454
- serialize(spec.left, parent_spec),
455
- spec.respond_to?(:right) ? serialize(spec.right) : nil
456
- ].compact
457
- end
45
+ module Generators
458
46
  end
459
47
  end
460
48
 
49
+ require "js_routes/middleware"
461
50
  require "js_routes/generators/webpacker"
51
+ require "js_routes/generators/middleware"
data/lib/routes.js CHANGED
@@ -16,6 +16,7 @@ RubyVariables.WRAPPER((that) => {
16
16
  NodeTypes[NodeTypes["DOT"] = 8] = "DOT";
17
17
  })(NodeTypes || (NodeTypes = {}));
18
18
  const Root = that;
19
+ const isBroswer = typeof window !== "undefined";
19
20
  const ModuleReferences = {
20
21
  CJS: {
21
22
  define(routes) {
@@ -73,7 +74,7 @@ RubyVariables.WRAPPER((that) => {
73
74
  Utils.namespace(Root, RubyVariables.NAMESPACE, routes);
74
75
  },
75
76
  isSupported() {
76
- return !!Root;
77
+ return !RubyVariables.NAMESPACE || !!Root;
77
78
  },
78
79
  },
79
80
  DTS: {
@@ -204,7 +205,9 @@ RubyVariables.WRAPPER((that) => {
204
205
  throw new Error("Too many parameters provided for path");
205
206
  }
206
207
  let use_all_parts = args.length > required_params.length;
207
- const parts_options = {};
208
+ const parts_options = {
209
+ ...this.configuration.default_url_options,
210
+ };
208
211
  for (const key in options) {
209
212
  const value = options[key];
210
213
  if (!hasProp(options, key))
@@ -419,15 +422,15 @@ RubyVariables.WRAPPER((that) => {
419
422
  }
420
423
  current_host() {
421
424
  var _a;
422
- return ((_a = window === null || window === void 0 ? void 0 : window.location) === null || _a === void 0 ? void 0 : _a.hostname) || "";
425
+ return (isBroswer && ((_a = window === null || window === void 0 ? void 0 : window.location) === null || _a === void 0 ? void 0 : _a.hostname)) || "";
423
426
  }
424
427
  current_protocol() {
425
428
  var _a, _b;
426
- return ((_b = (_a = window === null || window === void 0 ? void 0 : window.location) === null || _a === void 0 ? void 0 : _a.protocol) === null || _b === void 0 ? void 0 : _b.replace(/:$/, "")) || "http";
429
+ return ((isBroswer && ((_b = (_a = window === null || window === void 0 ? void 0 : window.location) === null || _a === void 0 ? void 0 : _a.protocol) === null || _b === void 0 ? void 0 : _b.replace(/:$/, ""))) || "http");
427
430
  }
428
431
  current_port() {
429
432
  var _a;
430
- return ((_a = window === null || window === void 0 ? void 0 : window.location) === null || _a === void 0 ? void 0 : _a.port) || "";
433
+ return (isBroswer && ((_a = window === null || window === void 0 ? void 0 : window.location) === null || _a === void 0 ? void 0 : _a.port)) || "";
431
434
  }
432
435
  is_object(value) {
433
436
  return (typeof value === "object" &&
@@ -445,7 +448,7 @@ RubyVariables.WRAPPER((that) => {
445
448
  namespace(object, namespace, routes) {
446
449
  const parts = (namespace === null || namespace === void 0 ? void 0 : namespace.split(".")) || [];
447
450
  if (parts.length === 0) {
448
- return routes;
451
+ return;
449
452
  }
450
453
  for (let index = 0; index < parts.length; index++) {
451
454
  const part = parts[index];
@@ -453,7 +456,7 @@ RubyVariables.WRAPPER((that) => {
453
456
  object = object[part] || (object[part] = {});
454
457
  }
455
458
  else {
456
- return (object[part] = routes);
459
+ object[part] = routes;
457
460
  }
458
461
  }
459
462
  }