mirror of
https://github.com/alex8088/electron-vite.git
synced 2026-07-08 07:01:07 +08:00
Compare commits
9 Commits
c3939ade45
...
397b02e384
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
397b02e384 | ||
|
|
a4f7693712 | ||
|
|
56fb519092 | ||
|
|
eb0a7e3ffe | ||
|
|
de70dfe1dc | ||
|
|
8892bf3679 | ||
|
|
2576484604 | ||
|
|
88f6db2239 | ||
|
|
0a79da03db |
14
CHANGELOG.md
14
CHANGELOG.md
@ -1,3 +1,17 @@
|
||||
### v5.0.0-beta.1 (_2025-10-29_)
|
||||
|
||||
- feat: enhanced string protection
|
||||
- feat: add `isolatedEntries` option for `preload` and `renderer` to build entries as standalone bundles [#154](https://github.com/alex8088/electron-vite/issues/154)
|
||||
- refactor(asset): remove redundant path normalization
|
||||
- refactor: split electron plugin into preset and validator plugins
|
||||
- refactor(config)!: restructure Electron Vite config interfaces
|
||||
- refactor(build): simplify build logic
|
||||
- refactor: replace `JSON.parse/stringify` with manual deep clone
|
||||
- perf: build compatibility target for Electron 39
|
||||
- fix(modulePath): prevent duplicate asset emission
|
||||
- fix(modulePath): support watch mode
|
||||
- chore: improve logging message clarity and consistency
|
||||
|
||||
### v5.0.0-beta.0 (_2025-10-19_)
|
||||
|
||||
- refactor(bytecodePlugin): improved bytecode bundle generation and made a new string protection plugin
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "electron-vite",
|
||||
"version": "5.0.0-beta.0",
|
||||
"version": "5.0.0-beta.1",
|
||||
"description": "Electron build tooling based on Vite",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
|
||||
35
src/build.ts
35
src/build.ts
@ -7,27 +7,22 @@ import { type InlineConfig, resolveConfig } from './config'
|
||||
export async function build(inlineConfig: InlineConfig = {}): Promise<void> {
|
||||
process.env.NODE_ENV_ELECTRON_VITE = 'production'
|
||||
const config = await resolveConfig(inlineConfig, 'build', 'production')
|
||||
if (config.config) {
|
||||
const mainViteConfig = config.config?.main
|
||||
if (mainViteConfig) {
|
||||
if (mainViteConfig.build?.watch) {
|
||||
mainViteConfig.build.watch = null
|
||||
|
||||
if (!config.config) {
|
||||
return
|
||||
}
|
||||
|
||||
// Build targets in order: main -> preload -> renderer
|
||||
const buildTargets = ['main', 'preload', 'renderer'] as const
|
||||
|
||||
for (const target of buildTargets) {
|
||||
const viteConfig = config.config[target]
|
||||
if (viteConfig) {
|
||||
// Disable watch mode in production builds
|
||||
if (viteConfig.build?.watch) {
|
||||
viteConfig.build.watch = null
|
||||
}
|
||||
await viteBuild(mainViteConfig)
|
||||
}
|
||||
const preloadViteConfig = config.config?.preload
|
||||
if (preloadViteConfig) {
|
||||
if (preloadViteConfig.build?.watch) {
|
||||
preloadViteConfig.build.watch = null
|
||||
}
|
||||
await viteBuild(preloadViteConfig)
|
||||
}
|
||||
const rendererViteConfig = config.config?.renderer
|
||||
if (rendererViteConfig) {
|
||||
if (rendererViteConfig.build?.watch) {
|
||||
rendererViteConfig.build.watch = null
|
||||
}
|
||||
await viteBuild(rendererViteConfig)
|
||||
await viteBuild(viteConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
180
src/config.ts
180
src/config.ts
@ -5,9 +5,8 @@ import { createRequire } from 'node:module'
|
||||
import colors from 'picocolors'
|
||||
import {
|
||||
type UserConfig as ViteConfig,
|
||||
type UserConfigExport as ViteConfigExport,
|
||||
type ConfigEnv,
|
||||
type Plugin,
|
||||
type PluginOption,
|
||||
type LogLevel,
|
||||
createLogger,
|
||||
mergeConfig,
|
||||
@ -15,89 +14,97 @@ import {
|
||||
} from 'vite'
|
||||
import { build } from 'esbuild'
|
||||
|
||||
import { electronMainVitePlugin, electronPreloadVitePlugin, electronRendererVitePlugin } from './plugins/electron'
|
||||
import {
|
||||
electronMainConfigPresetPlugin,
|
||||
electronMainConfigValidatorPlugin,
|
||||
electronPreloadConfigPresetPlugin,
|
||||
electronPreloadConfigValidatorPlugin,
|
||||
electronRendererConfigPresetPlugin,
|
||||
electronRendererConfigValidatorPlugin
|
||||
} from './plugins/electron'
|
||||
import assetPlugin from './plugins/asset'
|
||||
import workerPlugin from './plugins/worker'
|
||||
import importMetaPlugin from './plugins/importMeta'
|
||||
import esmShimPlugin from './plugins/esmShim'
|
||||
import modulePathPlugin from './plugins/modulePath'
|
||||
import { isObject, isFilePathESM } from './utils'
|
||||
import isolateEntriesPlugin from './plugins/isolateEntries'
|
||||
import { isObject, isFilePathESM, deepClone } from './utils'
|
||||
|
||||
export { defineConfig as defineViteConfig } from 'vite'
|
||||
|
||||
interface IsolatedEntriesOption {
|
||||
/**
|
||||
* Build each entry point as an isolated bundle without code splitting.
|
||||
*
|
||||
* When enabled, each entry will include all its dependencies inline,
|
||||
* preventing automatic code splitting across entries and ensuring each
|
||||
* output file is fully standalone.
|
||||
*
|
||||
* @experimental
|
||||
* @default false
|
||||
*/
|
||||
isolatedEntries?: boolean
|
||||
}
|
||||
|
||||
export interface MainViteConfig extends ViteConfig {}
|
||||
|
||||
export interface PreloadViteConfig extends ViteConfig, IsolatedEntriesOption {}
|
||||
|
||||
export interface RendererViteConfig extends ViteConfig, IsolatedEntriesOption {}
|
||||
|
||||
export interface UserConfig {
|
||||
/**
|
||||
* Vite config options for electron main process
|
||||
*
|
||||
* https://vitejs.dev/config/
|
||||
* @see https://vitejs.dev/config/
|
||||
*/
|
||||
main?: ViteConfig & { configFile?: string | false }
|
||||
main?: MainViteConfig
|
||||
/**
|
||||
* Vite config options for electron renderer process
|
||||
*
|
||||
* https://vitejs.dev/config/
|
||||
* @see https://vitejs.dev/config/
|
||||
*/
|
||||
renderer?: ViteConfig & { configFile?: string | false }
|
||||
renderer?: RendererViteConfig
|
||||
/**
|
||||
* Vite config options for electron preload files
|
||||
* Vite config options for electron preload scripts
|
||||
*
|
||||
* https://vitejs.dev/config/
|
||||
* @see https://vitejs.dev/config/
|
||||
*/
|
||||
preload?: ViteConfig & { configFile?: string | false }
|
||||
preload?: PreloadViteConfig
|
||||
}
|
||||
|
||||
export interface ElectronViteConfig {
|
||||
/**
|
||||
* Vite config options for electron main process
|
||||
*
|
||||
* https://vitejs.dev/config/
|
||||
*/
|
||||
main?: ViteConfigExport
|
||||
/**
|
||||
* Vite config options for electron renderer process
|
||||
*
|
||||
* https://vitejs.dev/config/
|
||||
*/
|
||||
renderer?: ViteConfigExport
|
||||
/**
|
||||
* Vite config options for electron preload files
|
||||
*
|
||||
* https://vitejs.dev/config/
|
||||
*/
|
||||
preload?: ViteConfigExport
|
||||
}
|
||||
|
||||
export type InlineConfig = Omit<ViteConfig, 'base'> & {
|
||||
configFile?: string | false
|
||||
envFile?: false
|
||||
ignoreConfigWarning?: boolean
|
||||
}
|
||||
|
||||
export type ElectronViteConfigFnObject = (env: ConfigEnv) => ElectronViteConfig
|
||||
export type ElectronViteConfigFnPromise = (env: ConfigEnv) => Promise<ElectronViteConfig>
|
||||
export type ElectronViteConfigFn = (env: ConfigEnv) => ElectronViteConfig | Promise<ElectronViteConfig>
|
||||
export type ElectronViteConfigFnObject = (env: ConfigEnv) => UserConfig
|
||||
export type ElectronViteConfigFnPromise = (env: ConfigEnv) => Promise<UserConfig>
|
||||
export type ElectronViteConfigFn = (env: ConfigEnv) => UserConfig | Promise<UserConfig>
|
||||
|
||||
export type ElectronViteConfigExport =
|
||||
| ElectronViteConfig
|
||||
| Promise<ElectronViteConfig>
|
||||
| UserConfig
|
||||
| Promise<UserConfig>
|
||||
| ElectronViteConfigFnObject
|
||||
| ElectronViteConfigFnPromise
|
||||
| ElectronViteConfigFn
|
||||
|
||||
/**
|
||||
* Type helper to make it easier to use `electron.vite.config.*`
|
||||
* accepts a direct {@link ElectronViteConfig} object, or a function that returns it.
|
||||
* accepts a direct {@link UserConfig} object, or a function that returns it.
|
||||
* The function receives a object that exposes two properties:
|
||||
* `command` (either `'build'` or `'serve'`), and `mode`.
|
||||
*/
|
||||
export function defineConfig(config: ElectronViteConfig): ElectronViteConfig
|
||||
export function defineConfig(config: Promise<ElectronViteConfig>): Promise<ElectronViteConfig>
|
||||
export function defineConfig(config: UserConfig): UserConfig
|
||||
export function defineConfig(config: Promise<UserConfig>): Promise<UserConfig>
|
||||
export function defineConfig(config: ElectronViteConfigFnObject): ElectronViteConfigFnObject
|
||||
export function defineConfig(config: ElectronViteConfigFnPromise): ElectronViteConfigFnPromise
|
||||
export function defineConfig(config: ElectronViteConfigExport): ElectronViteConfigExport
|
||||
export function defineConfig(config: ElectronViteConfigExport): ElectronViteConfigExport {
|
||||
return config
|
||||
}
|
||||
|
||||
export type InlineConfig = Omit<ViteConfig, 'base'> & {
|
||||
configFile?: string | false
|
||||
envFile?: false
|
||||
ignoreConfigWarning?: boolean
|
||||
}
|
||||
|
||||
export interface ResolvedConfig {
|
||||
config?: UserConfig
|
||||
configFile?: string
|
||||
@ -123,6 +130,7 @@ export async function resolveConfig(
|
||||
mode,
|
||||
command
|
||||
}
|
||||
|
||||
const loadResult = await loadConfigFromFile(
|
||||
configEnv,
|
||||
configFile,
|
||||
@ -130,15 +138,18 @@ export async function resolveConfig(
|
||||
config.logLevel,
|
||||
config.ignoreConfigWarning
|
||||
)
|
||||
|
||||
if (loadResult) {
|
||||
const root = config.root
|
||||
delete config.root
|
||||
delete config.configFile
|
||||
|
||||
config.configFile = false
|
||||
|
||||
const outDir = config.build?.outDir
|
||||
|
||||
if (loadResult.config.main) {
|
||||
const mainViteConfig: ViteConfig = mergeConfig(loadResult.config.main, deepClone(config))
|
||||
const mainViteConfig: MainViteConfig = mergeConfig(loadResult.config.main, deepClone(config))
|
||||
|
||||
mainViteConfig.mode = inlineConfig.mode || mainViteConfig.mode || defaultMode
|
||||
|
||||
@ -146,47 +157,70 @@ export async function resolveConfig(
|
||||
resetOutDir(mainViteConfig, outDir, 'main')
|
||||
}
|
||||
|
||||
mergePlugins(mainViteConfig, [
|
||||
...electronMainVitePlugin({ root }),
|
||||
const builtInMainPlugins: PluginOption[] = [
|
||||
electronMainConfigPresetPlugin({ root }),
|
||||
electronMainConfigValidatorPlugin(),
|
||||
assetPlugin(),
|
||||
workerPlugin(),
|
||||
modulePathPlugin(
|
||||
mergeConfig(
|
||||
{
|
||||
plugins: [electronMainVitePlugin({ root })[0], assetPlugin(), importMetaPlugin(), esmShimPlugin()]
|
||||
plugins: [electronMainConfigPresetPlugin({ root }), assetPlugin(), importMetaPlugin(), esmShimPlugin()]
|
||||
},
|
||||
mainViteConfig
|
||||
)
|
||||
),
|
||||
importMetaPlugin(),
|
||||
esmShimPlugin()
|
||||
])
|
||||
]
|
||||
|
||||
mainViteConfig.plugins = builtInMainPlugins.concat(mainViteConfig.plugins || [])
|
||||
|
||||
loadResult.config.main = mainViteConfig
|
||||
loadResult.config.main.configFile = false
|
||||
}
|
||||
|
||||
if (loadResult.config.preload) {
|
||||
const preloadViteConfig: ViteConfig = mergeConfig(loadResult.config.preload, deepClone(config))
|
||||
const preloadViteConfig: PreloadViteConfig = mergeConfig(loadResult.config.preload, deepClone(config))
|
||||
|
||||
preloadViteConfig.mode = inlineConfig.mode || preloadViteConfig.mode || defaultMode
|
||||
|
||||
if (outDir) {
|
||||
resetOutDir(preloadViteConfig, outDir, 'preload')
|
||||
}
|
||||
mergePlugins(preloadViteConfig, [
|
||||
...electronPreloadVitePlugin({ root }),
|
||||
|
||||
const builtInPreloadPlugins: PluginOption[] = [
|
||||
electronPreloadConfigPresetPlugin({ root }),
|
||||
electronPreloadConfigValidatorPlugin(),
|
||||
assetPlugin(),
|
||||
importMetaPlugin(),
|
||||
esmShimPlugin()
|
||||
])
|
||||
]
|
||||
|
||||
if (preloadViteConfig.isolatedEntries) {
|
||||
builtInPreloadPlugins.push(
|
||||
isolateEntriesPlugin(
|
||||
mergeConfig(
|
||||
{
|
||||
plugins: [
|
||||
electronPreloadConfigPresetPlugin({ root }),
|
||||
assetPlugin(),
|
||||
importMetaPlugin(),
|
||||
esmShimPlugin()
|
||||
]
|
||||
},
|
||||
preloadViteConfig
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
preloadViteConfig.plugins = builtInPreloadPlugins.concat(preloadViteConfig.plugins)
|
||||
|
||||
loadResult.config.preload = preloadViteConfig
|
||||
loadResult.config.preload.configFile = false
|
||||
}
|
||||
|
||||
if (loadResult.config.renderer) {
|
||||
const rendererViteConfig: ViteConfig = mergeConfig(loadResult.config.renderer, deepClone(config))
|
||||
const rendererViteConfig: RendererViteConfig = mergeConfig(loadResult.config.renderer, deepClone(config))
|
||||
|
||||
rendererViteConfig.mode = inlineConfig.mode || rendererViteConfig.mode || defaultMode
|
||||
|
||||
@ -194,10 +228,27 @@ export async function resolveConfig(
|
||||
resetOutDir(rendererViteConfig, outDir, 'renderer')
|
||||
}
|
||||
|
||||
mergePlugins(rendererViteConfig, electronRendererVitePlugin({ root }))
|
||||
const builtInRendererPlugins: PluginOption[] = [
|
||||
electronRendererConfigPresetPlugin({ root }),
|
||||
electronRendererConfigValidatorPlugin()
|
||||
]
|
||||
|
||||
if (rendererViteConfig.isolatedEntries) {
|
||||
builtInRendererPlugins.push(
|
||||
isolateEntriesPlugin(
|
||||
mergeConfig(
|
||||
{
|
||||
plugins: [electronRendererConfigPresetPlugin({ root })]
|
||||
},
|
||||
rendererViteConfig
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
rendererViteConfig.plugins = builtInRendererPlugins.concat(rendererViteConfig.plugins || [])
|
||||
|
||||
loadResult.config.renderer = rendererViteConfig
|
||||
loadResult.config.renderer.configFile = false
|
||||
}
|
||||
|
||||
userConfig = loadResult.config
|
||||
@ -215,10 +266,6 @@ export async function resolveConfig(
|
||||
return resolved
|
||||
}
|
||||
|
||||
function deepClone<T>(data: T): T {
|
||||
return JSON.parse(JSON.stringify(data))
|
||||
}
|
||||
|
||||
function resetOutDir(config: ViteConfig, outDir: string, subOutDir: string): void {
|
||||
let userOutDir = config.build?.outDir
|
||||
if (outDir === userOutDir) {
|
||||
@ -231,11 +278,6 @@ function resetOutDir(config: ViteConfig, outDir: string, subOutDir: string): voi
|
||||
}
|
||||
}
|
||||
|
||||
function mergePlugins(config: ViteConfig, plugins: Plugin[]): void {
|
||||
const userPlugins = config.plugins || []
|
||||
config.plugins = userPlugins.concat(plugins)
|
||||
}
|
||||
|
||||
const CONFIG_FILE_NAME = 'electron.vite.config'
|
||||
|
||||
export async function loadConfigFromFile(
|
||||
|
||||
@ -69,6 +69,7 @@ export function getElectronNodeTarget(): string {
|
||||
const electronVer = getElectronMajorVer()
|
||||
|
||||
const nodeVer = {
|
||||
'39': '22.20',
|
||||
'38': '22.19',
|
||||
'37': '22.16',
|
||||
'36': '22.14',
|
||||
@ -99,6 +100,7 @@ export function getElectronChromeTarget(): string {
|
||||
const electronVer = getElectronMajorVer()
|
||||
|
||||
const chromeVer = {
|
||||
'39': '142',
|
||||
'38': '140',
|
||||
'37': '138',
|
||||
'36': '136',
|
||||
|
||||
@ -50,368 +50,363 @@ function resolveBuildOutputs(
|
||||
return outputs
|
||||
}
|
||||
|
||||
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()
|
||||
export function electronMainConfigPresetPlugin(options?: ElectronPluginOptions): Plugin {
|
||||
return {
|
||||
name: 'vite:electron-main-config-preset',
|
||||
apply: 'build',
|
||||
enforce: 'pre',
|
||||
config(config): void {
|
||||
const root = options?.root || process.cwd()
|
||||
|
||||
const nodeTarget = getElectronNodeTarget()
|
||||
const nodeTarget = getElectronNodeTarget()
|
||||
|
||||
const pkg = loadPackageData() || { type: 'commonjs' }
|
||||
const pkg = loadPackageData() || { type: 'commonjs' }
|
||||
|
||||
const format = pkg.type && pkg.type === 'module' && supportESM() ? 'es' : 'cjs'
|
||||
const format = pkg.type && pkg.type === 'module' && supportESM() ? 'es' : 'cjs'
|
||||
|
||||
const defaultConfig = {
|
||||
resolve: {
|
||||
browserField: false,
|
||||
mainFields: ['module', 'jsnext:main', 'jsnext'],
|
||||
conditions: ['node']
|
||||
const defaultConfig = {
|
||||
resolve: {
|
||||
browserField: false,
|
||||
mainFields: ['module', 'jsnext:main', 'jsnext'],
|
||||
conditions: ['node']
|
||||
},
|
||||
build: {
|
||||
outDir: path.resolve(root, 'out', 'main'),
|
||||
target: nodeTarget,
|
||||
assetsDir: 'chunks',
|
||||
rollupOptions: {
|
||||
external: ['electron', /^electron\/.+/, ...builtinModules.flatMap(m => [m, `node:${m}`])],
|
||||
output: {}
|
||||
},
|
||||
build: {
|
||||
outDir: path.resolve(root, 'out', 'main'),
|
||||
target: nodeTarget,
|
||||
assetsDir: 'chunks',
|
||||
rollupOptions: {
|
||||
external: ['electron', /^electron\/.+/, ...builtinModules.flatMap(m => [m, `node:${m}`])],
|
||||
output: {}
|
||||
},
|
||||
reportCompressedSize: false,
|
||||
minify: false
|
||||
}
|
||||
reportCompressedSize: false,
|
||||
minify: false
|
||||
}
|
||||
|
||||
const build = config.build || {}
|
||||
const rollupOptions = build.rollupOptions || {}
|
||||
if (!rollupOptions.input) {
|
||||
const libOptions = build.lib
|
||||
const outputOptions = rollupOptions.output
|
||||
defaultConfig.build['lib'] = {
|
||||
entry: findLibEntry(root, 'main'),
|
||||
formats:
|
||||
libOptions && libOptions.formats && libOptions.formats.length > 0
|
||||
? []
|
||||
: [
|
||||
outputOptions && !Array.isArray(outputOptions) && outputOptions.format
|
||||
? outputOptions.format
|
||||
: format
|
||||
]
|
||||
}
|
||||
} else {
|
||||
defaultConfig.build.rollupOptions.output['format'] = format
|
||||
}
|
||||
|
||||
defaultConfig.build.rollupOptions.output['assetFileNames'] = path.posix.join(
|
||||
build.assetsDir || defaultConfig.build.assetsDir,
|
||||
'[name]-[hash].[ext]'
|
||||
)
|
||||
|
||||
const buildConfig = mergeConfig(defaultConfig.build, build)
|
||||
config.build = buildConfig
|
||||
|
||||
config.resolve = mergeConfig(defaultConfig.resolve, config.resolve || {})
|
||||
|
||||
config.define = config.define || {}
|
||||
config.define = { ...processEnvDefine(), ...config.define }
|
||||
|
||||
config.envPrefix = config.envPrefix || ['MAIN_VITE_', 'VITE_']
|
||||
|
||||
config.publicDir = config.publicDir || 'resources'
|
||||
// do not copy public dir
|
||||
config.build.copyPublicDir = false
|
||||
// module preload polyfill does not apply to nodejs (main process)
|
||||
config.build.modulePreload = false
|
||||
// enable ssr build
|
||||
config.build.ssr = true
|
||||
config.build.ssrEmitAssets = true
|
||||
config.ssr = { ...config.ssr, ...{ noExternal: true } }
|
||||
}
|
||||
},
|
||||
{
|
||||
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 option is required in 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 option must be "node?".')
|
||||
}
|
||||
}
|
||||
|
||||
const build = config.build || {}
|
||||
const rollupOptions = build.rollupOptions || {}
|
||||
if (!rollupOptions.input) {
|
||||
const libOptions = build.lib
|
||||
const rollupOptions = build.rollupOptions
|
||||
|
||||
if (!(libOptions && libOptions.entry) && !rollupOptions?.input) {
|
||||
throw new Error(
|
||||
'An entry point is required in the electron vite main config, ' +
|
||||
'which can be specified using "build.lib.entry" or "build.rollupOptions.input".'
|
||||
)
|
||||
}
|
||||
|
||||
const resolvedOutputs = resolveBuildOutputs(rollupOptions.output, libOptions)
|
||||
|
||||
if (resolvedOutputs) {
|
||||
const outputs = Array.isArray(resolvedOutputs) ? resolvedOutputs : [resolvedOutputs]
|
||||
if (outputs.length > 1) {
|
||||
throw new Error('The electron vite main config does not support multiple outputs.')
|
||||
} else {
|
||||
const outpout = outputs[0]
|
||||
if (['es', 'cjs'].includes(outpout.format || '')) {
|
||||
if (outpout.format === 'es' && !supportESM()) {
|
||||
throw new Error(
|
||||
'The electron vite main config output format does not support "es", ' +
|
||||
'you can upgrade electron to the latest version or switch to "cjs" format.'
|
||||
)
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
`The electron vite main config output format must be "cjs"${supportESM() ? ' or "es"' : ''}.`
|
||||
)
|
||||
}
|
||||
}
|
||||
const outputOptions = rollupOptions.output
|
||||
defaultConfig.build['lib'] = {
|
||||
entry: findLibEntry(root, 'main'),
|
||||
formats:
|
||||
libOptions && libOptions.formats && libOptions.formats.length > 0
|
||||
? []
|
||||
: [outputOptions && !Array.isArray(outputOptions) && outputOptions.format ? outputOptions.format : format]
|
||||
}
|
||||
} else {
|
||||
defaultConfig.build.rollupOptions.output['format'] = format
|
||||
}
|
||||
|
||||
defaultConfig.build.rollupOptions.output['assetFileNames'] = path.posix.join(
|
||||
build.assetsDir || defaultConfig.build.assetsDir,
|
||||
'[name]-[hash].[ext]'
|
||||
)
|
||||
|
||||
const buildConfig = mergeConfig(defaultConfig.build, build)
|
||||
config.build = buildConfig
|
||||
|
||||
config.resolve = mergeConfig(defaultConfig.resolve, config.resolve || {})
|
||||
|
||||
config.define = config.define || {}
|
||||
config.define = { ...processEnvDefine(), ...config.define }
|
||||
|
||||
config.envPrefix = config.envPrefix || ['MAIN_VITE_', 'VITE_']
|
||||
|
||||
config.publicDir = config.publicDir || 'resources'
|
||||
// do not copy public dir
|
||||
config.build.copyPublicDir = false
|
||||
// module preload polyfill does not apply to nodejs (main process)
|
||||
config.build.modulePreload = false
|
||||
// enable ssr build
|
||||
config.build.ssr = true
|
||||
config.build.ssrEmitAssets = true
|
||||
config.ssr = { ...config.ssr, ...{ noExternal: true } }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
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 nodeTarget = getElectronNodeTarget()
|
||||
|
||||
const pkg = loadPackageData() || { type: 'commonjs' }
|
||||
|
||||
const format = pkg.type && pkg.type === 'module' && supportESM() ? 'es' : 'cjs'
|
||||
|
||||
const defaultConfig = {
|
||||
ssr: {
|
||||
resolve: {
|
||||
conditions: ['module', 'browser', 'development|production'],
|
||||
mainFields: ['browser', 'module', 'jsnext:main', 'jsnext']
|
||||
}
|
||||
},
|
||||
build: {
|
||||
outDir: path.resolve(root, 'out', 'preload'),
|
||||
target: nodeTarget,
|
||||
assetsDir: 'chunks',
|
||||
rollupOptions: {
|
||||
external: ['electron', /^electron\/.+/, ...builtinModules.flatMap(m => [m, `node:${m}`])],
|
||||
output: {}
|
||||
},
|
||||
reportCompressedSize: false,
|
||||
minify: false
|
||||
}
|
||||
export function electronMainConfigValidatorPlugin(): Plugin {
|
||||
return {
|
||||
name: 'vite:electron-main-config-validator',
|
||||
apply: 'build',
|
||||
enforce: 'post',
|
||||
configResolved(config): void {
|
||||
const build = config.build
|
||||
if (!build.target) {
|
||||
throw new Error('build.target option is required in 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 option must be "node?".')
|
||||
}
|
||||
|
||||
const build = config.build || {}
|
||||
const rollupOptions = build.rollupOptions || {}
|
||||
if (!rollupOptions.input) {
|
||||
const libOptions = build.lib
|
||||
const outputOptions = rollupOptions.output
|
||||
defaultConfig.build['lib'] = {
|
||||
entry: findLibEntry(root, 'preload'),
|
||||
formats:
|
||||
libOptions && libOptions.formats && libOptions.formats.length > 0
|
||||
? []
|
||||
: [
|
||||
outputOptions && !Array.isArray(outputOptions) && outputOptions.format
|
||||
? outputOptions.format
|
||||
: format
|
||||
]
|
||||
}
|
||||
} else {
|
||||
defaultConfig.build.rollupOptions.output['format'] = format
|
||||
}
|
||||
|
||||
defaultConfig.build.rollupOptions.output['assetFileNames'] = path.posix.join(
|
||||
build.assetsDir || defaultConfig.build.assetsDir,
|
||||
'[name]-[hash].[ext]'
|
||||
)
|
||||
|
||||
const buildConfig = mergeConfig(defaultConfig.build, build)
|
||||
config.build = buildConfig
|
||||
|
||||
const resolvedOutputs = resolveBuildOutputs(config.build.rollupOptions!.output, config.build.lib || false)
|
||||
|
||||
if (resolvedOutputs) {
|
||||
const outputs = Array.isArray(resolvedOutputs) ? resolvedOutputs : [resolvedOutputs]
|
||||
|
||||
if (outputs.find(({ format }) => format === 'es')) {
|
||||
if (Array.isArray(config.build.rollupOptions!.output)) {
|
||||
config.build.rollupOptions!.output.forEach(output => {
|
||||
if (output.format === 'es') {
|
||||
output['entryFileNames'] = '[name].mjs'
|
||||
output['chunkFileNames'] = '[name]-[hash].mjs'
|
||||
}
|
||||
})
|
||||
} else {
|
||||
config.build.rollupOptions!.output!['entryFileNames'] = '[name].mjs'
|
||||
config.build.rollupOptions!.output!['chunkFileNames'] = '[name]-[hash].mjs'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config.define = config.define || {}
|
||||
config.define = { ...processEnvDefine(), ...config.define }
|
||||
|
||||
config.envPrefix = config.envPrefix || ['PRELOAD_VITE_', 'VITE_']
|
||||
|
||||
config.publicDir = config.publicDir || 'resources'
|
||||
// do not copy public dir
|
||||
config.build.copyPublicDir = false
|
||||
// module preload polyfill does not apply to nodejs (preload scripts)
|
||||
config.build.modulePreload = false
|
||||
// enable ssr build
|
||||
config.build.ssr = true
|
||||
config.build.ssrEmitAssets = true
|
||||
config.ssr = mergeConfig(defaultConfig.ssr, config.ssr || {})
|
||||
config.ssr.noExternal = true
|
||||
}
|
||||
},
|
||||
{
|
||||
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 option is required in the electron vite preload config.')
|
||||
|
||||
const libOptions = build.lib
|
||||
const rollupOptions = build.rollupOptions
|
||||
|
||||
if (!(libOptions && libOptions.entry) && !rollupOptions?.input) {
|
||||
throw new Error(
|
||||
'An entry point is required in the electron vite main config, ' +
|
||||
'which can be specified using "build.lib.entry" or "build.rollupOptions.input".'
|
||||
)
|
||||
}
|
||||
|
||||
const resolvedOutputs = resolveBuildOutputs(rollupOptions.output, libOptions)
|
||||
|
||||
if (resolvedOutputs) {
|
||||
const outputs = Array.isArray(resolvedOutputs) ? resolvedOutputs : [resolvedOutputs]
|
||||
if (outputs.length > 1) {
|
||||
throw new Error('The electron vite main config does not support multiple outputs.')
|
||||
} 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 libOptions = build.lib
|
||||
const rollupOptions = build.rollupOptions
|
||||
|
||||
if (!(libOptions && libOptions.entry) && !rollupOptions?.input) {
|
||||
throw new Error(
|
||||
'An entry point is required in the electron vite preload config, ' +
|
||||
'which can be specified using "build.lib.entry" or "build.rollupOptions.input".'
|
||||
)
|
||||
}
|
||||
|
||||
const resolvedOutputs = resolveBuildOutputs(rollupOptions.output, libOptions)
|
||||
|
||||
if (resolvedOutputs) {
|
||||
const outputs = Array.isArray(resolvedOutputs) ? resolvedOutputs : [resolvedOutputs]
|
||||
if (outputs.length > 1) {
|
||||
throw new Error('The electron vite preload config does not support multiple outputs.')
|
||||
} else {
|
||||
const outpout = outputs[0]
|
||||
if (['es', 'cjs'].includes(outpout.format || '')) {
|
||||
if (outpout.format === 'es' && !supportESM()) {
|
||||
throw new Error(
|
||||
'The electron vite preload config output format does not support "es", ' +
|
||||
'you can upgrade electron to the latest version or switch to "cjs" format.'
|
||||
)
|
||||
}
|
||||
} else {
|
||||
const outpout = outputs[0]
|
||||
if (['es', 'cjs'].includes(outpout.format || '')) {
|
||||
if (outpout.format === 'es' && !supportESM()) {
|
||||
throw new Error(
|
||||
`The electron vite preload config output format must be "cjs"${supportESM() ? ' or "es"' : ''}.`
|
||||
'The electron vite main config output format does not support "es", ' +
|
||||
'you can upgrade electron to the latest version or switch to "cjs" format.'
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
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' || process.env.NODE_ENV_ELECTRON_VITE === 'production' ? './' : config.base
|
||||
config.root = config.root || './src/renderer'
|
||||
|
||||
const chromeTarget = getElectronChromeTarget()
|
||||
|
||||
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,
|
||||
modulePreload: { polyfill: false },
|
||||
rollupOptions: {
|
||||
input: findInput(root)
|
||||
},
|
||||
reportCompressedSize: false,
|
||||
minify: false,
|
||||
emptyOutDir: emptyOutDir()
|
||||
}
|
||||
}
|
||||
|
||||
if (config.build?.outDir) {
|
||||
config.build.outDir = path.resolve(root, config.build.outDir)
|
||||
}
|
||||
|
||||
const buildConfig = mergeConfig(defaultConfig.build, config.build || {})
|
||||
config.build = buildConfig
|
||||
|
||||
config.envDir = config.envDir || path.resolve(root)
|
||||
|
||||
config.envPrefix = config.envPrefix || ['RENDERER_VITE_', 'VITE_']
|
||||
}
|
||||
},
|
||||
{
|
||||
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" option for the electron vite renderer config.'))
|
||||
}
|
||||
|
||||
const build = config.build
|
||||
if (!build.target) {
|
||||
throw new Error('build.target option is required in the electron vite renderer config.')
|
||||
} else {
|
||||
const targets = Array.isArray(build.target) ? build.target : [build.target]
|
||||
if (targets.some(t => !t.startsWith('chrome') && !/^es((202\d{1})|next)$/.test(t))) {
|
||||
config.logger.warn(
|
||||
'The electron vite renderer config build.target is not "chrome?" or "es?". This could be a mistake.'
|
||||
} else {
|
||||
throw new Error(
|
||||
`The electron vite main config output format must be "cjs"${supportESM() ? ' or "es"' : ''}.`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 option is required in the electron vite renderer config.')
|
||||
export function electronPreloadConfigPresetPlugin(options?: ElectronPluginOptions): Plugin {
|
||||
return {
|
||||
name: 'vite:electron-preload-config-preset',
|
||||
apply: 'build',
|
||||
enforce: 'pre',
|
||||
config(config): void {
|
||||
const root = options?.root || process.cwd()
|
||||
|
||||
const nodeTarget = getElectronNodeTarget()
|
||||
|
||||
const pkg = loadPackageData() || { type: 'commonjs' }
|
||||
|
||||
const format = pkg.type && pkg.type === 'module' && supportESM() ? 'es' : 'cjs'
|
||||
|
||||
const defaultConfig = {
|
||||
ssr: {
|
||||
resolve: {
|
||||
conditions: ['module', 'browser', 'development|production'],
|
||||
mainFields: ['browser', 'module', 'jsnext:main', 'jsnext']
|
||||
}
|
||||
},
|
||||
build: {
|
||||
outDir: path.resolve(root, 'out', 'preload'),
|
||||
target: nodeTarget,
|
||||
assetsDir: 'chunks',
|
||||
rollupOptions: {
|
||||
external: ['electron', /^electron\/.+/, ...builtinModules.flatMap(m => [m, `node:${m}`])],
|
||||
output: {}
|
||||
},
|
||||
reportCompressedSize: false,
|
||||
minify: false
|
||||
}
|
||||
}
|
||||
|
||||
const build = config.build || {}
|
||||
const rollupOptions = build.rollupOptions || {}
|
||||
if (!rollupOptions.input) {
|
||||
const libOptions = build.lib
|
||||
const outputOptions = rollupOptions.output
|
||||
defaultConfig.build['lib'] = {
|
||||
entry: findLibEntry(root, 'preload'),
|
||||
formats:
|
||||
libOptions && libOptions.formats && libOptions.formats.length > 0
|
||||
? []
|
||||
: [outputOptions && !Array.isArray(outputOptions) && outputOptions.format ? outputOptions.format : format]
|
||||
}
|
||||
} else {
|
||||
defaultConfig.build.rollupOptions.output['format'] = format
|
||||
}
|
||||
|
||||
defaultConfig.build.rollupOptions.output['assetFileNames'] = path.posix.join(
|
||||
build.assetsDir || defaultConfig.build.assetsDir,
|
||||
'[name]-[hash].[ext]'
|
||||
)
|
||||
|
||||
const buildConfig = mergeConfig(defaultConfig.build, build)
|
||||
config.build = buildConfig
|
||||
|
||||
const resolvedOutputs = resolveBuildOutputs(config.build.rollupOptions!.output, config.build.lib || false)
|
||||
|
||||
if (resolvedOutputs) {
|
||||
const outputs = Array.isArray(resolvedOutputs) ? resolvedOutputs : [resolvedOutputs]
|
||||
|
||||
if (outputs.find(({ format }) => format === 'es')) {
|
||||
if (Array.isArray(config.build.rollupOptions!.output)) {
|
||||
config.build.rollupOptions!.output.forEach(output => {
|
||||
if (output.format === 'es') {
|
||||
output['entryFileNames'] = '[name].mjs'
|
||||
output['chunkFileNames'] = '[name]-[hash].mjs'
|
||||
}
|
||||
})
|
||||
} else {
|
||||
config.build.rollupOptions!.output!['entryFileNames'] = '[name].mjs'
|
||||
config.build.rollupOptions!.output!['chunkFileNames'] = '[name]-[hash].mjs'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config.define = config.define || {}
|
||||
config.define = { ...processEnvDefine(), ...config.define }
|
||||
|
||||
config.envPrefix = config.envPrefix || ['PRELOAD_VITE_', 'VITE_']
|
||||
|
||||
config.publicDir = config.publicDir || 'resources'
|
||||
// do not copy public dir
|
||||
config.build.copyPublicDir = false
|
||||
// module preload polyfill does not apply to nodejs (preload scripts)
|
||||
config.build.modulePreload = false
|
||||
// enable ssr build
|
||||
config.build.ssr = true
|
||||
config.build.ssrEmitAssets = true
|
||||
config.ssr = mergeConfig(defaultConfig.ssr, config.ssr || {})
|
||||
config.ssr.noExternal = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function electronPreloadConfigValidatorPlugin(): Plugin {
|
||||
return {
|
||||
name: 'vite:electron-preload-config-validator',
|
||||
apply: 'build',
|
||||
enforce: 'post',
|
||||
configResolved(config): void {
|
||||
const build = config.build
|
||||
if (!build.target) {
|
||||
throw new Error('build.target option is required in 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 libOptions = build.lib
|
||||
const rollupOptions = build.rollupOptions
|
||||
|
||||
if (!(libOptions && libOptions.entry) && !rollupOptions?.input) {
|
||||
throw new Error(
|
||||
'An entry point is required in the electron vite preload config, ' +
|
||||
'which can be specified using "build.lib.entry" or "build.rollupOptions.input".'
|
||||
)
|
||||
}
|
||||
|
||||
const resolvedOutputs = resolveBuildOutputs(rollupOptions.output, libOptions)
|
||||
|
||||
if (resolvedOutputs) {
|
||||
const outputs = Array.isArray(resolvedOutputs) ? resolvedOutputs : [resolvedOutputs]
|
||||
if (outputs.length > 1) {
|
||||
throw new Error('The electron vite preload config does not support multiple outputs.')
|
||||
} else {
|
||||
const outpout = outputs[0]
|
||||
if (['es', 'cjs'].includes(outpout.format || '')) {
|
||||
if (outpout.format === 'es' && !supportESM()) {
|
||||
throw new Error(
|
||||
'The electron vite preload config output format does not support "es", ' +
|
||||
'you can upgrade electron to the latest version or switch to "cjs" format.'
|
||||
)
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
`The electron vite preload config output format must be "cjs"${supportESM() ? ' or "es"' : ''}.`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
export function electronRendererConfigPresetPlugin(options?: ElectronPluginOptions): Plugin {
|
||||
return {
|
||||
name: 'vite:electron-renderer-config-preset',
|
||||
enforce: 'pre',
|
||||
config(config): void {
|
||||
const root = options?.root || process.cwd()
|
||||
|
||||
config.base =
|
||||
config.mode === 'production' || process.env.NODE_ENV_ELECTRON_VITE === 'production' ? './' : config.base
|
||||
config.root = config.root || './src/renderer'
|
||||
|
||||
const chromeTarget = getElectronChromeTarget()
|
||||
|
||||
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,
|
||||
modulePreload: { polyfill: false },
|
||||
rollupOptions: {
|
||||
input: findInput(root)
|
||||
},
|
||||
reportCompressedSize: false,
|
||||
minify: false,
|
||||
emptyOutDir: emptyOutDir()
|
||||
}
|
||||
}
|
||||
|
||||
if (config.build?.outDir) {
|
||||
config.build.outDir = path.resolve(root, config.build.outDir)
|
||||
}
|
||||
|
||||
const buildConfig = mergeConfig(defaultConfig.build, config.build || {})
|
||||
config.build = buildConfig
|
||||
|
||||
config.envDir = config.envDir || path.resolve(root)
|
||||
|
||||
config.envPrefix = config.envPrefix || ['RENDERER_VITE_', 'VITE_']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function electronRendererConfigValidatorPlugin(): Plugin {
|
||||
return {
|
||||
name: 'vite:electron-renderer-config-validator',
|
||||
enforce: 'post',
|
||||
configResolved(config): void {
|
||||
if (config.base !== './' && config.base !== '/') {
|
||||
config.logger.warn(colors.yellow('(!) Should not set "base" option for the electron vite renderer config.'))
|
||||
}
|
||||
|
||||
const build = config.build
|
||||
if (!build.target) {
|
||||
throw new Error('build.target option is required in the electron vite renderer config.')
|
||||
} else {
|
||||
const targets = Array.isArray(build.target) ? build.target : [build.target]
|
||||
if (targets.some(t => !t.startsWith('chrome') && !/^es((202\d{1})|next)$/.test(t))) {
|
||||
config.logger.warn(
|
||||
'The electron vite renderer config build.target is not "chrome?" or "es?". This could be a mistake.'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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 option is required in the electron vite renderer config.')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
148
src/plugins/isolateEntries.ts
Normal file
148
src/plugins/isolateEntries.ts
Normal file
@ -0,0 +1,148 @@
|
||||
import { type InlineConfig, type Plugin, type Logger, build as viteBuild, mergeConfig } from 'vite'
|
||||
import type { InputOptions, RollupOutput } from 'rollup'
|
||||
import colors from 'picocolors'
|
||||
|
||||
const VIRTUAL_ENTRY_ID = '\0virtual:isolate-entries'
|
||||
|
||||
export default function isolateEntriesPlugin(userConfig: InlineConfig): Plugin {
|
||||
let logger: Logger
|
||||
|
||||
let entries: string[] | Record<string, string>
|
||||
let transformedCount = 0
|
||||
|
||||
const assetCache = new Set<string>()
|
||||
|
||||
return {
|
||||
name: 'vite:isolate-entries',
|
||||
apply: 'build',
|
||||
configResolved(config): void {
|
||||
logger = config.logger
|
||||
},
|
||||
options(opts): InputOptions | void {
|
||||
const { input } = opts
|
||||
if (input && typeof input === 'object') {
|
||||
if ((Array.isArray(input) && input.length > 0) || Object.keys(input).length > 1) {
|
||||
opts.input = VIRTUAL_ENTRY_ID
|
||||
entries = input
|
||||
return opts
|
||||
}
|
||||
}
|
||||
},
|
||||
buildStart(): void {
|
||||
transformedCount = 0
|
||||
assetCache.clear()
|
||||
},
|
||||
resolveId(id): string | null {
|
||||
if (id === VIRTUAL_ENTRY_ID) {
|
||||
return id
|
||||
}
|
||||
return null
|
||||
},
|
||||
async load(id): Promise<string | void> {
|
||||
if (id === VIRTUAL_ENTRY_ID) {
|
||||
const _entries = Array.isArray(entries)
|
||||
? entries
|
||||
: Object.entries(entries).map(([key, value]) => ({ [key]: value }))
|
||||
const watchFiles = new Set<string>()
|
||||
for (const entry of _entries) {
|
||||
const re = await bundleEntryFile(entry, userConfig, this.meta.watchMode)
|
||||
const outputChunks = re.bundles.output
|
||||
for (const chunk of outputChunks) {
|
||||
if (chunk.type === 'asset' && assetCache.has(chunk.fileName)) {
|
||||
continue
|
||||
}
|
||||
this.emitFile({
|
||||
type: 'asset',
|
||||
fileName: chunk.fileName,
|
||||
source: chunk.type === 'chunk' ? chunk.code : chunk.source
|
||||
})
|
||||
if (chunk.type === 'asset') {
|
||||
assetCache.add(chunk.fileName)
|
||||
}
|
||||
}
|
||||
for (const id of re.watchFiles) {
|
||||
watchFiles.add(id)
|
||||
}
|
||||
transformedCount += re.transformedCount
|
||||
}
|
||||
for (const id of watchFiles) {
|
||||
this.addWatchFile(id)
|
||||
}
|
||||
return `
|
||||
// This is the virtual entry file
|
||||
console.log(1)`
|
||||
}
|
||||
},
|
||||
renderStart(): void {
|
||||
clearLine()
|
||||
logger.info(`${colors.green(`✓`)} ${transformedCount} modules transformed.`)
|
||||
},
|
||||
generateBundle(_, bundle): void {
|
||||
for (const chunkName in bundle) {
|
||||
if (chunkName.includes('virtual_isolate-entries')) {
|
||||
delete bundle[chunkName]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function bundleEntryFile(
|
||||
input: string | Record<string, string>,
|
||||
config: InlineConfig,
|
||||
watch: boolean
|
||||
): Promise<{ bundles: RollupOutput; watchFiles: string[]; transformedCount: number }> {
|
||||
const moduleIds: string[] = []
|
||||
let transformedCount = 0
|
||||
|
||||
const viteConfig = mergeConfig(config, {
|
||||
build: {
|
||||
write: false,
|
||||
watch: false
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
name: 'vite:transform-counter',
|
||||
transform(): void {
|
||||
transformedCount++
|
||||
}
|
||||
} as Plugin,
|
||||
...(watch
|
||||
? [
|
||||
{
|
||||
name: 'vite:get-watch-files',
|
||||
buildEnd(): void {
|
||||
const allModuleIds = Array.from(this.getModuleIds())
|
||||
|
||||
const sourceFiles = allModuleIds.filter(id => {
|
||||
const info = this.getModuleInfo(id)
|
||||
return info && !info.isExternal
|
||||
})
|
||||
|
||||
moduleIds.push(...sourceFiles)
|
||||
}
|
||||
} as Plugin
|
||||
]
|
||||
: [])
|
||||
],
|
||||
logLevel: 'warn',
|
||||
configFile: false
|
||||
}) as InlineConfig
|
||||
|
||||
// rewrite the input instead of merging
|
||||
viteConfig.build!.rollupOptions!.input = input
|
||||
|
||||
const bundles = await viteBuild(viteConfig)
|
||||
|
||||
return {
|
||||
bundles: bundles as RollupOutput,
|
||||
watchFiles: moduleIds,
|
||||
transformedCount
|
||||
}
|
||||
}
|
||||
|
||||
function clearLine(): void {
|
||||
process.stdout.moveCursor(0, -1)
|
||||
process.stdout.clearLine(0)
|
||||
process.stdout.cursorTo(0)
|
||||
}
|
||||
@ -23,8 +23,8 @@ export default function modulePathPlugin(config: InlineConfig): Plugin {
|
||||
async load(id): Promise<string | void> {
|
||||
if (id.endsWith('?modulePath')) {
|
||||
// id resolved by Vite resolve plugin
|
||||
const bundle = await bundleEntryFile(cleanUrl(id), config)
|
||||
const [outputChunk, ...outputChunks] = bundle.output
|
||||
const re = await bundleEntryFile(cleanUrl(id), config, this.meta.watchMode)
|
||||
const [outputChunk, ...outputChunks] = re.bundles.output
|
||||
const hash = this.emitFile({
|
||||
type: 'asset',
|
||||
fileName: outputChunk.fileName,
|
||||
@ -43,6 +43,9 @@ export default function modulePathPlugin(config: InlineConfig): Plugin {
|
||||
assetCache.add(chunk.fileName)
|
||||
}
|
||||
}
|
||||
for (const id of re.watchFiles) {
|
||||
this.addWatchFile(id)
|
||||
}
|
||||
const refId = `__VITE_MODULE_PATH__${hash}__`
|
||||
const dirnameExpr = isImportMetaPathSupported ? 'import.meta.dirname' : '__dirname'
|
||||
return `
|
||||
@ -78,7 +81,12 @@ export default function modulePathPlugin(config: InlineConfig): Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
async function bundleEntryFile(input: string, config: InlineConfig): Promise<RollupOutput> {
|
||||
async function bundleEntryFile(
|
||||
input: string,
|
||||
config: InlineConfig,
|
||||
watch: boolean
|
||||
): Promise<{ bundles: RollupOutput; watchFiles: string[] }> {
|
||||
const moduleIds: string[] = []
|
||||
const viteConfig = mergeConfig(config, {
|
||||
build: {
|
||||
rollupOptions: { input },
|
||||
@ -94,11 +102,32 @@ async function bundleEntryFile(input: string, config: InlineConfig): Promise<Rol
|
||||
}
|
||||
return output
|
||||
}
|
||||
}
|
||||
},
|
||||
...(watch
|
||||
? [
|
||||
{
|
||||
name: 'vite:get-watch-files',
|
||||
buildEnd(): void {
|
||||
const allModuleIds = Array.from(this.getModuleIds())
|
||||
|
||||
const sourceFiles = allModuleIds.filter(id => {
|
||||
const info = this.getModuleInfo(id)
|
||||
return info && !info.isExternal
|
||||
})
|
||||
|
||||
moduleIds.push(...sourceFiles)
|
||||
}
|
||||
} as Plugin
|
||||
]
|
||||
: [])
|
||||
],
|
||||
logLevel: 'warn',
|
||||
configFile: false
|
||||
})
|
||||
const bundles = await viteBuild(viteConfig)
|
||||
return bundles as RollupOutput
|
||||
|
||||
return {
|
||||
bundles: bundles as RollupOutput,
|
||||
watchFiles: moduleIds
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,5 +13,5 @@ export async function preview(inlineConfig: InlineConfig = {}, options: { skipBu
|
||||
|
||||
startElectron(inlineConfig.root)
|
||||
|
||||
logger.info(colors.green(`\nstart electron app...\n`))
|
||||
logger.info(colors.green(`\nstarting electron app...\n`))
|
||||
}
|
||||
|
||||
@ -31,22 +31,20 @@ export async function createServer(
|
||||
const mainViteConfig = config.config?.main
|
||||
if (mainViteConfig && !options.rendererOnly) {
|
||||
const watchHook = (): void => {
|
||||
logger.info(colors.green(`\nrebuild the electron main process successfully`))
|
||||
logger.info(colors.green(`\nelectron main process rebuilt successfully`))
|
||||
|
||||
if (ps) {
|
||||
logger.info(colors.cyan(`\n waiting for electron to exit...`))
|
||||
|
||||
ps.removeAllListeners()
|
||||
ps.kill()
|
||||
ps = startElectron(inlineConfig.root)
|
||||
|
||||
logger.info(colors.green(`\nrestart electron app...`))
|
||||
logger.info(colors.green(`\nrestarting electron app...\n`))
|
||||
}
|
||||
}
|
||||
|
||||
await doBuild(mainViteConfig, watchHook, errorHook)
|
||||
|
||||
logger.info(colors.green(`\nbuild the electron main process successfully`))
|
||||
logger.info(colors.green(`\nelectron main process built successfully`))
|
||||
}
|
||||
|
||||
const preloadViteConfig = config.config?.preload
|
||||
@ -54,10 +52,10 @@ export async function createServer(
|
||||
logger.info(colors.gray(`\n-----\n`))
|
||||
|
||||
const watchHook = (): void => {
|
||||
logger.info(colors.green(`\nrebuild the electron preload files successfully`))
|
||||
logger.info(colors.green(`\nelectron preload scripts rebuilt successfully`))
|
||||
|
||||
if (server) {
|
||||
logger.info(colors.cyan(`\n trigger renderer reload`))
|
||||
logger.info(colors.cyan(`\nreloading electron renderer...\n`))
|
||||
|
||||
server.ws.send({ type: 'full-reload' })
|
||||
}
|
||||
@ -65,14 +63,12 @@ export async function createServer(
|
||||
|
||||
await doBuild(preloadViteConfig, watchHook, errorHook)
|
||||
|
||||
logger.info(colors.green(`\nbuild the electron preload files successfully`))
|
||||
logger.info(colors.green(`\nelectron preload scripts built successfully`))
|
||||
}
|
||||
|
||||
if (options.rendererOnly) {
|
||||
logger.warn(
|
||||
`\n${colors.yellow(colors.bold('warn'))}:${colors.yellow(
|
||||
' you have skipped the main process and preload scripts building'
|
||||
)}`
|
||||
`\n${colors.yellow(colors.bold('(!)'))} ${colors.yellow('skipped building main process and preload scripts (using previous build)')}`
|
||||
)
|
||||
}
|
||||
|
||||
@ -106,7 +102,7 @@ export async function createServer(
|
||||
|
||||
ps = startElectron(inlineConfig.root)
|
||||
|
||||
logger.info(colors.green(`\nstart electron app...\n`))
|
||||
logger.info(colors.green(`\nstarting electron app...\n`))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
33
src/utils.ts
33
src/utils.ts
@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-function-type */
|
||||
import path from 'node:path'
|
||||
import fs from 'node:fs'
|
||||
import { createHash } from 'node:crypto'
|
||||
@ -78,3 +79,35 @@ export function isFilePathESM(filePath: string): boolean {
|
||||
return pkg?.type === 'module'
|
||||
}
|
||||
}
|
||||
|
||||
type DeepWritable<T> =
|
||||
T extends ReadonlyArray<unknown>
|
||||
? { -readonly [P in keyof T]: DeepWritable<T[P]> }
|
||||
: T extends RegExp
|
||||
? RegExp
|
||||
: T[keyof T] extends Function
|
||||
? T
|
||||
: { -readonly [P in keyof T]: DeepWritable<T[P]> }
|
||||
|
||||
export function deepClone<T>(value: T): DeepWritable<T> {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(v => deepClone(v)) as DeepWritable<T>
|
||||
}
|
||||
if (isObject(value)) {
|
||||
const cloned: Record<string, any> = {}
|
||||
for (const key in value) {
|
||||
cloned[key] = deepClone(value[key])
|
||||
}
|
||||
return cloned as DeepWritable<T>
|
||||
}
|
||||
if (typeof value === 'function') {
|
||||
return value as DeepWritable<T>
|
||||
}
|
||||
if (value instanceof RegExp) {
|
||||
return new RegExp(value) as DeepWritable<T>
|
||||
}
|
||||
if (typeof value === 'object' && value != null) {
|
||||
throw new Error('Cannot deep clone non-plain object')
|
||||
}
|
||||
return value as DeepWritable<T>
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user