Skip to content

Commit

Permalink
refactor(plugin-vite): move built-in vite config to template config file
Browse files Browse the repository at this point in the history
  • Loading branch information
caoxiemeihao committed Jan 19, 2024
1 parent 2d244f0 commit b4da787
Show file tree
Hide file tree
Showing 15 changed files with 289 additions and 236 deletions.
12 changes: 1 addition & 11 deletions packages/plugin/vite/src/Config.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,11 @@
import type { LibraryOptions } from 'vite';

export interface VitePluginBuildConfig {
/**
* Alias of `build.lib.entry` in `config`.
*/
entry?: LibraryOptions['entry'];
/**
* Vite config file path.
*/
config?: string;
config: string;
}

export interface VitePluginRendererConfig {
/**
* Human friendly name of your entry point
*/
name: string;
/**
* Vite config file path.
*/
Expand Down
97 changes: 16 additions & 81 deletions packages/plugin/vite/src/ViteConfig.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,30 @@
import path from 'node:path';

import debug from 'debug';
import { ConfigEnv, loadConfigFromFile, mergeConfig, UserConfig } from 'vite';

import { VitePluginConfig } from './Config';
import { externalBuiltins } from './util/plugins';
// eslint-disable-next-line node/no-unpublished-import
import { loadConfigFromFile } from 'vite';

const d = debug('electron-forge:plugin:vite:viteconfig');
import type { VitePluginConfig } from './Config';
// eslint-disable-next-line node/no-unpublished-import
import type { ConfigEnv, UserConfig } from 'vite';

/**
* Vite allows zero-config runs, if the user does not provide `vite.config.js`,
* then the value of `LoadResult` will become `null`.
*/
export type LoadResult = Awaited<ReturnType<typeof loadConfigFromFile>>;
const d = debug('@electron-forge/plugin-vite:ViteConfig');

export default class ViteConfigGenerator {
private readonly baseDir: string;

private rendererConfigCache!: Promise<UserConfig>[];

constructor(private readonly pluginConfig: VitePluginConfig, private readonly projectDir: string, private readonly isProd: boolean) {
this.baseDir = path.join(projectDir, '.vite');
d('Config mode:', this.mode);
}

resolveConfig(config: string, configEnv: Partial<ConfigEnv> = {}) {
// `command` is to be passed as an arguments when the user export a function in `vite.config.js`.
// @see - https://vitejs.dev/config/#conditional-config
configEnv.command ??= this.isProd ? 'build' : 'serve';
// `mode` affects `.env.[mode]` file loading.
// `mode` affects `.env.[mode]` file load.
configEnv.mode ??= this.mode;

// Hack! Pass the runtime path to the vite config file in the template.
Object.assign(configEnv, { root: this.projectDir });

// `configEnv` is to be passed as an arguments when the user export a function in `vite.config.js`.
return loadConfigFromFile(configEnv as ConfigEnv, config);
}

Expand All @@ -40,61 +35,14 @@ export default class ViteConfigGenerator {
return this.isProd ? 'production' : 'development';
}

async getDefines(): Promise<Record<string, string>> {
const defines: Record<string, any> = {};
const rendererConfigs = await this.getRendererConfig();
for (const [index, userConfig] of rendererConfigs.entries()) {
const name = this.pluginConfig.renderer[index].name;
if (!name) {
continue;
}
const NAME = name.toUpperCase().replace(/ /g, '_');
// `server.port` is set in `launchRendererDevServers` in `VitePlugin.ts`.
defines[`${NAME}_VITE_DEV_SERVER_URL`] = this.isProd ? undefined : JSON.stringify(`http://localhost:${userConfig?.server?.port}`);
defines[`${NAME}_VITE_NAME`] = JSON.stringify(name);
}
return defines;
}

async getBuildConfig(watch = false): Promise<UserConfig[]> {
async getBuildConfig(): Promise<UserConfig[]> {
if (!Array.isArray(this.pluginConfig.build)) {
throw new Error('"config.build" must be an Array');
}

const define = await this.getDefines();
const plugins = [externalBuiltins()];
const configs = this.pluginConfig.build
.filter(({ entry, config }) => entry || config)
.map<Promise<UserConfig>>(async ({ entry, config }) => {
const defaultConfig: UserConfig = {
// Ensure that each build config loads the .env file correctly.
mode: this.mode,
build: {
lib: entry
? {
entry,
// Electron can only support cjs.
formats: ['cjs'],
fileName: () => '[name].js',
}
: undefined,
// Prevent multiple builds from interfering with each other.
emptyOutDir: false,
// 🚧 Multiple builds may conflict.
outDir: path.join(this.baseDir, 'build'),
watch: watch ? {} : undefined,
minify: this.isProd,
},
clearScreen: false,
define,
plugins,
};
if (config) {
const loadResult = await this.resolveConfig(config);
return mergeConfig(defaultConfig, loadResult?.config ?? {});
}
return defaultConfig;
});
.filter(({ config }) => config)
.map<Promise<UserConfig>>(async ({ config }) => (await this.resolveConfig(config))?.config ?? {});

return await Promise.all(configs);
}
Expand All @@ -104,20 +52,7 @@ export default class ViteConfigGenerator {
throw new Error('"config.renderer" must be an Array');
}

const configs = (this.rendererConfigCache ??= this.pluginConfig.renderer.map(async ({ name, config }) => {
const defaultConfig: UserConfig = {
// Ensure that each build config loads the .env file correctly.
mode: this.mode,
// Make sure that Electron can be loaded into the local file using `loadFile` after packaging.
base: './',
build: {
outDir: path.join(this.baseDir, 'renderer', name),
},
clearScreen: false,
};
const loadResult = (await this.resolveConfig(config)) ?? { path: '', config: {}, dependencies: [] };
return mergeConfig(defaultConfig, loadResult.config);
}));
const configs = (this.rendererConfigCache ??= this.pluginConfig.renderer.map(async ({ config }) => (await this.resolveConfig(config))?.config ?? {}));

return await Promise.all(configs);
}
Expand Down
71 changes: 33 additions & 38 deletions packages/plugin/vite/src/VitePlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ import debug from 'debug';
import fs from 'fs-extra';
// eslint-disable-next-line node/no-extraneous-import
import { RollupWatcher } from 'rollup';
// eslint-disable-next-line node/no-unpublished-import
import { default as vite } from 'vite';

import { VitePluginConfig } from './Config';
import { getFlatDependencies } from './util/package';
import { onBuildDone } from './util/plugins';
import ViteConfigGenerator from './ViteConfig';

import type { VitePluginConfig } from './Config';

const d = debug('electron-forge:plugin:vite');

export default class VitePlugin extends PluginBase<VitePluginConfig> {
Expand Down Expand Up @@ -144,7 +147,7 @@ the generated files). Instead, it is ${JSON.stringify(pj.main)}`);
{
title: 'Compiling main process code',
task: async () => {
await this.build(true);
await this.build();
},
options: {
showTimer: true,
Expand All @@ -156,42 +159,34 @@ the generated files). Instead, it is ${JSON.stringify(pj.main)}`);
};

// Main process, Preload scripts and Worker process, etc.
build = async (watch = false): Promise<void> => {
await Promise.all(
(
await this.configGenerator.getBuildConfig(watch)
).map((userConfig) => {
return new Promise<void>((resolve, reject) => {
vite
.build({
// Avoid recursive builds caused by users configuring @electron-forge/plugin-vite in Vite config file.
configFile: false,
...userConfig,
plugins: [
{
name: '@electron-forge/plugin-vite:start',
closeBundle() {
resolve();

// TODO: implement hot-restart here
},
},
...(userConfig.plugins ?? []),
],
})
.then((result) => {
const isWatcher = (x: any): x is RollupWatcher => typeof x?.close === 'function';

if (isWatcher(result)) {
this.watchers.push(result);
}

return result;
})
.catch(reject);
});
})
);
build = async (): Promise<void> => {
const configs = await this.configGenerator.getBuildConfig();
const buildTasks: Promise<void>[] = [];
const isWatcher = (x: any): x is RollupWatcher => typeof x?.close === 'function';

for (const userConfig of configs) {
const buildTask = new Promise<void>((resolve, reject) => {
vite
.build({
// Avoid recursive builds caused by users configuring @electron-forge/plugin-vite in Vite config file.
configFile: false,
...userConfig,
plugins: [onBuildDone(resolve), ...(userConfig.plugins ?? [])],
})
.then((result) => {
if (isWatcher(result)) {
this.watchers.push(result);
}

return result;
})
.catch(reject);
});

buildTasks.push(buildTask);
}

await Promise.all(buildTasks);
};

// Renderer process
Expand Down
38 changes: 7 additions & 31 deletions packages/plugin/vite/src/util/plugins.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,11 @@
import { builtinModules } from 'node:module';

// eslint-disable-next-line node/no-unpublished-import
import type { Plugin } from 'vite';

/**
* `electron` and Node.js built-in modules should always be externalize.
*/
export function externalBuiltins() {
return <Plugin>{
name: '@electron-forge/plugin-vite:external-builtins',
config(config) {
const nativeModules = builtinModules.filter((e) => !e.startsWith('_'));
const builtins = ['electron', ...nativeModules, ...nativeModules.map((m) => `node:${m}`)];

config.build ??= {};
config.build.rollupOptions ??= {};

let external = config.build.rollupOptions.external;
if (Array.isArray(external) || typeof external === 'string' || external instanceof RegExp) {
external = builtins.concat(external as string[]);
} else if (typeof external === 'function') {
const original = external;
external = function (source, importer, isResolved) {
if (builtins.includes(source)) {
return true;
}
return original(source, importer, isResolved);
};
} else {
external = builtins;
}
config.build.rollupOptions.external = external;
export function onBuildDone(callback: () => void) {
return {
name: '@electron-forge/plugin-vite:build-done',
closeBundle() {
callback();
},
};
} as Plugin;
}
45 changes: 0 additions & 45 deletions packages/plugin/vite/test/util/plugins_spec.ts

This file was deleted.

4 changes: 0 additions & 4 deletions packages/template/vite-typescript/tmpl/forge.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,14 @@ const config: ForgeConfig = {
// If you are familiar with Vite configuration, it will look really familiar.
build: [
{
// `entry` is just an alias for `build.lib.entry` in the corresponding file of `config`.
entry: 'src/main.ts',
config: 'vite.main.config.ts',
},
{
entry: 'src/preload.ts',
config: 'vite.preload.config.ts',
},
],
renderer: [
{
name: 'main_window',
config: 'vite.renderer.config.ts',
},
],
Expand Down
Loading

0 comments on commit b4da787

Please sign in to comment.