mirror of
https://github.com/tenrok/vue-meta.git
synced 2026-06-23 10:00:34 +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 { metaInfoOptionKeys, metaInfoAttributeKeys } from '../shared/constants'
|
||||||
import { isArray } from '../utils/is-type'
|
import { isArray } from '../utils/is-type'
|
||||||
|
import { includes } from '../utils/array'
|
||||||
import { updateAttribute, updateTag, updateTitle } from './updaters'
|
import { updateAttribute, updateTag, updateTitle } from './updaters'
|
||||||
|
|
||||||
function getTag(tags, tag) {
|
function getTag(tags, tag) {
|
||||||
@@ -36,7 +37,7 @@ export default function updateClientMetaInfo(options = {}, newInfo) {
|
|||||||
|
|
||||||
for (const type in newInfo) {
|
for (const type in newInfo) {
|
||||||
// ignore these
|
// ignore these
|
||||||
if (metaInfoOptionKeys.includes(type)) {
|
if (includes(metaInfoOptionKeys, type)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +47,7 @@ export default function updateClientMetaInfo(options = {}, newInfo) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (metaInfoAttributeKeys.includes(type)) {
|
if (includes(metaInfoAttributeKeys, type)) {
|
||||||
const tagName = type.substr(0, 4)
|
const tagName = type.substr(0, 4)
|
||||||
updateAttribute(options, newInfo[type], getTag(tags, tagName))
|
updateAttribute(options, newInfo[type], getTag(tags, tagName))
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { booleanHtmlAttributes } from '../../shared/constants'
|
import { booleanHtmlAttributes } from '../../shared/constants'
|
||||||
|
import { toArray, includes } from '../../utils/array'
|
||||||
import { isArray } from '../../utils/is-type'
|
import { isArray } from '../../utils/is-type'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -10,18 +11,18 @@ import { isArray } from '../../utils/is-type'
|
|||||||
export default function updateAttribute({ attribute } = {}, attrs, tag) {
|
export default function updateAttribute({ attribute } = {}, attrs, tag) {
|
||||||
const vueMetaAttrString = tag.getAttribute(attribute)
|
const vueMetaAttrString = tag.getAttribute(attribute)
|
||||||
const vueMetaAttrs = vueMetaAttrString ? vueMetaAttrString.split(',') : []
|
const vueMetaAttrs = vueMetaAttrString ? vueMetaAttrString.split(',') : []
|
||||||
const toRemove = Array.from(vueMetaAttrs)
|
const toRemove = toArray(vueMetaAttrs)
|
||||||
|
|
||||||
const keepIndexes = []
|
const keepIndexes = []
|
||||||
for (const attr in attrs) {
|
for (const attr in attrs) {
|
||||||
if (attrs.hasOwnProperty(attr)) {
|
if (attrs.hasOwnProperty(attr)) {
|
||||||
const value = booleanHtmlAttributes.includes(attr)
|
const value = includes(booleanHtmlAttributes, attr)
|
||||||
? ''
|
? ''
|
||||||
: isArray(attrs[attr]) ? attrs[attr].join(' ') : attrs[attr]
|
: isArray(attrs[attr]) ? attrs[attr].join(' ') : attrs[attr]
|
||||||
|
|
||||||
tag.setAttribute(attr, value || '')
|
tag.setAttribute(attr, value || '')
|
||||||
|
|
||||||
if (!vueMetaAttrs.includes(attr)) {
|
if (!includes(vueMetaAttrs, attr)) {
|
||||||
vueMetaAttrs.push(attr)
|
vueMetaAttrs.push(attr)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,7 +32,7 @@ export default function updateAttribute({ attribute } = {}, attrs, tag) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const removedAttributesCount = toRemove
|
const removedAttributesCount = toRemove
|
||||||
.filter((el, index) => !keepIndexes.includes(index))
|
.filter((el, index) => !includes(keepIndexes, index))
|
||||||
.reduce((acc, attr) => {
|
.reduce((acc, attr) => {
|
||||||
tag.removeAttribute(attr)
|
tag.removeAttribute(attr)
|
||||||
return acc + 1
|
return acc + 1
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { isUndefined } from '../../utils/is-type'
|
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`:
|
* 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
|
* @return {Object} - a representation of what tags changed
|
||||||
*/
|
*/
|
||||||
export default function updateTag({ attribute, tagIDKeyName } = {}, type, tags, headTag, bodyTag) {
|
export default function updateTag({ attribute, tagIDKeyName } = {}, type, tags, headTag, bodyTag) {
|
||||||
const oldHeadTags = Array.from(headTag.querySelectorAll(`${type}[${attribute}]`))
|
const oldHeadTags = toArray(headTag.querySelectorAll(`${type}[${attribute}]`))
|
||||||
const oldBodyTags = Array.from(bodyTag.querySelectorAll(`${type}[${attribute}][data-body="true"]`))
|
const oldBodyTags = toArray(bodyTag.querySelectorAll(`${type}[${attribute}][data-body="true"]`))
|
||||||
|
const dataAttributes = [tagIDKeyName, 'body']
|
||||||
const newTags = []
|
const newTags = []
|
||||||
|
|
||||||
if (tags.length > 1) {
|
if (tags.length > 1) {
|
||||||
@@ -20,7 +22,7 @@ export default function updateTag({ attribute, tagIDKeyName } = {}, type, tags,
|
|||||||
const found = []
|
const found = []
|
||||||
tags = tags.filter((x) => {
|
tags = tags.filter((x) => {
|
||||||
const k = JSON.stringify(x)
|
const k = JSON.stringify(x)
|
||||||
const res = !found.includes(k)
|
const res = !includes(found, k)
|
||||||
found.push(k)
|
found.push(k)
|
||||||
return res
|
return res
|
||||||
})
|
})
|
||||||
@@ -44,13 +46,12 @@ export default function updateTag({ attribute, tagIDKeyName } = {}, type, tags,
|
|||||||
} else {
|
} else {
|
||||||
newElement.appendChild(document.createTextNode(tag.cssText))
|
newElement.appendChild(document.createTextNode(tag.cssText))
|
||||||
}
|
}
|
||||||
} else if ([tagIDKeyName, 'body'].includes(attr)) {
|
} else {
|
||||||
const _attr = `data-${attr}`
|
const _attr = includes(dataAttributes, attr)
|
||||||
|
? `data-${attr}`
|
||||||
|
: attr
|
||||||
const value = isUndefined(tag[attr]) ? '' : tag[attr]
|
const value = isUndefined(tag[attr]) ? '' : tag[attr]
|
||||||
newElement.setAttribute(_attr, value)
|
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 { isString, isArray, isObject } from '../utils/is-type'
|
||||||
|
import { includes } from '../utils/array'
|
||||||
import { metaInfoOptionKeys, disableOptionKeys } from './constants'
|
import { metaInfoOptionKeys, disableOptionKeys } from './constants'
|
||||||
|
|
||||||
export const serverSequences = [
|
export const serverSequences = [
|
||||||
@@ -27,13 +28,13 @@ export function escape(info, options, escapeOptions) {
|
|||||||
const value = info[key]
|
const value = info[key]
|
||||||
|
|
||||||
// no need to escape configuration options
|
// no need to escape configuration options
|
||||||
if (metaInfoOptionKeys.includes(key)) {
|
if (includes(metaInfoOptionKeys, key)) {
|
||||||
escaped[key] = value
|
escaped[key] = value
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
let [ disableKey ] = disableOptionKeys
|
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
|
// this info[key] doesnt need to escaped if the option is listed in __dangerouslyDisableSanitizers
|
||||||
escaped[key] = value
|
escaped[key] = value
|
||||||
continue
|
continue
|
||||||
@@ -44,7 +45,7 @@ export function escape(info, options, escapeOptions) {
|
|||||||
disableKey = disableOptionKeys[1]
|
disableKey = disableOptionKeys[1]
|
||||||
|
|
||||||
// keys which are listed in __dangerouslyDisableSanitizersByTagID for the current vmid do not need to be escaped
|
// 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
|
escaped[key] = value
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import getComponentOption from './getComponentOption'
|
|||||||
*/
|
*/
|
||||||
export default function getMetaInfo(options = {}, component, escapeSequences = []) {
|
export default function getMetaInfo(options = {}, component, escapeSequences = []) {
|
||||||
// collect & aggregate all metaInfo $options
|
// collect & aggregate all metaInfo $options
|
||||||
let info = getComponentOption({ ...options, component }, defaultInfo)
|
let info = getComponentOption(options, component, defaultInfo)
|
||||||
|
|
||||||
// Remove all "template" tags from meta
|
// Remove all "template" tags from meta
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -1,4 +1,5 @@
|
|||||||
import deepmerge from 'deepmerge'
|
import deepmerge from 'deepmerge'
|
||||||
|
import { findIndex } from '../utils/array'
|
||||||
import { applyTemplate } from './template'
|
import { applyTemplate } from './template'
|
||||||
import { metaInfoAttributeKeys } from './constants'
|
import { metaInfoAttributeKeys } from './constants'
|
||||||
|
|
||||||
@@ -15,7 +16,7 @@ export function arrayMerge({ component, tagIDKeyName, metaTemplateKeyName, conte
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const sourceIndex = source.findIndex(item => item[tagIDKeyName] === targetItem[tagIDKeyName])
|
const sourceIndex = findIndex(source, item => item[tagIDKeyName] === targetItem[tagIDKeyName])
|
||||||
const sourceItem = source[sourceIndex]
|
const sourceItem = source[sourceIndex]
|
||||||
|
|
||||||
// source doesnt contain any duplicate vmid's, we can keep targetItem
|
// 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 @@
|
|||||||
/**
|
import { setOptions } from '../../src/shared/options'
|
||||||
* @jest-environment node
|
|
||||||
*/
|
|
||||||
import setOptions from '../../src/shared/options'
|
|
||||||
import { defaultOptions } from '../../src/shared/constants'
|
import { defaultOptions } from '../../src/shared/constants'
|
||||||
import { ensureIsArray } from '../../src/utils/ensure'
|
|
||||||
import { hasGlobalWindowFn } from '../../src/utils/window'
|
|
||||||
|
|
||||||
describe('shared', () => {
|
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', () => {
|
test('can use setOptions', () => {
|
||||||
const keyName = 'MY KEY'
|
const keyName = 'MY KEY'
|
||||||
let options = { keyName }
|
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