feat: standalone bundler
This commit is contained in:
+340
@@ -0,0 +1,340 @@
|
||||
import { Stats } from 'fs'
|
||||
|
||||
export interface NexeBinary {
|
||||
blobPath: string
|
||||
resources: { [key: string]: number[] }
|
||||
layout: {
|
||||
stat: Stats
|
||||
resourceStart: number
|
||||
contentSize?: number
|
||||
contentStart?: number
|
||||
resourceSize?: number
|
||||
}
|
||||
}
|
||||
|
||||
let originalFsMethods: any = null
|
||||
let nexeBinary: NexeBinary | null = null
|
||||
|
||||
function restoreFs(fs: any = require('fs')): NexeBinary | false {
|
||||
if (!nexeBinary) {
|
||||
return false
|
||||
}
|
||||
const source = nexeBinary
|
||||
Object.assign(fs, originalFsMethods)
|
||||
nexeBinary = originalFsMethods = null
|
||||
return source
|
||||
}
|
||||
|
||||
function shimFs(binary: NexeBinary, fs: any = require('fs')) {
|
||||
if (originalFsMethods !== null) {
|
||||
return
|
||||
}
|
||||
originalFsMethods = Object.assign({}, fs)
|
||||
nexeBinary = binary
|
||||
const { blobPath, resources: manifest } = binary
|
||||
const { resourceStart, stat } = binary.layout
|
||||
const directories: { [key: string]: { [key: string]: boolean } } = {},
|
||||
isString = (x: any): x is string => typeof x === 'string' || x instanceof String,
|
||||
isNotFile = () => false,
|
||||
isNotDirectory = isNotFile,
|
||||
isFile = () => true,
|
||||
noop = () => {},
|
||||
isDirectory = isFile,
|
||||
path = require('path')
|
||||
|
||||
let log = (text: string) => {
|
||||
if ((process.env.DEBUG || '').toLowerCase().includes('nexe:require')) {
|
||||
process.stderr.write('[nexe] - ' + text + '\n')
|
||||
} else {
|
||||
log = noop
|
||||
}
|
||||
}
|
||||
|
||||
const getKey = process.platform.startsWith('win')
|
||||
? function getKey(filepath: string): string {
|
||||
let key = path.resolve(filepath)
|
||||
if (key.substr(1, 2) === ':\\') {
|
||||
key = key[0].toUpperCase() + key.substr(1)
|
||||
}
|
||||
return key
|
||||
}
|
||||
: path.resolve
|
||||
|
||||
const statTime = function() {
|
||||
return {
|
||||
dev: 0,
|
||||
ino: 0,
|
||||
nlink: 0,
|
||||
rdev: 0,
|
||||
uid: 123,
|
||||
gid: 500,
|
||||
blksize: 4096,
|
||||
blocks: 0,
|
||||
atime: new Date(stat.atime),
|
||||
atimeMs: stat.atime.getTime(),
|
||||
mtime: new Date(stat.mtime),
|
||||
mtimeMs: stat.mtime.getTime(),
|
||||
ctime: new Date(stat.ctime),
|
||||
ctimMs: stat.ctime.getTime(),
|
||||
birthtime: new Date(stat.birthtime),
|
||||
birthtimeMs: stat.birthtime.getTime()
|
||||
}
|
||||
}
|
||||
|
||||
const createStat = function(directoryExtensions: any, fileExtensions?: any) {
|
||||
if (!fileExtensions) {
|
||||
return Object.assign({}, binary.layout.stat, directoryExtensions, { size: 0 }, statTime())
|
||||
}
|
||||
const size = directoryExtensions[1]
|
||||
return Object.assign({}, binary.layout.stat, fileExtensions, { size }, statTime())
|
||||
}
|
||||
|
||||
const ownStat = function(filepath: string) {
|
||||
setupManifest()
|
||||
const key = getKey(filepath)
|
||||
if (directories[key]) {
|
||||
return createStat({ isDirectory, isFile: isNotFile })
|
||||
}
|
||||
if (manifest[key]) {
|
||||
return createStat(manifest[key], { isFile, isDirectory: isNotDirectory })
|
||||
}
|
||||
}
|
||||
|
||||
function makeLong(filepath: string) {
|
||||
return (path as any)._makeLong && (path as any)._makeLong(filepath)
|
||||
}
|
||||
|
||||
function fileOpts(options: any) {
|
||||
return !options ? {} : isString(options) ? { encoding: options } : options
|
||||
}
|
||||
|
||||
let setupManifest = () => {
|
||||
Object.keys(manifest).forEach(filepath => {
|
||||
const entry = manifest[filepath]
|
||||
const absolutePath = getKey(filepath)
|
||||
const longPath = makeLong(absolutePath)
|
||||
const normalizedPath = path.normalize(filepath)
|
||||
|
||||
if (!manifest[absolutePath]) {
|
||||
manifest[absolutePath] = entry
|
||||
}
|
||||
if (longPath && !manifest[longPath]) {
|
||||
manifest[longPath] = entry
|
||||
}
|
||||
if (!manifest[normalizedPath]) {
|
||||
manifest[normalizedPath] = manifest[filepath]
|
||||
}
|
||||
|
||||
let currentDir = path.dirname(absolutePath)
|
||||
let prevDir = absolutePath
|
||||
|
||||
while (currentDir !== prevDir) {
|
||||
directories[currentDir] = directories[currentDir] || {}
|
||||
directories[currentDir][path.basename(prevDir)] = true
|
||||
const longDir = makeLong(currentDir)
|
||||
if (longDir && !directories[longDir]) {
|
||||
directories[longDir] = directories[currentDir]
|
||||
}
|
||||
prevDir = currentDir
|
||||
currentDir = path.dirname(currentDir)
|
||||
}
|
||||
})
|
||||
setupManifest = noop
|
||||
}
|
||||
|
||||
//naive patches intended to work for most use cases
|
||||
const nfs: any = {
|
||||
existsSync: function existsSync(filepath: string) {
|
||||
setupManifest()
|
||||
const key = getKey(filepath)
|
||||
if (manifest[key] || directories[key]) {
|
||||
return true
|
||||
}
|
||||
return originalFsMethods.existsSync.apply(fs, arguments)
|
||||
},
|
||||
realpath: function realpath(filepath: any, options: any, cb: any): void {
|
||||
setupManifest()
|
||||
const key = getKey(filepath)
|
||||
if (isString(filepath) && (manifest[filepath] || manifest[key])) {
|
||||
return process.nextTick(() => cb(null, filepath))
|
||||
}
|
||||
return originalFsMethods.realpath.call(fs, filepath, options, cb)
|
||||
},
|
||||
realpathSync: function realpathSync(filepath: any, options: any) {
|
||||
setupManifest()
|
||||
const key = getKey(filepath)
|
||||
if (isString(filepath) && (manifest[filepath] || manifest[key])) {
|
||||
return filepath
|
||||
}
|
||||
return originalFsMethods.realpathSync.call(fs, filepath, options)
|
||||
},
|
||||
readdir: function readdir(filepath: string | Buffer, options: any, callback: any) {
|
||||
setupManifest()
|
||||
filepath = filepath.toString()
|
||||
if ('function' === typeof options) {
|
||||
callback = options
|
||||
options = { encoding: 'utf8' }
|
||||
}
|
||||
const dir = directories[getKey(filepath)]
|
||||
if (dir) {
|
||||
process.nextTick(() => {
|
||||
//todo merge with original?
|
||||
callback(null, Object.keys(dir))
|
||||
})
|
||||
} else {
|
||||
return originalFsMethods.readdir.apply(fs, arguments)
|
||||
}
|
||||
},
|
||||
|
||||
readdirSync: function readdirSync(filepath: string | Buffer, options: any) {
|
||||
setupManifest()
|
||||
filepath = filepath.toString()
|
||||
const dir = directories[getKey(filepath)]
|
||||
if (dir) {
|
||||
return Object.keys(dir)
|
||||
}
|
||||
return originalFsMethods.readdirSync.apply(fs, arguments)
|
||||
},
|
||||
|
||||
readFile: function readFile(filepath: any, options: any, callback: any) {
|
||||
setupManifest()
|
||||
const entry = manifest[filepath] || manifest[getKey(filepath)]
|
||||
if (!entry || !isString(filepath)) {
|
||||
return originalFsMethods.readFile.apply(fs, arguments)
|
||||
}
|
||||
const [offset, length] = entry
|
||||
const resourceOffset = resourceStart + offset
|
||||
const encoding = fileOpts(options).encoding
|
||||
callback = typeof options === 'function' ? options : callback
|
||||
|
||||
fs.open(blobPath, 'r', function(err: Error, fd: number) {
|
||||
if (err) return callback(err, null)
|
||||
fs.read(fd, Buffer.alloc(length), 0, length, resourceOffset, function(
|
||||
error: Error,
|
||||
bytesRead: number,
|
||||
result: Buffer
|
||||
) {
|
||||
if (error) {
|
||||
return fs.close(fd, function() {
|
||||
callback(error, null)
|
||||
})
|
||||
}
|
||||
fs.close(fd, function(err: Error) {
|
||||
if (err) {
|
||||
return callback(err, result)
|
||||
}
|
||||
callback(err, encoding ? result.toString(encoding) : result)
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
createReadStream: function createReadStream(filepath: any, options: any) {
|
||||
setupManifest()
|
||||
const entry = manifest[filepath] || manifest[getKey(filepath)]
|
||||
if (!entry || !isString(filepath)) {
|
||||
return originalFsMethods.createReadStream.apply(fs, arguments)
|
||||
}
|
||||
const [offset, length] = entry
|
||||
const resourceOffset = resourceStart + offset
|
||||
const opts = fileOpts(options)
|
||||
|
||||
return fs.createReadStream(
|
||||
blobPath,
|
||||
Object.assign({}, opts, {
|
||||
start: resourceOffset,
|
||||
end: resourceOffset + length - 1
|
||||
})
|
||||
)
|
||||
},
|
||||
readFileSync: function readFileSync(filepath: any, options: any) {
|
||||
setupManifest()
|
||||
|
||||
const entry = manifest[filepath] || manifest[getKey(filepath)]
|
||||
if (!entry || !isString(filepath)) {
|
||||
return originalFsMethods.readFileSync.apply(fs, arguments)
|
||||
}
|
||||
const [offset, length] = entry
|
||||
const resourceOffset = resourceStart + offset
|
||||
const encoding = fileOpts(options).encoding
|
||||
const fd = fs.openSync(process.execPath, 'r')
|
||||
const result = Buffer.alloc(length)
|
||||
fs.readSync(fd, result, 0, length, resourceOffset)
|
||||
fs.closeSync(fd)
|
||||
return encoding ? result.toString(encoding) : result
|
||||
},
|
||||
statSync: function statSync(filepath: string | Buffer) {
|
||||
const stat = isString(filepath) && ownStat(filepath)
|
||||
if (stat) {
|
||||
return stat
|
||||
}
|
||||
return originalFsMethods.statSync.apply(fs, arguments)
|
||||
},
|
||||
stat: function stat(filepath: string | Buffer, callback: any) {
|
||||
const stat = isString(filepath) && ownStat(filepath)
|
||||
if (stat) {
|
||||
process.nextTick(() => {
|
||||
callback(null, stat)
|
||||
})
|
||||
} else {
|
||||
return originalFsMethods.stat.apply(fs, arguments)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof fs.exists === 'function') {
|
||||
nfs.exists = function(filepath: string, cb: Function) {
|
||||
cb = cb || noop
|
||||
const exists = nfs.existsSync(filepath)
|
||||
process.nextTick(() => cb(exists))
|
||||
}
|
||||
}
|
||||
|
||||
const patches = (process as any).nexe.patches || {}
|
||||
delete (process as any).nexe
|
||||
patches.internalModuleReadFile = function(this: any, original: any, ...args: any[]) {
|
||||
const [filepath] = args
|
||||
setupManifest()
|
||||
if (manifest[filepath]) {
|
||||
log('read (hit) ' + filepath)
|
||||
return nfs.readFileSync(filepath, 'utf-8')
|
||||
}
|
||||
log('read (miss) ' + filepath)
|
||||
return original.call(this, ...args)
|
||||
}
|
||||
patches.internalModuleStat = function(this: any, original: any, ...args: any[]) {
|
||||
setupManifest()
|
||||
const [filepath] = args
|
||||
if (manifest[filepath]) {
|
||||
log('stat (hit) ' + filepath + ' ' + 0)
|
||||
return 0
|
||||
}
|
||||
if (directories[filepath]) {
|
||||
log('stat dir (hit) ' + filepath + ' ' + 1)
|
||||
return 1
|
||||
}
|
||||
const res = original.call(this, ...args)
|
||||
if (res === 0) {
|
||||
log('stat (miss) ' + filepath + ' ' + res)
|
||||
} else if (res === 1) {
|
||||
log('stat dir (miss) ' + filepath + ' ' + res)
|
||||
} else {
|
||||
log('stat (fail) ' + filepath + ' ' + res)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
if (typeof fs.exists === 'function') {
|
||||
nfs.exists = function(filepath: string, cb: Function) {
|
||||
cb = cb || noop
|
||||
const exists = nfs.existsSync(filepath)
|
||||
if (!exists) {
|
||||
return originalFsMethods.exists(filepath, cb)
|
||||
}
|
||||
process.nextTick(() => cb(exists))
|
||||
}
|
||||
}
|
||||
Object.assign(fs, nfs)
|
||||
return true
|
||||
}
|
||||
|
||||
export { shimFs, restoreFs }
|
||||
Reference in New Issue
Block a user