Skip to content
This repository was archived by the owner on Oct 1, 2025. It is now read-only.

Commit d38f81e

Browse files
pimlieTheAlexLichter
authored andcommitted
fix: implement simply array polyfills (fixes #328)
1 parent 02c7beb commit d38f81e

File tree

9 files changed

+133
-45
lines changed

9 files changed

+133
-45
lines changed

src/client/updateClientMetaInfo.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { metaInfoOptionKeys, metaInfoAttributeKeys } from '../shared/constants'
22
import { isArray } from '../utils/is-type'
3+
import { includes } from '../utils/array'
34
import { updateAttribute, updateTag, updateTitle } from './updaters'
45

56
function getTag(tags, tag) {
@@ -36,7 +37,7 @@ export default function updateClientMetaInfo(options = {}, newInfo) {
3637

3738
for (const type in newInfo) {
3839
// ignore these
39-
if (metaInfoOptionKeys.includes(type)) {
40+
if (includes(metaInfoOptionKeys, type)) {
4041
continue
4142
}
4243

@@ -46,7 +47,7 @@ export default function updateClientMetaInfo(options = {}, newInfo) {
4647
continue
4748
}
4849

49-
if (metaInfoAttributeKeys.includes(type)) {
50+
if (includes(metaInfoAttributeKeys, type)) {
5051
const tagName = type.substr(0, 4)
5152
updateAttribute(options, newInfo[type], getTag(tags, tagName))
5253
continue

src/client/updaters/attribute.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { booleanHtmlAttributes } from '../../shared/constants'
2+
import { toArray, includes } from '../../utils/array'
23
import { isArray } from '../../utils/is-type'
34

45
/**
@@ -10,18 +11,18 @@ import { isArray } from '../../utils/is-type'
1011
export default function updateAttribute({ attribute } = {}, attrs, tag) {
1112
const vueMetaAttrString = tag.getAttribute(attribute)
1213
const vueMetaAttrs = vueMetaAttrString ? vueMetaAttrString.split(',') : []
13-
const toRemove = Array.from(vueMetaAttrs)
14+
const toRemove = toArray(vueMetaAttrs)
1415

1516
const keepIndexes = []
1617
for (const attr in attrs) {
1718
if (attrs.hasOwnProperty(attr)) {
18-
const value = booleanHtmlAttributes.includes(attr)
19+
const value = includes(booleanHtmlAttributes, attr)
1920
? ''
2021
: isArray(attrs[attr]) ? attrs[attr].join(' ') : attrs[attr]
2122

2223
tag.setAttribute(attr, value || '')
2324

24-
if (!vueMetaAttrs.includes(attr)) {
25+
if (!includes(vueMetaAttrs, attr)) {
2526
vueMetaAttrs.push(attr)
2627
}
2728

@@ -31,7 +32,7 @@ export default function updateAttribute({ attribute } = {}, attrs, tag) {
3132
}
3233

3334
const removedAttributesCount = toRemove
34-
.filter((el, index) => !keepIndexes.includes(index))
35+
.filter((el, index) => !includes(keepIndexes, index))
3536
.reduce((acc, attr) => {
3637
tag.removeAttribute(attr)
3738
return acc + 1

src/client/updaters/tag.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { isUndefined } from '../../utils/is-type'
2+
import { toArray, includes } from '../../utils/array'
23

34
/**
45
* Updates meta tags inside <head> and <body> on the client. Borrowed from `react-helmet`:
@@ -9,8 +10,9 @@ import { isUndefined } from '../../utils/is-type'
910
* @return {Object} - a representation of what tags changed
1011
*/
1112
export default function updateTag({ attribute, tagIDKeyName } = {}, type, tags, headTag, bodyTag) {
12-
const oldHeadTags = Array.from(headTag.querySelectorAll(`${type}[${attribute}]`))
13-
const oldBodyTags = Array.from(bodyTag.querySelectorAll(`${type}[${attribute}][data-body="true"]`))
13+
const oldHeadTags = toArray(headTag.querySelectorAll(`${type}[${attribute}]`))
14+
const oldBodyTags = toArray(bodyTag.querySelectorAll(`${type}[${attribute}][data-body="true"]`))
15+
const dataAttributes = [tagIDKeyName, 'body']
1416
const newTags = []
1517

1618
if (tags.length > 1) {
@@ -20,7 +22,7 @@ export default function updateTag({ attribute, tagIDKeyName } = {}, type, tags,
2022
const found = []
2123
tags = tags.filter((x) => {
2224
const k = JSON.stringify(x)
23-
const res = !found.includes(k)
25+
const res = !includes(found, k)
2426
found.push(k)
2527
return res
2628
})
@@ -44,13 +46,12 @@ export default function updateTag({ attribute, tagIDKeyName } = {}, type, tags,
4446
} else {
4547
newElement.appendChild(document.createTextNode(tag.cssText))
4648
}
47-
} else if ([tagIDKeyName, 'body'].includes(attr)) {
48-
const _attr = `data-${attr}`
49-
const value = isUndefined(tag[attr]) ? '' : tag[attr]
50-
newElement.setAttribute(_attr, value)
5149
} else {
50+
const _attr = includes(dataAttributes, attr)
51+
? `data-${attr}`
52+
: attr
5253
const value = isUndefined(tag[attr]) ? '' : tag[attr]
53-
newElement.setAttribute(attr, value)
54+
newElement.setAttribute(_attr, value)
5455
}
5556
}
5657
}

src/shared/escaping.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { isString, isArray, isObject } from '../utils/is-type'
2+
import { includes } from '../utils/array'
23
import { metaInfoOptionKeys, disableOptionKeys } from './constants'
34

45
export const serverSequences = [
@@ -27,13 +28,13 @@ export function escape(info, options, escapeOptions) {
2728
const value = info[key]
2829

2930
// no need to escape configuration options
30-
if (metaInfoOptionKeys.includes(key)) {
31+
if (includes(metaInfoOptionKeys, key)) {
3132
escaped[key] = value
3233
continue
3334
}
3435

3536
let [ disableKey ] = disableOptionKeys
36-
if (escapeOptions[disableKey] && escapeOptions[disableKey].includes(key)) {
37+
if (escapeOptions[disableKey] && includes(escapeOptions[disableKey], key)) {
3738
// this info[key] doesnt need to escaped if the option is listed in __dangerouslyDisableSanitizers
3839
escaped[key] = value
3940
continue
@@ -44,7 +45,7 @@ export function escape(info, options, escapeOptions) {
4445
disableKey = disableOptionKeys[1]
4546

4647
// keys which are listed in __dangerouslyDisableSanitizersByTagID for the current vmid do not need to be escaped
47-
if (escapeOptions[disableKey] && escapeOptions[disableKey][tagId] && escapeOptions[disableKey][tagId].includes(key)) {
48+
if (escapeOptions[disableKey] && escapeOptions[disableKey][tagId] && includes(escapeOptions[disableKey][tagId], key)) {
4849
escaped[key] = value
4950
continue
5051
}

src/shared/getMetaInfo.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import getComponentOption from './getComponentOption'
1313
*/
1414
export default function getMetaInfo(options = {}, component, escapeSequences = []) {
1515
// collect & aggregate all metaInfo $options
16-
let info = getComponentOption({ ...options, component }, defaultInfo)
16+
let info = getComponentOption(options, component, defaultInfo)
1717

1818
// Remove all "template" tags from meta
1919

src/shared/merge.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import deepmerge from 'deepmerge'
2+
import { findIndex } from '../utils/array'
23
import { applyTemplate } from './template'
34
import { metaInfoAttributeKeys } from './constants'
45

@@ -15,7 +16,7 @@ export function arrayMerge({ component, tagIDKeyName, metaTemplateKeyName, conte
1516
return
1617
}
1718

18-
const sourceIndex = source.findIndex(item => item[tagIDKeyName] === targetItem[tagIDKeyName])
19+
const sourceIndex = findIndex(source, item => item[tagIDKeyName] === targetItem[tagIDKeyName])
1920
const sourceItem = source[sourceIndex]
2021

2122
// source doesnt contain any duplicate vmid's, we can keep targetItem

src/utils/array.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* To reduce build size, this file provides simple polyfills without
3+
* overly excessive type checking and without modifying
4+
* the global Array.prototype
5+
* The polyfills are automatically removed in the commonjs build
6+
* Also, only files in client/ & shared/ should use these functions
7+
* files in server/ still use normal js function
8+
*/
9+
10+
// this const is replaced by rollup to true for umd builds
11+
// which means the polyfills are removed for other build formats
12+
const polyfill = process.env.NODE_ENV === 'test'
13+
14+
export function findIndex(array, predicate) {
15+
if (polyfill && !Array.prototype.findIndex) {
16+
// idx needs to be a Number, for..in returns string
17+
for (let idx = 0; idx < array.length; idx++) {
18+
if (predicate.call(arguments[2], array[idx], idx, array)) {
19+
return idx
20+
}
21+
}
22+
return -1
23+
}
24+
return array.findIndex(predicate, arguments[2])
25+
}
26+
27+
export function toArray(arg) {
28+
if (polyfill && !Array.from) {
29+
return Array.prototype.slice.call(arg)
30+
}
31+
return Array.from(arg)
32+
}
33+
34+
export function includes(array, value) {
35+
if (polyfill && !Array.prototype.includes) {
36+
for (const idx in array) {
37+
if (array[idx] === value) {
38+
return true
39+
}
40+
}
41+
42+
return false
43+
}
44+
return array.includes(value)
45+
}

test/unit/shared.test.js

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,7 @@
1-
/**
2-
* @jest-environment node
3-
*/
4-
import setOptions from '../../src/shared/options'
1+
import { setOptions } from '../../src/shared/options'
52
import { defaultOptions } from '../../src/shared/constants'
6-
import { ensureIsArray } from '../../src/utils/ensure'
7-
import { hasGlobalWindowFn } from '../../src/utils/window'
83

94
describe('shared', () => {
10-
test('ensureIsArray ensures var is array', () => {
11-
let a = { p: 1 }
12-
expect(ensureIsArray(a)).toEqual([])
13-
14-
a = 1
15-
expect(ensureIsArray(a)).toEqual([])
16-
17-
a = [1]
18-
expect(ensureIsArray(a)).toBe(a)
19-
})
20-
21-
test('ensureIsArray ensures obj prop is array', () => {
22-
const a = { p: 1 }
23-
expect(ensureIsArray(a, 'p')).toEqual({ p: [] })
24-
})
25-
26-
test('no error when window is not defined', () => {
27-
expect(hasGlobalWindowFn()).toBe(false)
28-
})
29-
305
test('can use setOptions', () => {
316
const keyName = 'MY KEY'
327
let options = { keyName }

test/unit/utils.test.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* @jest-environment node
3+
*/
4+
import { findIndex, includes, toArray } from '../../src/utils/array'
5+
import { ensureIsArray } from '../../src/utils/ensure'
6+
import { hasGlobalWindowFn } from '../../src/utils/window'
7+
8+
describe('shared', () => {
9+
afterEach(() => jest.restoreAllMocks())
10+
11+
test('ensureIsArray ensures var is array', () => {
12+
let a = { p: 1 }
13+
expect(ensureIsArray(a)).toEqual([])
14+
15+
a = 1
16+
expect(ensureIsArray(a)).toEqual([])
17+
18+
a = [1]
19+
expect(ensureIsArray(a)).toBe(a)
20+
})
21+
22+
test('ensureIsArray ensures obj prop is array', () => {
23+
const a = { p: 1 }
24+
expect(ensureIsArray(a, 'p')).toEqual({ p: [] })
25+
})
26+
27+
test('no error when window is not defined', () => {
28+
expect(hasGlobalWindowFn()).toBe(false)
29+
})
30+
31+
/* eslint-disable no-extend-native */
32+
test('findIndex polyfill', () => {
33+
const _findIndex = Array.prototype.findIndex
34+
Array.prototype.findIndex = false
35+
36+
const arr = [1, 2, 3]
37+
expect(findIndex(arr, v => v === 2)).toBe(1)
38+
expect(findIndex(arr, v => v === 4)).toBe(-1)
39+
40+
Array.prototype.findIndex = _findIndex
41+
})
42+
43+
test('includes polyfill', () => {
44+
const _includes = Array.prototype.includes
45+
Array.prototype.includes = false
46+
47+
const arr = [1, 2, 3]
48+
expect(includes(arr, 2)).toBe(true)
49+
expect(includes(arr, 4)).toBe(false)
50+
51+
Array.prototype.includes = _includes
52+
})
53+
54+
test('from/toArray polyfill', () => {
55+
const _from = Array.from
56+
Array.from = false
57+
58+
expect(toArray('foo')).toEqual(['f', 'o', 'o'])
59+
60+
Array.from = _from
61+
})
62+
/* eslint-enable no-extend-native */
63+
})

0 commit comments

Comments
 (0)