fix(bytecodePlugin): bytecode loader injection and chunk module parsing errors (#49)

This commit is contained in:
alex8088 2022-11-11 20:48:23 +08:00
parent 41ff7372c2
commit baf538cfe4

View file

@ -4,6 +4,7 @@ import { spawn } from 'node:child_process'
import colors from 'picocolors' import colors from 'picocolors'
import { type Plugin, type ResolvedConfig, normalizePath } from 'vite' import { type Plugin, type ResolvedConfig, normalizePath } from 'vite'
import * as babel from '@babel/core' import * as babel from '@babel/core'
import MagicString from 'magic-string'
import { getElectronPath } from '../electron' import { getElectronPath } from '../electron'
// Inspired by https://github.com/bytenode/bytenode // Inspired by https://github.com/bytenode/bytenode
@ -151,19 +152,27 @@ export function bytecodePlugin(options: BytecodeOptions = {}): Plugin | null {
const { chunkAlias = [], transformArrowFunctions = true, removeBundleJS = true } = options const { chunkAlias = [], transformArrowFunctions = true, removeBundleJS = true } = options
const _chunkAlias = Array.isArray(chunkAlias) ? chunkAlias : [chunkAlias] const _chunkAlias = Array.isArray(chunkAlias) ? chunkAlias : [chunkAlias]
const transformAllChunks = _chunkAlias.length === 0
const bytecodeChunks: string[] = [] const bytecodeChunks: string[] = []
const nonEntryChunks: string[] = []
const transformAllChunks = _chunkAlias.length === 0
const isBytecodeChunk = (chunkName: string): boolean => {
return transformAllChunks || _chunkAlias.some(alias => alias === chunkName)
}
const _transform = (code: string): string => { const _transform = (code: string): string => {
const re = babel.transform(code, { const re = babel.transform(code, {
plugins: ['@babel/plugin-transform-arrow-functions'] plugins: ['@babel/plugin-transform-arrow-functions']
}) })
return re.code || '' return re.code || ''
} }
const requireBytecodeLoaderStr = '"use strict";\nrequire("./bytecode-loader.js");' const requireBytecodeLoaderStr = '"use strict";\nrequire("./bytecode-loader.js");'
let config: ResolvedConfig let config: ResolvedConfig
let useInRenderer = false let useInRenderer = false
let bytecodeFiles: { name: string; size: number }[] = [] let bytecodeFiles: { name: string; size: number }[] = []
return { return {
name: 'vite:bytecode', name: 'vite:bytecode',
apply: 'build', apply: 'build',
@ -179,29 +188,11 @@ export function bytecodePlugin(options: BytecodeOptions = {}): Plugin | null {
if (useInRenderer) { if (useInRenderer) {
return null return null
} }
if (!transformAllChunks) { if (chunk.type === 'chunk' && isBytecodeChunk(chunk.name)) {
const isBytecodeChunk = _chunkAlias.some(alias => chunk.fileName.startsWith(alias)) bytecodeChunks.push(chunk.fileName)
if (isBytecodeChunk) { if (transformArrowFunctions) {
bytecodeChunks.push(chunk.fileName) return {
if (!chunk.isEntry) { code: _transform(code)
nonEntryChunks.push(chunk.fileName)
}
if (transformArrowFunctions) {
return {
code: _transform(code)
}
}
}
} else {
if (chunk.type === 'chunk') {
bytecodeChunks.push(chunk.fileName)
if (!chunk.isEntry) {
nonEntryChunks.push(chunk.fileName)
}
if (transformArrowFunctions) {
return {
code: _transform(code)
}
} }
} }
} }
@ -224,17 +215,27 @@ export function bytecodePlugin(options: BytecodeOptions = {}): Plugin | null {
const bundles = Object.keys(output) const bundles = Object.keys(output)
const outDir = options.dir! const outDir = options.dir!
bytecodeFiles = [] bytecodeFiles = []
const bytecodeRE = new RegExp(bytecodeChunks.map(chunk => `(${chunk})`).join('|'), 'g')
const keepBundle = (chunkFileName: string): void => {
const newFileName = path.resolve(path.dirname(chunkFileName), `_${path.basename(chunkFileName)}`)
fs.renameSync(chunkFileName, newFileName)
}
await Promise.all( await Promise.all(
bundles.map(async name => { bundles.map(async name => {
const chunk = output[name] const chunk = output[name]
if (chunk.type === 'chunk') { if (chunk.type === 'chunk') {
let _code = chunk.code let _code = chunk.code
nonEntryChunks.forEach(bcc => { if (_code.match(bytecodeRE)) {
if (bcc !== name) { let match: RegExpExecArray | null
const reg = new RegExp(bcc, 'g') const s = new MagicString(_code)
_code = _code.replace(reg, `${bcc}c`) while ((match = bytecodeRE.exec(_code))) {
const [chunkName] = match
s.overwrite(match.index, match.index + chunkName.length, chunkName + 'c', {
contentOnly: true
})
} }
}) _code = s.toString()
}
const chunkFileName = path.resolve(outDir, name) const chunkFileName = path.resolve(outDir, name)
if (bytecodeChunks.includes(name)) { if (bytecodeChunks.includes(name)) {
const bytecodeBuffer = await compileToBytecode(_code) const bytecodeBuffer = await compileToBytecode(_code)
@ -242,8 +243,7 @@ export function bytecodePlugin(options: BytecodeOptions = {}): Plugin | null {
fs.writeFileSync(bytecodeFileName, bytecodeBuffer) fs.writeFileSync(bytecodeFileName, bytecodeBuffer)
if (chunk.isEntry) { if (chunk.isEntry) {
if (!removeBundleJS) { if (!removeBundleJS) {
const newFileName = path.resolve(outDir, `_${name}`) keepBundle(chunkFileName)
fs.renameSync(chunkFileName, newFileName)
} }
const code = requireBytecodeLoaderStr + `\nrequire("./${normalizePath(name + 'c')}");\n` const code = requireBytecodeLoaderStr + `\nrequire("./${normalizePath(name + 'c')}");\n`
fs.writeFileSync(chunkFileName, code) fs.writeFileSync(chunkFileName, code)
@ -251,14 +251,27 @@ export function bytecodePlugin(options: BytecodeOptions = {}): Plugin | null {
if (removeBundleJS) { if (removeBundleJS) {
fs.unlinkSync(chunkFileName) fs.unlinkSync(chunkFileName)
} else { } else {
const newFileName = path.resolve(outDir, `_${name}`) keepBundle(chunkFileName)
fs.renameSync(chunkFileName, newFileName)
} }
} }
bytecodeFiles.push({ name: name + 'c', size: bytecodeBuffer.length }) bytecodeFiles.push({ name: name + 'c', size: bytecodeBuffer.length })
} else { } else {
if (chunk.isEntry) { if (chunk.isEntry) {
_code = _code.replace('"use strict";', requireBytecodeLoaderStr) let hasBytecodeMoudle = false
const idsToHandle = new Set([...chunk.imports, ...chunk.dynamicImports])
for (const moduleId of idsToHandle) {
if (bytecodeChunks.includes(moduleId)) {
hasBytecodeMoudle = true
break
}
const moduleInfo = this.getModuleInfo(moduleId)
if (moduleInfo && !moduleInfo.isExternal) {
const { importers, dynamicImporters } = moduleInfo
for (const importerId of importers) idsToHandle.add(importerId)
for (const importerId of dynamicImporters) idsToHandle.add(importerId)
}
}
_code = hasBytecodeMoudle ? _code.replace('"use strict";', requireBytecodeLoaderStr) : _code
} }
fs.writeFileSync(chunkFileName, _code) fs.writeFileSync(chunkFileName, _code)
} }