@webjsdev/server 0.8.76 → 0.8.77

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": "@webjsdev/server",
3
- "version": "0.8.76",
3
+ "version": "0.8.77",
4
4
  "type": "module",
5
5
  "description": "The server for WebJs, a full-stack JavaScript framework built on web components with server-side rendering and no build step. Provides the file-based router, SSR, server actions, route handlers, middleware, and live reload on Node 24+ or Bun.",
6
6
  "main": "index.js",
@@ -1607,3 +1607,66 @@ export function lazyComponentFiles(components, elidable) {
1607
1607
  }
1608
1608
  return out;
1609
1609
  }
1610
+
1611
+ /** `import <bindings> from 'x'` (the bindings part is captured). */
1612
+ const BINDING_IMPORT_RE = /\bimport\s+(type\s+)?([\w$*{}\s,]+?)\s+from\s+(['"])([^'"]+)\3/g;
1613
+ /** `export { a } from 'x'` / `export * from 'x'` (a re-export needs the module too). */
1614
+ const REEXPORT_RE = /\bexport\s+(type\s+)?(?:\*|\{[^}]*\}|\*\s+as\s+[\w$]+)\s+from\s+(['"])([^'"]+)\2/g;
1615
+
1616
+ /**
1617
+ * Is this import clause types only (`import type { A }`, or every named
1618
+ * specifier marked `type`)? Those are erased before the browser sees them.
1619
+ *
1620
+ * @param {string | undefined} typeKw
1621
+ * @param {string} clause
1622
+ */
1623
+ function typeOnlyClause(typeKw, clause) {
1624
+ if (typeKw) return true;
1625
+ const m = clause.trim().match(/^\{([\s\S]*)\}$/);
1626
+ if (!m) return false;
1627
+ const names = m[1].split(',').map((s) => s.trim()).filter(Boolean);
1628
+ return names.length > 0 && names.every((n) => /^type\s/.test(n));
1629
+ }
1630
+
1631
+ /**
1632
+ * The lazy component files some module imports WITH bindings (#1524). A
1633
+ * binding import loads the module eagerly whatever the class says, because the
1634
+ * importer needs the value when it runs. Such a file is not lazy in practice,
1635
+ * so the caller drops it from the lazy set: otherwise the preload walks would
1636
+ * skip a module the browser still fetches up front, which only makes it slower.
1637
+ *
1638
+ * Only importers whose graph edges reach a lazy file are read. A server file is
1639
+ * never served, so its imports do not count.
1640
+ *
1641
+ * @param {Map<string, string[]>} lazyFiles
1642
+ * @param {import('./module-graph.js').ModuleGraph | undefined} moduleGraph
1643
+ * @param {(file: string) => Promise<string>} readFileFn
1644
+ * @param {(spec: string, fromFile: string, appDir: string) => (string|null)} resolveImport
1645
+ * @param {string} appDir
1646
+ * @returns {Promise<Set<string>>}
1647
+ */
1648
+ export async function bindingImportedLazyFiles(lazyFiles, moduleGraph, readFileFn, resolveImport, appDir) {
1649
+ /** @type {Set<string>} */
1650
+ const out = new Set();
1651
+ if (!lazyFiles.size || !moduleGraph) return out;
1652
+ for (const [importer, deps] of moduleGraph) {
1653
+ if (SERVER_FILE_RE.test(importer)) continue;
1654
+ let reaches = false;
1655
+ for (const d of deps) if (lazyFiles.has(d) && !out.has(d)) { reaches = true; break; }
1656
+ if (!reaches) continue;
1657
+ let src;
1658
+ try { src = await readFileFn(importer); } catch { continue; }
1659
+ const code = redactStringsAndTemplates(maskComments(src));
1660
+ for (const m of code.matchAll(BINDING_IMPORT_RE)) {
1661
+ if (typeOnlyClause(m[1], m[2])) continue;
1662
+ const abs = resolveImport(m[4], importer, appDir);
1663
+ if (abs && lazyFiles.has(abs)) out.add(abs);
1664
+ }
1665
+ for (const m of code.matchAll(REEXPORT_RE)) {
1666
+ if (m[1]) continue;
1667
+ const abs = resolveImport(m[3], importer, appDir);
1668
+ if (abs && lazyFiles.has(abs)) out.add(abs);
1669
+ }
1670
+ }
1671
+ return out;
1672
+ }
@@ -31,9 +31,9 @@ import { mintNonce, buildCspHeader, cspHeaderName } from '../csp.js';
31
31
  import { propagateTrustedRemoteIp } from '../rate-limit.js';
32
32
  import { reachedBareImports, resolveVendorImports, clearVendorCache, hasVendorPin, readPinFile, prunePinToReachable } from '../vendor.js';
33
33
  import { browserEntryFiles } from '../browser-entries.js';
34
- import { buildModuleGraph, seenFilesFor, appImportsMap, reachableFromEntries } from '../module-graph.js';
34
+ import { buildModuleGraph, seenFilesFor, appImportsMap, reachableFromEntries, resolveImport } from '../module-graph.js';
35
35
  import { primeComponentRegistry, findOrphanComponents, scanComponents } from '../component-scanner.js';
36
- import { analyzeElision, lazyComponentFiles } from '../component-elision.js';
36
+ import { analyzeElision, lazyComponentFiles, bindingImportedLazyFiles } from '../component-elision.js';
37
37
 
38
38
  import { setVendorEntries, setCoreInstall, publishBuildId, setAppSourceId, setBasePath, basePath, setImportAliasEntries, importAliasBrowserEntries } from '../importmap.js';
39
39
  import { stripBasePath, withBasePath } from '../base-path.js';
@@ -614,6 +614,10 @@ export async function createRequestHandler(opts) {
614
614
  state.inertRouteModules = r.inertRouteModules;
615
615
  state.importOnlyRouteModules = r.importOnlyRouteModules;
616
616
  state.lazyComponentFiles = lazyComponentFiles(components, state.elidableComponents);
617
+ // A lazy component some module imports WITH bindings loads eagerly
618
+ // anyway, so it keeps its preload hint and its imports as written.
619
+ for (const f of await bindingImportedLazyFiles(state.lazyComponentFiles, state.moduleGraph,
620
+ (f) => readFile(f, 'utf8'), resolveImport, appDir)) state.lazyComponentFiles.delete(f);
617
621
  // Dev live-reload classification (#1398): derive the three sets the
618
622
  // watch-event classifier reads. `browserBoundFiles` above is the
619
623
  // AUTHORIZATION gate (it walks from every entry, elided ones