rails-vite-plugin 0.1.0-beta.1 → 0.1.1

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.
@@ -1,6 +1,186 @@
1
- <html>
2
- <body>
3
- <h1>rails-vite-plugin</h1>
4
- <p>This is the Vite dev server. Your Rails app is running on a different port.</p>
5
- </body>
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Rails Vite</title>
7
+ <style>
8
+ *,
9
+ *::before,
10
+ *::after {
11
+ box-sizing: border-box;
12
+ margin: 0;
13
+ padding: 0;
14
+ }
15
+
16
+ html {
17
+ -webkit-font-smoothing: antialiased;
18
+ -moz-osx-font-smoothing: grayscale;
19
+ }
20
+
21
+ body {
22
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
23
+ Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
24
+ min-height: 100vh;
25
+ display: flex;
26
+ align-items: center;
27
+ justify-content: center;
28
+ background-color: #f8f9fa;
29
+ color: #374151;
30
+ line-height: 1.6;
31
+ padding: 1.5rem;
32
+ }
33
+
34
+ @media (prefers-color-scheme: dark) {
35
+ body {
36
+ background-color: #111827;
37
+ color: #9ca3af;
38
+ }
39
+ }
40
+
41
+ .card {
42
+ background: #fff;
43
+ border-radius: 12px;
44
+ box-shadow:
45
+ 0 1px 3px rgba(0, 0, 0, 0.06),
46
+ 0 20px 40px rgba(0, 0, 0, 0.07);
47
+ max-width: 520px;
48
+ width: 100%;
49
+ padding: 2.5rem 2rem;
50
+ }
51
+
52
+ @media (prefers-color-scheme: dark) {
53
+ .card {
54
+ background: #1f2937;
55
+ box-shadow:
56
+ 0 1px 3px rgba(0, 0, 0, 0.2),
57
+ 0 20px 40px rgba(0, 0, 0, 0.3);
58
+ }
59
+ }
60
+
61
+ .logos {
62
+ display: flex;
63
+ align-items: center;
64
+ justify-content: center;
65
+ gap: 1.25rem;
66
+ margin-bottom: 2rem;
67
+ }
68
+
69
+ .logos svg {
70
+ flex-shrink: 0;
71
+ }
72
+
73
+ .plus {
74
+ color: #9ca3af;
75
+ font-size: 1.25rem;
76
+ font-weight: 300;
77
+ user-select: none;
78
+ }
79
+
80
+ .content p {
81
+ margin-bottom: 1rem;
82
+ font-size: 0.9375rem;
83
+ }
84
+
85
+ .content p:last-child {
86
+ margin-bottom: 0;
87
+ }
88
+
89
+ .divider {
90
+ border: none;
91
+ border-top: 1px solid #e5e7eb;
92
+ margin: 1.5rem 0;
93
+ }
94
+
95
+ @media (prefers-color-scheme: dark) {
96
+ .divider {
97
+ border-top-color: #374151;
98
+ }
99
+ }
100
+
101
+ .hint {
102
+ font-size: 0.8125rem;
103
+ color: #6b7280;
104
+ }
105
+
106
+ @media (prefers-color-scheme: dark) {
107
+ .hint {
108
+ color: #6b7280;
109
+ }
110
+ }
111
+
112
+ code {
113
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
114
+ "Liberation Mono", monospace;
115
+ font-size: 0.8125rem;
116
+ background: #f3f4f6;
117
+ color: #1f2937;
118
+ padding: 0.15em 0.4em;
119
+ border-radius: 4px;
120
+ }
121
+
122
+ @media (prefers-color-scheme: dark) {
123
+ code {
124
+ background: #374151;
125
+ color: #e5e7eb;
126
+ }
127
+ }
128
+
129
+ a {
130
+ color: #cc342d;
131
+ text-decoration: none;
132
+ font-weight: 600;
133
+ }
134
+
135
+ a:hover {
136
+ color: #a02a24;
137
+ text-decoration: underline;
138
+ }
139
+
140
+ @media (prefers-color-scheme: dark) {
141
+ a {
142
+ color: #ef4444;
143
+ }
144
+
145
+ a:hover {
146
+ color: #f87171;
147
+ }
148
+ }
149
+ </style>
150
+ </head>
151
+ <body>
152
+ <div class="card">
153
+ <div class="logos">
154
+ <a href="https://rubyonrails.org" aria-label="Ruby on Rails">
155
+ <svg width="42" height="42" fill="#D30001" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Ruby on Rails</title><path d="M.741 19.365h8.36s-1.598-7.291 3.693-10.243l.134-.066c1.286-.637 4.907-2.431 10.702 1.854.19-.159.37-.286.37-.286s-5.503-5.492-11.63-4.878c-3.079.275-6.867 3.079-9.09 6.783C1.058 16.233.741 19.365.741 19.365Zm8.804-.783a10.682 10.682 0 0 1-.127-1.333l1.143.412c.063.498.159.963.254 1.376l-1.27-.455Zm-7.799-4.317L.529 13.82c-.201.455-.423.984-.529 1.27l1.217.444c.137-.359.36-.878.529-1.269Zm7.831.296.857.677c.042-.413.116-.825.222-1.238l-.762-.603c-.137.391-.233.783-.317 1.164Zm2.042-2.646-.508-.762c.191-.243.413-.486.656-.709l.476.72a5.958 5.958 0 0 0-.624.751ZM4.19 8.878l.752.656c-.254.265-.498.551-.72.836l-.815-.698c.244-.265.508-.529.783-.794Zm9.799 1.027-.243-.73c.265-.117.571-.233.931-.339l.233.698a6.82 6.82 0 0 0-.921.371Zm3.122-.656.042-.667c.339.021.688.064 1.048.138l-.042.656a5.859 5.859 0 0 0-1.048-.127ZM8.942 6.392l-.476-.731c-.265.138-.54.286-.826.455l.487.741c.275-.169.54-.328.815-.465Zm9.217-.053.042-.709c-.095-.053-.36-.18-1.026-.371l-.043.699c.349.116.688.243 1.027.381ZM13.238 5.28h.106l-.212-.645c-.328 0-.666.021-1.016.063l.201.625a8.87 8.87 0 0 1 .921-.043Z"/></svg>
156
+ </a>
157
+ <span class="plus">+</span>
158
+ <a href="https://vite.dev" aria-label="Vite">
159
+ <svg width="38" height="38" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg">
160
+ <path d="M399.641 59.525 215.643 388.545c-3.816 6.826-13.638 6.994-17.69.304L7.876 59.221c-4.456-7.348 2.596-15.985 10.707-13.608l187.033 54.783c1.318.386 2.706.395 4.028.025L393.244 45.9c8.124-2.32 15.053 6.396 10.397 13.625Z" fill="url(#vite-a)"/>
161
+ <path d="M293.573 1.34 163.46 28.672c-2.2.462-3.834 2.347-3.967 4.577l-8.624 144.716c-.178 2.991 2.798 5.224 5.667 4.254l25.488-8.625c3.14-1.063 6.209 1.589 5.67 4.9l-7.828 48.08c-.555 3.412 2.72 6.085 5.882 4.8l19.6-7.952c3.16-1.282 6.433 1.39 5.878 4.8l-12.44 76.427c-.857 5.265 5.727 8.142 8.808 3.85l2.06-2.867L341.3 103.14c1.665-3.2-1.17-6.855-4.633-5.975l-26.357 6.698c-3.28.834-6.227-2.18-5.286-5.406L331.34 7.21c.95-3.25-2.065-6.296-5.3-5.358l-32.467 8.488Z" fill="url(#vite-b)"/>
162
+ <defs>
163
+ <linearGradient id="vite-a" x1="6.715" y1="32.086" x2="235.479" y2="344.885" gradientUnits="userSpaceOnUse">
164
+ <stop stop-color="#41D1FF"/>
165
+ <stop offset="1" stop-color="#BD34FE"/>
166
+ </linearGradient>
167
+ <linearGradient id="vite-b" x1="194.651" y1="8.818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse">
168
+ <stop stop-color="#FFBD4F"/>
169
+ <stop offset="1" stop-color="#FF9640"/>
170
+ </linearGradient>
171
+ </defs>
172
+ </svg>
173
+ </a>
174
+ </div>
175
+ <div class="content">
176
+ <p>This is the <strong>Vite development server</strong> providing Hot Module Replacement for your Rails application.</p>
177
+ <p>Your Rails app is running on a different port. Start it with:</p>
178
+ <p><code>bin/dev</code></p>
179
+ <hr class="divider">
180
+ <p class="hint">
181
+ Need help? Read the <a href="https://github.com/skryukov/rails_vite#readme">docs &rarr;</a>
182
+ </p>
183
+ </div>
184
+ </div>
185
+ </body>
6
186
  </html>
package/dist/index.d.ts CHANGED
@@ -1,12 +1,14 @@
1
1
  import { Plugin } from 'vite';
2
+ export type InputOption = string | string[] | Record<string, string>;
2
3
  export interface RailsViteOptions {
3
- input?: string | string[];
4
+ input?: InputOption;
4
5
  sourceDir?: string;
5
- ssr?: string;
6
+ ssr?: InputOption;
6
7
  ssrOutputDirectory?: string;
7
8
  devMetaFile?: string;
8
9
  buildDirectory?: string;
9
10
  publicDirectory?: string;
10
11
  refresh?: boolean | string | string[];
11
12
  }
13
+ export declare const refreshPaths: string[];
12
14
  export default function rails(options?: RailsViteOptions): Plugin;
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import path from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import picomatch from 'picomatch';
5
5
  import { loadEnv, defaultAllowedOrigins, } from 'vite';
6
- const defaultRefreshPaths = [
6
+ export const refreshPaths = [
7
7
  'app/views/**/*.{erb,slim,haml}',
8
8
  'app/helpers/**/*.rb',
9
9
  ];
@@ -16,9 +16,8 @@ export default function rails(options = {}) {
16
16
  const devMetaPath = options.devMetaFile ?? path.join('tmp', 'rails-vite.json');
17
17
  const ssrOutputDirectory = options.ssrOutputDirectory ?? 'ssr';
18
18
  const resolvedInput = resolveInput(input, sourceDir);
19
- const resolvedSsr = options.ssr ? resolveInput(options.ssr, sourceDir) : undefined;
19
+ const resolvedSsr = options.ssr !== undefined ? resolveInput(options.ssr, sourceDir) : undefined;
20
20
  let resolvedConfig;
21
- let devServerUrl;
22
21
  let reactRefresh = false;
23
22
  return {
24
23
  name: 'rails-vite',
@@ -74,7 +73,7 @@ export default function rails(options = {}) {
74
73
  server.httpServer?.once('listening', () => {
75
74
  const address = server.httpServer?.address();
76
75
  if (isAddressInfo(address)) {
77
- devServerUrl = resolveDevServerUrl(address, resolvedConfig);
76
+ const devServerUrl = resolveDevServerUrl(address, resolvedConfig);
78
77
  resolvedConfig.server.origin = devServerUrl;
79
78
  const meta = { url: devServerUrl, sourceDir };
80
79
  if (reactRefresh)
@@ -96,10 +95,10 @@ export default function rails(options = {}) {
96
95
  exitHandlersBound = true;
97
96
  }
98
97
  // Watch view templates for full-page reload
99
- const refreshPaths = resolveRefreshPaths(options.refresh);
100
- if (refreshPaths.length) {
101
- const match = picomatch(refreshPaths);
102
- server.watcher.add(refreshPaths);
98
+ const resolvedRefreshPaths = resolveRefreshPaths(options.refresh);
99
+ if (resolvedRefreshPaths.length) {
100
+ const match = picomatch(resolvedRefreshPaths);
101
+ server.watcher.add(resolvedRefreshPaths);
103
102
  server.watcher.on('change', (filePath) => {
104
103
  const relativePath = path.relative(process.cwd(), filePath);
105
104
  if (match(relativePath)) {
@@ -121,6 +120,9 @@ export default function rails(options = {}) {
121
120
  };
122
121
  }
123
122
  function resolveInput(input, sourceDir) {
123
+ if (typeof input === 'object' && !Array.isArray(input)) {
124
+ return Object.fromEntries(Object.entries(input).map(([key, value]) => [key, prefixWithSourceDir(value, sourceDir)]));
125
+ }
124
126
  if (Array.isArray(input)) {
125
127
  return input.map((entry) => prefixWithSourceDir(entry, sourceDir));
126
128
  }
@@ -132,9 +134,13 @@ function prefixWithSourceDir(entry, sourceDir) {
132
134
  }
133
135
  return `${sourceDir}/${entry}`;
134
136
  }
135
- const defaultEntryExtensions = ['.js', '.ts', '.jsx', '.tsx'];
137
+ const entrypointExtensions = /\.(mjs|js|mts|ts|jsx|tsx|css|scss|sass|less|styl|pcss)$/;
136
138
  function detectEntrypoint(sourceDir) {
137
- for (const ext of defaultEntryExtensions) {
139
+ const entrypointsDir = path.join(sourceDir, 'entrypoints');
140
+ if (fs.existsSync(entrypointsDir)) {
141
+ return discoverEntrypoints(entrypointsDir).map((entry) => `entrypoints/${entry}`);
142
+ }
143
+ for (const ext of ['.js', '.mjs', '.ts', '.mts', '.jsx', '.tsx']) {
138
144
  const candidate = path.join(sourceDir, `application${ext}`);
139
145
  if (fs.existsSync(candidate)) {
140
146
  return `application${ext}`;
@@ -142,35 +148,42 @@ function detectEntrypoint(sourceDir) {
142
148
  }
143
149
  return 'application.js';
144
150
  }
145
- function resolveRefreshPaths(refresh) {
146
- if (refresh === false || refresh === undefined) {
147
- return defaultRefreshPaths;
148
- }
149
- if (refresh === true) {
150
- return defaultRefreshPaths;
151
+ function discoverEntrypoints(dir, base = dir) {
152
+ const entries = [];
153
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
154
+ if (entry.isDirectory()) {
155
+ entries.push(...discoverEntrypoints(path.join(dir, entry.name), base));
156
+ }
157
+ else if (entrypointExtensions.test(entry.name)) {
158
+ entries.push(path.relative(base, path.join(dir, entry.name)));
159
+ }
151
160
  }
152
- if (typeof refresh === 'string') {
161
+ return entries;
162
+ }
163
+ function resolveRefreshPaths(refresh) {
164
+ if (refresh === false)
165
+ return [];
166
+ if (!refresh || refresh === true)
167
+ return refreshPaths;
168
+ if (typeof refresh === 'string')
153
169
  return [refresh];
154
- }
155
170
  return refresh;
156
171
  }
157
172
  function resolveDevServerUrl(address, config) {
158
- const configHmrProtocol = typeof config.server.hmr === 'object' ? config.server.hmr.protocol : null;
159
- const clientProtocol = configHmrProtocol
160
- ? configHmrProtocol === 'wss'
173
+ const hmr = typeof config.server.hmr === 'object' ? config.server.hmr : null;
174
+ const clientProtocol = hmr?.protocol
175
+ ? hmr.protocol === 'wss'
161
176
  ? 'https'
162
177
  : 'http'
163
178
  : null;
164
179
  const serverProtocol = config.server.https ? 'https' : 'http';
165
180
  const protocol = clientProtocol ?? serverProtocol;
166
- const configHmrHost = typeof config.server.hmr === 'object' ? config.server.hmr.host : null;
167
181
  const configHost = typeof config.server.host === 'string' ? config.server.host : null;
168
182
  const serverAddress = address.family === 'IPv6' || address.family === 6
169
183
  ? `[${address.address}]`
170
184
  : address.address;
171
- const host = configHmrHost ?? configHost ?? serverAddress;
172
- const configHmrClientPort = typeof config.server.hmr === 'object' ? config.server.hmr.clientPort : null;
173
- const port = configHmrClientPort ?? address.port;
185
+ const host = hmr?.host ?? configHost ?? serverAddress;
186
+ const port = hmr?.clientPort ?? address.port;
174
187
  return `${protocol}://${host}:${port}`;
175
188
  }
176
189
  function isAddressInfo(x) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rails-vite-plugin",
3
- "version": "0.1.0-beta.1",
3
+ "version": "0.1.1",
4
4
  "description": "Vite plugin for Rails integration",
5
5
  "author": "Svyatoslav Kryukov <me@skryukov.dev>",
6
6
  "license": "MIT",