mirror of https://github.com/webpack/webpack.git
342 lines
8.7 KiB
JavaScript
342 lines
8.7 KiB
JavaScript
/*
|
|
MIT License http://www.opensource.org/licenses/mit-license.php
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
const SerializerMiddleware = require("./SerializerMiddleware");
|
|
|
|
/*
|
|
Format:
|
|
|
|
File -> Section*
|
|
|
|
Section -> NullsSection |
|
|
F64NumbersSection |
|
|
I32NumbersSection |
|
|
I8NumbersSection |
|
|
ShortStringSection |
|
|
StringSection |
|
|
BufferSection |
|
|
BooleanSection |
|
|
NopSection
|
|
|
|
|
|
|
|
NullsSection -> NullsSectionHeaderByte
|
|
F64NumbersSection -> F64NumbersSectionHeaderByte f64*
|
|
I32NumbersSection -> I32NumbersSectionHeaderByte i32*
|
|
I8NumbersSection -> I8NumbersSectionHeaderByte i8*
|
|
ShortStringSection -> ShortStringSectionHeaderByte utf8-byte*
|
|
StringSection -> StringSectionHeaderByte i32:length utf8-byte*
|
|
BufferSection -> BufferSectionHeaderByte i32:length byte*
|
|
BooleanSection -> TrueHeaderByte | FalseHeaderByte
|
|
NopSection --> NopSectionHeaderByte
|
|
|
|
ShortStringSectionHeaderByte -> 0b1nnn_nnnn (n:length)
|
|
|
|
F64NumbersSectionHeaderByte -> 0b001n_nnnn (n:length)
|
|
I32NumbersSectionHeaderByte -> 0b010n_nnnn (n:length)
|
|
I8NumbersSectionHeaderByte -> 0b011n_nnnn (n:length)
|
|
|
|
NullsSectionHeaderByte -> 0b0001_nnnn (n:length)
|
|
|
|
StringSectionHeaderByte -> 0b0000_1110
|
|
BufferSectionHeaderByte -> 0b0000_1111
|
|
NopSectionHeaderByte -> 0b0000_1011
|
|
FalseHeaderByte -> 0b0000_1100
|
|
TrueHeaderByte -> 0b0000_1101
|
|
|
|
RawNumber -> n (n <= 10)
|
|
|
|
*/
|
|
|
|
const NOP_HEADER = 0x0b;
|
|
const TRUE_HEADER = 0x0c;
|
|
const FALSE_HEADER = 0x0d;
|
|
const STRING_HEADER = 0x0e;
|
|
const BUFFER_HEADER = 0x0f;
|
|
const NULLS_HEADER_MASK = 0xf0;
|
|
const NULLS_HEADER = 0x10;
|
|
const NUMBERS_HEADER_MASK = 0xe0;
|
|
const I8_HEADER = 0x60;
|
|
const I32_HEADER = 0x40;
|
|
const F64_HEADER = 0x20;
|
|
const SHORT_STRING_HEADER = 0x80;
|
|
|
|
const identifyNumber = n => {
|
|
if (n === (n | 0)) {
|
|
if (n <= 127 && n >= -128) return 0;
|
|
if (n <= 2147483647 && n >= -2147483648) return 1;
|
|
}
|
|
return 2;
|
|
};
|
|
|
|
class BinaryMiddleware extends SerializerMiddleware {
|
|
_handleFunctionSerialization(fn, context) {
|
|
return () => {
|
|
const r = fn();
|
|
if (r instanceof Promise)
|
|
return r.then(data => this.serialize(data, context));
|
|
return this.serialize(r, context);
|
|
};
|
|
}
|
|
|
|
_handleFunctionDeserialization(fn, context) {
|
|
return () => {
|
|
const r = fn();
|
|
if (r instanceof Promise)
|
|
return r.then(data => this.deserialize(data, context));
|
|
return this.deserialize(r, context);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param {any[]} data data items
|
|
* @param {TODO} context TODO
|
|
* @returns {any[]|Promise<any[]>} serialized data
|
|
*/
|
|
serialize(data, context) {
|
|
/** @type {Buffer} */
|
|
let currentBuffer = null;
|
|
let currentPosition = 0;
|
|
const buffers = [];
|
|
const allocate = (bytesNeeded, exact = false) => {
|
|
if (currentBuffer !== null) {
|
|
if (currentBuffer.length - currentPosition >= bytesNeeded) return;
|
|
flush();
|
|
}
|
|
currentBuffer = Buffer.alloc(
|
|
exact ? bytesNeeded : Math.max(bytesNeeded, 1024)
|
|
);
|
|
};
|
|
const flush = () => {
|
|
if (currentBuffer !== null) {
|
|
buffers.push(currentBuffer.slice(0, currentPosition));
|
|
currentBuffer = null;
|
|
currentPosition = 0;
|
|
}
|
|
};
|
|
const writeU8 = byte => {
|
|
currentBuffer.writeUInt8(byte, currentPosition++);
|
|
};
|
|
const writeU32 = ui32 => {
|
|
currentBuffer.writeUInt32LE(ui32, currentPosition);
|
|
currentPosition += 4;
|
|
};
|
|
for (let i = 0; i < data.length; i++) {
|
|
const thing = data[i];
|
|
switch (typeof thing) {
|
|
case "function": {
|
|
flush();
|
|
buffers.push(this._handleFunctionSerialization(thing, context));
|
|
break;
|
|
}
|
|
case "string": {
|
|
const len = Buffer.byteLength(thing);
|
|
if (len > 128) {
|
|
allocate(len + 5);
|
|
writeU8(STRING_HEADER);
|
|
writeU32(len);
|
|
} else {
|
|
allocate(len + 1);
|
|
writeU8(SHORT_STRING_HEADER | len);
|
|
}
|
|
currentBuffer.write(thing, currentPosition);
|
|
currentPosition += len;
|
|
break;
|
|
}
|
|
case "number": {
|
|
const type = identifyNumber(thing);
|
|
if (type === 0 && thing >= 0 && thing <= 10) {
|
|
// shortcut for very small numbers
|
|
allocate(1);
|
|
writeU8(thing);
|
|
break;
|
|
}
|
|
let n;
|
|
for (n = 1; n < 32 && i + n < data.length; n++) {
|
|
const item = data[i + n];
|
|
if (typeof item !== "number") break;
|
|
if (identifyNumber(item) !== type) break;
|
|
}
|
|
switch (type) {
|
|
case 0:
|
|
allocate(1 + n);
|
|
writeU8(I8_HEADER | (n - 1));
|
|
while (n > 0) {
|
|
currentBuffer.writeInt8(data[i], currentPosition);
|
|
currentPosition++;
|
|
n--;
|
|
i++;
|
|
}
|
|
break;
|
|
case 1:
|
|
allocate(1 + 4 * n);
|
|
writeU8(I32_HEADER | (n - 1));
|
|
while (n > 0) {
|
|
currentBuffer.writeInt32LE(data[i], currentPosition);
|
|
currentPosition += 4;
|
|
n--;
|
|
i++;
|
|
}
|
|
break;
|
|
case 2:
|
|
allocate(1 + 8 * n);
|
|
writeU8(F64_HEADER | (n - 1));
|
|
while (n > 0) {
|
|
currentBuffer.writeDoubleLE(data[i], currentPosition);
|
|
currentPosition += 8;
|
|
n--;
|
|
i++;
|
|
}
|
|
break;
|
|
}
|
|
i--;
|
|
break;
|
|
}
|
|
case "boolean":
|
|
allocate(1);
|
|
writeU8(thing === true ? TRUE_HEADER : FALSE_HEADER);
|
|
break;
|
|
case "object": {
|
|
if (thing === null) {
|
|
let n;
|
|
for (n = 1; n < 16 && i + n < data.length; n++) {
|
|
const item = data[i + n];
|
|
if (item !== null) break;
|
|
}
|
|
allocate(1);
|
|
writeU8(NULLS_HEADER | (n - 1));
|
|
i += n - 1;
|
|
} else if (Buffer.isBuffer(thing)) {
|
|
allocate(5, true);
|
|
writeU8(BUFFER_HEADER);
|
|
writeU32(thing.length);
|
|
flush();
|
|
buffers.push(thing);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
flush();
|
|
return buffers;
|
|
}
|
|
|
|
/**
|
|
* @param {any[]} data data items
|
|
* @param {TODO} context TODO
|
|
* @returns {any[]|Promise<any[]>} deserialized data
|
|
*/
|
|
deserialize(data, context) {
|
|
let currentDataItem = 0;
|
|
let currentBuffer = data[0];
|
|
let currentPosition = 0;
|
|
const checkOverflow = () => {
|
|
if (currentPosition >= currentBuffer.length) {
|
|
currentPosition = 0;
|
|
currentDataItem++;
|
|
currentBuffer =
|
|
currentDataItem < data.length ? data[currentDataItem] : null;
|
|
}
|
|
};
|
|
const read = n => {
|
|
if (currentBuffer === null) throw new Error("Unexpected end of stream");
|
|
if (!Buffer.isBuffer(currentBuffer))
|
|
throw new Error("Unexpected lazy element in stream");
|
|
const rem = currentBuffer.length - currentPosition;
|
|
if (rem < n) {
|
|
return Buffer.concat([read(rem), read(n - rem)]);
|
|
}
|
|
const res = currentBuffer.slice(currentPosition, currentPosition + n);
|
|
currentPosition += n;
|
|
checkOverflow();
|
|
return res;
|
|
};
|
|
const readU8 = () => {
|
|
if (currentBuffer === null) throw new Error("Unexpected end of stream");
|
|
if (!Buffer.isBuffer(currentBuffer))
|
|
throw new Error("Unexpected lazy element in stream");
|
|
const byte = currentBuffer.readUInt8(currentPosition);
|
|
currentPosition++;
|
|
checkOverflow();
|
|
return byte;
|
|
};
|
|
const readU32 = () => {
|
|
return read(4).readUInt32LE(0);
|
|
};
|
|
const result = [];
|
|
while (currentBuffer !== null) {
|
|
if (typeof currentBuffer === "function") {
|
|
result.push(
|
|
this._handleFunctionDeserialization(currentBuffer, context)
|
|
);
|
|
currentDataItem++;
|
|
currentBuffer =
|
|
currentDataItem < data.length ? data[currentDataItem] : null;
|
|
continue;
|
|
}
|
|
const header = readU8();
|
|
switch (header) {
|
|
case NOP_HEADER:
|
|
break;
|
|
case BUFFER_HEADER: {
|
|
const len = readU32();
|
|
result.push(read(len));
|
|
break;
|
|
}
|
|
case TRUE_HEADER:
|
|
result.push(true);
|
|
break;
|
|
case FALSE_HEADER:
|
|
result.push(false);
|
|
break;
|
|
case STRING_HEADER: {
|
|
const len = readU32();
|
|
const buf = read(len);
|
|
result.push(buf.toString());
|
|
break;
|
|
}
|
|
default:
|
|
if (header <= 10) {
|
|
result.push(header);
|
|
} else if ((header & SHORT_STRING_HEADER) === SHORT_STRING_HEADER) {
|
|
const len = header & 0x7f;
|
|
const buf = read(len);
|
|
result.push(buf.toString());
|
|
} else if ((header & NUMBERS_HEADER_MASK) === F64_HEADER) {
|
|
const len = header & 0x1f;
|
|
const buf = read(8 * len + 8);
|
|
for (let i = 0; i <= len; i++) {
|
|
result.push(buf.readDoubleLE(i * 8));
|
|
}
|
|
} else if ((header & NUMBERS_HEADER_MASK) === I32_HEADER) {
|
|
const len = header & 0x1f;
|
|
const buf = read(4 * len + 4);
|
|
for (let i = 0; i <= len; i++) {
|
|
result.push(buf.readInt32LE(i * 4));
|
|
}
|
|
} else if ((header & NUMBERS_HEADER_MASK) === I8_HEADER) {
|
|
const len = header & 0x1f;
|
|
const buf = read(len + 1);
|
|
for (let i = 0; i <= len; i++) {
|
|
result.push(buf.readInt8(i));
|
|
}
|
|
} else if ((header & NULLS_HEADER_MASK) === NULLS_HEADER) {
|
|
const len = header & 0x0f;
|
|
for (let i = 0; i <= len; i++) {
|
|
result.push(null);
|
|
}
|
|
} else {
|
|
throw new Error(`Unexpected header byte 0x${header.toString(16)}`);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
module.exports = BinaryMiddleware;
|