rails-vite-plugin 0.2.0 → 0.2.2
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.js +24 -10
- package/dist/jsbundling.js +40 -18
- package/dist/shared/bundler-compat.d.ts +5 -0
- package/dist/shared/bundler-compat.js +9 -0
- package/dist/shared/dev-server.d.ts +1 -0
- package/dist/shared/dev-server.js +6 -0
- package/dist/shared/types.d.ts +1 -0
- package/dist/shared/types.js +3 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,9 +2,11 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import picomatch from 'picomatch';
|
|
4
4
|
import { loadEnv, defaultAllowedOrigins, } from 'vite';
|
|
5
|
+
import { ORIGIN_PLACEHOLDER } from './shared/types.js';
|
|
5
6
|
import { resolveInput, detectEntrypointsDir, discoverEntrypointInputs, detectEntrypoint } from './shared/entries.js';
|
|
6
7
|
import { resolveAlias } from './shared/alias.js';
|
|
7
|
-
import { resolveDevServerUrl, isAddressInfo } from './shared/dev-server.js';
|
|
8
|
+
import { resolveDevServerUrl, isAddressInfo, replaceOriginPlaceholder } from './shared/dev-server.js';
|
|
9
|
+
import { resolveBundlerOptionsKey, getUserBundlerInput } from './shared/bundler-compat.js';
|
|
8
10
|
import { ensureCommandShouldRunInEnvironment } from './shared/env-guard.js';
|
|
9
11
|
import { refreshPaths, resolveRefreshPaths } from './shared/refresh.js';
|
|
10
12
|
import { readDevServerIndexHtml } from './shared/dev-server-page.js';
|
|
@@ -16,32 +18,39 @@ export default function rails(options = {}) {
|
|
|
16
18
|
const entrypointsDir = options.input === undefined ? detectEntrypointsDir(sourceDir) : null;
|
|
17
19
|
const input = options.input ?? (entrypointsDir ? discoverEntrypointInputs(sourceDir, entrypointsDir) : detectEntrypoint(sourceDir));
|
|
18
20
|
const publicDir = options.publicDir ?? 'public';
|
|
19
|
-
const
|
|
21
|
+
const userBuildDir = options.buildDir;
|
|
20
22
|
const devMetaPath = options.devMetaFile ?? path.join('tmp', 'rails-vite.json');
|
|
21
23
|
const ssrOutDir = options.ssrOutDir ?? 'ssr';
|
|
22
24
|
const resolvedInput = resolveInput(input, sourceDir);
|
|
23
25
|
const resolvedSsr = options.ssr !== undefined ? resolveInput(options.ssr, sourceDir) : undefined;
|
|
24
26
|
let resolvedConfig;
|
|
25
27
|
let reactRefresh = false;
|
|
28
|
+
let devServerUrl = null;
|
|
29
|
+
let effectiveBuildDir;
|
|
26
30
|
return {
|
|
27
31
|
name: 'rails-vite',
|
|
28
32
|
enforce: 'post',
|
|
29
33
|
config(userConfig, { command, mode, isSsrBuild }) {
|
|
30
34
|
const env = loadEnv(mode, userConfig.envDir || process.cwd(), '');
|
|
31
35
|
ensureCommandShouldRunInEnvironment(command, env, 'rails-vite-plugin');
|
|
36
|
+
// @ts-expect-error -- `this.meta.rolldownVersion` exists in Vite 8+
|
|
37
|
+
const bundlerOptionsKey = resolveBundlerOptionsKey(this.meta);
|
|
38
|
+
const userBundlerInput = getUserBundlerInput(userConfig);
|
|
39
|
+
effectiveBuildDir = userBuildDir ?? (mode === 'test' ? 'vite-test' : 'vite');
|
|
32
40
|
return {
|
|
33
|
-
base: userConfig.base ?? (command === 'build' ? `/${
|
|
41
|
+
base: userConfig.base ?? (command === 'build' ? `/${effectiveBuildDir}/` : ''),
|
|
34
42
|
publicDir: userConfig.publicDir ?? false,
|
|
35
43
|
build: {
|
|
36
44
|
manifest: userConfig.build?.manifest ?? (isSsrBuild ? false : 'manifest.json'),
|
|
37
45
|
ssrManifest: userConfig.build?.ssrManifest ?? (isSsrBuild ? 'ssr-manifest.json' : false),
|
|
38
|
-
outDir: userConfig.build?.outDir ?? (isSsrBuild ? ssrOutDir : path.join(publicDir,
|
|
39
|
-
|
|
40
|
-
input:
|
|
46
|
+
outDir: userConfig.build?.outDir ?? (isSsrBuild ? ssrOutDir : path.join(publicDir, effectiveBuildDir)),
|
|
47
|
+
[bundlerOptionsKey]: {
|
|
48
|
+
input: userBundlerInput ?? (isSsrBuild ? resolvedSsr : resolvedInput),
|
|
41
49
|
},
|
|
42
50
|
assetsInlineLimit: userConfig.build?.assetsInlineLimit ?? 0,
|
|
43
51
|
},
|
|
44
52
|
server: {
|
|
53
|
+
origin: command === 'serve' ? (userConfig.server?.origin ?? ORIGIN_PLACEHOLDER) : undefined,
|
|
45
54
|
cors: userConfig.server?.cors ?? {
|
|
46
55
|
origin: userConfig.server?.origin ?? defaultAllowedOrigins,
|
|
47
56
|
},
|
|
@@ -62,15 +71,20 @@ export default function rails(options = {}) {
|
|
|
62
71
|
if (resolvedConfig.build.ssr)
|
|
63
72
|
return;
|
|
64
73
|
const outDir = resolvedConfig.build.outDir;
|
|
65
|
-
|
|
74
|
+
const meta = { sourceDir, buildDir: effectiveBuildDir };
|
|
75
|
+
if (entrypointsDir)
|
|
76
|
+
meta.entrypointsDir = entrypointsDir;
|
|
77
|
+
fs.writeFileSync(path.join(outDir, 'rails-vite.json'), JSON.stringify(meta));
|
|
78
|
+
},
|
|
79
|
+
transform(code) {
|
|
80
|
+
return replaceOriginPlaceholder(code, devServerUrl);
|
|
66
81
|
},
|
|
67
82
|
configureServer(server) {
|
|
68
83
|
server.httpServer?.once('listening', () => {
|
|
69
84
|
const address = server.httpServer?.address();
|
|
70
85
|
if (isAddressInfo(address)) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const meta = { url: devServerUrl, sourceDir };
|
|
86
|
+
devServerUrl = resolveDevServerUrl(address, resolvedConfig);
|
|
87
|
+
const meta = { url: devServerUrl, sourceDir, buildDir: effectiveBuildDir };
|
|
74
88
|
if (entrypointsDir)
|
|
75
89
|
meta.entrypointsDir = entrypointsDir;
|
|
76
90
|
if (reactRefresh)
|
package/dist/jsbundling.js
CHANGED
|
@@ -2,9 +2,11 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import picomatch from 'picomatch';
|
|
4
4
|
import { loadEnv, defaultAllowedOrigins, } from 'vite';
|
|
5
|
+
import { ORIGIN_PLACEHOLDER } from './shared/types.js';
|
|
5
6
|
import { resolveEntries, entriesToRollupInput, prefixWithSourceDir, detectEntrypointsDir, discoverEntrypointInputs, detectEntrypoint, isEntrypointFile } from './shared/entries.js';
|
|
6
7
|
import { resolveAlias } from './shared/alias.js';
|
|
7
|
-
import { resolveDevServerUrl, isAddressInfo } from './shared/dev-server.js';
|
|
8
|
+
import { resolveDevServerUrl, isAddressInfo, replaceOriginPlaceholder } from './shared/dev-server.js';
|
|
9
|
+
import { resolveBundlerOptionsKey, getUserBundlerInput } from './shared/bundler-compat.js';
|
|
8
10
|
import { ensureCommandShouldRunInEnvironment } from './shared/env-guard.js';
|
|
9
11
|
import { refreshPaths, resolveRefreshPaths } from './shared/refresh.js';
|
|
10
12
|
import { cssExtensions } from './shared/css.js';
|
|
@@ -30,6 +32,7 @@ export default function jsbundling(options = {}) {
|
|
|
30
32
|
const ssrConfig = resolveSsrConfig(options.ssr, sourceDir, outputDir);
|
|
31
33
|
let resolvedConfig;
|
|
32
34
|
let reactRefresh = false;
|
|
35
|
+
let devServerUrl = null;
|
|
33
36
|
// Track stubs written in dev so we can clean them up
|
|
34
37
|
const writtenStubs = [];
|
|
35
38
|
return {
|
|
@@ -38,6 +41,9 @@ export default function jsbundling(options = {}) {
|
|
|
38
41
|
config(userConfig, { command, mode, isSsrBuild }) {
|
|
39
42
|
const env = loadEnv(mode, userConfig.envDir || process.cwd(), '');
|
|
40
43
|
ensureCommandShouldRunInEnvironment(command, env, 'rails-vite-plugin/jsbundling');
|
|
44
|
+
// @ts-expect-error -- `this.meta.rolldownVersion` exists in Vite 8+
|
|
45
|
+
const bundlerOptionsKey = resolveBundlerOptionsKey(this.meta);
|
|
46
|
+
const userBundlerInput = getUserBundlerInput(userConfig);
|
|
41
47
|
// SSR builds get minimal config — just entry, outDir, base, and alias.
|
|
42
48
|
// No asset pipeline stubs or client-specific settings.
|
|
43
49
|
if (isSsrBuild) {
|
|
@@ -48,8 +54,8 @@ export default function jsbundling(options = {}) {
|
|
|
48
54
|
build: {
|
|
49
55
|
ssrManifest: userConfig.build?.ssrManifest ?? 'ssr-manifest.json',
|
|
50
56
|
outDir: userConfig.build?.outDir ?? ssrConfig.outDir,
|
|
51
|
-
|
|
52
|
-
input:
|
|
57
|
+
[bundlerOptionsKey]: {
|
|
58
|
+
input: userBundlerInput ?? ssrConfig.entry,
|
|
53
59
|
},
|
|
54
60
|
},
|
|
55
61
|
} : {}),
|
|
@@ -61,25 +67,20 @@ export default function jsbundling(options = {}) {
|
|
|
61
67
|
},
|
|
62
68
|
};
|
|
63
69
|
}
|
|
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
70
|
return {
|
|
70
71
|
base: userConfig.base ?? (command === 'build' ? buildBase : ''),
|
|
71
72
|
publicDir: userConfig.publicDir ?? false,
|
|
72
73
|
build: {
|
|
73
74
|
manifest: userConfig.build?.manifest ?? false,
|
|
74
75
|
outDir: userConfig.build?.outDir ?? outputDir,
|
|
75
|
-
|
|
76
|
-
input:
|
|
76
|
+
[bundlerOptionsKey]: {
|
|
77
|
+
input: userBundlerInput ?? rollupInput,
|
|
77
78
|
output: {
|
|
78
79
|
entryFileNames: (chunkInfo) => {
|
|
79
80
|
if (chunkInfo.facadeModuleId && cssExtensions.test(chunkInfo.facadeModuleId)) {
|
|
80
81
|
return `${CSS_FACADE_PREFIX}[name].js`;
|
|
81
82
|
}
|
|
82
|
-
return '[name].js';
|
|
83
|
+
return '_[name]-[hash].js';
|
|
83
84
|
},
|
|
84
85
|
chunkFileNames: '[name]-[hash].js',
|
|
85
86
|
assetFileNames: '[name][extname]',
|
|
@@ -89,6 +90,7 @@ export default function jsbundling(options = {}) {
|
|
|
89
90
|
cssCodeSplit: true,
|
|
90
91
|
},
|
|
91
92
|
server: {
|
|
93
|
+
origin: command === 'serve' ? (userConfig.server?.origin ?? ORIGIN_PLACEHOLDER) : undefined,
|
|
92
94
|
cors: userConfig.server?.cors ?? {
|
|
93
95
|
origin: userConfig.server?.origin ?? defaultAllowedOrigins,
|
|
94
96
|
},
|
|
@@ -118,19 +120,30 @@ export default function jsbundling(options = {}) {
|
|
|
118
120
|
// SSR bundles are Node.js server code — not served to browsers.
|
|
119
121
|
if (resolvedConfig.build.ssr)
|
|
120
122
|
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
123
|
fs.mkdirSync(assetPipelineDir, { recursive: true });
|
|
125
124
|
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
125
|
const entryNames = new Set(entries.map(e => e.name));
|
|
129
126
|
for (const [fileName, chunk] of Object.entries(bundle)) {
|
|
130
127
|
const isEntryJs = chunk.type === 'chunk' && chunk.isEntry;
|
|
131
128
|
const isEntryCss = chunk.type === 'asset' && cssExtensions.test(fileName)
|
|
132
129
|
&& entryNames.has(fileName.replace(/\.[^.]+$/, ''));
|
|
133
|
-
if (isEntryJs
|
|
130
|
+
if (isEntryJs) {
|
|
131
|
+
// JS entries are built as _[name]-[hash].js (content-hashed).
|
|
132
|
+
// Write a thin shim as [name].js for the asset pipeline so that
|
|
133
|
+
// Propshaft/Sprockets can serve it via javascript_include_tag.
|
|
134
|
+
//
|
|
135
|
+
// Why: Propshaft digests entry filenames (inertia.js → inertia-abc123.js)
|
|
136
|
+
// but cannot rewrite import paths inside Vite's chunks. Without the shim,
|
|
137
|
+
// the <script> tag and chunk imports resolve to different ES module
|
|
138
|
+
// instances — duplicating React, Inertia, and other stateful singletons.
|
|
139
|
+
// The shim ensures both paths chain to the same _[name]-[hash].js module.
|
|
140
|
+
const shimName = chunk.name + '.js';
|
|
141
|
+
const shim = `import "./${fileName}";export * from "./${fileName}";\n`;
|
|
142
|
+
fs.writeFileSync(path.join(outDir, shimName), shim);
|
|
143
|
+
fs.writeFileSync(path.join(assetPipelineDir, shimName), shim);
|
|
144
|
+
}
|
|
145
|
+
else if (isEntryCss) {
|
|
146
|
+
// Copy entry CSS to the asset pipeline. Shared chunk CSS stays in outputDir.
|
|
134
147
|
const src = path.join(outDir, fileName);
|
|
135
148
|
const dest = path.join(assetPipelineDir, fileName);
|
|
136
149
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
@@ -138,8 +151,10 @@ export default function jsbundling(options = {}) {
|
|
|
138
151
|
}
|
|
139
152
|
}
|
|
140
153
|
},
|
|
154
|
+
transform(code) {
|
|
155
|
+
return replaceOriginPlaceholder(code, devServerUrl);
|
|
156
|
+
},
|
|
141
157
|
configureServer(server) {
|
|
142
|
-
let devServerUrl = null;
|
|
143
158
|
let syncTimer = null;
|
|
144
159
|
// Re-discover entries from the entrypoints dir and regenerate all stubs.
|
|
145
160
|
// Called on initial listen and whenever entrypoint files are added/removed.
|
|
@@ -162,6 +177,13 @@ export default function jsbundling(options = {}) {
|
|
|
162
177
|
clearTimeout(syncTimer);
|
|
163
178
|
syncTimer = setTimeout(syncStubs, 100);
|
|
164
179
|
};
|
|
180
|
+
// Write placeholder stubs so Propshaft/Sprockets can discover asset files
|
|
181
|
+
// at boot time, before the Vite dev server is ready with its URL.
|
|
182
|
+
// Only write when httpServer exists — skip for embedded Vite instances
|
|
183
|
+
// (e.g., Storybook) that would otherwise overwrite real dev stubs.
|
|
184
|
+
if (server.httpServer && !server.httpServer.listening) {
|
|
185
|
+
writePlaceholderStubs(entries, assetPipelineDir, writtenStubs);
|
|
186
|
+
}
|
|
165
187
|
server.httpServer?.once('listening', () => {
|
|
166
188
|
const address = server.httpServer?.address();
|
|
167
189
|
if (isAddressInfo(address)) {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Vite 8 (Rolldown) uses `rolldownOptions` instead of `rollupOptions`.
|
|
2
|
+
// Detect at runtime to support both Vite 7 and 8.
|
|
3
|
+
export function resolveBundlerOptionsKey(meta) {
|
|
4
|
+
return meta?.rolldownVersion ? 'rolldownOptions' : 'rollupOptions';
|
|
5
|
+
}
|
|
6
|
+
export function getUserBundlerInput(userConfig) {
|
|
7
|
+
// @ts-expect-error -- `rolldownOptions` exists in Vite 8+ only
|
|
8
|
+
return userConfig.build?.rolldownOptions?.input ?? userConfig.build?.rollupOptions?.input;
|
|
9
|
+
}
|
|
@@ -3,3 +3,4 @@ import type { ResolvedConfig } from 'vite';
|
|
|
3
3
|
import type { DevServerUrl } from './types.js';
|
|
4
4
|
export declare function resolveDevServerUrl(address: AddressInfo, config: ResolvedConfig): DevServerUrl;
|
|
5
5
|
export declare function isAddressInfo(x: string | AddressInfo | null | undefined): x is AddressInfo;
|
|
6
|
+
export declare function replaceOriginPlaceholder(code: string, devServerUrl: string | null): string | undefined;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ORIGIN_PLACEHOLDER } from './types.js';
|
|
1
2
|
export function resolveDevServerUrl(address, config) {
|
|
2
3
|
const hmr = typeof config.server.hmr === 'object' ? config.server.hmr : null;
|
|
3
4
|
const clientProtocol = hmr?.protocol
|
|
@@ -18,3 +19,8 @@ export function resolveDevServerUrl(address, config) {
|
|
|
18
19
|
export function isAddressInfo(x) {
|
|
19
20
|
return typeof x === 'object' && x !== null;
|
|
20
21
|
}
|
|
22
|
+
export function replaceOriginPlaceholder(code, devServerUrl) {
|
|
23
|
+
if (devServerUrl && code.includes(ORIGIN_PLACEHOLDER)) {
|
|
24
|
+
return code.replaceAll(ORIGIN_PLACEHOLDER, devServerUrl);
|
|
25
|
+
}
|
|
26
|
+
}
|
package/dist/shared/types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export type InputOption = string | string[] | Record<string, string>;
|
|
2
2
|
export type DevServerUrl = `${'http' | 'https'}://${string}:${number}`;
|
|
3
|
+
export declare const ORIGIN_PLACEHOLDER = "http://__rails_vite_placeholder__.test";
|
|
3
4
|
export interface ResolvedEntry {
|
|
4
5
|
/** Output name (e.g., 'application', 'admin/index') */
|
|
5
6
|
name: string;
|
package/dist/shared/types.js
CHANGED