electron-vite/src/load.ts

131 lines
4.0 KiB
TypeScript

import path from 'node:path'
import fs from 'node:fs'
import { pathToFileURL } from 'node:url'
import { createRequire } from 'node:module'
import { rolldown } from 'rolldown'
import type { RolldownOutput, OutputChunk } from 'rolldown'
const CONFIG_FILE_NAME = 'electron.vite.config'
export function findConfigFile(
configRoot: string,
extensions: string[] = ['js', 'ts', 'mjs', 'cjs', 'mts', 'cts']
): string {
for (const ext of extensions) {
const configFile = path.resolve(configRoot, `${CONFIG_FILE_NAME}.${ext}`)
if (fs.existsSync(configFile)) {
return configFile
}
}
return ''
}
export async function bundleConfigFile(
fileName: string,
isESM: boolean
): Promise<{ code: string; dependencies: string[] }> {
const dirnameVarName = '__electron_vite_injected_dirname'
const filenameVarName = '__electron_vite_injected_filename'
const importMetaUrlVarName = '__electron_vite_injected_import_meta_url'
const bundle = await rolldown({
input: path.resolve(fileName),
platform: 'node',
transform: {
target: 'node20',
define: {
__dirname: dirnameVarName,
__filename: filenameVarName,
'import.meta.url': importMetaUrlVarName
}
},
plugins: [
{
name: 'externalize-deps',
resolveId(id) {
if (id[0] !== '.' && !path.isAbsolute(id)) {
return { id, external: true }
}
return null
}
},
{
name: 'inject-file-scope-variables',
transform: {
filter: { id: /\.[cm]?[jt]s$/ },
handler(code, id) {
const injectValues =
`const ${dirnameVarName} = ${JSON.stringify(path.dirname(id))};` +
`const ${filenameVarName} = ${JSON.stringify(id)};` +
`const ${importMetaUrlVarName} = ${JSON.stringify(pathToFileURL(id).href)};`
return { code: injectValues + code, map: null }
}
}
}
]
})
let result: RolldownOutput
try {
result = await bundle.generate({
format: isESM ? 'esm' : 'cjs',
sourcemap: 'inline',
sourcemapPathTransform(relative, sourcemapPath) {
return path.resolve(path.dirname(sourcemapPath), relative)
},
codeSplitting: false
})
} finally {
await bundle.close()
}
const entryChunk = result.output.find((chunk): chunk is OutputChunk => chunk.type === 'chunk' && chunk.isEntry)
if (!entryChunk) {
throw new Error(`Failed to bundle config file: ${fileName}`)
}
return {
code: entryChunk.code,
dependencies: entryChunk.moduleIds.filter(id => !id.startsWith('\0'))
}
}
interface NodeModuleWithCompile extends NodeModule {
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
_compile(code: string, filename: string): any
}
const _require = createRequire(import.meta.url)
export async function loadConfigFormBundledFile<T>(fileName: string, bundledCode: string, isESM: boolean): Promise<T> {
if (isESM) {
const fileNameTmp = `${fileName}.${Date.now()}-${Math.random().toString(16).slice(2)}.mjs`
fs.writeFileSync(fileNameTmp, bundledCode)
const fileUrl = pathToFileURL(fileNameTmp)
try {
return (await import(fileUrl.href)).default
} finally {
try {
fs.unlinkSync(fileNameTmp)
} catch {}
}
} else {
const extension = path.extname(fileName)
const realFileName = fs.realpathSync(fileName)
const loaderExt = extension in _require.extensions ? extension : '.js'
const defaultLoader = _require.extensions[loaderExt]!
_require.extensions[loaderExt] = (module: NodeModule, filename: string): void => {
if (filename === realFileName) {
;(module as NodeModuleWithCompile)._compile(bundledCode, filename)
} else {
defaultLoader(module, filename)
}
}
delete _require.cache[_require.resolve(fileName)]
const raw = _require(fileName)
_require.extensions[loaderExt] = defaultLoader
return raw.__esModule ? raw.default : raw
}
}