js-routes 2.1.1 → 2.2.2

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.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
  }