Skip to content

Commit

Permalink
support Buffer for Node v5+
Browse files Browse the repository at this point in the history
  • Loading branch information
samthor committed Apr 28, 2020
1 parent 13b4e90 commit 320a34d
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 24 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ const buffer = new TextEncoder().encode('Turn me into UTF-8!');
// buffer is now a Uint8Array of [84, 117, 114, 110, ...]
```

In Node v5.1 and above, this polyfill uses `Buffer` to implement `TextDecoder`.

# Release

Compile code with [Closure Compiler](https://closure-compiler.appspot.com/home).
Expand Down
3 changes: 2 additions & 1 deletion compile.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#!/usr/bin/env sh

google-closure-compiler \
google-closure-compiler \
--compilation_level ADVANCED \
--js_output_file text.min.js \
--language_out ECMASCRIPT5 \
--warning_level VERBOSE \
--create_source_map %outname%.map \
--externs externs.js \
text.js
20 changes: 20 additions & 0 deletions externs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

/**
* @constructor
*/
var Buffer;

/**
* @param {!ArrayBuffer} raw
* @param {number} byteOffset
* @param {number} byteLength
* @return {!Buffer}
*/
Buffer.from = function(raw, byteOffset, byteLength) {};

/**
* @this {*}
* @param {string} encoding
* @return {string}
*/
Buffer.prototype.toString = function(encoding) {};
82 changes: 66 additions & 16 deletions text.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ FastTextEncoder.prototype['encode'] = function(string, options={stream: false})
target[at++] = value; // ASCII
continue;
} else if ((value & 0xfffff800) === 0) { // 2-byte
target[at++] = ((value >>> 6) & 0x1f) | 0xc0;
target[at++] = ((value >>> 6) & 0x1f) | 0xc0;
} else if ((value & 0xffff0000) === 0) { // 3-byte
target[at++] = ((value >>> 12) & 0x0f) | 0xe0;
target[at++] = ((value >>> 6) & 0x3f) | 0x80;
Expand Down Expand Up @@ -131,32 +131,46 @@ Object.defineProperty(FastTextDecoder.prototype, 'fatal', {value: false});
Object.defineProperty(FastTextDecoder.prototype, 'ignoreBOM', {value: false});

/**
* @param {(!ArrayBuffer|!ArrayBufferView)} buffer
* @param {{stream: boolean}=} options
* @param {!Uint8Array} bytes
* @return {string}
*/
FastTextDecoder.prototype['decode'] = function(buffer, options={stream: false}) {
if (options['stream']) {
throw new Error(`Failed to decode: the 'stream' option is unsupported.`);
function decodeBuffer(bytes) {
return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString('utf-8');
}

/**
* @param {!Uint8Array} bytes
* @return {string}
*/
function decodeSyncXHR(bytes) {
const b = new Blob([bytes]);
const u = URL.createObjectURL(b);

try {
const x = new XMLHttpRequest();
x.open('GET', u, false);
x.send(null);
return x.responseText;
} catch (e) {
return decodeFallback(bytes);
} finally {
URL.revokeObjectURL(u);
}
}

// Accept Uint8Array instances as-is.
let bytes = buffer;
/**
* @param {!Uint8Array} bytes
* @return {string}
*/
function decodeFallback(bytes) {
let inputIndex = 0;

// Look for ArrayBufferView, which isn't a real type, but basically represents
// all the valid TypedArray types plus DataView. They all have ".buffer" as
// an instance of ArrayBuffer.
if (!(bytes instanceof Uint8Array) && bytes.buffer instanceof ArrayBuffer) {
bytes = new Uint8Array(buffer.buffer);
}

// Create a working buffer for UTF-16 code points, but don't generate one
// which is too large for small input sizes. UTF-8 to UCS-16 conversion is
// going to be at most 1:1, if all code points are ASCII. The other extreme
// is 4-byte UTF-8, which results in two UCS-16 points, but this is still 50%
// fewer entries in the output.
const pendingSize = Math.min(256 * 256, buffer.length + 1);
const pendingSize = Math.min(256 * 256, bytes.length + 1);
const pending = new Uint16Array(pendingSize);
const chunks = [];
let pendingIndex = 0;
Expand Down Expand Up @@ -217,6 +231,42 @@ FastTextDecoder.prototype['decode'] = function(buffer, options={stream: false})
}
}

// Decoding a string is pretty slow, but use alternative options where possible.
let decodeImpl = decodeFallback;
if (typeof Buffer === 'function' && Buffer.from) {
// Buffer.from was added in Node v5.10.0 (2015-11-17).
decodeImpl = decodeBuffer;
} else if (typeof Blob === 'function' && typeof URL === 'function' && typeof URL.createObjectURL === 'function') {
// Blob and URL.createObjectURL are available from IE10, Safari 6, Chrome 19
// (all released in 2012), Firefox 19 (2013), ...

// TODO(samthor): I should probably check that this hack works in IE10 before shipping it.
// decodeImpl = decodeXHR;
}

/**
* @param {(!ArrayBuffer|!ArrayBufferView)} buffer
* @param {{stream: boolean}=} options
* @return {string}
*/
FastTextDecoder.prototype['decode'] = function(buffer, options={stream: false}) {
if (options['stream']) {
throw new Error(`Failed to decode: the 'stream' option is unsupported.`);
}

// Accept Uint8Array instances as-is.
let bytes = buffer;

// Look for ArrayBufferView, which isn't a real type, but basically represents
// all the valid TypedArray types plus DataView. They all have ".buffer" as
// an instance of ArrayBuffer.
if (!(bytes instanceof Uint8Array) && bytes.buffer instanceof ArrayBuffer) {
bytes = new Uint8Array(buffer.buffer);
}

return decodeImpl(/** @type {!Uint8Array} */ (bytes));
}

scope['TextEncoder'] = FastTextEncoder;
scope['TextDecoder'] = FastTextDecoder;

Expand Down
10 changes: 5 additions & 5 deletions text.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 320a34d

Please sign in to comment.