js-routes 2.1.1 → 2.2.2

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: 64ddd33bda1dffe333670e22c7abe2e578d960709c5ddb42a1559fea57495288
4
- data.tar.gz: af6b923046c1d3e0e838be4502355d9a2c8646af03c90b084e0bf04ec41cbce5
3
+ metadata.gz: 52db2ffdef9bea714377b9c00e152177b17e40de0fd8ef57ba09a84fee2f206e
4
+ data.tar.gz: 8f899234c70859fee6a7c0486825c12ce501a01052e2b5d9a038f08544ac6df9
5
5
  SHA512:
6
- metadata.gz: 15d336c5dae45e63e7ca1700a58ca9d9146c867a59034f9a1346fbdb03153162055fee77d018a8b206bf5ead8a813730ca8618332d1fd5b1a42e0336db8c4ece
7
- data.tar.gz: fe9f55283a5b5d71a25b117f35cf9c2562128a4ca6d1efe4022a3f0108fb93779dd1ec13183b019cbf895df30ea52657c4c0f8c9df780ec7dcd486ad6eb71932
6
+ metadata.gz: e697c45b1908906c40cdc4591f5914dd415226de79a42a8e948f6095e0f09eb5edb5bfb0c5fd61953fde7121d9a025b3472b644ddb17ca2c13cb075c97311000
7
+ data.tar.gz: 7f48edacee6659d4e4b5be0d71b5127f26e473119a8bf13a73c55a7b3c295dbee7fc34b9d3f515725c5871baad319ceb0bb32ed176900845b48a84c786ba3f9c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  ## master
2
2
 
3
+ ## v2.2.2.
4
+
5
+ * Fix custom file path [#295](https://github.com/railsware/js-routes/issues/295)
6
+
7
+ ## v2.2.1
8
+
9
+ * Improve generator to update route files on `assets:precompile` and add them to `.gitignore by default` [#288](https://github.com/railsware/js-routes/issues/288#issuecomment-1012182815)
10
+
11
+ ## v2.2.0
12
+
13
+ * Use Rack Middleware to automatically update routes file in development [#288](https://github.com/railsware/js-routes/issues/288)
14
+ * This setup is now a default recommended due to lack of any downside comparing to [ERB Loader](./Readme.md#webpacker) and [Manual Setup](./Readme.md#advanced-setup)
15
+
16
+ ## v2.1.3
17
+
18
+ * Fix `default_url_options` bug. [#290](https://github.com/railsware/js-routes/issues/290)
19
+
20
+ ## v2.1.2
21
+
22
+ * Improve browser window object detection. [#287](https://github.com/railsware/js-routes/issues/287)
23
+
3
24
  ## v2.1.1
4
25
 
5
26
  * Added webpacker generator `./bin/rails generate js_routes:webpacker`
data/Readme.md CHANGED
@@ -16,13 +16,16 @@ gem "js-routes"
16
16
 
17
17
  ## Setup
18
18
 
19
- There are 3 possible ways to setup JsRoutes:
19
+ There are several possible ways to setup JsRoutes:
20
20
 
21
21
  * [Quick and easy](#quick-start)
22
- * Requires rake task to be run each time route file is updated
23
- * [Webpacker](#webpacker) automatic updates
22
+ * Uses Rack Middleware to automatically update routes locally
23
+ * Works great for a simple Rails application
24
+ * [Webpacker ERB Loader](#webpacker)
24
25
  * Requires ESM module system (the default)
25
26
  * Doesn't support typescript definitions
27
+ * [Advanced Setup](#advanced-setup)
28
+ * Allows very custom setups
26
29
  * [Sprockets](#sprockets) legacy
27
30
  * Deprecated and not recommended for modern apps
28
31
 
@@ -30,36 +33,54 @@ There are 3 possible ways to setup JsRoutes:
30
33
 
31
34
  ### Quick Start
32
35
 
33
- Run:
36
+ Setup [Rack Middleware](https://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack)
37
+ to automatically generate and maintain `routes.js` file and corresponding
38
+ [Typescript definitions](https://www.typescriptlang.org/docs/handbook/declaration-files/templates/module-d-ts.html) `routes.d.ts`:
39
+
40
+ #### Use a Generator
41
+
42
+ Run a command:
34
43
 
35
44
  ``` sh
36
- rake js:routes
37
- # OR for typescript support
38
- rake js:routes:typescript
45
+ rails generate js_routes:middleware
39
46
  ```
40
47
 
41
- **IMPORTANT**: that this setup requires the rake task to be run each time routes file is updated.
48
+ #### Setup Manually
42
49
 
43
- Individual routes can be imported using:
50
+ Add the following to `config/environments/development.rb`:
44
51
 
45
- ``` javascript
46
- import {edit_post_path, posts_path} from 'routes';
47
- console.log(posts_path({format: 'json'})) // => "/posts.json"
48
- console.log(edit_post_path(1)) // => "/posts/1/edit"
52
+ ``` ruby
53
+ config.middleware.use(JsRoutes::Middleware)
49
54
  ```
50
55
 
51
- Make routes available globally in `app/javascript/packs/application.js`:
56
+ Use it in `app/javascript/packs/application.js`:
52
57
 
53
58
  ``` javascript
54
- import * as Routes from 'routes';
55
- window.Routes = Routes;
59
+ import * as Routes from '../routes';
60
+ // window.Routes = Routes;
61
+ alert(Routes.post_path(1))
62
+ ```
63
+
64
+ Upgrade `rake assets:precompile` to update js-routes files:
65
+
66
+ ``` ruby
67
+ namespace :assets do
68
+ task :precompile => "js:routes:typescript"
69
+ end
70
+ ```
71
+
72
+ Add js-routes files to `.gitignore`:
73
+
74
+ ```
75
+ /app/javascript/routes.js
76
+ /app/javascript/routes.d.ts
56
77
  ```
57
78
 
58
79
  <div id='webpacker'></div>
59
80
 
60
- ### Webpacker + automatic updates - Typescript
81
+ ### Webpacker ERB loader
61
82
 
62
- **IMPORTANT**: this setup doesn't support IDE autocompletion with [Typescript](#definitions)
83
+ **IMPORTANT**: this setup doesn't support IDE autocompletion with [Typescript](https://www.typescriptlang.org/docs/handbook/declaration-files/templates/module-d-ts.html)
63
84
 
64
85
 
65
86
  #### Use a Generator
@@ -118,9 +139,54 @@ import * as Routes from 'routes.js.erb';
118
139
  window.Routes = Routes;
119
140
  ```
120
141
 
142
+ <div id='advanced-setup'></div>
143
+
144
+ ### Advanced Setup
145
+
146
+ **IMPORTANT**: that this setup requires the JS routes file to be updates manually
147
+
148
+ Routes file can be generated with a `rake` task:
149
+
150
+ ``` sh
151
+ rake js:routes
152
+ # OR for typescript support
153
+ rake js:routes:typescript
154
+ ```
155
+
156
+ In case you need multiple route files for different parts of your application, you have to create the files manually.
157
+ If your application has an `admin` and an `application` namespace for example:
158
+
159
+ **IMPORTANT**: Requires [Webpacker ERB Loader](#webpacker) setup.
160
+
161
+ ``` erb
162
+ // app/javascript/admin/routes.js.erb
163
+ <%= JsRoutes.generate(include: /admin/) %>
164
+ ```
165
+
166
+ ``` erb
167
+ // app/javascript/customer/routes.js.erb
168
+ <%= JsRoutes.generate(exclude: /admin/) %>
169
+ ```
170
+
171
+ You can manipulate the generated helper manually by injecting ruby into javascript:
172
+
173
+ ``` erb
174
+ export const routes = <%= JsRoutes.generate(module_type: nil, namespace: nil) %>
175
+ ```
176
+
177
+ If you want to generate the routes files outside of the asset pipeline, you can use `JsRoutes.generate!`:
178
+
179
+ ``` ruby
180
+ path = Rails.root.join("app/javascript")
181
+
182
+ JsRoutes.generate!("#{path}/app_routes.js", exclude: [/^admin_/, /^api_/])
183
+ JsRoutes.generate!("#{path}/adm_routes.js", include: /^admin_/)
184
+ JsRoutes.generate!("#{path}/api_routes.js", include: /^api_/, default_url_options: {format: "json"})
185
+ ```
186
+
121
187
  <div id='definitions'></div>
122
188
 
123
- ### Typescript Definitions
189
+ #### Typescript Definitions
124
190
 
125
191
  JsRoutes has typescript support out of the box.
126
192
 
@@ -230,6 +296,8 @@ Options to configure JavaScript file generator. These options are only available
230
296
  * `file` - a file location where generated routes are stored
231
297
  * Default: `app/javascript/routes.js` if setup with Webpacker, otherwise `app/assets/javascripts/routes.js` if setup with Sprockets.
232
298
 
299
+ <div id="formatter-options"></div>
300
+
233
301
  #### Formatter Options
234
302
 
235
303
  Options to configure routes formatting. These options are available both in Ruby and JavaScript context.
@@ -248,36 +316,6 @@ Options to configure routes formatting. These options are available both in Ruby
248
316
  * This option exists because JS doesn't provide a difference between an object and a hash
249
317
  * Default: `_options`
250
318
 
251
- ## Advanced Setup
252
-
253
- In case you need multiple route files for different parts of your application, you have to create the files manually.
254
- If your application has an `admin` and an `application` namespace for example:
255
-
256
- ``` erb
257
- // app/javascript/admin/routes.js.erb
258
- <%= JsRoutes.generate(include: /admin/) %>
259
- ```
260
-
261
- ``` erb
262
- // app/javascript/customer/routes.js.erb
263
- <%= JsRoutes.generate(exclude: /admin/) %>
264
- ```
265
-
266
- You can manipulate the generated helper manually by injecting ruby into javascript:
267
-
268
- ``` erb
269
- export const routes = <%= JsRoutes.generate(module_type: nil, namespace: nil) %>
270
- ```
271
-
272
- If you want to generate the routes files outside of the asset pipeline, you can use `JsRoutes.generate!`:
273
-
274
- ``` ruby
275
- path = Rails.root.join("app/javascript")
276
-
277
- JsRoutes.generate!("#{path}/app_routes.js", exclude: [/^admin_/, /^api_/])
278
- JsRoutes.generate!("#{path}/adm_routes.js", include: /^admin_/)
279
- JsRoutes.generate!("#{path}/api_routes.js", include: /^api_/, default_url_options: {format: "json"})
280
- ```
281
319
 
282
320
  ## Usage
283
321
 
@@ -0,0 +1,111 @@
1
+ require "pathname"
2
+
3
+ module JsRoutes
4
+ class Configuration
5
+ DEFAULTS = {
6
+ namespace: nil,
7
+ exclude: [],
8
+ include: //,
9
+ file: nil,
10
+ prefix: -> { Rails.application.config.relative_url_root || "" },
11
+ url_links: false,
12
+ camel_case: false,
13
+ default_url_options: {},
14
+ compact: false,
15
+ serializer: nil,
16
+ special_options_key: "_options",
17
+ application: -> { Rails.application },
18
+ module_type: 'ESM',
19
+ documentation: true,
20
+ } #:nodoc:
21
+
22
+ attr_accessor(*DEFAULTS.keys)
23
+
24
+ def initialize(attributes = nil)
25
+ assign(DEFAULTS)
26
+ return unless attributes
27
+ assign(attributes)
28
+ end
29
+
30
+ def assign(attributes = nil, &block)
31
+ if !attributes && !block
32
+ raise "Provide attributes or block"
33
+ end
34
+ tap(&block) if block
35
+ if attributes
36
+ attributes.each do |attribute, value|
37
+ value = value.call if value.is_a?(Proc)
38
+ send(:"#{attribute}=", value)
39
+ end
40
+ end
41
+ normalize_and_verify
42
+ self
43
+ end
44
+
45
+ def [](attribute)
46
+ send(attribute)
47
+ end
48
+
49
+ def merge(attributes)
50
+ clone.assign(attributes)
51
+ end
52
+
53
+ def to_hash
54
+ Hash[*members.zip(values).flatten(1)].symbolize_keys
55
+ end
56
+
57
+ def esm?
58
+ module_type === 'ESM'
59
+ end
60
+
61
+ def dts?
62
+ self.module_type === 'DTS'
63
+ end
64
+
65
+ def modern?
66
+ esm? || dts?
67
+ end
68
+
69
+ def require_esm
70
+ raise "ESM module type is required" unless modern?
71
+ end
72
+
73
+ def source_file
74
+ File.dirname(__FILE__) + "/../" + default_file_name
75
+ end
76
+
77
+ def output_file
78
+ webpacker_dir = pathname('app', 'javascript')
79
+ sprockets_dir = pathname('app','assets','javascripts')
80
+ file_name = file || default_file_name
81
+ sprockets_file = sprockets_dir.join(file_name)
82
+ webpacker_file = webpacker_dir.join(file_name)
83
+ !Dir.exist?(webpacker_dir) && defined?(::Sprockets) ? sprockets_file : webpacker_file
84
+ end
85
+
86
+ protected
87
+
88
+ def normalize_and_verify
89
+ normalize
90
+ verify
91
+ end
92
+
93
+ def pathname(*parts)
94
+ Pathname.new(File.join(*parts))
95
+ end
96
+
97
+ def default_file_name
98
+ dts? ? "routes.d.ts" : "routes.js"
99
+ end
100
+
101
+ def normalize
102
+ self.module_type = module_type&.upcase || 'NIL'
103
+ end
104
+
105
+ def verify
106
+ if module_type != 'NIL' && namespace
107
+ raise "JsRoutes namespace option can only be used if module_type is nil"
108
+ end
109
+ end
110
+ end
111
+ end
@@ -1,4 +1,4 @@
1
- class JsRoutes
1
+ module JsRoutes
2
2
  class SprocketsExtension
3
3
  def initialize(filename, &block)
4
4
  @filename = filename
@@ -0,0 +1,58 @@
1
+ require "rails/generators"
2
+
3
+ class JsRoutes::Generators::Middleware < Rails::Generators::Base
4
+
5
+ source_root File.expand_path(__FILE__ + "/../../../templates")
6
+
7
+ def create_middleware
8
+ copy_file "initializer.rb", "config/initializers/js_routes.rb"
9
+ inject_into_file "app/javascript/packs/application.js", pack_content
10
+ inject_into_file "config/environments/development.rb", middleware_content, before: /^end\n\z/
11
+ inject_into_file "Rakefile", rakefile_content
12
+ inject_into_file ".gitignore", gitignore_content
13
+ JsRoutes.generate!
14
+ JsRoutes.definitions!
15
+ end
16
+
17
+ protected
18
+
19
+ def pack_content
20
+ <<-JS
21
+ import * as Routes from '../routes';
22
+ window.Routes = Routes;
23
+ JS
24
+ end
25
+
26
+ def middleware_content
27
+ <<-RB
28
+
29
+ # Automatically update js-routes file
30
+ # when routes.rb is changed
31
+ config.middleware.use(JsRoutes::Middleware)
32
+ RB
33
+ end
34
+
35
+ def rakefile_content
36
+ <<-RB
37
+
38
+ # Update js-routes file before assets precompile
39
+ namespace :assets do
40
+ task :precompile => "js:routes:typescript"
41
+ end
42
+ RB
43
+ end
44
+
45
+ def gitignore_content
46
+ banner = <<-TXT
47
+
48
+ # Ignore automatically generated js-routes files.
49
+ TXT
50
+
51
+ banner + [
52
+ {},
53
+ {module_type: 'DTS'}
54
+ ].map do |config|
55
+ File.join('/', JsRoutes.new(config).configuration.output_file) + "\n"
56
+ end.join
57
+ end
58
+ end
@@ -1,6 +1,6 @@
1
1
  require "rails/generators"
2
2
 
3
- class JsRoutes::Webpacker < Rails::Generators::Base
3
+ class JsRoutes::Generators::Webpacker < Rails::Generators::Base
4
4
 
5
5
  source_root File.expand_path(__FILE__ + "/../../../templates")
6
6
 
@@ -0,0 +1,154 @@
1
+ require "js_routes/configuration"
2
+ require "js_routes/route"
3
+
4
+ module JsRoutes
5
+ class Instance
6
+
7
+ attr_reader :configuration
8
+ #
9
+ # Implementation
10
+ #
11
+
12
+ def initialize(options = {})
13
+ @configuration = JsRoutes.configuration.merge(options)
14
+ end
15
+
16
+ def generate
17
+ # Ensure routes are loaded. If they're not, load them.
18
+ if named_routes.empty? && application.respond_to?(:reload_routes!)
19
+ application.reload_routes!
20
+ end
21
+ content = File.read(@configuration.source_file)
22
+
23
+ if !@configuration.dts?
24
+ content = js_variables.inject(content) do |js, (key, value)|
25
+ js.gsub!("RubyVariables.#{key}", value.to_s) ||
26
+ raise("Missing key #{key} in JS template")
27
+ end
28
+ end
29
+ content + routes_export + prevent_types_export
30
+ end
31
+
32
+ def generate!
33
+ # Some libraries like Devise did not load their routes yet
34
+ # so we will wait until initialization process finishes
35
+ # https://github.com/railsware/js-routes/issues/7
36
+ Rails.configuration.after_initialize do
37
+ file_path = Rails.root.join(@configuration.output_file)
38
+ source_code = generate
39
+
40
+ # We don't need to rewrite file if it already exist and have same content.
41
+ # It helps asset pipeline or webpack understand that file wasn't changed.
42
+ next if File.exist?(file_path) && File.read(file_path) == source_code
43
+
44
+ File.open(file_path, 'w') do |f|
45
+ f.write source_code
46
+ end
47
+ end
48
+ end
49
+
50
+ protected
51
+
52
+ def js_variables
53
+ {
54
+ 'GEM_VERSION' => JsRoutes::VERSION,
55
+ 'ROUTES_OBJECT' => routes_object,
56
+ 'RAILS_VERSION' => ActionPack.version,
57
+ 'DEPRECATED_GLOBBING_BEHAVIOR' => ActionPack::VERSION::MAJOR == 4 && ActionPack::VERSION::MINOR == 0,
58
+
59
+ 'APP_CLASS' => application.class.to_s,
60
+ 'NAMESPACE' => json(@configuration.namespace),
61
+ 'DEFAULT_URL_OPTIONS' => json(@configuration.default_url_options),
62
+ 'PREFIX' => json(@configuration.prefix),
63
+ 'SPECIAL_OPTIONS_KEY' => json(@configuration.special_options_key),
64
+ 'SERIALIZER' => @configuration.serializer || json(nil),
65
+ 'MODULE_TYPE' => json(@configuration.module_type),
66
+ 'WRAPPER' => @configuration.esm? ? 'const __jsr = ' : '',
67
+ }
68
+ end
69
+
70
+ def application
71
+ @configuration.application
72
+ end
73
+
74
+ def json(string)
75
+ JsRoutes.json(string)
76
+ end
77
+
78
+ def named_routes
79
+ application.routes.named_routes.to_a
80
+ end
81
+
82
+ def routes_object
83
+ return json({}) if @configuration.modern?
84
+ properties = routes_list.map do |comment, name, body|
85
+ "#{comment}#{name}: #{body}".indent(2)
86
+ end
87
+ "{\n" + properties.join(",\n\n") + "}\n"
88
+ end
89
+
90
+ def static_exports
91
+ [:configure, :config, :serialize].map do |name|
92
+ [
93
+ "", name,
94
+ @configuration.dts? ?
95
+ "RouterExposedMethods['#{name}']" :
96
+ "__jsr.#{name}"
97
+ ]
98
+ end
99
+ end
100
+
101
+ def routes_export
102
+ return "" unless @configuration.modern?
103
+ [*static_exports, *routes_list].map do |comment, name, body|
104
+ "#{comment}export const #{name}#{export_separator}#{body};\n\n"
105
+ end.join
106
+ end
107
+
108
+ def prevent_types_export
109
+ return "" unless @configuration.dts?
110
+ <<-JS
111
+ // By some reason this line prevents all types in a file
112
+ // from being automatically exported
113
+ export {};
114
+ JS
115
+ end
116
+
117
+ def export_separator
118
+ @configuration.dts? ? ': ' : ' = '
119
+ end
120
+
121
+ def routes_list
122
+ named_routes.sort_by(&:first).flat_map do |_, route|
123
+ route_helpers_if_match(route) + mounted_app_routes(route)
124
+ end
125
+ end
126
+
127
+ def mounted_app_routes(route)
128
+ rails_engine_app = app_from_route(route)
129
+ if rails_engine_app.respond_to?(:superclass) &&
130
+ rails_engine_app.superclass == Rails::Engine && !route.path.anchored
131
+ rails_engine_app.routes.named_routes.flat_map do |_, engine_route|
132
+ route_helpers_if_match(engine_route, route)
133
+ end
134
+ else
135
+ []
136
+ end
137
+ end
138
+
139
+ def app_from_route(route)
140
+ app = route.app
141
+ # rails engine in Rails 4.2 use additional
142
+ # ActionDispatch::Routing::Mapper::Constraints, which contain app
143
+ if app.respond_to?(:app) && app.respond_to?(:constraints)
144
+ app.app
145
+ else
146
+ app
147
+ end
148
+ end
149
+
150
+ def route_helpers_if_match(route, parent_route = nil)
151
+ Route.new(@configuration, route, parent_route).helpers
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,36 @@
1
+ module JsRoutes
2
+ # A Rack middleware that automatically updates routes file
3
+ # whenever routes.rb is modified
4
+ #
5
+ # Inspired by
6
+ # https://github.com/fnando/i18n-js/blob/main/lib/i18n/js/middleware.rb
7
+ class Middleware
8
+ def initialize(app)
9
+ @app = app
10
+ @routes_file = Rails.root.join("config/routes.rb")
11
+ @mtime = nil
12
+ end
13
+
14
+ def call(env)
15
+ update_js_routes
16
+ @app.call(env)
17
+ end
18
+
19
+ protected
20
+
21
+ def update_js_routes
22
+ new_mtime = routes_mtime
23
+ unless new_mtime == @mtime
24
+ JsRoutes.generate!
25
+ JsRoutes.definitions!
26
+ @mtime = new_mtime
27
+ end
28
+ end
29
+
30
+ def routes_mtime
31
+ File.mtime(@routes_file)
32
+ rescue Errno::ENOENT
33
+ nil
34
+ end
35
+ end
36
+ end