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:
committed by
Alexander Lichter
parent
02c7beb6de
commit
d38f81e0a9
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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,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 }
|
||||
|
||||
@@ -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 */
|
||||
})
|
||||
Reference in New Issue
Block a user