js-routes 2.0.6 → 2.1.1

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.
data/lib/js_routes.rb CHANGED
@@ -7,48 +7,26 @@ require 'active_support/core_ext/string/indent'
7
7
 
8
8
  class JsRoutes
9
9
 
10
- #
11
- # OPTIONS
12
- #
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)
13
29
 
14
- DEFAULTS = {
15
- namespace: nil,
16
- exclude: [],
17
- include: //,
18
- file: -> do
19
- webpacker_dir = Rails.root.join('app', 'javascript')
20
- sprockets_dir = Rails.root.join('app','assets','javascripts')
21
- sprockets_file = sprockets_dir.join('routes.js')
22
- webpacker_file = webpacker_dir.join('routes.js')
23
- !Dir.exist?(webpacker_dir) && defined?(::Sprockets) ? sprockets_file : webpacker_file
24
- end,
25
- prefix: -> { Rails.application.config.relative_url_root || "" },
26
- url_links: false,
27
- camel_case: false,
28
- default_url_options: {},
29
- compact: false,
30
- serializer: nil,
31
- special_options_key: "_options",
32
- application: -> { Rails.application },
33
- module_type: 'ESM',
34
- documentation: true,
35
- } #:nodoc:
36
-
37
- NODE_TYPES = {
38
- GROUP: 1,
39
- CAT: 2,
40
- SYMBOL: 3,
41
- OR: 4,
42
- STAR: 5,
43
- LITERAL: 6,
44
- SLASH: 7,
45
- DOT: 8
46
- } #:nodoc:
47
-
48
- FILTERED_DEFAULT_PARTS = [:controller, :action] #:nodoc:
49
- URL_OPTIONS = [:protocol, :domain, :host, :port, :subdomain] #:nodoc:
50
-
51
- class Configuration < Struct.new(*DEFAULTS.keys)
52
30
  def initialize(attributes = nil)
53
31
  assign(DEFAULTS)
54
32
  return unless attributes
@@ -60,6 +38,7 @@ class JsRoutes
60
38
  value = value.call if value.is_a?(Proc)
61
39
  send(:"#{attribute}=", value)
62
40
  end
41
+ normalize_and_verify
63
42
  self
64
43
  end
65
44
 
@@ -76,7 +55,53 @@ class JsRoutes
76
55
  end
77
56
 
78
57
  def esm?
79
- self.module_type === '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
80
105
  end
81
106
  end
82
107
 
@@ -87,22 +112,28 @@ class JsRoutes
87
112
  class << self
88
113
  def setup(&block)
89
114
  configuration.tap(&block) if block
115
+ configuration.normalize_and_verify
90
116
  end
91
117
 
92
118
  def configuration
93
119
  @configuration ||= Configuration.new
94
120
  end
95
121
 
96
- def generate(opts = {})
122
+ def generate(**opts)
97
123
  new(opts).generate
98
124
  end
99
125
 
100
- def generate!(file_name=nil, opts = {})
101
- if file_name.is_a?(Hash)
102
- opts = file_name
103
- file_name = opts[:file]
104
- end
105
- new(opts).generate!(file_name)
126
+ def generate!(file_name=nil, **opts)
127
+ new(file: file_name, **opts).generate!
128
+ end
129
+
130
+ def definitions(**opts)
131
+ generate(module_type: 'DTS', **opts)
132
+ end
133
+
134
+ def definitions!(file_name = nil, **opts)
135
+ file_name ||= configuration.file&.sub!(%r{\.(j|t)s\Z}, ".d.ts")
136
+ generate!(file_name, module_type: 'DTS', **opts)
106
137
  end
107
138
 
108
139
  def json(string)
@@ -123,48 +154,55 @@ class JsRoutes
123
154
  if named_routes.to_a.empty? && application.respond_to?(:reload_routes!)
124
155
  application.reload_routes!
125
156
  end
157
+ content = File.read(@configuration.source_file)
126
158
 
127
- {
128
- 'GEM_VERSION' => JsRoutes::VERSION,
129
- 'ROUTES_OBJECT' => routes_object,
130
- 'RAILS_VERSION' => ActionPack.version,
131
- 'DEPRECATED_GLOBBING_BEHAVIOR' => ActionPack::VERSION::MAJOR == 4 && ActionPack::VERSION::MINOR == 0,
132
-
133
- 'APP_CLASS' => application.class.to_s,
134
- 'NAMESPACE' => json(@configuration.namespace),
135
- 'DEFAULT_URL_OPTIONS' => json(@configuration.default_url_options),
136
- 'PREFIX' => json(@configuration.prefix),
137
- 'SPECIAL_OPTIONS_KEY' => json(@configuration.special_options_key),
138
- 'SERIALIZER' => @configuration.serializer || json(nil),
139
- 'MODULE_TYPE' => json(@configuration.module_type),
140
- 'WRAPPER' => @configuration.esm? ? 'const __jsr = ' : '',
141
- }.inject(File.read(File.dirname(__FILE__) + "/routes.js")) do |js, (key, value)|
142
- js.gsub!("RubyVariables.#{key}", value.to_s) ||
159
+ if !@configuration.dts?
160
+ content = js_variables.inject(content) do |js, (key, value)|
161
+ js.gsub!("RubyVariables.#{key}", value.to_s) ||
143
162
  raise("Missing key #{key} in JS template")
144
- end + routes_export
163
+ end
164
+ end
165
+ content + routes_export + prevent_types_export
145
166
  end
146
167
 
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
168
+ def generate!
169
+ # Some libraries like Devise did not load their routes yet
170
+ # so we will wait until initialization process finishes
150
171
  # https://github.com/railsware/js-routes/issues/7
151
172
  Rails.configuration.after_initialize do
152
- file_name ||= self.class.configuration['file']
153
- file_path = Rails.root.join(file_name)
154
- js_content = generate
173
+ file_path = Rails.root.join(@configuration.output_file)
174
+ source_code = generate
155
175
 
156
176
  # We don't need to rewrite file if it already exist and have same content.
157
177
  # It helps asset pipeline or webpack understand that file wasn't changed.
158
- next if File.exist?(file_path) && File.read(file_path) == js_content
178
+ next if File.exist?(file_path) && File.read(file_path) == source_code
159
179
 
160
180
  File.open(file_path, 'w') do |f|
161
- f.write js_content
181
+ f.write source_code
162
182
  end
163
183
  end
164
184
  end
165
185
 
166
186
  protected
167
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
+
168
206
  def application
169
207
  @configuration.application
170
208
  end
@@ -178,32 +216,52 @@ class JsRoutes
178
216
  end
179
217
 
180
218
  def routes_object
181
- return json({}) if @configuration.esm?
219
+ return json({}) if @configuration.modern?
182
220
  properties = routes_list.map do |comment, name, body|
183
221
  "#{comment}#{name}: #{body}".indent(2)
184
222
  end
185
223
  "{\n" + properties.join(",\n\n") + "}\n"
186
224
  end
187
225
 
188
- STATIC_EXPORTS = [:configure, :config, :serialize].map do |name|
189
- ["", name, "__jsr.#{name}"]
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
190
235
  end
191
236
 
192
237
  def routes_export
193
- return "" unless @configuration.esm?
194
- [*STATIC_EXPORTS, *routes_list].map do |comment, name, body|
195
- "#{comment}export const #{name} = #{body};"
196
- end.join("\n\n")
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? ? ': ' : ' = '
197
255
  end
198
256
 
199
257
  def routes_list
200
258
  named_routes.sort_by(&:first).flat_map do |_, route|
201
259
  route_helpers_if_match(route) + mounted_app_routes(route)
202
- end.compact
260
+ end
203
261
  end
204
262
 
205
263
  def mounted_app_routes(route)
206
- rails_engine_app = get_app_from_route(route)
264
+ rails_engine_app = app_from_route(route)
207
265
  if rails_engine_app.respond_to?(:superclass) &&
208
266
  rails_engine_app.superclass == Rails::Engine && !route.path.anchored
209
267
  rails_engine_app.routes.named_routes.flat_map do |_, engine_route|
@@ -214,7 +272,7 @@ class JsRoutes
214
272
  end
215
273
  end
216
274
 
217
- def get_app_from_route(route)
275
+ def app_from_route(route)
218
276
  # rails engine in Rails 4.2 use additional
219
277
  # ActionDispatch::Routing::Mapper::Constraints, which contain app
220
278
  if route.app.respond_to?(:app) && route.app.respond_to?(:constraints)
@@ -229,6 +287,19 @@ class JsRoutes
229
287
  end
230
288
 
231
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
+
232
303
  attr_reader :configuration, :route, :parent_route
233
304
 
234
305
  def initialize(configuration, route, parent_route = nil)
@@ -238,22 +309,36 @@ class JsRoutes
238
309
  end
239
310
 
240
311
  def helpers
241
- unless match_configuration?
242
- []
243
- else
244
- [false, true].map do |absolute|
245
- absolute && !@configuration[:url_links] ?
246
- nil : [ documentation, helper_name(absolute), body(absolute) ]
247
- end
312
+ helper_types.map do |absolute|
313
+ [ documentation, helper_name(absolute), body(absolute) ]
248
314
  end
249
315
  end
250
316
 
317
+ def helper_types
318
+ return [] unless match_configuration?
319
+ @configuration[:url_links] ? [true, false] : [false]
320
+ end
321
+
251
322
  def body(absolute)
252
- "__jsr.r(#{arguments(absolute).join(', ')})"
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"
253
331
  end
254
332
 
333
+ def optional_parts_type
334
+ @optional_parts_type ||=
335
+ "{" + optional_parts.map {|p| "#{p}?: OptionalRouteParameter"}.join(', ') + "}"
336
+ end
337
+
338
+ protected
339
+
255
340
  def arguments(absolute)
256
- absolute ? base_arguments + [json(true)] : base_arguments
341
+ absolute ? [*base_arguments, true] : base_arguments
257
342
  end
258
343
 
259
344
  def match_configuration?
@@ -298,10 +383,15 @@ JS
298
383
  route.required_parts
299
384
  end
300
385
 
301
- protected
386
+ def optional_parts
387
+ route.path.optional_names
388
+ end
302
389
 
303
390
  def base_arguments
304
- return @base_arguments if defined?(@base_arguments)
391
+ @base_arguments ||= [parts_table, serialize(spec, parent_spec)]
392
+ end
393
+
394
+ def parts_table
305
395
  parts_table = {}
306
396
  route.parts.each do |part, hash|
307
397
  parts_table[part] ||= {}
@@ -318,11 +408,7 @@ JS
318
408
  parts_table[part][:d] = value
319
409
  end
320
410
  end
321
- @base_arguments = [
322
- parts_table, serialize(spec, parent_spec)
323
- ].map do |argument|
324
- json(argument)
325
- end
411
+ parts_table
326
412
  end
327
413
 
328
414
  def documentation_params
@@ -371,3 +457,5 @@ JS
371
457
  end
372
458
  end
373
459
  end
460
+
461
+ require "js_routes/generators/webpacker"
data/lib/routes.d.ts CHANGED
@@ -2,63 +2,78 @@
2
2
  * File generated by js-routes RubyVariables.GEM_VERSION
3
3
  * Based on Rails RubyVariables.RAILS_VERSION routes of RubyVariables.APP_CLASS
4
4
  */
5
- declare type RouteParameter = unknown;
6
- declare type RouteParameters = Record<string, RouteParameter>;
7
- declare type Serializer = (value: unknown) => string;
8
- declare type RouteHelper = {
9
- (...args: RouteParameter[]): string;
10
- requiredParams(): string[];
11
- toString(): string;
5
+ declare type Optional<T> = {
6
+ [P in keyof T]?: T[P] | null;
7
+ };
8
+ declare type BaseRouteParameter = string | boolean | Date | number;
9
+ declare type MethodRouteParameter = BaseRouteParameter | (() => BaseRouteParameter);
10
+ declare type ModelRouteParameter = {
11
+ id: MethodRouteParameter;
12
+ } | {
13
+ to_param: MethodRouteParameter;
14
+ } | {
15
+ toParam: MethodRouteParameter;
16
+ };
17
+ declare type RequiredRouteParameter = BaseRouteParameter | ModelRouteParameter;
18
+ declare type OptionalRouteParameter = undefined | null | RequiredRouteParameter;
19
+ declare type QueryRouteParameter = OptionalRouteParameter | QueryRouteParameter[] | {
20
+ [k: string]: QueryRouteParameter;
21
+ };
22
+ declare type RouteParameters = Record<string, QueryRouteParameter>;
23
+ declare type Serializable = Record<string, unknown>;
24
+ declare type Serializer = (value: Serializable) => string;
25
+ declare type RouteHelperExtras = {
26
+ requiredParams(): string[];
27
+ toString(): string;
12
28
  };
29
+ declare type RequiredParameters<T extends number> = T extends 1 ? [RequiredRouteParameter] : T extends 2 ? [RequiredRouteParameter, RequiredRouteParameter] : T extends 3 ? [RequiredRouteParameter, RequiredRouteParameter, RequiredRouteParameter] : T extends 4 ? [
30
+ RequiredRouteParameter,
31
+ RequiredRouteParameter,
32
+ RequiredRouteParameter,
33
+ RequiredRouteParameter
34
+ ] : RequiredRouteParameter[];
35
+ declare type RouteHelperOptions<T extends string> = RouteOptions & Optional<Record<T, OptionalRouteParameter>>;
36
+ declare type RouteHelper<T extends number = number, U extends string = string> = ((...args: [...RequiredParameters<T>, RouteHelperOptions<U>]) => string) & RouteHelperExtras;
13
37
  declare type RouteHelpers = Record<string, RouteHelper>;
14
38
  declare type Configuration = {
15
- prefix: string;
16
- default_url_options: RouteParameters;
17
- special_options_key: string;
18
- serializer: Serializer;
19
- };
20
- declare type Optional<T> = {
21
- [P in keyof T]?: T[P] | null;
39
+ prefix: string;
40
+ default_url_options: RouteParameters;
41
+ special_options_key: string;
42
+ serializer: Serializer;
22
43
  };
23
44
  interface RouterExposedMethods {
24
- config(): Configuration;
25
- configure(arg: Partial<Configuration>): Configuration;
26
- serialize: Serializer;
45
+ config(): Configuration;
46
+ configure(arg: Partial<Configuration>): Configuration;
47
+ serialize: Serializer;
27
48
  }
28
49
  declare type KeywordUrlOptions = Optional<{
29
- host: string;
30
- protocol: string;
31
- subdomain: string;
32
- port: string;
33
- anchor: string;
34
- trailing_slash: boolean;
50
+ host: string;
51
+ protocol: string;
52
+ subdomain: string;
53
+ port: string | number;
54
+ anchor: string;
55
+ trailing_slash: boolean;
35
56
  }>;
36
- declare type PartsTable = Record<
37
- string,
38
- {
57
+ declare type RouteOptions = KeywordUrlOptions & RouteParameters;
58
+ declare type PartsTable = Record<string, {
39
59
  r?: boolean;
40
- d?: unknown;
41
- }
42
- >;
43
- declare type ModuleType = "CJS" | "AMD" | "UMD" | "ESM";
60
+ d?: OptionalRouteParameter;
61
+ }>;
62
+ declare type ModuleType = "CJS" | "AMD" | "UMD" | "ESM" | "DTS" | "NIL";
44
63
  declare const RubyVariables: {
45
- PREFIX: string;
46
- DEPRECATED_GLOBBING_BEHAVIOR: boolean;
47
- SPECIAL_OPTIONS_KEY: string;
48
- DEFAULT_URL_OPTIONS: RouteParameters;
49
- SERIALIZER: Serializer;
50
- NAMESPACE: string;
51
- ROUTES_OBJECT: RouteHelpers;
52
- MODULE_TYPE: ModuleType | null;
53
- WRAPPER: <T>(callback: T) => T;
64
+ PREFIX: string;
65
+ DEPRECATED_GLOBBING_BEHAVIOR: boolean;
66
+ SPECIAL_OPTIONS_KEY: string;
67
+ DEFAULT_URL_OPTIONS: RouteParameters;
68
+ SERIALIZER: Serializer;
69
+ NAMESPACE: string;
70
+ ROUTES_OBJECT: RouteHelpers;
71
+ MODULE_TYPE: ModuleType;
72
+ WRAPPER: <T>(callback: T) => T;
54
73
  };
55
- declare const define:
56
- | undefined
57
- | (((arg: unknown[], callback: () => unknown) => void) & {
58
- amd?: unknown;
59
- });
60
- declare const module:
61
- | {
62
- exports: any;
63
- }
64
- | undefined;
74
+ declare const define: undefined | (((arg: unknown[], callback: () => unknown) => void) & {
75
+ amd?: unknown;
76
+ });
77
+ declare const module: {
78
+ exports: any;
79
+ } | undefined;