@garfish/utils
Advanced tools
Comparing version 0.0.9 to 0.0.12
@@ -33,7 +33,6 @@ 'use strict'; | ||
}, | ||
// 测试环境允许 delete | ||
configurable: true , | ||
}); | ||
} | ||
// 数组变为对象 `['a'] => { a: true }` | ||
// Array to Object `['a'] => { a: true }` | ||
function makeMap(list) { | ||
@@ -46,2 +45,5 @@ const map = Object.create(null); | ||
} | ||
function inBrowser() { | ||
return typeof window !== 'undefined'; | ||
} | ||
const warnPrefix = '[Garfish warning]'; | ||
@@ -76,9 +78,2 @@ const processError = (error, fn) => { | ||
} | ||
// 将字符串被设置为对象属性名时,会被尝试改造为常量化版本,避免浏览器重复产生缓存 | ||
function internFunc(internalizeString) { | ||
// 暂时不考虑Hash-collision,https://en.wikipedia.org/wiki/Collision_(computer_science)。v8貌似在16383长度时会发生hash-collision,经过测试后发现正常 | ||
const temporaryOb = {}; | ||
temporaryOb[internalizeString] = true; | ||
return Object.keys(temporaryOb)[0]; | ||
} | ||
function validURL(str) { | ||
@@ -93,5 +88,14 @@ const pattern = new RegExp('^(https?:\\/\\/)?' + // protocol | ||
} | ||
// When the string is set as the object property name, | ||
// it will be attempted to be transformed into a constant version to avoid repeated caching by the browser | ||
function internFunc(internalizeString) { | ||
// Don't consider "Hash-collision,https://en.wikipedia.org/wiki/Collision_(computer_science)" | ||
// v8貌似在 16383 长度时会发生 hash-collision,经过测试后发现正常 | ||
const temporaryOb = {}; | ||
temporaryOb[internalizeString] = true; | ||
return Object.keys(temporaryOb)[0]; | ||
} | ||
function evalWithEnv(code, params) { | ||
const keys = Object.keys(params); | ||
// 不可使用随机值,否则无法作为常量字符串复用 | ||
// No random value can be used, otherwise it cannot be reused as a constant string | ||
const randomValKey = '__garfish__exec_temporary__'; | ||
@@ -144,7 +148,2 @@ const vales = keys.map((k) => `window.${randomValKey}.${k}`); | ||
} | ||
function mixins(...list) { | ||
return function (target) { | ||
Object.assign(target.prototype, ...list); | ||
}; | ||
} | ||
// 有些测试 jest.mock 不好测,可用这个工具方法 | ||
@@ -175,27 +174,26 @@ function callTestCallback(obj, ...args) { | ||
} | ||
// 深度合并两个对象,能处理循环引用,后面的覆盖前面的,可选数组去重 | ||
// Deeply merge two objects, can handle circular references, the latter overwrite the previous | ||
function deepMerge(o, n, dp) { | ||
const lRecord = new WeakMap(); | ||
const rRecord = new WeakMap(); | ||
const vRecord = new WeakMap(); | ||
const leftRecord = new WeakMap(); | ||
const rightRecord = new WeakMap(); | ||
const valueRecord = new WeakMap(); | ||
const isArray = Array.isArray; | ||
const isAllRefs = (a, b) => { | ||
// 判断 merge 左右两边,不需要用到 vRecord | ||
if (lRecord.has(a) || rRecord.has(a)) { | ||
return lRecord.has(b) || rRecord.has(b); | ||
if (leftRecord.has(a) || rightRecord.has(a)) { | ||
return leftRecord.has(b) || rightRecord.has(b); | ||
} | ||
}; | ||
const clone = (v) => { | ||
// 深拷贝 | ||
// Deep clone | ||
if (isPrimitive(v) || typeof v === 'function') { | ||
return v; | ||
} | ||
else if (vRecord.has(v)) { | ||
return vRecord.get(v); | ||
else if (valueRecord.has(v)) { | ||
return valueRecord.get(v); | ||
} | ||
else if (lRecord.has(v)) { | ||
return lRecord.get(v); | ||
else if (leftRecord.has(v)) { | ||
return leftRecord.get(v); | ||
} | ||
else if (rRecord.has(v)) { | ||
return rRecord.get(v); | ||
else if (rightRecord.has(v)) { | ||
return rightRecord.get(v); | ||
} | ||
@@ -205,15 +203,15 @@ else if (isArray(v)) { | ||
v = unique(v); | ||
const res = []; | ||
vRecord.set(v, res); | ||
const arr = []; | ||
valueRecord.set(v, arr); | ||
for (let i = 0, len = v.length; i < len; i++) { | ||
res[i] = clone(v[i]); | ||
arr[i] = clone(v[i]); | ||
} | ||
return res; | ||
return arr; | ||
} | ||
else if (typeof v === 'object') { | ||
const res = {}; | ||
vRecord.set(v, res); | ||
const obj = {}; | ||
valueRecord.set(v, obj); | ||
const keys = Reflect.ownKeys(v); | ||
keys.forEach((key) => (res[key] = clone(v[key]))); | ||
return res; | ||
keys.forEach((key) => (obj[key] = clone(v[key]))); | ||
return obj; | ||
} | ||
@@ -235,7 +233,7 @@ }; | ||
const res = {}; | ||
const lkeys = Reflect.ownKeys(l); | ||
const rkeys = Reflect.ownKeys(r); | ||
lRecord.set(l, res); | ||
rRecord.set(r, res); | ||
lkeys.forEach((key) => { | ||
const leftKeys = Reflect.ownKeys(l); | ||
const rightKeys = Reflect.ownKeys(r); | ||
leftRecord.set(l, res); | ||
rightRecord.set(r, res); | ||
leftKeys.forEach((key) => { | ||
const lv = l[key]; | ||
@@ -250,17 +248,17 @@ const rv = r[key]; | ||
res[key] = isAllRefs(lv, rv) | ||
? lRecord.get(lv) // 左边右边同一个值,取哪个都行 | ||
? leftRecord.get(lv) // The same value on the left and right, whichever is OK | ||
: mergeObject(lv, rv); | ||
} | ||
else { | ||
res[key] = setValue(rRecord, rv); | ||
res[key] = setValue(rightRecord, rv); | ||
} | ||
} | ||
else { | ||
res[key] = setValue(lRecord, lv); | ||
res[key] = setValue(leftRecord, lv); | ||
} | ||
}); | ||
rkeys.forEach((key) => { | ||
rightKeys.forEach((key) => { | ||
if (hasOwn(res, key)) | ||
return; | ||
res[key] = setValue(rRecord, r[key]); | ||
res[key] = setValue(rightRecord, r[key]); | ||
}); | ||
@@ -271,5 +269,61 @@ return res; | ||
} | ||
function inBrowser() { | ||
return typeof window !== 'undefined'; | ||
// Scheme: https://tools.ietf.org/html/rfc3986#section-3.1 | ||
// Absolute URL: https://tools.ietf.org/html/rfc3986#section-4.3 | ||
function isAbsolute(url) { | ||
// `c:\\` 这种 case 返回 false,在浏览器中使用本地图片,应该用 file 协议 | ||
if (!/^[a-zA-Z]:\\/.test(url)) { | ||
if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(url)) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
function transformUrl(resolvePath, curPath) { | ||
const baseUrl = new URL(resolvePath, location.href); | ||
const realPath = new URL(curPath, baseUrl.href); | ||
return realPath.href; | ||
} | ||
function findTarget(el, selectors) { | ||
for (const s of selectors) { | ||
const target = el.querySelector(s); | ||
if (target) | ||
return target; | ||
} | ||
return el; | ||
} | ||
function setDocCurrentScript(target, code, define, url, async) { | ||
if (!target) | ||
return noop; | ||
const el = document.createElement('script'); | ||
if (async) { | ||
el.setAttribute('async', 'true'); | ||
} | ||
if (url) { | ||
el.setAttribute('src', url); | ||
} | ||
else if (code) { | ||
el.textContent = code; | ||
} | ||
const set = (val) => { | ||
try { | ||
if (define) { | ||
Object.defineProperty(target, 'currentScript', { | ||
value: val, | ||
writable: true, | ||
configurable: true, | ||
}); | ||
} | ||
else { | ||
target.currentScript = val; | ||
} | ||
} | ||
catch (e) { | ||
{ | ||
warn(e); | ||
} | ||
} | ||
}; | ||
set(el); | ||
return () => set(null); | ||
} | ||
@@ -420,61 +474,137 @@ // copy from https://github.com/getsentry/sentry-javascript/blob/6.4.0/packages/browser/src/tracekit.ts | ||
// Scheme: https://tools.ietf.org/html/rfc3986#section-3.1 | ||
// Absolute URL: https://tools.ietf.org/html/rfc3986#section-4.3 | ||
function isAbsolute(url) { | ||
// `c:\\` 这种 case 返回 false,在浏览器中使用本地图片,应该用 file 协议 | ||
if (!/^[a-zA-Z]:\\/.test(url)) { | ||
if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(url)) { | ||
return true; | ||
const xChar = 120; // "x" char | ||
const colonChar = 58; // ":" char | ||
const ns = 'http://www.w3.org/2000/svg'; | ||
const xlinkNS = 'http://www.w3.org/1999/xlink'; // xmlns:xlink | ||
const xmlNS = 'http://www.w3.org/XML/1998/namespace'; // xmlns | ||
// https://developer.mozilla.org/en-US/docs/Web/SVG/Element | ||
const SVG_TAGS = 'svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,' + | ||
'defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,' + | ||
'feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,' + | ||
'feDistanceLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,' + | ||
'feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,' + | ||
'fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,' + | ||
'foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,' + | ||
'mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,' + | ||
'polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,' + | ||
'text,textPath,title,tspan,unknown,use,view'; | ||
const isSVG = makeMap(SVG_TAGS.split(',')); | ||
function attributesString(attributes) { | ||
if (!attributes || attributes.length === 0) | ||
return ''; | ||
return attributes.reduce((total, { key, value }) => { | ||
return total + (value ? `${key}="${value}" ` : key); | ||
}, ''); | ||
} | ||
const DOMApis = { | ||
isText(node) { | ||
return node && node.type === 'text'; | ||
}, | ||
isNode(node) { | ||
return node && node.type === 'element'; | ||
}, | ||
isCommentNode(node) { | ||
return node && node.type === 'comment'; | ||
}, | ||
isCssLinkNode(node) { | ||
if (this.isNode(node) && node.tagName === 'link') { | ||
return !!node.attributes.find(({ key, value }) => key === 'rel' && value === 'stylesheet'); | ||
} | ||
} | ||
return false; | ||
} | ||
function transformUrl(resolvePath, curPath) { | ||
const baseUrl = new URL(resolvePath, location.href); | ||
const realPath = new URL(curPath, baseUrl.href); | ||
return realPath.href; | ||
} | ||
function findTarget(el, selectors) { | ||
for (const s of selectors) { | ||
const target = el.querySelector(s); | ||
if (target) | ||
return target; | ||
} | ||
return el; | ||
} | ||
function setDocCurrentScript(target, code, define, url, async) { | ||
if (!target) | ||
return noop; | ||
const el = document.createElement('script'); | ||
if (async) { | ||
el.setAttribute('async', 'true'); | ||
} | ||
if (url) { | ||
el.setAttribute('src', url); | ||
} | ||
else if (code) { | ||
el.textContent = code; | ||
} | ||
const set = (val) => { | ||
try { | ||
if (define) { | ||
Object.defineProperty(target, 'currentScript', { | ||
value: val, | ||
writable: true, | ||
configurable: true, | ||
}); | ||
return false; | ||
}, | ||
isIconLinkNode(node) { | ||
if (this.isNode(node) && node.tagName === 'link') { | ||
return !!node.attributes.find(({ key, value }) => key === 'rel' && value === 'icon'); | ||
} | ||
return false; | ||
}, | ||
isPrefetchJsLinkNode(node) { | ||
if (!this.isNode(node) || node.tagName !== 'link') | ||
return false; | ||
let hasRelAttr, hasAsAttr; | ||
for (const { key, value } of node.attributes) { | ||
if (key === 'rel') { | ||
hasRelAttr = true; | ||
if (value !== 'preload' && value !== 'prefetch') { | ||
return false; | ||
} | ||
} | ||
else { | ||
target.currentScript = val; | ||
else if (key === 'as') { | ||
hasAsAttr = true; | ||
if (value !== 'script') | ||
return false; | ||
} | ||
} | ||
catch (e) { | ||
{ | ||
warn(e); | ||
return Boolean(hasRelAttr && hasAsAttr); | ||
}, | ||
removeElement(el) { | ||
const parentNode = el && el.parentNode; | ||
if (parentNode) { | ||
parentNode.removeChild(el); | ||
} | ||
}, | ||
createElement(node) { | ||
const { tagName, attributes } = node; | ||
const el = isSVG(tagName) | ||
? document.createElementNS(ns, tagName) | ||
: document.createElement(tagName); | ||
this.applyAttributes(el, attributes); | ||
return el; | ||
}, | ||
createTextNode(node) { | ||
return document.createTextNode(node.content); | ||
}, | ||
createStyleNode(content) { | ||
const el = document.createElement('style'); | ||
content && (el.textContent = content); | ||
this.applyAttributes(el, [{ key: 'type', value: 'text/css' }]); | ||
return el; | ||
}, | ||
createLinkCommentNode(node) { | ||
if (this.isNode(node)) { | ||
const ps = attributesString(node.attributes); | ||
return `<link ${ps.slice(0, -1)}></link>`; | ||
} | ||
else { | ||
node = node ? `src="${node}" ` : ''; | ||
return document.createComment(`<link ${node}execute by garfish(dynamic)></link>`); | ||
} | ||
}, | ||
createScriptCommentNode(node) { | ||
if (this.isNode(node)) { | ||
const { attributes, children } = node; | ||
const ps = attributesString(attributes); | ||
const code = (children === null || children === void 0 ? void 0 : children[0]) ? children[0].content : ''; | ||
return document.createComment(`<script ${ps} execute by garfish>${code}</script>`); | ||
} | ||
else { | ||
const { src, code } = node; | ||
const url = src ? `src="${src}" ` : ''; | ||
return document.createComment(`<script ${url}execute by garfish(dynamic)>${code}</script>`); | ||
} | ||
}, | ||
applyAttributes(el, attributes) { | ||
if (!attributes || attributes.length === 0) | ||
return; | ||
for (const { key, value } of attributes) { | ||
if (value === null) { | ||
el.setAttribute(key, ''); | ||
} | ||
else if (typeof value === 'string') { | ||
if (key.charCodeAt(0) !== xChar) { | ||
el.setAttribute(key, value); | ||
} | ||
else if (key.charCodeAt(3) === colonChar) { | ||
el.setAttributeNS(xmlNS, key, value); | ||
} | ||
else if (key.charCodeAt(5) === colonChar) { | ||
el.setAttributeNS(xlinkNS, key, value); | ||
} | ||
else { | ||
el.setAttribute(key, value); | ||
} | ||
} | ||
} | ||
}; | ||
set(el); | ||
return () => set(null); | ||
} | ||
}, | ||
}; | ||
@@ -489,3 +619,3 @@ const __GARFISH_FLAG__ = Symbol.for('__GARFISH_FLAG__'); | ||
let type = ''; | ||
let subtype = ''; | ||
let subType = ''; | ||
while (idx < input.length && input[idx] !== '/') { | ||
@@ -501,11 +631,11 @@ type += input[idx]; | ||
while (idx < input.length && input[idx] !== ';') { | ||
subtype += input[idx]; | ||
subType += input[idx]; | ||
idx++; | ||
} | ||
subtype = subtype.replace(/[ \t\n\r]+$/, ''); | ||
if (subtype.length === 0) | ||
subType = subType.replace(/[ \t\n\r]+$/, ''); | ||
if (subType.length === 0) | ||
return null; | ||
return { | ||
type: type.toLocaleLowerCase(), | ||
subtype: subtype.toLocaleLowerCase(), | ||
subtype: subType.toLocaleLowerCase(), | ||
}; | ||
@@ -587,2 +717,3 @@ } | ||
exports.DOMApis = DOMApis; | ||
exports.__GARFISH_FLAG__ = __GARFISH_FLAG__; | ||
@@ -614,3 +745,2 @@ exports.assert = assert; | ||
exports.makeMap = makeMap; | ||
exports.mixins = mixins; | ||
exports.nextTick = nextTick; | ||
@@ -617,0 +747,0 @@ exports.noop = noop; |
@@ -28,7 +28,6 @@ 'use strict'; | ||
}, | ||
// 测试环境允许 delete | ||
configurable: false, | ||
}); | ||
} | ||
// 数组变为对象 `['a'] => { a: true }` | ||
// Array to Object `['a'] => { a: true }` | ||
function makeMap(list) { | ||
@@ -41,2 +40,5 @@ const map = Object.create(null); | ||
} | ||
function inBrowser() { | ||
return typeof window !== 'undefined'; | ||
} | ||
const warnPrefix = '[Garfish warning]'; | ||
@@ -71,9 +73,2 @@ const processError = (error, fn) => { | ||
} | ||
// 将字符串被设置为对象属性名时,会被尝试改造为常量化版本,避免浏览器重复产生缓存 | ||
function internFunc(internalizeString) { | ||
// 暂时不考虑Hash-collision,https://en.wikipedia.org/wiki/Collision_(computer_science)。v8貌似在16383长度时会发生hash-collision,经过测试后发现正常 | ||
const temporaryOb = {}; | ||
temporaryOb[internalizeString] = true; | ||
return Object.keys(temporaryOb)[0]; | ||
} | ||
function validURL(str) { | ||
@@ -88,5 +83,14 @@ const pattern = new RegExp('^(https?:\\/\\/)?' + // protocol | ||
} | ||
// When the string is set as the object property name, | ||
// it will be attempted to be transformed into a constant version to avoid repeated caching by the browser | ||
function internFunc(internalizeString) { | ||
// Don't consider "Hash-collision,https://en.wikipedia.org/wiki/Collision_(computer_science)" | ||
// v8貌似在 16383 长度时会发生 hash-collision,经过测试后发现正常 | ||
const temporaryOb = {}; | ||
temporaryOb[internalizeString] = true; | ||
return Object.keys(temporaryOb)[0]; | ||
} | ||
function evalWithEnv(code, params) { | ||
const keys = Object.keys(params); | ||
// 不可使用随机值,否则无法作为常量字符串复用 | ||
// No random value can be used, otherwise it cannot be reused as a constant string | ||
const randomValKey = '__garfish__exec_temporary__'; | ||
@@ -139,7 +143,2 @@ const vales = keys.map((k) => `window.${randomValKey}.${k}`); | ||
} | ||
function mixins(...list) { | ||
return function (target) { | ||
Object.assign(target.prototype, ...list); | ||
}; | ||
} | ||
// 有些测试 jest.mock 不好测,可用这个工具方法 | ||
@@ -170,27 +169,26 @@ function callTestCallback(obj, ...args) { | ||
} | ||
// 深度合并两个对象,能处理循环引用,后面的覆盖前面的,可选数组去重 | ||
// Deeply merge two objects, can handle circular references, the latter overwrite the previous | ||
function deepMerge(o, n, dp) { | ||
const lRecord = new WeakMap(); | ||
const rRecord = new WeakMap(); | ||
const vRecord = new WeakMap(); | ||
const leftRecord = new WeakMap(); | ||
const rightRecord = new WeakMap(); | ||
const valueRecord = new WeakMap(); | ||
const isArray = Array.isArray; | ||
const isAllRefs = (a, b) => { | ||
// 判断 merge 左右两边,不需要用到 vRecord | ||
if (lRecord.has(a) || rRecord.has(a)) { | ||
return lRecord.has(b) || rRecord.has(b); | ||
if (leftRecord.has(a) || rightRecord.has(a)) { | ||
return leftRecord.has(b) || rightRecord.has(b); | ||
} | ||
}; | ||
const clone = (v) => { | ||
// 深拷贝 | ||
// Deep clone | ||
if (isPrimitive(v) || typeof v === 'function') { | ||
return v; | ||
} | ||
else if (vRecord.has(v)) { | ||
return vRecord.get(v); | ||
else if (valueRecord.has(v)) { | ||
return valueRecord.get(v); | ||
} | ||
else if (lRecord.has(v)) { | ||
return lRecord.get(v); | ||
else if (leftRecord.has(v)) { | ||
return leftRecord.get(v); | ||
} | ||
else if (rRecord.has(v)) { | ||
return rRecord.get(v); | ||
else if (rightRecord.has(v)) { | ||
return rightRecord.get(v); | ||
} | ||
@@ -200,15 +198,15 @@ else if (isArray(v)) { | ||
v = unique(v); | ||
const res = []; | ||
vRecord.set(v, res); | ||
const arr = []; | ||
valueRecord.set(v, arr); | ||
for (let i = 0, len = v.length; i < len; i++) { | ||
res[i] = clone(v[i]); | ||
arr[i] = clone(v[i]); | ||
} | ||
return res; | ||
return arr; | ||
} | ||
else if (typeof v === 'object') { | ||
const res = {}; | ||
vRecord.set(v, res); | ||
const obj = {}; | ||
valueRecord.set(v, obj); | ||
const keys = Reflect.ownKeys(v); | ||
keys.forEach((key) => (res[key] = clone(v[key]))); | ||
return res; | ||
keys.forEach((key) => (obj[key] = clone(v[key]))); | ||
return obj; | ||
} | ||
@@ -230,7 +228,7 @@ }; | ||
const res = {}; | ||
const lkeys = Reflect.ownKeys(l); | ||
const rkeys = Reflect.ownKeys(r); | ||
lRecord.set(l, res); | ||
rRecord.set(r, res); | ||
lkeys.forEach((key) => { | ||
const leftKeys = Reflect.ownKeys(l); | ||
const rightKeys = Reflect.ownKeys(r); | ||
leftRecord.set(l, res); | ||
rightRecord.set(r, res); | ||
leftKeys.forEach((key) => { | ||
const lv = l[key]; | ||
@@ -245,17 +243,17 @@ const rv = r[key]; | ||
res[key] = isAllRefs(lv, rv) | ||
? lRecord.get(lv) // 左边右边同一个值,取哪个都行 | ||
? leftRecord.get(lv) // The same value on the left and right, whichever is OK | ||
: mergeObject(lv, rv); | ||
} | ||
else { | ||
res[key] = setValue(rRecord, rv); | ||
res[key] = setValue(rightRecord, rv); | ||
} | ||
} | ||
else { | ||
res[key] = setValue(lRecord, lv); | ||
res[key] = setValue(leftRecord, lv); | ||
} | ||
}); | ||
rkeys.forEach((key) => { | ||
rightKeys.forEach((key) => { | ||
if (hasOwn(res, key)) | ||
return; | ||
res[key] = setValue(rRecord, r[key]); | ||
res[key] = setValue(rightRecord, r[key]); | ||
}); | ||
@@ -266,5 +264,58 @@ return res; | ||
} | ||
function inBrowser() { | ||
return typeof window !== 'undefined'; | ||
// Scheme: https://tools.ietf.org/html/rfc3986#section-3.1 | ||
// Absolute URL: https://tools.ietf.org/html/rfc3986#section-4.3 | ||
function isAbsolute(url) { | ||
// `c:\\` 这种 case 返回 false,在浏览器中使用本地图片,应该用 file 协议 | ||
if (!/^[a-zA-Z]:\\/.test(url)) { | ||
if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(url)) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
function transformUrl(resolvePath, curPath) { | ||
const baseUrl = new URL(resolvePath, location.href); | ||
const realPath = new URL(curPath, baseUrl.href); | ||
return realPath.href; | ||
} | ||
function findTarget(el, selectors) { | ||
for (const s of selectors) { | ||
const target = el.querySelector(s); | ||
if (target) | ||
return target; | ||
} | ||
return el; | ||
} | ||
function setDocCurrentScript(target, code, define, url, async) { | ||
if (!target) | ||
return noop; | ||
const el = document.createElement('script'); | ||
if (async) { | ||
el.setAttribute('async', 'true'); | ||
} | ||
if (url) { | ||
el.setAttribute('src', url); | ||
} | ||
else if (code) { | ||
el.textContent = code; | ||
} | ||
const set = (val) => { | ||
try { | ||
if (define) { | ||
Object.defineProperty(target, 'currentScript', { | ||
value: val, | ||
writable: true, | ||
configurable: true, | ||
}); | ||
} | ||
else { | ||
target.currentScript = val; | ||
} | ||
} | ||
catch (e) { | ||
} | ||
}; | ||
set(el); | ||
return () => set(null); | ||
} | ||
@@ -415,58 +466,137 @@ // copy from https://github.com/getsentry/sentry-javascript/blob/6.4.0/packages/browser/src/tracekit.ts | ||
// Scheme: https://tools.ietf.org/html/rfc3986#section-3.1 | ||
// Absolute URL: https://tools.ietf.org/html/rfc3986#section-4.3 | ||
function isAbsolute(url) { | ||
// `c:\\` 这种 case 返回 false,在浏览器中使用本地图片,应该用 file 协议 | ||
if (!/^[a-zA-Z]:\\/.test(url)) { | ||
if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(url)) { | ||
return true; | ||
const xChar = 120; // "x" char | ||
const colonChar = 58; // ":" char | ||
const ns = 'http://www.w3.org/2000/svg'; | ||
const xlinkNS = 'http://www.w3.org/1999/xlink'; // xmlns:xlink | ||
const xmlNS = 'http://www.w3.org/XML/1998/namespace'; // xmlns | ||
// https://developer.mozilla.org/en-US/docs/Web/SVG/Element | ||
const SVG_TAGS = 'svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,' + | ||
'defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,' + | ||
'feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,' + | ||
'feDistanceLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,' + | ||
'feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,' + | ||
'fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,' + | ||
'foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,' + | ||
'mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,' + | ||
'polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,' + | ||
'text,textPath,title,tspan,unknown,use,view'; | ||
const isSVG = makeMap(SVG_TAGS.split(',')); | ||
function attributesString(attributes) { | ||
if (!attributes || attributes.length === 0) | ||
return ''; | ||
return attributes.reduce((total, { key, value }) => { | ||
return total + (value ? `${key}="${value}" ` : key); | ||
}, ''); | ||
} | ||
const DOMApis = { | ||
isText(node) { | ||
return node && node.type === 'text'; | ||
}, | ||
isNode(node) { | ||
return node && node.type === 'element'; | ||
}, | ||
isCommentNode(node) { | ||
return node && node.type === 'comment'; | ||
}, | ||
isCssLinkNode(node) { | ||
if (this.isNode(node) && node.tagName === 'link') { | ||
return !!node.attributes.find(({ key, value }) => key === 'rel' && value === 'stylesheet'); | ||
} | ||
} | ||
return false; | ||
} | ||
function transformUrl(resolvePath, curPath) { | ||
const baseUrl = new URL(resolvePath, location.href); | ||
const realPath = new URL(curPath, baseUrl.href); | ||
return realPath.href; | ||
} | ||
function findTarget(el, selectors) { | ||
for (const s of selectors) { | ||
const target = el.querySelector(s); | ||
if (target) | ||
return target; | ||
} | ||
return el; | ||
} | ||
function setDocCurrentScript(target, code, define, url, async) { | ||
if (!target) | ||
return noop; | ||
const el = document.createElement('script'); | ||
if (async) { | ||
el.setAttribute('async', 'true'); | ||
} | ||
if (url) { | ||
el.setAttribute('src', url); | ||
} | ||
else if (code) { | ||
el.textContent = code; | ||
} | ||
const set = (val) => { | ||
try { | ||
if (define) { | ||
Object.defineProperty(target, 'currentScript', { | ||
value: val, | ||
writable: true, | ||
configurable: true, | ||
}); | ||
return false; | ||
}, | ||
isIconLinkNode(node) { | ||
if (this.isNode(node) && node.tagName === 'link') { | ||
return !!node.attributes.find(({ key, value }) => key === 'rel' && value === 'icon'); | ||
} | ||
return false; | ||
}, | ||
isPrefetchJsLinkNode(node) { | ||
if (!this.isNode(node) || node.tagName !== 'link') | ||
return false; | ||
let hasRelAttr, hasAsAttr; | ||
for (const { key, value } of node.attributes) { | ||
if (key === 'rel') { | ||
hasRelAttr = true; | ||
if (value !== 'preload' && value !== 'prefetch') { | ||
return false; | ||
} | ||
} | ||
else { | ||
target.currentScript = val; | ||
else if (key === 'as') { | ||
hasAsAttr = true; | ||
if (value !== 'script') | ||
return false; | ||
} | ||
} | ||
catch (e) { | ||
return Boolean(hasRelAttr && hasAsAttr); | ||
}, | ||
removeElement(el) { | ||
const parentNode = el && el.parentNode; | ||
if (parentNode) { | ||
parentNode.removeChild(el); | ||
} | ||
}; | ||
set(el); | ||
return () => set(null); | ||
} | ||
}, | ||
createElement(node) { | ||
const { tagName, attributes } = node; | ||
const el = isSVG(tagName) | ||
? document.createElementNS(ns, tagName) | ||
: document.createElement(tagName); | ||
this.applyAttributes(el, attributes); | ||
return el; | ||
}, | ||
createTextNode(node) { | ||
return document.createTextNode(node.content); | ||
}, | ||
createStyleNode(content) { | ||
const el = document.createElement('style'); | ||
content && (el.textContent = content); | ||
this.applyAttributes(el, [{ key: 'type', value: 'text/css' }]); | ||
return el; | ||
}, | ||
createLinkCommentNode(node) { | ||
if (this.isNode(node)) { | ||
const ps = attributesString(node.attributes); | ||
return `<link ${ps.slice(0, -1)}></link>`; | ||
} | ||
else { | ||
node = node ? `src="${node}" ` : ''; | ||
return document.createComment(`<link ${node}execute by garfish(dynamic)></link>`); | ||
} | ||
}, | ||
createScriptCommentNode(node) { | ||
if (this.isNode(node)) { | ||
const { attributes, children } = node; | ||
const ps = attributesString(attributes); | ||
const code = (children === null || children === void 0 ? void 0 : children[0]) ? children[0].content : ''; | ||
return document.createComment(`<script ${ps} execute by garfish>${code}</script>`); | ||
} | ||
else { | ||
const { src, code } = node; | ||
const url = src ? `src="${src}" ` : ''; | ||
return document.createComment(`<script ${url}execute by garfish(dynamic)>${code}</script>`); | ||
} | ||
}, | ||
applyAttributes(el, attributes) { | ||
if (!attributes || attributes.length === 0) | ||
return; | ||
for (const { key, value } of attributes) { | ||
if (value === null) { | ||
el.setAttribute(key, ''); | ||
} | ||
else if (typeof value === 'string') { | ||
if (key.charCodeAt(0) !== xChar) { | ||
el.setAttribute(key, value); | ||
} | ||
else if (key.charCodeAt(3) === colonChar) { | ||
el.setAttributeNS(xmlNS, key, value); | ||
} | ||
else if (key.charCodeAt(5) === colonChar) { | ||
el.setAttributeNS(xlinkNS, key, value); | ||
} | ||
else { | ||
el.setAttribute(key, value); | ||
} | ||
} | ||
} | ||
}, | ||
}; | ||
@@ -481,3 +611,3 @@ const __GARFISH_FLAG__ = Symbol.for('__GARFISH_FLAG__'); | ||
let type = ''; | ||
let subtype = ''; | ||
let subType = ''; | ||
while (idx < input.length && input[idx] !== '/') { | ||
@@ -493,11 +623,11 @@ type += input[idx]; | ||
while (idx < input.length && input[idx] !== ';') { | ||
subtype += input[idx]; | ||
subType += input[idx]; | ||
idx++; | ||
} | ||
subtype = subtype.replace(/[ \t\n\r]+$/, ''); | ||
if (subtype.length === 0) | ||
subType = subType.replace(/[ \t\n\r]+$/, ''); | ||
if (subType.length === 0) | ||
return null; | ||
return { | ||
type: type.toLocaleLowerCase(), | ||
subtype: subtype.toLocaleLowerCase(), | ||
subtype: subType.toLocaleLowerCase(), | ||
}; | ||
@@ -579,2 +709,3 @@ } | ||
exports.DOMApis = DOMApis; | ||
exports.__GARFISH_FLAG__ = __GARFISH_FLAG__; | ||
@@ -606,3 +737,2 @@ exports.assert = assert; | ||
exports.makeMap = makeMap; | ||
exports.mixins = mixins; | ||
exports.nextTick = nextTick; | ||
@@ -609,0 +739,0 @@ exports.noop = noop; |
@@ -29,7 +29,6 @@ const objectToString = Object.prototype.toString; | ||
}, | ||
// 测试环境允许 delete | ||
configurable: true , | ||
}); | ||
} | ||
// 数组变为对象 `['a'] => { a: true }` | ||
// Array to Object `['a'] => { a: true }` | ||
function makeMap(list) { | ||
@@ -42,2 +41,5 @@ const map = Object.create(null); | ||
} | ||
function inBrowser() { | ||
return typeof window !== 'undefined'; | ||
} | ||
const warnPrefix = '[Garfish warning]'; | ||
@@ -72,9 +74,2 @@ const processError = (error, fn) => { | ||
} | ||
// 将字符串被设置为对象属性名时,会被尝试改造为常量化版本,避免浏览器重复产生缓存 | ||
function internFunc(internalizeString) { | ||
// 暂时不考虑Hash-collision,https://en.wikipedia.org/wiki/Collision_(computer_science)。v8貌似在16383长度时会发生hash-collision,经过测试后发现正常 | ||
const temporaryOb = {}; | ||
temporaryOb[internalizeString] = true; | ||
return Object.keys(temporaryOb)[0]; | ||
} | ||
function validURL(str) { | ||
@@ -89,5 +84,14 @@ const pattern = new RegExp('^(https?:\\/\\/)?' + // protocol | ||
} | ||
// When the string is set as the object property name, | ||
// it will be attempted to be transformed into a constant version to avoid repeated caching by the browser | ||
function internFunc(internalizeString) { | ||
// Don't consider "Hash-collision,https://en.wikipedia.org/wiki/Collision_(computer_science)" | ||
// v8貌似在 16383 长度时会发生 hash-collision,经过测试后发现正常 | ||
const temporaryOb = {}; | ||
temporaryOb[internalizeString] = true; | ||
return Object.keys(temporaryOb)[0]; | ||
} | ||
function evalWithEnv(code, params) { | ||
const keys = Object.keys(params); | ||
// 不可使用随机值,否则无法作为常量字符串复用 | ||
// No random value can be used, otherwise it cannot be reused as a constant string | ||
const randomValKey = '__garfish__exec_temporary__'; | ||
@@ -140,7 +144,2 @@ const vales = keys.map((k) => `window.${randomValKey}.${k}`); | ||
} | ||
function mixins(...list) { | ||
return function (target) { | ||
Object.assign(target.prototype, ...list); | ||
}; | ||
} | ||
// 有些测试 jest.mock 不好测,可用这个工具方法 | ||
@@ -171,27 +170,26 @@ function callTestCallback(obj, ...args) { | ||
} | ||
// 深度合并两个对象,能处理循环引用,后面的覆盖前面的,可选数组去重 | ||
// Deeply merge two objects, can handle circular references, the latter overwrite the previous | ||
function deepMerge(o, n, dp) { | ||
const lRecord = new WeakMap(); | ||
const rRecord = new WeakMap(); | ||
const vRecord = new WeakMap(); | ||
const leftRecord = new WeakMap(); | ||
const rightRecord = new WeakMap(); | ||
const valueRecord = new WeakMap(); | ||
const isArray = Array.isArray; | ||
const isAllRefs = (a, b) => { | ||
// 判断 merge 左右两边,不需要用到 vRecord | ||
if (lRecord.has(a) || rRecord.has(a)) { | ||
return lRecord.has(b) || rRecord.has(b); | ||
if (leftRecord.has(a) || rightRecord.has(a)) { | ||
return leftRecord.has(b) || rightRecord.has(b); | ||
} | ||
}; | ||
const clone = (v) => { | ||
// 深拷贝 | ||
// Deep clone | ||
if (isPrimitive(v) || typeof v === 'function') { | ||
return v; | ||
} | ||
else if (vRecord.has(v)) { | ||
return vRecord.get(v); | ||
else if (valueRecord.has(v)) { | ||
return valueRecord.get(v); | ||
} | ||
else if (lRecord.has(v)) { | ||
return lRecord.get(v); | ||
else if (leftRecord.has(v)) { | ||
return leftRecord.get(v); | ||
} | ||
else if (rRecord.has(v)) { | ||
return rRecord.get(v); | ||
else if (rightRecord.has(v)) { | ||
return rightRecord.get(v); | ||
} | ||
@@ -201,15 +199,15 @@ else if (isArray(v)) { | ||
v = unique(v); | ||
const res = []; | ||
vRecord.set(v, res); | ||
const arr = []; | ||
valueRecord.set(v, arr); | ||
for (let i = 0, len = v.length; i < len; i++) { | ||
res[i] = clone(v[i]); | ||
arr[i] = clone(v[i]); | ||
} | ||
return res; | ||
return arr; | ||
} | ||
else if (typeof v === 'object') { | ||
const res = {}; | ||
vRecord.set(v, res); | ||
const obj = {}; | ||
valueRecord.set(v, obj); | ||
const keys = Reflect.ownKeys(v); | ||
keys.forEach((key) => (res[key] = clone(v[key]))); | ||
return res; | ||
keys.forEach((key) => (obj[key] = clone(v[key]))); | ||
return obj; | ||
} | ||
@@ -231,7 +229,7 @@ }; | ||
const res = {}; | ||
const lkeys = Reflect.ownKeys(l); | ||
const rkeys = Reflect.ownKeys(r); | ||
lRecord.set(l, res); | ||
rRecord.set(r, res); | ||
lkeys.forEach((key) => { | ||
const leftKeys = Reflect.ownKeys(l); | ||
const rightKeys = Reflect.ownKeys(r); | ||
leftRecord.set(l, res); | ||
rightRecord.set(r, res); | ||
leftKeys.forEach((key) => { | ||
const lv = l[key]; | ||
@@ -246,17 +244,17 @@ const rv = r[key]; | ||
res[key] = isAllRefs(lv, rv) | ||
? lRecord.get(lv) // 左边右边同一个值,取哪个都行 | ||
? leftRecord.get(lv) // The same value on the left and right, whichever is OK | ||
: mergeObject(lv, rv); | ||
} | ||
else { | ||
res[key] = setValue(rRecord, rv); | ||
res[key] = setValue(rightRecord, rv); | ||
} | ||
} | ||
else { | ||
res[key] = setValue(lRecord, lv); | ||
res[key] = setValue(leftRecord, lv); | ||
} | ||
}); | ||
rkeys.forEach((key) => { | ||
rightKeys.forEach((key) => { | ||
if (hasOwn(res, key)) | ||
return; | ||
res[key] = setValue(rRecord, r[key]); | ||
res[key] = setValue(rightRecord, r[key]); | ||
}); | ||
@@ -267,5 +265,61 @@ return res; | ||
} | ||
function inBrowser() { | ||
return typeof window !== 'undefined'; | ||
// Scheme: https://tools.ietf.org/html/rfc3986#section-3.1 | ||
// Absolute URL: https://tools.ietf.org/html/rfc3986#section-4.3 | ||
function isAbsolute(url) { | ||
// `c:\\` 这种 case 返回 false,在浏览器中使用本地图片,应该用 file 协议 | ||
if (!/^[a-zA-Z]:\\/.test(url)) { | ||
if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(url)) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
function transformUrl(resolvePath, curPath) { | ||
const baseUrl = new URL(resolvePath, location.href); | ||
const realPath = new URL(curPath, baseUrl.href); | ||
return realPath.href; | ||
} | ||
function findTarget(el, selectors) { | ||
for (const s of selectors) { | ||
const target = el.querySelector(s); | ||
if (target) | ||
return target; | ||
} | ||
return el; | ||
} | ||
function setDocCurrentScript(target, code, define, url, async) { | ||
if (!target) | ||
return noop; | ||
const el = document.createElement('script'); | ||
if (async) { | ||
el.setAttribute('async', 'true'); | ||
} | ||
if (url) { | ||
el.setAttribute('src', url); | ||
} | ||
else if (code) { | ||
el.textContent = code; | ||
} | ||
const set = (val) => { | ||
try { | ||
if (define) { | ||
Object.defineProperty(target, 'currentScript', { | ||
value: val, | ||
writable: true, | ||
configurable: true, | ||
}); | ||
} | ||
else { | ||
target.currentScript = val; | ||
} | ||
} | ||
catch (e) { | ||
{ | ||
warn(e); | ||
} | ||
} | ||
}; | ||
set(el); | ||
return () => set(null); | ||
} | ||
@@ -416,61 +470,137 @@ // copy from https://github.com/getsentry/sentry-javascript/blob/6.4.0/packages/browser/src/tracekit.ts | ||
// Scheme: https://tools.ietf.org/html/rfc3986#section-3.1 | ||
// Absolute URL: https://tools.ietf.org/html/rfc3986#section-4.3 | ||
function isAbsolute(url) { | ||
// `c:\\` 这种 case 返回 false,在浏览器中使用本地图片,应该用 file 协议 | ||
if (!/^[a-zA-Z]:\\/.test(url)) { | ||
if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(url)) { | ||
return true; | ||
const xChar = 120; // "x" char | ||
const colonChar = 58; // ":" char | ||
const ns = 'http://www.w3.org/2000/svg'; | ||
const xlinkNS = 'http://www.w3.org/1999/xlink'; // xmlns:xlink | ||
const xmlNS = 'http://www.w3.org/XML/1998/namespace'; // xmlns | ||
// https://developer.mozilla.org/en-US/docs/Web/SVG/Element | ||
const SVG_TAGS = 'svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,' + | ||
'defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,' + | ||
'feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,' + | ||
'feDistanceLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,' + | ||
'feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,' + | ||
'fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,' + | ||
'foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,' + | ||
'mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,' + | ||
'polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,' + | ||
'text,textPath,title,tspan,unknown,use,view'; | ||
const isSVG = makeMap(SVG_TAGS.split(',')); | ||
function attributesString(attributes) { | ||
if (!attributes || attributes.length === 0) | ||
return ''; | ||
return attributes.reduce((total, { key, value }) => { | ||
return total + (value ? `${key}="${value}" ` : key); | ||
}, ''); | ||
} | ||
const DOMApis = { | ||
isText(node) { | ||
return node && node.type === 'text'; | ||
}, | ||
isNode(node) { | ||
return node && node.type === 'element'; | ||
}, | ||
isCommentNode(node) { | ||
return node && node.type === 'comment'; | ||
}, | ||
isCssLinkNode(node) { | ||
if (this.isNode(node) && node.tagName === 'link') { | ||
return !!node.attributes.find(({ key, value }) => key === 'rel' && value === 'stylesheet'); | ||
} | ||
} | ||
return false; | ||
} | ||
function transformUrl(resolvePath, curPath) { | ||
const baseUrl = new URL(resolvePath, location.href); | ||
const realPath = new URL(curPath, baseUrl.href); | ||
return realPath.href; | ||
} | ||
function findTarget(el, selectors) { | ||
for (const s of selectors) { | ||
const target = el.querySelector(s); | ||
if (target) | ||
return target; | ||
} | ||
return el; | ||
} | ||
function setDocCurrentScript(target, code, define, url, async) { | ||
if (!target) | ||
return noop; | ||
const el = document.createElement('script'); | ||
if (async) { | ||
el.setAttribute('async', 'true'); | ||
} | ||
if (url) { | ||
el.setAttribute('src', url); | ||
} | ||
else if (code) { | ||
el.textContent = code; | ||
} | ||
const set = (val) => { | ||
try { | ||
if (define) { | ||
Object.defineProperty(target, 'currentScript', { | ||
value: val, | ||
writable: true, | ||
configurable: true, | ||
}); | ||
return false; | ||
}, | ||
isIconLinkNode(node) { | ||
if (this.isNode(node) && node.tagName === 'link') { | ||
return !!node.attributes.find(({ key, value }) => key === 'rel' && value === 'icon'); | ||
} | ||
return false; | ||
}, | ||
isPrefetchJsLinkNode(node) { | ||
if (!this.isNode(node) || node.tagName !== 'link') | ||
return false; | ||
let hasRelAttr, hasAsAttr; | ||
for (const { key, value } of node.attributes) { | ||
if (key === 'rel') { | ||
hasRelAttr = true; | ||
if (value !== 'preload' && value !== 'prefetch') { | ||
return false; | ||
} | ||
} | ||
else { | ||
target.currentScript = val; | ||
else if (key === 'as') { | ||
hasAsAttr = true; | ||
if (value !== 'script') | ||
return false; | ||
} | ||
} | ||
catch (e) { | ||
{ | ||
warn(e); | ||
return Boolean(hasRelAttr && hasAsAttr); | ||
}, | ||
removeElement(el) { | ||
const parentNode = el && el.parentNode; | ||
if (parentNode) { | ||
parentNode.removeChild(el); | ||
} | ||
}, | ||
createElement(node) { | ||
const { tagName, attributes } = node; | ||
const el = isSVG(tagName) | ||
? document.createElementNS(ns, tagName) | ||
: document.createElement(tagName); | ||
this.applyAttributes(el, attributes); | ||
return el; | ||
}, | ||
createTextNode(node) { | ||
return document.createTextNode(node.content); | ||
}, | ||
createStyleNode(content) { | ||
const el = document.createElement('style'); | ||
content && (el.textContent = content); | ||
this.applyAttributes(el, [{ key: 'type', value: 'text/css' }]); | ||
return el; | ||
}, | ||
createLinkCommentNode(node) { | ||
if (this.isNode(node)) { | ||
const ps = attributesString(node.attributes); | ||
return `<link ${ps.slice(0, -1)}></link>`; | ||
} | ||
else { | ||
node = node ? `src="${node}" ` : ''; | ||
return document.createComment(`<link ${node}execute by garfish(dynamic)></link>`); | ||
} | ||
}, | ||
createScriptCommentNode(node) { | ||
if (this.isNode(node)) { | ||
const { attributes, children } = node; | ||
const ps = attributesString(attributes); | ||
const code = (children === null || children === void 0 ? void 0 : children[0]) ? children[0].content : ''; | ||
return document.createComment(`<script ${ps} execute by garfish>${code}</script>`); | ||
} | ||
else { | ||
const { src, code } = node; | ||
const url = src ? `src="${src}" ` : ''; | ||
return document.createComment(`<script ${url}execute by garfish(dynamic)>${code}</script>`); | ||
} | ||
}, | ||
applyAttributes(el, attributes) { | ||
if (!attributes || attributes.length === 0) | ||
return; | ||
for (const { key, value } of attributes) { | ||
if (value === null) { | ||
el.setAttribute(key, ''); | ||
} | ||
else if (typeof value === 'string') { | ||
if (key.charCodeAt(0) !== xChar) { | ||
el.setAttribute(key, value); | ||
} | ||
else if (key.charCodeAt(3) === colonChar) { | ||
el.setAttributeNS(xmlNS, key, value); | ||
} | ||
else if (key.charCodeAt(5) === colonChar) { | ||
el.setAttributeNS(xlinkNS, key, value); | ||
} | ||
else { | ||
el.setAttribute(key, value); | ||
} | ||
} | ||
} | ||
}; | ||
set(el); | ||
return () => set(null); | ||
} | ||
}, | ||
}; | ||
@@ -485,3 +615,3 @@ const __GARFISH_FLAG__ = Symbol.for('__GARFISH_FLAG__'); | ||
let type = ''; | ||
let subtype = ''; | ||
let subType = ''; | ||
while (idx < input.length && input[idx] !== '/') { | ||
@@ -497,11 +627,11 @@ type += input[idx]; | ||
while (idx < input.length && input[idx] !== ';') { | ||
subtype += input[idx]; | ||
subType += input[idx]; | ||
idx++; | ||
} | ||
subtype = subtype.replace(/[ \t\n\r]+$/, ''); | ||
if (subtype.length === 0) | ||
subType = subType.replace(/[ \t\n\r]+$/, ''); | ||
if (subType.length === 0) | ||
return null; | ||
return { | ||
type: type.toLocaleLowerCase(), | ||
subtype: subtype.toLocaleLowerCase(), | ||
subtype: subType.toLocaleLowerCase(), | ||
}; | ||
@@ -583,2 +713,2 @@ } | ||
export { __GARFISH_FLAG__, assert, callTestCallback, computeErrorUrl, computeStackTraceFromStackProp, createAppContainer, createKey, deepMerge, def, error, evalWithEnv, filterAndWrapEventListener, findTarget, getRenderNode, hasOwn, inBrowser, internFunc, isAbsolute, isCss, isHtml, isJs, isObject, isPlainObject, isPrimitive, isPromise, makeMap, mixins, nextTick, noop, objectToString, parseContentType, remove, setDocCurrentScript, sourceListTags, sourceNode, toBoolean, transformUrl, unique, validURL, warn }; | ||
export { DOMApis, __GARFISH_FLAG__, assert, callTestCallback, computeErrorUrl, computeStackTraceFromStackProp, createAppContainer, createKey, deepMerge, def, error, evalWithEnv, filterAndWrapEventListener, findTarget, getRenderNode, hasOwn, inBrowser, internFunc, isAbsolute, isCss, isHtml, isJs, isObject, isPlainObject, isPrimitive, isPromise, makeMap, nextTick, noop, objectToString, parseContentType, remove, setDocCurrentScript, sourceListTags, sourceNode, toBoolean, transformUrl, unique, validURL, warn }; |
@@ -35,7 +35,6 @@ (function (global, factory) { | ||
}, | ||
// 测试环境允许 delete | ||
configurable: true , | ||
}); | ||
} | ||
// 数组变为对象 `['a'] => { a: true }` | ||
// Array to Object `['a'] => { a: true }` | ||
function makeMap(list) { | ||
@@ -48,2 +47,5 @@ const map = Object.create(null); | ||
} | ||
function inBrowser() { | ||
return typeof window !== 'undefined'; | ||
} | ||
const warnPrefix = '[Garfish warning]'; | ||
@@ -78,9 +80,2 @@ const processError = (error, fn) => { | ||
} | ||
// 将字符串被设置为对象属性名时,会被尝试改造为常量化版本,避免浏览器重复产生缓存 | ||
function internFunc(internalizeString) { | ||
// 暂时不考虑Hash-collision,https://en.wikipedia.org/wiki/Collision_(computer_science)。v8貌似在16383长度时会发生hash-collision,经过测试后发现正常 | ||
const temporaryOb = {}; | ||
temporaryOb[internalizeString] = true; | ||
return Object.keys(temporaryOb)[0]; | ||
} | ||
function validURL(str) { | ||
@@ -95,5 +90,14 @@ const pattern = new RegExp('^(https?:\\/\\/)?' + // protocol | ||
} | ||
// When the string is set as the object property name, | ||
// it will be attempted to be transformed into a constant version to avoid repeated caching by the browser | ||
function internFunc(internalizeString) { | ||
// Don't consider "Hash-collision,https://en.wikipedia.org/wiki/Collision_(computer_science)" | ||
// v8貌似在 16383 长度时会发生 hash-collision,经过测试后发现正常 | ||
const temporaryOb = {}; | ||
temporaryOb[internalizeString] = true; | ||
return Object.keys(temporaryOb)[0]; | ||
} | ||
function evalWithEnv(code, params) { | ||
const keys = Object.keys(params); | ||
// 不可使用随机值,否则无法作为常量字符串复用 | ||
// No random value can be used, otherwise it cannot be reused as a constant string | ||
const randomValKey = '__garfish__exec_temporary__'; | ||
@@ -146,7 +150,2 @@ const vales = keys.map((k) => `window.${randomValKey}.${k}`); | ||
} | ||
function mixins(...list) { | ||
return function (target) { | ||
Object.assign(target.prototype, ...list); | ||
}; | ||
} | ||
// 有些测试 jest.mock 不好测,可用这个工具方法 | ||
@@ -177,27 +176,26 @@ function callTestCallback(obj, ...args) { | ||
} | ||
// 深度合并两个对象,能处理循环引用,后面的覆盖前面的,可选数组去重 | ||
// Deeply merge two objects, can handle circular references, the latter overwrite the previous | ||
function deepMerge(o, n, dp) { | ||
const lRecord = new WeakMap(); | ||
const rRecord = new WeakMap(); | ||
const vRecord = new WeakMap(); | ||
const leftRecord = new WeakMap(); | ||
const rightRecord = new WeakMap(); | ||
const valueRecord = new WeakMap(); | ||
const isArray = Array.isArray; | ||
const isAllRefs = (a, b) => { | ||
// 判断 merge 左右两边,不需要用到 vRecord | ||
if (lRecord.has(a) || rRecord.has(a)) { | ||
return lRecord.has(b) || rRecord.has(b); | ||
if (leftRecord.has(a) || rightRecord.has(a)) { | ||
return leftRecord.has(b) || rightRecord.has(b); | ||
} | ||
}; | ||
const clone = (v) => { | ||
// 深拷贝 | ||
// Deep clone | ||
if (isPrimitive(v) || typeof v === 'function') { | ||
return v; | ||
} | ||
else if (vRecord.has(v)) { | ||
return vRecord.get(v); | ||
else if (valueRecord.has(v)) { | ||
return valueRecord.get(v); | ||
} | ||
else if (lRecord.has(v)) { | ||
return lRecord.get(v); | ||
else if (leftRecord.has(v)) { | ||
return leftRecord.get(v); | ||
} | ||
else if (rRecord.has(v)) { | ||
return rRecord.get(v); | ||
else if (rightRecord.has(v)) { | ||
return rightRecord.get(v); | ||
} | ||
@@ -207,15 +205,15 @@ else if (isArray(v)) { | ||
v = unique(v); | ||
const res = []; | ||
vRecord.set(v, res); | ||
const arr = []; | ||
valueRecord.set(v, arr); | ||
for (let i = 0, len = v.length; i < len; i++) { | ||
res[i] = clone(v[i]); | ||
arr[i] = clone(v[i]); | ||
} | ||
return res; | ||
return arr; | ||
} | ||
else if (typeof v === 'object') { | ||
const res = {}; | ||
vRecord.set(v, res); | ||
const obj = {}; | ||
valueRecord.set(v, obj); | ||
const keys = Reflect.ownKeys(v); | ||
keys.forEach((key) => (res[key] = clone(v[key]))); | ||
return res; | ||
keys.forEach((key) => (obj[key] = clone(v[key]))); | ||
return obj; | ||
} | ||
@@ -237,7 +235,7 @@ }; | ||
const res = {}; | ||
const lkeys = Reflect.ownKeys(l); | ||
const rkeys = Reflect.ownKeys(r); | ||
lRecord.set(l, res); | ||
rRecord.set(r, res); | ||
lkeys.forEach((key) => { | ||
const leftKeys = Reflect.ownKeys(l); | ||
const rightKeys = Reflect.ownKeys(r); | ||
leftRecord.set(l, res); | ||
rightRecord.set(r, res); | ||
leftKeys.forEach((key) => { | ||
const lv = l[key]; | ||
@@ -252,17 +250,17 @@ const rv = r[key]; | ||
res[key] = isAllRefs(lv, rv) | ||
? lRecord.get(lv) // 左边右边同一个值,取哪个都行 | ||
? leftRecord.get(lv) // The same value on the left and right, whichever is OK | ||
: mergeObject(lv, rv); | ||
} | ||
else { | ||
res[key] = setValue(rRecord, rv); | ||
res[key] = setValue(rightRecord, rv); | ||
} | ||
} | ||
else { | ||
res[key] = setValue(lRecord, lv); | ||
res[key] = setValue(leftRecord, lv); | ||
} | ||
}); | ||
rkeys.forEach((key) => { | ||
rightKeys.forEach((key) => { | ||
if (hasOwn(res, key)) | ||
return; | ||
res[key] = setValue(rRecord, r[key]); | ||
res[key] = setValue(rightRecord, r[key]); | ||
}); | ||
@@ -273,5 +271,61 @@ return res; | ||
} | ||
function inBrowser() { | ||
return typeof window !== 'undefined'; | ||
// Scheme: https://tools.ietf.org/html/rfc3986#section-3.1 | ||
// Absolute URL: https://tools.ietf.org/html/rfc3986#section-4.3 | ||
function isAbsolute(url) { | ||
// `c:\\` 这种 case 返回 false,在浏览器中使用本地图片,应该用 file 协议 | ||
if (!/^[a-zA-Z]:\\/.test(url)) { | ||
if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(url)) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
function transformUrl(resolvePath, curPath) { | ||
const baseUrl = new URL(resolvePath, location.href); | ||
const realPath = new URL(curPath, baseUrl.href); | ||
return realPath.href; | ||
} | ||
function findTarget(el, selectors) { | ||
for (const s of selectors) { | ||
const target = el.querySelector(s); | ||
if (target) | ||
return target; | ||
} | ||
return el; | ||
} | ||
function setDocCurrentScript(target, code, define, url, async) { | ||
if (!target) | ||
return noop; | ||
const el = document.createElement('script'); | ||
if (async) { | ||
el.setAttribute('async', 'true'); | ||
} | ||
if (url) { | ||
el.setAttribute('src', url); | ||
} | ||
else if (code) { | ||
el.textContent = code; | ||
} | ||
const set = (val) => { | ||
try { | ||
if (define) { | ||
Object.defineProperty(target, 'currentScript', { | ||
value: val, | ||
writable: true, | ||
configurable: true, | ||
}); | ||
} | ||
else { | ||
target.currentScript = val; | ||
} | ||
} | ||
catch (e) { | ||
{ | ||
warn(e); | ||
} | ||
} | ||
}; | ||
set(el); | ||
return () => set(null); | ||
} | ||
@@ -422,61 +476,137 @@ // copy from https://github.com/getsentry/sentry-javascript/blob/6.4.0/packages/browser/src/tracekit.ts | ||
// Scheme: https://tools.ietf.org/html/rfc3986#section-3.1 | ||
// Absolute URL: https://tools.ietf.org/html/rfc3986#section-4.3 | ||
function isAbsolute(url) { | ||
// `c:\\` 这种 case 返回 false,在浏览器中使用本地图片,应该用 file 协议 | ||
if (!/^[a-zA-Z]:\\/.test(url)) { | ||
if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(url)) { | ||
return true; | ||
const xChar = 120; // "x" char | ||
const colonChar = 58; // ":" char | ||
const ns = 'http://www.w3.org/2000/svg'; | ||
const xlinkNS = 'http://www.w3.org/1999/xlink'; // xmlns:xlink | ||
const xmlNS = 'http://www.w3.org/XML/1998/namespace'; // xmlns | ||
// https://developer.mozilla.org/en-US/docs/Web/SVG/Element | ||
const SVG_TAGS = 'svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,' + | ||
'defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,' + | ||
'feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,' + | ||
'feDistanceLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,' + | ||
'feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,' + | ||
'fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,' + | ||
'foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,' + | ||
'mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,' + | ||
'polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,' + | ||
'text,textPath,title,tspan,unknown,use,view'; | ||
const isSVG = makeMap(SVG_TAGS.split(',')); | ||
function attributesString(attributes) { | ||
if (!attributes || attributes.length === 0) | ||
return ''; | ||
return attributes.reduce((total, { key, value }) => { | ||
return total + (value ? `${key}="${value}" ` : key); | ||
}, ''); | ||
} | ||
const DOMApis = { | ||
isText(node) { | ||
return node && node.type === 'text'; | ||
}, | ||
isNode(node) { | ||
return node && node.type === 'element'; | ||
}, | ||
isCommentNode(node) { | ||
return node && node.type === 'comment'; | ||
}, | ||
isCssLinkNode(node) { | ||
if (this.isNode(node) && node.tagName === 'link') { | ||
return !!node.attributes.find(({ key, value }) => key === 'rel' && value === 'stylesheet'); | ||
} | ||
} | ||
return false; | ||
} | ||
function transformUrl(resolvePath, curPath) { | ||
const baseUrl = new URL(resolvePath, location.href); | ||
const realPath = new URL(curPath, baseUrl.href); | ||
return realPath.href; | ||
} | ||
function findTarget(el, selectors) { | ||
for (const s of selectors) { | ||
const target = el.querySelector(s); | ||
if (target) | ||
return target; | ||
} | ||
return el; | ||
} | ||
function setDocCurrentScript(target, code, define, url, async) { | ||
if (!target) | ||
return noop; | ||
const el = document.createElement('script'); | ||
if (async) { | ||
el.setAttribute('async', 'true'); | ||
} | ||
if (url) { | ||
el.setAttribute('src', url); | ||
} | ||
else if (code) { | ||
el.textContent = code; | ||
} | ||
const set = (val) => { | ||
try { | ||
if (define) { | ||
Object.defineProperty(target, 'currentScript', { | ||
value: val, | ||
writable: true, | ||
configurable: true, | ||
}); | ||
return false; | ||
}, | ||
isIconLinkNode(node) { | ||
if (this.isNode(node) && node.tagName === 'link') { | ||
return !!node.attributes.find(({ key, value }) => key === 'rel' && value === 'icon'); | ||
} | ||
return false; | ||
}, | ||
isPrefetchJsLinkNode(node) { | ||
if (!this.isNode(node) || node.tagName !== 'link') | ||
return false; | ||
let hasRelAttr, hasAsAttr; | ||
for (const { key, value } of node.attributes) { | ||
if (key === 'rel') { | ||
hasRelAttr = true; | ||
if (value !== 'preload' && value !== 'prefetch') { | ||
return false; | ||
} | ||
} | ||
else { | ||
target.currentScript = val; | ||
else if (key === 'as') { | ||
hasAsAttr = true; | ||
if (value !== 'script') | ||
return false; | ||
} | ||
} | ||
catch (e) { | ||
{ | ||
warn(e); | ||
return Boolean(hasRelAttr && hasAsAttr); | ||
}, | ||
removeElement(el) { | ||
const parentNode = el && el.parentNode; | ||
if (parentNode) { | ||
parentNode.removeChild(el); | ||
} | ||
}, | ||
createElement(node) { | ||
const { tagName, attributes } = node; | ||
const el = isSVG(tagName) | ||
? document.createElementNS(ns, tagName) | ||
: document.createElement(tagName); | ||
this.applyAttributes(el, attributes); | ||
return el; | ||
}, | ||
createTextNode(node) { | ||
return document.createTextNode(node.content); | ||
}, | ||
createStyleNode(content) { | ||
const el = document.createElement('style'); | ||
content && (el.textContent = content); | ||
this.applyAttributes(el, [{ key: 'type', value: 'text/css' }]); | ||
return el; | ||
}, | ||
createLinkCommentNode(node) { | ||
if (this.isNode(node)) { | ||
const ps = attributesString(node.attributes); | ||
return `<link ${ps.slice(0, -1)}></link>`; | ||
} | ||
else { | ||
node = node ? `src="${node}" ` : ''; | ||
return document.createComment(`<link ${node}execute by garfish(dynamic)></link>`); | ||
} | ||
}, | ||
createScriptCommentNode(node) { | ||
if (this.isNode(node)) { | ||
const { attributes, children } = node; | ||
const ps = attributesString(attributes); | ||
const code = (children === null || children === void 0 ? void 0 : children[0]) ? children[0].content : ''; | ||
return document.createComment(`<script ${ps} execute by garfish>${code}</script>`); | ||
} | ||
else { | ||
const { src, code } = node; | ||
const url = src ? `src="${src}" ` : ''; | ||
return document.createComment(`<script ${url}execute by garfish(dynamic)>${code}</script>`); | ||
} | ||
}, | ||
applyAttributes(el, attributes) { | ||
if (!attributes || attributes.length === 0) | ||
return; | ||
for (const { key, value } of attributes) { | ||
if (value === null) { | ||
el.setAttribute(key, ''); | ||
} | ||
else if (typeof value === 'string') { | ||
if (key.charCodeAt(0) !== xChar) { | ||
el.setAttribute(key, value); | ||
} | ||
else if (key.charCodeAt(3) === colonChar) { | ||
el.setAttributeNS(xmlNS, key, value); | ||
} | ||
else if (key.charCodeAt(5) === colonChar) { | ||
el.setAttributeNS(xlinkNS, key, value); | ||
} | ||
else { | ||
el.setAttribute(key, value); | ||
} | ||
} | ||
} | ||
}; | ||
set(el); | ||
return () => set(null); | ||
} | ||
}, | ||
}; | ||
@@ -491,3 +621,3 @@ const __GARFISH_FLAG__ = Symbol.for('__GARFISH_FLAG__'); | ||
let type = ''; | ||
let subtype = ''; | ||
let subType = ''; | ||
while (idx < input.length && input[idx] !== '/') { | ||
@@ -503,11 +633,11 @@ type += input[idx]; | ||
while (idx < input.length && input[idx] !== ';') { | ||
subtype += input[idx]; | ||
subType += input[idx]; | ||
idx++; | ||
} | ||
subtype = subtype.replace(/[ \t\n\r]+$/, ''); | ||
if (subtype.length === 0) | ||
subType = subType.replace(/[ \t\n\r]+$/, ''); | ||
if (subType.length === 0) | ||
return null; | ||
return { | ||
type: type.toLocaleLowerCase(), | ||
subtype: subtype.toLocaleLowerCase(), | ||
subtype: subType.toLocaleLowerCase(), | ||
}; | ||
@@ -589,2 +719,3 @@ } | ||
exports.DOMApis = DOMApis; | ||
exports.__GARFISH_FLAG__ = __GARFISH_FLAG__; | ||
@@ -616,3 +747,2 @@ exports.assert = assert; | ||
exports.makeMap = makeMap; | ||
exports.mixins = mixins; | ||
exports.nextTick = nextTick; | ||
@@ -619,0 +749,0 @@ exports.noop = noop; |
{ | ||
"name": "@garfish/utils", | ||
"version": "0.0.9", | ||
"version": "0.0.12", | ||
"description": "utils module.", | ||
@@ -40,3 +40,3 @@ "keywords": [ | ||
}, | ||
"gitHead": "e54d52d3757bb6d2fa95eb9d688e737b4e9fb25f" | ||
"gitHead": "306ff92ee38c5ca5b01c60b91453619f885620fe" | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
192413
18
4218