Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 46 additions & 2 deletions packages/plugin/vite/spec/ViteConfig.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,26 @@ describe('ViteConfigGenerator', () => {
});
});

it('getBuildConfigs:main with type module', async () => {
const forgeConfig: VitePluginConfig = {
build: [
{
entry: 'src/main.js',
config: path.join(configRoot, 'vite.main.config.mjs'),
target: 'main',
},
],
renderer: [],
type: 'module',
};
const generator = new ViteConfigGenerator(forgeConfig, configRoot, true);
const buildConfig = (await generator.getBuildConfigs())[0];

expect(buildConfig.build?.lib && buildConfig.build.lib.formats).toEqual([
'es',
]);
});

it('getBuildConfigs:preload', async () => {
const forgeConfig: VitePluginConfig = {
build: [
Expand Down Expand Up @@ -84,8 +104,8 @@ describe('ViteConfigGenerator', () => {
expect(buildConfig.build?.rollupOptions?.output).toEqual({
format: 'cjs',
inlineDynamicImports: true,
entryFileNames: '[name].js',
chunkFileNames: '[name].js',
entryFileNames: '[name].cjs',
chunkFileNames: '[name].cjs',
assetFileNames: '[name].[ext]',
});
expect(buildConfig.clearScreen).toBe(false);
Expand All @@ -94,6 +114,30 @@ describe('ViteConfigGenerator', () => {
).toEqual(['@electron-forge/plugin-vite:hot-restart']);
});

it('getBuildConfigs:preload with type module', async () => {
const forgeConfig: VitePluginConfig = {
build: [
{
entry: 'src/preload.js',
config: path.join(configRoot, 'vite.preload.config.mjs'),
target: 'preload',
},
],
renderer: [],
type: 'module',
};
const generator = new ViteConfigGenerator(forgeConfig, configRoot, true);
const buildConfig = (await generator.getBuildConfigs())[0];

expect(buildConfig.build?.rollupOptions?.output).toEqual({
format: 'es',
inlineDynamicImports: true,
entryFileNames: '[name].mjs',
chunkFileNames: '[name].mjs',
assetFileNames: '[name].[ext]',
});
});

it('getRendererConfig:renderer', async () => {
const forgeConfig = {
build: [],
Expand Down
60 changes: 55 additions & 5 deletions packages/plugin/vite/spec/VitePlugin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,17 +88,21 @@ describe('VitePlugin', async () => {
plugin.packageAfterCopy({} as ResolvedForgeConfig, packagedPath),
).rejects.toThrow(/entry point/);
});

afterAll(async () => {
await fs.promises.rm(viteTestDir, { recursive: true });
});
});

describe('resolveForgeConfig', () => {
const packageJSONPath = path.join(viteTestDir, 'package.json');
let plugin: VitePlugin;

beforeAll(() => {
beforeAll(async () => {
plugin = new VitePlugin(baseConfig);
plugin.setDirectories(viteTestDir);
// Write a default package.json for tests that don't care about its contents
await fs.promises.writeFile(
packageJSONPath,
JSON.stringify({ main: '.vite/build/main.js' }),
'utf-8',
);
});

it('sets packagerConfig and packagerConfig.ignore if it does not exist', async () => {
Expand All @@ -107,6 +111,48 @@ describe('VitePlugin', async () => {
expect(config.packagerConfig.ignore).toBeTypeOf('function');
});

it('should fail if plugin type is "module" but package.json has no "type": "module" and main is not .mjs', async () => {
const esmPlugin = new VitePlugin({ ...baseConfig, type: 'module' });
esmPlugin.setDirectories(viteTestDir);

await fs.promises.writeFile(
packageJSONPath,
JSON.stringify({ main: '.vite/build/main.js' }),
'utf-8',
);
await expect(
esmPlugin.resolveForgeConfig({} as ResolvedForgeConfig),
).rejects.toThrow(/type: "module"/);
});

it('should succeed if plugin type is "module" and package.json has "type": "module"', async () => {
const esmPlugin = new VitePlugin({ ...baseConfig, type: 'module' });
esmPlugin.setDirectories(viteTestDir);

await fs.promises.writeFile(
packageJSONPath,
JSON.stringify({ main: '.vite/build/main.js', type: 'module' }),
'utf-8',
);
await expect(
esmPlugin.resolveForgeConfig({} as ResolvedForgeConfig),
).resolves.toBeDefined();
});

it('should succeed if plugin type is "module" and main entry uses .mjs extension', async () => {
const esmPlugin = new VitePlugin({ ...baseConfig, type: 'module' });
esmPlugin.setDirectories(viteTestDir);

await fs.promises.writeFile(
packageJSONPath,
JSON.stringify({ main: '.vite/build/main.mjs' }),
'utf-8',
);
await expect(
esmPlugin.resolveForgeConfig({} as ResolvedForgeConfig),
).resolves.toBeDefined();
});

describe('packagerConfig.ignore', () => {
it('does not overwrite an existing ignore value', async () => {
const config = await plugin.resolveForgeConfig({
Expand Down Expand Up @@ -207,4 +253,8 @@ describe('VitePlugin', async () => {
});
});
});

afterAll(async () => {
await fs.promises.rm(viteTestDir, { recursive: true });
});
});
2 changes: 1 addition & 1 deletion packages/plugin/vite/spec/subprocess-worker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ describe('subprocess-worker', () => {
const { code, stderr } = await runWorker('build', 0, config);
expect(code, stderr).toBe(0);

const outFile = path.join(viteOutDir, 'build', 'preload.js');
const outFile = path.join(viteOutDir, 'build', 'preload.cjs');
expect(fs.existsSync(outFile)).toBe(true);
const contents = fs.readFileSync(outFile, 'utf8');
expect(contents).toContain('from-preload');
Expand Down
12 changes: 12 additions & 0 deletions packages/plugin/vite/src/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,16 @@ export interface VitePluginConfig {
* @defaultValue `true`
*/
concurrent?: boolean | number;

/**
* The module type to use for the main process and preload script builds.
*
* - `'commonjs'` outputs CJS bundles (the default, matching Electron's traditional module system).
* - `'module'` outputs ES module bundles. When using this option, make sure your `package.json`
* has `"type": "module"` and that your Electron version supports ESM (Electron >= 28).
*
* @defaultValue `'commonjs'`
* @see https://www.electronjs.org/docs/latest/tutorial/esm
*/
type?: 'commonjs' | 'module';
}
18 changes: 16 additions & 2 deletions packages/plugin/vite/src/VitePlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const subprocessWorkerPath = path.resolve(
);

function spawnViteBuild(
pluginConfig: Pick<VitePluginConfig, 'build' | 'renderer'>,
pluginConfig: Pick<VitePluginConfig, 'build' | 'renderer' | 'type'>,
kind: 'build' | 'renderer',
index: number,
projectDir: string,
Expand Down Expand Up @@ -222,6 +222,19 @@ export default class VitePlugin extends PluginBase<VitePluginConfig> {
resolveForgeConfig = async (
forgeConfig: ResolvedForgeConfig,
): Promise<ResolvedForgeConfig> => {
if (this.config.type === 'module') {
const pj = await fs.readJson(
path.resolve(this.projectDir, 'package.json'),
);
if (pj.type !== 'module' && !pj.main?.endsWith('.mjs')) {
throw new Error(
`The Vite plugin is configured with type: "module", but your package.json does not have "type": "module" ` +
`and the main entry point does not use an .mjs extension. Electron requires one of these for ESM support in the main process. ` +
`See https://www.electronjs.org/docs/latest/tutorial/esm for more details.`,
);
}
}

forgeConfig.packagerConfig ??= {};

if (forgeConfig.packagerConfig.ignore) {
Expand Down Expand Up @@ -275,11 +288,12 @@ the generated files). Instead, it is ${JSON.stringify(pj.main)}.`);
*/
private get serializableConfig(): Pick<
VitePluginConfig,
'build' | 'renderer'
'build' | 'renderer' | 'type'
> {
return {
build: this.config.build,
renderer: this.config.renderer,
type: this.config.type,
};
}

Expand Down
5 changes: 3 additions & 2 deletions packages/plugin/vite/src/config/vite.main.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ export function getConfig(
forgeEnv: ConfigEnv<'build'>,
userConfig: UserConfig = {},
): UserConfig {
const { forgeConfigSelf } = forgeEnv;
const { forgeConfigSelf, forgeConfig } = forgeEnv;
const isEsm = forgeConfig.type === 'module';
const define = getBuildDefine(forgeEnv);
const config: UserConfig = {
build: {
Expand All @@ -34,7 +35,7 @@ export function getConfig(
config.build!.lib = {
entry: forgeConfigSelf.entry,
fileName: () => '[name].js',
formats: ['cjs'],
formats: [isEsm ? 'es' : 'cjs'],
};
}

Expand Down
9 changes: 5 additions & 4 deletions packages/plugin/vite/src/config/vite.preload.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export function getConfig(
forgeEnv: ConfigEnv<'build'>,
userConfig: UserConfig = {},
): UserConfig {
const { forgeConfigSelf } = forgeEnv;
const { forgeConfigSelf, forgeConfig } = forgeEnv;
const isEsm = forgeConfig.type === 'module';
const config: UserConfig = {
build: {
copyPublicDir: false,
Expand All @@ -19,11 +20,11 @@ export function getConfig(
// Preload scripts may contain Web assets, so use the `build.rollupOptions.input` instead `build.lib.entry`.
input: forgeConfigSelf.entry,
output: {
format: 'cjs',
format: isEsm ? 'es' : 'cjs',
// It should not be split chunks.
inlineDynamicImports: true,
entryFileNames: '[name].js',
chunkFileNames: '[name].js',
entryFileNames: isEsm ? '[name].mjs' : '[name].cjs',
chunkFileNames: isEsm ? '[name].mjs' : '[name].cjs',
assetFileNames: '[name].[ext]',
},
},
Expand Down
2 changes: 1 addition & 1 deletion packages/template/vite-typescript/tmpl/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const createWindow = () => {
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
preload: path.join(__dirname, 'preload.cjs'),
},
});

Expand Down
12 changes: 6 additions & 6 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10828,8 +10828,8 @@ __metadata:
linkType: hard

"got@npm:^11.7.0, got@npm:^11.8.5":
version: 11.8.5
resolution: "got@npm:11.8.5"
version: 11.8.6
resolution: "got@npm:11.8.6"
dependencies:
"@sindresorhus/is": "npm:^4.0.0"
"@szmarczak/http-timer": "npm:^4.0.5"
Expand All @@ -10842,13 +10842,13 @@ __metadata:
lowercase-keys: "npm:^2.0.0"
p-cancelable: "npm:^2.0.0"
responselike: "npm:^2.0.0"
checksum: 10c0/14d160a21d085b0fca1c794ae17411d6abe05491a1db37b97e8218bf434d086eea335cadc022964f1896a60ac036db6af0debad94d3747f85503bc7d21bf0fa0
checksum: 10c0/754dd44877e5cf6183f1e989ff01c648d9a4719e357457bd4c78943911168881f1cfb7b2cb15d885e2105b3ad313adb8f017a67265dd7ade771afdb261ee8cb1
languageName: node
linkType: hard

"got@npm:^14.4.5":
version: 14.6.4
resolution: "got@npm:14.6.4"
version: 14.6.6
resolution: "got@npm:14.6.6"
dependencies:
"@sindresorhus/is": "npm:^7.0.1"
byte-counter: "npm:^0.1.0"
Expand All @@ -10862,7 +10862,7 @@ __metadata:
p-cancelable: "npm:^4.0.1"
responselike: "npm:^4.0.2"
type-fest: "npm:^4.26.1"
checksum: 10c0/ee8980feb842db876cffa42fa27da6d90cc1a9cfe2a38942f4b319cbb37c000e34919a7e5dea017a0fa53b0535c02d00426abbbf528d6a4e89c6eb07b2bde977
checksum: 10c0/dab4dbd35deac5634450cd745187ba68cfb9fd8d9236bec4861b633c7dc54f6383fde04cf504b16148625c307a229ff8cccf35d6622824ab13243c9d0af0fcc1
languageName: node
linkType: hard

Expand Down