2
0
mirror of https://github.com/tenrok/vue-meta.git synced 2026-06-07 04:02:23 +03:00

fix: implement simply array polyfills (fixes #328)

This commit is contained in:
pimlie
2019-03-11 17:19:55 +01:00
committed by Alexander Lichter
parent 02c7beb6de
commit d38f81e0a9
9 changed files with 133 additions and 45 deletions
+3 -2
View File
@@ -1,5 +1,6 @@
import { metaInfoOptionKeys, metaInfoAttributeKeys } from '../shared/constants'
import { isArray } from '../utils/is-type'
import { includes } from '../utils/array'
import { updateAttribute, updateTag, updateTitle } from './updaters'
function getTag(tags, tag) {
@@ -36,7 +37,7 @@ export default function updateClientMetaInfo(options = {}, newInfo) {
for (const type in newInfo) {
// ignore these
if (metaInfoOptionKeys.includes(type)) {
if (includes(metaInfoOptionKeys, type)) {
continue
}
@@ -46,7 +47,7 @@ export default function updateClientMetaInfo(options = {}, newInfo) {
continue
}
if (metaInfoAttributeKeys.includes(type)) {
if (includes(metaInfoAttributeKeys, type)) {
const tagName = type.substr(0, 4)
updateAttribute(options, newInfo[type], getTag(tags, tagName))
continue
+5 -4
View File
@@ -1,4 +1,5 @@
import { booleanHtmlAttributes } from '../../shared/constants'
import { toArray, includes } from '../../utils/array'
import { isArray } from '../../utils/is-type'
/**
@@ -10,18 +11,18 @@ import { isArray } from '../../utils/is-type'
export default function updateAttribute({ attribute } = {}, attrs, tag) {
const vueMetaAttrString = tag.getAttribute(attribute)
const vueMetaAttrs = vueMetaAttrString ? vueMetaAttrString.split(',') : []
const toRemove = Array.from(vueMetaAttrs)
const toRemove = toArray(vueMetaAttrs)
const keepIndexes = []
for (const attr in attrs) {
if (attrs.hasOwnProperty(attr)) {
const value = booleanHtmlAttributes.includes(attr)
const value = includes(booleanHtmlAttributes, attr)
? ''
: isArray(attrs[attr]) ? attrs[attr].join(' ') : attrs[attr]
tag.setAttribute(attr, value || '')
if (!vueMetaAttrs.includes(attr)) {
if (!includes(vueMetaAttrs, attr)) {
vueMetaAttrs.push(attr)
}
@@ -31,7 +32,7 @@ export default function updateAttribute({ attribute } = {}, attrs, tag) {
}
const removedAttributesCount = toRemove
.filter((el, index) => !keepIndexes.includes(index))
.filter((el, index) => !includes(keepIndexes, index))
.reduce((acc, attr) => {
tag.removeAttribute(attr)
return acc + 1
+9 -8
View File
@@ -1,4 +1,5 @@
import { isUndefined } from '../../utils/is-type'
import { toArray, includes } from '../../utils/array'
/**
* Updates meta tags inside <head> and <body> on the client. Borrowed from `react-helmet`:
@@ -9,8 +10,9 @@ import { isUndefined } from '../../utils/is-type'
* @return {Object} - a representation of what tags changed
*/
export default function updateTag({ attribute, tagIDKeyName } = {}, type, tags, headTag, bodyTag) {
const oldHeadTags = Array.from(headTag.querySelectorAll(`${type}[${attribute}]`))
const oldBodyTags = Array.from(bodyTag.querySelectorAll(`${type}[${attribute}][data-body="true"]`))
const oldHeadTags = toArray(headTag.querySelectorAll(`${type}[${attribute}]`))
const oldBodyTags = toArray(bodyTag.querySelectorAll(`${type}[${attribute}][data-body="true"]`))
const dataAttributes = [tagIDKeyName, 'body']
const newTags = []
if (tags.length > 1) {
@@ -20,7 +22,7 @@ export default function updateTag({ attribute, tagIDKeyName } = {}, type, tags,
const found = []
tags = tags.filter((x) => {
const k = JSON.stringify(x)
const res = !found.includes(k)
const res = !includes(found, k)
found.push(k)
return res
})
@@ -44,13 +46,12 @@ export default function updateTag({ attribute, tagIDKeyName } = {}, type, tags,
} else {
newElement.appendChild(document.createTextNode(tag.cssText))
}
} else if ([tagIDKeyName, 'body'].includes(attr)) {
const _attr = `data-${attr}`
} else {
const _attr = includes(dataAttributes, attr)
? `data-${attr}`
: attr
const value = isUndefined(tag[attr]) ? '' : tag[attr]
newElement.setAttribute(_attr, value)
} else {
const value = isUndefined(tag[attr]) ? '' : tag[attr]
newElement.setAttribute(attr, value)
}
}
}
+4 -3
View File
@@ -1,4 +1,5 @@
import { isString, isArray, isObject } from '../utils/is-type'
import { includes } from '../utils/array'
import { metaInfoOptionKeys, disableOptionKeys } from './constants'
export const serverSequences = [
@@ -27,13 +28,13 @@ export function escape(info, options, escapeOptions) {
const value = info[key]
// no need to escape configuration options
if (metaInfoOptionKeys.includes(key)) {
if (includes(metaInfoOptionKeys, key)) {
escaped[key] = value
continue
}
let [ disableKey ] = disableOptionKeys
if (escapeOptions[disableKey] && escapeOptions[disableKey].includes(key)) {
if (escapeOptions[disableKey] && includes(escapeOptions[disableKey], key)) {
// this info[key] doesnt need to escaped if the option is listed in __dangerouslyDisableSanitizers
escaped[key] = value
continue
@@ -44,7 +45,7 @@ export function escape(info, options, escapeOptions) {
disableKey = disableOptionKeys[1]
// keys which are listed in __dangerouslyDisableSanitizersByTagID for the current vmid do not need to be escaped
if (escapeOptions[disableKey] && escapeOptions[disableKey][tagId] && escapeOptions[disableKey][tagId].includes(key)) {
if (escapeOptions[disableKey] && escapeOptions[disableKey][tagId] && includes(escapeOptions[disableKey][tagId], key)) {
escaped[key] = value
continue
}
+1 -1
View File
@@ -13,7 +13,7 @@ import getComponentOption from './getComponentOption'
*/
export default function getMetaInfo(options = {}, component, escapeSequences = []) {
// collect & aggregate all metaInfo $options
let info = getComponentOption({ ...options, component }, defaultInfo)
let info = getComponentOption(options, component, defaultInfo)
// Remove all "template" tags from meta
+2 -1
View File
@@ -1,4 +1,5 @@
import deepmerge from 'deepmerge'
import { findIndex } from '../utils/array'
import { applyTemplate } from './template'
import { metaInfoAttributeKeys } from './constants'
@@ -15,7 +16,7 @@ export function arrayMerge({ component, tagIDKeyName, metaTemplateKeyName, conte
return
}
const sourceIndex = source.findIndex(item => item[tagIDKeyName] === targetItem[tagIDKeyName])
const sourceIndex = findIndex(source, item => item[tagIDKeyName] === targetItem[tagIDKeyName])
const sourceItem = source[sourceIndex]
// source doesnt contain any duplicate vmid's, we can keep targetItem
+45
View File
@@ -0,0 +1,45 @@
/*
* To reduce build size, this file provides simple polyfills without
* overly excessive type checking and without modifying
* the global Array.prototype
* The polyfills are automatically removed in the commonjs build
* Also, only files in client/ & shared/ should use these functions
* files in server/ still use normal js function
*/
// this const is replaced by rollup to true for umd builds
// which means the polyfills are removed for other build formats
const polyfill = process.env.NODE_ENV === 'test'
export function findIndex(array, predicate) {
if (polyfill && !Array.prototype.findIndex) {
// idx needs to be a Number, for..in returns string
for (let idx = 0; idx < array.length; idx++) {
if (predicate.call(arguments[2], array[idx], idx, array)) {
return idx
}
}
return -1
}
return array.findIndex(predicate, arguments[2])
}
export function toArray(arg) {
if (polyfill && !Array.from) {
return Array.prototype.slice.call(arg)
}
return Array.from(arg)
}
export function includes(array, value) {
if (polyfill && !Array.prototype.includes) {
for (const idx in array) {
if (array[idx] === value) {
return true
}
}
return false
}
return array.includes(value)
}
+1 -26
View File
@@ -1,32 +1,7 @@
/**
* @jest-environment node
*/
import setOptions from '../../src/shared/options'
import { setOptions } from '../../src/shared/options'
import { defaultOptions } from '../../src/shared/constants'
import { ensureIsArray } from '../../src/utils/ensure'
import { hasGlobalWindowFn } from '../../src/utils/window'
describe('shared', () => {
test('ensureIsArray ensures var is array', () => {
let a = { p: 1 }
expect(ensureIsArray(a)).toEqual([])
a = 1
expect(ensureIsArray(a)).toEqual([])
a = [1]
expect(ensureIsArray(a)).toBe(a)
})
test('ensureIsArray ensures obj prop is array', () => {
const a = { p: 1 }
expect(ensureIsArray(a, 'p')).toEqual({ p: [] })
})
test('no error when window is not defined', () => {
expect(hasGlobalWindowFn()).toBe(false)
})
test('can use setOptions', () => {
const keyName = 'MY KEY'
let options = { keyName }
+63
View File
@@ -0,0 +1,63 @@
/**
* @jest-environment node
*/
import { findIndex, includes, toArray } from '../../src/utils/array'
import { ensureIsArray } from '../../src/utils/ensure'
import { hasGlobalWindowFn } from '../../src/utils/window'
describe('shared', () => {
afterEach(() => jest.restoreAllMocks())
test('ensureIsArray ensures var is array', () => {
let a = { p: 1 }
expect(ensureIsArray(a)).toEqual([])
a = 1
expect(ensureIsArray(a)).toEqual([])
a = [1]
expect(ensureIsArray(a)).toBe(a)
})
test('ensureIsArray ensures obj prop is array', () => {
const a = { p: 1 }
expect(ensureIsArray(a, 'p')).toEqual({ p: [] })
})
test('no error when window is not defined', () => {
expect(hasGlobalWindowFn()).toBe(false)
})
/* eslint-disable no-extend-native */
test('findIndex polyfill', () => {
const _findIndex = Array.prototype.findIndex
Array.prototype.findIndex = false
const arr = [1, 2, 3]
expect(findIndex(arr, v => v === 2)).toBe(1)
expect(findIndex(arr, v => v === 4)).toBe(-1)
Array.prototype.findIndex = _findIndex
})
test('includes polyfill', () => {
const _includes = Array.prototype.includes
Array.prototype.includes = false
const arr = [1, 2, 3]
expect(includes(arr, 2)).toBe(true)
expect(includes(arr, 4)).toBe(false)
Array.prototype.includes = _includes
})
test('from/toArray polyfill', () => {
const _from = Array.from
Array.from = false
expect(toArray('foo')).toEqual(['f', 'o', 'o'])
Array.from = _from
})
/* eslint-enable no-extend-native */
})