js-routes 2.3.7 → 2.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8e1323cccad426e331d7f99923e967b09d06e028ec3f2665798dad91ff5ce582
4
- data.tar.gz: 4ddb07ef468133b25d7ace1313b948f689a77a1c576e3102e88ff2b87f82a5c5
3
+ metadata.gz: 80c67cfe21ee9772f3912537cec0994797a76128083d495d9963381888e7833c
4
+ data.tar.gz: b2d63fcf45117749fa00be661b94fcd8a606ac711bdbcfd1bc2699be87d7c407
5
5
  SHA512:
6
- metadata.gz: 9840b6fa125bec3de173ce942e7daae8cac4de026d424d70a4bb82d67d9cf075d050c182c02fadef9d5c64a989599dd3a0cbc59197652d836b44f3452588bad2
7
- data.tar.gz: 449669e3e6719f2c1ee22bd1b6b7bfaa34ca2821f7d6050563d76f43781c8e4efc8c1d6e986443007451550002abad649e6dc6bdf10629ba683be3ccf3f3f1ee
6
+ metadata.gz: abebd07a033d7046989e71b1fc00aad559c1973f9eadaf0f93df25057bd01d1c3cd9bca2447d272c2c56ccef10a9803d6ae81479999be7ed315eefeb7c0211e1
7
+ data.tar.gz: c000eb635b4b77167cb04df8b3b691528cebe78ce557d8bcab75e95c420e033b79d066e3f8135a7b04f5b899c964512e741b5c0fc0a30c99692d1b2ebb76b883
data/CHANGELOG.md CHANGED
@@ -2,6 +2,118 @@
2
2
 
3
3
  ## Pending
4
4
 
5
+ ## [2.4.1]
6
+
7
+ ### Fix camelCase TypeScript definitions for optional parameters
8
+
9
+ Fix a bug introduced in 2.4.0 where `camel_case: true` caused TypeScript type definitions to advertise camelCase keys for optional route parameters while the JavaScript runtime still required snake_case. Fixes [#351](https://github.com/railsware/js-routes/issues/351).
10
+
11
+ Optional parameter names in `.d.ts` output now match what the runtime actually accepts:
12
+
13
+ ```typescript
14
+ // Before (broken): type advertised perPage but runtime required per_page
15
+ itemsPath({ perPage: 20 }) // → "/items?perPage=20" ✗ (silently wrong)
16
+ itemsPath({ per_page: 20 }) // → "/items/20" ✓ (worked but type was misleading)
17
+
18
+ // After (fixed): type and runtime agree on snake_case
19
+ itemsPath({ per_page: 20 }) // → "/items/20" ✓
20
+ ```
21
+
22
+ ## [2.4.0]
23
+
24
+ ### Package mode
25
+
26
+ Add `package` option and `JsRoutes.package` / `JsRoutes.package!` API for sharing a single Router runtime across multiple route files.
27
+
28
+ **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.
29
+
30
+ Generate the shared router package (no route definitions, just the runtime):
31
+
32
+ ``` ruby
33
+ # config/initializers/js_routes.rb
34
+ JsRoutes.package! # writes app/javascript/router.js
35
+ # or with a custom path:
36
+ JsRoutes.package!("shared/router.js")
37
+ ```
38
+
39
+ Generate consumer route files that import from it:
40
+
41
+ ``` ruby
42
+ JsRoutes.generate!(
43
+ "app/javascript/admin_routes.js",
44
+ typed: true,
45
+ package: "./router.js", # or package: true for the default path
46
+ include: /\Aadmin_/
47
+ )
48
+
49
+ JsRoutes.generate!(
50
+ "app/javascript/api_routes.js",
51
+ typed: true,
52
+ package: "./router.js",
53
+ include: /\Aapi_/
54
+ )
55
+ ```
56
+
57
+ Or share one configuration block:
58
+
59
+ ``` ruby
60
+ JsRoutes.setup do |c|
61
+ c.module_type = "ESM"
62
+ c.package = "./router.js" # all generated files import the same runtime
63
+ end
64
+
65
+ JsRoutes.package! # router.js — runtime only
66
+ JsRoutes.generate! # routes.js — route helpers only
67
+ ```
68
+
69
+ `package: true` is a shorthand for `package: "./router.js"`.
70
+
71
+ ### `include_undefined_query_parameters` option
72
+
73
+ Add `include_undefined_query_parameters` configuration option. Fixes [#345](https://github.com/railsware/js-routes/issues/345).
74
+
75
+ 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`.
76
+
77
+ * 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.
78
+ * Set it to `true` only when the application depends on serializing `undefined` as Rails `nil` (legacy behavior).
79
+ * Explicit `null` values continue to serialize as Rails `nil` in all cases.
80
+ * When unset, the legacy behavior is preserved and a deprecation warning is emitted so apps can opt in deliberately.
81
+
82
+ ### JavaScript reserved word escaping
83
+
84
+ 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.
85
+
86
+ Given a route like:
87
+
88
+ ``` ruby
89
+ scope "/returns/:return" do
90
+ resources :objects, only: [:show]
91
+ end
92
+ ```
93
+
94
+ The generated `.d.ts` now uses `return_` instead of the invalid `return`:
95
+
96
+ ``` typescript
97
+ export const object_path: ((
98
+ return_: RequiredRouteParameter,
99
+ id: RequiredRouteParameter,
100
+ options?: RouteOptions
101
+ ) => string) & RouteHelperExtras;
102
+ ```
103
+
104
+ In `compact` mode, if the short helper name would itself be a reserved word, the `_path` suffix is kept as a fallback:
105
+
106
+ ``` ruby
107
+ # route named :return with compact: true
108
+ return_path(…) # kept — `return` alone would be invalid
109
+ ```
110
+
111
+ ### Bug Fixes
112
+
113
+ * 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.
114
+ * Support `config.javascript_path` Rails configuration. Fixes [#344](https://github.com/railsware/js-routes/issues/344).
115
+ * Do not emit a deprecation warning when `prefix` is set to an empty string. Fixes [#340](https://github.com/railsware/js-routes/issues/340).
116
+
5
117
  ## [2.3.7]
6
118
 
7
119
  * 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,29 @@ 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 + routes_js + routes_export
48
41
  else
49
- content.sub('"use strict";', "")
42
+ routes_js
50
43
  end
51
44
 
52
45
  end
53
46
 
47
+ sig {returns(String)}
48
+ def package
49
+ raise "Package generation requires module_type: 'PKG'" unless @configuration.pkg?
50
+
51
+ routes_js
52
+ end
53
+
54
54
  sig { returns(String) }
55
55
  def banner
56
56
  banner = @configuration.banner
@@ -83,6 +83,22 @@ module JsRoutes
83
83
  end
84
84
  end
85
85
 
86
+ sig { void }
87
+ def package!
88
+ raise "Package generation requires module_type: 'PKG'" unless @configuration.pkg?
89
+
90
+ file_path = Rails.root.join(@configuration.output_file)
91
+ source_code = package
92
+
93
+ # We don't need to rewrite file if it already exist and have same content.
94
+ # It helps asset pipeline or webpack understand that file wasn't changed.
95
+ return if File.exist?(file_path) && File.read(file_path) == source_code
96
+
97
+ File.open(file_path, 'w') do |f|
98
+ f.write source_code
99
+ end
100
+ end
101
+
86
102
  sig { void }
87
103
  def remove!
88
104
  path = Rails.root.join(@configuration.output_file)
@@ -92,41 +108,105 @@ module JsRoutes
92
108
 
93
109
  protected
94
110
 
111
+ ESM_MODULE_MARKER = /export \{\};\n?\z/
112
+
113
+ def read_js(path)
114
+ File.read(path).sub(ESM_MODULE_MARKER, "")
115
+ end
116
+
117
+ sig { returns(String) }
118
+ def routes_js
119
+ return pkg_jsr if @configuration.pkg?
120
+
121
+ if @configuration.dts?
122
+ return File.read(@configuration.router_source_file)
123
+ end
124
+
125
+ content = read_js(@configuration.source_file)
126
+
127
+ js_variables.inject(content) do |js, (key, value)|
128
+ js.gsub!("RubyVariables.#{key}", value.to_s) ||
129
+ raise("Missing key #{key} in JS template")
130
+ end
131
+ end
132
+
133
+ sig { returns(String) }
134
+ def pkg_jsr
135
+ read_js(@configuration.router_source_file) + "export default Router;\n"
136
+ end
137
+
95
138
  sig { returns(T::Hash[String, String]) }
96
139
  def js_variables
140
+ warn_on_implicit_undefined_query_parameter_behavior
141
+
97
142
  prefix = @configuration.prefix
98
143
  prefix = prefix.call if prefix.is_a?(Proc)
99
144
  {
100
145
  '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',
146
+ 'DEPRECATED_FALSE_PARAMETER_BEHAVIOR' => @configuration.deprecated_false_parameter_behavior,
147
+ 'DEPRECATED_NIL_QUERY_PARAMETER_BEHAVIOR' => @configuration.deprecated_nil_query_parameter_behavior,
148
+ 'INCLUDE_UNDEFINED_QUERY_PARAMETERS' => json(@configuration.include_undefined_query_parameters != false),
103
149
  'DEFAULT_URL_OPTIONS' => json(@configuration.default_url_options),
104
150
  'PREFIX' => json(prefix),
105
151
  'SPECIAL_OPTIONS_KEY' => json(@configuration.special_options_key),
106
152
  'SERIALIZER' => @configuration.serializer || json(nil),
107
153
  'MODULE_TYPE' => json(@configuration.module_type),
108
154
  'WRAPPER' => wrapper_variable,
155
+ "EMBED_ROUTER" => embed_router_variable,
109
156
  }
110
157
  end
111
158
 
159
+ sig { void }
160
+ def warn_on_implicit_undefined_query_parameter_behavior
161
+ return unless @configuration.include_undefined_query_parameters.nil?
162
+
163
+ JsRoutes::Utils.deprecator.warn(
164
+ "JsRoutes include_undefined_query_parameters is not configured. " \
165
+ "Set JsRoutes.setup { |c| c.include_undefined_query_parameters = false } " \
166
+ "to omit undefined query parameters, or set it to true to keep legacy nil serialization. " \
167
+ "The default will change to false in a future release."
168
+ )
169
+ end
170
+
171
+ sig { returns(String) }
172
+ def embed_router_variable
173
+ unless @configuration.use_package? || @configuration.modern?
174
+ read_js(@configuration.router_source_file)
175
+ else
176
+ ""
177
+ end
178
+ end
179
+
180
+ sig { returns(String) }
181
+ def router_js
182
+ if @configuration.use_package?
183
+ "import Router from '#{@configuration.package}';"
184
+ elsif @configuration.modern?
185
+ read_js(@configuration.router_source_file)
186
+ else
187
+ ""
188
+ end
189
+ end
190
+
112
191
  sig { returns(String) }
113
192
  def wrapper_variable
114
193
  case @configuration.module_type
115
- when 'ESM'
116
- 'const __jsr = '
194
+ when 'ESM', 'PKG'
195
+ "#{router_js}const __jsr = "
117
196
  when 'NIL'
118
197
  namespace = @configuration.namespace
119
198
  if namespace
120
- if namespace.include?('.')
199
+ assignment = if namespace.include?('.')
121
200
  "#{namespace} = "
122
201
  else
123
202
  "(typeof window !== 'undefined' ? window : this).#{namespace} = "
124
203
  end
204
+ "#{router_js}#{assignment}"
125
205
  else
126
- ''
206
+ router_js
127
207
  end
128
208
  else
129
- ''
209
+ router_js
130
210
  end
131
211
  end
132
212
 
@@ -148,7 +228,7 @@ module JsRoutes
148
228
 
149
229
  sig { returns(String) }
150
230
  def routes_object
151
- return json({}) if @configuration.modern?
231
+ return json({}) if @configuration.modern? || @configuration.pkg?
152
232
  properties = routes_list.map do |comment, name, body|
153
233
  "#{comment}#{name}: #{body}".indent(2)
154
234
  end
@@ -157,7 +237,7 @@ module JsRoutes
157
237
 
158
238
  sig { returns(T::Array[StringArray]) }
159
239
  def static_exports
160
- [:configure, :config, :serialize].map do |name|
240
+ [:configure, :config, :serialize, :__route].map do |name|
161
241
  [
162
242
  "", name.to_s,
163
243
  @configuration.dts? ?
@@ -175,16 +255,6 @@ module JsRoutes
175
255
  end.join
176
256
  end
177
257
 
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
258
  sig { returns(String) }
189
259
  def export_separator
190
260
  @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
@@ -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.