Skip to content

Commit e4385e9

Browse files
committed
size: split platform per-platform
1 parent da7c6a6 commit e4385e9

File tree

7 files changed

+176
-128
lines changed

7 files changed

+176
-128
lines changed

fallback/platform.browser.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { decodePartAddition as decodePart } from './platform.native.js'
2+
3+
export const nativeBuffer = null
4+
export const isHermes = false
5+
export const isDeno = false
6+
export const nativeEncoder = /* @__PURE__ */ (() => new TextEncoder())()
7+
export const nativeDecoder = /* @__PURE__ */ (() => new TextDecoder('utf-8', { ignoreBOM: true }))()
8+
export const nativeDecoderLatin1 = /* @__PURE__ */ (() =>
9+
new TextDecoder('latin1', { ignoreBOM: true }))()
10+
11+
// Block Firefox < 146 specifically from using native hex/base64, as it's very slow there
12+
// Refs: https://bugzilla.mozilla.org/show_bug.cgi?id=1994067 (and linked issues), fixed in 146
13+
// Before that, all versions of Firefox >= 133 are slow
14+
// TODO: this could be removed when < 146 usage diminishes (note ESR)
15+
// We do not worry about false-negatives here but worry about false-positives!
16+
function shouldSkipBuiltins() {
17+
const g = globalThis
18+
// First, attempt to exclude as many things as we can using trivial checks, just in case, and to not hit ua
19+
if (!g.window || g.chrome || !g.navigator) return false
20+
try {
21+
// This was fixed specifically in Firefox 146. Other browser engines get this right
22+
new WeakSet().add(Symbol()) // eslint-disable-line symbol-description
23+
return false
24+
} catch {
25+
// In catch and not after in case if something too smart optimizes out code in try. False-negative is acceptable in that case
26+
if (!('onmozfullscreenerror' in g)) return false // Firefox has it (might remove in the future, but we don't care)
27+
return /firefox/i.test(g.navigator.userAgent || '') // as simple as we can
28+
}
29+
30+
/* c8 ignore next */
31+
return false // eslint-disable-line no-unreachable
32+
}
33+
34+
export const skipWeb = /* @__PURE__ */ shouldSkipBuiltins()
35+
36+
export { isLE } from './platform.native.js'
37+
38+
export function decode2string(arr, start, end, m) {
39+
if (end - start > 30_000) {
40+
// Limit concatenation to avoid excessive GC
41+
// Thresholds checked on Hermes for toHex
42+
const concat = []
43+
for (let i = start; i < end; ) {
44+
const step = i + 500
45+
const iNext = step > end ? end : step
46+
concat.push(decodePart(arr, i, iNext, m))
47+
i = iNext
48+
}
49+
50+
const res = concat.join('')
51+
concat.length = 0
52+
return res
53+
}
54+
55+
return decodePart(arr, start, end, m)
56+
}

fallback/platform.js

Lines changed: 2 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,121 +1,2 @@
1-
const { Buffer } = globalThis
2-
const haveNativeBuffer = Buffer && !Buffer.TYPED_ARRAY_SUPPORT
3-
export const nativeBuffer = haveNativeBuffer ? Buffer : null
4-
export const isHermes = /* @__PURE__ */ (() => !!globalThis.HermesInternal)()
5-
export const isDeno = /* @__PURE__ */ (() => !!globalThis.Deno)()
6-
export const isLE = /* @__PURE__ */ (() => new Uint8Array(Uint16Array.of(258).buffer)[0] === 2)()
7-
8-
// We consider Node.js TextDecoder/TextEncoder native
9-
let isNative = (x) => x && (haveNativeBuffer || `${x}`.includes('[native code]'))
10-
if (!haveNativeBuffer && isNative(() => {})) isNative = () => false // e.g. XS, we don't want false positives
11-
12-
export const nativeEncoder = /* @__PURE__ */ (() =>
13-
isNative(globalThis.TextEncoder) ? new TextEncoder() : null)()
14-
export const nativeDecoder = /* @__PURE__ */ (() =>
15-
isNative(globalThis.TextDecoder) ? new TextDecoder('utf-8', { ignoreBOM: true }) : null)()
16-
17-
// Actually windows-1252, compatible with ascii and latin1 decoding
18-
// Beware that on non-latin1, i.e. on windows-1252, this is broken in ~all Node.js versions released
19-
// in 2025 due to a regression, so we call it Latin1 as it's usable only for that
20-
export const nativeDecoderLatin1 = /* @__PURE__ */ (() => {
21-
// Not all barebone engines with TextDecoder support something except utf-8, detect
22-
if (nativeDecoder) {
23-
try {
24-
return new TextDecoder('latin1', { ignoreBOM: true })
25-
} catch {}
26-
}
27-
28-
return null
29-
})()
30-
31-
// Block Firefox < 146 specifically from using native hex/base64, as it's very slow there
32-
// Refs: https://bugzilla.mozilla.org/show_bug.cgi?id=1994067 (and linked issues), fixed in 146
33-
// Before that, all versions of Firefox >= 133 are slow
34-
// TODO: this could be removed when < 146 usage diminishes (note ESR)
35-
// We do not worry about false-negatives here but worry about false-positives!
36-
function shouldSkipBuiltins() {
37-
const g = globalThis
38-
// First, attempt to exclude as many things as we can using trivial checks, just in case, and to not hit ua
39-
if (haveNativeBuffer || isHermes || !g.window || g.chrome || !g.navigator) return false
40-
try {
41-
// This was fixed specifically in Firefox 146. Other engines except Hermes (already returned) get this right
42-
new WeakSet().add(Symbol()) // eslint-disable-line symbol-description
43-
return false
44-
} catch {
45-
// In catch and not after in case if something too smart optimizes out code in try. False-negative is acceptable in that case
46-
if (!('onmozfullscreenerror' in g)) return false // Firefox has it (might remove in the future, but we don't care)
47-
return /firefox/i.test(g.navigator.userAgent || '') // as simple as we can
48-
}
49-
50-
/* c8 ignore next */
51-
return false // eslint-disable-line no-unreachable
52-
}
53-
54-
export const skipWeb = /* @__PURE__ */ shouldSkipBuiltins()
55-
56-
function decodePartAddition(a, start, end, m) {
57-
let o = ''
58-
let i = start
59-
for (const last3 = end - 3; i < last3; i += 4) {
60-
const x0 = a[i]
61-
const x1 = a[i + 1]
62-
const x2 = a[i + 2]
63-
const x3 = a[i + 3]
64-
o += m[x0]
65-
o += m[x1]
66-
o += m[x2]
67-
o += m[x3]
68-
}
69-
70-
while (i < end) o += m[a[i++]]
71-
return o
72-
}
73-
74-
// Decoding with templates is faster on Hermes
75-
function decodePartTemplates(a, start, end, m) {
76-
let o = ''
77-
let i = start
78-
for (const last15 = end - 15; i < last15; i += 16) {
79-
const x0 = a[i]
80-
const x1 = a[i + 1]
81-
const x2 = a[i + 2]
82-
const x3 = a[i + 3]
83-
const x4 = a[i + 4]
84-
const x5 = a[i + 5]
85-
const x6 = a[i + 6]
86-
const x7 = a[i + 7]
87-
const x8 = a[i + 8]
88-
const x9 = a[i + 9]
89-
const x10 = a[i + 10]
90-
const x11 = a[i + 11]
91-
const x12 = a[i + 12]
92-
const x13 = a[i + 13]
93-
const x14 = a[i + 14]
94-
const x15 = a[i + 15]
95-
o += `${m[x0]}${m[x1]}${m[x2]}${m[x3]}${m[x4]}${m[x5]}${m[x6]}${m[x7]}${m[x8]}${m[x9]}${m[x10]}${m[x11]}${m[x12]}${m[x13]}${m[x14]}${m[x15]}`
96-
}
97-
98-
while (i < end) o += m[a[i++]]
99-
return o
100-
}
101-
102-
const decodePart = isHermes ? decodePartTemplates : decodePartAddition
103-
export function decode2string(arr, start, end, m) {
104-
if (end - start > 30_000) {
105-
// Limit concatenation to avoid excessive GC
106-
// Thresholds checked on Hermes for toHex
107-
const concat = []
108-
for (let i = start; i < end; ) {
109-
const step = i + 500
110-
const iNext = step > end ? end : step
111-
concat.push(decodePart(arr, i, iNext, m))
112-
i = iNext
113-
}
114-
115-
const res = concat.join('')
116-
concat.length = 0
117-
return res
118-
}
119-
120-
return decodePart(arr, start, end, m)
121-
}
1+
// it's safe even if bundler does not follow browser resolution for browser and skipWeb misdetects
2+
export * from './platform.native.js'

fallback/platform.native.js

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
const { Buffer } = globalThis
2+
const haveNativeBuffer = Buffer && !Buffer.TYPED_ARRAY_SUPPORT
3+
export const nativeBuffer = haveNativeBuffer ? Buffer : null
4+
export const isHermes = /* @__PURE__ */ (() => !!globalThis.HermesInternal)()
5+
export const isDeno = /* @__PURE__ */ (() => !!globalThis.Deno)()
6+
export const isLE = /* @__PURE__ */ (() => new Uint8Array(Uint16Array.of(258).buffer)[0] === 2)()
7+
8+
// We consider Node.js TextDecoder/TextEncoder native
9+
// Still needed in platform.native.js as this is re-exported to platform.js
10+
let isNative = (x) => x && (haveNativeBuffer || `${x}`.includes('[native code]'))
11+
if (!haveNativeBuffer && isNative(() => {})) isNative = () => false // e.g. XS, we don't want false positives
12+
13+
export const nativeEncoder = /* @__PURE__ */ (() =>
14+
isNative(globalThis.TextEncoder) ? new TextEncoder() : null)()
15+
export const nativeDecoder = /* @__PURE__ */ (() =>
16+
isNative(globalThis.TextDecoder) ? new TextDecoder('utf-8', { ignoreBOM: true }) : null)()
17+
18+
// Actually windows-1252, compatible with ascii and latin1 decoding
19+
// Beware that on non-latin1, i.e. on windows-1252, this is broken in ~all Node.js versions released
20+
// in 2025 due to a regression, so we call it Latin1 as it's usable only for that
21+
export const nativeDecoderLatin1 = /* @__PURE__ */ (() => {
22+
// Not all barebone engines with TextDecoder support something except utf-8, detect
23+
if (nativeDecoder) {
24+
try {
25+
return new TextDecoder('latin1', { ignoreBOM: true })
26+
} catch {}
27+
}
28+
29+
return null
30+
})()
31+
32+
export const skipWeb = false
33+
34+
export function decodePartAddition(a, start, end, m) {
35+
let o = ''
36+
let i = start
37+
for (const last3 = end - 3; i < last3; i += 4) {
38+
const x0 = a[i]
39+
const x1 = a[i + 1]
40+
const x2 = a[i + 2]
41+
const x3 = a[i + 3]
42+
o += m[x0]
43+
o += m[x1]
44+
o += m[x2]
45+
o += m[x3]
46+
}
47+
48+
while (i < end) o += m[a[i++]]
49+
return o
50+
}
51+
52+
// Decoding with templates is faster on Hermes
53+
export function decodePartTemplates(a, start, end, m) {
54+
let o = ''
55+
let i = start
56+
for (const last15 = end - 15; i < last15; i += 16) {
57+
const x0 = a[i]
58+
const x1 = a[i + 1]
59+
const x2 = a[i + 2]
60+
const x3 = a[i + 3]
61+
const x4 = a[i + 4]
62+
const x5 = a[i + 5]
63+
const x6 = a[i + 6]
64+
const x7 = a[i + 7]
65+
const x8 = a[i + 8]
66+
const x9 = a[i + 9]
67+
const x10 = a[i + 10]
68+
const x11 = a[i + 11]
69+
const x12 = a[i + 12]
70+
const x13 = a[i + 13]
71+
const x14 = a[i + 14]
72+
const x15 = a[i + 15]
73+
o += `${m[x0]}${m[x1]}${m[x2]}${m[x3]}${m[x4]}${m[x5]}${m[x6]}${m[x7]}${m[x8]}${m[x9]}${m[x10]}${m[x11]}${m[x12]}${m[x13]}${m[x14]}${m[x15]}`
74+
}
75+
76+
while (i < end) o += m[a[i++]]
77+
return o
78+
}
79+
80+
const decodePart = isHermes ? decodePartTemplates : decodePartAddition
81+
export function decode2string(arr, start, end, m) {
82+
if (end - start > 30_000) {
83+
// Limit concatenation to avoid excessive GC
84+
// Thresholds checked on Hermes for toHex
85+
const concat = []
86+
for (let i = start; i < end; ) {
87+
const step = i + 500
88+
const iNext = step > end ? end : step
89+
concat.push(decodePart(arr, i, iNext, m))
90+
i = iNext
91+
}
92+
93+
const res = concat.join('')
94+
concat.length = 0
95+
return res
96+
}
97+
98+
return decodePart(arr, start, end, m)
99+
}

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@
7777
"/fallback/latin1.js",
7878
"/fallback/percent.js",
7979
"/fallback/platform.js",
80+
"/fallback/platform.browser.js",
81+
"/fallback/platform.native.js",
8082
"/fallback/multi-byte.encodings.cjs",
8183
"/fallback/multi-byte.encodings.json",
8284
"/fallback/multi-byte.js",
@@ -226,11 +228,13 @@
226228
},
227229
"browser": {
228230
"./utf16.js": "./utf16.browser.js",
231+
"./fallback/platform.js": "./fallback/platform.browser.js",
229232
"./fallback/utf8.auto.js": "./fallback/utf8.auto.browser.js"
230233
},
231234
"react-native": {
232235
"./encoding-browser.js": "./encoding-browser.native.js",
233236
"./utf16.js": "./utf16.native.js",
237+
"./fallback/platform.js": "./fallback/platform.native.js",
234238
"./fallback/utf8.auto.js": "./fallback/utf8.auto.native.js"
235239
},
236240
"peerDependencies": {

tests/base64.noenc.test.cjs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1-
delete globalThis.TextEncoder
2-
delete globalThis.TextDecoder
3-
require('./base64.test.js')
1+
if (process.env.EXODUS_TEST_IS_BROWSER) {
2+
require('node:test').test.skip('Under browsers, TextEncoder / TextDecoder is required')
3+
} else {
4+
delete globalThis.TextEncoder
5+
delete globalThis.TextDecoder
6+
require('./base64.test.js')
7+
}

tests/hex.noenc.test.cjs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1-
delete globalThis.TextEncoder
2-
delete globalThis.TextDecoder
3-
require('./hex.test.js')
1+
if (process.env.EXODUS_TEST_IS_BROWSER) {
2+
require('node:test').test.skip('Under browsers, TextEncoder / TextDecoder is required')
3+
} else {
4+
delete globalThis.TextEncoder
5+
delete globalThis.TextDecoder
6+
require('./hex.test.js')
7+
}

utf16.native.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { encodeApi, decodeApiDecoders, decodeApiJS } from './fallback/utf16.js'
2-
import { nativeDecoder } from './fallback/platform.js'
2+
import { nativeDecoder } from './fallback/platform.native.js'
33

44
function checkDecoders() {
55
// Not all barebone engines with TextDecoder support something except utf-8

0 commit comments

Comments
 (0)