@erickxavier/no-js 1.3.1 → 1.4.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erickxavier/no-js",
3
- "version": "1.3.1",
3
+ "version": "1.4.2",
4
4
  "description": "The HTML-first reactive framework — build dynamic web apps with just HTML attributes, no JavaScript required",
5
5
  "main": "dist/cjs/no.js",
6
6
  "module": "dist/esm/no.js",
package/src/dom.js CHANGED
@@ -161,6 +161,20 @@ export async function _loadTemplateElement(tpl) {
161
161
  // Content-include templates: load nested ones now.
162
162
  if (!tpl.hasAttribute("route")) {
163
163
  await _loadRemoteTemplates(tpl.content || tpl);
164
+ } else if (useCache && tpl.content) {
165
+ // Route templates: pre-warm HTML cache for nested subtemplates so
166
+ // navigation finds cache hits instead of issuing network requests.
167
+ // Only the HTML is fetched — no DOM processing or skeleton insertion.
168
+ const nested = tpl.content.querySelectorAll("template[src]");
169
+ const warmups = [...nested].map((sub) => {
170
+ const subSrc = sub.getAttribute("src");
171
+ const subUrl = _resolveTemplateSrc(subSrc, sub);
172
+ if (_templateHtmlCache.has(subUrl)) return;
173
+ return fetch(subUrl).then((r) => r.text()).then((h) => {
174
+ _templateHtmlCache.set(subUrl, h);
175
+ }).catch(() => {});
176
+ });
177
+ Promise.all(warmups);
164
178
  }
165
179
  // Remove loading placeholder once real content is ready
166
180
  if (loadingMarker) loadingMarker.remove();
package/src/index.js CHANGED
@@ -170,7 +170,7 @@ const NoJS = {
170
170
  if (_config.i18n.persist && typeof localStorage !== "undefined") {
171
171
  try {
172
172
  const saved = localStorage.getItem("nojs-locale");
173
- if (saved && _i18n.locales[saved]) { _i18n._locale = saved; return; }
173
+ if (saved) { _i18n._locale = saved; return; }
174
174
  } catch (_) {}
175
175
  }
176
176
 
@@ -216,7 +216,7 @@ const NoJS = {
216
216
  resolve,
217
217
 
218
218
  // Version
219
- version: "1.3.1",
219
+ version: "1.4.2",
220
220
  };
221
221
 
222
222
  export default NoJS;
package/src/router.js CHANGED
@@ -242,6 +242,63 @@ export function _createRouter() {
242
242
  });
243
243
  }
244
244
 
245
+ function _prefetchRoutes() {
246
+ const outletEls = document.querySelectorAll("[route-view]");
247
+ for (const outletEl of outletEls) {
248
+ const rawSrc = outletEl.getAttribute("src") || _config.router.templates || "";
249
+ if (!rawSrc) continue;
250
+ const baseSrc = rawSrc.replace(/\/?$/, "/");
251
+ const ext = outletEl.getAttribute("ext") || _config.router.ext || ".html";
252
+ const indexName = outletEl.getAttribute("route-index") || "index";
253
+ const outletName = (outletEl.getAttribute("route-view") || "").trim() || "default";
254
+
255
+ // Collect routes from links, keeping most aggressive lazy level per path
256
+ const routeLazy = new Map();
257
+ document.querySelectorAll("[route]:not([route-view])").forEach((link) => {
258
+ const raw = link.getAttribute("route");
259
+ if (!raw) return;
260
+ const path = raw.split("?")[0].split("#")[0];
261
+ const lazy = link.getAttribute("lazy");
262
+ const prev = routeLazy.get(path);
263
+ if (!routeLazy.has(path) || lazy === "priority" ||
264
+ (prev === "ondemand" && lazy !== "ondemand")) {
265
+ routeLazy.set(path, lazy);
266
+ }
267
+ });
268
+
269
+ const priorityFetches = [];
270
+ const backgroundFetches = [];
271
+
272
+ for (const [path, lazy] of routeLazy) {
273
+ if (lazy === "ondemand" || path === current.path) continue;
274
+ const segment = path === "/" ? indexName : path.replace(/^\//, "");
275
+ const fullSrc = baseSrc + segment + ext;
276
+ const cacheKey = outletName + ":" + fullSrc;
277
+ if (_autoTemplateCache.has(cacheKey)) continue;
278
+
279
+ const tpl = document.createElement("template");
280
+ tpl.setAttribute("src", fullSrc);
281
+ tpl.setAttribute("route", path);
282
+ document.body.appendChild(tpl);
283
+ _autoTemplateCache.set(cacheKey, tpl);
284
+ _log("[ROUTER] Prefetch:", path, "→", fullSrc, lazy === "priority" ? "(priority)" : "(background)");
285
+
286
+ if (outletEl.hasAttribute("i18n-ns")) {
287
+ tpl.setAttribute("i18n-ns", segment);
288
+ }
289
+
290
+ if (lazy === "priority") priorityFetches.push(tpl);
291
+ else backgroundFetches.push(tpl);
292
+ }
293
+
294
+ if (priorityFetches.length || backgroundFetches.length) {
295
+ Promise.all(priorityFetches.map(_loadTemplateElement)).then(() => {
296
+ backgroundFetches.forEach(_loadTemplateElement);
297
+ });
298
+ }
299
+ }
300
+ }
301
+
245
302
  const router = {
246
303
  get current() {
247
304
  return current;
@@ -333,6 +390,9 @@ export function _createRouter() {
333
390
  window.location.pathname.replace(_config.router.base, "") || "/";
334
391
  await navigate(path, true);
335
392
  }
393
+
394
+ // Prefetch route templates declared via <a route> links
395
+ _prefetchRoutes();
336
396
  },
337
397
  };
338
398