js-routes 1.4.9 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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;