mirror of
https://github.com/alex8088/electron-vite.git
synced 2026-11-02 14:51:51 +08:00
Compare commits
15 Commits
v6.0.0-bet
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23d7bfc8ba | ||
|
|
2ca5e40433 | ||
|
|
bffc6c95df | ||
|
|
b0b33bdd5e | ||
|
|
3ec79bbf65 | ||
|
|
19050aaec9 | ||
|
|
0655933704 | ||
|
|
6fc103eb5b | ||
|
|
6b0f85a2a7 | ||
|
|
fa199eb372 | ||
|
|
b66840e2ec | ||
|
|
1cbef3fb80 | ||
|
|
36a0147a6d | ||
|
|
4c4bdf5248 | ||
|
|
395f0dae0c |
80
CHANGELOG.md
80
CHANGELOG.md
@ -1,53 +1,85 @@
|
|||||||
### v6.0.0-beta.5 (_2026-09-29_)
|
### v6.0.0 (_2026-10-15_)
|
||||||
|
|
||||||
- feat(bytecode): support RegExp for chunkAlias option ([#885](https://github.com/alex8088/electron-vite/issues/885))
|
- feat(bytecode): support RegExp for chunkAlias option ([#885](https://github.com/alex8088/electron-vite/issues/885))
|
||||||
|
- refactor: share and normalize build output resolution
|
||||||
|
- refactor: inline bytecode build plugin name
|
||||||
|
- refactor!: invoke the bytecode compiler only once to compile all code
|
||||||
|
- refactor: use pipe stdio for spawn Electron bytecode compliler and append stderr error
|
||||||
|
- refactor: use rolldown instead of esbuild to bundle config file
|
||||||
|
- refactor: compile bytecode using the Electron main process or renderer process rather then via the `ELECTRON_RUN_AS_NODE`
|
||||||
|
- refactor!: simplify resolve config and isolate user config for sub-builds
|
||||||
|
- refactor: `?modulePath` sub-build output strategy
|
||||||
|
- perf(swcPlugin): skip redundant minification when using Oxc ([#922](https://github.com/alex8088/electron-vite/issues/922))
|
||||||
|
- perf: skip Babel loading in development and builds without bytecode
|
||||||
|
- perf: short-circuit bytecode detection in builds
|
||||||
|
- perf: setup rollupOption compatibility via config factory
|
||||||
|
- perf: build compatibility target for Electron 42, 43, 44
|
||||||
|
- fix(asset): use consistent file paths for asset cache lookups
|
||||||
|
- fix(bytecode): unable to obtain the bytecode build session in Node.js 24 ([#928](https://github.com/alex8088/electron-vite/issues/928))
|
||||||
- fix(dev): exit non-zero when Electron is killed by a signal ([#918](https://github.com/alex8088/electron-vite/issues/918))
|
- fix(dev): exit non-zero when Electron is killed by a signal ([#918](https://github.com/alex8088/electron-vite/issues/918))
|
||||||
- fix(dev): forward termination signals to Electron child ([#900](https://github.com/alex8088/electron-vite/issues/900))
|
- fix(dev): forward termination signals to Electron child ([#900](https://github.com/alex8088/electron-vite/issues/900))
|
||||||
- fix(dev): improve Electron process signal handling
|
- fix(dev): improve Electron process signal handling
|
||||||
- fix(bytecode): set the regular expression's `lastIndex` to 0 to perform a global match for chunkAlias option
|
- fix(bytecode): set the regular expression's `lastIndex` to 0 to perform a global match for chunkAlias option
|
||||||
|
|
||||||
### v6.0.0-beta.4 (_2026-09-28_)
|
|
||||||
|
|
||||||
- fix(esmShim)!: use `this.parse` to find the last static import ([#906](https://github.com/alex8088/electron-vite/issues/906))
|
- fix(esmShim)!: use `this.parse` to find the last static import ([#906](https://github.com/alex8088/electron-vite/issues/906))
|
||||||
|
|
||||||
### v6.0.0-beta.3 (_2026-09-27_)
|
|
||||||
|
|
||||||
- fix(deps)!: downgraded @babel/* to v7 due to Node requirements
|
- fix(deps)!: downgraded @babel/* to v7 due to Node requirements
|
||||||
|
|
||||||
### v6.0.0-beta.2 (_2026-09-27_)
|
|
||||||
|
|
||||||
- refactor: use rolldown instead of esbuild to bundle config file
|
|
||||||
- refactor: compile bytecode using the Electron main process or renderer process rather then via the `ELECTRON_RUN_AS_NODE`
|
|
||||||
- perf: setup rollupOption compatibility via config factory
|
|
||||||
- perf: build compatibility target for Electron 42, 43, 44
|
|
||||||
- fix: remove useless preset config
|
- fix: remove useless preset config
|
||||||
- fix(swcPlugin): also disable oxc on Vite 8 ([#920](https://github.com/alex8088/electron-vite/issues/920))
|
- fix(swcPlugin): also disable oxc on Vite 8 ([#920](https://github.com/alex8088/electron-vite/issues/920))
|
||||||
- fix: clone config without plugins [#902](https://github.com/alex8088/electron-vite/issues/902)
|
- fix: clone config without plugins [#902](https://github.com/alex8088/electron-vite/issues/902)
|
||||||
- fix(deps)!: update magic-string to v1
|
- fix(deps)!: update magic-string to v1
|
||||||
- fix(deps)!: update @babel/* to v8
|
- fix(deps)!: update @babel/* to v8
|
||||||
- chore(deps): update all non-major dependencies
|
|
||||||
- chore(deps): update lint-staged to v17
|
|
||||||
|
|
||||||
### v6.0.0-beta.1 (_2026-04-12_)
|
|
||||||
|
|
||||||
- refactor!: simplify resolve config and isolate user config for sub-builds
|
|
||||||
- fix!: compatible with rollupOptions and rolldownOptions
|
- fix!: compatible with rollupOptions and rolldownOptions
|
||||||
- fix: chunk filename is wrong in ES mode
|
- fix: chunk filename is wrong in ES mode
|
||||||
|
|
||||||
### v6.0.0-beta.0 (_2026-04-09_)
|
|
||||||
|
|
||||||
- refactor: `?modulePath` sub-build output strategy
|
|
||||||
- fix(asset): isolate asset caches per environment
|
- fix(asset): isolate asset caches per environment
|
||||||
- fix: use the environment api in plugins
|
- fix: use the environment api in plugins
|
||||||
- fix: skip TTY terminal manipulations in CI environments
|
- fix: skip TTY terminal manipulations in CI environments
|
||||||
- fix(deps)!: update vite to v8 ([#894](https://github.com/alex8088/electron-vite/issues/894))
|
- fix(deps)!: update vite to v8 ([#894](https://github.com/alex8088/electron-vite/issues/894))
|
||||||
- fix: Typescript compiler option 'outDir' must be located inside Rollup 'dir' option
|
- fix: Typescript compiler option 'outDir' must be located inside Rollup 'dir' option
|
||||||
- fix(isolateEntries): handle non-TTY stdout in build reporter (#887)
|
- fix(isolateEntries): handle non-TTY stdout in build reporter (#887)
|
||||||
|
- chore: simplify bytecode compiler initialization
|
||||||
|
- chore: improve jsdoc for bytecode options ([#898](https://github.com/alex8088/electron-vite/issues/898))
|
||||||
|
- chore(deps): update all non-major dependencies
|
||||||
|
- chore(deps): update lint-staged to v17
|
||||||
- chore(deps): update cac to v7
|
- chore(deps): update cac to v7
|
||||||
- chore(deps): update globals to v17
|
- chore(deps): update globals to v17
|
||||||
- chore(deps): update eslint and @eslint/js to v10
|
- chore(deps): update eslint and @eslint/js to v10
|
||||||
- chore(deps): update all non-major dependencies
|
- chore(deps): update all non-major dependencies
|
||||||
|
|
||||||
|
### v6.0.0-beta.8 (_2026-10-11_)
|
||||||
|
|
||||||
|
See [v6.0.0-beta.8 changelog](https://github.com/alex8088/electron-vite/blob/v6.0.0-beta.8/CHANGELOG.md)
|
||||||
|
|
||||||
|
### v6.0.0-beta.7 (_2026-10-08_)
|
||||||
|
|
||||||
|
See [v6.0.0-beta.7 changelog](https://github.com/alex8088/electron-vite/blob/v6.0.0-beta.7/CHANGELOG.md)
|
||||||
|
|
||||||
|
### v6.0.0-beta.6 (_2026-10-07_)
|
||||||
|
|
||||||
|
See [v6.0.0-beta.6 changelog](https://github.com/alex8088/electron-vite/blob/v6.0.0-beta.6/CHANGELOG.md)
|
||||||
|
|
||||||
|
### v6.0.0-beta.5 (_2026-09-29_)
|
||||||
|
|
||||||
|
See [v6.0.0-beta.5 changelog](https://github.com/alex8088/electron-vite/blob/v6.0.0-beta.5/CHANGELOG.md)
|
||||||
|
|
||||||
|
### v6.0.0-beta.4 (_2026-09-28_)
|
||||||
|
|
||||||
|
See [v6.0.0-beta.4 changelog](https://github.com/alex8088/electron-vite/blob/v6.0.0-beta.4/CHANGELOG.md)
|
||||||
|
|
||||||
|
### v6.0.0-beta.3 (_2026-09-27_)
|
||||||
|
|
||||||
|
See [v6.0.0-beta.3 changelog](https://github.com/alex8088/electron-vite/blob/v6.0.0-beta.3/CHANGELOG.md)
|
||||||
|
|
||||||
|
### v6.0.0-beta.2 (_2026-09-27_)
|
||||||
|
|
||||||
|
See [v6.0.0-beta.2 changelog](https://github.com/alex8088/electron-vite/blob/v6.0.0-beta.2/CHANGELOG.md)
|
||||||
|
|
||||||
|
### v6.0.0-beta.1 (_2026-04-12_)
|
||||||
|
|
||||||
|
See [v6.0.0-beta.1 changelog](https://github.com/alex8088/electron-vite/blob/v6.0.0-beta.1/CHANGELOG.md)
|
||||||
|
|
||||||
|
### v6.0.0-beta.0 (_2026-04-09_)
|
||||||
|
|
||||||
|
See [v6.0.0-beta.0 changelog](https://github.com/alex8088/electron-vite/blob/v6.0.0-beta.0/CHANGELOG.md)
|
||||||
|
|
||||||
### v5.0.0 (_2025-12-07_)
|
### v5.0.0 (_2025-12-07_)
|
||||||
|
|
||||||
- feat(config): add `build.externalizeDeps` and `build.bytecode` config options to replace `externalizeDepsPlugin` and `bytecodePlugin`
|
- feat(config): add `build.externalizeDeps` and `build.bytecode` config options to replace `externalizeDepsPlugin` and `bytecodePlugin`
|
||||||
|
|||||||
@ -11,6 +11,7 @@ const channel = 'electron-vite:bytecode:rpc'
|
|||||||
const readyChannel = 'electron-vite:bytecode:ready'
|
const readyChannel = 'electron-vite:bytecode:ready'
|
||||||
let win
|
let win
|
||||||
let rendererRPC
|
let rendererRPC
|
||||||
|
let rendererStarting
|
||||||
let stopping = false
|
let stopping = false
|
||||||
|
|
||||||
function fail(error) {
|
function fail(error) {
|
||||||
@ -74,17 +75,18 @@ function createRenderer() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register readiness immediately, but only resolve it after the selected backend is usable.
|
const initialized = app.whenReady().then(() => true)
|
||||||
const initialized = app.whenReady().then(async () => {
|
|
||||||
if (process.env.ELECTRON_VITE_RENDERER === '1' || process.env.ELECTRON_VITE_RENDERER === 'true') {
|
|
||||||
await createRenderer()
|
|
||||||
parentRPC.register('compile', (...args) => rendererRPC.request('compile', ...args))
|
|
||||||
} else {
|
|
||||||
parentRPC.register('compile', compile)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
parentRPC.register('ready', () => initialized)
|
parentRPC.register('ready', () => initialized)
|
||||||
|
parentRPC.register('compile', async (target, code) => {
|
||||||
|
await initialized
|
||||||
|
if (target === 'main') return compile(code)
|
||||||
|
if (target === 'preload') {
|
||||||
|
rendererStarting ||= createRenderer()
|
||||||
|
await rendererStarting
|
||||||
|
return rendererRPC.request('compile', code)
|
||||||
|
}
|
||||||
|
throw new Error(`Invalid bytecode target: ${target}`)
|
||||||
|
})
|
||||||
initialized.catch(fail)
|
initialized.catch(fail)
|
||||||
|
|
||||||
process.on('message', message => {
|
process.on('message', message => {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "electron-vite",
|
"name": "electron-vite",
|
||||||
"version": "6.0.0-beta.5",
|
"version": "6.0.0",
|
||||||
"description": "Electron build tooling based on Vite",
|
"description": "Electron build tooling based on Vite",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
|
|||||||
52
src/build.ts
52
src/build.ts
@ -1,28 +1,56 @@
|
|||||||
import { build as viteBuild } from 'vite'
|
import { build as viteBuild, mergeConfig } from 'vite'
|
||||||
import { type InlineConfig, resolveConfig } from './config'
|
import { type InlineConfig, type MainViteConfig, type PreloadViteConfig, resolveConfig } from './config'
|
||||||
|
import { BytecodeCompiler } from './bytecodeCompiler'
|
||||||
|
import { asyncFlatten } from './utils'
|
||||||
|
|
||||||
|
async function usesBytecode(config?: MainViteConfig | PreloadViteConfig): Promise<boolean> {
|
||||||
|
if (!config) return false
|
||||||
|
if (config.build?.bytecode) return true
|
||||||
|
const plugins = await asyncFlatten(config.plugins || [])
|
||||||
|
return plugins.some(plugin => plugin && typeof plugin === 'object' && plugin.name === 'vite:bytecode')
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bundles the electron app for production.
|
* Bundles the electron app for production.
|
||||||
*/
|
*/
|
||||||
export async function build(inlineConfig: InlineConfig = {}): Promise<void> {
|
export async function build(inlineConfig: InlineConfig = {}): Promise<void> {
|
||||||
process.env.NODE_ENV_ELECTRON_VITE = 'production'
|
process.env.NODE_ENV_ELECTRON_VITE = 'production'
|
||||||
const config = await resolveConfig(inlineConfig, 'build', 'production')
|
|
||||||
|
const bytecodeCompiler = new BytecodeCompiler()
|
||||||
|
const config = await resolveConfig(
|
||||||
|
mergeConfig(inlineConfig, {
|
||||||
|
plugins: [{ name: 'vite:electron-bytecode-build', api: { compiler: bytecodeCompiler } }]
|
||||||
|
}),
|
||||||
|
'build',
|
||||||
|
'production'
|
||||||
|
)
|
||||||
|
|
||||||
if (!config.config) {
|
if (!config.config) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build targets in order: main -> preload -> renderer
|
const hasBytecode = (await usesBytecode(config.config.main)) || (await usesBytecode(config.config.preload))
|
||||||
const buildTargets = ['main', 'preload', 'renderer'] as const
|
|
||||||
|
|
||||||
for (const target of buildTargets) {
|
const buildTargets = async (): Promise<void> => {
|
||||||
const viteConfig = config.config[target]
|
// Build targets in order: main -> preload -> renderer
|
||||||
if (viteConfig) {
|
for (const target of ['main', 'preload', 'renderer'] as const) {
|
||||||
// Disable watch mode in production builds
|
const viteConfig = config.config?.[target]
|
||||||
if (viteConfig.build?.watch) {
|
if (viteConfig) {
|
||||||
viteConfig.build.watch = null
|
// Disable watch mode in production builds
|
||||||
|
if (viteConfig.build?.watch) {
|
||||||
|
viteConfig.build.watch = null
|
||||||
|
}
|
||||||
|
await viteBuild(viteConfig)
|
||||||
}
|
}
|
||||||
await viteBuild(viteConfig)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!hasBytecode) return buildTargets()
|
||||||
|
|
||||||
|
try {
|
||||||
|
await bytecodeCompiler.start()
|
||||||
|
await buildTargets()
|
||||||
|
} finally {
|
||||||
|
await bytecodeCompiler.stop()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
import { type ChildProcess, spawn } from 'node:child_process'
|
import { type ChildProcess, spawn } from 'node:child_process'
|
||||||
|
import path from 'node:path'
|
||||||
|
import { createRequire } from 'node:module'
|
||||||
|
import { getElectronPath } from './electron'
|
||||||
|
|
||||||
export type ElectronCompilerOptions = {
|
const require = createRequire(import.meta.url)
|
||||||
path: string
|
const RPC_TIMEOUT = 30_000
|
||||||
args: string[]
|
const SHUTDOWN_TIMEOUT = 3_000
|
||||||
env?: NodeJS.ProcessEnv
|
|
||||||
timeout?: number
|
|
||||||
shutdownTimeout?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export type BytecodeCompileResult = {
|
export type BytecodeCompileResult = {
|
||||||
sourceLength: number
|
sourceLength: number
|
||||||
@ -14,7 +13,9 @@ export type BytecodeCompileResult = {
|
|||||||
cachedData: string
|
cachedData: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ElectronResponse = {
|
export type BytecodeTarget = 'main' | 'preload'
|
||||||
|
|
||||||
|
type CompilerResponse = {
|
||||||
type: 'response'
|
type: 'response'
|
||||||
invocationId: number
|
invocationId: number
|
||||||
result?: unknown
|
result?: unknown
|
||||||
@ -27,24 +28,17 @@ type PendingRequest = {
|
|||||||
timer: ReturnType<typeof setTimeout>
|
timer: ReturnType<typeof setTimeout>
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ElectronCompiler {
|
export class BytecodeCompiler {
|
||||||
private ps?: ChildProcess
|
private ps?: ChildProcess
|
||||||
private rpcMap = new Map<number, PendingRequest>()
|
private rpcMap = new Map<number, PendingRequest>()
|
||||||
private invocationId = 0
|
private invocationId = 0
|
||||||
private starting?: Promise<void>
|
private starting?: Promise<void>
|
||||||
private stopping?: Promise<void>
|
private stopping?: Promise<void>
|
||||||
private ready = false
|
private ready = false
|
||||||
|
private stderr = ''
|
||||||
constructor(readonly options: ElectronCompilerOptions) {
|
|
||||||
for (const timeout of [options.timeout, options.shutdownTimeout]) {
|
|
||||||
if (timeout !== undefined && (!Number.isFinite(timeout) || timeout <= 0)) {
|
|
||||||
throw new RangeError('ElectronCompiler timeouts must be positive finite numbers')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
start(): Promise<void> {
|
start(): Promise<void> {
|
||||||
if (this.stopping) return Promise.reject(new Error('ElectronCompiler is stopping'))
|
if (this.stopping) return Promise.reject(new Error('BytecodeCompiler is stopping'))
|
||||||
if (this.starting) return this.starting
|
if (this.starting) return this.starting
|
||||||
if (this.ready) return Promise.resolve()
|
if (this.ready) return Promise.resolve()
|
||||||
if (this.ps) return Promise.reject(new Error('Previous Electron process has not closed; await stop() first'))
|
if (this.ps) return Promise.reject(new Error('Previous Electron process has not closed; await stop() first'))
|
||||||
@ -55,15 +49,27 @@ export class ElectronCompiler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async initialize(): Promise<void> {
|
private async initialize(): Promise<void> {
|
||||||
const env: NodeJS.ProcessEnv = { ...process.env, ...this.options.env, ELECTRON_VITE_RPC: 'true' }
|
const env: NodeJS.ProcessEnv = { ...process.env, ELECTRON_VITE_RPC: 'true' }
|
||||||
delete env.ELECTRON_RUN_AS_NODE
|
delete env.ELECTRON_RUN_AS_NODE
|
||||||
const ps = spawn(this.options.path, this.options.args, {
|
this.stderr = ''
|
||||||
stdio: ['ignore', 'inherit', 'inherit', 'ipc'],
|
const compilerPath = path.join(
|
||||||
|
path.dirname(require.resolve('electron-vite/package.json')),
|
||||||
|
'bin',
|
||||||
|
'electron-bytecode-main.cjs'
|
||||||
|
)
|
||||||
|
const ps = spawn(getElectronPath(), [compilerPath], {
|
||||||
|
stdio: ['ignore', 'ignore', 'pipe', 'ipc'],
|
||||||
windowsHide: true,
|
windowsHide: true,
|
||||||
env
|
env
|
||||||
})
|
})
|
||||||
this.ps = ps
|
this.ps = ps
|
||||||
ps.on('message', (message: ElectronResponse) => {
|
if (ps.stderr) {
|
||||||
|
ps.stderr.setEncoding('utf8')
|
||||||
|
ps.stderr.on('data', (chunk: string) => {
|
||||||
|
if (this.ps === ps) this.stderr = (this.stderr + chunk).slice(-64 * 1024)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ps.on('message', (message: CompilerResponse) => {
|
||||||
if (this.ps !== ps || !message || message.type !== 'response') return
|
if (this.ps !== ps || !message || message.type !== 'response') return
|
||||||
const pending = this.rpcMap.get(message.invocationId)
|
const pending = this.rpcMap.get(message.invocationId)
|
||||||
if (!pending) return
|
if (!pending) return
|
||||||
@ -101,12 +107,12 @@ export class ElectronCompiler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
compile(code: string): Promise<BytecodeCompileResult> {
|
compile(target: BytecodeTarget, code: string): Promise<BytecodeCompileResult> {
|
||||||
return this.request<BytecodeCompileResult>('compile', code)
|
return this.request<BytecodeCompileResult>('compile', target, code)
|
||||||
}
|
}
|
||||||
|
|
||||||
async request<T = unknown>(cmd: string, ...args: unknown[]): Promise<T> {
|
async request<T = unknown>(cmd: string, ...args: unknown[]): Promise<T> {
|
||||||
if (!this.ready || this.stopping) throw new Error('ElectronCompiler is not ready; await start() first')
|
if (!this.ready || this.stopping) throw new Error('BytecodeCompiler is not ready; await start() first')
|
||||||
return this.sendRequest(cmd, args) as Promise<T>
|
return this.sendRequest(cmd, args) as Promise<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,9 +126,9 @@ export class ElectronCompiler {
|
|||||||
if (!pending) return
|
if (!pending) return
|
||||||
clearTimeout(pending.timer)
|
clearTimeout(pending.timer)
|
||||||
this.rpcMap.delete(invocationId)
|
this.rpcMap.delete(invocationId)
|
||||||
reject(error)
|
reject(this.withStderr(error))
|
||||||
}
|
}
|
||||||
const timer = setTimeout(() => fail(new Error('Electron RPC timed out: ' + cmd)), this.options.timeout ?? 30000)
|
const timer = setTimeout(() => fail(new Error('Electron RPC timed out: ' + cmd)), RPC_TIMEOUT)
|
||||||
this.rpcMap.set(invocationId, { resolve, reject, timer })
|
this.rpcMap.set(invocationId, { resolve, reject, timer })
|
||||||
try {
|
try {
|
||||||
ps.send({ type: 'request', invocationId, cmd, args }, error => {
|
ps.send({ type: 'request', invocationId, cmd, args }, error => {
|
||||||
@ -136,6 +142,7 @@ export class ElectronCompiler {
|
|||||||
|
|
||||||
private rejectPending(error: Error): void {
|
private rejectPending(error: Error): void {
|
||||||
this.ready = false
|
this.ready = false
|
||||||
|
if (this.rpcMap.size) this.withStderr(error)
|
||||||
for (const pending of this.rpcMap.values()) {
|
for (const pending of this.rpcMap.values()) {
|
||||||
clearTimeout(pending.timer)
|
clearTimeout(pending.timer)
|
||||||
pending.reject(error)
|
pending.reject(error)
|
||||||
@ -143,13 +150,23 @@ export class ElectronCompiler {
|
|||||||
this.rpcMap.clear()
|
this.rpcMap.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private withStderr(error: Error): Error {
|
||||||
|
const stderr = this.stderr.trim()
|
||||||
|
if (stderr) {
|
||||||
|
const stack = error.stack
|
||||||
|
error.message += '\n' + stderr
|
||||||
|
if (stack) error.stack = stack + '\n' + stderr
|
||||||
|
}
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
|
||||||
stop(): Promise<void> {
|
stop(): Promise<void> {
|
||||||
if (this.stopping) return this.stopping
|
if (this.stopping) return this.stopping
|
||||||
this.rejectPending(new Error('ElectronCompiler stopped'))
|
this.rejectPending(new Error('BytecodeCompiler stopped'))
|
||||||
const ps = this.ps
|
const ps = this.ps
|
||||||
if (!ps) return Promise.resolve()
|
if (!ps) return Promise.resolve()
|
||||||
this.stopping = new Promise<void>(resolve => {
|
this.stopping = new Promise<void>(resolve => {
|
||||||
const timer = setTimeout(() => ps.kill('SIGKILL'), this.options.shutdownTimeout ?? 3000)
|
const timer = setTimeout(() => ps.kill('SIGKILL'), SHUTDOWN_TIMEOUT)
|
||||||
ps.once('close', () => {
|
ps.once('close', () => {
|
||||||
clearTimeout(timer)
|
clearTimeout(timer)
|
||||||
resolve()
|
resolve()
|
||||||
@ -68,7 +68,7 @@ interface BytecodeMixin {
|
|||||||
* Options pass on to `bytecode` plugin in electron-vite.
|
* Options pass on to `bytecode` plugin in electron-vite.
|
||||||
* https://electron-vite.org/guide/source-code-protection#options
|
* https://electron-vite.org/guide/source-code-protection#options
|
||||||
*
|
*
|
||||||
* Compile source code to v8 bytecode.
|
* Compiles source code to v8 bytecode and works only in the `build` command.
|
||||||
*/
|
*/
|
||||||
bytecode?: boolean | BytecodeOptions
|
bytecode?: boolean | BytecodeOptions
|
||||||
}
|
}
|
||||||
|
|||||||
@ -67,7 +67,7 @@ export default function assetPlugin(): Plugin {
|
|||||||
referenceId = `__VITE_NODE_PUBLIC_ASSET__${hash}__`
|
referenceId = `__VITE_NODE_PUBLIC_ASSET__${hash}__`
|
||||||
} else {
|
} else {
|
||||||
const cache = assetCache.get(this.environment)!
|
const cache = assetCache.get(this.environment)!
|
||||||
const cached = cache.get(id)
|
const cached = cache.get(file)
|
||||||
if (cached) {
|
if (cached) {
|
||||||
referenceId = cached
|
referenceId = cached
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -2,28 +2,29 @@ import path from 'node:path'
|
|||||||
import { randomBytes } from 'node:crypto'
|
import { randomBytes } from 'node:crypto'
|
||||||
import { createRequire } from 'node:module'
|
import { createRequire } from 'node:module'
|
||||||
import colors from 'picocolors'
|
import colors from 'picocolors'
|
||||||
import { type Plugin, type LibraryOptions, type Rolldown, normalizePath } from 'vite'
|
import { type Plugin, type Rolldown, normalizePath } from 'vite'
|
||||||
import * as babel from '@babel/core'
|
import type * as Babel from '@babel/core'
|
||||||
import MagicString from 'magic-string'
|
import MagicString from 'magic-string'
|
||||||
import { getElectronPath } from '../electron'
|
import { toRelativePath, resolveBuildOutputs } from '../utils'
|
||||||
import { toRelativePath } from '../utils'
|
import type { BytecodeCompiler, BytecodeTarget } from '../bytecodeCompiler'
|
||||||
import { ElectronCompiler } from './electronCompiler'
|
|
||||||
|
|
||||||
// Inspired by https://github.com/bytenode/bytenode
|
// Inspired by https://github.com/bytenode/bytenode
|
||||||
|
|
||||||
const _require = createRequire(import.meta.url)
|
const require = createRequire(import.meta.url)
|
||||||
|
let babel: typeof Babel | undefined
|
||||||
|
|
||||||
function getBytecodeCompilerPath(): string {
|
function getBytecodeCompiler(plugins: readonly Plugin[]): BytecodeCompiler | undefined {
|
||||||
return path.join(path.dirname(_require.resolve('electron-vite/package.json')), 'bin', 'electron-bytecode-main.cjs')
|
const plugin = plugins.find(plugin => plugin.name === 'vite:electron-bytecode-build')
|
||||||
|
return (plugin?.api as { compiler?: BytecodeCompiler } | undefined)?.compiler
|
||||||
}
|
}
|
||||||
|
|
||||||
// Versioned envelope: magic/version (8), UTF-16 source length (4), module ID (16), V8 cache.
|
// Versioned envelope: magic/version (8), UTF-16 source length (4), module ID (16), V8 cache.
|
||||||
// Keep our metadata separate from the private V8 cache header.
|
// Keep our metadata separate from the private V8 cache header.
|
||||||
async function compileToBytecode(compiler: ElectronCompiler, code: string): Promise<Buffer> {
|
async function compileToBytecode(compiler: BytecodeCompiler, target: BytecodeTarget, code: string): Promise<Buffer> {
|
||||||
const id = randomBytes(16)
|
const id = randomBytes(16)
|
||||||
// Reserve enough source space for an untruncated, per-artifact placeholder identity.
|
// Reserve enough source space for an untruncated, per-artifact placeholder identity.
|
||||||
const source = code + '\n/*' + id.toString('hex') + '*/'
|
const source = code + '\n/*' + id.toString('hex') + '*/'
|
||||||
const result = await compiler.compile(source)
|
const result = await compiler.compile(target, source)
|
||||||
const header = Buffer.alloc(28)
|
const header = Buffer.alloc(28)
|
||||||
header.write('EVBC0001', 0, 'ascii')
|
header.write('EVBC0001', 0, 'ascii')
|
||||||
header.writeUInt32LE(result.sourceLength, 8)
|
header.writeUInt32LE(result.sourceLength, 8)
|
||||||
@ -100,14 +101,14 @@ export function bytecodePlugin(options: BytecodeOptions = {}): Plugin | null {
|
|||||||
return _chunkAlias.length === 0 || _chunkAlias.some(alias => alias === chunkName)
|
return _chunkAlias.length === 0 || _chunkAlias.some(alias => alias === chunkName)
|
||||||
}
|
}
|
||||||
|
|
||||||
const plugins: babel.PluginItem[] = []
|
const plugins: Babel.PluginItem[] = []
|
||||||
|
|
||||||
if (transformArrowFunctions) {
|
if (transformArrowFunctions) {
|
||||||
plugins.push('@babel/plugin-transform-arrow-functions')
|
plugins.push('@babel/plugin-transform-arrow-functions')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (protectedStrings.length > 0) {
|
if (protectedStrings.length > 0) {
|
||||||
plugins.push([protectStringsPlugin as babel.PluginTarget, { protectedStrings: new Set(protectedStrings) }])
|
plugins.push([protectStringsPlugin as Babel.PluginTarget, { protectedStrings: new Set(protectedStrings) }])
|
||||||
}
|
}
|
||||||
|
|
||||||
const shouldTransformBytecodeChunk = plugins.length !== 0
|
const shouldTransformBytecodeChunk = plugins.length !== 0
|
||||||
@ -116,6 +117,7 @@ export function bytecodePlugin(options: BytecodeOptions = {}): Plugin | null {
|
|||||||
code: string,
|
code: string,
|
||||||
sourceMaps: boolean = false
|
sourceMaps: boolean = false
|
||||||
): { code: string; map?: Rolldown.SourceMapInput } | null => {
|
): { code: string; map?: Rolldown.SourceMapInput } | null => {
|
||||||
|
babel ??= require('@babel/core') as typeof Babel
|
||||||
const re = babel.transformSync(code, { plugins, sourceMaps })
|
const re = babel.transformSync(code, { plugins, sourceMaps })
|
||||||
return re ? { code: re.code || '', map: re.map as Rolldown.SourceMapInput } : null
|
return re ? { code: re.code || '', map: re.map as Rolldown.SourceMapInput } : null
|
||||||
}
|
}
|
||||||
@ -125,12 +127,14 @@ export function bytecodePlugin(options: BytecodeOptions = {}): Plugin | null {
|
|||||||
|
|
||||||
let supported = false
|
let supported = false
|
||||||
let isPreload = false
|
let isPreload = false
|
||||||
|
let bytecodeCompiler: BytecodeCompiler | undefined
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: 'vite:bytecode',
|
name: 'vite:bytecode',
|
||||||
apply: 'build',
|
apply: 'build',
|
||||||
enforce: 'post',
|
enforce: 'post',
|
||||||
configResolved(config): void {
|
configResolved(config): void {
|
||||||
|
bytecodeCompiler = getBytecodeCompiler(config.plugins)
|
||||||
if (supported) {
|
if (supported) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -141,10 +145,9 @@ export function bytecodePlugin(options: BytecodeOptions = {}): Plugin | null {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const build = config.build
|
const build = config.build
|
||||||
const resolvedOutputs = resolveBuildOutputs(build.rolldownOptions.output, build.lib)
|
const outputs = resolveBuildOutputs(build.rolldownOptions.output, build.lib)
|
||||||
if (resolvedOutputs) {
|
const output = outputs[0]
|
||||||
const outputs = Array.isArray(resolvedOutputs) ? resolvedOutputs : [resolvedOutputs]
|
if (output) {
|
||||||
const output = outputs[0]
|
|
||||||
if (output.format === 'es') {
|
if (output.format === 'es') {
|
||||||
config.logger.warn(
|
config.logger.warn(
|
||||||
colors.yellow(
|
colors.yellow(
|
||||||
@ -189,85 +192,78 @@ export function bytecodePlugin(options: BytecodeOptions = {}): Plugin | null {
|
|||||||
|
|
||||||
const bundles = Object.keys(output)
|
const bundles = Object.keys(output)
|
||||||
|
|
||||||
const compiler = new ElectronCompiler({
|
if (!bytecodeCompiler) {
|
||||||
path: getElectronPath(),
|
throw new Error('Bytecode compilation requires an electron-vite build')
|
||||||
args: [getBytecodeCompilerPath()],
|
}
|
||||||
env: { ELECTRON_VITE_RENDERER: isPreload ? '1' : '0' }
|
for (const name of bundles) {
|
||||||
})
|
const chunk = output[name]
|
||||||
try {
|
if (chunk.type === 'chunk') {
|
||||||
await compiler.start()
|
let _code = chunk.code
|
||||||
for (const name of bundles) {
|
if (bytecodeRE) {
|
||||||
const chunk = output[name]
|
let match: RegExpExecArray | null
|
||||||
if (chunk.type === 'chunk') {
|
let s: MagicString | undefined
|
||||||
let _code = chunk.code
|
while ((match = bytecodeRE.exec(_code))) {
|
||||||
if (bytecodeRE) {
|
s ||= new MagicString(_code)
|
||||||
let match: RegExpExecArray | null
|
const [prefix, chunkName] = match
|
||||||
let s: MagicString | undefined
|
const len = prefix.length + chunkName.length
|
||||||
while ((match = bytecodeRE.exec(_code))) {
|
s.overwrite(match.index, match.index + len, prefix + chunkName + 'c', {
|
||||||
s ||= new MagicString(_code)
|
contentOnly: true
|
||||||
const [prefix, chunkName] = match
|
|
||||||
const len = prefix.length + chunkName.length
|
|
||||||
s.overwrite(match.index, match.index + len, prefix + chunkName + 'c', {
|
|
||||||
contentOnly: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (s) {
|
|
||||||
_code = s.toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (bytecodeChunks.includes(name)) {
|
|
||||||
const bytecodeBuffer = await compileToBytecode(compiler, _code)
|
|
||||||
this.emitFile({
|
|
||||||
type: 'asset',
|
|
||||||
fileName: name + 'c',
|
|
||||||
source: bytecodeBuffer
|
|
||||||
})
|
})
|
||||||
if (!removeBundleJS) {
|
}
|
||||||
this.emitFile({
|
if (s) {
|
||||||
type: 'asset',
|
_code = s.toString()
|
||||||
fileName: '_' + chunk.fileName,
|
|
||||||
source: chunk.code
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (chunk.isEntry) {
|
|
||||||
const bytecodeLoaderBlock = getBytecodeLoaderBlock(chunk.fileName)
|
|
||||||
const bytecodeModuleBlock = `require("./${path.basename(name) + 'c'}");`
|
|
||||||
const code = `${useStrict}\n${bytecodeLoaderBlock}\n${bytecodeModuleBlock}\n`
|
|
||||||
chunk.code = code
|
|
||||||
} else {
|
|
||||||
delete output[chunk.fileName]
|
|
||||||
}
|
|
||||||
bytecodeChunkCount += 1
|
|
||||||
} else {
|
|
||||||
if (chunk.isEntry) {
|
|
||||||
let hasBytecodeMoudle = false
|
|
||||||
const idsToHandle = new Set([...chunk.imports, ...chunk.dynamicImports])
|
|
||||||
for (const moduleId of idsToHandle) {
|
|
||||||
if (bytecodeChunks.includes(moduleId)) {
|
|
||||||
hasBytecodeMoudle = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
const moduleInfo = this.getModuleInfo(moduleId)
|
|
||||||
if (moduleInfo) {
|
|
||||||
const { importers, dynamicImporters } = moduleInfo
|
|
||||||
for (const importerId of importers) idsToHandle.add(importerId)
|
|
||||||
for (const importerId of dynamicImporters) idsToHandle.add(importerId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (hasBytecodeMoudle) {
|
|
||||||
const loader = getBytecodeLoaderBlock(chunk.fileName)
|
|
||||||
const strictRE = /^(#![^\n]*\n)?(["'])use strict\2;/
|
|
||||||
_code = strictRE.test(_code)
|
|
||||||
? _code.replace(strictRE, `$&\n${loader}`)
|
|
||||||
: _code.replace(/^(#![^\n]*\n)?/, `$&${loader}\n`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
chunk.code = _code
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (bytecodeChunks.includes(name)) {
|
||||||
|
const bytecodeBuffer = await compileToBytecode(bytecodeCompiler, isPreload ? 'preload' : 'main', _code)
|
||||||
|
this.emitFile({
|
||||||
|
type: 'asset',
|
||||||
|
fileName: name + 'c',
|
||||||
|
source: bytecodeBuffer
|
||||||
|
})
|
||||||
|
if (!removeBundleJS) {
|
||||||
|
this.emitFile({
|
||||||
|
type: 'asset',
|
||||||
|
fileName: '_' + chunk.fileName,
|
||||||
|
source: chunk.code
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (chunk.isEntry) {
|
||||||
|
const bytecodeLoaderBlock = getBytecodeLoaderBlock(chunk.fileName)
|
||||||
|
const bytecodeModuleBlock = `require("./${path.basename(name) + 'c'}");`
|
||||||
|
const code = `${useStrict}\n${bytecodeLoaderBlock}\n${bytecodeModuleBlock}\n`
|
||||||
|
chunk.code = code
|
||||||
|
} else {
|
||||||
|
delete output[chunk.fileName]
|
||||||
|
}
|
||||||
|
bytecodeChunkCount += 1
|
||||||
|
} else {
|
||||||
|
if (chunk.isEntry) {
|
||||||
|
let hasBytecodeMoudle = false
|
||||||
|
const idsToHandle = new Set([...chunk.imports, ...chunk.dynamicImports])
|
||||||
|
for (const moduleId of idsToHandle) {
|
||||||
|
if (bytecodeChunks.includes(moduleId)) {
|
||||||
|
hasBytecodeMoudle = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
const moduleInfo = this.getModuleInfo(moduleId)
|
||||||
|
if (moduleInfo) {
|
||||||
|
const { importers, dynamicImporters } = moduleInfo
|
||||||
|
for (const importerId of importers) idsToHandle.add(importerId)
|
||||||
|
for (const importerId of dynamicImporters) idsToHandle.add(importerId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasBytecodeMoudle) {
|
||||||
|
const loader = getBytecodeLoaderBlock(chunk.fileName)
|
||||||
|
const strictRE = /^(#![^\n]*\n)?(["'])use strict\2;/
|
||||||
|
_code = strictRE.test(_code)
|
||||||
|
? _code.replace(strictRE, `$&\n${loader}`)
|
||||||
|
: _code.replace(/^(#![^\n]*\n)?/, `$&${loader}\n`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chunk.code = _code
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
await compiler.stop()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bytecodeChunkCount && !_chunks.some(ass => ass.type === 'asset' && ass.fileName === bytecodeModuleLoader)) {
|
if (bytecodeChunkCount && !_chunks.some(ass => ass.type === 'asset' && ass.fileName === bytecodeModuleLoader)) {
|
||||||
@ -288,25 +284,14 @@ export function bytecodePlugin(options: BytecodeOptions = {}): Plugin | null {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveBuildOutputs(
|
interface ProtectStringsPluginState extends Babel.PluginPass {
|
||||||
outputs: Rolldown.OutputOptions | Rolldown.OutputOptions[] | undefined,
|
|
||||||
libOptions: LibraryOptions | false
|
|
||||||
): Rolldown.OutputOptions | Rolldown.OutputOptions[] | undefined {
|
|
||||||
if (libOptions && !Array.isArray(outputs)) {
|
|
||||||
const libFormats = libOptions.formats || []
|
|
||||||
return libFormats.map(format => ({ ...outputs, format }))
|
|
||||||
}
|
|
||||||
return outputs
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ProtectStringsPluginState extends babel.PluginPass {
|
|
||||||
opts: { protectedStrings: Set<string> }
|
opts: { protectedStrings: Set<string> }
|
||||||
}
|
}
|
||||||
|
|
||||||
function protectStringsPlugin(api: typeof babel & babel.ConfigAPI): babel.PluginObj<ProtectStringsPluginState> {
|
function protectStringsPlugin(api: typeof Babel & Babel.ConfigAPI): Babel.PluginObj<ProtectStringsPluginState> {
|
||||||
const { types: t } = api
|
const { types: t } = api
|
||||||
|
|
||||||
function createFromCharCodeFunction(value: string): babel.types.CallExpression {
|
function createFromCharCodeFunction(value: string): Babel.types.CallExpression {
|
||||||
const charCodes = Array.from(value).map(s => s.charCodeAt(0))
|
const charCodes = Array.from(value).map(s => s.charCodeAt(0))
|
||||||
const charCodeLiterals = charCodes.map(code => t.numericLiteral(code))
|
const charCodeLiterals = charCodes.map(code => t.numericLiteral(code))
|
||||||
|
|
||||||
|
|||||||
@ -2,9 +2,9 @@ import path from 'node:path'
|
|||||||
import fs from 'node:fs'
|
import fs from 'node:fs'
|
||||||
import { builtinModules } from 'node:module'
|
import { builtinModules } from 'node:module'
|
||||||
import colors from 'picocolors'
|
import colors from 'picocolors'
|
||||||
import { type Plugin, type LibraryOptions, type Rolldown, type UserConfig, mergeConfig, normalizePath } from 'vite'
|
import { type Plugin, type UserConfig, mergeConfig, normalizePath } from 'vite'
|
||||||
import { getElectronNodeTarget, getElectronChromeTarget, supportESM } from '../electron'
|
import { getElectronNodeTarget, getElectronChromeTarget, supportESM } from '../electron'
|
||||||
import { loadPackageData } from '../utils'
|
import { loadPackageData, resolveBuildOutputs } from '../utils'
|
||||||
|
|
||||||
export interface ElectronPluginOptions {
|
export interface ElectronPluginOptions {
|
||||||
root?: string
|
root?: string
|
||||||
@ -38,17 +38,6 @@ function processEnvDefine(): Record<string, string> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveBuildOutputs(
|
|
||||||
outputs: Rolldown.OutputOptions | Rolldown.OutputOptions[] | undefined,
|
|
||||||
libOptions: LibraryOptions | false
|
|
||||||
): Rolldown.OutputOptions | Rolldown.OutputOptions[] | undefined {
|
|
||||||
if (libOptions && !Array.isArray(outputs)) {
|
|
||||||
const libFormats = libOptions.formats || []
|
|
||||||
return libFormats.map(format => ({ ...outputs, format }))
|
|
||||||
}
|
|
||||||
return outputs
|
|
||||||
}
|
|
||||||
|
|
||||||
export function electronMainConfigPresetPlugin(options?: ElectronPluginOptions): Plugin {
|
export function electronMainConfigPresetPlugin(options?: ElectronPluginOptions): Plugin {
|
||||||
return {
|
return {
|
||||||
name: 'vite:electron-main-config-preset',
|
name: 'vite:electron-main-config-preset',
|
||||||
@ -148,16 +137,15 @@ export function electronMainConfigValidatorPlugin(): Plugin {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const resolvedOutputs = resolveBuildOutputs(rolldownOptions.output, libOptions)
|
const outputs = resolveBuildOutputs(rolldownOptions.output, libOptions)
|
||||||
|
|
||||||
if (resolvedOutputs) {
|
if (outputs.length > 0) {
|
||||||
const outputs = Array.isArray(resolvedOutputs) ? resolvedOutputs : [resolvedOutputs]
|
|
||||||
if (outputs.length > 1) {
|
if (outputs.length > 1) {
|
||||||
throw new Error('The electron vite main config does not support multiple outputs.')
|
throw new Error('The electron vite main config does not support multiple outputs.')
|
||||||
} else {
|
} else {
|
||||||
const outpout = outputs[0]
|
const output = outputs[0]
|
||||||
if (['es', 'cjs'].includes(outpout.format || '')) {
|
if (['es', 'cjs'].includes(output.format || '')) {
|
||||||
if (outpout.format === 'es' && !supportESM()) {
|
if (output.format === 'es' && !supportESM()) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'The electron vite main config output format does not support "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.'
|
'you can upgrade electron to the latest version or switch to "cjs" format.'
|
||||||
@ -232,25 +220,21 @@ export function electronPreloadConfigPresetPlugin(options?: ElectronPluginOption
|
|||||||
const buildConfig = mergeConfig(defaultConfig.build, build)
|
const buildConfig = mergeConfig(defaultConfig.build, build)
|
||||||
config.build = buildConfig
|
config.build = buildConfig
|
||||||
|
|
||||||
const resolvedOutputs = resolveBuildOutputs(config.build.rolldownOptions!.output, config.build.lib || false)
|
const outputs = resolveBuildOutputs(config.build.rolldownOptions!.output, config.build.lib)
|
||||||
|
|
||||||
if (resolvedOutputs) {
|
if (outputs.some(({ format }) => format === 'es')) {
|
||||||
const outputs = Array.isArray(resolvedOutputs) ? resolvedOutputs : [resolvedOutputs]
|
if (Array.isArray(config.build.rolldownOptions!.output)) {
|
||||||
|
config.build.rolldownOptions!.output.forEach(output => {
|
||||||
if (outputs.find(({ format }) => format === 'es')) {
|
if (output.format === 'es') {
|
||||||
if (Array.isArray(config.build.rolldownOptions!.output)) {
|
output['entryFileNames'] = '[name].mjs'
|
||||||
config.build.rolldownOptions!.output.forEach(output => {
|
output['chunkFileNames'] = '[name]-[hash].mjs'
|
||||||
if (output.format === 'es') {
|
}
|
||||||
output['entryFileNames'] = '[name].mjs'
|
})
|
||||||
output['chunkFileNames'] = '[name]-[hash].mjs'
|
} else {
|
||||||
}
|
config.build.rolldownOptions!.output!['entryFileNames'] = '[name].mjs'
|
||||||
})
|
config.build.rolldownOptions!.output!['chunkFileNames'] = config.build.lib
|
||||||
} else {
|
? '[name]-[hash].mjs'
|
||||||
config.build.rolldownOptions!.output!['entryFileNames'] = '[name].mjs'
|
: path.posix.join(build.assetsDir || defaultConfig.build.assetsDir, '[name]-[hash].mjs')
|
||||||
config.build.rolldownOptions!.output!['chunkFileNames'] = config.build.lib
|
|
||||||
? '[name]-[hash].mjs'
|
|
||||||
: path.posix.join(build.assetsDir || defaultConfig.build.assetsDir, '[name]-[hash].mjs')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,16 +286,15 @@ export function electronPreloadConfigValidatorPlugin(): Plugin {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const resolvedOutputs = resolveBuildOutputs(rolldownOptions.output, libOptions)
|
const outputs = resolveBuildOutputs(rolldownOptions.output, libOptions)
|
||||||
|
|
||||||
if (resolvedOutputs) {
|
if (outputs.length > 0) {
|
||||||
const outputs = Array.isArray(resolvedOutputs) ? resolvedOutputs : [resolvedOutputs]
|
|
||||||
if (outputs.length > 1) {
|
if (outputs.length > 1) {
|
||||||
throw new Error('The electron vite preload config does not support multiple outputs.')
|
throw new Error('The electron vite preload config does not support multiple outputs.')
|
||||||
} else {
|
} else {
|
||||||
const outpout = outputs[0]
|
const output = outputs[0]
|
||||||
if (['es', 'cjs'].includes(outpout.format || '')) {
|
if (['es', 'cjs'].includes(output.format || '')) {
|
||||||
if (outpout.format === 'es' && !supportESM()) {
|
if (output.format === 'es' && !supportESM()) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'The electron vite preload config output format does not support "es", ' +
|
'The electron vite preload config output format does not support "es", ' +
|
||||||
'you can upgrade electron to the latest version or switch to "cjs" format.'
|
'you can upgrade electron to the latest version or switch to "cjs" format.'
|
||||||
|
|||||||
@ -105,7 +105,7 @@ export function swcPlugin(options: SwcOptions = {}): Plugin {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async renderChunk(code, chunk): Promise<null | { code: string; map: SourceMap }> {
|
async renderChunk(code, chunk): Promise<null | { code: string; map: SourceMap }> {
|
||||||
if (!minify || minify === 'terser') {
|
if (!minify || minify === 'terser' || minify === 'oxc') {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const result = await transformWithSWC(code, chunk.fileName, {
|
const result = await transformWithSWC(code, chunk.fileName, {
|
||||||
|
|||||||
19
src/utils.ts
19
src/utils.ts
@ -3,7 +3,7 @@ import path from 'node:path'
|
|||||||
import fs from 'node:fs'
|
import fs from 'node:fs'
|
||||||
import { createHash } from 'node:crypto'
|
import { createHash } from 'node:crypto'
|
||||||
import { createRequire } from 'node:module'
|
import { createRequire } from 'node:module'
|
||||||
import { loadEnv as viteLoadEnv } from 'vite'
|
import { type LibraryOptions, type Rolldown, loadEnv as viteLoadEnv } from 'vite'
|
||||||
|
|
||||||
export function isObject(value: unknown): value is Record<string, unknown> {
|
export function isObject(value: unknown): value is Record<string, unknown> {
|
||||||
return Object.prototype.toString.call(value) === '[object Object]'
|
return Object.prototype.toString.call(value) === '[object Object]'
|
||||||
@ -32,6 +32,23 @@ export function toRelativePath(filename: string, importer: string): string {
|
|||||||
return relPath.startsWith('.') ? relPath : `./${relPath}`
|
return relPath.startsWith('.') ? relPath : `./${relPath}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve configured build outputs into an array.
|
||||||
|
*/
|
||||||
|
export function resolveBuildOutputs(
|
||||||
|
outputs: Rolldown.OutputOptions | Rolldown.OutputOptions[] | undefined,
|
||||||
|
libOptions: LibraryOptions | false | undefined
|
||||||
|
): Rolldown.OutputOptions[] {
|
||||||
|
if (Array.isArray(outputs)) {
|
||||||
|
return outputs
|
||||||
|
}
|
||||||
|
if (libOptions) {
|
||||||
|
const libFormats = libOptions.formats || []
|
||||||
|
return libFormats.map(format => ({ ...outputs, format }))
|
||||||
|
}
|
||||||
|
return outputs ? [outputs] : []
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load `.env` files within the `envDir` (default: `process.cwd()`) .
|
* Load `.env` files within the `envDir` (default: `process.cwd()`) .
|
||||||
* By default, only env variables prefixed with `VITE_`, `MAIN_VITE_`, `PRELOAD_VITE_` and
|
* By default, only env variables prefixed with `VITE_`, `MAIN_VITE_`, `PRELOAD_VITE_` and
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user