perf(plugin): more efficient module filtering via regular expressions

This commit is contained in:
alex8088 2025-10-19 11:57:23 +08:00
parent 28bb22b353
commit 70e027d38a
4 changed files with 34 additions and 88 deletions

View File

@ -3,48 +3,15 @@ import fs from 'node:fs/promises'
import type { SourceMapInput } from 'rollup' import type { SourceMapInput } from 'rollup'
import { type Plugin, normalizePath } from 'vite' import { type Plugin, normalizePath } from 'vite'
import MagicString from 'magic-string' import MagicString from 'magic-string'
import { cleanUrl, parseRequest, getHash, toRelativePath } from '../utils' import { cleanUrl, getHash, toRelativePath } from '../utils'
interface AssetResolved {
type: 'asset' | 'native' | 'wasm'
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
}
}
if (id.endsWith('.wasm?loader')) {
return {
type: 'wasm',
file,
query
}
}
return null
}
const nodeAssetRE = /__VITE_NODE_ASSET__([\w$]+)__/g const nodeAssetRE = /__VITE_NODE_ASSET__([\w$]+)__/g
const nodePublicAssetRE = /__VITE_NODE_PUBLIC_ASSET__([a-z\d]{8})__/g const nodePublicAssetRE = /__VITE_NODE_PUBLIC_ASSET__([a-z\d]{8})__/g
const assetImportRE = /(?:[?|&]asset(?:&|$)|\.wasm\?loader$|\.node$)/
const assetRE = /[?|&]asset(?:&|$)/
const assetUnpackRE = /[?|&]asset&asarUnpack$/
const wasmHelperId = '\0__electron-vite-wasm-helper' const wasmHelperId = '\0__electron-vite-wasm-helper'
const wasmHelperCode = ` const wasmHelperCode = `
@ -87,19 +54,12 @@ export default function assetPlugin(): Plugin {
return wasmHelperCode return wasmHelperCode
} }
if (id.startsWith('\0')) { if (id.startsWith('\0') || !assetImportRE.test(id)) {
// Rollup convention, this id should be handled by the
// plugin that marked it with \0
return
}
const assetResolved = resolveAsset(id)
if (!assetResolved) {
return return
} }
let referenceId: string let referenceId: string
const file = assetResolved.file const file = cleanUrl(id)
if (publicDir && file.startsWith(publicDir)) { if (publicDir && file.startsWith(publicDir)) {
const hash = getHash(file) const hash = getHash(file)
if (!publicAssetPathCache.get(hash)) { if (!publicAssetPathCache.get(hash)) {
@ -122,8 +82,8 @@ export default function assetPlugin(): Plugin {
} }
} }
if (assetResolved.type === 'asset') { if (assetRE.test(id)) {
if (assetResolved.query && typeof assetResolved.query.asarUnpack === 'string') { if (assetUnpackRE.test(id)) {
return ` return `
import { join } from 'path' import { join } from 'path'
export default join(__dirname, ${referenceId}).replace('app.asar', 'app.asar.unpacked')` export default join(__dirname, ${referenceId}).replace('app.asar', 'app.asar.unpacked')`
@ -134,11 +94,11 @@ export default function assetPlugin(): Plugin {
} }
} }
if (assetResolved.type === 'native') { if (id.endsWith('.node')) {
return `export default require(${referenceId})` return `export default require(${referenceId})`
} }
if (assetResolved.type === 'wasm') { if (id.endsWith('.wasm?loader')) {
return ` return `
import loadWasm from ${JSON.stringify(wasmHelperId)} import loadWasm from ${JSON.stringify(wasmHelperId)}
export default importObject => loadWasm(${referenceId}, importObject)` export default importObject => loadWasm(${referenceId}, importObject)`

View File

@ -2,7 +2,7 @@ import path from 'node:path'
import { type Plugin, type InlineConfig, build as viteBuild, mergeConfig } from 'vite' import { type Plugin, type InlineConfig, build as viteBuild, mergeConfig } from 'vite'
import type { SourceMapInput, RollupOutput, OutputOptions } from 'rollup' import type { SourceMapInput, RollupOutput, OutputOptions } from 'rollup'
import MagicString from 'magic-string' import MagicString from 'magic-string'
import { cleanUrl, parseRequest, toRelativePath } from '../utils' import { cleanUrl, toRelativePath } from '../utils'
const modulePathRE = /__VITE_MODULE_PATH__([\w$]+)__/g const modulePathRE = /__VITE_MODULE_PATH__([\w$]+)__/g
@ -18,17 +18,10 @@ export default function modulePathPlugin(config: InlineConfig): Plugin {
configResolved(config): void { configResolved(config): void {
sourcemap = config.build.sourcemap sourcemap = config.build.sourcemap
}, },
resolveId(id, importer): string | void {
const query = parseRequest(id)
if (query && typeof query.modulePath === 'string') {
return id + `&importer=${importer}`
}
},
async load(id): Promise<string | void> { async load(id): Promise<string | void> {
const query = parseRequest(id) if (id.endsWith('?modulePath')) {
if (query && typeof query.modulePath === 'string' && typeof query.importer === 'string') { // id resolved by Vite resolve plugin
const entry = path.resolve(path.dirname(query.importer), cleanUrl(id)) const bundle = await bundleEntryFile(cleanUrl(id), config)
const bundle = await bundleEntryFile(entry, config)
const [outputChunk, ...outputChunks] = bundle.output const [outputChunk, ...outputChunks] = bundle.output
const hash = this.emitFile({ const hash = this.emitFile({
type: 'asset', type: 'asset',
@ -44,8 +37,8 @@ export default function modulePathPlugin(config: InlineConfig): Plugin {
}) })
const refId = `__VITE_MODULE_PATH__${hash}__` const refId = `__VITE_MODULE_PATH__${hash}__`
return ` return `
import { join } from 'path' import { join } from 'path'
export default join(__dirname, ${refId})` export default join(__dirname, ${refId})`
} }
}, },
renderChunk(code, chunk): { code: string; map: SourceMapInput } | null { renderChunk(code, chunk): { code: string; map: SourceMapInput } | null {

View File

@ -1,9 +1,11 @@
import type { Plugin } from 'vite' import type { Plugin } from 'vite'
import type { SourceMapInput } from 'rollup' import type { SourceMapInput } from 'rollup'
import MagicString from 'magic-string' import MagicString from 'magic-string'
import { cleanUrl, parseRequest, toRelativePath } from '../utils' import { cleanUrl, toRelativePath } from '../utils'
const nodeWorkerAssetUrlRE = /__VITE_NODE_WORKER_ASSET__([\w$]+)__/g const nodeWorkerAssetUrlRE = /__VITE_NODE_WORKER_ASSET__([\w$]+)__/g
const nodeWorkerRE = /\?nodeWorker(?:&|$)/
const nodeWorkerImporterRE = /(?:\?)nodeWorker&importer=([^&]+)(?:&|$)/
/** /**
* Resolve `?nodeWorker` import and automatically generate `Worker` wrapper. * Resolve `?nodeWorker` import and automatically generate `Worker` wrapper.
@ -18,24 +20,24 @@ export default function workerPlugin(): Plugin {
sourcemap = config.build.sourcemap sourcemap = config.build.sourcemap
}, },
resolveId(id, importer): string | void { resolveId(id, importer): string | void {
const query = parseRequest(id) if (id.endsWith('?nodeWorker')) {
if (query && typeof query.nodeWorker === 'string') {
return id + `&importer=${importer}` return id + `&importer=${importer}`
} }
}, },
load(id): string | void { load(id): string | void {
const query = parseRequest(id) if (nodeWorkerRE.test(id)) {
if (query && typeof query.nodeWorker === 'string' && typeof query.importer === 'string') { const match = nodeWorkerImporterRE.exec(id)
const cleanPath = cleanUrl(id) if (match) {
const hash = this.emitFile({ const hash = this.emitFile({
type: 'chunk', type: 'chunk',
id: cleanPath, id: cleanUrl(id),
importer: query.importer importer: match[1]
}) })
const assetRefId = `__VITE_NODE_WORKER_ASSET__${hash}__` const assetRefId = `__VITE_NODE_WORKER_ASSET__${hash}__`
return ` return `
import { Worker } from 'node:worker_threads'; import { Worker } from 'node:worker_threads';
export default function (options) { return new Worker(new URL(${assetRefId}, import.meta.url), options); }` export default function (options) { return new Worker(new URL(${assetRefId}, import.meta.url), options); }`
}
} }
}, },
renderChunk(code, chunk): { code: string; map: SourceMapInput } | null { renderChunk(code, chunk): { code: string; map: SourceMapInput } | null {

View File

@ -1,4 +1,3 @@
import { URL, URLSearchParams } from 'node:url'
import path from 'node:path' 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'
@ -20,14 +19,6 @@ export const hashRE = /#.*$/s
export const cleanUrl = (url: string): string => url.replace(hashRE, '').replace(queryRE, '') export const cleanUrl = (url: string): string => url.replace(hashRE, '').replace(queryRE, '')
export function parseRequest(id: string): Record<string, string> | null {
const { search } = new URL(id, 'file:')
if (!search) {
return null
}
return Object.fromEntries(new URLSearchParams(search))
}
export function getHash(text: Buffer | string): string { export function getHash(text: Buffer | string): string {
return createHash('sha256') return createHash('sha256')
.update(text as unknown as Uint8Array) .update(text as unknown as Uint8Array)