mirror of
https://github.com/alex8088/electron-vite.git
synced 2025-04-29 05:39:02 +08:00
352 lines
11 KiB
TypeScript
352 lines
11 KiB
TypeScript
import path from 'path'
|
|
import * as fs from 'fs'
|
|
import colors from 'picocolors'
|
|
import { builtinModules, createRequire } from 'module'
|
|
import { Plugin, mergeConfig, normalizePath } from 'vite'
|
|
|
|
export interface ElectronPluginOptions {
|
|
root?: string
|
|
}
|
|
|
|
function findLibEntry(root: string, scope: string): string {
|
|
for (const name of ['index', scope]) {
|
|
for (const ext of ['js', 'ts', 'mjs', 'cjs']) {
|
|
const entryFile = path.resolve(root, 'src', scope, `${name}.${ext}`)
|
|
if (fs.existsSync(entryFile)) {
|
|
return entryFile
|
|
}
|
|
}
|
|
}
|
|
return ''
|
|
}
|
|
|
|
function findInput(root: string, scope = 'renderer'): string {
|
|
const rendererDir = path.resolve(root, 'src', scope, 'index.html')
|
|
if (fs.existsSync(rendererDir)) {
|
|
return rendererDir
|
|
}
|
|
return ''
|
|
}
|
|
|
|
function processEnvDefine(): Record<string, string> {
|
|
return {
|
|
'process.env': `process.env`,
|
|
'global.process.env': `global.process.env`,
|
|
'globalThis.process.env': `globalThis.process.env`
|
|
}
|
|
}
|
|
|
|
export function electronMainVitePlugin(options?: ElectronPluginOptions): Plugin[] {
|
|
return [
|
|
{
|
|
name: 'vite:electron-main-preset-config',
|
|
apply: 'build',
|
|
enforce: 'pre',
|
|
config(config): void {
|
|
const root = options?.root || process.cwd()
|
|
|
|
const electornVer = getElectronMainVer(root)
|
|
const nodeTarget = getElectronNodeTarget(electornVer)
|
|
|
|
const defaultConfig = {
|
|
build: {
|
|
outDir: path.resolve(root, 'out', 'main'),
|
|
target: nodeTarget,
|
|
lib: {
|
|
entry: findLibEntry(root, 'main'),
|
|
formats: ['cjs']
|
|
},
|
|
rollupOptions: {
|
|
external: ['electron', ...builtinModules.flatMap(m => [m, `node:${m}`])],
|
|
output: {
|
|
entryFileNames: '[name].js'
|
|
}
|
|
},
|
|
minify: false
|
|
}
|
|
}
|
|
|
|
const buildConfig = mergeConfig(defaultConfig.build, config.build || {})
|
|
config.build = buildConfig
|
|
|
|
config.define = config.define || {}
|
|
config.define = { ...processEnvDefine(), ...config.define }
|
|
}
|
|
},
|
|
{
|
|
name: 'vite:electron-main-resolved-config',
|
|
apply: 'build',
|
|
enforce: 'post',
|
|
configResolved(config): void {
|
|
const build = config.build
|
|
if (!build.target) {
|
|
throw new Error('build target required for the electron vite main config')
|
|
} else {
|
|
const targets = Array.isArray(build.target) ? build.target : [build.target]
|
|
if (targets.some(t => !t.startsWith('node'))) {
|
|
throw new Error('the electron vite main config build target must be node')
|
|
}
|
|
}
|
|
|
|
const lib = build.lib
|
|
if (!lib) {
|
|
throw new Error('build lib field required for the electron vite main config')
|
|
} else {
|
|
if (!lib.entry) {
|
|
throw new Error('build entry field required for the electron vite main config')
|
|
}
|
|
if (!lib.formats) {
|
|
throw new Error('build format field required for the electron vite main config')
|
|
} else if (!lib.formats.includes('cjs')) {
|
|
throw new Error('the electron vite main config build lib format must be cjs')
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
export function electronPreloadVitePlugin(options?: ElectronPluginOptions): Plugin[] {
|
|
return [
|
|
{
|
|
name: 'vite:electron-preload-preset-config',
|
|
apply: 'build',
|
|
enforce: 'pre',
|
|
config(config): void {
|
|
const root = options?.root || process.cwd()
|
|
|
|
const electornVer = getElectronMainVer(root)
|
|
const nodeTarget = getElectronNodeTarget(electornVer)
|
|
|
|
const defaultConfig = {
|
|
build: {
|
|
outDir: path.resolve(root, 'out', 'preload'),
|
|
target: nodeTarget,
|
|
rollupOptions: {
|
|
external: ['electron', ...builtinModules.flatMap(m => [m, `node:${m}`])],
|
|
output: {
|
|
entryFileNames: '[name].js'
|
|
}
|
|
},
|
|
minify: false
|
|
}
|
|
}
|
|
|
|
const build = config.build || {}
|
|
const rollupOptions = build.rollupOptions || {}
|
|
if (!rollupOptions.input) {
|
|
defaultConfig.build['lib'] = {
|
|
entry: findLibEntry(root, 'preload'),
|
|
formats: ['cjs']
|
|
}
|
|
} else {
|
|
if (!rollupOptions.output) {
|
|
defaultConfig.build.rollupOptions.output['format'] = 'cjs'
|
|
}
|
|
}
|
|
|
|
const buildConfig = mergeConfig(defaultConfig.build, config.build || {})
|
|
config.build = buildConfig
|
|
|
|
config.define = config.define || {}
|
|
config.define = { ...processEnvDefine(), ...config.define }
|
|
}
|
|
},
|
|
{
|
|
name: 'vite:electron-preload-resolved-config',
|
|
apply: 'build',
|
|
enforce: 'post',
|
|
configResolved(config): void {
|
|
const build = config.build
|
|
if (!build.target) {
|
|
throw new Error('build target required for the electron vite preload config')
|
|
} else {
|
|
const targets = Array.isArray(build.target) ? build.target : [build.target]
|
|
if (targets.some(t => !t.startsWith('node'))) {
|
|
throw new Error('the electron vite preload config build target must be node')
|
|
}
|
|
}
|
|
|
|
const lib = build.lib
|
|
if (!lib) {
|
|
const rollupOptions = build.rollupOptions
|
|
if (!rollupOptions?.input) {
|
|
throw new Error('build lib field required for the electron vite preload config')
|
|
} else {
|
|
const output = rollupOptions?.output
|
|
if (output) {
|
|
const formats = Array.isArray(output) ? output : [output]
|
|
if (!formats.some(f => f !== 'cjs')) {
|
|
throw new Error('the electron vite preload config output format must be cjs')
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if (!lib.entry) {
|
|
throw new Error('build entry field required for the electron vite preload config')
|
|
}
|
|
if (!lib.formats) {
|
|
throw new Error('build format field required for the electron vite preload config')
|
|
} else if (!lib.formats.includes('cjs')) {
|
|
throw new Error('the electron vite preload config lib format must be cjs')
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
export function electronRendererVitePlugin(options?: ElectronPluginOptions): Plugin[] {
|
|
return [
|
|
{
|
|
name: 'vite:electron-renderer-preset-config',
|
|
enforce: 'pre',
|
|
config(config): void {
|
|
const root = options?.root || process.cwd()
|
|
|
|
config.base = config.mode === 'production' ? './' : config.base
|
|
config.root = config.root || './src/renderer'
|
|
|
|
const electornVer = getElectronMainVer(root)
|
|
const chromeTarget = getElectronChromeTarget(electornVer)
|
|
|
|
const emptyOutDir = (): boolean => {
|
|
let outDir = config.build?.outDir
|
|
if (outDir) {
|
|
if (!path.isAbsolute(outDir)) {
|
|
outDir = path.resolve(root, outDir)
|
|
}
|
|
const resolvedRoot = normalizePath(path.resolve(root))
|
|
return normalizePath(outDir).startsWith(resolvedRoot + '/')
|
|
}
|
|
return true
|
|
}
|
|
|
|
const defaultConfig = {
|
|
build: {
|
|
outDir: path.resolve(root, 'out', 'renderer'),
|
|
target: chromeTarget,
|
|
polyfillModulePreload: false,
|
|
rollupOptions: {
|
|
input: findInput(root),
|
|
external: [...builtinModules.flatMap(m => [m, `node:${m}`])]
|
|
},
|
|
minify: false,
|
|
emptyOutDir: emptyOutDir()
|
|
}
|
|
}
|
|
|
|
const buildConfig = mergeConfig(defaultConfig.build, config.build || {})
|
|
config.build = buildConfig
|
|
}
|
|
},
|
|
{
|
|
name: 'vite:electron-renderer-resolved-config',
|
|
enforce: 'post',
|
|
configResolved(config): void {
|
|
if (config.base !== './' && config.base !== '/') {
|
|
config.logger.warn(colors.yellow('should not set base field for the electron vite renderer config'))
|
|
}
|
|
|
|
const build = config.build
|
|
if (!build.target) {
|
|
throw new Error('build target required for the electron vite renderer config')
|
|
} else {
|
|
const targets = Array.isArray(build.target) ? build.target : [build.target]
|
|
if (targets.some(t => !t.startsWith('chrome'))) {
|
|
throw new Error('the electron vite renderer config build target must be chrome')
|
|
}
|
|
}
|
|
|
|
const rollupOptions = build.rollupOptions
|
|
if (!rollupOptions.input) {
|
|
config.logger.warn(colors.yellow(`index.html file is not found in ${colors.dim('/src/renderer')} directory`))
|
|
throw new Error('build rollupOptions input field required for the electron vite renderer config')
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
export function electronConfigServeVitePlugin(options: {
|
|
configFile: string
|
|
configFileDependencies: string[]
|
|
}): Plugin {
|
|
const getShortName = (file: string, root: string): string => {
|
|
return file.startsWith(root + '/') ? path.posix.relative(root, file) : file
|
|
}
|
|
|
|
return {
|
|
name: 'vite:electron-config-serve',
|
|
apply: 'serve',
|
|
handleHotUpdate({ file, server }): void {
|
|
const { config } = server
|
|
const logger = config.logger
|
|
const shortFile = getShortName(file, config.root)
|
|
const isConfig = file === options.configFile
|
|
const isConfigDependency = options.configFileDependencies.some(name => file === path.resolve(name))
|
|
if (isConfig || isConfigDependency) {
|
|
logger.info(`[config change] ${colors.dim(shortFile)}`)
|
|
logger.info(colors.green(`${path.relative(process.cwd(), file)} changed, restarting server...`), {
|
|
clear: true,
|
|
timestamp: true
|
|
})
|
|
try {
|
|
server.restart()
|
|
} catch (e) {
|
|
logger.error(colors.red('failed to restart server'), { error: e as Error })
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function getElectronMainVer(root: string): string {
|
|
let mainVer = process.env.ELECTRON_MAIN_VER || ''
|
|
if (!mainVer) {
|
|
const electronModulePath = path.resolve(root, 'node_modules', 'electron')
|
|
const pkg = path.join(electronModulePath, 'package.json')
|
|
if (fs.existsSync(pkg)) {
|
|
const require = createRequire(import.meta.url)
|
|
const version = require(pkg).version
|
|
mainVer = version.split('.')[0]
|
|
process.env.ELECTRON_MAIN_VER = mainVer
|
|
}
|
|
}
|
|
return mainVer
|
|
}
|
|
|
|
function getElectronNodeTarget(electronVer: string): string {
|
|
const nodeVer = {
|
|
'18': '16.13',
|
|
'17': '16.13',
|
|
'16': '16.9',
|
|
'15': '16.5',
|
|
'14': '14.17',
|
|
'13': '14.17',
|
|
'12': '14.16',
|
|
'11': '12.18'
|
|
}
|
|
if (electronVer && parseInt(electronVer) > 10) {
|
|
return 'node' + nodeVer[electronVer]
|
|
}
|
|
return ''
|
|
}
|
|
|
|
function getElectronChromeTarget(electronVer: string): string {
|
|
const chromeVer = {
|
|
'18': '99',
|
|
'17': '98',
|
|
'16': '96',
|
|
'15': '94',
|
|
'14': '93',
|
|
'13': '91',
|
|
'12': '89',
|
|
'11': '87'
|
|
}
|
|
if (electronVer && parseInt(electronVer) > 10) {
|
|
return 'chrome' + chromeVer[electronVer]
|
|
}
|
|
return ''
|
|
}
|