UNPKG

@nosto/autocomplete

Version:

Library designed to simplify the implementation of search autocomplete functionality

1,009 lines (1,008 loc) 25.7 kB
"use strict"; var u = require("react/jsx-runtime"); function de({ response: e, history: t }) { const n = !!e?.keywords?.hits?.length, r = !!e?.products?.hits?.length, o = !!t?.length; return !n && !r && !o ? null : /* @__PURE__ */ u.jsx("div", { className: "ns-autocomplete-results", children: !n && !r && o ? /* @__PURE__ */ u.jsx(fe, { history: t }) : n || r ? /* @__PURE__ */ u.jsxs(u.Fragment, { children: [ n && /* @__PURE__ */ u.jsx(me, { keywords: e.keywords.hits }), r && /* @__PURE__ */ u.jsx(pe, { products: e.products.hits }), /* @__PURE__ */ u.jsx("div", { className: "ns-autocomplete-submit", children: /* @__PURE__ */ u.jsx("button", { type: "submit", className: "ns-autocomplete-button", children: "See all search results" }) }) ] }) : null }); } function fe({ history: e }) { return /* @__PURE__ */ u.jsxs(u.Fragment, { children: [ /* @__PURE__ */ u.jsxs("div", { className: "ns-autocomplete-history", children: [ /* @__PURE__ */ u.jsx("div", { className: "ns-autocomplete-header", children: "Recently searched" }), e?.map((t, n) => /* @__PURE__ */ u.jsxs( "div", { className: "ns-autocomplete-history-item", "data-ns-hit": JSON.stringify(t), "data-testid": "history", children: [ t.item, /* @__PURE__ */ u.jsx("a", { href: "#", className: "ns-autocomplete-history-item-remove", "data-ns-remove-history": t.item, children: "\u2715" }) ] }, n )) ] }), /* @__PURE__ */ u.jsx("div", { className: "ns-autocomplete-history-clear", children: /* @__PURE__ */ u.jsx("button", { type: "button", className: "ns-autocomplete-button", "data-ns-remove-history": "all", children: "Clear history" }) }) ] }); } function me({ keywords: e }) { return /* @__PURE__ */ u.jsxs("div", { className: "ns-autocomplete-keywords", children: [ /* @__PURE__ */ u.jsx("div", { className: "ns-autocomplete-header", children: "Keywords" }), e?.map((t, n) => /* @__PURE__ */ u.jsx("div", { className: "ns-autocomplete-keyword", "data-ns-hit": JSON.stringify(t), "data-testid": "keyword", children: t._highlight && t._highlight.keyword ? /* @__PURE__ */ u.jsx( "span", { dangerouslySetInnerHTML: { __html: t._highlight.keyword } } ) : /* @__PURE__ */ u.jsx("span", { children: t.keyword }) }, n)) ] }); } function pe({ products: e }) { return /* @__PURE__ */ u.jsxs("div", { className: "ns-autocomplete-products", children: [ /* @__PURE__ */ u.jsx("div", { className: "ns-autocomplete-header", children: "Products" }), e.map((t) => /* @__PURE__ */ u.jsxs( "a", { className: "ns-autocomplete-product", href: "#", "data-ns-hit": JSON.stringify(t), "data-testid": "product", children: [ /* @__PURE__ */ u.jsx("img", { className: "ns-autocomplete-product-image", src: t.imageUrl, alt: t.name, width: "60", height: "40" }), /* @__PURE__ */ u.jsxs("div", { className: "ns-autocomplete-product-info", children: [ t.brand && /* @__PURE__ */ u.jsx("div", { className: "ns-autocomplete-product-brand", children: t.brand }), /* @__PURE__ */ u.jsx("div", { className: "ns-autocomplete-product-name", children: t.name }), /* @__PURE__ */ u.jsxs("div", { children: [ /* @__PURE__ */ u.jsxs("span", { children: [ t.price, "\u20AC" ] }), t.listPrice && t.listPrice !== t.price && /* @__PURE__ */ u.jsxs("span", { className: "ns-autocomplete-product-list-price", children: [ t.listPrice, "\u20AC" ] }) ] }) ] }) ] }, t.productId )) ] }); } function R() { window.nostojs = window.nostojs ?? function(e) { (window.nostojs.q = window.nostojs.q ?? []).push(e); }; } async function E(e) { return window.nostojs(e); } typeof window < "u" && (R(), E((e) => { e.internal.getSettings(); })), typeof window < "u" && R(); function ye(e) { E((t) => t.recordSearchClick("autocomplete", e)); } function ge(e) { E((t) => t.recordSearchSubmit(e)); } function z() { window.nostojs = window.nostojs ?? function(e) { (window.nostojs.q = window.nostojs.q ?? []).push(e); }; } async function S(e) { return window.nostojs(e); } typeof window < "u" && (z(), S((e) => { e.internal.getSettings(); })), typeof window < "u" && z(); function G(e) { return String(e) === "[object Object]"; } function K(e) { if (!G(e)) return !1; const t = e.constructor; if (t === void 0) return !0; const n = t.prototype; return !(!G(n) || !n.hasOwnProperty("isPrototypeOf")); } function b(e, t) { if (e === t) return !0; if (e instanceof Date && t instanceof Date) return e.getTime() === t.getTime(); if (Array.isArray(e) && Array.isArray(t)) return e.length !== t.length ? !1 : e.every((n, r) => b(n, t[r])); if (K(e) && K(t)) { const n = Object.entries(e); return n.length !== Object.keys(t).length ? !1 : n.every(([r, o]) => b(o, t[r])); } return !1; } function B() { const e = /* @__PURE__ */ new Map(); return { getItem(t) { return e.get(t) ?? null; }, setItem(t, n) { e.set(t, n); }, removeItem(t) { e.delete(t); }, clear() { e.clear(); }, key(t) { return Array.from(e.keys())[t] ?? null; }, get length() { return e.size; } }; } function j(e) { return (...t) => { S((n) => { (n.internal.context.mode.isPreview() ? console[e] : n.internal.logger[e])(...t); }); }; } const y = { debug: j("debug"), info: j("info"), warn: j("warn"), error: j("error") }; function he(e, t, n) { try { const r = JSON.stringify(t); n.setItem(e, r); } catch (r) { y.warn(r); } } function we(e, t) { try { const n = t.getItem(e); if (n) return JSON.parse(n); } catch (n) { y.warn(n); } } let Q, N; try { Q = window.localStorage, N = window.sessionStorage; } catch (e) { y.warn(e), Q = B(), N = B(); } function ve(e, t) { he(e, t, N); } function ke(e) { return we(e, N); } async function Se(e, { hitDecorators: t, ...n }, r) { const o = await r(e, n); if (!o.products?.hits?.length || !t?.length) return o; const i = (a) => t.reduce((c, m) => m(c), a); return { ...o, products: { ...o.products, hits: o.products.hits.map(i) } }; } const V = "nosto:search-js:cache", be = 60 * 1e3; function je(e, t) { ve(V, { query: e, result: t, created: Date.now() }); } function Ne(e) { const t = ke(V); if (!t || !Ae(t)) return null; const n = W(t.query); return !b(W(e), n) || Date.now() - t.created > be ? null : t.result; } function W(e) { const t = { ...e, time: void 0, products: { ...e.products, size: void 0 } }; return JSON.parse(JSON.stringify(t)); } function Ae(e) { return typeof e == "object" && e !== null && "query" in e && "result" in e && "created" in e; } async function xe(e, { usePersistentCache: t, ...n }, r) { if (!t) return r(e, n); const o = await Ce(e, n, r); return je(e, o), o; } async function Ce(e, t, n) { const { from: r = 0, size: o = 0 } = e.products || {}, i = Ne(e); if (!i) return await n(e, t); const a = i?.products?.size ?? 0, c = i?.products?.hits ?? []; if (o === a) return i; if (o < a) return { ...i, products: { ...i.products, size: o, hits: c.slice(0, o), total: i.products?.total || 0 } }; const m = o - c.length, l = r > 0 ? r + 1 : o - m, s = { ...e, products: { ...e.products, from: l, size: m } }, d = await n(s, t); return { ...i, products: { ...i.products, size: o, hits: [...i.products?.hits || [], ...d.products?.hits || []], total: d.products?.total || 0 } }; } const Ie = 3e4, L = /* @__PURE__ */ new Map(); function Pe(e, t) { const n = L.get(e); if (!n) return; const r = Date.now() - n.created > Ie, o = b(t, n.query); if (r || !o) { L.delete(e); return; } return n.result; } function De(e, t, n) { L.set(e, { query: t, result: n, created: Date.now() }); } async function Ee(e, t, n) { if (!t.useMemoryCache) return n(e, t); const r = JSON.stringify(e), o = Pe(r, e); if (o) return o; const i = await n(e, t); return De(r, e, i), i; } async function Le(e, t, n) { if (!t.redirect) return n(e, t); const r = await n(e, t); if (r.keywords?.hits?.length) { const o = r.keywords.hits.find((i) => i.keyword === e.query && i._redirect); o && o._redirect && (window.location.href = o._redirect); } return r; } function $e(e) { return new Promise((t) => setTimeout(t, e)); } async function Oe(e, { maxRetries: t = 0, retryInterval: n = 0, ...r }, o) { let i = 0; for (; ; ) try { return await o(e, r); } catch (a) { if (i >= t) throw a; if (!He(a)) throw y.info("Skipping retry logic for", a), a; i++, await $e(n); } } function He(e) { return !e || typeof e != "object" ? !1 : !("status" in e) || _e(e.status); } function _e(e) { return typeof e == "number" && (e < 400 || e >= 500); } async function qe(e, t = {}) { const n = await new Promise(S); return Te( n.search, Oe, Le, Ee, xe, Se )(e, t); } function Te(e, ...t) { return t.reduce((n, r) => (o, i) => r(o, i, n), e); } const Ue = [ "productId", "url", "name", "imageUrl", "imageHash", "thumbUrl", "description", "brand", "variantId", "availability", "price", "priceText", "categoryIds", "categories", "customFields.key", "customFields.value", "priceCurrencyCode", "datePublished", "listPrice", "unitPricingBaseMeasure", "unitPricingUnit", "unitPricingMeasure", "googleCategory", "gtin", "ageGroup", "gender", "condition", "alternateImageUrls", "ratingValue", "reviewCount", "inventoryLevel", "skus.id", "skus.name", "skus.price", "skus.listPrice", "skus.priceText", "skus.url", "skus.imageUrl", "skus.inventoryLevel", "skus.customFields.key", "skus.customFields.value", "skus.availability", "pid", "onDiscount", "extra.key", "extra.value", "saleable", "available", "tags1", "tags2", "tags3" ]; async function $(e, t = {}) { const n = e.products?.fields ?? Ue, r = e.products?.facets ?? ["*"], o = e.products?.size ?? 20, i = e.products?.from ?? 0, a = await qe( { ...e, products: { ...e.products, fields: n, facets: r, size: o, from: i } }, t ); return { query: e, response: a }; } const O = { serpPath: "/search", queryParamName: "query", enabled: !0 }; function Z() { return { minQueryLength: 2, historyEnabled: !0, historySize: 5, hitDecorators: [], nostoAnalytics: !0, googleAnalytics: O, routingHandler: (e) => { location.href = e; }, nativeSubmit: !1, submit: (e, t, n) => { e.length >= (t.minQueryLength ?? Z().minQueryLength) && $( { query: e }, { redirect: !0, track: t.nostoAnalytics ? "serp" : void 0, hitDecorators: t.hitDecorators, ...n } ); } }; } function Me(e, { onClick: t, onFocus: n, onInput: r, onKeyDown: o, onSubmit: i }, { form: a = e.form ?? void 0, nativeSubmit: c } = {}) { const m = []; function l(s, d, g) { s.addEventListener(d, g), m.push(() => s.removeEventListener(d, g)); } return (o || i) && l(e, "keydown", (s) => { const d = o?.(e.value, s.key); o && (s.key === "ArrowDown" || s.key === "ArrowUp") ? s.preventDefault() : i && s.key === "Enter" && (e.value !== "" && !s.repeat && !d && i(e.value), c || s.preventDefault()); }), i && a && (l(a, "submit", (s) => { c || s.preventDefault(), i(e.value); }), a.querySelectorAll("[type=submit]").forEach((s) => { l(s, "click", (d) => { c || d.preventDefault(), i(e.value); }); })), t && l(e, "click", () => t(e.value)), n && l(e, "focus", () => n(e.value)), r && l(e, "input", () => r(e.value)), { destroy() { m.forEach((s) => s()); } }; } function Je(e) { e.setAttribute("autocomplete", "off"); } function Fe(e, t, n, r, o, i, a) { let c = [], m = [], l = !0, s = -1; function d(f) { const h = f?.dataset?.nsHit; if (h) { const p = H(h); if (v(), p?.item) { r(p.item); return; } if (p?.keyword) { r(p.keyword, { redirect: !!p?._redirect, isKeyword: !0 }), p?._redirect && i(p._redirect); return; } p?.url && i(p.url); } } function g() { l = !e.innerHTML.trim(), l || (c = Array.from(e.querySelectorAll("[data-ns-hit]")).map((f) => (ne(f), f))); } function T() { Object.entries(a ?? {}).map(([f, h]) => { const p = `[data-ns-${f.replace(/([A-Z])/g, "-$1").toLowerCase()}]`; Array.from(e.querySelectorAll(p)).map((w) => { const le = w?.dataset?.[`ns${f.charAt(0).toUpperCase() + f.slice(1)}`], F = () => { h({ data: le, el: w, update: U }); }; w.addEventListener("click", F), m.push(() => { w.removeEventListener("click", F); }); }); }); } function ne(f) { const h = () => { d(f); }; f.addEventListener("click", h), m.push(() => { f.removeEventListener("click", h); }); } function I(f, h) { if (typeof h == "number" && c[h] && c[h].classList.remove("selected"), typeof f == "number" && c[f]) { c[f]?.classList.add("selected"); const p = c[f]?.dataset?.nsHit; if (p) { const w = H(p); if (w.item) { o(w.item); return; } if (w.keyword) { o(w.keyword); return; } } } } function P() { D(), c = [], m.forEach((f) => f()), m = []; } async function U(f) { P(), await n(e, f), setTimeout(() => { g(), T(), M(); }, 0); } function v() { D(), e.style.display = "none"; } function M() { l ? v() : e.style.display = ""; } function re() { P(), l = !0, v(); } function J() { return e.style.display !== "none"; } function oe() { let f = s; s === c.length - 1 ? s = 0 : f = s++, I(s, f); } function ie() { if (k()) { let f = s; s === 0 ? s = c.length - 1 : f = s--, I(s, f); } else s = c.length - 1, I(s); } function se() { J() && k() && c[s] && d(c[s]); } function k() { return s > -1; } function ce() { return c[s]; } function D() { k() && (c[s]?.classList.remove("selected"), s = -1); } function ae() { P(), l = !0, e.innerHTML = ""; } async function ue() { const f = await Promise.resolve(t); await Promise.resolve(n(e, f)), setTimeout(() => { g(), T(), v(); }, 0); } return ue(), { update: U, clear: re, isOpen: J, goDown: oe, goUp: ie, handleSubmit: se, destroy: ae, show: M, hide: v, resetHighlight: D, hasHighlight: k, getHighlight: ce, container: e }; } function H(e) { try { return JSON.parse(e) ?? {}; } catch (t) { return y.warn("Could not parse hit", t), {}; } } class X extends Error { } function Re(e) { let t = !1; return { promise: new Promise((n, r) => e.then( (o) => t ? r(new X("cancelled promise")) : n(o), (o) => r(t ? new X("cancelled promise") : o) )), cancel() { t = !0; } }; } function ze({ config: e, history: t, input: n }) { let r; const o = (s, d, g) => typeof d.fetch == "function" ? d.fetch(s) : $( { query: s, ...d.fetch }, { track: d.nostoAnalytics ? "autocomplete" : void 0, redirect: !1, hitDecorators: d.hitDecorators, ...g } ); function i(s) { return Promise.resolve({ query: { query: s }, history: t?.getItems() }); } function a(s, d) { return r?.cancel(), s && s.length >= e.minQueryLength ? (r = Re(o(s, e, d)), r.promise) : t ? i(s ?? "") : ( // @ts-expect-error type mismatch r?.promise ?? Promise.resolve({}) ); } function c(s) { return t && t.add(s), i(n.value); } function m(s) { return t && t.remove(s), i(n.value); } function l() { return t && t.clear(), i(n.value); } return { updateState: a, addHistoryItem: c, removeHistoryItem: m, clearHistory: l }; } function A(e, t) { return (typeof e == "string" ? Array.from(document.querySelectorAll(e)) : [e]).filter((n) => t ? n instanceof t : !0); } function Y(e, t) { let n = []; return A(e).forEach((r) => { const o = r.parentNode; o !== document && o instanceof Element && (n.push(o), n = n.concat(Y(o))); }), n.filter((r) => t === void 0); } function Ge([e, t], n) { const r = (o) => { const i = o.target; i instanceof HTMLElement && e && i !== e && i !== t && !Y(i).includes(e) && n(); }; return document.addEventListener("click", r), { destroy: () => { document.removeEventListener("click", r); } }; } const _ = "nosto:autocomplete:gaEvent"; function x(e) { const { delay: t = !1, title: n = document.title, location: r = window.location.href } = e || {}; if (t) Ke(n, r); else { if ("gtag" in window && typeof window.gtag == "function") { const o = "google_tag_manager" in window && typeof window.google_tag_manager == "object" ? Object.keys(window.google_tag_manager || []).filter((i) => i.substring(0, 2) == "G-") : []; if (o.length > 1) for (let i = 0; i < o.length; i++) window.gtag("event", "page_view", { page_title: n, page_location: r, send_to: o[i] }); else window.gtag("event", "page_view", { page_title: n, page_location: r }); } if ("ga" in window && typeof window.ga == "function" && "getAll" in window.ga && typeof window.ga.getAll == "function") try { const o = new URL(r), i = window.ga.getAll(); i?.length > 0 && i[0]?.send("pageview", o.pathname + o.search); } catch (o) { y.warn("Could not send pageview to GA", o); } } } const C = (e) => typeof e.googleAnalytics == "boolean" ? e.googleAnalytics : typeof e.googleAnalytics == "object" && e.googleAnalytics.enabled, q = (e, t) => { const n = C(t) ? typeof t.googleAnalytics == "boolean" ? O : { ...O, ...t.googleAnalytics } : void 0; if (e && n) try { return new URL( `${n?.serpPath || location.pathname}?${`${encodeURIComponent( n.queryParamName )}=${encodeURIComponent(e).replace(/%20/g, "+")}`}`, window.location.origin ).toString(); } catch (r) { y.warn("Could not create track url", r); return; } }; function Ke(e, t) { localStorage.setItem(_, JSON.stringify({ title: e, location: t })); } function Be() { const e = localStorage.getItem(_); if (typeof e == "string") { localStorage.removeItem(_); try { const t = JSON.parse(e); x(t); } catch (t) { y.warn("Could not consume pageView", t); } } } function Qe(e) { let t, n = !1; return (r) => { n ? (t && clearTimeout(t), t = setTimeout(async () => { await r(), n = !1; }, e)) : (r(), n = !0); }; } function Ve(e) { const t = "nosto:autocomplete:history"; let n = r(); function r() { try { return JSON.parse(localStorage.getItem(t) ?? "[]") ?? []; } catch (l) { return y.error("Could not get history items.", l), []; } } function o(l) { try { localStorage.setItem(t, JSON.stringify(l)); } catch (s) { y.error("Could not set history items.", s); } } function i(l) { o(n = [{ item: l }, ...n?.filter((s) => s.item !== l) || []].slice(0, e)); } function a() { o(n = []); } function c(l) { o(n = n.filter((s) => s.item !== l)); } function m() { return n; } return { add: i, clear: a, remove: c, getItems: m }; } function We(e) { const t = { ...Z(), ...e }, n = t.historyEnabled ? Ve(t.historySize) : void 0, r = Qe(300); C(e) && setTimeout(Be, 1e3); const o = A(e.inputSelector, HTMLInputElement).map((i) => { const a = ze({ config: t, history: n, input: i }), c = Ze({ input: i, config: t, actions: a }); if (!c) return; Je(i); const m = Me( i, { onInput: async (s) => { r(async () => { const d = await a.updateState(s); c.update(d); }); }, onClick() { c.resetHighlight(); }, onFocus() { c.show(); }, onSubmit() { if (c.isOpen() && c.hasHighlight()) { const s = c.getHighlight()?.dataset?.nsHit; s && ee({ config: t, data: s, query: i.value }), c.handleSubmit(); } else te({ actions: a, config: t })(i.value); }, onKeyDown(s, d) { d === "Escape" ? c.hide() : d === "ArrowDown" ? c.isOpen() ? c.goDown() : c.show() : d === "ArrowUp" && c.isOpen() && c.goUp(); } }, { nativeSubmit: t.nativeSubmit } ), l = Ge([c.container, i], () => { c.hide(); }); return { open() { c.show(); }, close() { c.hide(); }, destroy() { m.destroy(), l.destroy(), c.destroy(); } }; }); return { destroy() { o.forEach((i) => i?.destroy()); }, open() { o.forEach((i) => i?.open()); }, close() { o.forEach((i) => i?.close()); } }; } function Ze({ input: e, config: t, actions: n }) { const r = typeof t.dropdownSelector == "function" ? A(t.dropdownSelector(e), HTMLElement) : A(t.dropdownSelector, HTMLElement); if (r.length === 0) { y.error(`No dropdown element found for input ${e}`); return; } else r.length > 1 && y.error(`Multiple dropdown elements found for input ${e}, using the first element`); const o = r[0]; return Fe( o, n.updateState(e.value), t.render, te({ actions: n, config: t }), (i) => e.value = i, t.routingHandler, { removeHistory: async function({ data: i, update: a }) { if (i === "all") { const c = await n.clearHistory(); return a(c); } else if (i) { const c = await n.removeHistoryItem(i); return a(c); } }, hit: function({ data: i }) { i && ee({ config: t, data: i, query: e.value }); } } ); } async function ee({ config: e, data: t, query: n }) { if (!e.googleAnalytics && !e.nostoAnalytics) return; const r = H(t); e.nostoAnalytics && r && ye(r), C(e) && (r._redirect && x({ delay: !0, location: q(r.keyword, e) }), r.url && x({ delay: !0, location: q(n, e) })); } function te(e) { return async (t, n) => { const { config: r, actions: o } = e, { redirect: i = !1 } = n ?? {}; t.length > 0 && (r.historyEnabled && o.addHistoryItem(t), r.nostoAnalytics && ge(t), C(r) && x({ delay: !0, location: q(t, r) }), !i && typeof r?.submit == "function" && r.submit(t, r, n)); }; } const Xe = { defaultCurrency: "EUR", defaultLocale: "en-US", /** @hidden */ currencySettings: {} }, Ye = { EUR: "de-DE", GBP: "en-GB", USD: "en-US", AUD: "en-AU", CAD: "en-CA", //India, Afghanistan, Bangladesh, Bhutan, Myanmar, Nepal, and Pakistan uses lakhs and crores notation INR: "en-IN", AFN: "en-IN", BDT: "en-IN", BTN: "en-IN", MMK: "en-IN", NPR: "en-IN", PKR: "en-IN" }; function et(e = {}) { const t = { ...Xe, ...e }; e.currencySettings || S((r) => { t.currencySettings = r.internal.getSettings().currencySettings ?? {}; }); function n(r, o) { const { defaultCurrency: i, currencySettings: a, defaultLocale: c } = t, m = o ?? i, l = Ye[m] ?? c; if (m in a) { const s = a[m], d = new Intl.NumberFormat(l, { useGrouping: !!s.groupingSeparator, minimumFractionDigits: s.decimalPlaces, maximumFractionDigits: s.decimalPlaces }).formatToParts(r).map((g) => g.type === "group" ? s.groupingSeparator : g.type === "decimal" ? s.decimalCharacter : g.value).join(""); return s?.currencyBeforeAmount ? `${s.currencyToken}${d}` : `${d}${s?.currencyToken}`; } return new Intl.NumberFormat(l, { style: "currency", currency: m }).format(r); } return { formatCurrency: n }; } function tt(e) { const { formatCurrency: t } = et(e); function n(o, i) { const a = {}; return o.price !== void 0 && (a.priceText = t(o.price, i)), o.listPrice !== void 0 && (a.listPriceText = t(o.listPrice, i)), Object.assign({}, o, a); } function r(o) { return o.price !== void 0 || o.listPrice !== void 0; } return function(o) { if (!r(o)) return o; const i = n(o, o.priceCurrencyCode); return i.skus && i.skus.some(r) && (i.skus = i.skus.map((a) => r(a) ? n(a, o.priceCurrencyCode) : a)), i; }; } exports.Autocomplete = de, exports.autocomplete = We, exports.priceDecorator = tt, exports.search = $; //# sourceMappingURL=autocomplete.cjs.map