baremetal.js 1.2.5 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/baremetal.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * baremetal.js v1.2.5
2
+ * baremetal.js v1.2.6
3
3
  * A lightweight, dependency-free Vanilla JavaScript SPA engine prioritizing extreme performance, native browser features, and explicit lifecycle management.
4
4
  * (c) 2026 dkydivyansh
5
5
  * Released under the GPL-3.0 License
@@ -290,6 +290,7 @@ const Router = {
290
290
  anchor.target === '_blank' ||
291
291
  (anchor.rel && anchor.rel.includes('noreferrer')) ||
292
292
  anchor.hasAttribute('download') ||
293
+ anchor.getAttribute('data-baremetal') === 'ignore' ||
293
294
  (anchor.getAttribute('href') || '').startsWith('#')
294
295
  ) {
295
296
  return;
@@ -312,6 +313,7 @@ const Router = {
312
313
  anchor.origin === window.location.origin &&
313
314
  anchor.target !== '_blank' &&
314
315
  !anchor.hasAttribute('download') &&
316
+ anchor.getAttribute('data-baremetal') !== 'ignore' &&
315
317
  !(anchor.getAttribute('href') || '').startsWith('#') &&
316
318
  !this.htmlCache[anchor.href]
317
319
  ) {
@@ -491,6 +493,18 @@ const Router = {
491
493
  console.error("Routing error:", err);
492
494
  stateManager.publish('ROUTE_ERROR', { url, error: err.message });
493
495
 
496
+ // If fetch fails due to CORS (e.g. following an external redirect) or network issues,
497
+ // fall back to native browser navigation immediately so the browser can handle it natively.
498
+ if (err instanceof TypeError && err.message.includes('fetch')) {
499
+ Loader.log(`Fetch failed for ${url} (possible CORS/Redirect). Falling back to native navigation.`);
500
+ if (this.historyStack.length > 0) {
501
+ const prev = this.historyStack.pop();
502
+ history.replaceState(null, '', prev);
503
+ }
504
+ window.location.assign(url);
505
+ return;
506
+ }
507
+
494
508
  if (Loader.config.showErrorNotification) {
495
509
 
496
510
  if (this.historyStack.length > 0) {
@@ -1 +1 @@
1
- const t=new class{constructor(){this.state={},this.listeners={},this.eventBus={}}initPersistence(){try{const t=sessionStorage.getItem("baremetal_state");if(t){const e=JSON.parse(t);for(const[t,i]of Object.entries(e))this.state[t]=i}}catch(t){console.warn("Failed to hydrate state",t)}}init(t,e){void 0===this.state[t]&&(this.state[t]=e)}subscribe(t,e){return this.listeners[t]||(this.listeners[t]=[]),this.listeners[t].push(e),void 0!==this.state[t]&&e(this.state[t]),()=>this.unsubscribe(t,e)}unsubscribe(t,e){this.listeners[t]&&(this.listeners[t]=this.listeners[t].filter(t=>t!==e))}update(t,e){if(this.state[t]=e,this.listeners[t]&&this.listeners[t].forEach(t=>t(e)),window.__baremetal_persist_state)try{sessionStorage.setItem("baremetal_state",JSON.stringify(this.state))}catch(t){}}get(t){return this.state[t]}on(t,e){return this.eventBus[t]||(this.eventBus[t]=[]),this.eventBus[t].push(e),()=>this.off(t,e)}off(t,e){this.eventBus[t]&&(this.eventBus[t]=this.eventBus[t].filter(t=>t!==e))}publish(t,e){this.eventBus[t]&&this.eventBus[t].forEach(t=>t(e))}},e={activeModules:{},config:{keepAliveSameModules:!0,debug:!1,autoWrap:!0,hoverPrefetch:!1,showErrorNotification:!1,persistState:!1,virtualizeDom:!1,transition:{enabled:!1,simulatedDelay:0,module:null,useViewTransitions:!1}},setConfig(t){this.config={...this.config,...t}},log(...t){this.config.debug&&console.log("[BareMetal Loader]",...t)},async prepare(t){if(this.config.transition&&this.config.transition.enabled){const e=this.config.transition.module||"/src/transition.js";t.__baremetal_transition=e}const e={},i=[],o={};for(const[o,s]of Object.entries(this.activeModules)){const n="__baremetal_transition"===o,r="string"==typeof t[o]?t[o]:t[o]?t[o].path:null;(this.config.keepAliveSameModules||n)&&r===s.path?(e[o]=s,this.log(`Keep-Alive: ${o} (${s.path})`)):i.push(s)}for(const t of i)if(this.log(`Destroying module: ${t.path}`),t.module&&"function"==typeof t.module.destroy)try{t.module.destroy()}catch(e){console.error(`Error destroying module ${t.path}`,e)}for(const[i,s]of Object.entries(t))e[i]||(o[i]=s);return this.activeModules=e,o},async loadPrepared(e){const i=Object.keys(e).length;let o=0;const s=Object.entries(e).map(async([e,s])=>{const n="string"==typeof s?s:s.path,a="string"==typeof s?null:s.lazy;this.log(`Preparing module: ${n}`);const l=async()=>{try{const i=new URL(n,document.baseURI).href,o=this.config.debug?`${i}?t=${Date.now()}`:i;let s;if(this.config.autoWrap){const t=await fetch(o);if(!t.ok)throw new Error(`Failed to fetch ${o}`);const e=await t.text();if(/export\s+(function|const|let|var)\s+mount\b/.test(e)||/export\s+\{.*?\bmount\b.*?\}/.test(e))s=await import(o);else{console.warn(`[BareMetal] WARNING: Module ${n} does not explicitly export a mount() function. Auto-wrapping it...`);const t=`export async function mount(context) { const { state } = context; ${e} }`,i=new Blob([t],{type:"application/javascript"}),o=URL.createObjectURL(i);s=await import(o),URL.revokeObjectURL(o)}}else s=await import(o);if("function"==typeof s.mount){this.log(`Mounting module: ${n}`);const i={state:t};if(this.config.virtualizeDom){const{virtualize:t}=await Promise.resolve().then(function(){return r});i.virtualize=t}const o=[];i.onCleanup=t=>o.push(t);const a=await s.mount(i),l=()=>{if(o.forEach(t=>{try{t()}catch(t){console.error(t)}}),a&&"function"==typeof a.destroy)try{a.destroy()}catch(t){console.error(t)}};this.activeModules[e]={path:n,module:{destroy:l}}}else console.error(`[BareMetal] Module ${n} failed to provide a mount function even after wrapping.`)}catch(t){console.error(`Failed to load module: ${n}`,t)}};if(a){const e=document.querySelector(a);if(e&&window.IntersectionObserver){this.log(`Deferred loading of module ${n} until ${a} is visible`);const s=new IntersectionObserver(t=>{t[0].isIntersecting&&(s.disconnect(),l())});if(s.observe(e),o++,i>0){const e=50+o/i*50;t.publish("ROUTE_PROGRESS",{url:window.location.pathname,progress:e})}return Promise.resolve()}await l()}else await l();if(o++,i>0){const e=50+o/i*50;t.publish("ROUTE_PROGRESS",{url:window.location.pathname,progress:e})}});await Promise.all(s),t.publish("ROUTE_END",{url:window.location.pathname})},async load(t){const e=await this.prepare(t);await this.loadPrepared(e)}};function i(t){return e.load(t)}const o={htmlCache:{},scrollMemory:{},historyStack:[],currentAbortController:null,lastPathAndSearch:"",init(){this.lastPathAndSearch=window.location.pathname+window.location.search,"scrollRestoration"in history&&(history.scrollRestoration="manual"),window.addEventListener("popstate",this.handleRoute.bind(this)),document.body.addEventListener("click",t=>{const e=t.target.closest("a");e&&(e.origin!==window.location.origin||"_blank"===e.target||e.rel&&e.rel.includes("noreferrer")||e.hasAttribute("download")||(e.getAttribute("href")||"").startsWith("#")||(t.preventDefault(),this.historyStack.push(window.location.pathname),this.scrollMemory[window.location.pathname]=window.scrollY,history.pushState(null,"",e.href),this.handleRoute()))}),document.body.addEventListener("mouseover",t=>{if(!e.config.hoverPrefetch)return;const i=t.target.closest("a");i&&(i.origin!==window.location.origin||"_blank"===i.target||i.hasAttribute("download")||(i.getAttribute("href")||"").startsWith("#")||this.htmlCache[i.href]||(this.htmlCache[i.href]="fetching",fetch(i.href).then(t=>{if(t.ok)return t.text();throw new Error("Failed to prefetch")}).then(t=>this.htmlCache[i.href]=t).catch(()=>delete this.htmlCache[i.href])))})},back(){if(this.historyStack.length>0){const t=this.historyStack.pop();history.pushState(null,"",t),this.handleRoute()}else history.back()},reload(){window.location.reload()},async handleRoute(i){const o=window.location.pathname+window.location.search;if(this.lastPathAndSearch===o)return;this.lastPathAndSearch=o,this.currentAbortController&&this.currentAbortController.abort(),this.currentAbortController=new AbortController;const s=this.currentAbortController.signal,n=window.location.pathname;try{let o;e.log(`Navigating to ${n}`),t.publish("ROUTE_START",{url:n}),e.config.transition&&e.config.transition.simulatedDelay&&(t.publish("ROUTE_PROGRESS",{url:n,progress:10}),await new Promise(t=>setTimeout(t,e.config.transition.simulatedDelay/2)),t.publish("ROUTE_PROGRESS",{url:n,progress:30}),await new Promise(t=>setTimeout(t,e.config.transition.simulatedDelay/2)));const r=new URL(n,document.baseURI).href;if(e.config.hoverPrefetch&&this.htmlCache[r]&&"fetching"!==this.htmlCache[r])o=this.htmlCache[r],e.log(`Used pre-fetched cache for ${n}`);else{const t=await fetch(n,{signal:s});if(t.redirected){const i=new URL(t.url);e.log(`Redirect detected to ${i.pathname}. Continuing SPA transition to new destination.`),history.replaceState(null,"",i.pathname+i.search),this.lastPathAndSearch=i.pathname+i.search}if(!t.ok)throw new Error(`HTTP error! status: ${t.status}`);o=await t.text()}t.publish("ROUTE_PROGRESS",{url:n,progress:50});const a=(new DOMParser).parseFromString(o,"text/html");let l=null;const c=a.querySelectorAll("script");for(const t of c)if(t.textContent.includes("loader(")){const e=t.textContent.match(/loader\s*\(\s*(\{[\s\S]*?\})\s*\)/);if(e&&e[1])try{l=new Function("return "+e[1])()}catch(i){console.error("Failed to parse loader config in new page",i)}}if(!l)return e.log(`No BareMetal config found on ${n}. Falling back to native navigation.`),void window.location.assign(n);const h=await e.prepare(l);document.title=a.title;document.head.querySelectorAll('link[data-baremetal="style"], style[data-baremetal="style"]').forEach(t=>t.remove());a.head.querySelectorAll('link[data-baremetal="style"], style[data-baremetal="style"]').forEach(t=>document.head.appendChild(t.cloneNode(!0)));const d=[];document.querySelectorAll("[data-baremetal-preserve]").forEach(t=>{if(t.id&&a.getElementById(t.id)){const e=document.createElement("div");t.parentNode.replaceChild(e,t),d.push(t)}});const u=document.getElementById("baremetal-transition-root");u&&u.parentNode.removeChild(u);const p=()=>{document.body.innerHTML=a.body.innerHTML,d.forEach(t=>{const e=document.getElementById(t.id);e&&(Array.from(t.attributes).forEach(e=>{"id"!==e.name&&"data-baremetal-preserve"!==e.name&&t.removeAttribute(e.name)}),Array.from(e.attributes).forEach(e=>{"id"!==e.name&&t.setAttribute(e.name,e.value)}),e.parentNode.replaceChild(t,e))}),u&&document.body.appendChild(u),t.publish("DOM_SWAPPED",null)},f=()=>{p(),window.scrollTo(0,this.scrollMemory[n]||0)};e.config.transition&&e.config.transition.useViewTransitions&&document.startViewTransition?document.startViewTransition(()=>{f()}):f(),await e.loadPrepared(h)}catch(i){if("AbortError"===i.name)return void e.log(`Aborted fetch for ${n} due to new navigation.`);if(console.error("Routing error:",i),t.publish("ROUTE_ERROR",{url:n,error:i.message}),e.config.showErrorNotification){if(this.historyStack.length>0){const t=this.historyStack.pop();history.replaceState(null,"",t)}const t=document.createElement("div");t.style.position="fixed",t.style.bottom="20px",t.style.left="20px",t.style.background="#e74c3c",t.style.color="white",t.style.padding="15px 20px",t.style.borderRadius="8px",t.style.boxShadow="0 4px 12px rgba(0,0,0,0.15)",t.style.zIndex="999999",t.style.fontFamily="sans-serif",t.style.transition="opacity 0.3s ease",t.innerHTML=`<strong>Navigation Failed:</strong> ${i.message}`,document.body.appendChild(t),setTimeout(()=>{t.style.opacity="0",setTimeout(()=>t.remove(),300)},4e3)}else{if(this.historyStack.length>0){const t=this.historyStack.pop();history.replaceState(null,"",t)}window.location.assign(n)}}}};class s{constructor(t,e,i,o,s=20){this.container=document.getElementById(t),this.container&&(this.items=e,this.renderRow=i,this.itemHeight=o,this.visibleCount=s,this.totalHeight=this.items.length*this.itemHeight,this.container.style.overflowY="auto",this.container.style.position="relative",this.innerWrapper=document.createElement("div"),this.innerWrapper.style.height=`${this.totalHeight}px`,this.innerWrapper.style.position="relative",this.container.appendChild(this.innerWrapper),this.startIndex=0,this.onScroll=this.onScroll.bind(this),this.container.addEventListener("scroll",this.onScroll),this.render())}onScroll(){const t=this.container.scrollTop,e=Math.max(0,Math.floor(t/this.itemHeight)-2);e!==this.startIndex&&(this.startIndex=e,this.render())}render(){this.innerWrapper.innerHTML="";const t=Math.min(this.items.length-1,this.startIndex+this.visibleCount+4);for(let e=this.startIndex;e<=t;e++){const t=this.renderRow(this.items[e],e);t.style.position="absolute",t.style.top=e*this.itemHeight+"px",t.style.width="100%",this.innerWrapper.appendChild(t)}}destroy(){this.container&&(this.container.removeEventListener("scroll",this.onScroll),this.container.innerHTML="")}}const n=(t,e,i,o,n)=>new s(t,e,i,o,n);var r=Object.freeze({__proto__:null,Virtualizer:s,virtualize:n});const a={state:t,events:t,loader:i,router:o,virtualize:n,init(i={}){void 0!==i.debug&&e.setConfig({debug:i.debug}),void 0!==i.keepAliveSameModules&&e.setConfig({keepAliveSameModules:i.keepAliveSameModules}),void 0!==i.transition&&e.setConfig({transition:i.transition}),void 0!==i.autoWrap&&e.setConfig({autoWrap:i.autoWrap}),void 0!==i.hoverPrefetch&&e.setConfig({hoverPrefetch:i.hoverPrefetch}),void 0!==i.showErrorNotification&&e.setConfig({showErrorNotification:i.showErrorNotification}),void 0!==i.persistState&&(e.setConfig({persistState:i.persistState}),i.persistState&&(window.__baremetal_persist_state=!0,t.initPersistence())),void 0!==i.virtualizeDom&&e.setConfig({virtualizeDom:i.virtualizeDom}),o.init(),e.log("Initialized BareMetal Engine with config:",i)}};export{a as BareMetal,i as loader};
1
+ const t=new class{constructor(){this.state={},this.listeners={},this.eventBus={}}initPersistence(){try{const t=sessionStorage.getItem("baremetal_state");if(t){const e=JSON.parse(t);for(const[t,i]of Object.entries(e))this.state[t]=i}}catch(t){console.warn("Failed to hydrate state",t)}}init(t,e){void 0===this.state[t]&&(this.state[t]=e)}subscribe(t,e){return this.listeners[t]||(this.listeners[t]=[]),this.listeners[t].push(e),void 0!==this.state[t]&&e(this.state[t]),()=>this.unsubscribe(t,e)}unsubscribe(t,e){this.listeners[t]&&(this.listeners[t]=this.listeners[t].filter(t=>t!==e))}update(t,e){if(this.state[t]=e,this.listeners[t]&&this.listeners[t].forEach(t=>t(e)),window.__baremetal_persist_state)try{sessionStorage.setItem("baremetal_state",JSON.stringify(this.state))}catch(t){}}get(t){return this.state[t]}on(t,e){return this.eventBus[t]||(this.eventBus[t]=[]),this.eventBus[t].push(e),()=>this.off(t,e)}off(t,e){this.eventBus[t]&&(this.eventBus[t]=this.eventBus[t].filter(t=>t!==e))}publish(t,e){this.eventBus[t]&&this.eventBus[t].forEach(t=>t(e))}},e={activeModules:{},config:{keepAliveSameModules:!0,debug:!1,autoWrap:!0,hoverPrefetch:!1,showErrorNotification:!1,persistState:!1,virtualizeDom:!1,transition:{enabled:!1,simulatedDelay:0,module:null,useViewTransitions:!1}},setConfig(t){this.config={...this.config,...t}},log(...t){this.config.debug&&console.log("[BareMetal Loader]",...t)},async prepare(t){if(this.config.transition&&this.config.transition.enabled){const e=this.config.transition.module||"/src/transition.js";t.__baremetal_transition=e}const e={},i=[],o={};for(const[o,n]of Object.entries(this.activeModules)){const s="__baremetal_transition"===o,r="string"==typeof t[o]?t[o]:t[o]?t[o].path:null;(this.config.keepAliveSameModules||s)&&r===n.path?(e[o]=n,this.log(`Keep-Alive: ${o} (${n.path})`)):i.push(n)}for(const t of i)if(this.log(`Destroying module: ${t.path}`),t.module&&"function"==typeof t.module.destroy)try{t.module.destroy()}catch(e){console.error(`Error destroying module ${t.path}`,e)}for(const[i,n]of Object.entries(t))e[i]||(o[i]=n);return this.activeModules=e,o},async loadPrepared(e){const i=Object.keys(e).length;let o=0;const n=Object.entries(e).map(async([e,n])=>{const s="string"==typeof n?n:n.path,a="string"==typeof n?null:n.lazy;this.log(`Preparing module: ${s}`);const l=async()=>{try{const i=new URL(s,document.baseURI).href,o=this.config.debug?`${i}?t=${Date.now()}`:i;let n;if(this.config.autoWrap){const t=await fetch(o);if(!t.ok)throw new Error(`Failed to fetch ${o}`);const e=await t.text();if(/export\s+(function|const|let|var)\s+mount\b/.test(e)||/export\s+\{.*?\bmount\b.*?\}/.test(e))n=await import(o);else{console.warn(`[BareMetal] WARNING: Module ${s} does not explicitly export a mount() function. Auto-wrapping it...`);const t=`export async function mount(context) { const { state } = context; ${e} }`,i=new Blob([t],{type:"application/javascript"}),o=URL.createObjectURL(i);n=await import(o),URL.revokeObjectURL(o)}}else n=await import(o);if("function"==typeof n.mount){this.log(`Mounting module: ${s}`);const i={state:t};if(this.config.virtualizeDom){const{virtualize:t}=await Promise.resolve().then(function(){return r});i.virtualize=t}const o=[];i.onCleanup=t=>o.push(t);const a=await n.mount(i),l=()=>{if(o.forEach(t=>{try{t()}catch(t){console.error(t)}}),a&&"function"==typeof a.destroy)try{a.destroy()}catch(t){console.error(t)}};this.activeModules[e]={path:s,module:{destroy:l}}}else console.error(`[BareMetal] Module ${s} failed to provide a mount function even after wrapping.`)}catch(t){console.error(`Failed to load module: ${s}`,t)}};if(a){const e=document.querySelector(a);if(e&&window.IntersectionObserver){this.log(`Deferred loading of module ${s} until ${a} is visible`);const n=new IntersectionObserver(t=>{t[0].isIntersecting&&(n.disconnect(),l())});if(n.observe(e),o++,i>0){const e=50+o/i*50;t.publish("ROUTE_PROGRESS",{url:window.location.pathname,progress:e})}return Promise.resolve()}await l()}else await l();if(o++,i>0){const e=50+o/i*50;t.publish("ROUTE_PROGRESS",{url:window.location.pathname,progress:e})}});await Promise.all(n),t.publish("ROUTE_END",{url:window.location.pathname})},async load(t){const e=await this.prepare(t);await this.loadPrepared(e)}};function i(t){return e.load(t)}const o={htmlCache:{},scrollMemory:{},historyStack:[],currentAbortController:null,lastPathAndSearch:"",init(){this.lastPathAndSearch=window.location.pathname+window.location.search,"scrollRestoration"in history&&(history.scrollRestoration="manual"),window.addEventListener("popstate",this.handleRoute.bind(this)),document.body.addEventListener("click",t=>{const e=t.target.closest("a");e&&(e.origin!==window.location.origin||"_blank"===e.target||e.rel&&e.rel.includes("noreferrer")||e.hasAttribute("download")||"ignore"===e.getAttribute("data-baremetal")||(e.getAttribute("href")||"").startsWith("#")||(t.preventDefault(),this.historyStack.push(window.location.pathname),this.scrollMemory[window.location.pathname]=window.scrollY,history.pushState(null,"",e.href),this.handleRoute()))}),document.body.addEventListener("mouseover",t=>{if(!e.config.hoverPrefetch)return;const i=t.target.closest("a");i&&(i.origin!==window.location.origin||"_blank"===i.target||i.hasAttribute("download")||"ignore"===i.getAttribute("data-baremetal")||(i.getAttribute("href")||"").startsWith("#")||this.htmlCache[i.href]||(this.htmlCache[i.href]="fetching",fetch(i.href).then(t=>{if(t.ok)return t.text();throw new Error("Failed to prefetch")}).then(t=>this.htmlCache[i.href]=t).catch(()=>delete this.htmlCache[i.href])))})},back(){if(this.historyStack.length>0){const t=this.historyStack.pop();history.pushState(null,"",t),this.handleRoute()}else history.back()},reload(){window.location.reload()},async handleRoute(i){const o=window.location.pathname+window.location.search;if(this.lastPathAndSearch===o)return;this.lastPathAndSearch=o,this.currentAbortController&&this.currentAbortController.abort(),this.currentAbortController=new AbortController;const n=this.currentAbortController.signal,s=window.location.pathname;try{let o;e.log(`Navigating to ${s}`),t.publish("ROUTE_START",{url:s}),e.config.transition&&e.config.transition.simulatedDelay&&(t.publish("ROUTE_PROGRESS",{url:s,progress:10}),await new Promise(t=>setTimeout(t,e.config.transition.simulatedDelay/2)),t.publish("ROUTE_PROGRESS",{url:s,progress:30}),await new Promise(t=>setTimeout(t,e.config.transition.simulatedDelay/2)));const r=new URL(s,document.baseURI).href;if(e.config.hoverPrefetch&&this.htmlCache[r]&&"fetching"!==this.htmlCache[r])o=this.htmlCache[r],e.log(`Used pre-fetched cache for ${s}`);else{const t=await fetch(s,{signal:n});if(t.redirected){const i=new URL(t.url);e.log(`Redirect detected to ${i.pathname}. Continuing SPA transition to new destination.`),history.replaceState(null,"",i.pathname+i.search),this.lastPathAndSearch=i.pathname+i.search}if(!t.ok)throw new Error(`HTTP error! status: ${t.status}`);o=await t.text()}t.publish("ROUTE_PROGRESS",{url:s,progress:50});const a=(new DOMParser).parseFromString(o,"text/html");let l=null;const c=a.querySelectorAll("script");for(const t of c)if(t.textContent.includes("loader(")){const e=t.textContent.match(/loader\s*\(\s*(\{[\s\S]*?\})\s*\)/);if(e&&e[1])try{l=new Function("return "+e[1])()}catch(i){console.error("Failed to parse loader config in new page",i)}}if(!l)return e.log(`No BareMetal config found on ${s}. Falling back to native navigation.`),void window.location.assign(s);const h=await e.prepare(l);document.title=a.title;document.head.querySelectorAll('link[data-baremetal="style"], style[data-baremetal="style"]').forEach(t=>t.remove());a.head.querySelectorAll('link[data-baremetal="style"], style[data-baremetal="style"]').forEach(t=>document.head.appendChild(t.cloneNode(!0)));const d=[];document.querySelectorAll("[data-baremetal-preserve]").forEach(t=>{if(t.id&&a.getElementById(t.id)){const e=document.createElement("div");t.parentNode.replaceChild(e,t),d.push(t)}});const u=document.getElementById("baremetal-transition-root");u&&u.parentNode.removeChild(u);const p=()=>{document.body.innerHTML=a.body.innerHTML,d.forEach(t=>{const e=document.getElementById(t.id);e&&(Array.from(t.attributes).forEach(e=>{"id"!==e.name&&"data-baremetal-preserve"!==e.name&&t.removeAttribute(e.name)}),Array.from(e.attributes).forEach(e=>{"id"!==e.name&&t.setAttribute(e.name,e.value)}),e.parentNode.replaceChild(t,e))}),u&&document.body.appendChild(u),t.publish("DOM_SWAPPED",null)},f=()=>{p(),window.scrollTo(0,this.scrollMemory[s]||0)};e.config.transition&&e.config.transition.useViewTransitions&&document.startViewTransition?document.startViewTransition(()=>{f()}):f(),await e.loadPrepared(h)}catch(i){if("AbortError"===i.name)return void e.log(`Aborted fetch for ${s} due to new navigation.`);if(console.error("Routing error:",i),t.publish("ROUTE_ERROR",{url:s,error:i.message}),i instanceof TypeError&&i.message.includes("fetch")){if(e.log(`Fetch failed for ${s} (possible CORS/Redirect). Falling back to native navigation.`),this.historyStack.length>0){const t=this.historyStack.pop();history.replaceState(null,"",t)}return void window.location.assign(s)}if(e.config.showErrorNotification){if(this.historyStack.length>0){const t=this.historyStack.pop();history.replaceState(null,"",t)}const t=document.createElement("div");t.style.position="fixed",t.style.bottom="20px",t.style.left="20px",t.style.background="#e74c3c",t.style.color="white",t.style.padding="15px 20px",t.style.borderRadius="8px",t.style.boxShadow="0 4px 12px rgba(0,0,0,0.15)",t.style.zIndex="999999",t.style.fontFamily="sans-serif",t.style.transition="opacity 0.3s ease",t.innerHTML=`<strong>Navigation Failed:</strong> ${i.message}`,document.body.appendChild(t),setTimeout(()=>{t.style.opacity="0",setTimeout(()=>t.remove(),300)},4e3)}else{if(this.historyStack.length>0){const t=this.historyStack.pop();history.replaceState(null,"",t)}window.location.assign(s)}}}};class n{constructor(t,e,i,o,n=20){this.container=document.getElementById(t),this.container&&(this.items=e,this.renderRow=i,this.itemHeight=o,this.visibleCount=n,this.totalHeight=this.items.length*this.itemHeight,this.container.style.overflowY="auto",this.container.style.position="relative",this.innerWrapper=document.createElement("div"),this.innerWrapper.style.height=`${this.totalHeight}px`,this.innerWrapper.style.position="relative",this.container.appendChild(this.innerWrapper),this.startIndex=0,this.onScroll=this.onScroll.bind(this),this.container.addEventListener("scroll",this.onScroll),this.render())}onScroll(){const t=this.container.scrollTop,e=Math.max(0,Math.floor(t/this.itemHeight)-2);e!==this.startIndex&&(this.startIndex=e,this.render())}render(){this.innerWrapper.innerHTML="";const t=Math.min(this.items.length-1,this.startIndex+this.visibleCount+4);for(let e=this.startIndex;e<=t;e++){const t=this.renderRow(this.items[e],e);t.style.position="absolute",t.style.top=e*this.itemHeight+"px",t.style.width="100%",this.innerWrapper.appendChild(t)}}destroy(){this.container&&(this.container.removeEventListener("scroll",this.onScroll),this.container.innerHTML="")}}const s=(t,e,i,o,s)=>new n(t,e,i,o,s);var r=Object.freeze({__proto__:null,Virtualizer:n,virtualize:s});const a={state:t,events:t,loader:i,router:o,virtualize:s,init(i={}){void 0!==i.debug&&e.setConfig({debug:i.debug}),void 0!==i.keepAliveSameModules&&e.setConfig({keepAliveSameModules:i.keepAliveSameModules}),void 0!==i.transition&&e.setConfig({transition:i.transition}),void 0!==i.autoWrap&&e.setConfig({autoWrap:i.autoWrap}),void 0!==i.hoverPrefetch&&e.setConfig({hoverPrefetch:i.hoverPrefetch}),void 0!==i.showErrorNotification&&e.setConfig({showErrorNotification:i.showErrorNotification}),void 0!==i.persistState&&(e.setConfig({persistState:i.persistState}),i.persistState&&(window.__baremetal_persist_state=!0,t.initPersistence())),void 0!==i.virtualizeDom&&e.setConfig({virtualizeDom:i.virtualizeDom}),o.init(),e.log("Initialized BareMetal Engine with config:",i)}};export{a as BareMetal,i as loader};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baremetal.js",
3
- "version": "1.2.5",
3
+ "version": "1.2.6",
4
4
  "description": "A lightweight, dependency-free Vanilla JavaScript SPA engine prioritizing extreme performance, native browser features, and explicit lifecycle management.",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/router.js CHANGED
@@ -25,6 +25,7 @@ export const Router = {
25
25
  anchor.target === '_blank' ||
26
26
  (anchor.rel && anchor.rel.includes('noreferrer')) ||
27
27
  anchor.hasAttribute('download') ||
28
+ anchor.getAttribute('data-baremetal') === 'ignore' ||
28
29
  (anchor.getAttribute('href') || '').startsWith('#')
29
30
  ) {
30
31
  return;
@@ -47,6 +48,7 @@ export const Router = {
47
48
  anchor.origin === window.location.origin &&
48
49
  anchor.target !== '_blank' &&
49
50
  !anchor.hasAttribute('download') &&
51
+ anchor.getAttribute('data-baremetal') !== 'ignore' &&
50
52
  !(anchor.getAttribute('href') || '').startsWith('#') &&
51
53
  !this.htmlCache[anchor.href]
52
54
  ) {
@@ -226,6 +228,18 @@ export const Router = {
226
228
  console.error("Routing error:", err);
227
229
  stateManager.publish('ROUTE_ERROR', { url, error: err.message });
228
230
 
231
+ // If fetch fails due to CORS (e.g. following an external redirect) or network issues,
232
+ // fall back to native browser navigation immediately so the browser can handle it natively.
233
+ if (err instanceof TypeError && err.message.includes('fetch')) {
234
+ Loader.log(`Fetch failed for ${url} (possible CORS/Redirect). Falling back to native navigation.`);
235
+ if (this.historyStack.length > 0) {
236
+ const prev = this.historyStack.pop();
237
+ history.replaceState(null, '', prev);
238
+ }
239
+ window.location.assign(url);
240
+ return;
241
+ }
242
+
229
243
  if (Loader.config.showErrorNotification) {
230
244
 
231
245
  if (this.historyStack.length > 0) {