19 |
20 |
--------------------------------------------------------------------------------
/docs/.vitepress/theme/index.js:
--------------------------------------------------------------------------------
1 | import { VPTheme } from '@mussi/vitepress-theme'
2 | import Quote from './components/Quote.vue'
3 |
4 | import 'windi.css'
5 | import './styles/styles.css'
6 |
7 | export default {
8 | ...VPTheme,
9 | enhanceApp ({ app }) {
10 | app.component('Quote', Quote)
11 | },
12 | }
13 |
--------------------------------------------------------------------------------
/docs/.vitepress/theme/styles/styles.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --vt-c-brand: #c00;
3 | --vt-c-brand-light: #d53f3f;
4 | --vt-c-brand-dark: var(--vt-c-brand);
5 | --vt-c-brand-highlight: var(--vt-c-brand-light);
6 | --vt-c-contrast-light: #0ea5e9;
7 | --bg-colored-light: #f3f7f7;
8 | --kbd-border: #d1d5da;
9 | --vt-c-tip: #0ea5e9;
10 | }
11 |
12 | html.dark {
13 | --kbd-border: var(--vt-c-text-dark-3);
14 |
15 | p {
16 | --code-inline-bg-color: #ffcaca0d;
17 | }
18 |
19 | .vt-doc a {
20 | color: var(--vt-c-text-dark-1);
21 | text-decoration: underline;
22 | text-decoration-color: var(--vt-c-text-dark-2);
23 | }
24 |
25 | .vt-doc a > code {
26 | --vt-c-brand-dark: var(--vt-c-text-dark-1);
27 | }
28 |
29 | .content a:hover,
30 | .content a:hover code {
31 | color: white;
32 | text-decoration-color: currentColor;
33 | }
34 |
35 | .DocSearch.DocSearch {
36 | --docsearch-container-background: rgba(12, 12, 12, 0.8);
37 | }
38 |
39 | .page .content {
40 | --vt-c-brand: var(--vt-c-text);
41 | }
42 |
43 | .nav-link.action:not(.alt) .item,
44 | .nav-link.action .item.item:hover,
45 | .custom-block.custom-block.tip a:hover {
46 | color: white;
47 | }
48 |
49 | kbd {
50 | background-color: #333;
51 | box-shadow: none;
52 | }
53 |
54 | kbd a {
55 | color: var(--vt-c-text);
56 | font-weight: 500;
57 | }
58 | }
59 |
60 | .VPNavBarTitle .text {
61 | display: none;
62 | }
63 |
64 | .VPNavBarSearch {
65 | justify-content: flex-end;
66 | }
67 |
68 | .vt-doc .custom-block.tip {
69 | --vt-c-brand: var(--vt-c-text-1);
70 | --vt-c-brand-dark: var(--vt-c-text-1);
71 | --vt-c-brand-highlight: var(--vt-c-text-1);
72 | }
73 |
74 | @media (min-width: 720px) {
75 | .home-hero.home-hero.home-hero {
76 | margin-top: 2rem;
77 | }
78 | }
79 |
80 | .nav-bar .logo {
81 | height: 30px;
82 | margin-right: 2px;
83 | }
84 |
85 | h2 .logo {
86 | display: inline-block;
87 | height: 32px;
88 | margin-left: 4px;
89 | margin-bottom: 4px;
90 | vertical-align: middle;
91 | }
92 |
93 | kbd {
94 | background-color: #fafbfc;
95 | border: 1px solid var(--kbd-border);
96 | border-radius: 6px;
97 | box-shadow: inset 0 -1px 0 var(--kbd-border);
98 | box-sizing: border-box;
99 | display: inline-block;
100 | font-size: 0.9rem;
101 | line-height: 10px;
102 | padding: 5px;
103 | vertical-align: middle;
104 | }
105 |
106 | code kbd {
107 | font-size: 0.7rem;
108 | padding: 2px 5px;
109 | }
110 |
111 | html:not(.dark) .vt-doc [class*='language-'] code {
112 | color: white;
113 | }
114 |
115 | .DocSearch-Search-Icon.DocSearch-Search-Icon {
116 | top: 0px;
117 | }
118 |
119 | .search-key {
120 | vertical-align: 3px;
121 | }
122 |
--------------------------------------------------------------------------------
/docs/client/index.md:
--------------------------------------------------------------------------------
1 | [Vite Rails]: https://vite-ruby.netlify.app/
2 | [aliases]: https://vite-ruby.netlify.app/guide/development.html#import-aliases-%F0%9F%91%89
3 | [codegen]: /guide/codegen
4 | [axios]: https://github.com/axios/axios
5 | [redaxios]: https://github.com/developit/redaxios
6 | [Inertia.js]: https://github.com/inertiajs/inertia
7 | [@js-from-routes/client]: https://github.com/ElMassimo/js_from_routes/tree/main/packages/client
8 | [@js-from-routes/axios]: https://github.com/ElMassimo/js_from_routes/tree/main/packages/axios
9 | [@js-from-routes/inertia]: https://github.com/ElMassimo/js_from_routes/tree/main/packages/inertia
10 | [@js-from-routes/redaxios]: https://github.com/ElMassimo/js_from_routes/tree/main/packages/redaxios
11 | [client_library]: /config/#client-library
12 | [easily submit forms]: https://github.com/ElMassimo/pingcrm-vite/blob/05e462cbed63ef150b1e1f12c984ef03a2e485aa/app/javascript/Pages/Contacts/Edit.vue#L24
13 | [config.ts]: https://github.com/ElMassimo/js_from_routes/blob/main/packages/client/src/config.ts
14 | [@rails/ujs]: https://github.com/rails/rails/tree/main/actionview/app/assets/javascripts
15 | [extracted]: https://github.com/ElMassimo/js_from_routes/blob/babeae83294efe58c4fa6bea0d76b5e146b0b92a/packages/client/src/config.ts#L37-L42
16 |
17 | # Client Libraries
18 |
19 | Several JS packages are provided to handle URL interpolation and requests.
20 |
21 | You can select which one to use by configuring [client_library], which also enables you to target your own code instead.
22 |
23 | #### [@js-from-routes/client]
24 |
25 | The default client. Uses `fetch` to avoid additional dependencies.
26 |
27 | #### [@js-from-routes/axios]
28 |
29 | Choose it if already using [axios], or have a [complex use case](https://github.com/axios/axios#creating-an-instance) with extensive usage of [interceptors](https://github.com/axios/axios#interceptors).
30 |
31 | #### [@js-from-routes/inertia]
32 |
33 | Allows you to [easily submit forms] and make requests using your existing configuration in [Inertia.js].
34 |
35 | #### [@js-from-routes/redaxios]
36 |
37 | Choose it if already using [redaxios], for consistency within your codebase.
38 |
39 | ## Usage 🚀
40 |
41 | The methods are fully typed, and documented using JSDoc.
42 |
43 | Here a few examples when using [@js-from-routes/client], more examples coming soon.
44 |
45 | ### Importing helpers
46 |
47 | If you use [Vite Rails], [aliases] will allow you to import these files as:
48 |
49 | ```js
50 | import { videoClips } from '~/api'
51 | // or
52 | import VideoClipsApi from '~/api/VideoClipsApi'
53 | ```
54 |
55 | When using webpack, it's highly recommended to [add an alias](https://webpack.js.org/configuration/resolve/#resolvealias) to simplify imports.
56 |
57 | ### Passing parameters
58 |
59 | You can pass a plain object as parameters; properties will be interpolated on any matching placeholders (`:id`) in the URL.
60 |
61 | ```js
62 | const video = { id: 5, title: 'New Wave' }
63 | videoClips.download.path(video) == "/video_clips/5/download"
64 | ```
65 |
66 | Missing parameters will throw an error providing the full context.
67 |
68 | ### Submitting data
69 |
70 | You can pass `data` to specify the request body, just like in [axios].
71 |
72 | ```js
73 | videoClips.update({ params: video, data: { title: 'New Waves' } })
74 | ```
75 |
76 | By default, the CSRF token will be [extracted] using the same conventions in [@rails/ujs].
77 |
78 | ### Obtaining the raw response
79 |
80 | By default, JSON responses are automatically unwrapped.
81 |
82 | If you need access to the response, or are using MIME types, pass `responseAs`:
83 |
84 | ```js
85 | const response = await videoClips.download({ params: video, responseAs: 'response' })
86 | ```
87 |
88 | The type of the response object depends on which library you are using.
89 |
90 | For example, it will be an `AxiosResponse` if using [@js-from-routes/axios].
91 |
92 | ## Configuring Requests ⚙️
93 |
94 | You can configure the defaults in all clients by using `Config`:
95 |
96 | ```js
97 | import { Config } from '@js-from-routes/client'
98 |
99 | Config.baseUrl = process.env.API_BASE_URL
100 | ```
101 |
102 | #### Disabling Case Conversion
103 |
104 | By default, object keys are `camelCased` when deserialized, and `snake_cased`
105 | when sent back to the server, but you can disable this behavior.
106 |
107 | ```js
108 | const noop = val => val
109 | Config.deserializeData = noop
110 | Config.serializeData = noop
111 | ```
112 |
113 | #### Other Options
114 |
115 | You can provide default headers, intercept all requests, handle response errors, and more.
116 |
117 | A new section documenting all available configuration options will be added soon.
118 |
119 | In the meantime, refer to [the source code][config.ts].
120 |
--------------------------------------------------------------------------------
/docs/config/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | [default template library]: https://github.com/ElMassimo/js_from_routes/blob/main/js_from_routes/lib/js_from_routes/template.js.erb#L3
6 | [template all]: https://github.com/ElMassimo/js_from_routes/blob/main/js_from_routes/lib/js_from_routes/template_all.js.erb
7 | [template index]: https://github.com/ElMassimo/js_from_routes/blob/main/js_from_routes/lib/js_from_routes/template_index.js.erb
8 | [default template]: https://github.com/ElMassimo/js_from_routes/blob/main/js_from_routes/lib/js_from_routes/template.js.erb
9 | [config options]: https://github.com/ElMassimo/js_from_routes/blob/main/js_from_routes/lib/js_from_routes/generator.rb#L178-L189
10 | [generate TypeScript]: https://github.com/ElMassimo/js_from_routes/blob/main/playground/vanilla/config/initializers/js_from_routes.rb
11 | [jQuery]: https://gist.github.com/ElMassimo/cab56e64e20ff797f3054b661a883646
12 | [ping]: https://github.com/ElMassimo/pingcrm-vite
13 |
14 | [client]: /client/
15 | [codegen]: /guide/codegen
16 | [client_library]: /config/#client-library
17 | [different template]: /guide/codegen.html#using-a-different-template
18 |
19 | # Configuration Reference
20 |
21 | The following section contains references to all configuration options in the provided libraries.
22 |
23 | ## Code Generation
24 |
25 | In order to customize code generation, you can add an initializer to your Rails app:
26 |
27 | ```ruby
28 | # config/initializers/js_from_routes.rb
29 | if Rails.env.development?
30 | JsFromRoutes.config do |config|
31 | ...
32 | end
33 | end
34 | ```
35 |
36 | You can fully customize how code is generated, which [client libraries][client] to use,
37 | whether to [generate TypeScript], target [jQuery], or [adapt to your framework of choice][ping].
38 |
39 | The following [config options] are available:
40 |
41 | ### `all_helpers_file`
42 |
43 | Whether to generate a file that exports all available helpers.
44 |
45 | You can specify a different name for the file, or pass `false` to disable it.
46 |
47 | __Default__: `true`, will output `index.js` in the [output_folder][config options]
48 |
49 | ```ruby
50 | config.all_helpers_file = false # Don't generate the file
51 | ```
52 |
53 | ### `client_library`
54 |
55 | The [library][client] from which to [import `definePathHelper`][default template library] in the [default template](#template-path).
56 |
57 | Read more about it in [_Code Generation_][codegen].
58 |
59 | __Default__: `@js-from-routes/client`
60 |
61 | ```ruby
62 | config.client_library = '~/services/ApiService'
63 | ```
64 |
65 | ### `export_if`
66 |
67 | Allows to configure which routes should be exported.
68 |
69 | Enables advanced usages, such as exporting different routes to different paths,
70 | which is helpful for monoliths with several apps inside them.
71 |
72 | __Default__: `->(route) { route.defaults.fetch(:export, nil) }`
73 |
74 | ```ruby
75 | config.export_if = ->(route) { route.defaults[:export] == :main }
76 | ```
77 |
78 | ### `file_suffix`
79 |
80 | This suffix is added by default to all generated files. You can modify it if
81 | you [are using TypeScript][generate TypeScript], or want to use a different convention.
82 |
83 | __Default__: `Api.js`
84 |
85 | ```ruby
86 | config.file_suffix = 'Api.ts'
87 | ```
88 |
89 | ### `helper_mappings`
90 |
91 | Defines how to obtain a path helper name from the name of a route (controller action).
92 |
93 | __Default__: `{"index" => "list", "show" => "get"}`
94 |
95 | ```ruby
96 | config.helper_mappings = {"edit" => "infoForUpdate"}
97 | ```
98 |
99 | ### `output_folder`
100 |
101 | The directory where the generated files are created.
102 |
103 | By default it will use the first of the following directories that exists.
104 |
105 | __Default__: `app/{frontend,javascript,packs,assets}/api`
106 |
107 | ```ruby
108 | config.output_folder = Rails.root.join('app', 'path_helpers')
109 | ```
110 |
111 | ### `template_path`
112 |
113 | The path of an ERB template that will be used to generate a helpers file.
114 |
115 | Read more about it in [_Code Generation_][different template].
116 |
117 | [__Default__][default template]
118 |
119 | ```ruby
120 | config.template_path = Rails.root.join('custom_js_from_routes.js.erb')
121 | ```
122 |
123 | ### `template_all_path`
124 |
125 | The path of an ERB template that will be used to export all helper files.
126 |
127 | You can provide a path to a custom template if the default conventions don't suit your needs.
128 |
129 | [__Default__][template all]
130 |
131 | ```ruby
132 | config.template_all_path = Rails.root.join('custom_all_helpers.js.erb')
133 | ```
134 |
135 | ### `template_index_path`
136 |
137 | Similar to the above, it re-exports all helpers, but also combines them in a default export allowing you to use a single object to access all the helpers.
138 |
139 | You can provide a path to a custom template if the default conventions don't suit your needs.
140 |
141 | [__Default__][template index]
142 |
143 | ```ruby
144 | config.template_index_path = Rails.root.join('custom_index_helpers.js.erb')
145 | ```
146 |
147 | ## Client Configuration
148 |
149 | Coming soon...
150 |
151 | For now, check the [_Client Libraries_](/client/#configuring-requests-⚙%EF%B8%8F) section.
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
--------------------------------------------------------------------------------
/docs/faqs/index.md:
--------------------------------------------------------------------------------
1 | [project]: https://github.com/ElMassimo/js_from_routes
2 | [GitHub Issues]: https://github.com/ElMassimo/js_from_routes/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc
3 | [GitHub Discussions]: https://github.com/ElMassimo/js_from_routes/discussions
4 | [client]: /client/
5 | [cache key]: /guide/codegen.html#caching-📦
6 |
7 | # Troubleshooting
8 |
9 | This section lists a few common gotchas, and bugs introduced in the past.
10 |
11 | Please skim through __before__ opening an [issue][GitHub Issues].
12 |
13 | ## Could not resolve "@js-from-routes/core"
14 |
15 | Some package managers do not install `peerDependencies`, while others [do](https://github.com/npm/rfcs/blob/latest/implemented/0025-install-peer-deps.md).
16 |
17 | Try adding the missing package explicitly:
18 |
19 | ```bash
20 | npm install @js-from-routes/core # yarn add @js-from-routes/core
21 | ```
22 |
23 | ## Imports in the generated code are not working
24 |
25 | Make sure that you have added one of the [client libraries][client] to your `package.json` and that the packages are installed.
26 |
27 | ## Changes to my custom templates are not being picked up
28 |
29 | Modifying the template should change the [cache key], so you might have introduced additional dependencies in your custom template.
30 |
31 | Try forcing regeneration by running:
32 |
33 | ```bash
34 | JS_FROM_ROUTES_FORCE=true bin/rake js_from_routes:generate
35 | ```
36 |
37 | ## Contact ✉️
38 |
39 | Please visit [GitHub Issues] to report bugs you find, and [GitHub Discussions] to make feature requests, or to get help.
40 |
41 | Don't hesitate to [⭐️ star the project][project] if you find it useful!
42 |
43 | Using it in production? Always love to hear about it! 😃
44 |
--------------------------------------------------------------------------------
/docs/guide/codegen.md:
--------------------------------------------------------------------------------
1 | [route dsl]: https://github.com/ElMassimo/js_from_routes/blob/main/js_from_routes/lib/js_from_routes/generator.rb#L77-L107
2 | [this pull request]: https://github.com/ElMassimo/pingcrm-vite/pull/2
3 |
4 | [exported routes]: /guide/#export-the-routes
5 | [client]: /client/
6 | [config]: /config/#code-generation
7 | [default template]: /config/#template-path
8 | [template_path]: /config/#template-path
9 | [template all]: /config/#template-all-path
10 |
11 | [client_library]: /config/#client-library
12 |
13 | # Code Generation 🤖
14 |
15 | Whenever you add a new route and _refresh the page_, the Rails reloader will kick in and generate path helpers for any of the [exported routes].
16 |
17 | If you are not running the development server, you can run a rake task to generate path helpers:
18 |
19 | ```bash
20 | bin/rake js_from_routes:generate
21 | ```
22 |
23 | By default, it will generate one file per controller, with one helper per exported action:
24 |
25 | ```js
26 | // app/javascript/api/VideoClipsApi.ts
27 | import { definePathHelper } from '@js-from-routes/client'
28 |
29 | export default {
30 | download: definePathHelper('get', '/video_clips/:id/download'),
31 |
32 | update: definePathHelper('patch', '/video_clips/:id'),
33 | }
34 | ```
35 |
36 | Notice how the HTTP verb becomes an implementation detail.
37 |
38 | Changing the verb in `routes.rb` does not require updating your client code!
39 |
40 | ## Customizing the Generated Code 🛠
41 |
42 | You can customize the code produced by the [default template], or use your own template.
43 |
44 | The following code examples assume that you are configuring _JS From Routes_ in an [initializer][config].
45 |
46 | ### Using a different client
47 |
48 | You can use any of the [provided client libraries][client] by using [client_library]:
49 |
50 | ```ruby
51 | config.client_library = '@js-from-routes/axios'
52 | ```
53 |
54 | ### Using your own code
55 |
56 | You can also use [client_library] to target your own code when generating path helpers:
57 |
58 | ```ruby
59 | config.client_library = '~/MyPathHelpers'
60 | ```
61 |
62 | As a result, the [default template] will generate:
63 |
64 | ```js
65 | // app/javascript/api/VideoClipsApi.ts
66 | import { definePathHelper } from '~/MyPathHelpers'
67 |
68 | export default {
69 | ...
70 | }
71 | ```
72 |
73 | ### Using a different template
74 |
75 | If you need to generate helpers in a different way, or want do something entirely different with exported routes, you can configure [template_path] to use your own template:
76 |
77 | ```ruby
78 | config.template_path = Rails.root.join('custom_js_from_routes.js.erb')
79 | ```
80 |
81 | A `routes` variable will be available in the template, with the exported routes for a controller.
82 |
83 | Each `route` exposes properties such as `verb` and `path`, [check the source code][route dsl] for details.
84 |
85 | See [this pull request] to get a sense of how flexible it can be.
86 |
87 | ## Caching 📦
88 |
89 | Code generation is skipped when routes have not changed.
90 |
91 | This is achieved by adding a header to generated files:
92 |
93 | ```js
94 | // JsFromRoutes CacheKey 12d79db32ed146448798751582013757
95 | //
96 | // DO NOT MODIFY: This file was automatically generated by JsFromRoutes.
97 | ```
98 |
99 | If for some reason you want to force regeneration, you can run:
100 |
101 | ```bash
102 | JS_FROM_ROUTES_FORCE=true bin/rake js_from_routes:generate
103 | ```
104 |
--------------------------------------------------------------------------------
/docs/guide/index.md:
--------------------------------------------------------------------------------
1 | [library]: https://github.com/ElMassimo/js_from_routes
2 | [GitHub Issues]: https://github.com/ElMassimo/js_from_routes/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc
3 | [GitHub Discussions]: https://github.com/ElMassimo/js_from_routes/discussions
4 | [client]: /client/
5 | [config]: /config/
6 | [rails bytes]: https://railsbytes.com/templates/X6ksgn
7 | [codegen]: /guide/codegen
8 | [path]: /client/#passing-parameters
9 | [request]: /client/#submitting-data
10 | [all helpers]: /config/#all-helpers-file
11 | [ping]: https://github.com/ElMassimo/pingcrm-vite
12 |
13 | [development]: /client/
14 | [routes]: https://github.com/ElMassimo/js_from_routes/blob/main/playground/vanilla/config/routes.rb#L6
15 | [export false]: https://github.com/ElMassimo/js_from_routes/blob/main/playground/vanilla/config/routes.rb#L18
16 |
17 | # Getting Started
18 |
19 | If you are interested to learn more about JS From Routes before trying it, check out the [Introduction](./introduction).
20 |
21 | ## Installation 💿
22 |
23 | For a one-line installation, you can [run this][rails bytes]:
24 |
25 | ```bash
26 | rails app:template LOCATION='https://railsbytes.com/script/X6ksgn'
27 | ```
28 |
29 | Or, add this line to your application's Gemfile in the `development` group and execute `bundle`:
30 |
31 | ```ruby
32 | group :development, :test do
33 | gem 'js_from_routes'
34 | end
35 | ```
36 |
37 | And then, add a [client library][client] to your `package.json`:
38 |
39 | ```bash
40 | npm install @js-from-routes/client # yarn add @js-from-routes/client
41 | ```
42 |
43 | ## Usage 🚀
44 |
45 | Once the library is installed, all you need to do is:
46 |
47 | ### Export the routes
48 |
49 | Use `export: true` to specify which [routes] should be taken into account when generating JS.
50 |
51 | ```ruby {2}
52 | Rails.application.routes.draw do
53 | resources :video_clips, only: [:show], export: true do
54 | get :download, on: :member
55 | end
56 |
57 | # Or:
58 | defaults export: true do
59 | # All routes defined inside this block will be exported.
60 | end
61 | end
62 | ```
63 |
64 | It will cascade to any nested action or resource, but you can [opt out][export false] with `export: false`.
65 |
66 | ### Refresh the page
67 |
68 | Rails' reloader will detect the changes, and path helpers will be [generated][codegen] for the exported actions.
69 |
70 | ```js
71 | // app/frontend/api/VideoClipsApi.ts
72 | import { definePathHelper } from '@js-from-routes/client'
73 |
74 | export default {
75 | download: definePathHelper('get', '/video_clips/:id/download'),
76 |
77 | show: definePathHelper('get', '/video_clips/:id'),
78 | }
79 | ```
80 |
81 | A file will be generated per controller, with a path helper per exported action, although this is [fully customizable][codegen].
82 |
83 | You can run bin/rake js_from_routes:generate to trigger generation manually.
84 |
85 | ### Use the generated helpers
86 |
87 | Calling a helper will [make a request][request] and return a promise with the unwrapped JSON result.
88 |
89 | ```js
90 | import { videoClips } from '~/api'
91 |
92 | const video = await videoClips.show({ id: 'oHg5SJYRHA0' })
93 | ```
94 |
95 | Use [`path`][path] when you only need the URL. It will interpolate parameters, such as `:id`.
96 |
97 | ```js
98 | const downloadPath = videoClips.download.path(video)
99 | ```
100 |
101 | You can also use an object that combines [all helpers].
102 |
103 | ```js
104 | import api from '~/api'
105 |
106 | const video = await api.videoClips.show({ id: 'oHg5SJYRHA0' })
107 |
108 | const comments = await api.comments.index()
109 | ```
110 |
111 | Depending on your use case, you may prefer to use this object instead of explicit imports.
112 |
113 | ## Onwards 🛣
114 |
115 | You should now be able to get started with [JS From Routes][library].
116 |
117 | Have in mind that code generation is [fully customizable][codegen]; this is just the default way to use it.
118 |
119 | For more information about using the helpers, check out [this section][development].
120 |
121 | If you would like to see a working example, check [this repo][ping].
122 |
123 | ### Contact ✉️
124 |
125 | Please visit [GitHub Issues] to report bugs you find, and [GitHub Discussions] to make feature requests, or to get help.
126 |
127 | Don't hesitate to [⭐️ star the project][library] if you find it useful!
128 |
129 | Using it in production? Always love to hear about it! 😃
130 |
--------------------------------------------------------------------------------
/docs/guide/introduction.md:
--------------------------------------------------------------------------------
1 | [library]: https://github.com/ElMassimo/js_from_routes
2 | [motivation]: /motivation
3 | [rails]: http://rubyonrails.org/
4 | [blog post]: https://maximomussini.com/posts/js-from-routes/
5 | [path helpers]: https://guides.rubyonrails.org/routing.html#path-and-url-helpers
6 |
7 | [codegen]: /guide/codegen
8 | [client]: /client/
9 | [config]: /config/
10 | [paths]: /client/#passing-parameters
11 | [requests]: /client/#submitting-data
12 | [case conversion]: /client/#disabling-case-conversion
13 | [responseAs]: /config/#responseAs
14 |
15 | # Introduction
16 |
17 | [__JS From Routes__][library] is a library to generate JS from routes defined in your [Rails] application.
18 |
19 | ## Why JS From Routes? 🤔
20 |
21 | [Path helpers] in Rails make it easy to build URLs, while avoiding typos and mistakes.
22 |
23 | Frontend code in Rails typically receives URLs from the server through JSON or HTML templates, or hardcodes paths of any endpoints that need to be accessed. Both approaches are fragile and cumbersome, and don't scale well.
24 |
25 | [JS From Routes][library] provides path helpers in JS, generating them from your Rails routes, so that you can make requests without manually handling paths, parameter interpolation, or HTTP verbs.
26 |
27 | Interested in hearing more? Read the original [blog post].
28 |
29 | ## Features ⚡️
30 |
31 | ### 🚀 Path and Request Helpers
32 |
33 | Use the controller and action name to [obtain paths][paths] or [make requests][requests]. No need to use URLs or manually interpolate parameters, preventing mistakes and saving development time.
34 |
35 | ### 🔁 Serialization / Deserialization
36 |
37 | Consuming JSON APIs works out of the box, but you can easily consume [other types of media][responseAs].
38 |
39 | [Case conversion] between Ruby and JS is handled for you, but you can also [opt-out][case conversion].
40 |
41 | ### ✅ Safety
42 |
43 | Prevents routing mistakes when renaming or removing an action.
44 |
45 | Path helpers are fully typed, and client libraries are entirely written in TypeScript.
46 |
47 | ### 🤖 Automatic Generation
48 |
49 | Path helpers are [generated automatically][codegen] whenever Rails reload is triggered.
50 |
51 | Add a route, refresh the page, and start using the path helper!
52 |
53 | ### 🛠 Customizable Generation
54 |
55 | Select a [client library][client] that uses `fetch` or `axios`, or use your [own code][client].
56 |
57 | Choose different conventions by [customizing][codegen] how code is generated.
58 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | page: true
3 | sidebar: false
4 | features:
5 | - title: 🚀 Productive
6 | details: No need to specify the URL, use the controller and action name
7 | link: /guide/#use-the-generated-helpers
8 | - title: 🎩 Elegant
9 | details: Make requests with function calls that return promises
10 | link: /client/#submitting-data
11 | - title: ✅ Safer
12 | details: Path helpers are fully typed and typos are no longer possible
13 | link: /guide/introduction.html#✅-safety
14 | - title: 🛠 Customizable
15 | details: Client libraries for fetch, axios, and more. Or use your own code
16 | link: /client/
17 | ---
18 |
19 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "js_from_routes_docs",
3 | "private": true,
4 | "version": "unknown",
5 | "license": "MIT",
6 | "scripts": {
7 | "dev": "vitepress dev --open",
8 | "build": "vitepress build",
9 | "preview": "vite preview .vitepress",
10 | "search": "docker run -it --env-file=.algolia/.env -e \"CONFIG=$(cat .algolia/config.json | jq -r tostring)\" algolia/docsearch-scraper",
11 | "css": "stylelint --ignore-pattern 'dist' '.vitepress/**/*.(vue|scss|css|postcss)'"
12 | },
13 | "devDependencies": {
14 | "@mussi/vitepress-theme": "^1.0.1",
15 | "stylelint": "^13.12.0",
16 | "stylelint-config-standard": "^20.0.0",
17 | "stylelint-order": "^4.1.0",
18 | "typescript": "^4.2.3",
19 | "vite": "^2.9",
20 | "vite-plugin-windicss": "^1",
21 | "vitepress": "^0.22.3"
22 | },
23 | "pnpm": {
24 | "peerDependencyRules": {
25 | "ignoreMissing": [
26 | "@algolia/client-search",
27 | "react",
28 | "react-dom",
29 | "@types/react"
30 | ]
31 | }
32 | },
33 | "dependencies": {
34 | "vue": "^3.2.36"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/docs/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElMassimo/js_from_routes/036e768f2723b36baaf0938b96325075437b14d2/docs/pnpm-workspace.yaml
--------------------------------------------------------------------------------
/docs/public/_headers:
--------------------------------------------------------------------------------
1 | /assets/*
2 | cache-control: max-age=31536000
3 | cache-control: immutable
--------------------------------------------------------------------------------
/docs/public/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElMassimo/js_from_routes/036e768f2723b36baaf0938b96325075437b14d2/docs/public/banner.png
--------------------------------------------------------------------------------
/docs/public/favicon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/public/logo-with-text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElMassimo/js_from_routes/036e768f2723b36baaf0938b96325075437b14d2/docs/public/logo-with-text.png
--------------------------------------------------------------------------------
/docs/public/logo-with-text.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/docs/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElMassimo/js_from_routes/036e768f2723b36baaf0938b96325075437b14d2/docs/public/logo.png
--------------------------------------------------------------------------------
/docs/public/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from 'path'
2 | import { defineConfig } from 'vite'
3 | import WindiCSS from 'vite-plugin-windicss'
4 |
5 | export default defineConfig({
6 | plugins: [
7 | WindiCSS({
8 | preflight: false,
9 | scan: {
10 | dirs: [resolve(__dirname, '.vitepress/theme/components')],
11 | },
12 | }),
13 | ],
14 | })
15 |
--------------------------------------------------------------------------------
/gemfiles/Gemfile-rails-edge:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | git_source(:github) { |repo| "https://github.com/#{repo}.git" }
4 |
5 | gem 'rails', github: 'rails/rails', branch: 'main'
6 |
7 | gemspec path: '../js_from_routes'
8 |
--------------------------------------------------------------------------------
/gemfiles/Gemfile-rails.7.2.x:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gem 'rails', '~> 7.2.1'
4 |
5 | gemspec path: '../js_from_routes'
6 |
--------------------------------------------------------------------------------
/gemfiles/Gemfile-rails.8.0.x:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gem 'rails', '~> 8.0'
4 |
5 | gemspec path: '../js_from_routes'
6 |
--------------------------------------------------------------------------------
/js_from_routes/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2021 Máximo Mussini
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/js_from_routes/README.md:
--------------------------------------------------------------------------------
1 |
2 | JS From Rails Routes
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | [Vite Rails]: https://vite-ruby.netlify.app/
14 | [aliases]: https://vite-ruby.netlify.app/guide/development.html#import-aliases-%F0%9F%91%89
15 | [config options]: https://github.com/ElMassimo/js_from_routes/blob/main/lib/js_from_routes/generator.rb#L82-L85
16 | [readme]: https://github.com/ElMassimo/js_from_routes
17 |
18 | For more information, check the main [README].
19 |
20 | ### Installation 💿
21 |
22 | Add this line to your application's Gemfile in the `development` group:
23 |
24 | ```ruby
25 | gem 'js_from_routes'
26 | ```
27 |
28 | And then execute:
29 |
30 | $ bundle
31 |
32 | Or install it yourself as:
33 |
34 | $ gem install js_from_routes
35 |
--------------------------------------------------------------------------------
/js_from_routes/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "bundler/gem_tasks"
4 |
5 | class Bundler::GemHelper
6 | def version_tag
7 | "js_from_routes@#{version}"
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/js_from_routes/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | pry -r './lib/js_from_routes.rb'
3 |
--------------------------------------------------------------------------------
/js_from_routes/bin/release:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | cd .. && pnpm release js_from_routes "$@"
3 |
--------------------------------------------------------------------------------
/js_from_routes/js_from_routes.gemspec:
--------------------------------------------------------------------------------
1 | require File.expand_path("../lib/js_from_routes/version", __FILE__)
2 |
3 | Gem::Specification.new do |s|
4 | s.name = "js_from_routes"
5 | s.version = JsFromRoutes::VERSION
6 | s.authors = ["Máximo Mussini"]
7 | s.email = ["maximomussini@gmail.com"]
8 | s.summary = "Generate JS automatically from Rails routes."
9 | s.description = "js_from_routes helps you by automatically generating path and API helpers from Rails route definitions, allowing you to save development effort and focus on the things that matter."
10 | s.homepage = "https://github.com/ElMassimo/js_from_routes"
11 | s.license = "MIT"
12 | s.extra_rdoc_files = ["README.md"]
13 | s.files = Dir.glob("{lib,exe,templates}/**/*") + %w[README.md CHANGELOG.md LICENSE.txt]
14 | s.require_path = "lib"
15 |
16 | s.add_dependency "railties", ">= 5.1", "< 9"
17 |
18 | s.add_development_dependency "bundler", "~> 2"
19 | s.add_development_dependency "listen", "~> 3.2"
20 | s.add_development_dependency "pry-byebug", "~> 3.9"
21 | s.add_development_dependency "rake", "~> 13"
22 | s.add_development_dependency "rspec-given", "~> 3.8"
23 | s.add_development_dependency "simplecov", "< 0.18"
24 | s.add_development_dependency "standard", "~> 1.0"
25 | end
26 |
--------------------------------------------------------------------------------
/js_from_routes/lib/js_from_routes.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Splitting the generator file allows consumers to skip the Railtie if desired:
4 | # - gem 'js_from_routes', require: false
5 | # - require 'js_from_routes/generator'
6 | require "js_from_routes/generator"
7 | require "js_from_routes/railtie"
8 |
--------------------------------------------------------------------------------
/js_from_routes/lib/js_from_routes/railtie.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "rails/railtie"
4 |
5 | # NOTE: Not strictly required, but it helps to simplify the setup.
6 | class JsFromRoutes::Railtie < Rails::Railtie
7 | railtie_name :js_from_routes
8 |
9 | if Rails.env.development?
10 | # Allows to automatically trigger code generation after updating routes.
11 | initializer "js_from_routes.reloader" do |app|
12 | app.config.to_prepare do
13 | JsFromRoutes.generate!(app)
14 | end
15 | end
16 | end
17 |
18 | # Suitable when triggering code generation manually.
19 | rake_tasks do |app|
20 | namespace :js_from_routes do
21 | desc "Generates JavaScript files from Rails routes, one file per controller, and one function per route."
22 | task generate: :environment do
23 | JsFromRoutes.generate!(app)
24 | end
25 | end
26 | end
27 |
28 | # Prevents Rails from interpreting the :export option as a required default,
29 | # which would cause controller tests to fail.
30 | initializer "js_from_routes.required_defaults" do |app|
31 | ActionDispatch::Journey::Route.prepend Module.new {
32 | def required_default?(key)
33 | (key == :export) ? false : super
34 | end
35 | }
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/js_from_routes/lib/js_from_routes/template.js.erb:
--------------------------------------------------------------------------------
1 | //
2 | // DO NOT MODIFY: This file was automatically generated by JsFromRoutes.
3 | import { definePathHelper } from '<%= client_library %>'
4 |
5 | export default {
6 | <% routes.each do |route| %>
7 | <%= route.helper %>: /* #__PURE__ */ definePathHelper('<%= route.verb %>', '<%= route.path %>'),
8 | <% end %>
9 | }
10 |
--------------------------------------------------------------------------------
/js_from_routes/lib/js_from_routes/template_all.js.erb:
--------------------------------------------------------------------------------
1 | //
2 | // DO NOT MODIFY: This file was automatically generated by JsFromRoutes.
3 | <% helpers.each do |helper| %>
4 | export { default as <%= helper.js_name %> } from './<%= helper.import_filename %>'
5 | <% end %>
6 |
--------------------------------------------------------------------------------
/js_from_routes/lib/js_from_routes/template_index.js.erb:
--------------------------------------------------------------------------------
1 | //
2 | // DO NOT MODIFY: This file was automatically generated by JsFromRoutes.
3 | import * as helpers from './all'
4 | export * from './all'
5 | export default helpers
6 |
--------------------------------------------------------------------------------
/js_from_routes/lib/js_from_routes/version.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Public: Automatically generates JS for Rails routes with { export: true }.
4 | # Generates one file per controller, and one function per route.
5 | module JsFromRoutes
6 | # Public: This library adheres to semantic versioning.
7 | VERSION = "4.0.2"
8 | end
9 |
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [build.environment]
2 | NODE_VERSION = "16"
3 | NPM_FLAGS = "--version" # uncomment if using pnpm to skip npm install
4 |
5 | [build]
6 | base = "docs/"
7 | ignore = "git diff --quiet 'HEAD^' HEAD ."
8 | publish = ".vitepress/dist"
9 | command = "npx pnpm i --store=node_modules/.pnpm-store && npx pnpm run build"
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "js_from_routes_monorepo",
3 | "version": "1.0.0",
4 | "private": true,
5 | "license": "MIT",
6 | "scripts": {
7 | "build": "npm -C packages run build",
8 | "docs": "npm -C docs run dev",
9 | "docs:search": "npm -C docs run search",
10 | "dev": "npm -C packages run dev",
11 | "release": "node scripts/release",
12 | "lint": "eslint . --ext .ts,.js",
13 | "postinstall": "husky install",
14 | "changelog": "node scripts/changelog",
15 | "test": "vitest"
16 | },
17 | "devDependencies": {
18 | "@mussi/eslint-config": "^0.5.0",
19 | "@types/node": "^14.14.31",
20 | "chalk": "^4.1.0",
21 | "conventional-changelog-cli": "^2.1.1",
22 | "enquirer": "^2.3.6",
23 | "eslint": "^7.17.0",
24 | "eslint-plugin-jest": "^24.1.5",
25 | "execa": "^5.0.0",
26 | "happy-dom": "7.7.2",
27 | "husky": "^5.1.1",
28 | "lint-staged": "^10.5.4",
29 | "minimist": "^1.2.5",
30 | "semver": "^7.3.4",
31 | "typescript": "^4.0.5",
32 | "vitest": "^0.29.8"
33 | },
34 | "lint-staged": {
35 | "*.{js,ts,tsx,jsx,vue}": [
36 | "eslint --fix"
37 | ],
38 | "*.rb": [
39 | "bin/standardrb --fix"
40 | ]
41 | },
42 | "repository": {
43 | "type": "git",
44 | "url": "https://github.com/ElMassimo/js_from_routes"
45 | },
46 | "homepage": "https://github.com/ElMassimo/js_from_routes"
47 | }
48 |
--------------------------------------------------------------------------------
/packages/axios/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [1.0.5](https://github.com/ElMassimo/js_from_routes/compare/axios@1.0.4...axios@1.0.5) (2025-03-27)
2 |
3 |
4 |
5 | ## [1.0.4](https://github.com/ElMassimo/js_from_routes/compare/axios@1.0.3...axios@1.0.4) (2021-03-16)
6 |
7 |
8 | ### Bug Fixes
9 |
10 | * Ensure callbacks are passed to Inertia.js ([c456b36](https://github.com/ElMassimo/js_from_routes/commit/c456b36e6f80927fa3f10999d46f3c91c34a408a))
11 | * Ensure packages are specified by using peerDependencies ([24b4918](https://github.com/ElMassimo/js_from_routes/commit/24b49183e3b6c7169b85eb0c0b06272b16455920))
12 |
13 |
14 |
15 | ## 1.0.3 (2021-03-13)
16 |
17 |
18 | ### Bug Fixes
19 |
20 | * Allow using responseAs: 'response' with Axios and Redaxios ([cdaf9cd](https://github.com/ElMassimo/js_from_routes/commit/cdaf9cd895407773851df4983108dcef1b0f6182))
21 |
22 |
23 |
24 | ## 1.0.2 (2021-03-13)
25 |
26 | - Specify bounded requirements for `@js-from-routes/client`.
27 |
28 | ## [1.0.1](https://github.com/ElMassimo/js_from_routes/compare/axios@1.0.0...axios@1.0.1) (2021-03-13)
29 |
30 | - Ensure `definePathHelper` is exported.
31 |
32 | ## [1.0.0](https://github.com/ElMassimo/js_from_routes/tree/axios%401.0.0)
33 |
--------------------------------------------------------------------------------
/packages/axios/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2021 Máximo Mussini
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/packages/axios/README.md:
--------------------------------------------------------------------------------
1 |
@js-from-routes/axios
2 |
3 |
Define path helpers to make API requests or interpolate URLs
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/playground/vanilla/public/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElMassimo/js_from_routes/036e768f2723b36baaf0938b96325075437b14d2/playground/vanilla/public/apple-touch-icon-precomposed.png
--------------------------------------------------------------------------------
/playground/vanilla/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElMassimo/js_from_routes/036e768f2723b36baaf0938b96325075437b14d2/playground/vanilla/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/playground/vanilla/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElMassimo/js_from_routes/036e768f2723b36baaf0938b96325075437b14d2/playground/vanilla/public/favicon.ico
--------------------------------------------------------------------------------
/playground/vanilla/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 |
--------------------------------------------------------------------------------
/playground/vanilla/spec/controllers/video_clips_spec.rb:
--------------------------------------------------------------------------------
1 | require "rails_helper"
2 |
3 | RSpec.describe VideoClipsController, type: :controller do
4 | describe "GET /" do
5 | it "should make a request" do
6 | get :new
7 | expect(response).to render_template(:new)
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/playground/vanilla/spec/rails_helper.rb:
--------------------------------------------------------------------------------
1 | # This file is copied to spec/ when you run 'rails generate rspec:install'
2 | require "spec_helper"
3 | ENV["RAILS_ENV"] ||= "test"
4 | require File.expand_path("../config/environment", __dir__)
5 | # Prevent database truncation if the environment is production
6 | abort("The Rails environment is running in production mode!") if Rails.env.production?
7 | require "rspec/rails"
8 | # Add additional requires below this line. Rails is not loaded until this point!
9 |
10 | # Requires supporting ruby files with custom matchers and macros, etc, in
11 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
12 | # run as spec files by default. This means that files in spec/support that end
13 | # in _spec.rb will both be required and run as specs, causing the specs to be
14 | # run twice. It is recommended that you do not name files matching this glob to
15 | # end with _spec.rb. You can configure this pattern with the --pattern
16 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
17 | #
18 | # The following line is provided for convenience purposes. It has the downside
19 | # of increasing the boot-up time by auto-requiring all files in the support
20 | # directory. Alternatively, in the individual `*_spec.rb` files, manually
21 | # require only the support files necessary.
22 | #
23 | # Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }
24 |
25 | RSpec.configure do |config|
26 | # Remove this line to enable support for ActiveRecord
27 | config.use_active_record = false
28 |
29 | # If you enable ActiveRecord support you should unncomment these lines,
30 | # note if you'd prefer not to run each example within a transaction, you
31 | # should set use_transactional_fixtures to false.
32 | #
33 | # config.fixture_path = "#{::Rails.root}/spec/fixtures"
34 | # config.use_transactional_fixtures = true
35 |
36 | # RSpec Rails can automatically mix in different behaviours to your tests
37 | # based on their file location, for example enabling you to call `get` and
38 | # `post` in specs under `spec/controllers`.
39 | #
40 | # You can disable this behaviour by removing the line below, and instead
41 | # explicitly tag your specs with their type, e.g.:
42 | #
43 | # RSpec.describe UsersController, type: :controller do
44 | # # ...
45 | # end
46 | #
47 | # The different available types are documented in the features, such as in
48 | # https://relishapp.com/rspec/rspec-rails/docs
49 | config.infer_spec_type_from_file_location!
50 |
51 | # Filter lines from Rails gems in backtraces.
52 | config.filter_rails_from_backtrace!
53 | # arbitrary gems may also be filtered via:
54 | # config.filter_gems_from_backtrace("gem name")
55 | end
56 |
--------------------------------------------------------------------------------
/playground/vanilla/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | # This file was generated by the `rails generate rspec:install` command. Conventionally, all
2 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3 | # The generated `.rspec` file contains `--require spec_helper` which will cause
4 | # this file to always be loaded, without a need to explicitly require it in any
5 | # files.
6 | #
7 | # Given that it is always loaded, you are encouraged to keep this file as
8 | # light-weight as possible. Requiring heavyweight dependencies from this file
9 | # will add to the boot time of your test suite on EVERY test run, even for an
10 | # individual file that may not need all of that loaded. Instead, consider making
11 | # a separate helper file that requires the additional dependencies and performs
12 | # the additional setup, and require it from the spec files that actually need
13 | # it.
14 | #
15 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
16 | RSpec.configure do |config|
17 | # rspec-expectations config goes here. You can use an alternate
18 | # assertion/expectation library such as wrong or the stdlib/minitest
19 | # assertions if you prefer.
20 | config.expect_with :rspec do |expectations|
21 | # This option will default to `true` in RSpec 4. It makes the `description`
22 | # and `failure_message` of custom matchers include text for helper methods
23 | # defined using `chain`, e.g.:
24 | # be_bigger_than(2).and_smaller_than(4).description
25 | # # => "be bigger than 2 and smaller than 4"
26 | # ...rather than:
27 | # # => "be bigger than 2"
28 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true
29 | end
30 |
31 | # rspec-mocks config goes here. You can use an alternate test double
32 | # library (such as bogus or mocha) by changing the `mock_with` option here.
33 | config.mock_with :rspec do |mocks|
34 | # Prevents you from mocking or stubbing a method that does not exist on
35 | # a real object. This is generally recommended, and will default to
36 | # `true` in RSpec 4.
37 | mocks.verify_partial_doubles = true
38 | end
39 |
40 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
41 | # have no way to turn it off -- the option exists only for backwards
42 | # compatibility in RSpec 3). It causes shared context metadata to be
43 | # inherited by the metadata hash of host groups and examples, rather than
44 | # triggering implicit auto-inclusion in groups with matching metadata.
45 | config.shared_context_metadata_behavior = :apply_to_host_groups
46 |
47 | # The settings below are suggested to provide a good initial experience
48 | # with RSpec, but feel free to customize to your heart's content.
49 | # # This allows you to limit a spec run to individual examples or groups
50 | # # you care about by tagging them with `:focus` metadata. When nothing
51 | # # is tagged with `:focus`, all examples get run. RSpec also provides
52 | # # aliases for `it`, `describe`, and `context` that include `:focus`
53 | # # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
54 | # config.filter_run_when_matching :focus
55 | #
56 | # # Allows RSpec to persist some state between runs in order to support
57 | # # the `--only-failures` and `--next-failure` CLI options. We recommend
58 | # # you configure your source control system to ignore this file.
59 | # config.example_status_persistence_file_path = "spec/examples.txt"
60 | #
61 | # # Limits the available syntax to the non-monkey patched syntax that is
62 | # # recommended. For more details, see:
63 | # # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
64 | # # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
65 | # # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
66 | # config.disable_monkey_patching!
67 | #
68 | # # Many RSpec users commonly either run the entire suite or an individual
69 | # # file, and it's useful to allow more verbose output when running an
70 | # # individual spec file.
71 | # if config.files_to_run.one?
72 | # # Use the documentation formatter for detailed output,
73 | # # unless a formatter has already been configured
74 | # # (e.g. via a command-line flag).
75 | # config.default_formatter = "doc"
76 | # end
77 | #
78 | # # Print the 10 slowest examples and example groups at the
79 | # # end of the spec run, to help surface which specs are running
80 | # # particularly slow.
81 | # config.profile_examples = 10
82 | #
83 | # # Run specs in random order to surface order dependencies. If you find an
84 | # # order dependency and want to debug it, you can fix the order by providing
85 | # # the seed, which is printed after each run.
86 | # # --seed 1234
87 | # config.order = :random
88 | #
89 | # # Seed global randomization in this process using the `--seed` CLI option.
90 | # # Setting this allows you to use `--seed` to deterministically reproduce
91 | # # test failures related to randomization by passing the same `--seed` value
92 | # # as the one that triggered the failure.
93 | # Kernel.srand config.seed
94 | end
95 |
--------------------------------------------------------------------------------
/playground/vanilla/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "module": "ESNext",
5 | "target": "es2017",
6 | "lib": ["ESNext", "DOM"],
7 | "esModuleInterop": true,
8 | "strict": true,
9 | "strictNullChecks": true,
10 | "moduleResolution": "Node",
11 | "resolveJsonModule": true,
12 | "skipLibCheck": true,
13 | "paths": {
14 | "@/*": ["./app/javascript/*"],
15 | "~/*": ["./app/javascript/*"]
16 | }
17 | },
18 | "exclude": [
19 | "**/dist",
20 | "**/node_modules",
21 | "**/test"
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/playground/vanilla/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import RubyPlugin from 'vite-plugin-ruby'
3 | import VuePlugin from '@vitejs/plugin-vue'
4 | import WindiCSSPlugin from 'vite-plugin-windicss'
5 |
6 | export default defineConfig({
7 | plugins: [
8 | VuePlugin(),
9 | RubyPlugin(),
10 | WindiCSSPlugin({
11 | root: __dirname,
12 | scan: {
13 | fileExtensions: ['html', 'erb', 'vue', 'js', 'ts'],
14 | dirs: ['app/javascript', 'app/views'],
15 | },
16 | }),
17 | ],
18 | })
19 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - packages/core/
3 | - packages/client/
4 | - packages/axios/
5 | - packages/redaxios/
6 | - packages/inertia/
7 |
--------------------------------------------------------------------------------
/scripts/changelog.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-var-requires */
2 | const path = require('path')
3 | const fs = require('fs')
4 | const args = require('minimist')(process.argv.slice(2))
5 | const execa = require('execa')
6 | const chalk = require('chalk')
7 |
8 | const name = args._[0]?.trim()
9 |
10 | if (!name) {
11 | console.error(chalk.red(`Expected library name as an argument, received ${name}`))
12 | process.exit(1)
13 | }
14 |
15 | const isRubyPackage = name === 'js_from_routes'
16 | const packagePath = isRubyPackage ? name : `packages/${name}`
17 |
18 | /**
19 | * @param {string} bin
20 | * @param {string[]} args
21 | * @param {object} opts
22 | */
23 | const run = (bin, args, opts = {}) =>
24 | execa(bin, args, { stdio: 'inherit', ...opts })
25 |
26 | /**
27 | * @param {string} paths
28 | */
29 | const resolve = paths => path.resolve(__dirname, `../${packagePath}/${paths}`)
30 |
31 | /**
32 | * @param {string} name
33 | */
34 | function writePackageJson (name) {
35 | const versionRegex = /VERSION = "([\d.]+)"/
36 | const versionFile = fs.readFileSync(resolve(`lib/${name}/version.rb`), 'utf-8')
37 | const versionCaptures = versionFile.match(versionRegex)
38 | const version = versionCaptures && versionCaptures[1]
39 | if (!version) {
40 | console.error(chalk.red(`Could not infer version for ${name}.`))
41 | process.exit(1)
42 | }
43 | fs.writeFileSync(resolve('package.json'), `{ "version": "${version}" }`)
44 | }
45 |
46 | async function main () {
47 | if (isRubyPackage) writePackageJson(name)
48 |
49 | await run('npx', [
50 | 'conventional-changelog',
51 | '-p', 'angular',
52 | '-i', `${packagePath}/CHANGELOG.md`,
53 | '-s',
54 | '-t', `${name}@`,
55 | '--pkg', `./${packagePath}/package.json`,
56 | '--commit-path', `./${packagePath}`,
57 | ])
58 |
59 | if (isRubyPackage) fs.rmSync(resolve('package.json'))
60 | }
61 |
62 | main().catch((err) => {
63 | console.error(err)
64 | })
65 |
--------------------------------------------------------------------------------
/scripts/verifyCommit.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-var-requires */
2 | /* eslint-disable import/order */
3 | const args = require('minimist')(process.argv.slice(2))
4 | const msgPath = args._[0]
5 | const msg = require('fs').readFileSync(msgPath, 'utf-8').trim()
6 |
7 | const releaseRE = /^v\d/
8 | const commitRE = /^(revert: )?(feat|fix|docs|dx|refactor|perf|test|workflow|build|ci|chore|types|wip|release|deps)(\(.+\))?: .{1,50}/
9 |
10 | if (!releaseRE.test(msg) && !commitRE.test(msg)) {
11 | console.log()
12 | const chalk = require('chalk')
13 | console.error(
14 | ` ${chalk.bgRed.white(' ERROR ')} ${chalk.red(
15 | 'invalid commit message format.',
16 | )}\n\n${
17 | chalk.red(
18 | ' Proper commit message format is required for automated changelog generation. Examples:\n\n',
19 | )
20 | } ${chalk.green('feat: add \'comments\' option')}\n`
21 | + ` ${chalk.green('fix: handle events on blur (close #28)')}\n\n${
22 | chalk.red(' See .github/commit-convention.md for more details.\n')}`,
23 | )
24 | process.exit(1)
25 | }
26 |
--------------------------------------------------------------------------------
/spec/js_from_routes/js_from_routes_spec.rb:
--------------------------------------------------------------------------------
1 | require "vanilla/config/application"
2 | require "vanilla/config/routes"
3 |
4 | describe JsFromRoutes do
5 | original_template_path = JsFromRoutes.config.template_path
6 |
7 | let(:output_dir) { Pathname.new File.expand_path("../support/generated", __dir__) }
8 | let(:sample_dir) { Rails.root.join("app", "javascript", "api") }
9 | let(:different_template_path) { File.expand_path("../support/jquery_template.js.erb", __dir__) }
10 | let(:controllers_with_exported_routes) { %w[Comments Settings/UserPreferences VideoClips] }
11 |
12 | def file_for(dir, name)
13 | dir.join("#{name}Api.ts")
14 | end
15 |
16 | def sample_file_for(name)
17 | file_for(sample_dir, name)
18 | end
19 |
20 | def output_file_for(name)
21 | file_for(output_dir, name)
22 | end
23 |
24 | def expect_templates
25 | expect_any_instance_of(JsFromRoutes::Template)
26 | end
27 |
28 | def be_rendered
29 | receive(:render_template)
30 | end
31 |
32 | before do
33 | # Sanity checks
34 | expect(sample_dir.exist?).to eq true
35 | expect(Rails.application.routes.routes).to be_present
36 |
37 | # Remove directory from a previous test run.
38 | begin
39 | FileUtils.remove_dir(output_dir)
40 | rescue
41 | nil
42 | end
43 |
44 | # Change the configuration to use a different directory.
45 | JsFromRoutes.config do |config|
46 | config.file_suffix = "Api.ts"
47 | config.all_helpers_file = false
48 | config.output_folder = output_dir
49 | config.template_path = original_template_path
50 | end
51 | end
52 |
53 | # NOTE: We do a manual snapshot test for now, more tests coming in the future.
54 | it "should generate the files as expected" do
55 | expect_templates.to be_rendered.exactly(3).times.and_call_original
56 | JsFromRoutes.generate!
57 |
58 | # It does not generate routes that don't have `export: true`.
59 | expect(output_file_for("Welcome").exist?).to eq false
60 |
61 | # It generates one file per controller with exported routes.
62 | controllers_with_exported_routes.each do |file_name|
63 | expect(output_file_for(file_name).read).to eq sample_file_for(file_name).read
64 | end
65 |
66 | # It does not render if generating again.
67 | JsFromRoutes.generate!
68 | end
69 |
70 | context "changing the template" do
71 | before do
72 | JsFromRoutes.generate!
73 |
74 | JsFromRoutes.config do |config|
75 | config.template_path = different_template_path
76 | end
77 | end
78 |
79 | it "detects changes and re-renders" do
80 | expect_templates.to be_rendered.exactly(3).times.and_call_original
81 | JsFromRoutes.generate!
82 |
83 | # These files should no longer match the sample ones.
84 | controllers_with_exported_routes.each do |file_name|
85 | expect(output_file_for(file_name).read).not_to eq sample_file_for(file_name).read
86 | end
87 |
88 | # It should not rewrite the files if the cache key has not changed.
89 | JsFromRoutes.generate!
90 | end
91 | end
92 |
93 | context "when generating all_helpers_file" do
94 | before do
95 | JsFromRoutes.generate!
96 |
97 | JsFromRoutes.config do |config|
98 | config.all_helpers_file = true
99 | end
100 | end
101 |
102 | it "generates a file with all helpers" do
103 | JsFromRoutes.generate!
104 | expect(output_dir.join("all.ts").exist?).to eq true
105 | expect(output_dir.join("index.ts").exist?).to eq true
106 |
107 | # Should not trigger another render.
108 | expect_templates.not_to be_rendered
109 | JsFromRoutes.generate!
110 | end
111 | end
112 |
113 | it "should have a rake task available" do
114 | Rails.application.load_tasks
115 | expect_templates.to be_rendered.exactly(3).times
116 | expect { Rake::Task["js_from_routes:generate"].invoke }.not_to raise_error
117 | end
118 | end
119 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require "simplecov"
2 | SimpleCov.start {
3 | add_filter "/spec/"
4 | add_filter "/playground/"
5 | }
6 |
7 | require "rails"
8 | require "js_from_routes"
9 | require "rspec/given"
10 | require "pry-byebug"
11 |
12 | $LOAD_PATH.push File.expand_path("../playground", __dir__)
13 |
--------------------------------------------------------------------------------
/spec/support/jquery_template.js.erb:
--------------------------------------------------------------------------------
1 | //
2 | // DO NOT MODIFY: This file was automatically generated by JsFromRoutes.
3 | import { formatUrl } from '@js-from-routes/core'
4 |
5 | export default {
6 | <% routes.each_with_index do |route, index| %>
7 | <% if index > 0 %><%= "\n" %><% end
8 | %> <%= route.helper %>: options =>
9 | <% unless route.export == :path_only
10 | %>$.<%= route.verb %>({ url: <% end %>formatUrl('<%= route.path %>', options)<%
11 | unless route.export == :path_only %>, ...options })<% end %>,
12 | <% end %>
13 | }
14 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "ESNext",
4 | "target": "es2020",
5 | "lib": ["ESNext", "DOM"],
6 | "esModuleInterop": true,
7 | "strict": true,
8 | "strictNullChecks": true,
9 | "moduleResolution": "Node",
10 | "resolveJsonModule": true,
11 | "skipLibCheck": true
12 | },
13 | "exclude": [
14 | "**/dist",
15 | "**/node_modules",
16 | "**/test"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/vetur.config.js:
--------------------------------------------------------------------------------
1 | // vetur.config.js
2 | /** @type {import('vls').VeturConfig} */
3 | module.exports = {
4 | // **optional** default: `{}`
5 | // override vscode settings
6 | // Notice: It only affects the settings used by Vetur.
7 | settings: {
8 | 'vetur.useWorkspaceDependencies': true,
9 | 'vetur.experimental.templateInterpolationService': true,
10 | },
11 | // **optional** default: `[{ root: './' }]`
12 | // support monorepos
13 | projects: [
14 | {
15 | // **required**
16 | // Where is your project?
17 | // It is relative to `vetur.config.js`.
18 | root: './playground/vanilla',
19 | // **optional** default: `'package.json'`
20 | // Where is `package.json` in the project?
21 | // We use it to determine the version of vue.
22 | // It is relative to root property.
23 | package: './package.json',
24 | // **optional**
25 | // Where is TypeScript config file in the project?
26 | // It is relative to root property.
27 | tsconfig: './tsconfig.json',
28 | // **optional** default: `'./.vscode/vetur/snippets'`
29 | // Where is vetur custom snippets folders?
30 | // snippetFolder: './.vscode/vetur/snippets',
31 | // **optional** default: `[]`
32 | // Register globally Vue component glob.
33 | // If you set it, you can get completion by that components.
34 | // It is relative to root property.
35 | // Notice: It won't actually do it. You need to use `require.context` or `Vue.component`
36 | // globalComponents: [
37 | // './src/components/**/*.vue'
38 | // ]
39 | },
40 | ],
41 | }
42 |
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vitest/config'
2 |
3 | export default defineConfig({
4 | test: {
5 | environment: 'happy-dom', // or 'jsdom', 'node'
6 | },
7 | })
8 |
--------------------------------------------------------------------------------