@kudzujs/core 0.8.22 → 0.8.24

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.
@@ -0,0 +1,29 @@
1
+ import { dirname, extname, join, relative, resolve } from "node:path"
2
+ import ts from "typescript"
3
+
4
+ const root = process.cwd()
5
+
6
+ export function resolveSourceImport(importer, specifier, sourceFiles) {
7
+ const base = resolve(dirname(importer), specifier)
8
+ const extension = extname(base)
9
+ const stem = /\.(?:js|jsx|ts|tsx)$/.test(extension) ? base.slice(0, -extension.length) : base
10
+ const candidates = extension === ".ts" || extension === ".tsx"
11
+ ? [base]
12
+ : [`${stem}.ts`, `${stem}.tsx`, join(stem, "index.ts"), join(stem, "index.tsx")]
13
+ const matches = candidates.filter(candidate => sourceFiles.has(candidate))
14
+ if (matches.length !== 1) throw new Error(`${relative(root, importer)} Relative import ${JSON.stringify(specifier)} must resolve to one TypeScript file in src/`)
15
+ return matches[0]
16
+ }
17
+
18
+ export function runtimeModuleReference(node) {
19
+ if (ts.isExportDeclaration(node)) return !node.isTypeOnly && (!node.exportClause || !ts.isNamedExports(node.exportClause) || node.exportClause.elements.some(entry => !entry.isTypeOnly))
20
+ const clause = node.importClause
21
+ if (!clause) return true
22
+ if (clause.isTypeOnly) return false
23
+ if (clause.name || clause.namedBindings && ts.isNamespaceImport(clause.namedBindings)) return true
24
+ return clause.namedBindings?.elements.some(entry => !entry.isTypeOnly) ?? false
25
+ }
26
+
27
+ export function parseSourceFile(file, source) {
28
+ return ts.createSourceFile(file, source, ts.ScriptTarget.ES2022, true, file.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS)
29
+ }
@@ -4,11 +4,12 @@ import { dirname, relative, resolve, sep } from "node:path"
4
4
  import { build as bundle } from "esbuild"
5
5
  import ts from "typescript"
6
6
  import { containsJsx, isUnshadowedGlobal, nearestFunction, sourceNodeError } from "./ast-helpers.mjs"
7
+ import { assetPath } from "./path-helpers.mjs"
8
+ import { parseSourceFile, resolveSourceImport, runtimeModuleReference } from "./source-graph.mjs"
7
9
 
8
10
  export function createWorkerCompiler({
9
11
  root,
10
12
  sourceDirectory,
11
- outputDirectory,
12
13
  assetPath,
13
14
  parseSourceFile,
14
15
  resolveSourceImport,
@@ -158,7 +159,7 @@ export function createWorkerCompiler({
158
159
  const entry = resolve(root, metadata.entryPoint)
159
160
  const rootReferences = references.filter(reference => resolve(sourceDirectory, reference.root) === entry)
160
161
  const outputFile = resolve(root, output)
161
- const url = assetPath(base, relative(outputDirectory, outputFile).replaceAll(sep, "/"))
162
+ const url = assetPath(base, relative(resolve(assetsDirectory, ".."), outputFile).replaceAll(sep, "/"))
162
163
  for (const reference of rootReferences) emitted.set(reference.placeholder, url)
163
164
  }
164
165
  for (const reference of references) if (!emitted.has(reference.placeholder)) throw new Error(`Worker entry was not emitted: ${reference.root}`)
@@ -167,3 +168,9 @@ export function createWorkerCompiler({
167
168
 
168
169
  return { candidate, emit, rejectConstructions, rejectOrdinaryImports, rewriteEffect }
169
170
  }
171
+
172
+ export function emitWorkers(references, sourceFiles, assetsDirectory, base, minify) {
173
+ const root = process.cwd()
174
+ const sourceDirectory = resolve(root, "src")
175
+ return createWorkerCompiler({ root, sourceDirectory, assetPath, parseSourceFile, resolveSourceImport, runtimeModuleReference }).emit(references, sourceFiles, assetsDirectory, base, minify)
176
+ }
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto"
2
2
  import { readFile, stat, watch } from "node:fs/promises"
3
3
  import { createServer } from "node:http"
4
4
  import { extname, join, resolve, sep } from "node:path"
5
+ import { browserPath, withBase } from "./compiler/path-helpers.mjs"
5
6
  import { stateSchema } from "./dev-state.js"
6
7
 
7
8
  const devClient = (session, revision, schema) => `<script>(()=>{const show=event=>{let box=document.getElementById("__kudzu_error");if(!box){box=document.createElement("div");box.id="__kudzu_error";box.setAttribute("role","alert");box.setAttribute("aria-live","assertive");box.style.cssText="position:fixed;inset:0;z-index:2147483647;overflow:auto;padding:2rem;background:#200;color:#fff;font:16px/1.5 ui-monospace,monospace";const title=document.createElement("strong"),text=document.createElement("pre");title.textContent="Kudzu build error";text.style.whiteSpace="pre-wrap";box.append(title,text);document.body.append(box)}box.querySelector("pre").textContent=event.data};const schema=${inlineJson(schema)},route=location.pathname+location.search+location.hash,urls=[...document.querySelectorAll('script[type="module"][src]')].map(node=>node.src).filter(url=>/\/assets\/kudzu(?:-(?:binding|list|native))?\.js$/.test(new URL(url).pathname));const devImport=import("/__kudzu_dev.js"),runtimeImports=Promise.allSettled(urls.map(url=>import(url)));const ready=(async()=>{const dev=await devImport,modules=await runtimeImports,runtime=modules.find(result=>result.status==="fulfilled"&&result.value.browserState instanceof Map&&typeof result.value.commitDom==="function")?.value;try{dev.restoreState(sessionStorage,route,runtime?.browserState,schema,runtime?.commitDom)}catch{}return{dev,runtime}})().catch(()=>({}));const events=new EventSource("/__kudzu_reload?session=${session}&revision=${revision}");let reloading=false;events.addEventListener("reload",async()=>{if(reloading)return;reloading=true;try{const{dev,runtime}=await ready;dev?.snapshotState(sessionStorage,route,runtime?.browserState,schema)}catch{}location.reload()});events.addEventListener("build-error",show)})()</script>`
@@ -216,14 +217,6 @@ function escapeHtml(value) {
216
217
  return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;")
217
218
  }
218
219
 
219
- function browserPath(path) {
220
- return path ? new URL(path, "http://kudzu.local").pathname : ""
221
- }
222
-
223
- function withBase(base, path) {
224
- return base ? `${base}${path}` : path
225
- }
226
-
227
220
  async function exists(path) {
228
221
  try {
229
222
  await stat(path)
@@ -282,7 +282,8 @@ function updateList(list) {
282
282
  if (run.firstChild) parent.insertBefore(run, list.boundary)
283
283
  ordered = true
284
284
  } else parent.insertBefore(additions, list.boundary)
285
- if (addedNodes) for (const node of addedNodes) mountDom(node)
285
+ if (addedNodes?.length > 32 && addedNodes.length * 2 > next.length && addedNodes.length * 2 > parent.children.length && !list.descriptor.children && !list.descriptor.ownerField) mountDom(parent)
286
+ else if (addedNodes) for (const node of addedNodes) mountDom(node)
286
287
  list.container ??= parent
287
288
  }
288
289
  let anchor = list.boundary
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kudzujs/core",
3
- "version": "0.8.22",
3
+ "version": "0.8.24",
4
4
  "description": "HTML-first TSX framework with synchronous state semantics and no virtual DOM",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -55,6 +55,8 @@
55
55
  "check": "tsc --noEmit && tsc -p test/fixtures/tsconfig.json --noEmit && node ./bin/kudzu.mjs build",
56
56
  "test": "node --test test/*.test.mjs",
57
57
  "benchmark": "node test/performance.mjs",
58
+ "benchmark:keyed": "node test/keyed-performance.mjs",
59
+ "benchmark:commerce": "node test/commerce-build-performance.mjs",
58
60
  "prepublishOnly": "npm run check && npm test",
59
61
  "deploy": "wrangler deploy",
60
62
  "preview": "wrangler dev"