js-routes 1.4.9 → 2.0.0

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,61 @@
1
+ ## Version 2.0 upgrade notes
2
+
3
+ ### Using ESM module by default
4
+
5
+ The default setting are now changed:
6
+
7
+ Setting | Old | New
8
+ --- | --- | ---
9
+ module\_type | nil | ESM
10
+ namespace | Routes | nil
11
+
12
+ This is more optimized setup for WebPacker. You can restore the old configuration like this:
13
+
14
+ ``` ruby
15
+ JsRoutes.setup do |config|
16
+ config.module_type = nil
17
+ config.namespace = 'Routes'
18
+ end
19
+ ```
20
+
21
+ However, [ESM+Webpacker](/Readme.md#webpacker) upgrade is recommended.
22
+
23
+ ### `required_params` renamed
24
+
25
+ In case you are using `required_params` property, it is now renamed and converted to a method:
26
+
27
+ ``` javascript
28
+ // Old style
29
+ Routes.post_path.required_params // => ['id']
30
+ // New style
31
+ Routes.post_path.requiredParams() // => ['id']
32
+ ```
33
+
34
+ ### ParameterMissing error rework
35
+
36
+ `ParameterMissing` is renamed to `ParametersMissing` error and now list all missing parameters instead of just first encountered in its message. Missing parameters are now available via `ParametersMissing#keys` property.
37
+
38
+ ``` javascript
39
+ try {
40
+ return Routes.inbox_path();
41
+ } catch(error) {
42
+ if (error.name === 'ParametersMissing') {
43
+ console.warn(`Missing route keys ${error.keys.join(', ')}. Ignoring.`);
44
+ return "#";
45
+ } else {
46
+ throw error;
47
+ }
48
+ }
49
+ ```
50
+
51
+ ### JSDoc comment format
52
+
53
+ New version of js-routes generates function comment in the [JSDoc](https://jsdoc.app) format.
54
+ If you have any problems with that disable the annotation:
55
+
56
+ ``` ruby
57
+ JsRoutes.setup do |config|
58
+ config.documentation = false
59
+ end
60
+ ```
61
+
data/js-routes.gemspec CHANGED
@@ -7,13 +7,16 @@ Gem::Specification.new do |s|
7
7
  s.name = %q{js-routes}
8
8
  s.version = JsRoutes::VERSION
9
9
 
10
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
10
+ if s.respond_to? :required_rubygems_version=
11
+ s.required_rubygems_version = Gem::Requirement.new(">= 0")
12
+ end
11
13
  s.authors = ["Bogdan Gusiev"]
12
14
  s.description = %q{Generates javascript file that defines all Rails named routes as javascript helpers}
13
15
  s.email = %q{agresso@gmail.com}
14
16
  s.extra_rdoc_files = [
15
17
  "LICENSE.txt"
16
18
  ]
19
+ s.required_ruby_version = '>= 2.4.0'
17
20
  s.files = `git ls-files`.split("\n")
18
21
  s.homepage = %q{http://github.com/railsware/js-routes}
19
22
  s.licenses = ["MIT"]
@@ -21,16 +24,16 @@ Gem::Specification.new do |s|
21
24
  s.summary = %q{Brings Rails named routes to javascript}
22
25
 
23
26
  s.add_runtime_dependency(%q<railties>, [">= 4"])
24
- s.add_runtime_dependency(%q<sprockets-rails>)
27
+ s.add_development_dependency(%q<sprockets-rails>)
25
28
  s.add_development_dependency(%q<rspec>, [">= 3.0.0"])
26
29
  s.add_development_dependency(%q<bundler>, [">= 1.1.0"])
27
- s.add_development_dependency(%q<coffee-script>, [">= 0"])
28
30
  s.add_development_dependency(%q<appraisal>, [">= 0.5.2"])
31
+ s.add_development_dependency(%q<bump>, [">= 0.10.0"])
29
32
  if defined?(JRUBY_VERSION)
30
33
  s.add_development_dependency(%q<therubyrhino>, [">= 2.0.4"])
31
34
  else
32
35
  s.add_development_dependency(%q<byebug>)
33
36
  s.add_development_dependency(%q<pry-byebug>)
34
- s.add_development_dependency(%q<mini_racer>, [">= 0.2.4"])
37
+ s.add_development_dependency(%q<mini_racer>, [">= 0.4.0"])
35
38
  end
36
39
  end
data/lib/js_routes.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  require 'uri'
2
- require 'js_routes/engine' if defined?(Rails)
2
+ if defined?(::Rails) && defined?(::Sprockets::Railtie)
3
+ require 'js_routes/engine'
4
+ end
3
5
  require 'js_routes/version'
6
+ require 'active_support/core_ext/string/indent'
4
7
 
5
8
  class JsRoutes
6
9
 
@@ -8,13 +11,17 @@ class JsRoutes
8
11
  # OPTIONS
9
12
  #
10
13
 
11
- DEFAULT_PATH = File.join('app','assets','javascripts','routes.js')
12
-
13
14
  DEFAULTS = {
14
- namespace: "Routes",
15
+ namespace: -> { defined?(Webpacker) ? nil : "Routes" },
15
16
  exclude: [],
16
17
  include: //,
17
- file: DEFAULT_PATH,
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,
18
25
  prefix: -> { Rails.application.config.relative_url_root || "" },
19
26
  url_links: false,
20
27
  camel_case: false,
@@ -22,8 +29,10 @@ class JsRoutes
22
29
  compact: false,
23
30
  serializer: nil,
24
31
  special_options_key: "_options",
25
- application: -> { Rails.application }
26
- }
32
+ application: -> { Rails.application },
33
+ module_type: 'ESM',
34
+ documentation: true,
35
+ } #:nodoc:
27
36
 
28
37
  NODE_TYPES = {
29
38
  GROUP: 1,
@@ -34,11 +43,11 @@ class JsRoutes
34
43
  LITERAL: 6,
35
44
  SLASH: 7,
36
45
  DOT: 8
37
- }
46
+ } #:nodoc:
38
47
 
39
- LAST_OPTIONS_KEY = "options".freeze
40
- FILTERED_DEFAULT_PARTS = [:controller, :action]
41
- URL_OPTIONS = [:protocol, :domain, :host, :port, :subdomain]
48
+ LAST_OPTIONS_KEY = "options".freeze #:nodoc:
49
+ FILTERED_DEFAULT_PARTS = [:controller, :action] #:nodoc:
50
+ URL_OPTIONS = [:protocol, :domain, :host, :port, :subdomain] #:nodoc:
42
51
 
43
52
  class Configuration < Struct.new(*DEFAULTS.keys)
44
53
  def initialize(attributes = nil)
@@ -66,6 +75,10 @@ class JsRoutes
66
75
  def to_hash
67
76
  Hash[*members.zip(values).flatten(1)].symbolize_keys
68
77
  end
78
+
79
+ def esm?
80
+ self.module_type === 'ESM'
81
+ end
69
82
  end
70
83
 
71
84
  #
@@ -77,11 +90,6 @@ class JsRoutes
77
90
  configuration.tap(&block) if block
78
91
  end
79
92
 
80
- def options
81
- ActiveSupport::Deprecation.warn('JsRoutes.options method is deprecated use JsRoutes.configuration instead')
82
- configuration
83
- end
84
-
85
93
  def configuration
86
94
  @configuration ||= Configuration.new
87
95
  end
@@ -119,8 +127,7 @@ class JsRoutes
119
127
 
120
128
  {
121
129
  'GEM_VERSION' => JsRoutes::VERSION,
122
- 'ROUTES' => js_routes,
123
- 'NODE_TYPES' => json(NODE_TYPES),
130
+ 'ROUTES_OBJECT' => routes_object,
124
131
  'RAILS_VERSION' => ActionPack.version,
125
132
  'DEPRECATED_GLOBBING_BEHAVIOR' => ActionPack::VERSION::MAJOR == 4 && ActionPack::VERSION::MINOR == 0,
126
133
 
@@ -130,9 +137,12 @@ class JsRoutes
130
137
  'PREFIX' => json(@configuration.prefix),
131
138
  'SPECIAL_OPTIONS_KEY' => json(@configuration.special_options_key),
132
139
  'SERIALIZER' => @configuration.serializer || json(nil),
140
+ 'MODULE_TYPE' => json(@configuration.module_type),
141
+ 'WRAPPER' => @configuration.esm? ? 'const __jsr = ' : '',
133
142
  }.inject(File.read(File.dirname(__FILE__) + "/routes.js")) do |js, (key, value)|
134
- js.gsub!(key, value.to_s)
135
- end
143
+ js.gsub!("RubyVariables.#{key}", value.to_s) ||
144
+ raise("Missing key #{key} in JS template")
145
+ end + routes_export
136
146
  end
137
147
 
138
148
  def generate!(file_name = nil)
@@ -164,19 +174,37 @@ class JsRoutes
164
174
  application.routes.named_routes.to_a
165
175
  end
166
176
 
167
- def js_routes
168
- js_routes = named_routes.sort_by(&:first).flat_map do |_, route|
169
- [build_route_if_match(route)] + mounted_app_routes(route)
177
+ def routes_object
178
+ return json({}) if @configuration.esm?
179
+ properties = routes_list.map do |comment, name, body|
180
+ "#{comment}#{name}: #{body}".indent(2)
181
+ end
182
+ "{\n" + properties.join(",\n\n") + "}\n"
183
+ end
184
+
185
+ STATIC_EXPORTS = [:configure, :config, :serialize].map do |name|
186
+ ["", name, "__jsr.#{name}"]
187
+ end
188
+
189
+ def routes_export
190
+ return "" unless @configuration.esm?
191
+ [*STATIC_EXPORTS, *routes_list].map do |comment, name, body|
192
+ "#{comment}export const #{name} = #{body};"
193
+ end.join("\n\n")
194
+ end
195
+
196
+ def routes_list
197
+ named_routes.sort_by(&:first).flat_map do |_, route|
198
+ build_routes_if_match(route) + mounted_app_routes(route)
170
199
  end.compact
171
- "{\n" + js_routes.join(",\n") + "}\n"
172
200
  end
173
201
 
174
202
  def mounted_app_routes(route)
175
203
  rails_engine_app = get_app_from_route(route)
176
204
  if rails_engine_app.respond_to?(:superclass) &&
177
205
  rails_engine_app.superclass == Rails::Engine && !route.path.anchored
178
- rails_engine_app.routes.named_routes.map do |_, engine_route|
179
- build_route_if_match(engine_route, route)
206
+ rails_engine_app.routes.named_routes.flat_map do |_, engine_route|
207
+ build_routes_if_match(engine_route, route)
180
208
  end
181
209
  else
182
210
  []
@@ -193,12 +221,17 @@ class JsRoutes
193
221
  end
194
222
  end
195
223
 
196
- def build_route_if_match(route, parent_route = nil)
224
+ def build_routes_if_match(route, parent_route = nil)
197
225
  if any_match?(route, parent_route, @configuration[:exclude]) ||
198
226
  !any_match?(route, parent_route, @configuration[:include])
199
- nil
227
+ []
200
228
  else
201
- build_js(route, parent_route)
229
+ name = [parent_route.try(:name), route.name].compact
230
+ parent_spec = parent_route.try(:path).try(:spec)
231
+ route_arguments = route_js_arguments(route, parent_spec)
232
+ return [false, true].map do |absolute|
233
+ route_js(name, parent_spec, route, route_arguments, absolute)
234
+ end
202
235
  end
203
236
  end
204
237
 
@@ -209,59 +242,72 @@ class JsRoutes
209
242
  matchers.any? { |regex| full_route =~ regex }
210
243
  end
211
244
 
212
- def build_js(route, parent_route)
213
- name = [parent_route.try(:name), route.name].compact
214
- route_name = generate_route_name(name, (:path unless @configuration[:compact]))
215
- parent_spec = parent_route.try(:path).try(:spec)
216
- route_arguments = route_js_arguments(route, parent_spec)
217
- url_link = generate_url_link(name, route_arguments)
218
- _ = <<-JS.strip!
219
- // #{name.join('.')} => #{parent_spec}#{route.path.spec}
220
- // function(#{build_params(route.required_parts)})
221
- #{route_name}: Utils.route(#{route_arguments})#{",\n" + url_link if url_link.length > 0}
222
- JS
245
+ def route_js(name_parts, parent_spec, route, route_arguments, absolute)
246
+ if absolute
247
+ return nil unless @configuration[:url_links]
248
+ route_arguments = route_arguments + [json(true)]
249
+ end
250
+ name_suffix = absolute ? :url : @configuration[:compact] ? nil : :path
251
+ name = generate_route_name(name_parts, name_suffix)
252
+ body = "__jsr.r(#{route_arguments.join(', ')})"
253
+ comment = documentation(route, parent_spec)
254
+ [ comment, name, body ]
255
+ end
256
+
257
+ def documentation(route, parent_spec)
258
+ return nil unless @configuration[:documentation]
259
+ <<-JS
260
+ /**
261
+ * Generates rails route to
262
+ * #{parent_spec}#{route.path.spec}#{build_params(route.required_parts)}
263
+ * @param {object | undefined} options
264
+ * @returns {string} route path
265
+ */
266
+ JS
223
267
  end
224
268
 
225
269
  def route_js_arguments(route, parent_spec)
226
270
  required_parts = route.required_parts
227
- parts_table = route.parts.each_with_object({}) do |part, hash|
228
- hash[part] = required_parts.include?(part)
271
+ parts_table = {}
272
+ route.parts.each do |part, hash|
273
+ parts_table[part] ||= {}
274
+ if required_parts.include?(part)
275
+ # Using shortened keys to reduce js file size
276
+ parts_table[part][:r] = true
277
+ end
229
278
  end
230
- default_options = route.defaults.select do |part, _|
231
- FILTERED_DEFAULT_PARTS.exclude?(part) &&
279
+ route.defaults.each do |part, value|
280
+ if FILTERED_DEFAULT_PARTS.exclude?(part) &&
232
281
  URL_OPTIONS.include?(part) || parts_table[part]
282
+ parts_table[part] ||= {}
283
+ # Using shortened keys to reduce js file size
284
+ parts_table[part][:d] = value
285
+ end
233
286
  end
234
287
  [
235
- # JS objects don't preserve the order of properties which is crucial,
236
- # so array is a better choice.
237
- parts_table.to_a,
238
- default_options,
288
+ parts_table,
239
289
  serialize(route.path.spec, parent_spec)
240
290
  ].map do |argument|
241
291
  json(argument)
242
- end.join(', ')
243
- end
244
-
245
- def generate_url_link(name, route_arguments)
246
- return '' unless @configuration[:url_links]
247
-
248
- <<-JS.strip!
249
- #{generate_route_name(name, :url)}: Utils.route(#{route_arguments}, true)
250
- JS
292
+ end
251
293
  end
252
294
 
253
295
  def generate_route_name(*parts)
254
- route_name = parts.compact.join('_')
255
- @configuration[:camel_case] ? route_name.camelize(:lower) : route_name
296
+ apply_case(parts.compact.join('_'))
256
297
  end
257
298
 
258
299
  def json(string)
259
300
  self.class.json(string)
260
301
  end
261
302
 
303
+ def apply_case(value)
304
+ @configuration[:camel_case] ? value.to_s.camelize(:lower) : value
305
+ end
306
+
262
307
  def build_params(required_parts)
263
- params = required_parts + [LAST_OPTIONS_KEY]
264
- params.join(', ')
308
+ required_parts.map do |param|
309
+ "\n * @param {any} #{apply_case(param)}"
310
+ end.join
265
311
  end
266
312
 
267
313
  # This function serializes Journey route into JSON structure
@@ -290,7 +336,7 @@ class JsRoutes
290
336
  [
291
337
  NODE_TYPES[spec.type],
292
338
  serialize(spec.left, parent_spec),
293
- spec.respond_to?(:right) && serialize(spec.right)
294
- ]
339
+ spec.respond_to?(:right) ? serialize(spec.right) : nil
340
+ ].compact
295
341
  end
296
342
  end
@@ -47,7 +47,6 @@ class Engine < ::Rails::Engine
47
47
  when -> (v) { v2.match?('', v) },
48
48
  -> (v) { vgte3.match?('', v) }
49
49
 
50
- # Other rails version, assumed newer
51
50
  Rails.application.config.assets.configure do |config|
52
51
  config.register_preprocessor(
53
52
  "application/javascript",
@@ -1,3 +1,3 @@
1
1
  class JsRoutes
2
- VERSION = "1.4.9"
2
+ VERSION = "2.0.0"
3
3
  end
data/lib/routes.d.ts ADDED
@@ -0,0 +1,64 @@
1
+ /**
2
+ * File generated by js-routes RubyVariables.GEM_VERSION
3
+ * Based on Rails RubyVariables.RAILS_VERSION routes of RubyVariables.APP_CLASS
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;
12
+ };
13
+ declare type RouteHelpers = Record<string, RouteHelper>;
14
+ 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;
22
+ };
23
+ interface RouterExposedMethods {
24
+ config(): Configuration;
25
+ configure(arg: Partial<Configuration>): Configuration;
26
+ serialize: Serializer;
27
+ }
28
+ declare type KeywordUrlOptions = Optional<{
29
+ host: string;
30
+ protocol: string;
31
+ subdomain: string;
32
+ port: string;
33
+ anchor: string;
34
+ trailing_slash: boolean;
35
+ }>;
36
+ declare type PartsTable = Record<
37
+ string,
38
+ {
39
+ r?: boolean;
40
+ d?: unknown;
41
+ }
42
+ >;
43
+ declare type ModuleType = "CJS" | "AMD" | "UMD" | "ESM";
44
+ 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;
54
+ };
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;