rails-vite-plugin 0.1.2 → 0.2.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.
- package/dist/index.d.ts +9 -5
- package/dist/index.js +25 -140
- package/dist/jsbundling.d.ts +27 -0
- package/dist/jsbundling.js +307 -0
- package/dist/shared/alias.d.ts +2 -0
- package/dist/shared/alias.js +12 -0
- package/dist/shared/cleanup.d.ts +5 -0
- package/dist/shared/cleanup.js +14 -0
- package/dist/shared/css.d.ts +2 -0
- package/dist/shared/css.js +2 -0
- package/dist/shared/dev-server-page.d.ts +1 -0
- package/dist/shared/dev-server-page.js +6 -0
- package/dist/shared/dev-server.d.ts +5 -0
- package/dist/shared/dev-server.js +20 -0
- package/dist/shared/entries.d.ts +13 -0
- package/dist/shared/entries.js +123 -0
- package/dist/shared/env-guard.d.ts +1 -0
- package/dist/shared/env-guard.js +13 -0
- package/dist/shared/refresh.d.ts +2 -0
- package/dist/shared/refresh.js +13 -0
- package/dist/shared/ssr.d.ts +2 -0
- package/dist/shared/ssr.js +14 -0
- package/dist/shared/types.d.ts +8 -0
- package/dist/shared/types.js +1 -0
- package/package.json +7 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { Plugin } from 'vite';
|
|
2
|
-
|
|
2
|
+
import type { InputOption } from './shared/types.js';
|
|
3
|
+
import { refreshPaths } from './shared/refresh.js';
|
|
4
|
+
export type { InputOption };
|
|
5
|
+
export { refreshPaths };
|
|
3
6
|
export interface RailsViteOptions {
|
|
4
7
|
input?: InputOption;
|
|
5
8
|
sourceDir?: string;
|
|
6
9
|
ssr?: InputOption;
|
|
7
|
-
|
|
10
|
+
ssrOutDir?: string;
|
|
8
11
|
devMetaFile?: string;
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
/** Directory name under publicDir for Vite build output (default: 'vite') */
|
|
13
|
+
buildDir?: string;
|
|
14
|
+
/** Public directory (default: 'public') */
|
|
15
|
+
publicDir?: string;
|
|
11
16
|
refresh?: boolean | string | string[];
|
|
12
17
|
}
|
|
13
|
-
export declare const refreshPaths: string[];
|
|
14
18
|
export default function rails(options?: RailsViteOptions): Plugin;
|
package/dist/index.js
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { fileURLToPath } from 'url';
|
|
4
3
|
import picomatch from 'picomatch';
|
|
5
4
|
import { loadEnv, defaultAllowedOrigins, } from 'vite';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
import { resolveInput, detectEntrypointsDir, discoverEntrypointInputs, detectEntrypoint } from './shared/entries.js';
|
|
6
|
+
import { resolveAlias } from './shared/alias.js';
|
|
7
|
+
import { resolveDevServerUrl, isAddressInfo } from './shared/dev-server.js';
|
|
8
|
+
import { ensureCommandShouldRunInEnvironment } from './shared/env-guard.js';
|
|
9
|
+
import { refreshPaths, resolveRefreshPaths } from './shared/refresh.js';
|
|
10
|
+
import { readDevServerIndexHtml } from './shared/dev-server-page.js';
|
|
11
|
+
import { resolveNoExternal } from './shared/ssr.js';
|
|
12
|
+
import { bindExitHandler } from './shared/cleanup.js';
|
|
13
|
+
export { refreshPaths };
|
|
11
14
|
export default function rails(options = {}) {
|
|
12
15
|
const sourceDir = options.sourceDir ?? 'app/javascript';
|
|
13
16
|
const entrypointsDir = options.input === undefined ? detectEntrypointsDir(sourceDir) : null;
|
|
14
17
|
const input = options.input ?? (entrypointsDir ? discoverEntrypointInputs(sourceDir, entrypointsDir) : detectEntrypoint(sourceDir));
|
|
15
|
-
const
|
|
16
|
-
const
|
|
18
|
+
const publicDir = options.publicDir ?? 'public';
|
|
19
|
+
const buildDir = options.buildDir ?? 'vite';
|
|
17
20
|
const devMetaPath = options.devMetaFile ?? path.join('tmp', 'rails-vite.json');
|
|
18
|
-
const
|
|
21
|
+
const ssrOutDir = options.ssrOutDir ?? 'ssr';
|
|
19
22
|
const resolvedInput = resolveInput(input, sourceDir);
|
|
20
23
|
const resolvedSsr = options.ssr !== undefined ? resolveInput(options.ssr, sourceDir) : undefined;
|
|
21
24
|
let resolvedConfig;
|
|
@@ -23,19 +26,18 @@ export default function rails(options = {}) {
|
|
|
23
26
|
return {
|
|
24
27
|
name: 'rails-vite',
|
|
25
28
|
enforce: 'post',
|
|
26
|
-
config(userConfig, { command, mode }) {
|
|
29
|
+
config(userConfig, { command, mode, isSsrBuild }) {
|
|
27
30
|
const env = loadEnv(mode, userConfig.envDir || process.cwd(), '');
|
|
28
|
-
|
|
29
|
-
ensureCommandShouldRunInEnvironment(command, env);
|
|
31
|
+
ensureCommandShouldRunInEnvironment(command, env, 'rails-vite-plugin');
|
|
30
32
|
return {
|
|
31
|
-
base: userConfig.base ?? (command === 'build' ? `/${
|
|
33
|
+
base: userConfig.base ?? (command === 'build' ? `/${buildDir}/` : ''),
|
|
32
34
|
publicDir: userConfig.publicDir ?? false,
|
|
33
35
|
build: {
|
|
34
|
-
manifest: userConfig.build?.manifest ?? (
|
|
35
|
-
ssrManifest: userConfig.build?.ssrManifest ?? (
|
|
36
|
-
outDir: userConfig.build?.outDir ?? (
|
|
36
|
+
manifest: userConfig.build?.manifest ?? (isSsrBuild ? false : 'manifest.json'),
|
|
37
|
+
ssrManifest: userConfig.build?.ssrManifest ?? (isSsrBuild ? 'ssr-manifest.json' : false),
|
|
38
|
+
outDir: userConfig.build?.outDir ?? (isSsrBuild ? ssrOutDir : path.join(publicDir, buildDir)),
|
|
37
39
|
rollupOptions: {
|
|
38
|
-
input: userConfig.build?.rollupOptions?.input ?? (
|
|
40
|
+
input: userConfig.build?.rollupOptions?.input ?? (isSsrBuild ? resolvedSsr : resolvedInput),
|
|
39
41
|
},
|
|
40
42
|
assetsInlineLimit: userConfig.build?.assetsInlineLimit ?? 0,
|
|
41
43
|
},
|
|
@@ -45,15 +47,7 @@ export default function rails(options = {}) {
|
|
|
45
47
|
},
|
|
46
48
|
},
|
|
47
49
|
resolve: {
|
|
48
|
-
alias:
|
|
49
|
-
? [
|
|
50
|
-
...(userConfig.resolve?.alias ?? []),
|
|
51
|
-
{ find: '@', replacement: path.resolve(process.cwd(), sourceDir) },
|
|
52
|
-
]
|
|
53
|
-
: {
|
|
54
|
-
'@': path.resolve(process.cwd(), sourceDir),
|
|
55
|
-
...userConfig.resolve?.alias,
|
|
56
|
-
},
|
|
50
|
+
alias: resolveAlias(userConfig.resolve?.alias, sourceDir),
|
|
57
51
|
},
|
|
58
52
|
ssr: {
|
|
59
53
|
noExternal: resolveNoExternal(userConfig),
|
|
@@ -87,16 +81,9 @@ export default function rails(options = {}) {
|
|
|
87
81
|
}, 100);
|
|
88
82
|
}
|
|
89
83
|
});
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
};
|
|
94
|
-
process.on('exit', clean);
|
|
95
|
-
process.on('SIGINT', () => process.exit());
|
|
96
|
-
process.on('SIGTERM', () => process.exit());
|
|
97
|
-
process.on('SIGHUP', () => process.exit());
|
|
98
|
-
exitHandlersBound = true;
|
|
99
|
-
}
|
|
84
|
+
bindExitHandler(() => {
|
|
85
|
+
fs.rmSync(devMetaPath, { force: true });
|
|
86
|
+
});
|
|
100
87
|
// Watch view templates for full-page reload
|
|
101
88
|
const resolvedRefreshPaths = resolveRefreshPaths(options.refresh);
|
|
102
89
|
if (resolvedRefreshPaths.length) {
|
|
@@ -105,12 +92,11 @@ export default function rails(options = {}) {
|
|
|
105
92
|
server.watcher.on('change', (filePath) => {
|
|
106
93
|
const relativePath = path.relative(process.cwd(), filePath);
|
|
107
94
|
if (match(relativePath)) {
|
|
108
|
-
server.
|
|
95
|
+
server.hot.send({ type: 'full-reload', path: '*' });
|
|
109
96
|
}
|
|
110
97
|
});
|
|
111
98
|
}
|
|
112
|
-
|
|
113
|
-
const devServerIndexHtml = fs.readFileSync(path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'dev-server-index.html'), 'utf-8');
|
|
99
|
+
const devServerIndexHtml = readDevServerIndexHtml();
|
|
114
100
|
return () => server.middlewares.use((req, res, next) => {
|
|
115
101
|
if (req.url === '/index.html') {
|
|
116
102
|
res.statusCode = 404;
|
|
@@ -122,104 +108,3 @@ export default function rails(options = {}) {
|
|
|
122
108
|
},
|
|
123
109
|
};
|
|
124
110
|
}
|
|
125
|
-
function resolveInput(input, sourceDir) {
|
|
126
|
-
if (typeof input === 'object' && !Array.isArray(input)) {
|
|
127
|
-
return Object.fromEntries(Object.entries(input).map(([key, value]) => [key, prefixWithSourceDir(value, sourceDir)]));
|
|
128
|
-
}
|
|
129
|
-
if (Array.isArray(input)) {
|
|
130
|
-
return input.map((entry) => prefixWithSourceDir(entry, sourceDir));
|
|
131
|
-
}
|
|
132
|
-
return prefixWithSourceDir(input, sourceDir);
|
|
133
|
-
}
|
|
134
|
-
function prefixWithSourceDir(entry, sourceDir) {
|
|
135
|
-
if (entry.startsWith(sourceDir + '/') || entry.startsWith('/')) {
|
|
136
|
-
return entry;
|
|
137
|
-
}
|
|
138
|
-
return `${sourceDir}/${entry}`;
|
|
139
|
-
}
|
|
140
|
-
const entrypointExtensions = /\.(mjs|js|mts|ts|jsx|tsx|css|scss|sass|less|styl|pcss)$/;
|
|
141
|
-
function detectEntrypointsDir(sourceDir) {
|
|
142
|
-
const entrypointsDir = path.join(sourceDir, 'entrypoints');
|
|
143
|
-
return fs.existsSync(entrypointsDir) ? 'entrypoints' : null;
|
|
144
|
-
}
|
|
145
|
-
function discoverEntrypointInputs(sourceDir, entrypointsDir) {
|
|
146
|
-
const absDir = path.join(sourceDir, entrypointsDir);
|
|
147
|
-
return discoverEntrypoints(absDir).map((entry) => `${entrypointsDir}/${entry}`);
|
|
148
|
-
}
|
|
149
|
-
function detectEntrypoint(sourceDir) {
|
|
150
|
-
for (const ext of ['.js', '.mjs', '.ts', '.mts', '.jsx', '.tsx']) {
|
|
151
|
-
const candidate = path.join(sourceDir, `application${ext}`);
|
|
152
|
-
if (fs.existsSync(candidate)) {
|
|
153
|
-
return `application${ext}`;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
return 'application.js';
|
|
157
|
-
}
|
|
158
|
-
function discoverEntrypoints(dir, base = dir) {
|
|
159
|
-
const entries = [];
|
|
160
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
161
|
-
if (entry.isDirectory()) {
|
|
162
|
-
entries.push(...discoverEntrypoints(path.join(dir, entry.name), base));
|
|
163
|
-
}
|
|
164
|
-
else if (entrypointExtensions.test(entry.name)) {
|
|
165
|
-
entries.push(path.relative(base, path.join(dir, entry.name)));
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
return entries;
|
|
169
|
-
}
|
|
170
|
-
function resolveRefreshPaths(refresh) {
|
|
171
|
-
if (refresh === false)
|
|
172
|
-
return [];
|
|
173
|
-
if (!refresh || refresh === true)
|
|
174
|
-
return refreshPaths;
|
|
175
|
-
if (typeof refresh === 'string')
|
|
176
|
-
return [refresh];
|
|
177
|
-
return refresh;
|
|
178
|
-
}
|
|
179
|
-
function resolveDevServerUrl(address, config) {
|
|
180
|
-
const hmr = typeof config.server.hmr === 'object' ? config.server.hmr : null;
|
|
181
|
-
const clientProtocol = hmr?.protocol
|
|
182
|
-
? hmr.protocol === 'wss'
|
|
183
|
-
? 'https'
|
|
184
|
-
: 'http'
|
|
185
|
-
: null;
|
|
186
|
-
const serverProtocol = config.server.https ? 'https' : 'http';
|
|
187
|
-
const protocol = clientProtocol ?? serverProtocol;
|
|
188
|
-
const configHost = typeof config.server.host === 'string' ? config.server.host : null;
|
|
189
|
-
const serverAddress = address.family === 'IPv6' || address.family === 6
|
|
190
|
-
? `[${address.address}]`
|
|
191
|
-
: address.address;
|
|
192
|
-
const host = hmr?.host ?? configHost ?? serverAddress;
|
|
193
|
-
const port = hmr?.clientPort ?? address.port;
|
|
194
|
-
return `${protocol}://${host}:${port}`;
|
|
195
|
-
}
|
|
196
|
-
function isAddressInfo(x) {
|
|
197
|
-
return typeof x === 'object' && x !== null;
|
|
198
|
-
}
|
|
199
|
-
function ensureCommandShouldRunInEnvironment(command, env) {
|
|
200
|
-
if (command === 'build') {
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
if (env.CI !== undefined) {
|
|
204
|
-
throw new Error('rails-vite-plugin: You should not run the Vite dev server in CI. ' +
|
|
205
|
-
'Run `rake vite:build` instead.');
|
|
206
|
-
}
|
|
207
|
-
if (env.RAILS_ENV === 'production') {
|
|
208
|
-
throw new Error('rails-vite-plugin: You should not run the Vite dev server in production. ' +
|
|
209
|
-
'Run `rake vite:build` instead.');
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
function resolveNoExternal(config) {
|
|
213
|
-
const userNoExternal = config.ssr?.noExternal;
|
|
214
|
-
const pluginNoExternal = ['rails-vite-plugin'];
|
|
215
|
-
if (userNoExternal === true) {
|
|
216
|
-
return true;
|
|
217
|
-
}
|
|
218
|
-
if (userNoExternal === undefined) {
|
|
219
|
-
return pluginNoExternal;
|
|
220
|
-
}
|
|
221
|
-
return [
|
|
222
|
-
...(Array.isArray(userNoExternal) ? userNoExternal : [userNoExternal]),
|
|
223
|
-
...pluginNoExternal,
|
|
224
|
-
];
|
|
225
|
-
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
import type { InputOption } from './shared/types.js';
|
|
3
|
+
import { refreshPaths } from './shared/refresh.js';
|
|
4
|
+
export type { InputOption };
|
|
5
|
+
export { refreshPaths };
|
|
6
|
+
export interface JsbundlingOptions {
|
|
7
|
+
input?: InputOption;
|
|
8
|
+
sourceDir?: string;
|
|
9
|
+
/** Directory where Propshaft/Sprockets picks up entry files for Rails helpers.
|
|
10
|
+
* Default: 'app/assets/builds' */
|
|
11
|
+
assetPipelineDir?: string;
|
|
12
|
+
/** Public directory for the full Vite build output (chunks, assets).
|
|
13
|
+
* Default: 'public/assets' */
|
|
14
|
+
outputDir?: string;
|
|
15
|
+
/** SSR entry point for Inertia server-side rendering.
|
|
16
|
+
* String is a path relative to sourceDir (e.g., 'ssr/ssr.tsx').
|
|
17
|
+
* Object form allows customizing the output directory. */
|
|
18
|
+
ssr?: string | {
|
|
19
|
+
entry: string;
|
|
20
|
+
outDir?: string;
|
|
21
|
+
};
|
|
22
|
+
refresh?: boolean | string | string[];
|
|
23
|
+
/** Path to write dev server metadata JSON (default: 'tmp/rails-vite.json').
|
|
24
|
+
* Set to false to disable. Useful as a bridge for progressive upgrade to the rails_vite gem. */
|
|
25
|
+
devMetaFile?: string | false;
|
|
26
|
+
}
|
|
27
|
+
export default function jsbundling(options?: JsbundlingOptions): Plugin;
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import picomatch from 'picomatch';
|
|
4
|
+
import { loadEnv, defaultAllowedOrigins, } from 'vite';
|
|
5
|
+
import { resolveEntries, entriesToRollupInput, prefixWithSourceDir, detectEntrypointsDir, discoverEntrypointInputs, detectEntrypoint, isEntrypointFile } from './shared/entries.js';
|
|
6
|
+
import { resolveAlias } from './shared/alias.js';
|
|
7
|
+
import { resolveDevServerUrl, isAddressInfo } from './shared/dev-server.js';
|
|
8
|
+
import { ensureCommandShouldRunInEnvironment } from './shared/env-guard.js';
|
|
9
|
+
import { refreshPaths, resolveRefreshPaths } from './shared/refresh.js';
|
|
10
|
+
import { cssExtensions } from './shared/css.js';
|
|
11
|
+
import { readDevServerIndexHtml } from './shared/dev-server-page.js';
|
|
12
|
+
import { resolveNoExternal } from './shared/ssr.js';
|
|
13
|
+
import { bindExitHandler } from './shared/cleanup.js';
|
|
14
|
+
export { refreshPaths };
|
|
15
|
+
const CSS_FACADE_PREFIX = '_css_';
|
|
16
|
+
export default function jsbundling(options = {}) {
|
|
17
|
+
const sourceDir = options.sourceDir ?? 'app/javascript';
|
|
18
|
+
const epDir = detectEntrypointsDir(sourceDir);
|
|
19
|
+
const input = options.input ?? (epDir ? discoverEntrypointInputs(sourceDir, epDir) : detectEntrypoint(sourceDir));
|
|
20
|
+
const assetPipelineDir = options.assetPipelineDir ?? 'app/assets/builds';
|
|
21
|
+
const outputDir = options.outputDir ?? 'public/assets';
|
|
22
|
+
// The URL prefix matching outputDir under public/ — used as Vite's `base` in production
|
|
23
|
+
// so that dynamic import() URLs resolve to the right place.
|
|
24
|
+
const buildBase = '/' + path.relative('public', outputDir) + '/';
|
|
25
|
+
const devMetaPath = options.devMetaFile !== false
|
|
26
|
+
? (options.devMetaFile ?? path.join('tmp', 'rails-vite.json'))
|
|
27
|
+
: null;
|
|
28
|
+
const entries = resolveEntries(input, sourceDir);
|
|
29
|
+
const rollupInput = entriesToRollupInput(entries, typeof options.input === 'object' && !Array.isArray(options.input));
|
|
30
|
+
const ssrConfig = resolveSsrConfig(options.ssr, sourceDir, outputDir);
|
|
31
|
+
let resolvedConfig;
|
|
32
|
+
let reactRefresh = false;
|
|
33
|
+
// Track stubs written in dev so we can clean them up
|
|
34
|
+
const writtenStubs = [];
|
|
35
|
+
return {
|
|
36
|
+
name: 'rails-vite-jsbundling',
|
|
37
|
+
enforce: 'post',
|
|
38
|
+
config(userConfig, { command, mode, isSsrBuild }) {
|
|
39
|
+
const env = loadEnv(mode, userConfig.envDir || process.cwd(), '');
|
|
40
|
+
ensureCommandShouldRunInEnvironment(command, env, 'rails-vite-plugin/jsbundling');
|
|
41
|
+
// SSR builds get minimal config — just entry, outDir, base, and alias.
|
|
42
|
+
// No asset pipeline stubs or client-specific settings.
|
|
43
|
+
if (isSsrBuild) {
|
|
44
|
+
return {
|
|
45
|
+
base: userConfig.base ?? (command === 'build' ? buildBase : ''),
|
|
46
|
+
publicDir: userConfig.publicDir ?? false,
|
|
47
|
+
...(ssrConfig ? {
|
|
48
|
+
build: {
|
|
49
|
+
ssrManifest: userConfig.build?.ssrManifest ?? 'ssr-manifest.json',
|
|
50
|
+
outDir: userConfig.build?.outDir ?? ssrConfig.outDir,
|
|
51
|
+
rollupOptions: {
|
|
52
|
+
input: userConfig.build?.rollupOptions?.input ?? ssrConfig.entry,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
} : {}),
|
|
56
|
+
resolve: {
|
|
57
|
+
alias: resolveAlias(userConfig.resolve?.alias, sourceDir),
|
|
58
|
+
},
|
|
59
|
+
ssr: {
|
|
60
|
+
noExternal: resolveNoExternal(userConfig),
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
// In dev mode, write placeholder stubs immediately so Propshaft/Sprockets
|
|
65
|
+
// can discover them when Rails boots (both may start concurrently via bin/dev).
|
|
66
|
+
if (command === 'serve') {
|
|
67
|
+
writePlaceholderStubs(entries, assetPipelineDir, writtenStubs);
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
base: userConfig.base ?? (command === 'build' ? buildBase : ''),
|
|
71
|
+
publicDir: userConfig.publicDir ?? false,
|
|
72
|
+
build: {
|
|
73
|
+
manifest: userConfig.build?.manifest ?? false,
|
|
74
|
+
outDir: userConfig.build?.outDir ?? outputDir,
|
|
75
|
+
rollupOptions: {
|
|
76
|
+
input: userConfig.build?.rollupOptions?.input ?? rollupInput,
|
|
77
|
+
output: {
|
|
78
|
+
entryFileNames: (chunkInfo) => {
|
|
79
|
+
if (chunkInfo.facadeModuleId && cssExtensions.test(chunkInfo.facadeModuleId)) {
|
|
80
|
+
return `${CSS_FACADE_PREFIX}[name].js`;
|
|
81
|
+
}
|
|
82
|
+
return '[name].js';
|
|
83
|
+
},
|
|
84
|
+
chunkFileNames: '[name]-[hash].js',
|
|
85
|
+
assetFileNames: '[name][extname]',
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
assetsInlineLimit: userConfig.build?.assetsInlineLimit ?? 0,
|
|
89
|
+
cssCodeSplit: true,
|
|
90
|
+
},
|
|
91
|
+
server: {
|
|
92
|
+
cors: userConfig.server?.cors ?? {
|
|
93
|
+
origin: userConfig.server?.origin ?? defaultAllowedOrigins,
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
resolve: {
|
|
97
|
+
alias: resolveAlias(userConfig.resolve?.alias, sourceDir),
|
|
98
|
+
},
|
|
99
|
+
ssr: {
|
|
100
|
+
noExternal: resolveNoExternal(userConfig),
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
},
|
|
104
|
+
configResolved(config) {
|
|
105
|
+
resolvedConfig = config;
|
|
106
|
+
reactRefresh = config.plugins.some((p) => p.name === 'vite:react-babel' || p.name === 'vite:react-swc');
|
|
107
|
+
},
|
|
108
|
+
generateBundle(_, bundle) {
|
|
109
|
+
// Rollup wraps CSS entries in empty JS facade chunks. Delete them —
|
|
110
|
+
// the real CSS is emitted as an asset by Vite's CSS pipeline.
|
|
111
|
+
for (const fileName of Object.keys(bundle)) {
|
|
112
|
+
if (fileName.startsWith(CSS_FACADE_PREFIX)) {
|
|
113
|
+
delete bundle[fileName];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
writeBundle(_, bundle) {
|
|
118
|
+
// SSR bundles are Node.js server code — not served to browsers.
|
|
119
|
+
if (resolvedConfig.build.ssr)
|
|
120
|
+
return;
|
|
121
|
+
// Copy entry files (JS + CSS) to the asset pipeline directory
|
|
122
|
+
// so Propshaft/Sprockets can serve them via Rails helpers.
|
|
123
|
+
// Chunks stay in outputDir and are served directly by the web server.
|
|
124
|
+
fs.mkdirSync(assetPipelineDir, { recursive: true });
|
|
125
|
+
const outDir = resolvedConfig.build.outDir;
|
|
126
|
+
// Only copy CSS files that correspond to entries (entry CSS or CSS extracted
|
|
127
|
+
// from JS entries). Shared chunk CSS stays in outputDir.
|
|
128
|
+
const entryNames = new Set(entries.map(e => e.name));
|
|
129
|
+
for (const [fileName, chunk] of Object.entries(bundle)) {
|
|
130
|
+
const isEntryJs = chunk.type === 'chunk' && chunk.isEntry;
|
|
131
|
+
const isEntryCss = chunk.type === 'asset' && cssExtensions.test(fileName)
|
|
132
|
+
&& entryNames.has(fileName.replace(/\.[^.]+$/, ''));
|
|
133
|
+
if (isEntryJs || isEntryCss) {
|
|
134
|
+
const src = path.join(outDir, fileName);
|
|
135
|
+
const dest = path.join(assetPipelineDir, fileName);
|
|
136
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
137
|
+
fs.copyFileSync(src, dest);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
configureServer(server) {
|
|
142
|
+
let devServerUrl = null;
|
|
143
|
+
let syncTimer = null;
|
|
144
|
+
// Re-discover entries from the entrypoints dir and regenerate all stubs.
|
|
145
|
+
// Called on initial listen and whenever entrypoint files are added/removed.
|
|
146
|
+
const syncStubs = () => {
|
|
147
|
+
if (!devServerUrl)
|
|
148
|
+
return;
|
|
149
|
+
const freshInput = epDir ? discoverEntrypointInputs(sourceDir, epDir) : input;
|
|
150
|
+
const freshEntries = resolveEntries(freshInput, sourceDir);
|
|
151
|
+
// Clear old stubs
|
|
152
|
+
for (const stub of writtenStubs) {
|
|
153
|
+
fs.rmSync(stub, { force: true });
|
|
154
|
+
}
|
|
155
|
+
writtenStubs.length = 0;
|
|
156
|
+
writeDevStubs(freshEntries, assetPipelineDir, devServerUrl, reactRefresh, writtenStubs);
|
|
157
|
+
};
|
|
158
|
+
// Debounced version for watcher events — collapses burst operations
|
|
159
|
+
// (e.g., pasting multiple files, git checkout) into a single rescan.
|
|
160
|
+
const debouncedSyncStubs = () => {
|
|
161
|
+
if (syncTimer)
|
|
162
|
+
clearTimeout(syncTimer);
|
|
163
|
+
syncTimer = setTimeout(syncStubs, 100);
|
|
164
|
+
};
|
|
165
|
+
server.httpServer?.once('listening', () => {
|
|
166
|
+
const address = server.httpServer?.address();
|
|
167
|
+
if (isAddressInfo(address)) {
|
|
168
|
+
devServerUrl = resolveDevServerUrl(address, resolvedConfig);
|
|
169
|
+
syncStubs();
|
|
170
|
+
// Write dev meta file for progressive upgrade to the rails_vite gem
|
|
171
|
+
if (devMetaPath) {
|
|
172
|
+
const meta = { url: devServerUrl, sourceDir };
|
|
173
|
+
if (epDir)
|
|
174
|
+
meta.entrypointsDir = epDir;
|
|
175
|
+
if (reactRefresh)
|
|
176
|
+
meta.reactRefresh = true;
|
|
177
|
+
meta.jsbundling = true;
|
|
178
|
+
fs.mkdirSync(path.dirname(devMetaPath), { recursive: true });
|
|
179
|
+
fs.writeFileSync(devMetaPath, JSON.stringify(meta));
|
|
180
|
+
}
|
|
181
|
+
// Watch entrypoints dir for new/removed files and regenerate stubs
|
|
182
|
+
if (epDir) {
|
|
183
|
+
const epAbsDir = path.resolve(sourceDir, epDir);
|
|
184
|
+
server.watcher.add(epAbsDir);
|
|
185
|
+
const epAbsDirPrefix = epAbsDir + '/';
|
|
186
|
+
server.watcher.on('add', (filePath) => {
|
|
187
|
+
if (filePath.startsWith(epAbsDirPrefix) && isEntrypointFile(filePath)) {
|
|
188
|
+
server.config.logger.info(` RAILS new entrypoint: ${path.relative(sourceDir, filePath)}`);
|
|
189
|
+
debouncedSyncStubs();
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
server.watcher.on('unlink', (filePath) => {
|
|
193
|
+
if (filePath.startsWith(epAbsDirPrefix) && isEntrypointFile(filePath)) {
|
|
194
|
+
server.config.logger.info(` RAILS removed entrypoint: ${path.relative(sourceDir, filePath)}`);
|
|
195
|
+
debouncedSyncStubs();
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
server.config.logger.info(`\n RAILS rails-vite-plugin/jsbundling → ${assetPipelineDir}`);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
bindExitHandler(() => {
|
|
203
|
+
for (const stub of writtenStubs) {
|
|
204
|
+
fs.rmSync(stub, { force: true });
|
|
205
|
+
}
|
|
206
|
+
writtenStubs.length = 0;
|
|
207
|
+
if (devMetaPath) {
|
|
208
|
+
fs.rmSync(devMetaPath, { force: true });
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
// Watch view templates for full-page reload
|
|
212
|
+
const resolvedRefreshPaths = resolveRefreshPaths(options.refresh);
|
|
213
|
+
if (resolvedRefreshPaths.length) {
|
|
214
|
+
const match = picomatch(resolvedRefreshPaths);
|
|
215
|
+
server.watcher.add(resolvedRefreshPaths);
|
|
216
|
+
server.watcher.on('change', (filePath) => {
|
|
217
|
+
const relativePath = path.relative(process.cwd(), filePath);
|
|
218
|
+
if (match(relativePath)) {
|
|
219
|
+
server.hot.send({ type: 'full-reload', path: '*' });
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
const devServerIndexHtml = readDevServerIndexHtml();
|
|
224
|
+
return () => server.middlewares.use((req, res, next) => {
|
|
225
|
+
if (req.url === '/index.html') {
|
|
226
|
+
res.statusCode = 404;
|
|
227
|
+
res.setHeader('Content-Type', 'text/html');
|
|
228
|
+
res.end(devServerIndexHtml);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
next();
|
|
232
|
+
});
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function resolveSsrConfig(ssr, sourceDir, outputDir) {
|
|
237
|
+
if (!ssr)
|
|
238
|
+
return null;
|
|
239
|
+
const entry = typeof ssr === 'string' ? ssr : ssr.entry;
|
|
240
|
+
const outDir = (typeof ssr === 'object' ? ssr.outDir : undefined) ?? outputDir + '-ssr';
|
|
241
|
+
return {
|
|
242
|
+
entry: prefixWithSourceDir(entry, sourceDir),
|
|
243
|
+
outDir,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Write placeholder stubs so Propshaft/Sprockets can discover asset files
|
|
248
|
+
* at boot time, before the Vite dev server is ready with its URL.
|
|
249
|
+
* Writes unconditionally — stubs are overwritten by writeDevStubs once the server is listening.
|
|
250
|
+
*/
|
|
251
|
+
function writePlaceholderStubs(entries, outDir, writtenStubs) {
|
|
252
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
253
|
+
const seen = new Set();
|
|
254
|
+
for (const { name } of entries) {
|
|
255
|
+
if (seen.has(name))
|
|
256
|
+
continue;
|
|
257
|
+
seen.add(name);
|
|
258
|
+
const jsStub = path.join(outDir, `${name}.js`);
|
|
259
|
+
fs.mkdirSync(path.dirname(jsStub), { recursive: true });
|
|
260
|
+
fs.writeFileSync(jsStub, '// rails-vite placeholder – loading…\n');
|
|
261
|
+
writtenStubs.push(jsStub);
|
|
262
|
+
const cssStub = path.join(outDir, `${name}.css`);
|
|
263
|
+
fs.writeFileSync(cssStub, '/* rails-vite placeholder – loading… */\n');
|
|
264
|
+
writtenStubs.push(cssStub);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function writeDevStubs(entries, outDir, devServerUrl, reactRefresh, writtenStubs) {
|
|
268
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
269
|
+
const importLine = (url) => reactRefresh ? `await import("${url}");` : `import "${url}";`;
|
|
270
|
+
const jsEntries = entries.filter(e => !cssExtensions.test(e.sourcePath));
|
|
271
|
+
const cssEntries = entries.filter(e => cssExtensions.test(e.sourcePath));
|
|
272
|
+
// JS stubs: import @vite/client + all CSS entries (for HMR) + the JS entry itself.
|
|
273
|
+
for (const { name, sourcePath } of jsEntries) {
|
|
274
|
+
const stubPath = path.join(outDir, `${name}.js`);
|
|
275
|
+
fs.mkdirSync(path.dirname(stubPath), { recursive: true });
|
|
276
|
+
const lines = ['// rails-vite dev stub – DO NOT EDIT'];
|
|
277
|
+
if (reactRefresh) {
|
|
278
|
+
// Static import for the refresh runtime — just exports a function, safe to hoist.
|
|
279
|
+
// Everything else uses dynamic import() so the preamble executes first
|
|
280
|
+
// (static imports hoist, so injectIntoGlobalHook would run after all modules).
|
|
281
|
+
lines.push(`import { injectIntoGlobalHook } from "${devServerUrl}/@react-refresh";`);
|
|
282
|
+
lines.push('injectIntoGlobalHook(window);');
|
|
283
|
+
lines.push('window.$RefreshReg$ = () => {};');
|
|
284
|
+
lines.push('window.$RefreshSig$ = () => (type) => type;');
|
|
285
|
+
}
|
|
286
|
+
lines.push(importLine(`${devServerUrl}/@vite/client`));
|
|
287
|
+
// Import CSS entries so Vite's module graph tracks them for HMR
|
|
288
|
+
for (const { sourcePath: cssPath } of cssEntries) {
|
|
289
|
+
lines.push(importLine(`${devServerUrl}/${cssPath}`));
|
|
290
|
+
}
|
|
291
|
+
lines.push(importLine(`${devServerUrl}/${sourcePath}`));
|
|
292
|
+
fs.writeFileSync(stubPath, lines.join('\n') + '\n');
|
|
293
|
+
writtenStubs.push(stubPath);
|
|
294
|
+
}
|
|
295
|
+
// Empty CSS stubs so stylesheet_link_tag doesn't error.
|
|
296
|
+
// Actual CSS is injected by @vite/client through the JS imports above.
|
|
297
|
+
const seen = new Set();
|
|
298
|
+
for (const { name } of entries) {
|
|
299
|
+
if (seen.has(name))
|
|
300
|
+
continue;
|
|
301
|
+
seen.add(name);
|
|
302
|
+
const cssStubPath = path.join(outDir, `${name}.css`);
|
|
303
|
+
fs.mkdirSync(path.dirname(cssStubPath), { recursive: true });
|
|
304
|
+
fs.writeFileSync(cssStubPath, '/* rails-vite dev stub */\n');
|
|
305
|
+
writtenStubs.push(cssStubPath);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
export function resolveAlias(userAlias, sourceDir) {
|
|
3
|
+
const atReplacement = path.resolve(process.cwd(), sourceDir);
|
|
4
|
+
if (Array.isArray(userAlias)) {
|
|
5
|
+
const hasAt = userAlias.some(a => a.find === '@');
|
|
6
|
+
return hasAt ? userAlias : [...userAlias, { find: '@', replacement: atReplacement }];
|
|
7
|
+
}
|
|
8
|
+
return {
|
|
9
|
+
'@': atReplacement,
|
|
10
|
+
...userAlias,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
let exitHandlersBound = false;
|
|
2
|
+
/**
|
|
3
|
+
* Register a cleanup function that runs on process exit.
|
|
4
|
+
* Signal handlers are only registered once per process to avoid duplicates.
|
|
5
|
+
*/
|
|
6
|
+
export function bindExitHandler(cleanupFn) {
|
|
7
|
+
process.on('exit', cleanupFn);
|
|
8
|
+
if (!exitHandlersBound) {
|
|
9
|
+
process.on('SIGINT', () => process.exit());
|
|
10
|
+
process.on('SIGTERM', () => process.exit());
|
|
11
|
+
process.on('SIGHUP', () => process.exit());
|
|
12
|
+
exitHandlersBound = true;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function readDevServerIndexHtml(): string;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { AddressInfo } from 'net';
|
|
2
|
+
import type { ResolvedConfig } from 'vite';
|
|
3
|
+
import type { DevServerUrl } from './types.js';
|
|
4
|
+
export declare function resolveDevServerUrl(address: AddressInfo, config: ResolvedConfig): DevServerUrl;
|
|
5
|
+
export declare function isAddressInfo(x: string | AddressInfo | null | undefined): x is AddressInfo;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function resolveDevServerUrl(address, config) {
|
|
2
|
+
const hmr = typeof config.server.hmr === 'object' ? config.server.hmr : null;
|
|
3
|
+
const clientProtocol = hmr?.protocol
|
|
4
|
+
? hmr.protocol === 'wss'
|
|
5
|
+
? 'https'
|
|
6
|
+
: 'http'
|
|
7
|
+
: null;
|
|
8
|
+
const serverProtocol = config.server.https ? 'https' : 'http';
|
|
9
|
+
const protocol = clientProtocol ?? serverProtocol;
|
|
10
|
+
const configHost = typeof config.server.host === 'string' ? config.server.host : null;
|
|
11
|
+
const serverAddress = address.family === 'IPv6' || address.family === 6
|
|
12
|
+
? `[${address.address}]`
|
|
13
|
+
: address.address;
|
|
14
|
+
const host = hmr?.host ?? configHost ?? serverAddress;
|
|
15
|
+
const port = hmr?.clientPort ?? address.port;
|
|
16
|
+
return `${protocol}://${host}:${port}`;
|
|
17
|
+
}
|
|
18
|
+
export function isAddressInfo(x) {
|
|
19
|
+
return typeof x === 'object' && x !== null;
|
|
20
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { InputOption, ResolvedEntry } from './types.js';
|
|
2
|
+
export declare function isEntrypointFile(filePath: string): boolean;
|
|
3
|
+
export declare function resolveEntries(input: InputOption, sourceDir: string): ResolvedEntry[];
|
|
4
|
+
/**
|
|
5
|
+
* Convert resolved entries to Rollup-compatible input.
|
|
6
|
+
* Array form for string/array inputs; object form preserves explicit user names.
|
|
7
|
+
*/
|
|
8
|
+
export declare function entriesToRollupInput(entries: ResolvedEntry[], isNamedInput: boolean): InputOption;
|
|
9
|
+
export declare function resolveInput(input: InputOption, sourceDir: string): InputOption;
|
|
10
|
+
export declare function prefixWithSourceDir(entry: string, sourceDir: string): string;
|
|
11
|
+
export declare function detectEntrypointsDir(sourceDir: string): string | null;
|
|
12
|
+
export declare function discoverEntrypointInputs(sourceDir: string, entrypointsDir: string): string[];
|
|
13
|
+
export declare function detectEntrypoint(sourceDir: string): string;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { cssExtensionList } from './css.js';
|
|
4
|
+
const jsExtensions = ['mjs', 'js', 'mts', 'ts', 'jsx', 'tsx'];
|
|
5
|
+
const entrypointExtensions = new RegExp(`\\.(${[...jsExtensions, ...cssExtensionList].join('|')})$`);
|
|
6
|
+
export function isEntrypointFile(filePath) {
|
|
7
|
+
return entrypointExtensions.test(filePath);
|
|
8
|
+
}
|
|
9
|
+
export function resolveEntries(input, sourceDir) {
|
|
10
|
+
if (typeof input === 'object' && !Array.isArray(input)) {
|
|
11
|
+
return Object.entries(input).map(([name, value]) => ({
|
|
12
|
+
name,
|
|
13
|
+
sourcePath: prefixWithSourceDir(value, sourceDir),
|
|
14
|
+
}));
|
|
15
|
+
}
|
|
16
|
+
const inputs = Array.isArray(input) ? input : [input];
|
|
17
|
+
const sourcePaths = inputs.map(entry => prefixWithSourceDir(entry, sourceDir));
|
|
18
|
+
const commonPrefix = detectCommonEntryPrefix(sourcePaths, sourceDir);
|
|
19
|
+
return sourcePaths.map(sourcePath => ({
|
|
20
|
+
name: entrypointName(sourcePath, sourceDir, commonPrefix),
|
|
21
|
+
sourcePath,
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Convert resolved entries to Rollup-compatible input.
|
|
26
|
+
* Array form for string/array inputs; object form preserves explicit user names.
|
|
27
|
+
*/
|
|
28
|
+
export function entriesToRollupInput(entries, isNamedInput) {
|
|
29
|
+
if (isNamedInput) {
|
|
30
|
+
return Object.fromEntries(entries.map(e => [e.name, e.sourcePath]));
|
|
31
|
+
}
|
|
32
|
+
if (entries.length === 1)
|
|
33
|
+
return entries[0].sourcePath;
|
|
34
|
+
return entries.map(e => e.sourcePath);
|
|
35
|
+
}
|
|
36
|
+
export function resolveInput(input, sourceDir) {
|
|
37
|
+
if (typeof input === 'object' && !Array.isArray(input)) {
|
|
38
|
+
return Object.fromEntries(Object.entries(input).map(([key, value]) => [key, prefixWithSourceDir(value, sourceDir)]));
|
|
39
|
+
}
|
|
40
|
+
if (Array.isArray(input)) {
|
|
41
|
+
return input.map((entry) => prefixWithSourceDir(entry, sourceDir));
|
|
42
|
+
}
|
|
43
|
+
return prefixWithSourceDir(input, sourceDir);
|
|
44
|
+
}
|
|
45
|
+
export function prefixWithSourceDir(entry, sourceDir) {
|
|
46
|
+
if (entry.startsWith(sourceDir + '/') || entry.startsWith('/')) {
|
|
47
|
+
return entry;
|
|
48
|
+
}
|
|
49
|
+
return `${sourceDir}/${entry}`;
|
|
50
|
+
}
|
|
51
|
+
export function detectEntrypointsDir(sourceDir) {
|
|
52
|
+
const entrypointsDir = path.join(sourceDir, 'entrypoints');
|
|
53
|
+
return fs.existsSync(entrypointsDir) ? 'entrypoints' : null;
|
|
54
|
+
}
|
|
55
|
+
export function discoverEntrypointInputs(sourceDir, entrypointsDir) {
|
|
56
|
+
const absDir = path.join(sourceDir, entrypointsDir);
|
|
57
|
+
return discoverEntrypoints(absDir).map((entry) => `${entrypointsDir}/${entry}`);
|
|
58
|
+
}
|
|
59
|
+
export function detectEntrypoint(sourceDir) {
|
|
60
|
+
for (const ext of ['.js', '.mjs', '.ts', '.mts', '.jsx', '.tsx']) {
|
|
61
|
+
const candidate = path.join(sourceDir, `application${ext}`);
|
|
62
|
+
if (fs.existsSync(candidate)) {
|
|
63
|
+
return `application${ext}`;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return 'application.js';
|
|
67
|
+
}
|
|
68
|
+
function discoverEntrypoints(dir, base = dir) {
|
|
69
|
+
const entries = [];
|
|
70
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
71
|
+
if (entry.isDirectory()) {
|
|
72
|
+
entries.push(...discoverEntrypoints(path.join(dir, entry.name), base));
|
|
73
|
+
}
|
|
74
|
+
else if (entrypointExtensions.test(entry.name)) {
|
|
75
|
+
entries.push(path.relative(base, path.join(dir, entry.name)));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return entries;
|
|
79
|
+
}
|
|
80
|
+
function entrypointName(sourcePath, sourceDir, commonPrefix) {
|
|
81
|
+
let name = sourcePath;
|
|
82
|
+
if (name.startsWith(sourceDir + '/')) {
|
|
83
|
+
name = name.slice(sourceDir.length + 1);
|
|
84
|
+
}
|
|
85
|
+
if (commonPrefix && name.startsWith(commonPrefix + '/')) {
|
|
86
|
+
name = name.slice(commonPrefix.length + 1);
|
|
87
|
+
}
|
|
88
|
+
return name.replace(/\.[^.]+$/, '');
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Detect the common directory prefix among all input entries (after stripping sourceDir).
|
|
92
|
+
* This mirrors how Rollup computes entry chunk names and ensures dev stubs match build output.
|
|
93
|
+
*/
|
|
94
|
+
function detectCommonEntryPrefix(sourcePaths, sourceDir) {
|
|
95
|
+
if (sourcePaths.length === 0)
|
|
96
|
+
return null;
|
|
97
|
+
const dirs = sourcePaths.map((entry) => {
|
|
98
|
+
let name = entry;
|
|
99
|
+
if (name.startsWith(sourceDir + '/')) {
|
|
100
|
+
name = name.slice(sourceDir.length + 1);
|
|
101
|
+
}
|
|
102
|
+
const dir = path.dirname(name);
|
|
103
|
+
return dir === '.' ? '' : dir;
|
|
104
|
+
});
|
|
105
|
+
const first = dirs[0];
|
|
106
|
+
if (!first)
|
|
107
|
+
return null;
|
|
108
|
+
if (dirs.every((d) => d === first || d.startsWith(first + '/'))) {
|
|
109
|
+
return first;
|
|
110
|
+
}
|
|
111
|
+
const parts = first.split('/');
|
|
112
|
+
let common = '';
|
|
113
|
+
for (const part of parts) {
|
|
114
|
+
const candidate = common ? `${common}/${part}` : part;
|
|
115
|
+
if (dirs.every((d) => d === candidate || d.startsWith(candidate + '/'))) {
|
|
116
|
+
common = candidate;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return common || null;
|
|
123
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function ensureCommandShouldRunInEnvironment(command: string, env: Record<string, string>, pluginName: string): void;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function ensureCommandShouldRunInEnvironment(command, env, pluginName) {
|
|
2
|
+
if (command === 'build') {
|
|
3
|
+
return;
|
|
4
|
+
}
|
|
5
|
+
if (env.CI !== undefined) {
|
|
6
|
+
throw new Error(`${pluginName}: You should not run the Vite dev server in CI. ` +
|
|
7
|
+
'Run the build command instead.');
|
|
8
|
+
}
|
|
9
|
+
if (env.RAILS_ENV === 'production') {
|
|
10
|
+
throw new Error(`${pluginName}: You should not run the Vite dev server in production. ` +
|
|
11
|
+
'Run the build command instead.');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const refreshPaths = [
|
|
2
|
+
'app/views/**/*.{erb,slim,haml}',
|
|
3
|
+
'app/helpers/**/*.rb',
|
|
4
|
+
];
|
|
5
|
+
export function resolveRefreshPaths(refresh) {
|
|
6
|
+
if (refresh === false)
|
|
7
|
+
return [];
|
|
8
|
+
if (!refresh || refresh === true)
|
|
9
|
+
return refreshPaths;
|
|
10
|
+
if (typeof refresh === 'string')
|
|
11
|
+
return [refresh];
|
|
12
|
+
return refresh;
|
|
13
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function resolveNoExternal(config) {
|
|
2
|
+
const userNoExternal = config.ssr?.noExternal;
|
|
3
|
+
const pluginNoExternal = ['rails-vite-plugin'];
|
|
4
|
+
if (userNoExternal === true) {
|
|
5
|
+
return true;
|
|
6
|
+
}
|
|
7
|
+
if (userNoExternal === undefined) {
|
|
8
|
+
return pluginNoExternal;
|
|
9
|
+
}
|
|
10
|
+
return [
|
|
11
|
+
...(Array.isArray(userNoExternal) ? userNoExternal : [userNoExternal]),
|
|
12
|
+
...pluginNoExternal,
|
|
13
|
+
];
|
|
14
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type InputOption = string | string[] | Record<string, string>;
|
|
2
|
+
export type DevServerUrl = `${'http' | 'https'}://${string}:${number}`;
|
|
3
|
+
export interface ResolvedEntry {
|
|
4
|
+
/** Output name (e.g., 'application', 'admin/index') */
|
|
5
|
+
name: string;
|
|
6
|
+
/** Full path relative to project root */
|
|
7
|
+
sourcePath: string;
|
|
8
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rails-vite-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Vite plugin for Rails integration",
|
|
5
5
|
"author": "Svyatoslav Kryukov <me@skryukov.dev>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
".": {
|
|
12
12
|
"import": "./dist/index.js",
|
|
13
13
|
"types": "./dist/index.d.ts"
|
|
14
|
+
},
|
|
15
|
+
"./jsbundling": {
|
|
16
|
+
"import": "./dist/jsbundling.js",
|
|
17
|
+
"types": "./dist/jsbundling.d.ts"
|
|
14
18
|
}
|
|
15
19
|
},
|
|
16
20
|
"files": [
|
|
@@ -43,6 +47,8 @@
|
|
|
43
47
|
"keywords": [
|
|
44
48
|
"vite",
|
|
45
49
|
"rails",
|
|
50
|
+
"jsbundling",
|
|
51
|
+
"jsbundling-rails",
|
|
46
52
|
"vite-plugin"
|
|
47
53
|
],
|
|
48
54
|
"homepage": "https://github.com/skryukov/rails_vite",
|