Skip to content

Commit

Permalink
feat: support vite template
Browse files Browse the repository at this point in the history
  • Loading branch information
caoxiemeihao committed Nov 25, 2022
1 parent 51fcc92 commit 6abc16a
Show file tree
Hide file tree
Showing 20 changed files with 916 additions and 0 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"source-map-support": "^0.5.13",
"sudo-prompt": "^9.1.1",
"username": "^5.1.0",
"vite": "^3.2.3",
"webpack": "^5.69.1",
"webpack-dev-server": "^4.0.0",
"webpack-merge": "^5.7.3",
Expand Down
32 changes: 32 additions & 0 deletions packages/plugin/vite/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
## plugin-vite

This plugin makes it easy to set up standard vite tooling to compile both your main process code and your renderer process code, with built-in support for Hot Module Replacement (HMR) in the renderer process and support for multiple renderers.

```
// forge.config.js
module.exports = {
plugins: [
{
name: '@electron-forge/plugin-vite',
config: {
main: {
config: './vite.main.config.js',
},
renderer: {
config: './vite.renderer.config.js',
entryPoints: [
{
html: './src/index.html',
name: 'main_window',
preload: {
js: './src/preload.js',
},
},
],
},
},
}
]
}
```
39 changes: 39 additions & 0 deletions packages/plugin/vite/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"name": "@electron-forge/plugin-vite",
"version": "0.0.0",
"description": "Vite plugin for Electron Forge, lets you use Vite directly in your tooling",
"repository": {
"type": "git",
"url": "https://github.com/electron/forge",
"directory": "packages/plugin/vite"
},
"author": "caoxiemeihao",
"license": "MIT",
"main": "dist/VitePlugin.js",
"typings": "dist/VitePlugin.d.ts",
"scripts": {
"test": "xvfb-maybe mocha --config ../../../.mocharc.js test/**/*_spec.ts"
},
"devDependencies": {
"@malept/cross-spawn-promise": "^2.0.0",
"@types/node": "^18.0.3",
"chai": "^4.3.3",
"electron-packager": "^17.1.1",
"fs-extra": "^10.0.0",
"mocha": "^9.0.1",
"which": "^2.0.2",
"xvfb-maybe": "^0.2.1"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"dependencies": {
"@electron-forge/core-utils": "6.0.1",
"@electron-forge/plugin-base": "6.0.1",
"@electron-forge/shared-types": "6.0.1",
"@electron-forge/web-multi-logger": "6.0.1",
"chalk": "^4.0.0",
"debug": "^4.3.1",
"vite": "^3.2.3"
}
}
69 changes: 69 additions & 0 deletions packages/plugin/vite/src/Config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
export interface VitePreloadEntryPoint {
/**
* Relative or absolute path to the preload JS file.
*/
js: string;
/**
* The optional vite config for your preload process.
* Defaults to the renderer vite config if blank.
*/
config?: string;
}

export interface VitePluginEntryPointBase {
/**
* Human friendly name of your entry point
*/
name: string;
/**
* TODO: Is it considered to provide nodeIntegration option? Or let the user make their own choice. 🤔
*
* Here are some experiences from `vite-plugin-electron-renderer`.
*
* 1. Modify some default options.
* - build.cssCodeSplit = false (TODO)
* - build.rollupOptions.output.format = 'cjs' (nodeIntegration: true)
* - resolve.conditions = ['node']
* - optimizeDeps.exclude = ['electron'] - always
*
* 2. Pre-Bundling Node.js built-in modules and npm-package for Node.js.
*/
// nodeIntegration?: boolean;
}

export interface VitePluginEntryPoint extends VitePluginEntryPointBase {
/**
* Relative or absolute path to the HTML template file for this entry point.
*/
// TODO: Support for working in the `vite serve` phase.
// html: string;
/**
* Information about the preload script for this entry point. If you don't use
* preload scripts, you don't need to set this.
*/
preload?: VitePreloadEntryPoint;
}

export interface VitePluginRendererConfig {
/**
* The vite config for your renderer process
*/
config: string;
/**
* Array of entry points, these should map to the windows your app needs to
* open. Each window requires it's own entry point
*/
entryPoints: VitePluginEntryPoint[];
}

export interface VitePluginMainConfig {
/**
* The vite config for your main process
*/
config: string;
}

export interface VitePluginConfig {
main: VitePluginMainConfig;
renderer: VitePluginRendererConfig;
}
144 changes: 144 additions & 0 deletions packages/plugin/vite/src/ViteConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import path from 'node:path';

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

import { VitePluginConfig, VitePluginEntryPoint } from './Config';
import { externalBuiltins } from './util/plugins';

type ViteMode = 'production' | 'development';

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

export default class ViteConfigGenerator {
private isProd: boolean;

private pluginConfig: VitePluginConfig;

private port: number;

private projectDir!: string;

private baseDir!: string;

constructor(pluginConfig: VitePluginConfig, projectDir: string, isProd: boolean, port: number) {
this.pluginConfig = pluginConfig;
this.projectDir = projectDir;
this.baseDir = path.resolve(projectDir, '.vite');
this.isProd = isProd;
this.port = port;

d('Config mode:', this.mode);
}

async resolveConfig(config: string, configEnv: Partial<ConfigEnv> = {}) {
configEnv.command ??= 'build'; // should always build.
configEnv.mode ??= this.mode;
return loadConfigFromFile(configEnv as ConfigEnv, config);
}

get mode(): ViteMode {
return this.isProd ? 'production' : 'development';
}

private rendererEntryPoint(entryPoint: VitePluginEntryPoint, inRendererDir: boolean, basename: string): string {
if (this.isProd) {
return `\`file://$\{require('path').resolve(__dirname, '..', '${inRendererDir ? 'renderer' : '.'}', '${entryPoint.name}', '${basename}')}\``;
}
const baseUrl = `http://localhost:${this.port}/${entryPoint.name}`;
if (basename !== 'index.html') {
return `'${baseUrl}/${basename}'`;
}
return `'${baseUrl}'`;
}

getDefines(inRendererDir = true): Record<string, string> {
const defines: Record<string, string> = {};
if (!this.pluginConfig.renderer.entryPoints || !Array.isArray(this.pluginConfig.renderer.entryPoints)) {
throw new Error('Required config option "renderer.entryPoints" has not been defined');
}
for (const entryPoint of this.pluginConfig.renderer.entryPoints) {
const entryKey = this.toEnvironmentVariable(entryPoint);
// Vite uses html files as the entry point, so the html file is always present.
defines[entryKey] = this.rendererEntryPoint(entryPoint, inRendererDir, 'index.html');
defines[`process.env.${entryKey}`] = defines[entryKey];

const preloadDefineKey = this.toEnvironmentVariable(entryPoint, true);
defines[preloadDefineKey] = this.getPreloadDefine(entryPoint);
defines[`process.env.${preloadDefineKey}`] = defines[preloadDefineKey];
}

return defines;
}

async getMainConfig(watch = false): Promise<UserConfig> {
if (!this.pluginConfig.main.config) {
throw new Error('Required option "main.config" has not been defined');
}

const loadResult = await this.resolveConfig(this.pluginConfig.main.config)!;
const { build: userBuild = {}, define: userDefine, plugins = [], ...userConfig } = loadResult!.config;
const build: BuildOptions = {
...userBuild,
// User Configuration First Priority.
watch: userBuild.watch ?? (watch ? {} : null),
outDir: userBuild.outDir ?? path.join(this.baseDir, 'main'),
};
const define = { ...this.getDefines(), ...userDefine };

return <UserConfig>{
...userConfig,
build,
define,
plugins: plugins.concat(externalBuiltins()),
};
}

async getPreloadConfigForEntryPoint(entryPoint: VitePluginEntryPoint, watch = false): Promise<UserConfig> {
let config: UserConfig = {};
if (!entryPoint.preload?.js) {
return config;
}
if (entryPoint.preload?.config) {
config = (await this.resolveConfig(entryPoint.preload.config))!.config;
}

const { build: userBuild = {}, plugins = [], ...userConfig } = config;
const build: BuildOptions = {
...userBuild,
// User Configuration First Priority.
lib: userBuild.lib ?? {
entry: entryPoint.preload.js,
// At present, Electron can only support CommonJs.
formats: ['cjs'],
fileName: () => '[name].js',
},
watch: userBuild.watch ?? (watch ? {} : null),
outDir: path.join(this.baseDir, 'renderer', entryPoint.name),
};

return <UserConfig>{
...userConfig,
build,
plugins: plugins.concat(externalBuiltins()),
};
}

private toEnvironmentVariable(entryPoint: VitePluginEntryPoint, preload = false): string {
const suffix = preload ? '_PRELOAD_VITE_ENTRY' : '_VITE_ENTRY';
return `${entryPoint.name.toUpperCase().replace(/ /g, '_')}${suffix}`;
}

private getPreloadDefine(entryPoint: VitePluginEntryPoint): string {
if (entryPoint.preload?.js) {
if (this.isProd) {
return `require('path').resolve(__dirname, '../renderer', '${entryPoint.name}', 'preload.js')`;
}
return `'${path.resolve(this.baseDir, 'renderer', entryPoint.name, 'preload.js').replace(/\\/g, '\\\\')}'`;
} else {
// If this entry-point has no configured preload script just map this constant to `undefined`
// so that any code using it still works. This makes quick-start / docs simpler.
return 'undefined';
}
}
}
Loading

0 comments on commit 6abc16a

Please sign in to comment.