rails-vite-plugin 0.1.0-beta.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.
- package/dev-server-index.html +6 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +205 -0
- package/package.json +58 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
export interface RailsViteOptions {
|
|
3
|
+
input?: string | string[];
|
|
4
|
+
sourceDir?: string;
|
|
5
|
+
ssr?: string;
|
|
6
|
+
ssrOutputDirectory?: string;
|
|
7
|
+
devMetaFile?: string;
|
|
8
|
+
buildDirectory?: string;
|
|
9
|
+
publicDirectory?: string;
|
|
10
|
+
refresh?: boolean | string | string[];
|
|
11
|
+
}
|
|
12
|
+
export default function rails(options?: RailsViteOptions): Plugin;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import picomatch from 'picomatch';
|
|
5
|
+
import { loadEnv, defaultAllowedOrigins, } from 'vite';
|
|
6
|
+
const defaultRefreshPaths = [
|
|
7
|
+
'app/views/**/*.{erb,slim,haml}',
|
|
8
|
+
'app/helpers/**/*.rb',
|
|
9
|
+
];
|
|
10
|
+
let exitHandlersBound = false;
|
|
11
|
+
export default function rails(options = {}) {
|
|
12
|
+
const sourceDir = options.sourceDir ?? 'app/javascript';
|
|
13
|
+
const input = options.input ?? detectEntrypoint(sourceDir);
|
|
14
|
+
const publicDirectory = options.publicDirectory ?? 'public';
|
|
15
|
+
const buildDirectory = options.buildDirectory ?? 'vite';
|
|
16
|
+
const devMetaPath = options.devMetaFile ?? path.join('tmp', 'rails-vite.json');
|
|
17
|
+
const ssrOutputDirectory = options.ssrOutputDirectory ?? 'ssr';
|
|
18
|
+
const resolvedInput = resolveInput(input, sourceDir);
|
|
19
|
+
const resolvedSsr = options.ssr ? resolveInput(options.ssr, sourceDir) : undefined;
|
|
20
|
+
let resolvedConfig;
|
|
21
|
+
let devServerUrl;
|
|
22
|
+
let reactRefresh = false;
|
|
23
|
+
return {
|
|
24
|
+
name: 'rails-vite',
|
|
25
|
+
enforce: 'post',
|
|
26
|
+
config(userConfig, { command, mode }) {
|
|
27
|
+
const env = loadEnv(mode, userConfig.envDir || process.cwd(), '');
|
|
28
|
+
const ssr = !!userConfig.build?.ssr;
|
|
29
|
+
ensureCommandShouldRunInEnvironment(command, env);
|
|
30
|
+
return {
|
|
31
|
+
base: userConfig.base ?? (command === 'build' ? `/${buildDirectory}/` : ''),
|
|
32
|
+
publicDir: userConfig.publicDir ?? false,
|
|
33
|
+
build: {
|
|
34
|
+
manifest: userConfig.build?.manifest ?? (ssr ? false : 'manifest.json'),
|
|
35
|
+
ssrManifest: userConfig.build?.ssrManifest ?? (ssr ? 'ssr-manifest.json' : false),
|
|
36
|
+
outDir: userConfig.build?.outDir ?? (ssr ? ssrOutputDirectory : path.join(publicDirectory, buildDirectory)),
|
|
37
|
+
rollupOptions: {
|
|
38
|
+
input: userConfig.build?.rollupOptions?.input ?? (ssr ? resolvedSsr : resolvedInput),
|
|
39
|
+
},
|
|
40
|
+
assetsInlineLimit: userConfig.build?.assetsInlineLimit ?? 0,
|
|
41
|
+
},
|
|
42
|
+
server: {
|
|
43
|
+
cors: userConfig.server?.cors ?? {
|
|
44
|
+
origin: userConfig.server?.origin ?? defaultAllowedOrigins,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
resolve: {
|
|
48
|
+
alias: Array.isArray(userConfig.resolve?.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
|
+
},
|
|
57
|
+
},
|
|
58
|
+
ssr: {
|
|
59
|
+
noExternal: resolveNoExternal(userConfig),
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
configResolved(config) {
|
|
64
|
+
resolvedConfig = config;
|
|
65
|
+
reactRefresh = config.plugins.some((p) => p.name === 'vite:react-babel' || p.name === 'vite:react-swc');
|
|
66
|
+
},
|
|
67
|
+
writeBundle() {
|
|
68
|
+
if (resolvedConfig.build.ssr)
|
|
69
|
+
return;
|
|
70
|
+
const outDir = resolvedConfig.build.outDir;
|
|
71
|
+
fs.writeFileSync(path.join(outDir, 'rails-vite.json'), JSON.stringify({ sourceDir }));
|
|
72
|
+
},
|
|
73
|
+
configureServer(server) {
|
|
74
|
+
server.httpServer?.once('listening', () => {
|
|
75
|
+
const address = server.httpServer?.address();
|
|
76
|
+
if (isAddressInfo(address)) {
|
|
77
|
+
devServerUrl = resolveDevServerUrl(address, resolvedConfig);
|
|
78
|
+
resolvedConfig.server.origin = devServerUrl;
|
|
79
|
+
const meta = { url: devServerUrl, sourceDir };
|
|
80
|
+
if (reactRefresh)
|
|
81
|
+
meta.reactRefresh = true;
|
|
82
|
+
fs.writeFileSync(devMetaPath, JSON.stringify(meta));
|
|
83
|
+
setTimeout(() => {
|
|
84
|
+
server.config.logger.info(`\n RAILS rails-vite-plugin`);
|
|
85
|
+
}, 100);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
if (!exitHandlersBound) {
|
|
89
|
+
const clean = () => {
|
|
90
|
+
fs.rmSync(devMetaPath, { force: true });
|
|
91
|
+
};
|
|
92
|
+
process.on('exit', clean);
|
|
93
|
+
process.on('SIGINT', () => process.exit());
|
|
94
|
+
process.on('SIGTERM', () => process.exit());
|
|
95
|
+
process.on('SIGHUP', () => process.exit());
|
|
96
|
+
exitHandlersBound = true;
|
|
97
|
+
}
|
|
98
|
+
// 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);
|
|
103
|
+
server.watcher.on('change', (filePath) => {
|
|
104
|
+
const relativePath = path.relative(process.cwd(), filePath);
|
|
105
|
+
if (match(relativePath)) {
|
|
106
|
+
server.ws.send({ type: 'full-reload', path: '*' });
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
// Serve a helpful page at the dev server root
|
|
111
|
+
const devServerIndexHtml = fs.readFileSync(path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'dev-server-index.html'), 'utf-8');
|
|
112
|
+
return () => server.middlewares.use((req, res, next) => {
|
|
113
|
+
if (req.url === '/index.html') {
|
|
114
|
+
res.statusCode = 404;
|
|
115
|
+
res.end(devServerIndexHtml);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
next();
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function resolveInput(input, sourceDir) {
|
|
124
|
+
if (Array.isArray(input)) {
|
|
125
|
+
return input.map((entry) => prefixWithSourceDir(entry, sourceDir));
|
|
126
|
+
}
|
|
127
|
+
return prefixWithSourceDir(input, sourceDir);
|
|
128
|
+
}
|
|
129
|
+
function prefixWithSourceDir(entry, sourceDir) {
|
|
130
|
+
if (entry.startsWith(sourceDir + '/') || entry.startsWith('/')) {
|
|
131
|
+
return entry;
|
|
132
|
+
}
|
|
133
|
+
return `${sourceDir}/${entry}`;
|
|
134
|
+
}
|
|
135
|
+
const defaultEntryExtensions = ['.js', '.ts', '.jsx', '.tsx'];
|
|
136
|
+
function detectEntrypoint(sourceDir) {
|
|
137
|
+
for (const ext of defaultEntryExtensions) {
|
|
138
|
+
const candidate = path.join(sourceDir, `application${ext}`);
|
|
139
|
+
if (fs.existsSync(candidate)) {
|
|
140
|
+
return `application${ext}`;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return 'application.js';
|
|
144
|
+
}
|
|
145
|
+
function resolveRefreshPaths(refresh) {
|
|
146
|
+
if (refresh === false || refresh === undefined) {
|
|
147
|
+
return defaultRefreshPaths;
|
|
148
|
+
}
|
|
149
|
+
if (refresh === true) {
|
|
150
|
+
return defaultRefreshPaths;
|
|
151
|
+
}
|
|
152
|
+
if (typeof refresh === 'string') {
|
|
153
|
+
return [refresh];
|
|
154
|
+
}
|
|
155
|
+
return refresh;
|
|
156
|
+
}
|
|
157
|
+
function resolveDevServerUrl(address, config) {
|
|
158
|
+
const configHmrProtocol = typeof config.server.hmr === 'object' ? config.server.hmr.protocol : null;
|
|
159
|
+
const clientProtocol = configHmrProtocol
|
|
160
|
+
? configHmrProtocol === 'wss'
|
|
161
|
+
? 'https'
|
|
162
|
+
: 'http'
|
|
163
|
+
: null;
|
|
164
|
+
const serverProtocol = config.server.https ? 'https' : 'http';
|
|
165
|
+
const protocol = clientProtocol ?? serverProtocol;
|
|
166
|
+
const configHmrHost = typeof config.server.hmr === 'object' ? config.server.hmr.host : null;
|
|
167
|
+
const configHost = typeof config.server.host === 'string' ? config.server.host : null;
|
|
168
|
+
const serverAddress = address.family === 'IPv6' || address.family === 6
|
|
169
|
+
? `[${address.address}]`
|
|
170
|
+
: 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;
|
|
174
|
+
return `${protocol}://${host}:${port}`;
|
|
175
|
+
}
|
|
176
|
+
function isAddressInfo(x) {
|
|
177
|
+
return typeof x === 'object' && x !== null;
|
|
178
|
+
}
|
|
179
|
+
function ensureCommandShouldRunInEnvironment(command, env) {
|
|
180
|
+
if (command === 'build') {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (env.CI !== undefined) {
|
|
184
|
+
throw new Error('rails-vite-plugin: You should not run the Vite dev server in CI. ' +
|
|
185
|
+
'Run `rake vite:build` instead.');
|
|
186
|
+
}
|
|
187
|
+
if (env.RAILS_ENV === 'production') {
|
|
188
|
+
throw new Error('rails-vite-plugin: You should not run the Vite dev server in production. ' +
|
|
189
|
+
'Run `rake vite:build` instead.');
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function resolveNoExternal(config) {
|
|
193
|
+
const userNoExternal = config.ssr?.noExternal;
|
|
194
|
+
const pluginNoExternal = ['rails-vite-plugin'];
|
|
195
|
+
if (userNoExternal === true) {
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
if (userNoExternal === undefined) {
|
|
199
|
+
return pluginNoExternal;
|
|
200
|
+
}
|
|
201
|
+
return [
|
|
202
|
+
...(Array.isArray(userNoExternal) ? userNoExternal : [userNoExternal]),
|
|
203
|
+
...pluginNoExternal,
|
|
204
|
+
];
|
|
205
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rails-vite-plugin",
|
|
3
|
+
"version": "0.1.0-beta.1",
|
|
4
|
+
"description": "Vite plugin for Rails integration",
|
|
5
|
+
"author": "Svyatoslav Kryukov <me@skryukov.dev>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"dev-server-index.html"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc -p tsconfig.build.json",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"typecheck": "tsc --noEmit",
|
|
24
|
+
"lint": "eslint src/",
|
|
25
|
+
"prepublishOnly": "tsc -p tsconfig.build.json"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"vite": ">=5.0.0"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"picomatch": "^4.0.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@eslint/js": "^10.0.1",
|
|
35
|
+
"@types/node": "^22.0.0",
|
|
36
|
+
"@types/picomatch": "^3.0.0",
|
|
37
|
+
"eslint": "^10.0.2",
|
|
38
|
+
"typescript": "^5.0.0",
|
|
39
|
+
"typescript-eslint": "^8.56.1",
|
|
40
|
+
"vite": "^7.3.1",
|
|
41
|
+
"vitest": "^4.0.18"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"vite",
|
|
45
|
+
"rails",
|
|
46
|
+
"vite-plugin"
|
|
47
|
+
],
|
|
48
|
+
"homepage": "https://github.com/skryukov/rails_vite",
|
|
49
|
+
"bugs": "https://github.com/skryukov/rails_vite/issues",
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "https://github.com/skryukov/rails_vite",
|
|
53
|
+
"directory": "packages/rails-vite-plugin"
|
|
54
|
+
},
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=18"
|
|
57
|
+
}
|
|
58
|
+
}
|