@electron-forge/plugin-vite 6.1.0 → 6.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/LICENSE +19 -0
- package/README.md +10 -10
- package/dist/ViteConfig.d.ts.map +1 -1
- package/dist/ViteConfig.js +4 -11
- package/dist/VitePlugin.d.ts.map +1 -1
- package/dist/VitePlugin.js +52 -16
- package/index.ts +5 -0
- package/package.json +7 -6
- package/src/Config.ts +34 -0
- package/src/ViteConfig.ts +123 -0
- package/src/VitePlugin.ts +202 -0
- package/src/util/plugins.ts +35 -0
- package/test/ViteConfig_spec.ts +91 -0
- package/test/util/plugins_spec.ts +45 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import { AddressInfo } from 'node:net';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
import { namedHookWithTaskFn, PluginBase } from '@electron-forge/plugin-base';
|
|
6
|
+
import { ForgeMultiHookMap, StartResult } from '@electron-forge/shared-types';
|
|
7
|
+
import debug from 'debug';
|
|
8
|
+
// eslint-disable-next-line node/no-extraneous-import
|
|
9
|
+
import { RollupWatcher } from 'rollup';
|
|
10
|
+
import { default as vite } from 'vite';
|
|
11
|
+
|
|
12
|
+
import { VitePluginConfig } from './Config';
|
|
13
|
+
import ViteConfigGenerator from './ViteConfig';
|
|
14
|
+
|
|
15
|
+
const d = debug('electron-forge:plugin:vite');
|
|
16
|
+
|
|
17
|
+
export default class VitePlugin extends PluginBase<VitePluginConfig> {
|
|
18
|
+
private static alreadyStarted = false;
|
|
19
|
+
|
|
20
|
+
public name = 'vite';
|
|
21
|
+
|
|
22
|
+
private isProd = false;
|
|
23
|
+
|
|
24
|
+
// The root of the Electron app
|
|
25
|
+
private projectDir!: string;
|
|
26
|
+
|
|
27
|
+
// Where the Vite output is generated. Usually `${projectDir}/.vite`
|
|
28
|
+
private baseDir!: string;
|
|
29
|
+
|
|
30
|
+
private configGeneratorCache!: ViteConfigGenerator;
|
|
31
|
+
|
|
32
|
+
private watchers: RollupWatcher[] = [];
|
|
33
|
+
|
|
34
|
+
private servers: vite.ViteDevServer[] = [];
|
|
35
|
+
|
|
36
|
+
init = (dir: string): void => {
|
|
37
|
+
this.setDirectories(dir);
|
|
38
|
+
|
|
39
|
+
d('hooking process events');
|
|
40
|
+
process.on('exit', (_code) => this.exitHandler({ cleanup: true }));
|
|
41
|
+
process.on('SIGINT' as NodeJS.Signals, (_signal) => this.exitHandler({ exit: true }));
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
private setDirectories(dir: string): void {
|
|
45
|
+
this.projectDir = dir;
|
|
46
|
+
this.baseDir = path.join(dir, '.vite');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private get configGenerator(): ViteConfigGenerator {
|
|
50
|
+
return (this.configGeneratorCache ??= new ViteConfigGenerator(this.config, this.projectDir, this.isProd));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
getHooks = (): ForgeMultiHookMap => {
|
|
54
|
+
return {
|
|
55
|
+
prePackage: [
|
|
56
|
+
namedHookWithTaskFn<'prePackage'>(async () => {
|
|
57
|
+
this.isProd = true;
|
|
58
|
+
await fs.rm(this.baseDir, { recursive: true, force: true });
|
|
59
|
+
|
|
60
|
+
await Promise.all([this.build(), this.buildRenderer()]);
|
|
61
|
+
}, 'Building vite bundles'),
|
|
62
|
+
],
|
|
63
|
+
postStart: async (_config, child) => {
|
|
64
|
+
d('hooking electron process exit');
|
|
65
|
+
child.on('exit', () => {
|
|
66
|
+
if (child.restarted) return;
|
|
67
|
+
this.exitHandler({ cleanup: true, exit: true });
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
startLogic = async (): Promise<StartResult> => {
|
|
74
|
+
if (VitePlugin.alreadyStarted) return false;
|
|
75
|
+
VitePlugin.alreadyStarted = true;
|
|
76
|
+
|
|
77
|
+
await fs.rm(this.baseDir, { recursive: true, force: true });
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
tasks: [
|
|
81
|
+
{
|
|
82
|
+
title: 'Launching dev servers for renderer process code',
|
|
83
|
+
task: async () => {
|
|
84
|
+
await this.launchRendererDevServers();
|
|
85
|
+
},
|
|
86
|
+
options: {
|
|
87
|
+
persistentOutput: true,
|
|
88
|
+
showTimer: true,
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
// The main process depends on the `server.port` of the renderer process, so the renderer process is run first.
|
|
92
|
+
{
|
|
93
|
+
title: 'Compiling main process code',
|
|
94
|
+
task: async () => {
|
|
95
|
+
await this.build(true);
|
|
96
|
+
},
|
|
97
|
+
options: {
|
|
98
|
+
showTimer: true,
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
result: false,
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// Main process, Preload scripts and Worker process, etc.
|
|
107
|
+
build = async (watch = false): Promise<void> => {
|
|
108
|
+
await Promise.all(
|
|
109
|
+
(
|
|
110
|
+
await this.configGenerator.getBuildConfig(watch)
|
|
111
|
+
).map((userConfig) => {
|
|
112
|
+
return new Promise<void>((resolve, reject) => {
|
|
113
|
+
vite
|
|
114
|
+
.build({
|
|
115
|
+
// Avoid recursive builds caused by users configuring @electron-forge/plugin-vite in Vite config file.
|
|
116
|
+
configFile: false,
|
|
117
|
+
...userConfig,
|
|
118
|
+
plugins: [
|
|
119
|
+
{
|
|
120
|
+
name: '@electron-forge/plugin-vite:start',
|
|
121
|
+
closeBundle() {
|
|
122
|
+
resolve();
|
|
123
|
+
|
|
124
|
+
// TODO: implement hot-restart here
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
...(userConfig.plugins ?? []),
|
|
128
|
+
],
|
|
129
|
+
})
|
|
130
|
+
.then((result) => {
|
|
131
|
+
const isWatcher = (x: any): x is RollupWatcher => typeof x?.close === 'function';
|
|
132
|
+
|
|
133
|
+
if (isWatcher(result)) {
|
|
134
|
+
this.watchers.push(result);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return result;
|
|
138
|
+
})
|
|
139
|
+
.catch(reject);
|
|
140
|
+
});
|
|
141
|
+
})
|
|
142
|
+
);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// Renderer process
|
|
146
|
+
buildRenderer = async (): Promise<void> => {
|
|
147
|
+
for (const userConfig of await this.configGenerator.getRendererConfig()) {
|
|
148
|
+
await vite.build({
|
|
149
|
+
configFile: false,
|
|
150
|
+
...userConfig,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
launchRendererDevServers = async (): Promise<void> => {
|
|
156
|
+
for (const userConfig of await this.configGenerator.getRendererConfig()) {
|
|
157
|
+
const viteDevServer = await vite.createServer({
|
|
158
|
+
configFile: false,
|
|
159
|
+
...userConfig,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
await viteDevServer.listen();
|
|
163
|
+
viteDevServer.printUrls();
|
|
164
|
+
|
|
165
|
+
this.servers.push(viteDevServer);
|
|
166
|
+
|
|
167
|
+
if (viteDevServer.httpServer) {
|
|
168
|
+
// Make suee that `getDefines` in VitePlugin.ts gets the correct `server.port`. (#3198)
|
|
169
|
+
const addressInfo = viteDevServer.httpServer.address();
|
|
170
|
+
const isAddressInfo = (x: any): x is AddressInfo => x?.address;
|
|
171
|
+
|
|
172
|
+
if (isAddressInfo(addressInfo)) {
|
|
173
|
+
userConfig.server ??= {};
|
|
174
|
+
userConfig.server.port = addressInfo.port;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
exitHandler = (options: { cleanup?: boolean; exit?: boolean }, err?: Error): void => {
|
|
181
|
+
d('handling process exit with:', options);
|
|
182
|
+
if (options.cleanup) {
|
|
183
|
+
for (const watcher of this.watchers) {
|
|
184
|
+
d('cleaning vite watcher');
|
|
185
|
+
watcher.close();
|
|
186
|
+
}
|
|
187
|
+
this.watchers = [];
|
|
188
|
+
|
|
189
|
+
for (const server of this.servers) {
|
|
190
|
+
d('cleaning http server');
|
|
191
|
+
server.close();
|
|
192
|
+
}
|
|
193
|
+
this.servers = [];
|
|
194
|
+
}
|
|
195
|
+
if (err) console.error(err.stack);
|
|
196
|
+
// Why: This is literally what the option says to do.
|
|
197
|
+
// eslint-disable-next-line no-process-exit
|
|
198
|
+
if (options.exit) process.exit();
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export { VitePlugin };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { builtinModules } from 'node:module';
|
|
2
|
+
|
|
3
|
+
import type { Plugin } from 'vite';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `electron` and Node.js built-in modules should always be externalize.
|
|
7
|
+
*/
|
|
8
|
+
export function externalBuiltins() {
|
|
9
|
+
return <Plugin>{
|
|
10
|
+
name: '@electron-forge/plugin-vite:external-builtins',
|
|
11
|
+
config(config) {
|
|
12
|
+
const nativeModules = builtinModules.filter((e) => !e.startsWith('_'));
|
|
13
|
+
const builtins = ['electron', ...nativeModules, ...nativeModules.map((m) => `node:${m}`)];
|
|
14
|
+
|
|
15
|
+
config.build ??= {};
|
|
16
|
+
config.build.rollupOptions ??= {};
|
|
17
|
+
|
|
18
|
+
let external = config.build.rollupOptions.external;
|
|
19
|
+
if (Array.isArray(external) || typeof external === 'string' || external instanceof RegExp) {
|
|
20
|
+
external = builtins.concat(external as string[]);
|
|
21
|
+
} else if (typeof external === 'function') {
|
|
22
|
+
const original = external;
|
|
23
|
+
external = function (source, importer, isResolved) {
|
|
24
|
+
if (builtins.includes(source)) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
return original(source, importer, isResolved);
|
|
28
|
+
};
|
|
29
|
+
} else {
|
|
30
|
+
external = builtins;
|
|
31
|
+
}
|
|
32
|
+
config.build.rollupOptions.external = external;
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { AddressInfo } from 'node:net';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { expect } from 'chai';
|
|
5
|
+
import { UserConfig, default as vite, ViteDevServer } from 'vite';
|
|
6
|
+
|
|
7
|
+
import { VitePluginConfig } from '../src/Config';
|
|
8
|
+
import ViteConfigGenerator from '../src/ViteConfig';
|
|
9
|
+
|
|
10
|
+
describe('ViteConfigGenerator', () => {
|
|
11
|
+
it('is `getDefines` will be gets auto-updated `server.port`', async () => {
|
|
12
|
+
const config = {
|
|
13
|
+
renderer: [{ name: 'foo_window' }, { name: 'bar_window' }],
|
|
14
|
+
} as VitePluginConfig;
|
|
15
|
+
const generator = new ViteConfigGenerator(config, '', false);
|
|
16
|
+
const servers: ViteDevServer[] = [];
|
|
17
|
+
|
|
18
|
+
for (const userConfig of await generator.getRendererConfig()) {
|
|
19
|
+
const viteDevServer = await vite.createServer({
|
|
20
|
+
configFile: false,
|
|
21
|
+
...userConfig,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
await viteDevServer.listen();
|
|
25
|
+
viteDevServer.printUrls();
|
|
26
|
+
servers.push(viteDevServer);
|
|
27
|
+
|
|
28
|
+
// Make suee that `getDefines` in VitePlugin.ts gets the correct `server.port`. (#3198)
|
|
29
|
+
const addressInfo = viteDevServer.httpServer!.address() as AddressInfo;
|
|
30
|
+
userConfig.server ??= {};
|
|
31
|
+
userConfig.server.port = addressInfo.port;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const define = await generator.getDefines();
|
|
35
|
+
|
|
36
|
+
for (const server of servers) {
|
|
37
|
+
server.close();
|
|
38
|
+
}
|
|
39
|
+
servers.length = 0;
|
|
40
|
+
|
|
41
|
+
expect(define.FOO_WINDOW_VITE_DEV_SERVER_URL).equal('"http://localhost:5173"');
|
|
42
|
+
expect(define.FOO_WINDOW_VITE_NAME).equal('"foo_window"');
|
|
43
|
+
expect(define.BAR_WINDOW_VITE_DEV_SERVER_URL).equal('"http://localhost:5174"');
|
|
44
|
+
expect(define.BAR_WINDOW_VITE_NAME).equal('"bar_window"');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('getBuildConfig', async () => {
|
|
48
|
+
const config = {
|
|
49
|
+
build: [{ entry: 'foo.js' }],
|
|
50
|
+
renderer: [],
|
|
51
|
+
} as VitePluginConfig;
|
|
52
|
+
const generator = new ViteConfigGenerator(config, '', true);
|
|
53
|
+
const buildConfig = (await generator.getBuildConfig())[0];
|
|
54
|
+
expect(buildConfig).deep.equal({
|
|
55
|
+
mode: 'production',
|
|
56
|
+
build: {
|
|
57
|
+
lib: {
|
|
58
|
+
entry: 'foo.js',
|
|
59
|
+
formats: ['cjs'],
|
|
60
|
+
// shims
|
|
61
|
+
fileName: (buildConfig.build?.lib as any)?.fileName,
|
|
62
|
+
},
|
|
63
|
+
emptyOutDir: false,
|
|
64
|
+
outDir: path.join('.vite', 'build'),
|
|
65
|
+
watch: undefined,
|
|
66
|
+
},
|
|
67
|
+
clearScreen: false,
|
|
68
|
+
define: {},
|
|
69
|
+
// shims
|
|
70
|
+
plugins: [buildConfig.plugins?.[0]],
|
|
71
|
+
} as UserConfig);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('getRendererConfig', async () => {
|
|
75
|
+
const config = {
|
|
76
|
+
renderer: [{ name: 'foo_window' }, { name: 'bar_window' }],
|
|
77
|
+
} as VitePluginConfig;
|
|
78
|
+
const generator = new ViteConfigGenerator(config, '', false);
|
|
79
|
+
const configs = await generator.getRendererConfig();
|
|
80
|
+
for (const [index, rendererConfig] of configs.entries()) {
|
|
81
|
+
expect(rendererConfig).deep.equal({
|
|
82
|
+
mode: 'development',
|
|
83
|
+
base: './',
|
|
84
|
+
build: {
|
|
85
|
+
outDir: path.join('.vite', 'renderer', config.renderer[index].name),
|
|
86
|
+
},
|
|
87
|
+
clearScreen: false,
|
|
88
|
+
} as UserConfig);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
2
|
+
import { builtinModules } from 'module';
|
|
3
|
+
|
|
4
|
+
import { expect } from 'chai';
|
|
5
|
+
// eslint-disable-next-line node/no-extraneous-import
|
|
6
|
+
import { ExternalOption } from 'rollup';
|
|
7
|
+
import { resolveConfig } from 'vite';
|
|
8
|
+
|
|
9
|
+
import { externalBuiltins } from '../../src/util/plugins';
|
|
10
|
+
|
|
11
|
+
describe('plugins', () => {
|
|
12
|
+
it('externalBuiltins', async () => {
|
|
13
|
+
const nativeModules = builtinModules.filter((e) => !e.startsWith('_'));
|
|
14
|
+
const builtins: any[] = ['electron', ...nativeModules, ...nativeModules.map((m) => `node:${m}`)];
|
|
15
|
+
const getConfig = (external: ExternalOption) =>
|
|
16
|
+
resolveConfig(
|
|
17
|
+
{
|
|
18
|
+
configFile: false,
|
|
19
|
+
build: {
|
|
20
|
+
rollupOptions: {
|
|
21
|
+
external,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
plugins: [externalBuiltins()],
|
|
25
|
+
},
|
|
26
|
+
'build'
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const external_string: ExternalOption = 'electron';
|
|
30
|
+
const external_string2 = (await getConfig(external_string))!.build!.rollupOptions!.external;
|
|
31
|
+
expect(external_string2).deep.equal(builtins.concat(external_string));
|
|
32
|
+
|
|
33
|
+
const external_array: ExternalOption = ['electron'];
|
|
34
|
+
const external_array2 = (await getConfig(external_array))!.build!.rollupOptions!.external;
|
|
35
|
+
expect(external_array2).deep.equal(builtins.concat(external_array));
|
|
36
|
+
|
|
37
|
+
const external_regexp: ExternalOption = /electron/;
|
|
38
|
+
const external_regexp2 = (await getConfig(external_regexp))!.build!.rollupOptions!.external;
|
|
39
|
+
expect(external_regexp2).deep.equal(builtins.concat(external_regexp));
|
|
40
|
+
|
|
41
|
+
const external_function: ExternalOption = (source) => ['electron'].includes(source);
|
|
42
|
+
const external_function2 = (await getConfig(external_function))!.build!.rollupOptions!.external;
|
|
43
|
+
expect((external_function2 as (source: string) => boolean)('electron')).true;
|
|
44
|
+
});
|
|
45
|
+
});
|