Skip to content

Commit 93df146

Browse files
committed
refactor: Modernise polyfills with Object.hasOwn and ES2024 features
- Implement `Object.hasOwn` polyfill and use it for internal property checks. - Add [`Promise.withResolvers`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers) (ES2024) to the standard library. - Optimise `prepend`, `replaceWith`, and `toggleAttribute` for performance and spec compliance. - Set polyfilled methods to non-enumerable to match native behavior. - Introduce robust `createTextNode` and `createDocumentFragment` utility helpers. - Improve performance by replacing `Array.slice` on arguments with manual loops. (AI generated commit message)
1 parent d8bb199 commit 93df146

File tree

1 file changed

+114
-43
lines changed

1 file changed

+114
-43
lines changed

src/lib/polyfill.js

Lines changed: 114 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,62 @@
11
(function () {
2+
// polyfill for Object.hasOwn
3+
4+
(function () {
5+
var oldHasOwn = Function.prototype.call.bind(
6+
Object.prototype.hasOwnProperty,
7+
);
8+
if (oldHasOwn(Object, "hasOwn")) return;
9+
Object.defineProperty(Object, "hasOwn", {
10+
configurable: true,
11+
enumerable: false,
12+
writable: true,
13+
value: function hasOwn(obj, prop) {
14+
return oldHasOwn(obj, prop);
15+
},
16+
});
17+
Object.hasOwn.prototype = null;
18+
})();
19+
220
// polyfill for prepend
321

422
(function (arr) {
523
arr.forEach(function (item) {
6-
if (item.hasOwnProperty("prepend")) {
7-
return;
8-
}
24+
if (Object.hasOwn(item, "prepend")) return;
925
Object.defineProperty(item, "prepend", {
1026
configurable: true,
11-
enumerable: true,
27+
enumerable: false,
1228
writable: true,
1329
value: function prepend() {
14-
var argArr = Array.prototype.slice.call(arguments),
15-
docFrag = document.createDocumentFragment();
30+
var docFrag = createDocumentFragment();
1631

17-
argArr.forEach(function (argItem) {
18-
var node =
19-
argItem instanceof Node
20-
? argItem
21-
: document.createTextNode(String(argItem));
22-
docFrag.appendChild(node);
23-
});
32+
var argLength = arguments.length;
33+
for (var i = 0; i < argLength; i++) {
34+
var argItem = arguments[i];
35+
docFrag.appendChild(
36+
argItem instanceof Node ? argItem : createTextNode(argItem + ""),
37+
);
38+
}
2439

2540
this.insertBefore(docFrag, this.firstChild);
2641
},
2742
});
43+
item.prepend.prototype = null;
2844
});
2945
})([Element.prototype, Document.prototype, DocumentFragment.prototype]);
3046

3147
// polyfill for closest
3248

3349
(function (arr) {
3450
arr.forEach(function (item) {
35-
if (item.hasOwnProperty("closest")) {
36-
return;
37-
}
51+
if (Object.hasOwn(item, "closest")) return;
3852
Object.defineProperty(item, "closest", {
3953
configurable: true,
40-
enumerable: true,
54+
enumerable: false,
4155
writable: true,
4256
value: function closest(s) {
43-
var matches = (this.document || this.ownerDocument).querySelectorAll(s),
57+
var matches = (this.document || this.ownerDocument).querySelectorAll(
58+
s,
59+
),
4460
i,
4561
el = this;
4662
do {
@@ -50,68 +66,78 @@
5066
return el;
5167
},
5268
});
69+
item.closest.prototype = null;
5370
});
5471
})([Element.prototype]);
5572

5673
// polyfill for replaceWith
5774

5875
(function (arr) {
5976
arr.forEach(function (item) {
60-
if (item.hasOwnProperty("replaceWith")) {
61-
return;
62-
}
77+
if (Object.hasOwn(item, "replaceWith")) return;
6378
Object.defineProperty(item, "replaceWith", {
6479
configurable: true,
65-
enumerable: true,
80+
enumerable: false,
6681
writable: true,
6782
value: function replaceWith() {
68-
var parent = this.parentNode,
69-
i = arguments.length,
70-
currentNode;
83+
var parent = this.parentNode;
7184
if (!parent) return;
72-
if (!i)
73-
// if there are no arguments
74-
parent.removeChild(this);
75-
while (i--) {
76-
// i-- decrements i and returns the value of i before the decrement
77-
currentNode = arguments[i];
85+
var viableNextSibling = this.nextSibling;
86+
var argLength = arguments.length;
87+
while (viableNextSibling) {
88+
var inArgs = false;
89+
for (var j = 0; j < argLength; j++) {
90+
if (arguments[j] === viableNextSibling) { inArgs = true; break; }
91+
}
92+
if (!inArgs) break;
93+
viableNextSibling = viableNextSibling.nextSibling;
94+
}
95+
var docFrag = createDocumentFragment();
96+
for (var i = 0; i < argLength; i++) {
97+
var currentNode = arguments[i];
7898
if (typeof currentNode !== "object") {
79-
currentNode = this.ownerDocument.createTextNode(currentNode);
99+
currentNode = createTextNode(currentNode, this.ownerDocument);
80100
} else if (currentNode.parentNode) {
81101
currentNode.parentNode.removeChild(currentNode);
82102
}
83-
// the value of "i" below is after the decrement
84-
if (!i)
85-
// if currentNode is the first argument (currentNode === arguments[0])
86-
parent.replaceChild(currentNode, this);
87-
// if currentNode isn't the first
88-
else parent.insertBefore(this.previousSibling, currentNode);
103+
docFrag.appendChild(currentNode);
104+
}
105+
if (argLength >= 1) {
106+
if (viableNextSibling) {
107+
parent.insertBefore(docFrag, viableNextSibling);
108+
} else {
109+
parent.appendChild(docFrag);
110+
}
111+
} else {
112+
parent.removeChild(this);
89113
}
90114
},
91115
});
116+
item.replaceWith.prototype = null;
92117
});
93118
})([Element.prototype, CharacterData.prototype, DocumentType.prototype]);
94119

95120
// polyfill for toggleAttribute
96121

97122
(function (arr) {
98123
arr.forEach(function (item) {
99-
if (item.hasOwnProperty("toggleAttribute")) {
100-
return;
101-
}
124+
if (Object.hasOwn(item, "toggleAttribute")) return;
102125
Object.defineProperty(item, "toggleAttribute", {
103126
configurable: true,
104-
enumerable: true,
127+
enumerable: false,
105128
writable: true,
106129
value: function toggleAttribute() {
107130
var attr = arguments[0];
108131
if (this.hasAttribute(attr)) {
109132
this.removeAttribute(attr);
133+
return false;
110134
} else {
111-
this.setAttribute(attr, arguments[1] || "");
135+
this.setAttribute(attr, arguments.length >= 2 ? arguments[1] : "");
136+
return true;
112137
}
113138
},
114139
});
140+
item.toggleAttribute.prototype = null;
115141
});
116142
})([Element.prototype]);
117143

@@ -141,4 +167,49 @@
141167
};
142168
}
143169
})();
170+
171+
// polyfill for Promise.withResolvers
172+
173+
if (!Object.hasOwn(Promise, "withResolvers")) {
174+
Object.defineProperty(Promise, "withResolvers", {
175+
configurable: true,
176+
enumerable: false,
177+
writable: true,
178+
value: function withResolvers() {
179+
var resolve, reject;
180+
var promise = new this(function (_resolve, _reject) {
181+
resolve = _resolve;
182+
reject = _reject;
183+
});
184+
if (typeof resolve !== "function" || typeof reject !== "function") {
185+
throw new TypeError(
186+
"Promise resolve or reject function is not callable",
187+
);
188+
}
189+
return {
190+
promise: promise,
191+
resolve: resolve,
192+
reject: reject,
193+
};
194+
},
195+
});
196+
Promise.withResolvers.prototype = null;
197+
}
198+
199+
// utils
200+
201+
function createTextNode(text, doc) {
202+
if (doc === undefined) doc = document;
203+
if (doc !== document) try {
204+
return new Text(text);
205+
} catch (_) {}
206+
return doc.createTextNode(text);
207+
}
208+
function createDocumentFragment() {
209+
try {
210+
return new DocumentFragment();
211+
} catch (_) {
212+
return document.createDocumentFragment();
213+
}
214+
}
144215
})();

0 commit comments

Comments
 (0)