diff --git a/_src/lib/es6-proxy-polyfill.js b/_src/lib/es6-proxy-polyfill.js new file mode 100644 index 0000000..57c45bb --- /dev/null +++ b/_src/lib/es6-proxy-polyfill.js @@ -0,0 +1,495 @@ +// Constant variables +var UNDEFINED; // => undefined +var PROXY_TARGET = "[[ProxyTarget]]"; +var PROXY_HANDLER = "[[ProxyHandler]]"; +var GET = "[[Get]]"; +var SET = "[[Set]]"; +var CALL = "[[Call]]"; +var CONSTRUCT = "[[Construct]]"; +var PROTOTYPE = "__proto__"; +var PROXY_FLAG = "__PROXY__"; +var REVOKED_FLAG = "REVOKED"; + +var defineProperty = Object.defineProperty; +var defineProperties = Object.defineProperties; +var getPrototypeOf = Object.getPrototypeOf; +var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; +var supportES5 = defineProperties ? isNativeFn(defineProperties) : false; + +/** + * Return the prototype of proxy object + * @param {object} obj + * @returns {object} + */ +var getProxyProto = supportES5 + ? Object[PROTOTYPE] + ? getPrototypeOf + : function (obj) { + return typeof obj === "function" + ? obj[PROTOTYPE] || {} + : getPrototypeOf(obj); + } + : function (obj) { + return (_isVbObject(obj) && _getVbInternalOf(obj)[PROTOTYPE]) || {}; + }; + +/** + * Check if `value` is a pristine native function + * @param {any} value + * @returns {boolean} + */ +function isNativeFn(value) { + return ( + typeof value === "function" && /\[native code\]/.test(value.toString()) + ); +} + +/** + * The Proxy constructor + * @constructor + * @param {object} target + * @param {object} handler + */ +function ProxyPolyfill(target, handler) { + if (this instanceof ProxyPolyfill) { + return createProxy(new Internal(target, handler)); + } else { + throwTypeError("Constructor Proxy requires 'new'"); + } +} + +/** + * Create a revocable Proxy object + * @param {object} target + * @param {object} handler + * @returns {{proxy: object, revoke: function}} + */ +ProxyPolyfill.revocable = function (target, handler) { + if (this instanceof ProxyPolyfill.revocable) { + throwTypeError("Proxy.revocable is not a constructor"); + } + var internal = new Internal(target, handler); + var proxy = createProxy(internal); + return { + proxy: proxy, + revoke: function () { + internal[PROXY_TARGET] = UNDEFINED; + internal[PROXY_HANDLER] = UNDEFINED; + if (!supportES5) { + getProxyProto(proxy)[PROXY_FLAG] = REVOKED_FLAG; + } + }, + }; +}; + +/** + * Create a Proxy object + * @param {Internal} internal + * @returns {object} + */ +function createProxy(internal) { + var target = internal[PROXY_TARGET]; + var proxy; + if (typeof target === "function") { + proxy = proxyFunction(internal); + } else if (target instanceof Array) { + proxy = proxyArray(internal); + } else { + proxy = proxyObject(internal); + } + return proxy; +} + +/** + * Internal data + * @constructor + * @param {object} target + * @param {object} handler + */ +function Internal(target, handler) { + if (!isObject(target) || !isObject(handler)) { + throwTypeError( + "Cannot create proxy with a non-object as target or handler", + ); + } + if ( + (getProxyProto(target) && getProxyProto(target)[PROXY_FLAG]) || + (getProxyProto(handler) && getProxyProto(handler)[PROXY_FLAG]) === + REVOKED_FLAG + ) { + throwTypeError( + "Cannot create proxy with a revoked proxy as target or handler", + ); + } + this[PROXY_TARGET] = target; + this[PROXY_HANDLER] = handler; +} + +/** + * The implementation of internal method [[Get]] + * @param {string} property + * @param {object} receiver + * @returns {any} + */ +Internal.prototype[GET] = function (property, receiver) { + var handler = this[PROXY_HANDLER]; + validateProxyHanler(handler, "get"); + if (handler.get == UNDEFINED) { + return this[PROXY_TARGET][property]; + } + if (typeof handler.get === "function") { + return handler.get(this[PROXY_TARGET], property, receiver); + } + throwTypeError("Trap 'get' is not a function: " + handler.get); +}; + +/** + * The implementation of internal method [[Set]] + * @param {string} property + * @param {any} value + * @param {object} receiver + */ +Internal.prototype[SET] = function (property, value, receiver) { + var handler = this[PROXY_HANDLER]; + validateProxyHanler(handler, "set"); + if (handler.set == UNDEFINED) { + this[PROXY_TARGET][property] = value; + } else if (typeof handler.set === "function") { + var result = handler.set(this[PROXY_TARGET], property, value, receiver); + if (!result) { + // If the set() method returns false in strict-mode code, a TypeError will be thrown. + // throwTypeError("Trap 'set' returned false for property '" + property + "'"); + console.warn("Trap 'set' returned false for property '" + property + "'"); + } + return Boolean(result); + } else { + throwTypeError("Trap 'set' is not a function: " + handler.set); + } +}; + +/** + * The implementation of internal method [[Call]] + * @param {object} thisArg + * @param {any[]} argList + * @returns {any} + */ +Internal.prototype[CALL] = function (thisArg, argList) { + var handler = this[PROXY_HANDLER]; + validateProxyHanler(handler, "apply"); + if (handler.apply == UNDEFINED) { + return this[PROXY_TARGET].apply(thisArg, argList); + } + if (typeof handler.apply === "function") { + return handler.apply(this[PROXY_TARGET], thisArg, argList); + } + throwTypeError("Trap 'apply' is not a function: " + handler.apply); +}; + +/** + * The implementation of internal method [[Construct]] + * @param {any[]} argList + * @param {object} newTarget + * @returns {object} + */ +Internal.prototype[CONSTRUCT] = function (argList, newTarget) { + var handler = this[PROXY_HANDLER]; + validateProxyHanler(handler, "construct"); + + var newObj; + if (handler.construct == UNDEFINED) { + newObj = evaluateNew(this[PROXY_TARGET], argList); + } else if (typeof handler.construct === "function") { + newObj = handler.construct(this[PROXY_TARGET], argList, newTarget); + } else { + throwTypeError("Trap 'construct' is not a function: " + handler.construct); + } + + if (isObject(newObj)) { + return newObj; + } else { + throwTypeError("Trap 'construct' returned non-object: " + newObj); + } +}; + +/** + * Validate the proxy hanler + * @param {object} handler + * @param {string} trap + */ +function validateProxyHanler(handler, trap) { + if (!handler) { + throwTypeError( + "Cannot perform '" + trap + "' on a proxy that has been revoked", + ); + } +} + +/** + * Call constructor with 'new' + * @param {function} F constructor + * @param {any[]} argList + * @returns {object} + */ +function evaluateNew(F, argList) { + var params = []; + for (var i = 0, len = argList.length; i < len; ++i) { + params.push("args[" + i + "]"); + } + var executor = new Function( + "Ctor", + "args", + "return new Ctor(" + params.join(", ") + ")", + ); + return executor(F, argList); +} + +/** + * Throw a type error + * @param {string} message + */ +function throwTypeError(message) { + throw new TypeError(message); +} + +/** + * Check if value is the language type of Object + * @param {any} value + * @returns {boolean} + */ +function isObject(value) { + return value + ? typeof value === "object" || typeof value === "function" + : false; +} + +/** + * Check if key is an own property of object + * @param {object} obj + * @param {string} key + * @returns {boolean} + */ +function hasOwnProperty(obj, key) { + return Object.prototype.hasOwnProperty.call(obj, key); +} + +/** + * Hack `Object.getOwnPropertyNames` + */ +var getOwnPropertyNames = + Object.getOwnPropertyNames || + function (obj) { + var names = []; + for (var key in obj) { + if (hasOwnProperty(obj, key)) { + names.push(key); + } + } + return names; + }; + +/** + * Set the prototype of function + * @param {function} fn + * @param {object} proto + * @returns {object} + */ +var setFuncProto = isNativeFn(Object.setPrototypeOf) + ? Object.setPrototypeOf + : Object[PROTOTYPE] + ? function (fn, proto) { + fn[PROTOTYPE] = proto; + return fn; + } + : function (fn, proto) { + return defineProperty(fn, PROTOTYPE, { value: proto }); + }; + +/** + * Hack `Object.create` + */ +var objectCreate = supportES5 + ? Object.create + : function (_, props) { + var obj = defineProperties({}, props); + if (_isVbObject(obj)) { + var proto = {}; + proto[PROXY_FLAG] = UNDEFINED; + _getVbInternalOf(obj)[PROTOTYPE] = proto; + } + return obj; + }; + +/** + * Hack `Object.assign` + */ +var objectAssign = + Object.assign || + function (target, source) { + for (var key in source) { + if (hasOwnProperty(source, key)) { + target[key] = source[key]; + } + } + return target; + }; + +/** + * Proxy function + * @param {Internal} internal + * @returns {function} + */ +function proxyFunction(internal) { + var target = internal[PROXY_TARGET]; + + function P() { + return this instanceof P + ? internal[CONSTRUCT](arguments, P) + : internal[CALL](this, arguments); + } + P.prototype = target.prototype; // `prototype` is not configurable + + if (supportES5) { + var descMap = observeProto(internal); + var newProto = objectCreate(getPrototypeOf(target), descMap); + setFuncProto(P, newProto); + + descMap = observeProperties(target, internal); + for (var key in descMap) { + if (hasOwnProperty(P, key)) delete descMap[key]; + } + defineProperties(P, descMap); + } else { + objectAssign(P, target); + } + + return P; +} + +/** + * Proxy array + * @param {Internal} internal + * @returns {object} array-like object + */ +function proxyArray(internal) { + var target = internal[PROXY_TARGET]; + + var descMap, newProto; + if (supportES5) { + descMap = observeProto(internal); + // Fix: `concat` does not work correctly on array-like object + descMap.concat.get = function () { + var val = internal[GET]("concat", this); + return val === Array.prototype.concat ? val.bind(target) : val; + }; + newProto = objectCreate(getPrototypeOf(target), descMap); + } + + descMap = observeProperties(target, internal); + // Observe the change of `length`, and synchronize + // the properties of Proxy object to target array + descMap.length.set = function (value) { + var lenDiff = value - target.length; + internal[SET]("length", value, this); + if (lenDiff) syncArrayElement(lenDiff, this, internal); + }; + + return objectCreate(newProto, descMap); +} + +/** + * Proxy object + * @param {Internal} internal + * @returns {object} + */ +function proxyObject(internal) { + var target = internal[PROXY_TARGET]; + var descMap, newProto; + if (supportES5) { + descMap = observeProto(internal); + newProto = objectCreate(getPrototypeOf(target), descMap); + } + + descMap = observeProperties(target, internal); + return objectCreate(newProto, descMap); +} + +/** + * Observe [[Prototype]] + * @param {Internal} internal + * @returns {object} descriptors + */ +function observeProto(internal) { + var descMap = {}; + var proto = internal[PROXY_TARGET]; + while ((proto = getPrototypeOf(proto))) { + var props = observeProperties(proto, internal); + objectAssign(descMap, props); + } + descMap[PROXY_FLAG] = { + get: function () { + return internal[PROXY_TARGET] ? UNDEFINED : REVOKED_FLAG; + }, + }; + return descMap; +} + +/** + * Observe properties + * @param {object} obj + * @param {Internal} internal + * @returns {object} descriptors + */ +function observeProperties(obj, internal) { + var names = getOwnPropertyNames(obj); + var descMap = {}; + for (var i = names.length - 1; i >= 0; --i) { + descMap[names[i]] = observeProperty(obj, names[i], internal); + } + return descMap; +} + +/** + * Observe property + * @param {object} obj + * @param {string} prop + * @param {Internal} internal + * @returns {{get: function, set: function, enumerable: boolean, configurable: boolean}} + */ +function observeProperty(obj, prop, internal) { + var desc = getOwnPropertyDescriptor(obj, prop); + return { + get: function () { + return internal[GET](prop, this); + }, + set: function (value) { + internal[SET](prop, value, this); + }, + enumerable: desc.enumerable, + configurable: desc.configurable, + }; +} + +/** + * Sync array element from P to target + * @param {number} lenDiff + * @param {object} P + * @param {Internal} internal + */ +function syncArrayElement(lenDiff, P, internal) { + var target = internal[PROXY_TARGET]; + if (lenDiff > 0) { + for (var tLen = target.length, i = tLen - lenDiff; i < tLen; ++i) { + var desc = getOwnPropertyDescriptor(P, i); + if (desc) defineProperty(target, i, desc); + else target[i] = UNDEFINED; + desc = observeProperty(target, i, internal); + defineProperty(P, i, desc); + } + } else { + for (var i = target.length, pLen = i - lenDiff; i < pLen; ++i) { + delete P[i]; + } + } +} + +export default typeof Proxy === "undefined" ? ProxyPolyfill : Proxy; diff --git a/_src/lib/legacy-polyfill.js b/_src/lib/legacy-polyfill.js new file mode 100644 index 0000000..96f220e --- /dev/null +++ b/_src/lib/legacy-polyfill.js @@ -0,0 +1,14 @@ +import Es6ProxyPolyfill from "./es6-proxy-polyfill.js"; +import "promise-polyfill/src/polyfill"; +import "whatwg-fetch"; +import "events-polyfill"; +import "element-polyfill"; +import "@webcomponents/template/template.min.js"; + +const globalObj = + (typeof globalThis !== "undefined" && globalThis) || + (typeof self !== "undefined" && self) || + // eslint-disable-next-line no-undef + (typeof global !== "undefined" && global) || + {}; +globalObj.Proxy = typeof Proxy === "undefined" ? Es6ProxyPolyfill : Proxy; diff --git a/_vite.config.mjs b/_vite.config.mjs index 2a00a93..2245e87 100644 --- a/_vite.config.mjs +++ b/_vite.config.mjs @@ -107,6 +107,9 @@ export default defineConfig(({mode})=>({ }), legacy({ targets: [], + additionalLegacyPolyfills: [ + resolve(__dirname, '_src/lib/legacy-polyfill.js'), + ], }), visualizer({ filename: '_stats.html', diff --git a/package-lock.json b/package-lock.json index 16a8573..d2d0ce0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,12 +8,16 @@ "dependencies": { "@vitejs/plugin-legacy": "^5.3.2", "@vitejs/plugin-vue": "^5.0.4", + "@webcomponents/template": "^1.5.1", "bootstrap": "^5.3.3", + "element-polyfill": "^1.1.0", + "events-polyfill": "^2.1.2", "highlight.js": "^11.9.0", "lato-webfont": "^2.15.1", "liquidjs": "^10.10.2", "markup-js": "^1.5.21", "mustache": "^4.2.0", + "promise-polyfill": "^8.3.0", "sass": "^1.74.1", "sass-cast": "^0.5.6", "source-code-pro": "^2.38.0", @@ -22,7 +26,8 @@ "unplugin-vue-components": "^0.26.0", "vite": "^5.2.8", "vite-plugin-ruby": "^5.0.0", - "webfontloader": "^1.6.28" + "webfontloader": "^1.6.28", + "whatwg-fetch": "^3.6.20" }, "devDependencies": { "prettier": "^3.2.5", @@ -2393,6 +2398,11 @@ "integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==", "peer": true }, + "node_modules/@webcomponents/template": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@webcomponents/template/-/template-1.5.1.tgz", + "integrity": "sha512-3e8bx+bgRhyuRwFrDGu7CalILomo11ixuMzVGvpXSxL8lX+ijCIG6J3kS4O/7nElVuKE7vkuXB1xD+RICDNCCg==" + }, "node_modules/acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", @@ -2728,6 +2738,11 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.729.tgz", "integrity": "sha512-bx7+5Saea/qu14kmPTDHQxkp2UnziG3iajUQu3BxFvCOnpAJdDbMV4rSl+EqFDkkpNNVUFlR1kDfpL59xfy1HA==" }, + "node_modules/element-polyfill": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/element-polyfill/-/element-polyfill-1.1.0.tgz", + "integrity": "sha512-t+hZSDS8by+uMyFwGXo8cn6Jf5VM7AjY+p2ZGg1s/E7vA3VmuVFJjKJO3GIPAgH3HfUm/9codl8yGo3GgcMH2A==" + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -2812,6 +2827,11 @@ "node": ">=0.10.0" } }, + "node_modules/events-polyfill": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/events-polyfill/-/events-polyfill-2.1.2.tgz", + "integrity": "sha512-vx4kpGzymyD3CEjmg2wTQA6k5e0RhGTkX3ZwfC9m/Ol7+me2tbVuJ0GjSd8eIJxFioubicA0nUL0SIOAyfrgZA==" + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -3290,6 +3310,11 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/promise-polyfill": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz", + "integrity": "sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -3885,6 +3910,11 @@ "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.1.tgz", "integrity": "sha512-poXpCylU7ExuvZK8z+On3kX+S8o/2dQ/SVYueKA0D4WEMXROXgY8Ez50/bQEUmvoSMMrWcrJqCHuhAbsiwg7Dg==" }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==" + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/package.json b/package.json index 8d3e7b3..a574430 100644 --- a/package.json +++ b/package.json @@ -8,12 +8,16 @@ "dependencies": { "@vitejs/plugin-legacy": "^5.3.2", "@vitejs/plugin-vue": "^5.0.4", + "@webcomponents/template": "^1.5.1", "bootstrap": "^5.3.3", + "element-polyfill": "^1.1.0", + "events-polyfill": "^2.1.2", "highlight.js": "^11.9.0", "lato-webfont": "^2.15.1", "liquidjs": "^10.10.2", "markup-js": "^1.5.21", "mustache": "^4.2.0", + "promise-polyfill": "^8.3.0", "sass": "^1.74.1", "sass-cast": "^0.5.6", "source-code-pro": "^2.38.0", @@ -22,7 +26,8 @@ "unplugin-vue-components": "^0.26.0", "vite": "^5.2.8", "vite-plugin-ruby": "^5.0.0", - "webfontloader": "^1.6.28" + "webfontloader": "^1.6.28", + "whatwg-fetch": "^3.6.20" }, "devDependencies": { "prettier": "^3.2.5",