mirror of
https://github.com/alex8088/electron-vite.git
synced 2025-04-24 10:36:16 +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'
|
import { Worker, WorkerOptions } from 'node:worker_threads'
|
||||||
export default function (options: WorkerOptions): Worker
|
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 { build } from 'esbuild'
|
||||||
|
|
||||||
import { electronMainVitePlugin, electronPreloadVitePlugin, electronRendererVitePlugin } from './plugins/electron'
|
import { electronMainVitePlugin, electronPreloadVitePlugin, electronRendererVitePlugin } from './plugins/electron'
|
||||||
|
import assetPlugin from './plugins/asset'
|
||||||
import workerPlugin from './plugins/worker'
|
import workerPlugin from './plugins/worker'
|
||||||
import { isObject, dynamicImport } from './utils'
|
import { isObject, dynamicImport } from './utils'
|
||||||
|
|
||||||
@ -130,7 +131,7 @@ export async function resolveConfig(
|
|||||||
resetOutDir(mainViteConfig, outDir, 'main')
|
resetOutDir(mainViteConfig, outDir, 'main')
|
||||||
}
|
}
|
||||||
|
|
||||||
mergePlugins(mainViteConfig, [...electronMainVitePlugin({ root }), workerPlugin()])
|
mergePlugins(mainViteConfig, [...electronMainVitePlugin({ root }), assetPlugin(), workerPlugin()])
|
||||||
|
|
||||||
loadResult.config.main = mainViteConfig
|
loadResult.config.main = mainViteConfig
|
||||||
loadResult.config.main.configFile = false
|
loadResult.config.main.configFile = false
|
||||||
@ -142,7 +143,7 @@ export async function resolveConfig(
|
|||||||
if (outDir) {
|
if (outDir) {
|
||||||
resetOutDir(preloadViteConfig, outDir, 'preload')
|
resetOutDir(preloadViteConfig, outDir, 'preload')
|
||||||
}
|
}
|
||||||
mergePlugins(preloadViteConfig, electronPreloadVitePlugin({ root }))
|
mergePlugins(preloadViteConfig, [...electronPreloadVitePlugin({ root }), assetPlugin()])
|
||||||
|
|
||||||
loadResult.config.preload = preloadViteConfig
|
loadResult.config.preload = preloadViteConfig
|
||||||
loadResult.config.preload.configFile = false
|
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['format'] = 'cjs'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defaultConfig.build.rollupOptions.output['assetFileNames'] = path.posix.join(
|
||||||
|
build.assetsDir || defaultConfig.build.assetsDir,
|
||||||
|
'[name]-[hash].[ext]'
|
||||||
|
)
|
||||||
|
|
||||||
const buildConfig = mergeConfig(defaultConfig.build, build)
|
const buildConfig = mergeConfig(defaultConfig.build, build)
|
||||||
config.build = buildConfig
|
config.build = buildConfig
|
||||||
|
|
||||||
@ -82,6 +87,10 @@ export function electronMainVitePlugin(options?: ElectronPluginOptions): Plugin[
|
|||||||
config.define = { ...processEnvDefine(), ...config.define }
|
config.define = { ...processEnvDefine(), ...config.define }
|
||||||
|
|
||||||
config.envPrefix = config.envPrefix || 'MAIN_VITE_'
|
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['format'] = 'cjs'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defaultConfig.build.rollupOptions.output['assetFileNames'] = path.posix.join(
|
||||||
|
build.assetsDir || defaultConfig.build.assetsDir,
|
||||||
|
'[name]-[hash].[ext]'
|
||||||
|
)
|
||||||
|
|
||||||
const buildConfig = mergeConfig(defaultConfig.build, build)
|
const buildConfig = mergeConfig(defaultConfig.build, build)
|
||||||
config.build = buildConfig
|
config.build = buildConfig
|
||||||
|
|
||||||
@ -173,6 +187,10 @@ export function electronPreloadVitePlugin(options?: ElectronPluginOptions): Plug
|
|||||||
config.define = { ...processEnvDefine(), ...config.define }
|
config.define = { ...processEnvDefine(), ...config.define }
|
||||||
|
|
||||||
config.envPrefix = config.envPrefix || 'PRELOAD_VITE_'
|
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 { URL, URLSearchParams } from 'node:url'
|
||||||
|
import path from 'node:path'
|
||||||
|
import { createHash } from 'node:crypto'
|
||||||
import { loadEnv as viteLoadEnv } from 'vite'
|
import { 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> {
|
||||||
@ -26,6 +28,15 @@ export function parseRequest(id: string): Record<string, string> | null {
|
|||||||
return Object.fromEntries(new URLSearchParams(search))
|
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()`).
|
* Load `.env` files within the `envDir`(default: `process.cwd()`).
|
||||||
* By default, only env variables prefixed with `MAIN_VITE_`, `PRELOAD_VITE_` and
|
* By default, only env variables prefixed with `MAIN_VITE_`, `PRELOAD_VITE_` and
|
||||||
|
Loading…
x
Reference in New Issue
Block a user