@webjsdev/server 0.8.76 → 0.8.78

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.78",
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,71 @@ export function lazyComponentFiles(components, elidable) {
1607
1607
  }
1608
1608
  return out;
1609
1609
  }
1610
+
1611
+ /** A test module (the same rule the component scanner skips). */
1612
+ const TEST_FILE_RE = /\.(test|spec)\.m?[jt]sx?$/;
1613
+
1614
+ /** `import <bindings> from 'x'` (the bindings part is captured). */
1615
+ const BINDING_IMPORT_RE = /\bimport\s+(type\s+)?([\w$*{}\s,]+?)\s+from\s+(['"])([^'"]+)\3/g;
1616
+ /** `export { a } from 'x'` / `export * from 'x'` (a re-export needs the module too). */
1617
+ const REEXPORT_RE = /\bexport\s+(type\s+)?(?:\*|\{[^}]*\}|\*\s+as\s+[\w$]+)\s+from\s+(['"])([^'"]+)\2/g;
1618
+
1619
+ /**
1620
+ * Is this import clause types only (`import type { A }`, or every named
1621
+ * specifier marked `type`)? Those are erased before the browser sees them.
1622
+ *
1623
+ * @param {string | undefined} typeKw
1624
+ * @param {string} clause
1625
+ */
1626
+ function typeOnlyClause(typeKw, clause) {
1627
+ if (typeKw) return true;
1628
+ const m = clause.trim().match(/^\{([\s\S]*)\}$/);
1629
+ if (!m) return false;
1630
+ const names = m[1].split(',').map((s) => s.trim()).filter(Boolean);
1631
+ return names.length > 0 && names.every((n) => /^type\s/.test(n));
1632
+ }
1633
+
1634
+ /**
1635
+ * The lazy component files some module imports WITH bindings (#1524). A
1636
+ * binding import loads the module eagerly whatever the class says, because the
1637
+ * importer needs the value when it runs. Such a file is not lazy in practice,
1638
+ * so the caller drops it from the lazy set: otherwise the preload walks would
1639
+ * skip a module the browser still fetches up front, which only makes it slower.
1640
+ *
1641
+ * Only importers whose graph edges reach a lazy file are read. A server file is
1642
+ * never served, and a test file (`*.test.*` / `*.spec.*`) never ships to a
1643
+ * page, so their imports do not count: a component's own browser test imports
1644
+ * it with bindings, and that must not make it eager in the app.
1645
+ *
1646
+ * @param {Map<string, string[]>} lazyFiles
1647
+ * @param {import('./module-graph.js').ModuleGraph | undefined} moduleGraph
1648
+ * @param {(file: string) => Promise<string>} readFileFn
1649
+ * @param {(spec: string, fromFile: string, appDir: string) => (string|null)} resolveImport
1650
+ * @param {string} appDir
1651
+ * @returns {Promise<Set<string>>}
1652
+ */
1653
+ export async function bindingImportedLazyFiles(lazyFiles, moduleGraph, readFileFn, resolveImport, appDir) {
1654
+ /** @type {Set<string>} */
1655
+ const out = new Set();
1656
+ if (!lazyFiles.size || !moduleGraph) return out;
1657
+ for (const [importer, deps] of moduleGraph) {
1658
+ if (SERVER_FILE_RE.test(importer) || TEST_FILE_RE.test(importer)) continue;
1659
+ let reaches = false;
1660
+ for (const d of deps) if (lazyFiles.has(d) && !out.has(d)) { reaches = true; break; }
1661
+ if (!reaches) continue;
1662
+ let src;
1663
+ try { src = await readFileFn(importer); } catch { continue; }
1664
+ const code = redactStringsAndTemplates(maskComments(src));
1665
+ for (const m of code.matchAll(BINDING_IMPORT_RE)) {
1666
+ if (typeOnlyClause(m[1], m[2])) continue;
1667
+ const abs = resolveImport(m[4], importer, appDir);
1668
+ if (abs && lazyFiles.has(abs)) out.add(abs);
1669
+ }
1670
+ for (const m of code.matchAll(REEXPORT_RE)) {
1671
+ if (m[1]) continue;
1672
+ const abs = resolveImport(m[3], importer, appDir);
1673
+ if (abs && lazyFiles.has(abs)) out.add(abs);
1674
+ }
1675
+ }
1676
+ return out;
1677
+ }
@@ -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