mirror of
https://github.com/alex8088/electron-vite.git
synced 2025-04-06 04:05:44 +08:00
feat: static asset handling
This commit is contained in:
parent
f7b4146c56
commit
336b4292eb
18
node.d.ts
vendored
18
node.d.ts
vendored
@ -3,3 +3,21 @@ declare module '*?nodeWorker' {
|
||||
import { Worker, WorkerOptions } from 'node:worker_threads'
|
||||
export default function (options: WorkerOptions): Worker
|
||||
}
|
||||
|
||||
// node asset
|
||||
declare module '*?asset' {
|
||||
const src: string
|
||||
export default src
|
||||
}
|
||||
|
||||
declare module '*?asset&asarUnpack' {
|
||||
const src: string
|
||||
export default src
|
||||
}
|
||||
|
||||
// native node module
|
||||
declare module '*.node' {
|
||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||
const node: any
|
||||
export default node
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import {
|
||||
import { build } from 'esbuild'
|
||||
|
||||
import { electronMainVitePlugin, electronPreloadVitePlugin, electronRendererVitePlugin } from './plugins/electron'
|
||||
import assetPlugin from './plugins/asset'
|
||||
import workerPlugin from './plugins/worker'
|
||||
import { isObject, dynamicImport } from './utils'
|
||||
|
||||
@ -130,7 +131,7 @@ export async function resolveConfig(
|
||||
resetOutDir(mainViteConfig, outDir, 'main')
|
||||
}
|
||||
|
||||
mergePlugins(mainViteConfig, [...electronMainVitePlugin({ root }), workerPlugin()])
|
||||
mergePlugins(mainViteConfig, [...electronMainVitePlugin({ root }), assetPlugin(), workerPlugin()])
|
||||
|
||||
loadResult.config.main = mainViteConfig
|
||||
loadResult.config.main.configFile = false
|
||||
@ -142,7 +143,7 @@ export async function resolveConfig(
|
||||
if (outDir) {
|
||||
resetOutDir(preloadViteConfig, outDir, 'preload')
|
||||
}
|
||||
mergePlugins(preloadViteConfig, electronPreloadVitePlugin({ root }))
|
||||
mergePlugins(preloadViteConfig, [...electronPreloadVitePlugin({ root }), assetPlugin()])
|
||||
|
||||
loadResult.config.preload = preloadViteConfig
|
||||
loadResult.config.preload.configFile = false
|
||||
|
143
src/plugins/asset.ts
Normal file
143
src/plugins/asset.ts
Normal file
@ -0,0 +1,143 @@
|
||||
import path from 'node:path'
|
||||
import fs from 'node:fs/promises'
|
||||
import type { SourceMapInput } from 'rollup'
|
||||
import { type Plugin, normalizePath } from 'vite'
|
||||
import MagicString from 'magic-string'
|
||||
import { cleanUrl, parseRequest, getHash, toRelativePath } from '../utils'
|
||||
|
||||
interface AssetResolved {
|
||||
type: 'asset' | 'native'
|
||||
file: string
|
||||
query: Record<string, string> | null
|
||||
}
|
||||
|
||||
function resolveAsset(id: string): AssetResolved | null {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const nodeAssetRE = /__VITE_NODE_ASSET__([a-z\d]{8})__/g
|
||||
const nodePublicAssetRE = /__VITE_NODE_PUBLIC_ASSET__([a-z\d]{8})__/g
|
||||
|
||||
export default function assetPlugin(): Plugin {
|
||||
let sourcemap: boolean | 'inline' | 'hidden' = false
|
||||
let publicDir = ''
|
||||
let outDir = ''
|
||||
const publicAssetPathCache = new Map<string, string>()
|
||||
const assetCache = new Map<string, string>()
|
||||
return {
|
||||
name: 'vite:node-asset',
|
||||
apply: 'build',
|
||||
enforce: 'pre',
|
||||
configResolved(config): void {
|
||||
sourcemap = config.build.sourcemap
|
||||
publicDir = normalizePath(config.publicDir)
|
||||
outDir = normalizePath(config.build.outDir)
|
||||
},
|
||||
async load(id): Promise<string | void> {
|
||||
const assetResolved = resolveAsset(id)
|
||||
if (!assetResolved) {
|
||||
return
|
||||
}
|
||||
|
||||
let referenceId: string
|
||||
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.readFile(file)
|
||||
const hash = this.emitFile({
|
||||
type: 'asset',
|
||||
name: path.basename(file),
|
||||
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})`
|
||||
}
|
||||
},
|
||||
renderChunk(code, chunk): { code: string; map: SourceMapInput } | null {
|
||||
let match: RegExpExecArray | null
|
||||
let s: MagicString | undefined
|
||||
|
||||
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: true }) : null
|
||||
}
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -75,6 +75,11 @@ export function electronMainVitePlugin(options?: ElectronPluginOptions): Plugin[
|
||||
defaultConfig.build.rollupOptions.output['format'] = 'cjs'
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@ -82,6 +87,10 @@ export function electronMainVitePlugin(options?: ElectronPluginOptions): Plugin[
|
||||
config.define = { ...processEnvDefine(), ...config.define }
|
||||
|
||||
config.envPrefix = config.envPrefix || 'MAIN_VITE_'
|
||||
|
||||
config.publicDir = config.publicDir || 'resources'
|
||||
// do not copy public dir
|
||||
config.build.copyPublicDir = false
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -166,6 +175,11 @@ export function electronPreloadVitePlugin(options?: ElectronPluginOptions): Plug
|
||||
defaultConfig.build.rollupOptions.output['format'] = 'cjs'
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@ -173,6 +187,10 @@ export function electronPreloadVitePlugin(options?: ElectronPluginOptions): Plug
|
||||
config.define = { ...processEnvDefine(), ...config.define }
|
||||
|
||||
config.envPrefix = config.envPrefix || 'PRELOAD_VITE_'
|
||||
|
||||
config.publicDir = config.publicDir || 'resources'
|
||||
// do not copy public dir
|
||||
config.build.copyPublicDir = false
|
||||
}
|
||||
},
|
||||
{
|
||||
|
11
src/utils.ts
11
src/utils.ts
@ -1,4 +1,6 @@
|
||||
import { URL, URLSearchParams } from 'node:url'
|
||||
import path from 'node:path'
|
||||
import { createHash } from 'node:crypto'
|
||||
import { loadEnv as viteLoadEnv } from 'vite'
|
||||
|
||||
export function isObject(value: unknown): value is Record<string, unknown> {
|
||||
@ -26,6 +28,15 @@ export function parseRequest(id: string): Record<string, string> | null {
|
||||
return Object.fromEntries(new URLSearchParams(search))
|
||||
}
|
||||
|
||||
export function getHash(text: Buffer | string): string {
|
||||
return createHash('sha256').update(text).digest('hex').substring(0, 8)
|
||||
}
|
||||
|
||||
export function toRelativePath(filename: string, importer: string): string {
|
||||
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 `MAIN_VITE_`, `PRELOAD_VITE_` and
|
||||
|
Loading…
x
Reference in New Issue
Block a user