diff --git a/.changeset/brown-carrots-shave.md b/.changeset/brown-carrots-shave.md new file mode 100644 index 0000000..9f15b79 --- /dev/null +++ b/.changeset/brown-carrots-shave.md @@ -0,0 +1,5 @@ +--- +"devalue": minor +--- + +feat: use native alternatives to encode/decode base64 diff --git a/.changeset/happy-mugs-happen.md b/.changeset/happy-mugs-happen.md new file mode 100644 index 0000000..c6bc805 --- /dev/null +++ b/.changeset/happy-mugs-happen.md @@ -0,0 +1,5 @@ +--- +"devalue": minor +--- + +feat: simplify TypedArray slices diff --git a/.gitignore b/.gitignore index 2c5a5ee..7d46013 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .DS_Store /node_modules -/types \ No newline at end of file +/types +.results diff --git a/benchmarking/benchmarks/typed-array.js b/benchmarking/benchmarks/typed-array.js new file mode 100644 index 0000000..84f96a0 --- /dev/null +++ b/benchmarking/benchmarks/typed-array.js @@ -0,0 +1,121 @@ +import { fastest_test } from '../utils.js'; + +import { parse, stringify } from '../../index.js'; + +const value_small = new Uint8Array(Array.from({ length: 100 }, (_, i) => i)); +const value_medium = new Uint8Array(Array.from({ length: 10 * 1024 }, (_, i) => i % 256)); +const value_large = new Uint8Array(Array.from({ length: 1024 * 1024 }, (_, i) => i % 256)); + +const string_small = stringify(value_small); +const string_medium = stringify(value_medium); +const string_large = stringify(value_large); + +export default [ + { + label: `stringify: small`, + async fn() { + const value = value_small; + + // warm up + for (let i = 0; i < 10_000; i++) { + stringify(value); + } + + return await fastest_test(3, () => { + for (let i = 0; i < 500_000; i++) { + stringify(value); + } + }); + }, + }, + + { + label: `stringify: medium`, + async fn() { + const value = value_medium; + + // warm up + for (let i = 0; i < 1_000; i++) { + stringify(value); + } + + return await fastest_test(3, () => { + for (let i = 0; i < 5_000; i++) { + stringify(value); + } + }); + }, + }, + + { + label: `stringify: large`, + async fn() { + const value = value_large; + + // warm up + for (let i = 0; i < 10; i++) { + stringify(value); + } + + return await fastest_test(3, () => { + for (let i = 0; i < 50; i++) { + stringify(value); + } + }); + }, + }, + + { + label: `parse: small`, + async fn() { + const string = string_small; + + // warm up + for (let i = 0; i < 10_000; i++) { + parse(string); + } + + return await fastest_test(3, () => { + for (let i = 0; i < 500_000; i++) { + parse(string); + } + }); + }, + }, + + { + label: `parse: medium`, + async fn() { + const string = string_medium; + + // warm up + for (let i = 0; i < 1_000; i++) { + parse(string); + } + + return await fastest_test(3, () => { + for (let i = 0; i < 5_000; i++) { + parse(string); + } + }); + }, + }, + + { + label: `parse: large`, + async fn() { + const string = string_large; + + // warm up + for (let i = 0; i < 10; i++) { + parse(string); + } + + return await fastest_test(3, () => { + for (let i = 0; i < 50; i++) { + parse(string); + } + }); + }, + }, +]; diff --git a/benchmarking/compare/index.js b/benchmarking/compare/index.js new file mode 100644 index 0000000..a190e00 --- /dev/null +++ b/benchmarking/compare/index.js @@ -0,0 +1,111 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { execSync, fork } from 'node:child_process'; + +/** @type {(command: string) => string} */ +const exec = (command) => execSync(command).toString().trim(); + +const is_jj = execSync('git for-each-ref --count=1 refs/jj/').length > 0; + +const current_ref = exec( + is_jj + ? 'jj show --no-patch --template change_id' + : 'git symbolic-ref --short -q HEAD || git rev-parse --short HEAD', +); + +/** @type {(branch: string) => void} */ +const checkout = is_jj + ? (branch) => exec(`jj edit ${branch}`) + : (branch) => exec(`git checkout ${branch}`); + +const runner = path.resolve(import.meta.filename, '../runner.js'); +const outdir = path.resolve(import.meta.filename, '../.results'); + +fs.rmSync(outdir, { recursive: true, force: true }); +fs.mkdirSync(outdir); + +/** @type {string[]} */ +const branches = []; + +for (const arg of process.argv.slice(2)) { + if (arg.startsWith('--')) continue; + if (arg === import.meta.filename) continue; + + branches.push(arg); +} + +if (branches.length === 0) { + branches.push(current_ref); +} + +if (branches.length === 1) { + branches.push('main'); +} + +process.on('exit', () => checkout(current_ref)); + +for (const branch of branches) { + console.group(`Benchmarking ${branch}`); + + checkout(branch); + + await new Promise((fulfil, reject) => { + const child = fork(runner); + + child.on('message', (results) => { + fs.writeFileSync(`${outdir}/${branch}.json`, JSON.stringify(results, null, ' ')); + fulfil(undefined); + }); + + child.on('error', reject); + }); + + console.groupEnd(); +} + +const results = branches.map((branch) => { + return JSON.parse(fs.readFileSync(`${outdir}/${branch}.json`, 'utf-8')); +}); + +for (let i = 0; i < results[0].length; i += 1) { + console.group(`${results[0][i].benchmark}`); + + for (const metric of ['time', 'gc_time']) { + const times = results.map((result) => +result[i][metric]); + let min = Infinity; + let max = -Infinity; + let min_index = -1; + + for (let b = 0; b < times.length; b += 1) { + const time = times[b]; + + if (time < min) { + min = time; + min_index = b; + } + + if (time > max) { + max = time; + } + } + + if (min !== 0) { + console.group(`${metric}: fastest is ${char(min_index)} (${branches[min_index]})`); + times.forEach((time, b) => { + const SIZE = 20; + const n = Math.round(SIZE * (time / max)); + + console.log( + `${char(b)}: ${'◼'.repeat(n)}${' '.repeat(SIZE - n)} ${time.toFixed(2)}ms`, + ); + }); + console.groupEnd(); + } + } + + console.groupEnd(); +} + +function char(i) { + return String.fromCharCode(97 + i); +} diff --git a/benchmarking/compare/runner.js b/benchmarking/compare/runner.js new file mode 100644 index 0000000..aed998c --- /dev/null +++ b/benchmarking/compare/runner.js @@ -0,0 +1,13 @@ +import typedarray from '../benchmarks/typed-array.js'; + +const results = []; + +for (let i = 0; i < typedarray.length; i += 1) { + const benchmark = typedarray[i]; + + process.stderr.write(`Running ${i + 1}/${typedarray.length} ${benchmark.label} `); + results.push({ benchmark: benchmark.label, ...(await benchmark.fn()) }); + process.stderr.write('\x1b[2K\r'); +} + +process.send?.(results); diff --git a/benchmarking/run.js b/benchmarking/run.js new file mode 100644 index 0000000..e7143e8 --- /dev/null +++ b/benchmarking/run.js @@ -0,0 +1,78 @@ +import typed_array_benchmarks from './benchmarks/typed-array.js'; + +// e.g. `pnpm bench typedarray` to only run the typedarray benchmarks +const filters = process.argv.slice(2); + +/** @type {(b: { label: string }) => boolean} */ +const filter_fn = filters.length ? (b) => filters.some((f) => b.label.includes(f)) : (b) => true; + +const suites = [ + { + name: 'TypedArray benchmarks', + benchmarks: typed_array_benchmarks.filter(filter_fn), + }, +].filter((suite) => suite.benchmarks.length > 0); + +if (suites.length === 0) { + console.log('No benchmarks matched provided filters'); + process.exit(1); +} + +const COLUMN_WIDTHS = [40, 9, 9]; +const TOTAL_WIDTH = COLUMN_WIDTHS.reduce((a, b) => a + b); + +/** @type {(str: string, n: number) => string} */ +const pad_right = (str, n) => str + ' '.repeat(n - str.length); + +/** @type {(str: string, n: number) => string} */ +const pad_left = (str, n) => ' '.repeat(n - str.length) + str; + +let total_time = 0; +let total_gc_time = 0; + +try { + for (const { benchmarks, name } of suites) { + let suite_time = 0; + let suite_gc_time = 0; + + console.log(`\nRunning ${name}...\n`); + console.log( + pad_right('Benchmark', COLUMN_WIDTHS[0]) + + pad_left('Time', COLUMN_WIDTHS[1]) + + pad_left('GC time', COLUMN_WIDTHS[2]), + ); + console.log('='.repeat(TOTAL_WIDTH)); + + for (const benchmark of benchmarks) { + const results = await benchmark.fn(); + console.log( + pad_right(benchmark.label, COLUMN_WIDTHS[0]) + + pad_left(results.time.toFixed(2), COLUMN_WIDTHS[1]) + + pad_left(results.gc_time.toFixed(2), COLUMN_WIDTHS[2]), + ); + total_time += results.time; + total_gc_time += results.gc_time; + suite_time += results.time; + suite_gc_time += results.gc_time; + } + + console.log('='.repeat(TOTAL_WIDTH)); + console.log( + pad_right('suite', COLUMN_WIDTHS[0]) + + pad_left(suite_time.toFixed(2), COLUMN_WIDTHS[1]) + + pad_left(suite_gc_time.toFixed(2), COLUMN_WIDTHS[2]), + ); + console.log('='.repeat(TOTAL_WIDTH)); + } +} catch (e) { + console.error(e); + process.exit(1); +} + +console.log(''); + +console.log( + pad_right('total', COLUMN_WIDTHS[0]) + + pad_left(total_time.toFixed(2), COLUMN_WIDTHS[1]) + + pad_left(total_gc_time.toFixed(2), COLUMN_WIDTHS[2]), +); diff --git a/benchmarking/tsconfig.json b/benchmarking/tsconfig.json new file mode 100644 index 0000000..5f21aa9 --- /dev/null +++ b/benchmarking/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "noEmit": true, + "moduleResolution": "Bundler", + "target": "ESNext", + "module": "ESNext", + "verbatimModuleSyntax": true, + "isolatedModules": true, + "resolveJsonModule": true, + "sourceMap": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "allowJs": true, + "checkJs": true, + "types": ["node"] + }, + "include": ["./**/*.js"] +} diff --git a/benchmarking/utils.js b/benchmarking/utils.js new file mode 100644 index 0000000..9e80272 --- /dev/null +++ b/benchmarking/utils.js @@ -0,0 +1,43 @@ +import { performance, PerformanceObserver } from 'node:perf_hooks'; + +// Credit to https://github.com/milomg/js-reactivity-benchmark for the logic for timing + GC tracking. + +/** @type {(fn: () => void) => Promise<{ time: number, gc_time: number }>} */ +async function track(fn) { + %CollectGarbage(null); + + /** @type {PerformanceEntry[]} */ + const entries = []; + + const observer = new PerformanceObserver((list) => entries.push(...list.getEntries())); + observer.observe({ entryTypes: ['gc'] }); + + const start = performance.now(); + fn(); + const end = performance.now(); + + await new Promise((f) => setTimeout(f, 10)); + + const gc_time = entries + .filter((e) => e.startTime >= start && e.startTime < end) + .reduce((t, e) => e.duration + t, 0); + + observer.disconnect(); + + return { time: end - start, gc_time }; +} + +/** + * @param {number} times + * @param {() => void} fn + */ +export async function fastest_test(times, fn) { + /** @type {Array<{ time: number, gc_time: number }>} */ + const results = []; + + for (let i = 0; i < times; i++) { + results.push(await track(fn)); + } + + return results.reduce((a, b) => (a.time < b.time ? a : b)); +} diff --git a/package.json b/package.json index be4f24e..fd3b441 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "devDependencies": { "@changesets/cli": "^2.29.6", "@js-temporal/polyfill": "^0.5.1", + "@types/node": "^24.12.0", "dts-buddy": "^0.6.2", "publint": "^0.3.12", "typescript": "^5.9.2", @@ -30,7 +31,9 @@ "changeset:publish": "changeset publish", "build": "dts-buddy", "test": "uvu", - "prepublishOnly": "npm test && npm run build && publint" + "prepublishOnly": "npm test && npm run build && publint", + "bench": "node --allow-natives-syntax ./benchmarking/run.js", + "bench:compare": "node --allow-natives-syntax ./benchmarking/compare/index.js" }, "license": "MIT", "type": "module", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9c69c0d..d984431 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,10 +7,13 @@ settings: devDependencies: '@changesets/cli': specifier: ^2.29.6 - version: 2.29.6 + version: 2.29.6(@types/node@24.12.0) '@js-temporal/polyfill': specifier: ^0.5.1 version: 0.5.1 + '@types/node': + specifier: ^24.12.0 + version: 24.12.0 dts-buddy: specifier: ^0.6.2 version: 0.6.2(typescript@5.9.2) @@ -66,7 +69,7 @@ packages: '@changesets/types': 6.1.0 dev: true - /@changesets/cli@2.29.6: + /@changesets/cli@2.29.6(@types/node@24.12.0): resolution: {integrity: sha512-6qCcVsIG1KQLhpQ5zE8N0PckIx4+9QlHK3z6/lwKnw7Tir71Bjw8BeOZaxA/4Jt00pcgCnCSWZnyuZf5Il05QQ==} hasBin: true dependencies: @@ -84,7 +87,7 @@ packages: '@changesets/should-skip-package': 0.1.2 '@changesets/types': 6.1.0 '@changesets/write': 0.4.0 - '@inquirer/external-editor': 1.0.1 + '@inquirer/external-editor': 1.0.1(@types/node@24.12.0) '@manypkg/get-packages': 1.1.3 ansi-colors: 4.1.3 ci-info: 3.9.0 @@ -212,7 +215,7 @@ packages: prettier: 2.8.8 dev: true - /@inquirer/external-editor@1.0.1: + /@inquirer/external-editor@1.0.1(@types/node@24.12.0): resolution: {integrity: sha512-Oau4yL24d2B5IL4ma4UpbQigkVhzPDXLoqy1ggK4gnHg/stmkffJE4oOXHXF3uz0UEpywG68KcyXsyYpA1Re/Q==} engines: {node: '>=18'} peerDependencies: @@ -221,6 +224,7 @@ packages: '@types/node': optional: true dependencies: + '@types/node': 24.12.0 chardet: 2.1.0 iconv-lite: 0.6.3 dev: true @@ -312,6 +316,12 @@ packages: resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} dev: true + /@types/node@24.12.0: + resolution: {integrity: sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==} + dependencies: + undici-types: 7.16.0 + dev: true + /ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -333,9 +343,6 @@ packages: engines: {node: '>=8'} dev: true - /balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - /better-path-resolve@1.0.0: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} @@ -849,6 +856,10 @@ packages: hasBin: true dev: true + /undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + dev: true + /universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} diff --git a/src/base64.js b/src/base64.js index 23bd6b6..220fcac 100644 --- a/src/base64.js +++ b/src/base64.js @@ -1,110 +1,60 @@ -/** - * Base64 Encodes an arraybuffer - * @param {ArrayBuffer} arraybuffer - * @returns {string} - */ -export function encode64(arraybuffer) { - const dv = new DataView(arraybuffer); - let binaryString = ""; +/* Baseline 2025 runtimes */ - for (let i = 0; i < arraybuffer.byteLength; i++) { - binaryString += String.fromCharCode(dv.getUint8(i)); - } +/** @type {(array_buffer: ArrayBuffer) => string} */ +function encode_native(array_buffer) { + return new Uint8Array(array_buffer).toBase64(); +} - return binaryToAscii(binaryString); +/** @type {(base64: string) => ArrayBuffer} */ +function decode_native(base64) { + return Uint8Array.fromBase64(base64).buffer; } -/** - * Decodes a base64 string into an arraybuffer - * @param {string} string - * @returns {ArrayBuffer} - */ -export function decode64(string) { - const binaryString = asciiToBinary(string); - const arraybuffer = new ArrayBuffer(binaryString.length); - const dv = new DataView(arraybuffer); +/* Node-compatible runtimes */ - for (let i = 0; i < arraybuffer.byteLength; i++) { - dv.setUint8(i, binaryString.charCodeAt(i)); - } +/** @type {(array_buffer: ArrayBuffer) => string} */ +function encode_buffer(array_buffer) { + return Buffer.from(array_buffer).toString('base64'); +} - return arraybuffer; +/** @type {(base64: string) => ArrayBuffer} */ +function decode_buffer(base64) { + return Uint8Array.from(Buffer.from(base64, 'base64')).buffer; } -const KEY_STRING = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +/* Legacy runtimes */ -/** - * Substitute for atob since it's deprecated in node. - * Does not do any input validation. - * - * @see https://github.com/jsdom/abab/blob/master/lib/atob.js - * - * @param {string} data - * @returns {string} - */ -function asciiToBinary(data) { - if (data.length % 4 === 0) { - data = data.replace(/==?$/, ""); - } +/** @type {(array_buffer: ArrayBuffer) => string} */ +function encode_legacy(array_buffer) { + const array = new Uint8Array(array_buffer); + let binary = ''; - let output = ""; - let buffer = 0; - let accumulatedBits = 0; + // the maximum number of arguments to String.fromCharCode.apply + // should be around 0xFFFF in modern engines + const chunk_size = 0x8000; + for (let i = 0; i < array.length; i += chunk_size) { + const chunk = array.subarray(i, i + chunk_size); + binary += String.fromCharCode.apply(null, chunk); + } - for (let i = 0; i < data.length; i++) { - buffer <<= 6; - buffer |= KEY_STRING.indexOf(data[i]); - accumulatedBits += 6; - if (accumulatedBits === 24) { - output += String.fromCharCode((buffer & 0xff0000) >> 16); - output += String.fromCharCode((buffer & 0xff00) >> 8); - output += String.fromCharCode(buffer & 0xff); - buffer = accumulatedBits = 0; - } - } - if (accumulatedBits === 12) { - buffer >>= 4; - output += String.fromCharCode(buffer); - } else if (accumulatedBits === 18) { - buffer >>= 2; - output += String.fromCharCode((buffer & 0xff00) >> 8); - output += String.fromCharCode(buffer & 0xff); - } - return output; + return btoa(binary); } -/** - * Substitute for btoa since it's deprecated in node. - * Does not do any input validation. - * - * @see https://github.com/jsdom/abab/blob/master/lib/btoa.js - * - * @param {string} str - * @returns {string} - */ -function binaryToAscii(str) { - let out = ""; - for (let i = 0; i < str.length; i += 3) { - /** @type {[number, number, number, number]} */ - const groupsOfSix = [undefined, undefined, undefined, undefined]; - groupsOfSix[0] = str.charCodeAt(i) >> 2; - groupsOfSix[1] = (str.charCodeAt(i) & 0x03) << 4; - if (str.length > i + 1) { - groupsOfSix[1] |= str.charCodeAt(i + 1) >> 4; - groupsOfSix[2] = (str.charCodeAt(i + 1) & 0x0f) << 2; - } - if (str.length > i + 2) { - groupsOfSix[2] |= str.charCodeAt(i + 2) >> 6; - groupsOfSix[3] = str.charCodeAt(i + 2) & 0x3f; - } - for (let j = 0; j < groupsOfSix.length; j++) { - if (typeof groupsOfSix[j] === "undefined") { - out += "="; - } else { - out += KEY_STRING[groupsOfSix[j]]; - } - } - } - return out; +/** @type {(base64: string) => ArrayBuffer} */ +function decode_legacy(base64) { + const binary_string = atob(base64); + const len = binary_string.length; + const array = new Uint8Array(len); + + for (let i = 0; i < len; i++) { + array[i] = binary_string.charCodeAt(i); + } + + return array.buffer; } + +const native = typeof Uint8Array.fromBase64 === 'function'; +const buffer = typeof process === 'object' && process.versions?.node !== undefined; + +export const encode64 = native ? encode_native : buffer ? encode_buffer : encode_legacy; +export const decode64 = native ? decode_native : buffer ? decode_buffer : decode_legacy; diff --git a/src/parse.js b/src/parse.js index 25d667d..3c2cb0d 100644 --- a/src/parse.js +++ b/src/parse.js @@ -163,12 +163,10 @@ export function unflatten(parsed, revivers) { const TypedArrayConstructor = globalThis[type]; const buffer = hydrate(value[1]); - const typedArray = new TypedArrayConstructor(buffer); - hydrated[index] = - value[2] !== undefined - ? typedArray.subarray(value[2], value[3]) - : typedArray; + hydrated[index] = value[2] !== undefined + ? new TypedArrayConstructor(buffer, value[2], value[3]) + : new TypedArrayConstructor(buffer); break; } diff --git a/src/stringify.js b/src/stringify.js index 780644c..d2482ef 100644 --- a/src/stringify.js +++ b/src/stringify.js @@ -230,13 +230,10 @@ export function stringify(value, reducers) { const typedArray = thing; str = '["' + type + '",' + flatten(typedArray.buffer); - const a = thing.byteOffset; - const b = a + thing.byteLength; - // handle subarrays - if (a > 0 || b !== typedArray.buffer.byteLength) { - const m = +/(\d+)/.exec(type)[1] / 8; - str += `,${a / m},${b / m}`; + if (typedArray.byteLength !== typedArray.buffer.byteLength) { + // to be used with `new TypedArray(buffer, byteOffset, length)` + str += `,${typedArray.byteOffset},${typedArray.length}`; } str += ']'; diff --git a/src/uneval.js b/src/uneval.js index 7da1730..4f9bfcf 100644 --- a/src/uneval.js +++ b/src/uneval.js @@ -304,13 +304,11 @@ export function uneval(value, replacer) { str += `([${stringify(thing.buffer)}])`; } - const a = thing.byteOffset; - const b = a + thing.byteLength; - // handle subarrays - if (a > 0 || b !== thing.buffer.byteLength) { - const m = +/(\d+)/.exec(type)[1] / 8; - str += `.subarray(${a / m},${b / m})`; + if (thing.byteLength !== thing.buffer.byteLength) { + const start = thing.byteOffset / thing.BYTES_PER_ELEMENT; + const end = start + thing.length; + str += `.subarray(${start},${end})`; } return str; diff --git a/test/test.js b/test/index.test.js similarity index 99% rename from test/test.js rename to test/index.test.js index c523bc0..4e25091 100644 --- a/test/test.js +++ b/test/index.test.js @@ -240,7 +240,7 @@ const fixtures = { name: 'Sliced typed array', value: new Uint16Array([10, 20, 30, 40]).subarray(1, 3), js: 'new Uint16Array([10,20,30,40]).subarray(1,3)', - json: '[["Uint16Array",1,1,3],["ArrayBuffer","CgAUAB4AKAA="]]' + json: '[["Uint16Array",1,2,2],["ArrayBuffer","CgAUAB4AKAA="]]' }, { name: 'Temporal.Duration', diff --git a/tsconfig.json b/tsconfig.json index 20357cb..95b3da2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,8 @@ "noImplicitThis": true, "noEmitOnError": true, "lib": ["es6", "esnext", "dom"], - "target": "esnext" + "target": "esnext", + "types": ["node"] }, "module": "ES6", "include": ["index.js", "src/*.js"],