Skip to content

Commit

Permalink
feat(plugin-vite): upgrade to vite@5 (#3468)
Browse files Browse the repository at this point in the history
* refactor(plugin-vite): move built-in vite config to template config file

* feat(plugin-vite): hot restart, reload 🔥

* chore(plugin-vite): update test

* fix(plugin-vite): correct hot restart logic

* chore(template-vite): update template logic

* chore(plugin-vite): move plugin-vite deps to template-vite

* fix(template-vite): correct vite config

* chore: lint

* refactor(plugin-vite): restore API break changes

* chore(plugin-vite): add `rollup`, `vite` top top-level deps

* chore(plugin-vite): fix lint

* chore(plugin-vite): fix build types check error

* feat(template-vite): load node.js entry

* chore: update .gitignore

* fix: fix the Windows CI failed

* chore: update types

* chore: fix Mac CI test

---------

Co-authored-by: Erick Zhao <erick@hotmail.ca>
  • Loading branch information
caoxiemeihao and erickzhao authored Feb 7, 2024
1 parent 4a010d6 commit b0cae41
Show file tree
Hide file tree
Showing 30 changed files with 836 additions and 513 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.DS_Store
.nyc_output
*.lcov
/coverage
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@
"lint:fix": "prettier --write .",
"link:prepare": "lerna exec -- node ../../../tools/silent.js yarn link --silent --no-bin-links --link-folder ../../../.links",
"link:remove": "lerna exec -- node ../../../tools/silent.js yarn unlink --silent --no-bin-links --link-folder ../../../.links",
"test": "xvfb-maybe cross-env NODE_ENV=test TS_NODE_PROJECT='./tsconfig.test.json' TS_NODE_FILES=1 mocha",
"test": "npm run test:clear && xvfb-maybe cross-env NODE_ENV=test TS_NODE_PROJECT='./tsconfig.test.json' TS_NODE_FILES=1 mocha",
"test:fast": "npm run test -- --suite=fast",
"test:slow": "npm run test -- --suite=slow",
"test:clear": "ts-node tools/test-clear",
"postinstall": "rimraf node_modules/.bin/*.ps1 && ts-node ./tools/gen-tsconfigs.ts && ts-node ./tools/gen-ts-glue.ts",
"prepare": "husky install",
"preversion": "yarn build"
Expand Down Expand Up @@ -74,7 +75,7 @@
"source-map-support": "^0.5.13",
"sudo-prompt": "^9.1.1",
"username": "^5.1.0",
"vite": "^4.5.1",
"vite": "^5.0.12",
"webpack": "^5.69.1",
"webpack-dev-server": "^4.0.0",
"webpack-merge": "^5.7.3",
Expand Down
20 changes: 10 additions & 10 deletions packages/plugin/vite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,28 @@
"scripts": {
"test": "xvfb-maybe mocha --config ../../../.mocharc.js test/**/*_spec.ts test/*_spec.ts"
},
"dependencies": {
"@electron-forge/core-utils": "7.2.0",
"@electron-forge/plugin-base": "7.2.0",
"@electron-forge/shared-types": "7.2.0",
"@electron-forge/web-multi-logger": "7.2.0",
"chalk": "^4.0.0",
"debug": "^4.3.1",
"fs-extra": "^10.0.0"
},
"devDependencies": {
"@electron/packager": "^18.1.2",
"@malept/cross-spawn-promise": "^2.0.0",
"@types/node": "^18.0.3",
"chai": "^4.3.3",
"mocha": "^9.0.1",
"vite": "^5.0.12",
"which": "^2.0.2",
"xvfb-maybe": "^0.2.1"
},
"engines": {
"node": ">= 16.4.0"
},
"dependencies": {
"@electron-forge/core-utils": "7.2.0",
"@electron-forge/plugin-base": "7.2.0",
"@electron-forge/shared-types": "7.2.0",
"@electron-forge/web-multi-logger": "7.2.0",
"chalk": "^4.0.0",
"debug": "^4.3.1",
"fs-extra": "^10.0.0",
"vite": "^4.5.1"
},
"publishConfig": {
"access": "public"
},
Expand Down
13 changes: 9 additions & 4 deletions packages/plugin/vite/src/Config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// eslint-disable-next-line node/no-unpublished-import
import type { LibraryOptions } from 'vite';

export interface VitePluginBuildConfig {
Expand All @@ -8,27 +9,31 @@ export interface VitePluginBuildConfig {
/**
* Vite config file path.
*/
config?: string;
config: string;
}

export interface VitePluginRendererConfig {
/**
* Human friendly name of your entry point
* Human friendly name of your entry point.
*/
name: string;
name?: string;
/**
* Vite config file path.
*/
config: string;
}

export interface VitePluginConfig {
// Reserved option, may support modification in the future.
// @defaultValue '.vite'
// baseDir?: string;

/**
* Build anything such as Main process, Preload scripts and Worker process, etc.
*/
build: VitePluginBuildConfig[];
/**
* Renderer process.
* Renderer process Vite configs.
*/
renderer: VitePluginRendererConfig[];
}
110 changes: 25 additions & 85 deletions packages/plugin/vite/src/ViteConfig.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,33 @@
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 { VitePluginBuildConfig, VitePluginConfig, VitePluginRendererConfig } 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`.
resolveConfig(buildConfig: VitePluginBuildConfig | VitePluginRendererConfig, configEnv: Partial<ConfigEnv> = {}) {
// @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;
return loadConfigFromFile(configEnv as ConfigEnv, config);

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

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

get mode(): string {
Expand All @@ -40,61 +37,15 @@ 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;
});
// Prevent load the default `vite.config.js` file.
.filter(({ config }) => config)
.map<Promise<UserConfig>>(async (buildConfig) => (await this.resolveConfig(buildConfig))?.config ?? {});

return await Promise.all(configs);
}
Expand All @@ -104,20 +55,9 @@ 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.pluginConfig.renderer
.filter(({ config }) => config)
.map<Promise<UserConfig>>(async (buildConfig) => (await this.resolveConfig(buildConfig))?.config ?? {});

return await Promise.all(configs);
}
Expand Down
79 changes: 37 additions & 42 deletions packages/plugin/vite/src/VitePlugin.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import { AddressInfo } from 'node:net';
import path from 'node:path';

import { namedHookWithTaskFn, PluginBase } from '@electron-forge/plugin-base';
import { ForgeMultiHookMap, ResolvedForgeConfig, StartResult } from '@electron-forge/shared-types';
import chalk from 'chalk';
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';
import type { ForgeMultiHookMap, ResolvedForgeConfig, StartResult } from '@electron-forge/shared-types';
import type { AddressInfo } from 'node:net';
// eslint-disable-next-line node/no-extraneous-import
import type { RollupWatcher } from 'rollup';

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
Loading

0 comments on commit b0cae41

Please sign in to comment.