electron-vite
Version:
Electron build tooling based on Vite
1,198 lines (1,186 loc) • 48.2 kB
JavaScript
import path from 'node:path';
import fs from 'node:fs';
import { URL, URLSearchParams, pathToFileURL } from 'node:url';
import { createRequire, builtinModules } from 'node:module';
import colors from 'picocolors';
import { loadEnv as loadEnv$1, mergeConfig, normalizePath, createLogger } from 'vite';
import { build } from 'esbuild';
import { spawn } from 'node:child_process';
import { createHash } from 'node:crypto';
import fs$1 from 'node:fs/promises';
import MagicString from 'magic-string';
function isObject(value) {
return Object.prototype.toString.call(value) === '[object Object]';
}
const wildcardHosts = new Set(['0.0.0.0', '::', '0000:0000:0000:0000:0000:0000:0000:0000']);
function resolveHostname(optionsHost) {
return typeof optionsHost === 'string' && !wildcardHosts.has(optionsHost) ? optionsHost : 'localhost';
}
const queryRE = /\?.*$/s;
const hashRE = /#.*$/s;
const cleanUrl = (url) => url.replace(hashRE, '').replace(queryRE, '');
function parseRequest(id) {
const { search } = new URL(id, 'file:');
if (!search) {
return null;
}
return Object.fromEntries(new URLSearchParams(search));
}
function getHash(text) {
return createHash('sha256')
.update(text)
.digest('hex')
.substring(0, 8);
}
function toRelativePath(filename, importer) {
const relPath = path.posix.relative(path.dirname(importer), filename);
return relPath.startsWith('.') ? relPath : `./${relPath}`;
}
/**
* Load `.env` files within the `envDir` (default: `process.cwd()`) .
* By default, only env variables prefixed with `VITE_`, `MAIN_VITE_`, `PRELOAD_VITE_` and
* `RENDERER_VITE_` are loaded, unless `prefixes` is changed.
*/
function loadEnv(mode, envDir = process.cwd(), prefixes = ['VITE_', 'MAIN_VITE_', 'PRELOAD_VITE_', 'RENDERER_VITE_']) {
return loadEnv$1(mode, envDir, prefixes);
}
let packageCached = null;
function loadPackageData(root = process.cwd()) {
if (packageCached)
return packageCached;
const pkg = path.join(root, 'package.json');
if (fs.existsSync(pkg)) {
const _require = createRequire(import.meta.url);
const data = _require(pkg);
packageCached = {
main: data.main,
type: data.type,
dependencies: data.dependencies
};
return packageCached;
}
return null;
}
function isFilePathESM(filePath) {
if (/\.m[jt]s$/.test(filePath) || filePath.endsWith('.ts')) {
return true;
}
else if (/\.c[jt]s$/.test(filePath)) {
return false;
}
else {
const pkg = loadPackageData();
return pkg?.type === 'module';
}
}
const _require$1 = createRequire(import.meta.url);
const ensureElectronEntryFile = (root = process.cwd()) => {
if (process.env.ELECTRON_ENTRY)
return;
const pkg = loadPackageData();
if (pkg) {
if (!pkg.main) {
throw new Error('No entry point found for electron app, please add a "main" field to package.json');
}
else {
const entryPath = path.resolve(root, pkg.main);
if (!fs.existsSync(entryPath)) {
throw new Error(`No electron app entry file found: ${entryPath}`);
}
}
}
else {
throw new Error('Not found: package.json');
}
};
const getElectronMajorVer = () => {
let majorVer = process.env.ELECTRON_MAJOR_VER || '';
if (!majorVer) {
const pkg = _require$1.resolve('electron/package.json');
if (fs.existsSync(pkg)) {
const version = _require$1(pkg).version;
majorVer = version.split('.')[0];
process.env.ELECTRON_MAJOR_VER = majorVer;
}
}
return majorVer;
};
function supportESM() {
const majorVer = getElectronMajorVer();
return parseInt(majorVer) >= 28;
}
function getElectronMajorVersion() {
const majorVer = getElectronMajorVer();
return parseInt(majorVer);
}
function getElectronPath() {
let electronExecPath = process.env.ELECTRON_EXEC_PATH || '';
if (!electronExecPath) {
const electronModulePath = path.dirname(_require$1.resolve('electron'));
const pathFile = path.join(electronModulePath, 'path.txt');
let executablePath;
if (fs.existsSync(pathFile)) {
executablePath = fs.readFileSync(pathFile, 'utf-8');
}
if (executablePath) {
electronExecPath = path.join(electronModulePath, 'dist', executablePath);
process.env.ELECTRON_EXEC_PATH = electronExecPath;
}
else {
throw new Error('Electron uninstall');
}
}
return electronExecPath;
}
function getElectronNodeTarget() {
const electronVer = getElectronMajorVer();
const nodeVer = {
'37': '22.16',
'36': '22.14',
'35': '22.14',
'34': '20.18',
'33': '20.18',
'32': '20.16',
'31': '20.14',
'30': '20.11',
'29': '20.9',
'28': '18.18',
'27': '18.17',
'26': '18.16',
'25': '18.15',
'24': '18.14',
'23': '18.12',
'22': '16.17',
'21': '16.16',
'20': '16.15',
'19': '16.14',
'18': '16.13'
};
if (electronVer && parseInt(electronVer) > 10) {
let target = nodeVer[electronVer];
if (!target)
target = Object.values(nodeVer).reverse()[0];
return 'node' + target;
}
return '';
}
function getElectronChromeTarget() {
const electronVer = getElectronMajorVer();
const chromeVer = {
'37': '138',
'36': '136',
'35': '134',
'34': '132',
'33': '130',
'32': '128',
'31': '126',
'30': '124',
'29': '122',
'28': '120',
'27': '118',
'26': '116',
'25': '114',
'24': '112',
'23': '110',
'22': '108',
'21': '106',
'20': '104',
'19': '102',
'18': '100'
};
if (electronVer && parseInt(electronVer) > 10) {
let target = chromeVer[electronVer];
if (!target)
target = Object.values(chromeVer).reverse()[0];
return 'chrome' + target;
}
return '';
}
function startElectron(root) {
ensureElectronEntryFile(root);
const electronPath = getElectronPath();
const isDev = process.env.NODE_ENV_ELECTRON_VITE === 'development';
const args = process.env.ELECTRON_CLI_ARGS ? JSON.parse(process.env.ELECTRON_CLI_ARGS) : [];
if (!!process.env.REMOTE_DEBUGGING_PORT && isDev) {
args.push(`--remote-debugging-port=${process.env.REMOTE_DEBUGGING_PORT}`);
}
if (!!process.env.V8_INSPECTOR_PORT && isDev) {
args.push(`--inspect=${process.env.V8_INSPECTOR_PORT}`);
}
if (!!process.env.V8_INSPECTOR_BRK_PORT && isDev) {
args.push(`--inspect-brk=${process.env.V8_INSPECTOR_BRK_PORT}`);
}
if (process.env.NO_SANDBOX === '1') {
args.push('--no-sandbox');
}
const entry = process.env.ELECTRON_ENTRY || '.';
const ps = spawn(electronPath, [entry].concat(args), { stdio: 'inherit' });
ps.on('close', process.exit);
return ps;
}
function findLibEntry(root, scope) {
for (const name of ['index', scope]) {
for (const ext of ['js', 'ts', 'mjs', 'cjs']) {
const entryFile = path.resolve(root, 'src', scope, `${name}.${ext}`);
if (fs.existsSync(entryFile)) {
return entryFile;
}
}
}
return undefined;
}
function findInput(root, scope = 'renderer') {
const rendererDir = path.resolve(root, 'src', scope, 'index.html');
if (fs.existsSync(rendererDir)) {
return rendererDir;
}
return '';
}
function processEnvDefine() {
return {
'process.env': `process.env`,
'global.process.env': `global.process.env`,
'globalThis.process.env': `globalThis.process.env`
};
}
function resolveBuildOutputs(outputs, libOptions) {
if (libOptions && !Array.isArray(outputs)) {
const libFormats = libOptions.formats || [];
return libFormats.map(format => ({ ...outputs, format }));
}
return outputs;
}
function electronMainVitePlugin(options) {
return [
{
name: 'vite:electron-main-preset-config',
apply: 'build',
enforce: 'pre',
config(config) {
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 = {
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: {}
},
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) {
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 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"' : ''}.`);
}
}
}
}
}
];
}
function electronPreloadVitePlugin(options) {
return [
{
name: 'vite:electron-preload-preset-config',
apply: 'build',
enforce: 'pre',
config(config) {
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;
}
},
{
name: 'vite:electron-preload-resolved-config',
apply: 'build',
enforce: 'post',
configResolved(config) {
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"' : ''}.`);
}
}
}
}
}
];
}
function electronRendererVitePlugin(options) {
return [
{
name: 'vite:electron-renderer-preset-config',
enforce: 'pre',
config(config) {
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 = () => {
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) {
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.');
}
}
}
];
}
function resolveAsset(id) {
const file = cleanUrl(id);
const query = parseRequest(id);
if (query && typeof query.asset === 'string') {
return {
type: 'asset',
file,
query
};
}
if (file.endsWith('.node')) {
return {
type: 'native',
file,
query
};
}
if (id.endsWith('.wasm?loader')) {
return {
type: 'wasm',
file,
query
};
}
return null;
}
const nodeAssetRE = /__VITE_NODE_ASSET__([\w$]+)__/g;
const nodePublicAssetRE = /__VITE_NODE_PUBLIC_ASSET__([a-z\d]{8})__/g;
const wasmHelperId = '\0__electron-vite-wasm-helper';
const wasmHelperCode = `
import { join } from 'path'
import { readFile } from 'fs/promises'
export default async function loadWasm(file, importObject = {}) {
const wasmBuffer = await readFile(join(__dirname, file))
const result = await WebAssembly.instantiate(wasmBuffer, importObject)
return result.instance
}
`;
function assetPlugin() {
let sourcemap = false;
let publicDir = '';
let outDir = '';
const publicAssetPathCache = new Map();
const assetCache = new Map();
return {
name: 'vite:node-asset',
apply: 'build',
enforce: 'pre',
buildStart() {
publicAssetPathCache.clear();
assetCache.clear();
},
configResolved(config) {
sourcemap = config.build.sourcemap;
publicDir = normalizePath(config.publicDir);
outDir = normalizePath(path.resolve(config.root, config.build.outDir));
},
resolveId(id) {
if (id === wasmHelperId) {
return id;
}
},
async load(id) {
if (id === wasmHelperId) {
return wasmHelperCode;
}
if (id.startsWith('\0')) {
// Rollup convention, this id should be handled by the
// plugin that marked it with \0
return;
}
const assetResolved = resolveAsset(id);
if (!assetResolved) {
return;
}
let referenceId;
const file = assetResolved.file;
if (publicDir && file.startsWith(publicDir)) {
const hash = getHash(file);
if (!publicAssetPathCache.get(hash)) {
publicAssetPathCache.set(hash, file);
}
referenceId = `__VITE_NODE_PUBLIC_ASSET__${hash}__`;
}
else {
const cached = assetCache.get(file);
if (cached) {
referenceId = cached;
}
else {
const source = await fs$1.readFile(file);
const hash = this.emitFile({
type: 'asset',
name: path.basename(file),
source: source
});
referenceId = `__VITE_NODE_ASSET__${hash}__`;
assetCache.set(file, referenceId);
}
}
if (assetResolved.type === 'asset') {
if (assetResolved.query && typeof assetResolved.query.asarUnpack === 'string') {
return `
import { join } from 'path'
export default join(__dirname, ${referenceId}).replace('app.asar', 'app.asar.unpacked')`;
}
else {
return `
import { join } from 'path'
export default join(__dirname, ${referenceId})`;
}
}
if (assetResolved.type === 'native') {
return `export default require(${referenceId})`;
}
if (assetResolved.type === 'wasm') {
return `
import loadWasm from ${JSON.stringify(wasmHelperId)}
export default importObject => loadWasm(${referenceId}, importObject)`;
}
},
renderChunk(code, chunk) {
let match;
let s;
nodeAssetRE.lastIndex = 0;
if (code.match(nodeAssetRE)) {
while ((match = nodeAssetRE.exec(code))) {
s ||= new MagicString(code);
const [full, hash] = match;
const filename = this.getFileName(hash);
const outputFilepath = toRelativePath(filename, chunk.fileName);
const replacement = JSON.stringify(outputFilepath);
s.overwrite(match.index, match.index + full.length, replacement, {
contentOnly: true
});
}
}
nodePublicAssetRE.lastIndex = 0;
if (code.match(nodePublicAssetRE)) {
while ((match = nodePublicAssetRE.exec(code))) {
s ||= new MagicString(code);
const [full, hash] = match;
const filename = publicAssetPathCache.get(hash);
const outputFilepath = toRelativePath(filename, normalizePath(path.join(outDir, chunk.fileName)));
const replacement = JSON.stringify(outputFilepath);
s.overwrite(match.index, match.index + full.length, replacement, {
contentOnly: true
});
}
}
if (s) {
return {
code: s.toString(),
map: sourcemap ? s.generateMap({ hires: 'boundary' }) : null
};
}
else {
return null;
}
}
};
}
const nodeWorkerAssetUrlRE = /__VITE_NODE_WORKER_ASSET__([\w$]+)__/g;
/**
* Resolve `?nodeWorker` import and automatically generate `Worker` wrapper.
*/
function workerPlugin() {
let sourcemap = false;
return {
name: 'vite:node-worker',
apply: 'build',
enforce: 'pre',
configResolved(config) {
sourcemap = config.build.sourcemap;
},
resolveId(id, importer) {
const query = parseRequest(id);
if (query && typeof query.nodeWorker === 'string') {
return id + `&importer=${importer}`;
}
},
load(id) {
const query = parseRequest(id);
if (query && typeof query.nodeWorker === 'string' && typeof query.importer === 'string') {
const cleanPath = cleanUrl(id);
const hash = this.emitFile({
type: 'chunk',
id: cleanPath,
importer: query.importer
});
const assetRefId = `__VITE_NODE_WORKER_ASSET__${hash}__`;
return `
import { Worker } from 'node:worker_threads';
export default function (options) { return new Worker(new URL(${assetRefId}, import.meta.url), options); }`;
}
},
renderChunk(code, chunk) {
if (code.match(nodeWorkerAssetUrlRE)) {
let match;
const s = new MagicString(code);
while ((match = nodeWorkerAssetUrlRE.exec(code))) {
const [full, hash] = match;
const filename = this.getFileName(hash);
const outputFilepath = toRelativePath(filename, chunk.fileName);
const replacement = JSON.stringify(outputFilepath);
s.overwrite(match.index, match.index + full.length, replacement, {
contentOnly: true
});
}
return {
code: s.toString(),
map: sourcemap ? s.generateMap({ hires: 'boundary' }) : null
};
}
return null;
}
};
}
function importMetaPlugin() {
return {
name: 'vite:import-meta',
apply: 'build',
enforce: 'pre',
resolveImportMeta(property, { format }) {
if (property === 'url' && format === 'cjs') {
return `require("url").pathToFileURL(__filename).href`;
}
if (property === 'filename' && format === 'cjs') {
return `__filename`;
}
if (property === 'dirname' && format === 'cjs') {
return `__dirname`;
}
return null;
}
};
}
/*
* The core of this plugin was conceived by pi0 and is taken from the following repository:
* https://github.com/unjs/unbuild/blob/main/src/builder/plugins/cjs.ts
* license: https://github.com/unjs/unbuild/blob/main/LICENSE
*/
const CJSyntaxRe = /__filename|__dirname|require\(|require\.resolve\(/;
const CJSShim_normal = `
// -- CommonJS Shims --
import __cjs_url__ from 'node:url';
import __cjs_path__ from 'node:path';
import __cjs_mod__ from 'node:module';
const __filename = __cjs_url__.fileURLToPath(import.meta.url);
const __dirname = __cjs_path__.dirname(__filename);
const require = __cjs_mod__.createRequire(import.meta.url);
`;
const CJSShim_node_20_11 = `
// -- CommonJS Shims --
import __cjs_mod__ from 'node:module';
const __filename = import.meta.filename;
const __dirname = import.meta.dirname;
const require = __cjs_mod__.createRequire(import.meta.url);
`;
const ESMStaticImportRe = /(?<=\s|^|;)import\s*([\s"']*(?<imports>[\p{L}\p{M}\w\t\n\r $*,/{}@.]+)from\s*)?["']\s*(?<specifier>(?<="\s*)[^"]*[^\s"](?=\s*")|(?<='\s*)[^']*[^\s'](?=\s*'))\s*["'][\s;]*/gmu;
function findStaticImports(code) {
const matches = [];
for (const match of code.matchAll(ESMStaticImportRe)) {
matches.push({ end: (match.index || 0) + match[0].length });
}
return matches;
}
function esmShimPlugin() {
let sourcemap = false;
const CJSShim = getElectronMajorVersion() >= 30 ? CJSShim_node_20_11 : CJSShim_normal;
return {
name: 'vite:esm-shim',
apply: 'build',
enforce: 'post',
configResolved(config) {
sourcemap = config.build.sourcemap;
},
renderChunk(code, _chunk, options) {
if (options.format === 'es') {
if (code.includes(CJSShim) || !CJSyntaxRe.test(code)) {
return null;
}
const lastESMImport = findStaticImports(code).pop();
const indexToAppend = lastESMImport ? lastESMImport.end : 0;
const s = new MagicString(code);
s.appendRight(indexToAppend, CJSShim);
return {
code: s.toString(),
map: sourcemap ? s.generateMap({ hires: 'boundary' }) : null
};
}
return null;
}
};
}
const modulePathRE = /__VITE_MODULE_PATH__([\w$]+)__/g;
/**
* Resolve `?modulePath` import and return the module bundle path.
*/
function modulePathPlugin() {
let sourcemap = false;
return {
name: 'vite:module-path',
apply: 'build',
enforce: 'pre',
configResolved(config) {
sourcemap = config.build.sourcemap;
},
resolveId(id, importer) {
const query = parseRequest(id);
if (query && typeof query.modulePath === 'string') {
return id + `&importer=${importer}`;
}
},
load(id) {
const query = parseRequest(id);
if (query && typeof query.modulePath === 'string' && typeof query.importer === 'string') {
const cleanPath = cleanUrl(id);
const hash = this.emitFile({
type: 'chunk',
id: cleanPath,
importer: query.importer
});
const refId = `__VITE_MODULE_PATH__${hash}__`;
return `
import { join } from 'path'
export default join(__dirname, ${refId})`;
}
},
renderChunk(code, chunk) {
if (code.match(modulePathRE)) {
let match;
const s = new MagicString(code);
while ((match = modulePathRE.exec(code))) {
const [full, hash] = match;
const filename = this.getFileName(hash);
const outputFilepath = toRelativePath(filename, chunk.fileName);
const replacement = JSON.stringify(outputFilepath);
s.overwrite(match.index, match.index + full.length, replacement, {
contentOnly: true
});
}
return {
code: s.toString(),
map: sourcemap ? s.generateMap({ hires: 'boundary' }) : null
};
}
return null;
}
};
}
function defineConfig(config) {
return config;
}
async function resolveConfig(inlineConfig, command, defaultMode = 'development') {
const config = inlineConfig;
const mode = inlineConfig.mode || defaultMode;
process.env.NODE_ENV = defaultMode;
let userConfig;
let configFileDependencies = [];
let { configFile } = config;
if (configFile !== false) {
const configEnv = {
mode,
command
};
const loadResult = await loadConfigFromFile(configEnv, configFile, config.root, config.logLevel, config.ignoreConfigWarning);
if (loadResult) {
const root = config.root;
delete config.root;
delete config.configFile;
const outDir = config.build?.outDir;
if (loadResult.config.main) {
const mainViteConfig = mergeConfig(loadResult.config.main, deepClone(config));
mainViteConfig.mode = inlineConfig.mode || mainViteConfig.mode || defaultMode;
if (outDir) {
resetOutDir(mainViteConfig, outDir, 'main');
}
mergePlugins(mainViteConfig, [
...electronMainVitePlugin({ root }),
assetPlugin(),
workerPlugin(),
modulePathPlugin(),
importMetaPlugin(),
esmShimPlugin()
]);
loadResult.config.main = mainViteConfig;
loadResult.config.main.configFile = false;
}
if (loadResult.config.preload) {
const preloadViteConfig = mergeConfig(loadResult.config.preload, deepClone(config));
preloadViteConfig.mode = inlineConfig.mode || preloadViteConfig.mode || defaultMode;
if (outDir) {
resetOutDir(preloadViteConfig, outDir, 'preload');
}
mergePlugins(preloadViteConfig, [
...electronPreloadVitePlugin({ root }),
assetPlugin(),
importMetaPlugin(),
esmShimPlugin()
]);
loadResult.config.preload = preloadViteConfig;
loadResult.config.preload.configFile = false;
}
if (loadResult.config.renderer) {
const rendererViteConfig = mergeConfig(loadResult.config.renderer, deepClone(config));
rendererViteConfig.mode = inlineConfig.mode || rendererViteConfig.mode || defaultMode;
if (outDir) {
resetOutDir(rendererViteConfig, outDir, 'renderer');
}
mergePlugins(rendererViteConfig, electronRendererVitePlugin({ root }));
loadResult.config.renderer = rendererViteConfig;
loadResult.config.renderer.configFile = false;
}
userConfig = loadResult.config;
configFile = loadResult.path;
configFileDependencies = loadResult.dependencies;
}
}
const resolved = {
config: userConfig,
configFile: configFile ? normalizePath(configFile) : undefined,
configFileDependencies
};
return resolved;
}
function deepClone(data) {
return JSON.parse(JSON.stringify(data));
}
function resetOutDir(config, outDir, subOutDir) {
let userOutDir = config.build?.outDir;
if (outDir === userOutDir) {
userOutDir = path.resolve(config.root || process.cwd(), outDir, subOutDir);
if (config.build) {
config.build.outDir = userOutDir;
}
else {
config.build = { outDir: userOutDir };
}
}
}
function mergePlugins(config, plugins) {
const userPlugins = config.plugins || [];
config.plugins = userPlugins.concat(plugins);
}
const CONFIG_FILE_NAME = 'electron.vite.config';
async function loadConfigFromFile(configEnv, configFile, configRoot = process.cwd(), logLevel, ignoreConfigWarning = false) {
if (configFile && /^vite.config.(js|ts|mjs|cjs|mts|cts)$/.test(configFile)) {
throw new Error(`config file cannot be named ${configFile}.`);
}
const resolvedPath = configFile
? path.resolve(configFile)
: findConfigFile(configRoot, ['js', 'ts', 'mjs', 'cjs', 'mts', 'cts']);
if (!resolvedPath) {
return {
path: '',
config: { main: {}, preload: {}, renderer: {} },
dependencies: []
};
}
const isESM = isFilePathESM(resolvedPath);
try {
const bundled = await bundleConfigFile(resolvedPath, isESM);
const userConfig = await loadConfigFormBundledFile(configRoot, resolvedPath, bundled.code, isESM);
const config = await (typeof userConfig === 'function' ? userConfig(configEnv) : userConfig);
if (!isObject(config)) {
throw new Error(`config must export or return an object`);
}
const configRequired = [];
let mainConfig;
if (config.main) {
const mainViteConfig = config.main;
mainConfig = await (typeof mainViteConfig === 'function' ? mainViteConfig(configEnv) : mainViteConfig);
if (!isObject(mainConfig)) {
throw new Error(`main config must export or return an object`);
}
}
else {
configRequired.push('main');
}
let rendererConfig;
if (config.renderer) {
const rendererViteConfig = config.renderer;
rendererConfig = await (typeof rendererViteConfig === 'function'
? rendererViteConfig(configEnv)
: rendererViteConfig);
if (!isObject(rendererConfig)) {
throw new Error(`renderer config must export or return an object`);
}
}
else {
configRequired.push('renderer');
}
let preloadConfig;
if (config.preload) {
const preloadViteConfig = config.preload;
preloadConfig = await (typeof preloadViteConfig === 'function' ? preloadViteConfig(configEnv) : preloadViteConfig);
if (!isObject(preloadConfig)) {
throw new Error(`preload config must export or return an object`);
}
}
else {
configRequired.push('preload');
}
if (!ignoreConfigWarning && configRequired.length > 0) {
createLogger(logLevel).warn(colors.yellow(`${configRequired.join(' and ')} config is missing`));
}
return {
path: normalizePath(resolvedPath),
config: {
main: mainConfig,
renderer: rendererConfig,
preload: preloadConfig
},
dependencies: bundled.dependencies
};
}
catch (e) {
createLogger(logLevel).error(colors.red(`failed to load config from ${resolvedPath}`), { error: e });
throw e;
}
}
function findConfigFile(configRoot, extensions) {
for (const ext of extensions) {
const configFile = path.resolve(configRoot, `${CONFIG_FILE_NAME}.${ext}`);
if (fs.existsSync(configFile)) {
return configFile;
}
}
return '';
}
async function bundleConfigFile(fileName, isESM) {
const dirnameVarName = '__electron_vite_injected_dirname';
const filenameVarName = '__electron_vite_injected_filename';
const importMetaUrlVarName = '__electron_vite_injected_import_meta_url';
const result = await build({
absWorkingDir: process.cwd(),
entryPoints: [fileName],
write: false,
target: ['node20'],
platform: 'node',
bundle: true,
format: isESM ? 'esm' : 'cjs',
sourcemap: false,
metafile: true,
define: {
__dirname: dirnameVarName,
__filename: filenameVarName,
'import.meta.url': importMetaUrlVarName
},
plugins: [
{
name: 'externalize-deps',
setup(build) {
build.onResolve({ filter: /.*/ }, args => {
const id = args.path;
if (id[0] !== '.' && !path.isAbsolute(id)) {
return {
external: true
};
}
return null;
});
}
},
{
name: 'replace-import-meta',
setup(build) {
build.onLoad({ filter: /\.[cm]?[jt]s$/ }, async (args) => {
const contents = await fs.promises.readFile(args.path, 'utf8');
const injectValues = `const ${dirnameVarName} = ${JSON.stringify(path.dirname(args.path))};` +
`const ${filenameVarName} = ${JSON.stringify(args.path)};` +
`const ${importMetaUrlVarName} = ${JSON.stringify(pathToFileURL(args.path).href)};`;
return {
loader: args.path.endsWith('ts') ? 'ts' : 'js',
contents: injectValues + contents
};
});
}
}
]
});
const { text } = result.outputFiles[0];
return {
code: text,
dependencies: result.metafile ? Object.keys(result.metafile.inputs) : []
};
}
const _require = createRequire(import.meta.url);
async function loadConfigFormBundledFile(configRoot, configFile, bundledCode, isESM) {
if (isESM) {
const fileNameTmp = path.resolve(configRoot, `${CONFIG_FILE_NAME}.${Date.now()}.mjs`);
fs.writeFileSync(fileNameTmp, bundledCode);
const fileUrl = pathToFileURL(fileNameTmp);
try {
return (await import(fileUrl.href)).default;
}
finally {
try {
fs.unlinkSync(fileNameTmp);
// eslint-disable-next-line no-empty
}
catch { }
}
}
else {
const extension = path.extname(configFile);
const realFileName = fs.realpathSync(configFile);
const loaderExt = extension in _require.extensions ? extension : '.js';
const defaultLoader = _require.extensions[loaderExt];
_require.extensions[loaderExt] = (module, filename) => {
if (filename === realFileName) {
module._compile(bundledCode, filename);
}
else {
defaultLoader(module, filename);
}
};
delete _require.cache[_require.resolve(configFile)];
const raw = _require(configFile);
_require.extensions[loaderExt] = defaultLoader;
return raw.__esModule ? raw.default : raw;
}
}
export { loadEnv as a, loadConfigFromFile as b, resolveHostname as c, defineConfig as d, getElectronPath as g, loadPackageData as l, resolveConfig as r, startElectron as s, toRelativePath as t };