js-routes 2.2.6 → 2.2.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 55d34a6a511e250d1fc59aa2e868a3148734bc6fafc6f156ff86eea1f0d6f81e
4
- data.tar.gz: 94143965ae4a2a14db535dd484521c3348c18eee2ca6f61ee72186e908cdc786
3
+ metadata.gz: 6dfe30c594c24cf044d0f19342e17cd6b23857da7ec68a0bf68d8f8be9f33def
4
+ data.tar.gz: 3aefa8fc04221f07996ada7c57f35b22d2ae519575a6df0e20c6d860fe6203ed
5
5
  SHA512:
6
- metadata.gz: d727f254cec5e5269e92ef1180309aacb3362fc4aa8065c2fb604237d9096918c1e0ab534b7b443df656fb5218962e949522ce3338b4be9d99e17d0268f6efbb
7
- data.tar.gz: 9ce12dc8aefab9a646aadc2318e541f6b422fc9340856e69488889374727c7de7f7e70fa8fbe510263b9d7f5edf2097657ae62fac0a90d0206b9b2be5481b48a
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 `assets:precompile` in production
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
- * [Sprockets](#sprockets) legacy
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**: this setup doesn't support IDE autocompletion with [Typescript](https://www.typescriptlang.org/docs/handbook/declaration-files/templates/module-d-ts.html)
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
- ### Explicit Import + ESM Tree shaking
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
- Make sure `module_type` is set to `ESM` (the default) and JS files import only required routes into the file like:
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
- Such import structure allows for moddern JS bundlers like [Webpack](https://webpack.js.org/) to only include explicitly imported routes into JS bundle file.
432
- See [Tree Shaking](https://webpack.js.org/guides/tree-shaking/) for more information.
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.exists?(Rails.root.join(path))
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", "app/javascript/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
@@ -33,8 +33,15 @@ module JsRoutes
33
33
  end
34
34
 
35
35
  def body(absolute)
36
- @configuration.dts? ?
37
- definition_body : "__jsr.r(#{arguments(absolute).map{|a| json(a)}.join(', ')})"
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
@@ -1,3 +1,3 @@
1
1
  module JsRoutes
2
- VERSION = "2.2.6"
2
+ VERSION = "2.2.8"
3
3
  end
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<T extends string> = RouteOptions & Optional<Record<T, OptionalRouteParameter>>;
36
- declare type RouteHelper<T extends number = number, U extends string = string> = ((...args: [...RequiredParameters<T>, RouteHelperOptions<U>]) => string) & RouteHelperExtras;
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
- return segment.replace(UriEncoderSegmentRegex, (str) => encodeURIComponent(str));
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 "(" + this.build_path_spec(route[1]) + ")";
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<T extends string> = RouteOptions &
44
- Optional<Record<T, OptionalRouteParameter>>;
43
+ type RouteHelperOptions = RouteOptions & Record<string, OptionalRouteParameter>;
45
44
 
46
- type RouteHelper<T extends number = number, U extends string = string> = ((
47
- ...args: [...RequiredParameters<T>, RouteHelperOptions<U>]
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
- return segment.replace(UriEncoderSegmentRegex, (str) =>
530
- encodeURIComponent(str)
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 "(" + this.build_path_spec(route[1]) + ")";
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])
@@ -3,7 +3,7 @@ require "spec_helper"
3
3
  describe JsRoutes, "#serialize" do
4
4
 
5
5
  before(:each) do
6
- evaljs(JsRoutes.generate(module_type: nil, namespace: 'Routes'))
6
+ evallib(module_type: nil, namespace: 'Routes')
7
7
  end
8
8
 
9
9
  it "should provide this method" do
@@ -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<T extends string> = RouteOptions & Optional<Record<T, OptionalRouteParameter>>;
36
- declare type RouteHelper<T extends number = number, U extends string = string> = ((...args: [...RequiredParameters<T>, RouteHelperOptions<U>]) => string) & RouteHelperExtras;
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
@@ -79,7 +79,7 @@ DOC
79
79
  end
80
80
 
81
81
  it "should not generate file before initialization" do
82
- expect(File.exists?(name)).to be_falsey
82
+ expect(File.exist?(name)).to be_falsey
83
83
  end
84
84
  end
85
85
  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
- evaljs(generated_js)
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
- it "should support route with parameters containing symbols that need URI-encoding", :aggregate_failures do
91
- expectjs("Routes.inbox_path('#hello')").to eq(test_routes.inbox_path('#hello'))
92
- expectjs("Routes.inbox_path('some param')").to eq(test_routes.inbox_path('some param'))
93
- expectjs("Routes.inbox_path('some param with more & more encode symbols')").to eq(test_routes.inbox_path('some param with more & more encode symbols'))
94
- end
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('.+'))
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
- evaljs(generated_js)
7
+ evallib(module_type: nil, namespace: 'Routes')
11
8
  end
12
9
 
13
10
  context "when specs" do
@@ -47,7 +47,7 @@ describe "after Rails initialization", :slow do
47
47
  end
48
48
 
49
49
  it "should generate routes file" do
50
- expect(File.exists?(NAME)).to be_truthy
50
+ expect(File.exist?(NAME)).to be_truthy
51
51
  end
52
52
 
53
53
  it "should not rewrite routes file if nothing changed" do
data/spec/spec_helper.rb CHANGED
@@ -54,6 +54,10 @@ rescue MiniRacer::RuntimeError => e
54
54
  raise e
55
55
  end
56
56
 
57
+ def evallib(**options)
58
+ evaljs(JsRoutes.generate(**options), filename: 'lib/routes.js')
59
+ end
60
+
57
61
  def test_routes
58
62
  ::App.routes.url_helpers
59
63
  end
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.6
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-07-05 00:00:00.000000000 Z
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/zzz_last_post_rails_init_spec.rb
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.3.7
225
+ rubygems_version: 3.4.15
226
226
  signing_key:
227
227
  specification_version: 4
228
228
  summary: Brings Rails named routes to javascript