mirror of https://github.com/webpack/webpack.git
				
				
				
			
		
			
	
	
		
			209 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
		
		
			
		
	
	
			209 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
|  | /* | ||
|  | 	MIT License http://www.opensource.org/licenses/mit-license.php
 | ||
|  | */ | ||
|  | 
 | ||
|  | "use strict"; | ||
|  | 
 | ||
|  | const fs = require("fs"); | ||
|  | const mkdirp = require("mkdirp"); | ||
|  | const path = require("path"); | ||
|  | const SerializerMiddleware = require("./SerializerMiddleware"); | ||
|  | 
 | ||
|  | class Section { | ||
|  | 	constructor(items) { | ||
|  | 		this.items = items; | ||
|  | 		this.parts = undefined; | ||
|  | 		this.length = NaN; | ||
|  | 		this.offset = NaN; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	resolve() { | ||
|  | 		let hasPromise = false; | ||
|  | 		let lastPart = undefined; | ||
|  | 		const parts = []; | ||
|  | 		let length = 0; | ||
|  | 		for (const item of this.items) { | ||
|  | 			if (typeof item === "function") { | ||
|  | 				const r = item(); | ||
|  | 				if (r instanceof Promise) { | ||
|  | 					parts.push(r.then(items => new Section(items).resolve())); | ||
|  | 					hasPromise = true; | ||
|  | 				} else { | ||
|  | 					parts.push(new Section(r).resolve()); | ||
|  | 				} | ||
|  | 				length += 12; // 0, offset, size
 | ||
|  | 				lastPart = undefined; | ||
|  | 			} else if (lastPart) { | ||
|  | 				lastPart.push(item); | ||
|  | 				length += item.length; | ||
|  | 			} else { | ||
|  | 				length += 4; // size
 | ||
|  | 				length += item.length; | ||
|  | 				lastPart = [item]; | ||
|  | 				parts.push(lastPart); | ||
|  | 			} | ||
|  | 		} | ||
|  | 		this.length = length; | ||
|  | 		if (hasPromise) { | ||
|  | 			return Promise.all(parts).then(parts => { | ||
|  | 				this.parts = parts; | ||
|  | 				return this; | ||
|  | 			}); | ||
|  | 		} else { | ||
|  | 			this.parts = parts; | ||
|  | 			return this; | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	getSections() { | ||
|  | 		return this.parts.filter(p => p instanceof Section); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	emit(out) { | ||
|  | 		for (const part of this.parts) { | ||
|  | 			if (part instanceof Section) { | ||
|  | 				const pointerBuf = Buffer.alloc(12); | ||
|  | 				pointerBuf.writeUInt32LE(0, 0); | ||
|  | 				pointerBuf.writeUInt32LE(part.offset, 4); | ||
|  | 				pointerBuf.writeUInt32LE(part.length, 8); | ||
|  | 				out.push(pointerBuf); | ||
|  | 			} else { | ||
|  | 				const sizeBuf = Buffer.alloc(4); | ||
|  | 				out.push(sizeBuf); | ||
|  | 				let len = 0; | ||
|  | 				for (const buf of part) { | ||
|  | 					len += buf.length; | ||
|  | 					out.push(buf); | ||
|  | 				} | ||
|  | 				sizeBuf.writeUInt32LE(len, 0); | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | const createPointer = (filename, offset, size) => { | ||
|  | 	return () => { | ||
|  | 		return new Promise((resolve, reject) => { | ||
|  | 			// TODO handle concurrent access to file
 | ||
|  | 			fs.open(filename, "r", (err, file) => { | ||
|  | 				if (err) return reject(err); | ||
|  | 
 | ||
|  | 				readSection(filename, file, offset, size, (readErr, parts) => { | ||
|  | 					fs.close(file, err => { | ||
|  | 						if (err) return reject(err); | ||
|  | 						if (readErr) return reject(readErr); | ||
|  | 
 | ||
|  | 						resolve(parts); | ||
|  | 					}); | ||
|  | 				}); | ||
|  | 			}); | ||
|  | 		}); | ||
|  | 	}; | ||
|  | }; | ||
|  | 
 | ||
|  | const readSection = (filename, file, offset, size, callback) => { | ||
|  | 	const buffer = Buffer.alloc(size); | ||
|  | 	fs.read(file, buffer, 0, size, offset, err => { | ||
|  | 		if (err) return callback(err); | ||
|  | 
 | ||
|  | 		const result = []; | ||
|  | 		let pos = 0; | ||
|  | 		while (pos < buffer.length) { | ||
|  | 			const len = buffer.readUInt32LE(pos); | ||
|  | 			pos += 4; | ||
|  | 			if (len === 0) { | ||
|  | 				const pOffset = buffer.readUInt32LE(pos); | ||
|  | 				pos += 4; | ||
|  | 				const pSize = buffer.readUInt32LE(pos); | ||
|  | 				pos += 4; | ||
|  | 				result.push(createPointer(filename, pOffset, pSize)); | ||
|  | 			} else { | ||
|  | 				const buf = buffer.slice(pos, pos + len); | ||
|  | 				pos += len; | ||
|  | 				result.push(buf); | ||
|  | 			} | ||
|  | 		} | ||
|  | 		callback(null, result); | ||
|  | 	}); | ||
|  | }; | ||
|  | 
 | ||
|  | class FileMiddleware extends SerializerMiddleware { | ||
|  | 	/** | ||
|  | 	 * @param {any[]} data data items | ||
|  | 	 * @param {TODO} context TODO | ||
|  | 	 * @returns {any[]|Promise<any[]>} serialized data | ||
|  | 	 */ | ||
|  | 	serialize(data, { filename }) { | ||
|  | 		const root = new Section(data); | ||
|  | 
 | ||
|  | 		const r = root.resolve(); | ||
|  | 
 | ||
|  | 		return Promise.resolve(r).then(() => { | ||
|  | 			// calc positions in file
 | ||
|  | 			let currentOffset = 4; | ||
|  | 			const processOffsets = section => { | ||
|  | 				section.offset = currentOffset; | ||
|  | 				currentOffset += section.length; | ||
|  | 				for (const child of section.getSections()) { | ||
|  | 					processOffsets(child); | ||
|  | 				} | ||
|  | 			}; | ||
|  | 			processOffsets(root); | ||
|  | 
 | ||
|  | 			// get buffers to write
 | ||
|  | 			const sizeBuf = Buffer.alloc(4); | ||
|  | 			sizeBuf.writeUInt32LE(root.length, 0); | ||
|  | 			const buffers = [sizeBuf]; | ||
|  | 			const emit = (section, out) => { | ||
|  | 				section.emit(out); | ||
|  | 				for (const child of section.getSections()) { | ||
|  | 					emit(child, out); | ||
|  | 				} | ||
|  | 			}; | ||
|  | 			emit(root, buffers); | ||
|  | 
 | ||
|  | 			// write to file
 | ||
|  | 			return new Promise((resolve, reject) => { | ||
|  | 				mkdirp(path.dirname(filename), err => { | ||
|  | 					if (err) return reject(err); | ||
|  | 					fs.writeFile(filename, Buffer.concat(buffers), err => { | ||
|  | 						if (err) return reject(err); | ||
|  | 						resolve(); | ||
|  | 					}); | ||
|  | 				}); | ||
|  | 			}); | ||
|  | 		}); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 	 * @param {any[]} data data items | ||
|  | 	 * @param {TODO} context TODO | ||
|  | 	 * @returns {any[]|Promise<any[]>} deserialized data | ||
|  | 	 */ | ||
|  | 	deserialize(data, { filename }) { | ||
|  | 		return new Promise((resolve, reject) => { | ||
|  | 			fs.open(filename, "r", (err, file) => { | ||
|  | 				if (err) return reject(err); | ||
|  | 
 | ||
|  | 				const sizeBuf = Buffer.alloc(4); | ||
|  | 				fs.read(file, sizeBuf, 0, 4, 0, err => { | ||
|  | 					if (err) return reject(err); | ||
|  | 
 | ||
|  | 					const rootSize = sizeBuf.readUInt32LE(0); | ||
|  | 
 | ||
|  | 					readSection(filename, file, 4, rootSize, (readErr, parts) => { | ||
|  | 						fs.close(file, err => { | ||
|  | 							if (err) return reject(err); | ||
|  | 							if (readErr) return reject(readErr); | ||
|  | 
 | ||
|  | 							resolve(parts); | ||
|  | 						}); | ||
|  | 					}); | ||
|  | 				}); | ||
|  | 			}); | ||
|  | 		}); | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | module.exports = FileMiddleware; |