js-routes 2.3.7 → 2.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8e1323cccad426e331d7f99923e967b09d06e028ec3f2665798dad91ff5ce582
4
- data.tar.gz: 4ddb07ef468133b25d7ace1313b948f689a77a1c576e3102e88ff2b87f82a5c5
3
+ metadata.gz: 40c089f66c3042418a4b05690dd145fa20d0c61094d5242eb818b54cf8908e0a
4
+ data.tar.gz: 228996f6fe1b66fd475aecc4372da6d03ff25d06e23c426fdd44b3c608ef16ea
5
5
  SHA512:
6
- metadata.gz: 9840b6fa125bec3de173ce942e7daae8cac4de026d424d70a4bb82d67d9cf075d050c182c02fadef9d5c64a989599dd3a0cbc59197652d836b44f3452588bad2
7
- data.tar.gz: 449669e3e6719f2c1ee22bd1b6b7bfaa34ca2821f7d6050563d76f43781c8e4efc8c1d6e986443007451550002abad649e6dc6bdf10629ba683be3ccf3f3f1ee
6
+ metadata.gz: 72aaae2f2221a6db8e96d10f3bc410f82f6c9f5f9ff9c03c193bdac42bb332113acf972a9331afde0800dde6cb11128aa94adecee8e365af29cf647fff4c5b06
7
+ data.tar.gz: 87a8a03ff9d7929d9fc46c8c46fc913c43a6efa1a55b04b3b52476c75533349a6b538eff3fe2a1e306ce4cf198652578b33c0ca2382679f0dbaf5b39b8d25660
data/CHANGELOG.md CHANGED
@@ -2,6 +2,101 @@
2
2
 
3
3
  ## Pending
4
4
 
5
+ ## [2.4.0]
6
+
7
+ ### Package mode
8
+
9
+ Add `package` option and `JsRoutes.package` / `JsRoutes.package!` API for sharing a single Router runtime across multiple route files.
10
+
11
+ **Why:** When an app generates several ESM route files (e.g. one per domain or engine), each file previously embedded the full js-routes runtime (~10 KB minified). With `package`, the runtime is extracted into one `router.js` and every route file imports it — the runtime is downloaded and parsed only once.
12
+
13
+ Generate the shared router package (no route definitions, just the runtime):
14
+
15
+ ``` ruby
16
+ # config/initializers/js_routes.rb
17
+ JsRoutes.package! # writes app/javascript/router.js
18
+ # or with a custom path:
19
+ JsRoutes.package!("shared/router.js")
20
+ ```
21
+
22
+ Generate consumer route files that import from it:
23
+
24
+ ``` ruby
25
+ JsRoutes.generate!(
26
+ "app/javascript/admin_routes.js",
27
+ typed: true,
28
+ package: "./router.js", # or package: true for the default path
29
+ include: /\Aadmin_/
30
+ )
31
+
32
+ JsRoutes.generate!(
33
+ "app/javascript/api_routes.js",
34
+ typed: true,
35
+ package: "./router.js",
36
+ include: /\Aapi_/
37
+ )
38
+ ```
39
+
40
+ Or share one configuration block:
41
+
42
+ ``` ruby
43
+ JsRoutes.setup do |c|
44
+ c.module_type = "ESM"
45
+ c.package = "./router.js" # all generated files import the same runtime
46
+ end
47
+
48
+ JsRoutes.package! # router.js — runtime only
49
+ JsRoutes.generate! # routes.js — route helpers only
50
+ ```
51
+
52
+ `package: true` is a shorthand for `package: "./router.js"`.
53
+
54
+ ### `include_undefined_query_parameters` option
55
+
56
+ Add `include_undefined_query_parameters` configuration option. Fixes [#345](https://github.com/railsware/js-routes/issues/345).
57
+
58
+ JavaScript uses `undefined` to mean "not provided," but js-routes previously treated `undefined` query object values the same as `null`. With Rails 8.1's nil parameter handling, that causes `undefined` values to appear as bare keys like `?foo`.
59
+
60
+ * Set it to `false` to omit object properties whose value is `undefined`, matching typical JavaScript semantics. This is the recommended value for new and upgrading apps.
61
+ * Set it to `true` only when the application depends on serializing `undefined` as Rails `nil` (legacy behavior).
62
+ * Explicit `null` values continue to serialize as Rails `nil` in all cases.
63
+ * When unset, the legacy behavior is preserved and a deprecation warning is emitted so apps can opt in deliberately.
64
+
65
+ ### JavaScript reserved word escaping
66
+
67
+ Escape JavaScript reserved words in route helper names and TypeScript parameter names. Fixes broken `.d.ts` output when a route segment name collides with a JS keyword.
68
+
69
+ Given a route like:
70
+
71
+ ``` ruby
72
+ scope "/returns/:return" do
73
+ resources :objects, only: [:show]
74
+ end
75
+ ```
76
+
77
+ The generated `.d.ts` now uses `return_` instead of the invalid `return`:
78
+
79
+ ``` typescript
80
+ export const object_path: ((
81
+ return_: RequiredRouteParameter,
82
+ id: RequiredRouteParameter,
83
+ options?: RouteOptions
84
+ ) => string) & RouteHelperExtras;
85
+ ```
86
+
87
+ In `compact` mode, if the short helper name would itself be a reserved word, the `_path` suffix is kept as a fallback:
88
+
89
+ ``` ruby
90
+ # route named :return with compact: true
91
+ return_path(…) # kept — `return` alone would be invalid
92
+ ```
93
+
94
+ ### Bug Fixes
95
+
96
+ * Fix `generate!` with `typed: true` raising when `package:` option is set. The `package:` option is now stripped before being forwarded to `definitions!`, which only generates type declarations and has no use for it.
97
+ * Support `config.javascript_path` Rails configuration. Fixes [#344](https://github.com/railsware/js-routes/issues/344).
98
+ * Do not emit a deprecation warning when `prefix` is set to an empty string. Fixes [#340](https://github.com/railsware/js-routes/issues/340).
99
+
5
100
  ## [2.3.7]
6
101
 
7
102
  * Obfuscate assignment to module.exports in order to prevent warnings in javascript bundlers, like Vite. Fixes [#337](https://github.com/railsware/js-routes/issues/337).
data/Readme.md CHANGED
@@ -4,7 +4,16 @@
4
4
 
5
5
  <img src="/logo.webp" alt="Logo" width="200" height="200">
6
6
 
7
- Generates javascript file that defines all Rails named routes as javascript helpers:
7
+ Generates javascript file that defines all Rails named routes as javascript functions:
8
+
9
+ ``` ruby
10
+ Rails.application.routes.draw do
11
+ root "home#index"
12
+ namespace :api do
13
+ resources :users
14
+ end
15
+ end
16
+ ```
8
17
 
9
18
  ``` js
10
19
  import { root_path, api_user_path } from './routes';
@@ -15,6 +24,12 @@ api_user_path(25, include_profile: true, format: 'json') // => /api/users/25.jso
15
24
 
16
25
  [More Examples](#usage)
17
26
 
27
+ ## Philosophy
28
+
29
+ 1. Move fast, break nothing.
30
+ 2. Minimum invention, maximum compatiblity with Rails.
31
+ 3. Ready for your advanced architecture.
32
+
18
33
  ## Intallation
19
34
 
20
35
  Your Rails Gemfile:
@@ -240,6 +255,31 @@ which will cause any `JsRoutes` instance to generate defintions instead of route
240
255
 
241
256
  <div id="sprockets"></div>
242
257
 
258
+ <div id="package"></div>
259
+
260
+ #### Using shared package
261
+
262
+ Some setups may benefit from splitting routes into multiple files with the core js-routes utils shared between these files. This can be helpful for codebases that have a large number of routes and can improve tree shaking. This is only available when the `module_type` is set to `ESM`.
263
+
264
+ ```ruby
265
+ class AdvancedJsRoutesMiddleware < JsRoutes::Middleware
266
+ def regenerate
267
+ JsRoutes.package!
268
+
269
+ JsRoutes.generate!(
270
+ "admin_routes.js",
271
+ include: /^admin_/,
272
+ package: './router.js'
273
+ )
274
+ JsRoutes.generate!(
275
+ "api_routes.js",
276
+ include: /^api_/,
277
+ package: './router.js'
278
+ )
279
+ end
280
+ end
281
+ ```
282
+
243
283
  ### Sprockets (Deprecated)
244
284
 
245
285
  If you are using [Sprockets](https://github.com/rails/sprockets-rails) you may configure js-routes in the following way.
@@ -328,6 +368,10 @@ Options to configure JavaScript file generator. These options are only available
328
368
  * Default: `-> { Rails.application }`
329
369
  * `file` - a file location where generated routes are stored
330
370
  * Default: `app/javascript/routes.js` if setup with Webpacker, otherwise `app/assets/javascripts/routes.js` if setup with Sprockets.
371
+ * `package` - specify where the shared package will be imported from. e.g. `'./router.js'`.
372
+ * Generate the shared package with `package!`.
373
+ * See [Using shared package](#package).
374
+ * Default: `nil`
331
375
  * `optional_definition_params` - make all route paramters in definition optional
332
376
  * See [related compatibility issue](#optional-definition-params)
333
377
  * Default: `false`
@@ -359,6 +403,12 @@ Options to configure routes formatting. These options are available both in Ruby
359
403
  * Default: `nil`. Uses built-in serializer compatible with Rails
360
404
  * Example: `jQuery.param` - use jQuery's serializer algorithm. You can attach serialize function from your favorite AJAX framework.
361
405
  * Example: `function (object) { ... }` - use completely custom serializer of your application.
406
+ * `include_undefined_query_parameters` - when using the built-in serializer, include query object properties whose value is `undefined` as Rails `nil` instead of omitting them.
407
+ * Default: `nil`. Preserves legacy serialization and emits a warning until this option is set explicitly.
408
+ * New generated initializers set this option to `false`.
409
+ * Set this option to `false` to omit `undefined` query object properties.
410
+ * Compatible with Rails nil query serialization: explicit `null` still serializes as Rails `nil`, including the Rails 8.1 bare-key behavior.
411
+ * Custom `serializer` functions remain responsible for their own `undefined` handling.
362
412
  * `special_options_key` - a special key that helps JsRoutes to destinguish serialized model from options hash
363
413
  * This option exists because JS doesn't provide a difference between an object and a hash
364
414
  * Default: `_options`
@@ -18,6 +18,12 @@ module JsRoutes
18
18
  attr_accessor :include
19
19
  sig { returns(FileName) }
20
20
  attr_accessor :file
21
+ sig { returns(T.nilable(String)) }
22
+ attr_reader :package
23
+
24
+ def package=(value)
25
+ @package = value == true ? "./router.js" : (value == false ? nil : value)
26
+ end
21
27
  sig { returns(Prefix) }
22
28
  attr_reader :prefix
23
29
  sig { returns(T::Boolean) }
@@ -26,6 +32,8 @@ module JsRoutes
26
32
  attr_accessor :camel_case
27
33
  sig { returns(Options) }
28
34
  attr_accessor :default_url_options
35
+ sig { returns(T.nilable(T::Boolean)) }
36
+ attr_accessor :include_undefined_query_parameters
29
37
  sig { returns(T::Boolean) }
30
38
  attr_accessor :compact
31
39
  sig { returns(T.nilable(String)) }
@@ -42,6 +50,10 @@ module JsRoutes
42
50
  attr_accessor :optional_definition_params
43
51
  sig { returns(BannerCaller) }
44
52
  attr_accessor :banner
53
+ sig { returns(T::Boolean) }
54
+ attr_accessor :deprecated_false_parameter_behavior
55
+ sig { returns(T::Boolean) }
56
+ attr_accessor :deprecated_nil_query_parameter_behavior
45
57
 
46
58
  sig {params(attributes: T.nilable(Options)).void }
47
59
  def initialize(attributes = nil)
@@ -53,6 +65,7 @@ module JsRoutes
53
65
  @url_links = T.let(false, T::Boolean)
54
66
  @camel_case = T.let(false, T::Boolean)
55
67
  @default_url_options = T.let(T.unsafe({}), Options)
68
+ @include_undefined_query_parameters = T.let(nil, T.nilable(T::Boolean))
56
69
  @compact = T.let(false, T::Boolean)
57
70
  @serializer = T.let(nil, T.nilable(String))
58
71
  @special_options_key = T.let("_options", Literal)
@@ -61,6 +74,15 @@ module JsRoutes
61
74
  @documentation = T.let(true, T::Boolean)
62
75
  @optional_definition_params = T.let(false, T::Boolean)
63
76
  @banner = T.let(default_banner, BannerCaller)
77
+ @package = T.let(nil, T.nilable(String))
78
+ @deprecated_false_parameter_behavior = T.let(
79
+ defined?(Rails) ? JsRoutes::Utils.rails_version < Gem::Version.new('7.0.0') : false,
80
+ T::Boolean
81
+ )
82
+ @deprecated_nil_query_parameter_behavior = T.let(
83
+ defined?(Rails) ? JsRoutes::Utils.rails_version < Gem::Version.new('8.1.0') : false,
84
+ T::Boolean
85
+ )
64
86
 
65
87
  return unless attributes
66
88
  assign(attributes)
@@ -92,7 +114,7 @@ module JsRoutes
92
114
  end
93
115
 
94
116
  def prefix=(value)
95
- JsRoutes::Utils.deprecator.warn("JsRoutes configuration prefix is deprecated in favor of default_url_options.script_name.")
117
+ JsRoutes::Utils.deprecator.warn("JsRoutes configuration prefix is deprecated in favor of default_url_options.script_name.") unless value.blank?
96
118
  @prefix = value
97
119
  end
98
120
 
@@ -111,11 +133,20 @@ module JsRoutes
111
133
  self.module_type === 'DTS'
112
134
  end
113
135
 
136
+ sig {returns(T::Boolean)}
137
+ def pkg?
138
+ module_type === 'PKG'
139
+ end
140
+
114
141
  sig {returns(T::Boolean)}
115
142
  def modern?
116
143
  esm? || dts?
117
144
  end
118
145
 
146
+ def use_package?
147
+ esm? && package
148
+ end
149
+
119
150
  sig { void }
120
151
  def require_esm
121
152
  raise "ESM module type is required" unless modern?
@@ -123,21 +154,44 @@ module JsRoutes
123
154
 
124
155
  sig { returns(String) }
125
156
  def source_file
126
- File.dirname(__FILE__) + "/../" + default_file_name
157
+ template = dts? ? "routes.d.ts" : "routes.js"
158
+ File.dirname(__FILE__) + "/../" + template
159
+ end
160
+
161
+ sig { returns(String) }
162
+ def router_source_file
163
+ template = dts? ? "router.d.ts" : "router.js"
164
+ File.dirname(__FILE__) + "/../" + template
127
165
  end
128
166
 
129
167
  sig { returns(Pathname) }
130
168
  def output_file
169
+ output_file_path(file || default_file_name)
170
+ end
171
+
172
+ protected
173
+
174
+ sig { params(file_name: FileName).returns(Pathname) }
175
+ def output_file_path(file_name)
131
176
  shakapacker = JsRoutes::Utils.shakapacker
132
177
  shakapacker_dir = shakapacker ?
133
- shakapacker.config.source_path : pathname('app', 'javascript')
178
+ shakapacker.config.source_path : pathname(self.class.rails_javascript_path)
134
179
  sprockets_dir = pathname('app','assets','javascripts')
135
- file_name = file || default_file_name
136
180
  sprockets_file = sprockets_dir.join(file_name)
137
181
  webpacker_file = shakapacker_dir.join(file_name)
138
182
  !Dir.exist?(shakapacker_dir) && defined?(::Sprockets) ? sprockets_file : webpacker_file
139
183
  end
140
184
 
185
+ sig { returns(String) }
186
+ def self.rails_javascript_path
187
+ js_dir = if defined?(Rails) && Rails.application&.config&.respond_to?(:javascript_path)
188
+ Rails.application.config.javascript_path
189
+ else
190
+ "javascript"
191
+ end
192
+ "app/#{js_dir}"
193
+ end
194
+
141
195
  protected
142
196
 
143
197
  sig { void }
@@ -153,7 +207,7 @@ module JsRoutes
153
207
 
154
208
  sig { returns(String) }
155
209
  def default_file_name
156
- dts? ? "routes.d.ts" : "routes.js"
210
+ dts? ? "routes.d.ts" : pkg? ? "router.js" : "routes.js"
157
211
  end
158
212
 
159
213
  sig {void}
@@ -166,6 +220,9 @@ module JsRoutes
166
220
  if module_type != 'NIL' && namespace
167
221
  raise "JsRoutes namespace option can only be used if module_type is nil"
168
222
  end
223
+ if package && !esm?
224
+ raise "JsRoutes package option can only be used with ESM module type"
225
+ end
169
226
  end
170
227
 
171
228
  sig { returns(T.proc.returns(String)) }
@@ -11,11 +11,12 @@ class JsRoutes::Generators::Base < Rails::Generators::Base
11
11
  protected
12
12
 
13
13
  def application_js_path
14
+ js_dir = JsRoutes::Configuration.rails_javascript_path
14
15
  [
15
- "app/javascript/packs/application.ts",
16
- "app/javascript/packs/application.js",
17
- "app/javascript/controllers/application.ts",
18
- "app/javascript/controllers/application.js",
16
+ "#{js_dir}/packs/application.ts",
17
+ "#{js_dir}/packs/application.js",
18
+ "#{js_dir}/controllers/application.ts",
19
+ "#{js_dir}/controllers/application.js",
19
20
  ].find do |path|
20
21
  File.exist?(Rails.root.join(path))
21
22
  end
@@ -28,29 +28,30 @@ module JsRoutes
28
28
  application = T.unsafe(self.application)
29
29
  if routes_from(application).empty?
30
30
  if application.is_a?(Rails::Application)
31
- if Rails.version >= "8.0.0"
31
+ if JsRoutes::Utils.rails_version >= Gem::Version.new("8.0.0")
32
32
  T.unsafe(application).reload_routes_unless_loaded
33
33
  else
34
34
  T.unsafe(application).reload_routes!
35
35
  end
36
36
  end
37
37
  end
38
- content = File.read(@configuration.source_file)
39
38
 
40
- unless @configuration.dts?
41
- content = js_variables.inject(content) do |js, (key, value)|
42
- js.gsub!("RubyVariables.#{key}", value.to_s) ||
43
- raise("Missing key #{key} in JS template")
44
- end
45
- end
46
39
  unless @configuration.module_type == "NIL"
47
- banner + content + routes_export + prevent_types_export
40
+ banner + jsr + routes_export
48
41
  else
49
- content.sub('"use strict";', "")
42
+ # Strip the empty IMPORT_ROUTER statement (comment + semicolon) left after substitution
43
+ jsr.sub(/\A(\/\/[^\n]+\n)*;\n/, "")
50
44
  end
51
45
 
52
46
  end
53
47
 
48
+ sig {returns(String)}
49
+ def package
50
+ raise "Package generation requires module_type: 'PKG'" unless @configuration.pkg?
51
+
52
+ jsr
53
+ end
54
+
54
55
  sig { returns(String) }
55
56
  def banner
56
57
  banner = @configuration.banner
@@ -83,6 +84,22 @@ module JsRoutes
83
84
  end
84
85
  end
85
86
 
87
+ sig { void }
88
+ def package!
89
+ raise "Package generation requires module_type: 'PKG'" unless @configuration.pkg?
90
+
91
+ file_path = Rails.root.join(@configuration.output_file)
92
+ source_code = package
93
+
94
+ # We don't need to rewrite file if it already exist and have same content.
95
+ # It helps asset pipeline or webpack understand that file wasn't changed.
96
+ return if File.exist?(file_path) && File.read(file_path) == source_code
97
+
98
+ File.open(file_path, 'w') do |f|
99
+ f.write source_code
100
+ end
101
+ end
102
+
86
103
  sig { void }
87
104
  def remove!
88
105
  path = Rails.root.join(@configuration.output_file)
@@ -92,27 +109,91 @@ module JsRoutes
92
109
 
93
110
  protected
94
111
 
112
+ ESM_MODULE_MARKER = /export \{\};\n?\z/
113
+
114
+ def read_js(path)
115
+ File.read(path).sub(ESM_MODULE_MARKER, "")
116
+ end
117
+
118
+ sig { returns(String) }
119
+ def jsr
120
+ return pkg_jsr if @configuration.pkg?
121
+
122
+ if @configuration.dts?
123
+ return File.read(@configuration.router_source_file)
124
+ end
125
+
126
+ content = read_js(@configuration.source_file)
127
+
128
+ js_variables.inject(content) do |js, (key, value)|
129
+ js.gsub!("RubyVariables.#{key}", value.to_s) ||
130
+ raise("Missing key #{key} in JS template")
131
+ end
132
+ end
133
+
134
+ sig { returns(String) }
135
+ def pkg_jsr
136
+ read_js(@configuration.router_source_file) + "export default Router;\n"
137
+ end
138
+
95
139
  sig { returns(T::Hash[String, String]) }
96
140
  def js_variables
141
+ warn_on_implicit_undefined_query_parameter_behavior
142
+
97
143
  prefix = @configuration.prefix
98
144
  prefix = prefix.call if prefix.is_a?(Proc)
99
145
  {
100
146
  'ROUTES_OBJECT' => routes_object,
101
- 'DEPRECATED_FALSE_PARAMETER_BEHAVIOR' => Rails.version < '7.0.0',
102
- 'DEPRECATED_NIL_QUERY_PARAMETER_BEHAVIOR' => Rails.version < '8.1.0',
147
+ 'DEPRECATED_FALSE_PARAMETER_BEHAVIOR' => @configuration.deprecated_false_parameter_behavior,
148
+ 'DEPRECATED_NIL_QUERY_PARAMETER_BEHAVIOR' => @configuration.deprecated_nil_query_parameter_behavior,
149
+ 'INCLUDE_UNDEFINED_QUERY_PARAMETERS' => json(@configuration.include_undefined_query_parameters != false),
103
150
  'DEFAULT_URL_OPTIONS' => json(@configuration.default_url_options),
104
151
  'PREFIX' => json(prefix),
105
152
  'SPECIAL_OPTIONS_KEY' => json(@configuration.special_options_key),
106
153
  'SERIALIZER' => @configuration.serializer || json(nil),
107
154
  'MODULE_TYPE' => json(@configuration.module_type),
108
155
  'WRAPPER' => wrapper_variable,
156
+ "IMPORT_ROUTER" => import_router_variable,
157
+ "EMBED_ROUTER" => embed_router_variable,
109
158
  }
110
159
  end
111
160
 
161
+ sig { void }
162
+ def warn_on_implicit_undefined_query_parameter_behavior
163
+ return unless @configuration.include_undefined_query_parameters.nil?
164
+
165
+ JsRoutes::Utils.deprecator.warn(
166
+ "JsRoutes include_undefined_query_parameters is not configured. " \
167
+ "Set JsRoutes.setup { |c| c.include_undefined_query_parameters = false } " \
168
+ "to omit undefined query parameters, or set it to true to keep legacy nil serialization. " \
169
+ "The default will change to false in a future release."
170
+ )
171
+ end
172
+
173
+ sig { returns(String) }
174
+ def embed_router_variable
175
+ unless @configuration.use_package? || @configuration.modern?
176
+ read_js(@configuration.router_source_file)
177
+ else
178
+ ""
179
+ end
180
+ end
181
+
182
+ sig { returns(String) }
183
+ def import_router_variable
184
+ if @configuration.use_package?
185
+ "import Router from '#{@configuration.package}'"
186
+ elsif @configuration.modern?
187
+ read_js(@configuration.router_source_file)
188
+ else
189
+ ""
190
+ end
191
+ end
192
+
112
193
  sig { returns(String) }
113
194
  def wrapper_variable
114
195
  case @configuration.module_type
115
- when 'ESM'
196
+ when 'ESM', 'PKG'
116
197
  'const __jsr = '
117
198
  when 'NIL'
118
199
  namespace = @configuration.namespace
@@ -148,7 +229,7 @@ module JsRoutes
148
229
 
149
230
  sig { returns(String) }
150
231
  def routes_object
151
- return json({}) if @configuration.modern?
232
+ return json({}) if @configuration.modern? || @configuration.pkg?
152
233
  properties = routes_list.map do |comment, name, body|
153
234
  "#{comment}#{name}: #{body}".indent(2)
154
235
  end
@@ -157,7 +238,7 @@ module JsRoutes
157
238
 
158
239
  sig { returns(T::Array[StringArray]) }
159
240
  def static_exports
160
- [:configure, :config, :serialize].map do |name|
241
+ [:configure, :config, :serialize, :__route].map do |name|
161
242
  [
162
243
  "", name.to_s,
163
244
  @configuration.dts? ?
@@ -175,16 +256,6 @@ module JsRoutes
175
256
  end.join
176
257
  end
177
258
 
178
- sig { returns(String) }
179
- def prevent_types_export
180
- return "" unless @configuration.dts?
181
- <<-JS
182
- // By some reason this line prevents all types in a file
183
- // from being automatically exported
184
- export {};
185
- JS
186
- end
187
-
188
259
  sig { returns(String) }
189
260
  def export_separator
190
261
  @configuration.dts? ? ': ' : ' = '
@@ -10,6 +10,21 @@ module JsRoutes
10
10
 
11
11
  FILTERED_DEFAULT_PARTS = T.let([:controller, :action].freeze, SymbolArray)
12
12
  URL_OPTIONS = T.let([:protocol, :domain, :host, :port, :subdomain].freeze, SymbolArray)
13
+
14
+ # JavaScript reserved words that are invalid as function parameter names.
15
+ # @see https://www.w3schools.com/js/js_reserved.asp
16
+ JS_RESERVED_WORDS = T.let(%w[
17
+ abstract arguments async await boolean break byte case
18
+ catch char class const continue debugger default delete
19
+ do double else enum eval export extends false
20
+ final finally float for function goto if implements
21
+ import in instanceof int interface let long native
22
+ new null package private protected public return short
23
+ static super switch synchronized this throw throws transient
24
+ true try typeof using var void volatile while
25
+ with yield
26
+ ].freeze, T::Array[String])
27
+
13
28
  NODE_TYPES = T.let({
14
29
  GROUP: 1,
15
30
  CAT: 2,
@@ -55,11 +70,12 @@ module JsRoutes
55
70
  if @configuration.dts?
56
71
  definition_body
57
72
  else
58
- # For tree-shaking ESM, add a #__PURE__ comment informing js bundlers that the call to `__jsr.r`
59
- # has no side-effects (e.g. modifying global variables) and is safe to remove when unused.
73
+ # For tree-shaking ESM, add a #__PURE__ comment informing js bundlers that the call has
74
+ # no side-effects (e.g. modifying global variables) and is safe to remove when unused.
60
75
  # https://webpack.js.org/guides/tree-shaking/#clarifying-tree-shaking-and-sidyeeffects
61
76
  pure_comment = @configuration.esm? ? '/*#__PURE__*/ ' : ''
62
- "#{pure_comment}__jsr.r(#{arguments(absolute).map{|a| json(a)}.join(', ')})"
77
+ call = '__route'
78
+ "#{pure_comment}#{call}(#{arguments(absolute).map{|a| json(a)}.join(', ')})"
63
79
  end
64
80
  end
65
81
 
@@ -67,7 +83,7 @@ module JsRoutes
67
83
  def definition_body
68
84
  options_type = optional_parts_type ? "#{optional_parts_type} & RouteOptions" : "RouteOptions"
69
85
  predicate = @configuration.optional_definition_params ? '?' : ''
70
- args = required_parts.map{|p| "#{apply_case(p)}#{predicate}: RequiredRouteParameter"}
86
+ args = required_parts.map{|p| "#{format_param(p)}#{predicate}: RequiredRouteParameter"}
71
87
  args << "options?: #{options_type}"
72
88
  "((\n#{args.join(",\n").indent(2)}\n) => string) & RouteHelperExtras"
73
89
  end
@@ -76,7 +92,7 @@ module JsRoutes
76
92
  def optional_parts_type
77
93
  return nil if optional_parts.empty?
78
94
  @optional_parts_type ||= T.let(
79
- "{" + optional_parts.map {|p| "#{p}?: OptionalRouteParameter"}.join(', ') + "}",
95
+ "{" + optional_parts.map {|p| "#{format_param(p)}?: OptionalRouteParameter"}.join(', ') + "}",
80
96
  T.nilable(String)
81
97
  )
82
98
  end
@@ -117,8 +133,14 @@ module JsRoutes
117
133
 
118
134
  sig { params(absolute: T::Boolean).returns(String) }
119
135
  def helper_name(absolute)
120
- suffix = absolute ? :url : @configuration.compact ? nil : :path
121
- apply_case(base_name, suffix)
136
+ apply_case(base_name, helper_suffix(absolute))
137
+ end
138
+
139
+ sig { params(absolute: T::Boolean).returns(T.nilable(Symbol)) }
140
+ def helper_suffix(absolute)
141
+ return :url if absolute
142
+ return :path unless @configuration.compact
143
+ JS_RESERVED_WORDS.include?(apply_case(base_name)) ? :path : nil
122
144
  end
123
145
 
124
146
  sig { returns(String) }
@@ -173,7 +195,7 @@ JS
173
195
  sig { returns(String) }
174
196
  def documentation_params
175
197
  required_parts.map do |param|
176
- "\n * @param {any} #{apply_case(param)}"
198
+ "\n * @param {any} #{format_param(param)}"
177
199
  end.join
178
200
  end
179
201
 
@@ -188,6 +210,16 @@ JS
188
210
  @configuration.camel_case ? value.camelize(:lower) : value
189
211
  end
190
212
 
213
+ sig { params(part: T.nilable(Literal)).returns(String) }
214
+ def format_param(part)
215
+ sanitize_param(apply_case(part))
216
+ end
217
+
218
+ sig { params(name: String).returns(String) }
219
+ def sanitize_param(name)
220
+ JS_RESERVED_WORDS.include?(name) ? "#{name}_" : name
221
+ end
222
+
191
223
  # This function serializes Journey route into JSON structure
192
224
  # We do not use Hash for human readable serialization
193
225
  # And preffer Array serialization because it is shorter.
@@ -16,12 +16,17 @@ module JsRoutes
16
16
 
17
17
  sig { returns(T.untyped) }
18
18
  def self.deprecator
19
- if defined?(Rails) && Rails.version >= "7.1.0"
19
+ if defined?(Rails) && Rails.respond_to?(:deprecator)
20
20
  Rails.deprecator
21
21
  else
22
22
  ActiveSupport::Deprecation
23
23
  end
24
24
  end
25
+
26
+ sig { returns(Gem::Version) }
27
+ def self.rails_version
28
+ Gem::Version.new(Rails.version)
29
+ end
25
30
  end
26
31
 
27
32
  end
@@ -1,4 +1,4 @@
1
1
  # typed: strict
2
2
  module JsRoutes
3
- VERSION = "2.3.7"
3
+ VERSION = "2.4.0"
4
4
  end