petite_vite_rails 0.1.0
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 +7 -0
- data/LICENSE +20 -0
- data/README.md +68 -0
- data/Rakefile +5 -0
- data/lib/generators/petite_vite/install/USAGE +20 -0
- data/lib/generators/petite_vite/install/install_generator.rb +183 -0
- data/lib/generators/petite_vite/install/templates/Procfile.dev.tt +1 -0
- data/lib/generators/petite_vite/install/templates/bin/dev.tt +9 -0
- data/lib/generators/petite_vite/install/templates/initializer.rb.tt +6 -0
- data/lib/generators/petite_vite/install/templates/package.json.tt +8 -0
- data/lib/generators/petite_vite/install/templates/petite_vite.json.tt +10 -0
- data/lib/generators/petite_vite/scaffold/USAGE +17 -0
- data/lib/generators/petite_vite/scaffold/scaffold_generator.rb +83 -0
- data/lib/generators/petite_vite/scaffold/templates/controller.rb.tt +4 -0
- data/lib/generators/petite_vite/scaffold/templates/page.html.erb.tt +1 -0
- data/lib/petite_vite_rails/railtie.rb +13 -0
- data/lib/petite_vite_rails/version.rb +3 -0
- data/lib/petite_vite_rails/view_helper.rb +57 -0
- data/lib/petite_vite_rails.rb +61 -0
- data/lib/tasks/petite_vite.rake +32 -0
- data/petite_vite_rails.gemspec +21 -0
- metadata +74 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: d7f877c8e144f5c7b0a61d316d91a9465886a972590be434bef460a3c9317dc8
|
|
4
|
+
data.tar.gz: 2ce8f54f5d543dddd77d4a5973d2a1f9b7ae7ba72bb908cd1555644c6a681613
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 8fb5f89e45482976808e5b92e4a6aaaa464707fbb79c62cbc00b1a3e853c1dcef8cf327781e37ec9cdb2fbb3fd3d9f026f69896cdb47a4a111a3de2e62658faa
|
|
7
|
+
data.tar.gz: 418231e3782e40e49e0ea8933f5c52ba2b68a4e8bf910778d4707c8b1135995428d6569eb5a5b4b9ce515da2a0a55f31580c690f81ffc0f36759daa36236dfd0
|
data/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright Zach Ahn
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# A Petite Vite integration Rails
|
|
2
|
+
|
|
3
|
+
A small Rails plugin that wires a Vite frontend into a Rails app. It provides a `vite_tags` view helper, an install generator, and rake task hooks — nothing more.
|
|
4
|
+
|
|
5
|
+
The Vite project lives in a subdirectory (e.g. `frontend/`) and is wired up as a [yarn workspace](https://yarnpkg.com/features/workspaces).
|
|
6
|
+
|
|
7
|
+
In development, `vite_tags` emits script tags pointing at the Vite dev server. In production, it reads Vite's `manifest.json` and emits hashed asset tags with `modulepreload` links, following Vite's [backend integration](https://vite.dev/guide/backend-integration.html) recipe.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
bundle add petite_vite_rails
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
### 1. Setup Vite Project
|
|
18
|
+
|
|
19
|
+
Create a Vite project within your Rails app (e.g. with `yarn create vue <path/to/frontend>`). I recommend putting it under the folder `frontend` or `app/frontend`.
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
my-rails-site
|
|
23
|
+
├── app
|
|
24
|
+
│ ├── assets
|
|
25
|
+
│ ├── controllers
|
|
26
|
+
│ ├── frontend ← *also recommended*
|
|
27
|
+
│ ├── models
|
|
28
|
+
│ └── views
|
|
29
|
+
├── frontend ← *most recommended*
|
|
30
|
+
├── Gemfile
|
|
31
|
+
└── ...
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 2. Install PetiteVite
|
|
35
|
+
|
|
36
|
+
Then, run the install generator. This sets up required configuration in addition to editing a few key files.
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
rails generate petite_vite:install --frontend-root <path/to/frontend> # defaults to "frontend"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Run `rails generate petite_vite:install --help` for all available options.
|
|
43
|
+
|
|
44
|
+
### 3. Setup a controller
|
|
45
|
+
|
|
46
|
+
Your frontend framework will probably need a mount point — the HTML element where the frontend app would be rendered in. Run the scaffold generator to create the controller and view, and optionally to generate routes.
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
rails generate petite_vite:scaffold --route-root --route-all
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Run `rails generate petite_vite:scaffold --help` for all available options.
|
|
53
|
+
|
|
54
|
+
### 4. Start your app with `bin/dev`
|
|
55
|
+
|
|
56
|
+
Run `bin/dev` (see `Procfile.dev`) to run both the Vite development server and the Rails server. The Vite server allows for auto-reloads.
|
|
57
|
+
|
|
58
|
+
### 5. Production deploys
|
|
59
|
+
|
|
60
|
+
PetiteVite integrates with `rails assets:precompile`. PetiteVite does NOT support running the Vite server in production.
|
|
61
|
+
|
|
62
|
+
## Tips
|
|
63
|
+
|
|
64
|
+
- I like to set up a frontend router. I then create server routes for each frontend-based route. These routes just render the mountpoint, and the frontend app takes care of the rest.
|
|
65
|
+
|
|
66
|
+
## License
|
|
67
|
+
|
|
68
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Description:
|
|
2
|
+
Installs PetiteVite
|
|
3
|
+
|
|
4
|
+
Options:
|
|
5
|
+
--vite-dev-server-port
|
|
6
|
+
Port for the Vite dev server. Defaults to 5173.
|
|
7
|
+
|
|
8
|
+
--frontend-root
|
|
9
|
+
Path to the frontend (Vite) project. Defaults to "frontend".
|
|
10
|
+
|
|
11
|
+
--rails-development-url
|
|
12
|
+
Rails development server URL (used as the Vite CORS origin).
|
|
13
|
+
Defaults to "http://localhost:3000".
|
|
14
|
+
|
|
15
|
+
--skip-frontend-check
|
|
16
|
+
Skip the check that the frontend (Vite) project exists.
|
|
17
|
+
Defaults to false.
|
|
18
|
+
|
|
19
|
+
Example:
|
|
20
|
+
rails generate petite_vite:install
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
module PetiteVite
|
|
2
|
+
class InstallGenerator < Rails::Generators::Base
|
|
3
|
+
source_root File.expand_path("templates", __dir__)
|
|
4
|
+
|
|
5
|
+
def self.exit_on_failure?
|
|
6
|
+
true
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class_option :vite_dev_server_port, type: :numeric, default: 5173,
|
|
10
|
+
banner: "Port for the Vite dev server"
|
|
11
|
+
class_option :frontend_root, type: :string, default: "frontend",
|
|
12
|
+
banner: "Path to frontend (Vite) project"
|
|
13
|
+
class_option :rails_development_url, type: :string, default: "http://localhost:3000",
|
|
14
|
+
banner: "Specifies the Rails development server URL"
|
|
15
|
+
class_option :skip_frontend_check, type: :boolean, default: false,
|
|
16
|
+
banner: "Skip the check that frontend (Vite) project exists"
|
|
17
|
+
|
|
18
|
+
def verify_input
|
|
19
|
+
vite_dev_server_port!
|
|
20
|
+
frontend_root!
|
|
21
|
+
rails_development_url!
|
|
22
|
+
skip_frontend_check!
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def verify_frontend_exists
|
|
26
|
+
return if skip_frontend_check!
|
|
27
|
+
|
|
28
|
+
path = File.join(destination_root, frontend_root!)
|
|
29
|
+
unless File.exist?(path)
|
|
30
|
+
raise "Expected #{path} to exist. Pass --skip-frontend-check to bypass."
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
path = File.join(destination_root, frontend_root!, "package.json")
|
|
34
|
+
unless File.exist?(path)
|
|
35
|
+
raise "Expected #{path} to exist. Pass --skip-frontend-check to bypass."
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def create_initializer
|
|
40
|
+
template("initializer.rb", "config/initializers/petite_vite.rb")
|
|
41
|
+
template("petite_vite.json", "config/petite_vite.json")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def create_package_json
|
|
45
|
+
template("package.json", "package.json")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def create_dev_scripts
|
|
49
|
+
return if File.exist?(File.join(destination_root, "Procfile.dev"))
|
|
50
|
+
|
|
51
|
+
template("Procfile.dev", "Procfile.dev")
|
|
52
|
+
template("bin/dev", "bin/dev")
|
|
53
|
+
chmod("bin/dev", 0o755)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def insert_to_procfile_dev
|
|
57
|
+
append_to_file("Procfile.dev", "vite: cd #{frontend_root!} && yarn dev\n")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def insert_to_frontend_source_main
|
|
61
|
+
prepend_to_file(File.join(destination_root, frontend_root!, frontend_source_main!), "import 'vite/modulepreload-polyfill'\n")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def insert_to_vite_config
|
|
65
|
+
vite_config_path = File.join(destination_root, frontend_root!, frontend_vite_config!)
|
|
66
|
+
|
|
67
|
+
depth = frontend_root!.count("/") + 1
|
|
68
|
+
relative_prefix = "../" * depth
|
|
69
|
+
insert_into_file(vite_config_path, before: %r{^\s*export default defineConfig\b}, verbose: false) do
|
|
70
|
+
"import shared from '#{relative_prefix}config/petite_vite.json'\n"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
injected = <<~SCRIPT.indent(2)
|
|
74
|
+
clearScreen: false,
|
|
75
|
+
server: {
|
|
76
|
+
cors: {
|
|
77
|
+
origin: shared.viteCorsOrigin,
|
|
78
|
+
},
|
|
79
|
+
origin: shared.viteCorsOrigin,
|
|
80
|
+
port: shared.viteDevServerPort,
|
|
81
|
+
strictPort: true,
|
|
82
|
+
},
|
|
83
|
+
build: {
|
|
84
|
+
manifest: true,
|
|
85
|
+
rollupOptions: {
|
|
86
|
+
input: shared.frontendInput,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
SCRIPT
|
|
90
|
+
|
|
91
|
+
gsub_file(vite_config_path, %r{(export default defineConfig\s*\(\s*\{\s*\n)}, "\\1#{injected}", verbose: false)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def insert_to_layout
|
|
95
|
+
Dir.glob(File.join(destination_root, "app", "views", "layouts", "**.html.erb")).each do |abspath|
|
|
96
|
+
basename = File.basename(abspath)
|
|
97
|
+
next if basename == "mailer.html.erb"
|
|
98
|
+
relpath = Pathname.new(abspath).relative_path_from(destination_root).to_s
|
|
99
|
+
insert_into_file(relpath, before: %r{^\s*</head>}, verbose: false) do
|
|
100
|
+
"<%= vite_tags %>\n"
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private
|
|
106
|
+
|
|
107
|
+
def vite_dev_server_port!
|
|
108
|
+
@options_vite_dev_server_port ||=
|
|
109
|
+
options
|
|
110
|
+
.fetch("vite_dev_server_port")
|
|
111
|
+
.yield_self do |value|
|
|
112
|
+
port = Integer(value)
|
|
113
|
+
raise "Invalid option: vite_dev_server_port must be between 1 and 65535" if port < 1 || port > 65535
|
|
114
|
+
port
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def frontend_root!
|
|
119
|
+
@options_frontend_root ||=
|
|
120
|
+
options
|
|
121
|
+
.fetch("frontend_root")
|
|
122
|
+
.sub(%r{/+\z}, "")
|
|
123
|
+
.tap do |result|
|
|
124
|
+
raise "Invalid option: frontend_root must not be the Rails root directory" if result == "." || result.empty?
|
|
125
|
+
raise "Invalid option: frontend_root must not start with /" if result.start_with?("/")
|
|
126
|
+
raise "Invalid option: frontend_root must only have [a-z0-9_/-]" if !%r{\A[a-z0-9_/-]+\z}i.match?(result)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def rails_development_url!
|
|
131
|
+
@options_rails_development_url ||=
|
|
132
|
+
options
|
|
133
|
+
.fetch("rails_development_url")
|
|
134
|
+
.yield_self do
|
|
135
|
+
u = URI(it)
|
|
136
|
+
u.path = ""
|
|
137
|
+
u.fragment = nil
|
|
138
|
+
u.query = nil
|
|
139
|
+
u.to_s
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def skip_frontend_check!
|
|
144
|
+
if @options_skip_frontend_check.nil?
|
|
145
|
+
@options_skip_frontend_check = options.fetch("skip_frontend_check")
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
@options_skip_frontend_check
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def frontend_source_main!
|
|
152
|
+
@frontend_source_main ||= begin
|
|
153
|
+
candidates = %w[main.ts main.tsx main.js main.jsx]
|
|
154
|
+
found = candidates.find { |c| File.exist?(File.join(destination_root, frontend_root!, "src", c)) }
|
|
155
|
+
unless found
|
|
156
|
+
raise "Expected one of #{candidates.map { |c| "src/#{c}" }.join(", ")} to exist in #{frontend_root!}."
|
|
157
|
+
end
|
|
158
|
+
"/src/#{found}"
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Vite's dev server strips a .ts extension when serving (so /src/main.js
|
|
163
|
+
# resolves to main.ts), but does NOT do this for .tsx/.jsx — those must be
|
|
164
|
+
# requested with their original extension.
|
|
165
|
+
def frontend_output!
|
|
166
|
+
@frontend_output ||= frontend_source_main!.sub(/\.ts\z/, ".js")
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def frontend_vite_config!
|
|
170
|
+
@frontend_vite_config ||= begin
|
|
171
|
+
ts = File.join(destination_root, frontend_root!, "vite.config.ts")
|
|
172
|
+
js = File.join(destination_root, frontend_root!, "vite.config.js")
|
|
173
|
+
if File.exist?(ts)
|
|
174
|
+
"vite.config.ts"
|
|
175
|
+
elsif File.exist?(js)
|
|
176
|
+
"vite.config.js"
|
|
177
|
+
else
|
|
178
|
+
raise "Expected #{ts} or #{js} to exist."
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
web: bin/rails server
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Description:
|
|
2
|
+
Generates a PetiteViteController + page view with a mount div matching
|
|
3
|
+
the frontend project's index.html. Optionally wires up routes.
|
|
4
|
+
|
|
5
|
+
Options:
|
|
6
|
+
--route-root
|
|
7
|
+
Adds `root "petite_vite#page"` to config/routes.rb (skipped if a
|
|
8
|
+
root route is already defined).
|
|
9
|
+
|
|
10
|
+
--route-all
|
|
11
|
+
Adds `get "*path", to: "petite_vite#page"` to config/routes.rb so
|
|
12
|
+
client-side routing in the SPA works without extra wiring.
|
|
13
|
+
|
|
14
|
+
Example:
|
|
15
|
+
rails generate petite_vite:scaffold
|
|
16
|
+
rails generate petite_vite:scaffold --route-root
|
|
17
|
+
rails generate petite_vite:scaffold --route-root --route-all
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module PetiteVite
|
|
4
|
+
class ScaffoldGenerator < Rails::Generators::Base
|
|
5
|
+
source_root File.expand_path("templates", __dir__)
|
|
6
|
+
|
|
7
|
+
def self.exit_on_failure?
|
|
8
|
+
true
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class_option :route_root, type: :boolean, default: false,
|
|
12
|
+
banner: "Add `root \"petite_vite#page\"` to config/routes.rb"
|
|
13
|
+
class_option :route_all, type: :boolean, default: false,
|
|
14
|
+
banner: "Add `get \"*path\", to: \"petite_vite#page\"` to config/routes.rb"
|
|
15
|
+
|
|
16
|
+
def verify_input
|
|
17
|
+
mount_id!
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def create_controller
|
|
21
|
+
template("controller.rb", "app/controllers/petite_vite_controller.rb")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def create_view
|
|
25
|
+
template("page.html.erb", "app/views/petite_vite/page.html.erb")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def insert_root_route
|
|
29
|
+
return unless options.fetch("route_root")
|
|
30
|
+
|
|
31
|
+
routes_path = File.join(destination_root, "config/routes.rb")
|
|
32
|
+
if /^\s*root\s/.match?(File.read(routes_path))
|
|
33
|
+
say_status("skip", "root route already defined in config/routes.rb", :yellow)
|
|
34
|
+
return
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
insert_into_file("config/routes.rb", after: %r{Rails\.application\.routes\.draw do\n}, verbose: false) do
|
|
38
|
+
" root \"petite_vite#page\"\n"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def insert_catchall_route
|
|
43
|
+
return unless options.fetch("route_all")
|
|
44
|
+
|
|
45
|
+
insert_into_file("config/routes.rb", before: %r{^end\s*\z}, verbose: false) do
|
|
46
|
+
" get \"*path\", to: \"petite_vite#page\"\n"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def frontend_root!
|
|
53
|
+
@frontend_root ||= begin
|
|
54
|
+
shared_path = File.join(destination_root, "config/petite_vite.json")
|
|
55
|
+
unless File.exist?(shared_path)
|
|
56
|
+
raise "Expected #{shared_path} to exist. Run `rails g petite_vite:install` first."
|
|
57
|
+
end
|
|
58
|
+
JSON.parse(File.read(shared_path)).fetch("frontendRoot")
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def index_html_path!
|
|
63
|
+
@index_html_path ||= begin
|
|
64
|
+
path = File.join(destination_root, frontend_root!, "index.html")
|
|
65
|
+
unless File.exist?(path)
|
|
66
|
+
raise "Expected #{path} to exist."
|
|
67
|
+
end
|
|
68
|
+
path
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def mount_id!
|
|
73
|
+
@mount_id ||= begin
|
|
74
|
+
contents = File.read(index_html_path!)
|
|
75
|
+
match = contents.match(/<div\b[^>]*\bid=["']([^"']+)["']/i)
|
|
76
|
+
unless match
|
|
77
|
+
raise "Could not determine mount-point id from #{index_html_path!}. Expected a <div id=\"...\"> element."
|
|
78
|
+
end
|
|
79
|
+
match[1]
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<div id="<%= mount_id! %>"></div>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module PetiteViteRails
|
|
2
|
+
class Railtie < ::Rails::Railtie
|
|
3
|
+
initializer "petite_vite_rails.helpers" do
|
|
4
|
+
ActiveSupport.on_load(:action_view) do
|
|
5
|
+
include PetiteVite::ViewHelper
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
rake_tasks do
|
|
10
|
+
load File.expand_path("../tasks/petite_vite.rake", __dir__)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module PetiteVite
|
|
2
|
+
module ViewHelper
|
|
3
|
+
def vite_tags
|
|
4
|
+
if Rails.env.development?
|
|
5
|
+
port = PetiteVite.config.vite_dev_server_port
|
|
6
|
+
frontend_output = PetiteVite.config.frontend_output.sub(%r{^/+}, "")
|
|
7
|
+
<<~SCRIPTS.html_safe
|
|
8
|
+
<script type="module" src="http://localhost:#{port}/@vite/client"></script>
|
|
9
|
+
<script type="module" src="http://localhost:#{port}/#{frontend_output}"></script>
|
|
10
|
+
SCRIPTS
|
|
11
|
+
else
|
|
12
|
+
# https://vite.dev/guide/backend-integration.html
|
|
13
|
+
#
|
|
14
|
+
# <!-- 0. if production -->
|
|
15
|
+
#
|
|
16
|
+
# <!-- 1. for cssFile of manifest[name].css -->
|
|
17
|
+
# <link rel="stylesheet" href="/{{ cssFile }}" />
|
|
18
|
+
#
|
|
19
|
+
# <!-- 2. for chunk of importedChunks(manifest, name) -->
|
|
20
|
+
# <!-- 3. for cssFile of chunk.css -->
|
|
21
|
+
# <link rel="stylesheet" href="/{{ cssFile }}" />
|
|
22
|
+
#
|
|
23
|
+
# <!-- 4 -->
|
|
24
|
+
# <script type="module" src="/{{ manifest[name].file }}"></script>
|
|
25
|
+
#
|
|
26
|
+
# <!-- 5. for chunk of importedChunks(manifest, name) -->
|
|
27
|
+
# <link rel="modulepreload" href="/{{ chunk.file }}" />
|
|
28
|
+
|
|
29
|
+
out = []
|
|
30
|
+
PetiteVite.config.manifest.contents.each do |_name, chunk|
|
|
31
|
+
next if !chunk["isEntry"]
|
|
32
|
+
# 1
|
|
33
|
+
chunk["css"]&.each do |css|
|
|
34
|
+
out.push(%(<link rel="stylesheet" href="/#{css}" />))
|
|
35
|
+
end
|
|
36
|
+
# 2
|
|
37
|
+
chunk["dynamicImports"]&.each do |imported_name|
|
|
38
|
+
# 3
|
|
39
|
+
imported_chunk = PetiteVite.config.manifest.contents[imported_name]
|
|
40
|
+
imported_chunk["css"]&.each do |css|
|
|
41
|
+
out.push(%(<link rel="stylesheet" href="/#{css}" />))
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
# 4
|
|
45
|
+
out.push(%(<script type="module" src="/#{chunk.fetch("file")}"></script>))
|
|
46
|
+
# 5
|
|
47
|
+
chunk["dynamicImports"]&.each do |imported_name|
|
|
48
|
+
imported_chunk = PetiteVite.config.manifest.contents[imported_name]
|
|
49
|
+
out.push(%(<link rel="modulepreload" href="/#{imported_chunk.fetch("file")}" />))
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
out.join("\n").html_safe
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
require_relative "petite_vite_rails/version"
|
|
2
|
+
require_relative "petite_vite_rails/view_helper"
|
|
3
|
+
require_relative "petite_vite_rails/railtie"
|
|
4
|
+
|
|
5
|
+
module PetiteVite
|
|
6
|
+
class << self
|
|
7
|
+
attr_accessor :config
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class Config
|
|
11
|
+
DEFAULT_VITE_DEV_SERVER_PORT = 5173
|
|
12
|
+
|
|
13
|
+
def initialize(shared_json_path:, vite_manifest_relpath:)
|
|
14
|
+
@shared_json_path = shared_json_path
|
|
15
|
+
@contents = JSON.parse(File.read(shared_json_path))
|
|
16
|
+
@vite_manifest_relpath = vite_manifest_relpath
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def build_command = @contents.fetch("buildCommand")
|
|
20
|
+
|
|
21
|
+
def frontend_output = @contents.fetch("frontendOutput")
|
|
22
|
+
|
|
23
|
+
def frontend_root = @contents.fetch("frontendRoot")
|
|
24
|
+
|
|
25
|
+
def vite_dev_server_port
|
|
26
|
+
return DEFAULT_VITE_DEV_SERVER_PORT if !@contents.key?("viteDevServerPort")
|
|
27
|
+
|
|
28
|
+
value = @contents.fetch("viteDevServerPort")
|
|
29
|
+
if !value.is_a?(Integer)
|
|
30
|
+
raise "Invalid viteDevServerPort in #{@shared_json_path}: expected an Integer, got #{value.inspect}"
|
|
31
|
+
end
|
|
32
|
+
value
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def manifest_path = File.join(frontend_root, @vite_manifest_relpath)
|
|
36
|
+
|
|
37
|
+
def manifest
|
|
38
|
+
@manifest ||= Manifest.new(config: self, manifest_path: manifest_path)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class Manifest
|
|
43
|
+
def initialize(config:, manifest_path:)
|
|
44
|
+
@config = config
|
|
45
|
+
@manifest_path = manifest_path
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def contents
|
|
49
|
+
if instance_variable_defined?(:@contents)
|
|
50
|
+
return @contents
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
@contents =
|
|
54
|
+
if File.exist?(@manifest_path)
|
|
55
|
+
JSON.parse(File.read(@manifest_path))
|
|
56
|
+
else
|
|
57
|
+
{}
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
namespace :petite_vite do
|
|
2
|
+
task build: :environment do
|
|
3
|
+
frontend_root = Rails.root / PetiteVite.config.frontend_root
|
|
4
|
+
|
|
5
|
+
Dir.chdir(frontend_root) do
|
|
6
|
+
sh(PetiteVite.config.build_command || "yarn run build")
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
task place: :environment do
|
|
11
|
+
frontend_root = Rails.root / PetiteVite.config.frontend_root
|
|
12
|
+
rails_asset_root = Rails.root / "public" / "assets"
|
|
13
|
+
rails_asset_root.mkdir if !rails_asset_root.directory?
|
|
14
|
+
|
|
15
|
+
frontend_root.join("dist", "assets").each_child do |asset|
|
|
16
|
+
cp asset, rails_asset_root
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
return if ENV["SKIP_JS_BUILD"]
|
|
22
|
+
|
|
23
|
+
if Rake::Task.task_defined?("assets:precompile")
|
|
24
|
+
Rake::Task["assets:precompile"].enhance(["petite_vite:build", "petite_vite:place"])
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
["test:prepare", "spec:prepare"].each do |test_prepare|
|
|
28
|
+
if Rake::Task.task_defined?(test_prepare)
|
|
29
|
+
Rake::Task[test_prepare].enhance(["petite_vite:build", "petite_vite:place"])
|
|
30
|
+
break
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require_relative "lib/petite_vite_rails/version"
|
|
2
|
+
|
|
3
|
+
Gem::Specification.new do |spec|
|
|
4
|
+
spec.name = "petite_vite_rails"
|
|
5
|
+
spec.version = PetiteViteRails::VERSION
|
|
6
|
+
spec.authors = ["Zach Ahn"]
|
|
7
|
+
spec.email = ["engineering@zachahn.com"]
|
|
8
|
+
spec.homepage = "https://github.com/zachahn/petite_vite_rails"
|
|
9
|
+
spec.summary = "A petite Vite integration for Rails"
|
|
10
|
+
spec.license = "MIT"
|
|
11
|
+
|
|
12
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
13
|
+
|
|
14
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
15
|
+
Dir["{app,config,db,lib}/**/*", "LICENSE", "Rakefile", "README.md", "*.gemspec"]
|
|
16
|
+
.select { |path| File.file?(path) }
|
|
17
|
+
.sort
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
spec.add_dependency "rails", ">= 8.0.0"
|
|
21
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: petite_vite_rails
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Zach Ahn
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rails
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 8.0.0
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 8.0.0
|
|
26
|
+
email:
|
|
27
|
+
- engineering@zachahn.com
|
|
28
|
+
executables: []
|
|
29
|
+
extensions: []
|
|
30
|
+
extra_rdoc_files: []
|
|
31
|
+
files:
|
|
32
|
+
- LICENSE
|
|
33
|
+
- README.md
|
|
34
|
+
- Rakefile
|
|
35
|
+
- lib/generators/petite_vite/install/USAGE
|
|
36
|
+
- lib/generators/petite_vite/install/install_generator.rb
|
|
37
|
+
- lib/generators/petite_vite/install/templates/Procfile.dev.tt
|
|
38
|
+
- lib/generators/petite_vite/install/templates/bin/dev.tt
|
|
39
|
+
- lib/generators/petite_vite/install/templates/initializer.rb.tt
|
|
40
|
+
- lib/generators/petite_vite/install/templates/package.json.tt
|
|
41
|
+
- lib/generators/petite_vite/install/templates/petite_vite.json.tt
|
|
42
|
+
- lib/generators/petite_vite/scaffold/USAGE
|
|
43
|
+
- lib/generators/petite_vite/scaffold/scaffold_generator.rb
|
|
44
|
+
- lib/generators/petite_vite/scaffold/templates/controller.rb.tt
|
|
45
|
+
- lib/generators/petite_vite/scaffold/templates/page.html.erb.tt
|
|
46
|
+
- lib/petite_vite_rails.rb
|
|
47
|
+
- lib/petite_vite_rails/railtie.rb
|
|
48
|
+
- lib/petite_vite_rails/version.rb
|
|
49
|
+
- lib/petite_vite_rails/view_helper.rb
|
|
50
|
+
- lib/tasks/petite_vite.rake
|
|
51
|
+
- petite_vite_rails.gemspec
|
|
52
|
+
homepage: https://github.com/zachahn/petite_vite_rails
|
|
53
|
+
licenses:
|
|
54
|
+
- MIT
|
|
55
|
+
metadata:
|
|
56
|
+
homepage_uri: https://github.com/zachahn/petite_vite_rails
|
|
57
|
+
rdoc_options: []
|
|
58
|
+
require_paths:
|
|
59
|
+
- lib
|
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
61
|
+
requirements:
|
|
62
|
+
- - ">="
|
|
63
|
+
- !ruby/object:Gem::Version
|
|
64
|
+
version: '0'
|
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
66
|
+
requirements:
|
|
67
|
+
- - ">="
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '0'
|
|
70
|
+
requirements: []
|
|
71
|
+
rubygems_version: 4.0.12
|
|
72
|
+
specification_version: 4
|
|
73
|
+
summary: A petite Vite integration for Rails
|
|
74
|
+
test_files: []
|