js-routes 2.2.6 → 2.2.8
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 +4 -4
- data/CHANGELOG.md +10 -0
- data/Readme.md +24 -11
- data/lib/js_routes/configuration.rb +1 -1
- data/lib/js_routes/generators/base.rb +1 -1
- data/lib/js_routes/generators/webpacker.rb +1 -1
- data/lib/js_routes/route.rb +9 -2
- data/lib/js_routes/version.rb +1 -1
- data/lib/routes.d.ts +2 -2
- data/lib/routes.js +25 -3
- data/lib/routes.ts +34 -11
- data/spec/js_routes/default_serializer_spec.rb +1 -1
- data/spec/js_routes/module_types/dts/routes.spec.d.ts +2 -2
- data/spec/js_routes/module_types/dts_spec.rb +1 -1
- data/spec/js_routes/module_types/esm_spec.rb +2 -2
- data/spec/js_routes/module_types/umd_spec.rb +1 -1
- data/spec/js_routes/rails_routes_compatibility_spec.rb +17 -12
- data/spec/js_routes/route_specification_spec.rb +1 -4
- data/spec/js_routes/{zzz_last_post_rails_init_spec.rb → zzz_sprockets_spec.rb} +1 -1
- data/spec/spec_helper.rb +4 -0
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6dfe30c594c24cf044d0f19342e17cd6b23857da7ec68a0bf68d8f8be9f33def
|
4
|
+
data.tar.gz: 3aefa8fc04221f07996ada7c57f35b22d2ae519575a6df0e20c6d860fe6203ed
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 652fa60215a5c812e3a0cb0284870bfa63c6eeb1d249940388ade48a3f90a7209559d7cf66750e697ffc317bfcd1bec87e57fed44cad375795ead64fb1bcd254
|
7
|
+
data.tar.gz: a68bdb295fb704a5ed753d0576e700668aecfe30d5ec6d425ed458d029eb084de66ff26e0789c808e655574146973db02226a1a48f2cd81fa6ebda3379b7bc8c
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
## master
|
2
2
|
|
3
|
+
## v2.2.8
|
4
|
+
|
5
|
+
* Leave emoji symbols intact when encoding URI fragment [#312](https://github.com/railsware/js-routes/issues/312)
|
6
|
+
* Use webpacker config variable instead of hardcode [#309](https://github.com/railsware/js-routes/issues/309)
|
7
|
+
* Use `File.exist?` to be compatible with all versions of ruby [#310](https://github.com/railsware/js-routes/issues/310)
|
8
|
+
|
9
|
+
## v2.2.7
|
10
|
+
|
11
|
+
* Fix ESM Tree Shaking [#306](https://github.com/railsware/js-routes/issues/306)
|
12
|
+
|
3
13
|
## v2.2.6
|
4
14
|
|
5
15
|
* Prefer to extend `javascript:build` instead of `assets:precompile`. [#305](https://github.com/railsware/js-routes/issues/305)
|
data/Readme.md
CHANGED
@@ -16,16 +16,17 @@ gem "js-routes"
|
|
16
16
|
|
17
17
|
There are several possible ways to setup JsRoutes:
|
18
18
|
|
19
|
-
* [Quick and easy](#quick-start)
|
19
|
+
* [Quick and easy](#quick-start) - Recommended
|
20
20
|
* Uses Rack Middleware to automatically update routes locally
|
21
|
-
* Automatically generates routes files on
|
21
|
+
* Automatically generates routes files on javascript build
|
22
22
|
* Works great for a simple Rails application
|
23
|
-
* [Webpacker ERB Loader](#webpacker)
|
24
|
-
* Requires ESM module system (the default)
|
25
|
-
* Doesn't support typescript definitions
|
26
23
|
* [Advanced Setup](#advanced-setup)
|
27
24
|
* Allows very custom setups
|
28
|
-
*
|
25
|
+
* Automatic updates need to be customized
|
26
|
+
* [Webpacker ERB Loader](#webpacker) - Legacy
|
27
|
+
* Requires ESM module system (the default)
|
28
|
+
* Doesn't support typescript definitions
|
29
|
+
* [Sprockets](#sprockets) - Legacy
|
29
30
|
* Deprecated and not recommended for modern apps
|
30
31
|
|
31
32
|
<div id='quick-start'></div>
|
@@ -80,7 +81,7 @@ Add js-routes files to `.gitignore`:
|
|
80
81
|
|
81
82
|
### Webpacker ERB loader
|
82
83
|
|
83
|
-
**IMPORTANT**:
|
84
|
+
**IMPORTANT**: the setup doesn't support IDE autocompletion with [Typescript](https://www.typescriptlang.org/docs/handbook/declaration-files/templates/module-d-ts.html)
|
84
85
|
|
85
86
|
|
86
87
|
#### Use a Generator
|
@@ -414,9 +415,14 @@ JsRoutes itself does not have security holes.
|
|
414
415
|
It makes URLs without access protection more reachable by potential attacker.
|
415
416
|
If that is an issue for you, you may use one of the following solutions:
|
416
417
|
|
417
|
-
###
|
418
|
+
### ESM Tree shaking
|
419
|
+
|
420
|
+
Make sure `module_type` is set to `ESM` (the default). Modern JS bundlers like
|
421
|
+
[Webpack](https://webpack.js.org) can statically determine which ESM exports are used, and remove
|
422
|
+
the unused exports to reduce bundle size. This is known as [Tree
|
423
|
+
Shaking](https://webpack.js.org/guides/tree-shaking/).
|
418
424
|
|
419
|
-
|
425
|
+
JS files can use named imports to import only required routes into the file, like:
|
420
426
|
|
421
427
|
``` javascript
|
422
428
|
import {
|
@@ -428,8 +434,15 @@ import {
|
|
428
434
|
} from '../routes'
|
429
435
|
```
|
430
436
|
|
431
|
-
|
432
|
-
|
437
|
+
JS files can also use star imports (`import * as`) for tree shaking, as long as only explicit property accesses are used.
|
438
|
+
|
439
|
+
``` javascript
|
440
|
+
import * as routes from '../routes';
|
441
|
+
|
442
|
+
console.log(routes.inbox_path); // OK, only `inbox_path` is included in the bundle
|
443
|
+
|
444
|
+
console.log(Object.keys(routes)); // forces bundler to include all exports, breaking tree shaking
|
445
|
+
```
|
433
446
|
|
434
447
|
### Exclude option
|
435
448
|
|
@@ -75,7 +75,7 @@ module JsRoutes
|
|
75
75
|
end
|
76
76
|
|
77
77
|
def output_file
|
78
|
-
webpacker_dir = pathname('app', 'javascript')
|
78
|
+
webpacker_dir = defined?(Webpacker) ? Webpacker.config.source_path : pathname('app', 'javascript')
|
79
79
|
sprockets_dir = pathname('app','assets','javascripts')
|
80
80
|
file_name = file || default_file_name
|
81
81
|
sprockets_file = sprockets_dir.join(file_name)
|
@@ -10,7 +10,7 @@ class JsRoutes::Generators::Base < Rails::Generators::Base
|
|
10
10
|
"app/javascript/packs/application.js",
|
11
11
|
"app/javascript/controllers/application.js",
|
12
12
|
].find do |path|
|
13
|
-
File.
|
13
|
+
File.exist?(Rails.root.join(path))
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
@@ -7,7 +7,7 @@ class JsRoutes::Generators::Webpacker < Rails::Generators::Base
|
|
7
7
|
def create_webpack
|
8
8
|
copy_file "initializer.rb", "config/initializers/js_routes.rb"
|
9
9
|
copy_file "erb.js", "config/webpack/loaders/erb.js"
|
10
|
-
copy_file "routes.js.erb", "
|
10
|
+
copy_file "routes.js.erb", "#{Webpacker.config.source_path}/routes.js.erb"
|
11
11
|
inject_into_file "config/webpack/environment.js", loader_content
|
12
12
|
if path = application_js_path
|
13
13
|
inject_into_file path, pack_content
|
data/lib/js_routes/route.rb
CHANGED
@@ -33,8 +33,15 @@ module JsRoutes
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def body(absolute)
|
36
|
-
@configuration.dts?
|
37
|
-
definition_body
|
36
|
+
if @configuration.dts?
|
37
|
+
definition_body
|
38
|
+
else
|
39
|
+
# For tree-shaking ESM, add a #__PURE__ comment informing Webpack/minifiers that the call to `__jsr.r`
|
40
|
+
# has no side-effects (e.g. modifying global variables) and is safe to remove when unused.
|
41
|
+
# https://webpack.js.org/guides/tree-shaking/#clarifying-tree-shaking-and-sideeffects
|
42
|
+
pure_comment = @configuration.esm? ? '/*#__PURE__*/ ' : ''
|
43
|
+
"#{pure_comment}__jsr.r(#{arguments(absolute).map{|a| json(a)}.join(', ')})"
|
44
|
+
end
|
38
45
|
end
|
39
46
|
|
40
47
|
def definition_body
|
data/lib/js_routes/version.rb
CHANGED
data/lib/routes.d.ts
CHANGED
@@ -32,8 +32,8 @@ declare type RequiredParameters<T extends number> = T extends 1 ? [RequiredRoute
|
|
32
32
|
RequiredRouteParameter,
|
33
33
|
RequiredRouteParameter
|
34
34
|
] : RequiredRouteParameter[];
|
35
|
-
declare type RouteHelperOptions
|
36
|
-
declare type RouteHelper<T extends number = number
|
35
|
+
declare type RouteHelperOptions = RouteOptions & Record<string, OptionalRouteParameter>;
|
36
|
+
declare type RouteHelper<T extends number = number> = ((...args: [...RequiredParameters<T>, RouteHelperOptions]) => string) & RouteHelperExtras;
|
37
37
|
declare type RouteHelpers = Record<string, RouteHelper>;
|
38
38
|
declare type Configuration = {
|
39
39
|
prefix: string;
|
data/lib/routes.js
CHANGED
@@ -19,6 +19,14 @@ RubyVariables.WRAPPER(
|
|
19
19
|
NodeTypes[NodeTypes["DOT"] = 8] = "DOT";
|
20
20
|
})(NodeTypes || (NodeTypes = {}));
|
21
21
|
const isBrowser = typeof window !== "undefined";
|
22
|
+
const UnescapedSpecials = "-._~!$&'()*+,;=:@"
|
23
|
+
.split("")
|
24
|
+
.map((s) => s.charCodeAt(0));
|
25
|
+
const UnescapedRanges = [
|
26
|
+
["a", "z"],
|
27
|
+
["A", "Z"],
|
28
|
+
["0", "9"],
|
29
|
+
].map((range) => range.map((s) => s.charCodeAt(0)));
|
22
30
|
const ModuleReferences = {
|
23
31
|
CJS: {
|
24
32
|
define(routes) {
|
@@ -97,7 +105,6 @@ RubyVariables.WRAPPER(
|
|
97
105
|
this.name = ParametersMissing.name;
|
98
106
|
}
|
99
107
|
}
|
100
|
-
const UriEncoderSegmentRegex = /[^a-zA-Z0-9\-._~!$&'()*+,;=:@]/g;
|
101
108
|
const ReservedOptions = [
|
102
109
|
"anchor",
|
103
110
|
"trailing_slash",
|
@@ -349,7 +356,22 @@ RubyVariables.WRAPPER(
|
|
349
356
|
}
|
350
357
|
}
|
351
358
|
encode_segment(segment) {
|
352
|
-
|
359
|
+
if (segment.match(/^[a-zA-Z0-9-]$/)) {
|
360
|
+
// Performance optimization for 99% of cases
|
361
|
+
return segment;
|
362
|
+
}
|
363
|
+
return (segment.match(/./gu) || [])
|
364
|
+
.map((ch) => {
|
365
|
+
const code = ch.charCodeAt(0);
|
366
|
+
if (UnescapedRanges.find((range) => code >= range[0] && code <= range[1]) ||
|
367
|
+
UnescapedSpecials.includes(code)) {
|
368
|
+
return ch;
|
369
|
+
}
|
370
|
+
else {
|
371
|
+
return encodeURIComponent(ch);
|
372
|
+
}
|
373
|
+
})
|
374
|
+
.join("");
|
353
375
|
}
|
354
376
|
is_optional_node(node) {
|
355
377
|
return [NodeTypes.STAR, NodeTypes.SYMBOL, NodeTypes.CAT].includes(node);
|
@@ -358,7 +380,7 @@ RubyVariables.WRAPPER(
|
|
358
380
|
let key;
|
359
381
|
switch (route[0]) {
|
360
382
|
case NodeTypes.GROUP:
|
361
|
-
return
|
383
|
+
return `(${this.build_path_spec(route[1])})`;
|
362
384
|
case NodeTypes.CAT:
|
363
385
|
return (this.build_path_spec(route[1]) + this.build_path_spec(route[2]));
|
364
386
|
case NodeTypes.STAR:
|
data/lib/routes.ts
CHANGED
@@ -40,11 +40,10 @@ type RequiredParameters<T extends number> = T extends 1
|
|
40
40
|
]
|
41
41
|
: RequiredRouteParameter[];
|
42
42
|
|
43
|
-
type RouteHelperOptions
|
44
|
-
Optional<Record<T, OptionalRouteParameter>>;
|
43
|
+
type RouteHelperOptions = RouteOptions & Record<string, OptionalRouteParameter>;
|
45
44
|
|
46
|
-
type RouteHelper<T extends number = number
|
47
|
-
...args: [...RequiredParameters<T>, RouteHelperOptions
|
45
|
+
type RouteHelper<T extends number = number> = ((
|
46
|
+
...args: [...RequiredParameters<T>, RouteHelperOptions]
|
48
47
|
) => string) &
|
49
48
|
RouteHelperExtras;
|
50
49
|
|
@@ -101,7 +100,7 @@ declare const module: { exports: any } | undefined;
|
|
101
100
|
RubyVariables.WRAPPER(
|
102
101
|
// eslint-disable-next-line
|
103
102
|
(): RouterExposedMethods => {
|
104
|
-
const hasProp = (value: unknown, key: string) =>
|
103
|
+
const hasProp = (value: unknown, key: string): boolean =>
|
105
104
|
Object.prototype.hasOwnProperty.call(value, key);
|
106
105
|
enum NodeTypes {
|
107
106
|
GROUP = 1,
|
@@ -136,6 +135,16 @@ RubyVariables.WRAPPER(
|
|
136
135
|
define: (routes: RouterExposedMethods) => void;
|
137
136
|
isSupported: () => boolean;
|
138
137
|
};
|
138
|
+
|
139
|
+
const UnescapedSpecials = "-._~!$&'()*+,;=:@"
|
140
|
+
.split("")
|
141
|
+
.map((s) => s.charCodeAt(0));
|
142
|
+
const UnescapedRanges = [
|
143
|
+
["a", "z"],
|
144
|
+
["A", "Z"],
|
145
|
+
["0", "9"],
|
146
|
+
].map((range) => range.map((s) => s.charCodeAt(0)));
|
147
|
+
|
139
148
|
const ModuleReferences: Record<ModuleType, ModuleDefinition> = {
|
140
149
|
CJS: {
|
141
150
|
define(routes) {
|
@@ -216,8 +225,6 @@ RubyVariables.WRAPPER(
|
|
216
225
|
}
|
217
226
|
}
|
218
227
|
|
219
|
-
const UriEncoderSegmentRegex = /[^a-zA-Z0-9\-._~!$&'()*+,;=:@]/g;
|
220
|
-
|
221
228
|
const ReservedOptions = [
|
222
229
|
"anchor",
|
223
230
|
"trailing_slash",
|
@@ -526,9 +533,25 @@ RubyVariables.WRAPPER(
|
|
526
533
|
}
|
527
534
|
|
528
535
|
encode_segment(segment: string): string {
|
529
|
-
|
530
|
-
|
531
|
-
|
536
|
+
if (segment.match(/^[a-zA-Z0-9-]$/)) {
|
537
|
+
// Performance optimization for 99% of cases
|
538
|
+
return segment;
|
539
|
+
}
|
540
|
+
return (segment.match(/./gu) || [])
|
541
|
+
.map((ch) => {
|
542
|
+
const code = ch.charCodeAt(0);
|
543
|
+
if (
|
544
|
+
UnescapedRanges.find(
|
545
|
+
(range) => code >= range[0] && code <= range[1]
|
546
|
+
) ||
|
547
|
+
UnescapedSpecials.includes(code)
|
548
|
+
) {
|
549
|
+
return ch;
|
550
|
+
} else {
|
551
|
+
return encodeURIComponent(ch);
|
552
|
+
}
|
553
|
+
})
|
554
|
+
.join("");
|
532
555
|
}
|
533
556
|
|
534
557
|
is_optional_node(node: NodeTypes): boolean {
|
@@ -539,7 +562,7 @@ RubyVariables.WRAPPER(
|
|
539
562
|
let key: string;
|
540
563
|
switch (route[0]) {
|
541
564
|
case NodeTypes.GROUP:
|
542
|
-
return
|
565
|
+
return `(${this.build_path_spec(route[1])})`;
|
543
566
|
case NodeTypes.CAT:
|
544
567
|
return (
|
545
568
|
this.build_path_spec(route[1]) + this.build_path_spec(route[2])
|
@@ -32,8 +32,8 @@ declare type RequiredParameters<T extends number> = T extends 1 ? [RequiredRoute
|
|
32
32
|
RequiredRouteParameter,
|
33
33
|
RequiredRouteParameter
|
34
34
|
] : RequiredRouteParameter[];
|
35
|
-
declare type RouteHelperOptions
|
36
|
-
declare type RouteHelper<T extends number = number
|
35
|
+
declare type RouteHelperOptions = RouteOptions & Record<string, OptionalRouteParameter>;
|
36
|
+
declare type RouteHelper<T extends number = number> = ((...args: [...RequiredParameters<T>, RouteHelperOptions]) => string) & RouteHelperExtras;
|
37
37
|
declare type RouteHelpers = Record<string, RouteHelper>;
|
38
38
|
declare type Configuration = {
|
39
39
|
prefix: string;
|
@@ -105,7 +105,7 @@ DOC
|
|
105
105
|
describe "compiled javascript asset" do
|
106
106
|
subject { ERB.new(File.read("app/assets/javascripts/js-routes.js.erb")).result(binding) }
|
107
107
|
it "should have js routes code" do
|
108
|
-
is_expected.to include("export const inbox_message_path = __jsr.r(")
|
108
|
+
is_expected.to include("export const inbox_message_path = /*#__PURE__*/ __jsr.r(")
|
109
109
|
end
|
110
110
|
end
|
111
111
|
end
|
@@ -24,7 +24,7 @@ describe JsRoutes, "compatibility with ESM" do
|
|
24
24
|
* @param {object | undefined} options
|
25
25
|
* @returns {string} route path
|
26
26
|
*/
|
27
|
-
export const inboxes_path = __jsr.r
|
27
|
+
export const inboxes_path = /*#__PURE__*/ __jsr.r(
|
28
28
|
DOC
|
29
29
|
end
|
30
30
|
|
@@ -39,7 +39,7 @@ DOC
|
|
39
39
|
describe "compiled javascript asset" do
|
40
40
|
subject { ERB.new(File.read("app/assets/javascripts/js-routes.js.erb")).result(binding) }
|
41
41
|
it "should have js routes code" do
|
42
|
-
is_expected.to include("export const inbox_message_path = __jsr.r(")
|
42
|
+
is_expected.to include("export const inbox_message_path = /*#__PURE__*/ __jsr.r(")
|
43
43
|
end
|
44
44
|
end
|
45
45
|
end
|
@@ -2,11 +2,8 @@ require "spec_helper"
|
|
2
2
|
|
3
3
|
describe JsRoutes, "compatibility with Rails" do
|
4
4
|
|
5
|
-
let(:generated_js) do
|
6
|
-
JsRoutes.generate(module_type: nil, namespace: 'Routes')
|
7
|
-
end
|
8
5
|
before(:each) do
|
9
|
-
|
6
|
+
evallib(module_type: nil, namespace: 'Routes')
|
10
7
|
end
|
11
8
|
|
12
9
|
it "should generate collection routing" do
|
@@ -87,14 +84,22 @@ describe JsRoutes, "compatibility with Rails" do
|
|
87
84
|
expectjs("Routes.budgie_descendents_path(1)").to eq(test_routes.budgie_descendents_path(1))
|
88
85
|
end
|
89
86
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
87
|
+
describe "url parameters encoding" do
|
88
|
+
|
89
|
+
it "should support route with parameters containing symbols that need URI-encoding", :aggregate_failures do
|
90
|
+
expectjs("Routes.inbox_path('#hello')").to eq(test_routes.inbox_path('#hello'))
|
91
|
+
expectjs("Routes.inbox_path('some param')").to eq(test_routes.inbox_path('some param'))
|
92
|
+
expectjs("Routes.inbox_path('some param with more & more encode symbols')").to eq(test_routes.inbox_path('some param with more & more encode symbols'))
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should support route with parameters containing symbols not need URI-encoding", :aggregate_failures do
|
96
|
+
expectjs("Routes.inbox_path(':some_id')").to eq(test_routes.inbox_path(':some_id'))
|
97
|
+
expectjs("Routes.inbox_path('.+')").to eq(test_routes.inbox_path('.+'))
|
98
|
+
end
|
99
|
+
|
100
|
+
it "supports emoji characters", :aggregate_failures do
|
101
|
+
expectjs("Routes.inbox_path('💗')").to eq(test_routes.inbox_path('💗'))
|
102
|
+
end
|
98
103
|
end
|
99
104
|
|
100
105
|
describe "when route has defaults" do
|
@@ -3,11 +3,8 @@ require "spec_helper"
|
|
3
3
|
|
4
4
|
describe JsRoutes, "compatibility with Rails" do
|
5
5
|
|
6
|
-
let(:generated_js) do
|
7
|
-
JsRoutes.generate(module_type: nil, namespace: 'Routes')
|
8
|
-
end
|
9
6
|
before(:each) do
|
10
|
-
|
7
|
+
evallib(module_type: nil, namespace: 'Routes')
|
11
8
|
end
|
12
9
|
|
13
10
|
context "when specs" do
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: js-routes
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.2.
|
4
|
+
version: 2.2.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bogdan Gusiev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-11-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: railties
|
@@ -197,7 +197,7 @@ files:
|
|
197
197
|
- spec/js_routes/options_spec.rb
|
198
198
|
- spec/js_routes/rails_routes_compatibility_spec.rb
|
199
199
|
- spec/js_routes/route_specification_spec.rb
|
200
|
-
- spec/js_routes/
|
200
|
+
- spec/js_routes/zzz_sprockets_spec.rb
|
201
201
|
- spec/spec_helper.rb
|
202
202
|
- spec/support/routes.rb
|
203
203
|
- spec/tsconfig.json
|
@@ -222,7 +222,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
222
222
|
- !ruby/object:Gem::Version
|
223
223
|
version: '0'
|
224
224
|
requirements: []
|
225
|
-
rubygems_version: 3.
|
225
|
+
rubygems_version: 3.4.15
|
226
226
|
signing_key:
|
227
227
|
specification_version: 4
|
228
228
|
summary: Brings Rails named routes to javascript
|