@llui/vite-plugin 0.0.22 → 0.0.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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,MAAM,CAAA;AAOjD,OAAO,EAAY,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAEhE,YAAY,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAqBtD,MAAM,WAAW,iBAAiB;IAChC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IAExB;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IAEvB;;;;;;;;;;OAUG;IACH,gBAAgB,CAAC,EAAE,SAAS,cAAc,EAAE,CAAA;IAE5C;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAsCD,MAAM,CAAC,OAAO,UAAU,IAAI,CAAC,OAAO,GAAE,iBAAsB,GAAG,MAAM,CA2RpE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,MAAM,CAAA;AAOjD,OAAO,EAAY,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAEhE,YAAY,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAqBtD,MAAM,WAAW,iBAAiB;IAChC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IAExB;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IAEvB;;;;;;;;;;OAUG;IACH,gBAAgB,CAAC,EAAE,SAAS,cAAc,EAAE,CAAA;IAE5C;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAsCD,MAAM,CAAC,OAAO,UAAU,IAAI,CAAC,OAAO,GAAE,iBAAsB,GAAG,MAAM,CA4SpE"}
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { existsSync, readFileSync, writeFileSync, watch as fsWatch } from 'node:
3
3
  import { dirname, relative, resolve } from 'node:path';
4
4
  import { createRequire } from 'node:module';
5
5
  import { spawn } from 'node:child_process';
6
- import { transformLlui } from './transform.js';
6
+ import { transformLlui, transformUseClientSsr, hasUseClientDirective } from './transform.js';
7
7
  import { diagnose } from './diagnostics.js';
8
8
  /**
9
9
  * Locate the workspace root so we share the MCP active marker file
@@ -299,9 +299,25 @@ export default function llui(options = {}) {
299
299
  notifyMcpReady(server);
300
300
  });
301
301
  },
302
- transform(code, id) {
302
+ transform(code, id, options) {
303
303
  if (!id.endsWith('.ts') && !id.endsWith('.tsx'))
304
304
  return;
305
+ // `'use client'` directive — SSR builds replace the module with a
306
+ // stub so top-level imports and side effects never run on the
307
+ // server. Client builds pass through to the normal transform; the
308
+ // directive is effectively a no-op on the client.
309
+ if (options?.ssr && hasUseClientDirective(code)) {
310
+ const result = transformUseClientSsr(code, id);
311
+ if (result) {
312
+ const cwd = process.cwd();
313
+ const rel = relative(cwd, id);
314
+ const display = rel.startsWith('..') ? id : rel;
315
+ for (const warning of result.warnings) {
316
+ this.warn(`${display}: ${warning}`);
317
+ }
318
+ return { code: result.output, map: { mappings: '' } };
319
+ }
320
+ }
305
321
  const diagnostics = diagnose(code);
306
322
  if (diagnostics.length > 0) {
307
323
  // Prefix every diagnostic with `<file>:<line>:<col>` plus the
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Plugin, ViteDevServer } from 'vite'\nimport MagicString from 'magic-string'\nimport { existsSync, readFileSync, writeFileSync, watch as fsWatch, type FSWatcher } from 'node:fs'\nimport { dirname, relative, resolve } from 'node:path'\nimport { createRequire } from 'node:module'\nimport { spawn, type ChildProcess } from 'node:child_process'\nimport { transformLlui } from './transform.js'\nimport { diagnose, type DiagnosticRule } from './diagnostics.js'\n\nexport type { DiagnosticRule } from './diagnostics.js'\n\n/**\n * Locate the workspace root so we share the MCP active marker file\n * with @llui/mcp regardless of which subdirectory the dev server runs in.\n * Mirrors `findWorkspaceRoot` from @llui/mcp — duplicated to avoid a\n * vite-plugin → mcp dependency cycle. The contract must stay in sync.\n */\nfunction findWorkspaceRoot(start: string = process.cwd()): string {\n let dir = resolve(start)\n let lastPackageJson: string | null = null\n while (true) {\n if (existsSync(resolve(dir, 'pnpm-workspace.yaml'))) return dir\n if (existsSync(resolve(dir, '.git'))) return dir\n if (existsSync(resolve(dir, 'package.json'))) lastPackageJson = dir\n const parent = dirname(dir)\n if (parent === dir) return lastPackageJson ?? start\n dir = parent\n }\n}\n\nexport interface LluiPluginOptions {\n /**\n * Port for the MCP debug bridge. In dev mode, the runtime relay connects\n * to `ws://127.0.0.1:<port>` so an external `llui-mcp` server can forward\n * tool calls into the running app.\n *\n * When omitted, the plugin checks whether `@llui/mcp` is resolvable from\n * the Vite project root. If yes → defaults to `5200`. If no → stays\n * disabled. This means installing `@llui/mcp` (+ starting its server)\n * Just Works without an explicit config entry. Pass an explicit `false`\n * to opt out even when `@llui/mcp` is installed; pass a number to use\n * a non-default port. When enabled but the MCP server isn't running,\n * the plugin returns 404 from its discovery endpoint and the browser\n * silently skips the connection — no retry noise.\n */\n mcpPort?: number | false\n\n /**\n * Treat every compiler diagnostic as a build error.\n *\n * Default `false` — diagnostics are emitted via rollup's `this.warn` and\n * can be ignored. Set to `true` in CI so lint-style regressions (namespace\n * imports, bitmask overflow, spread-in-children, `.map()` on state, etc.)\n * fail the build without requiring a custom `build.rollupOptions.onwarn`\n * handler.\n */\n failOnWarning?: boolean\n\n /**\n * Silence specific diagnostic rules without disabling the whole lint\n * pass. Each message is tagged with a rule name (shown in brackets at\n * the start of every warning, e.g. `[spread-in-children]`). Listing\n * a rule here drops all diagnostics with that tag before rollup sees\n * them — so they don't fire via `this.warn` and don't fail the build\n * even when `failOnWarning` is enabled.\n *\n * The valid rule names are enumerated by the `DiagnosticRule` type\n * re-exported from this module. Unknown rule names are ignored.\n */\n disabledWarnings?: readonly DiagnosticRule[]\n\n /**\n * Emit `[llui]`-prefixed `console.info` logs for every transformed\n * component file — state-path bit assignments, mask injections, and\n * helper compile/bail counts. Useful when diagnosing why a binding\n * isn't gated the way you expect, or why a call fell back from\n * template-clone to `elSplit`. Off by default.\n */\n verbose?: boolean\n}\n\n/**\n * Does `@llui/mcp` resolve from `root`'s node_modules? Uses\n * `require.resolve` so monorepo workspaces and hoisted installs both\n * work. Catches failures silently — the only consequence is that we\n * leave `mcpPort` disabled, which is the safe default.\n */\nfunction hasMcpPackage(root: string): boolean {\n try {\n const req = createRequire(resolve(root, 'package.json'))\n req.resolve('@llui/mcp/package.json')\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Resolve the path to the llui-mcp CLI entry. Reads `bin.llui-mcp`\n * from @llui/mcp's package.json and joins it against the package\n * directory. Returns null if @llui/mcp isn't resolvable.\n */\nfunction resolveMcpCliPath(root: string): string | null {\n try {\n const req = createRequire(resolve(root, 'package.json'))\n const pkgJsonPath = req.resolve('@llui/mcp/package.json')\n const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf8')) as {\n bin?: string | Record<string, string>\n }\n const binEntry = typeof pkgJson.bin === 'string' ? pkgJson.bin : pkgJson.bin?.['llui-mcp']\n if (!binEntry) return null\n return resolve(dirname(pkgJsonPath), binEntry)\n } catch {\n return null\n }\n}\n\nexport default function llui(options: LluiPluginOptions = {}): Plugin {\n let devMode = false\n // `mcpPort` + `mcpMode` are resolved lazily in `configResolved` so we\n // can check for @llui/mcp in the consuming project's node_modules.\n // - `options.mcpPort === false` → disabled\n // - explicit number → wire-only (user manages the server)\n // - undefined + @llui/mcp found → spawn (plugin starts llui-mcp --http)\n // - undefined + no @llui/mcp → disabled\n let mcpPort: number | null = null\n let mcpMode: 'disabled' | 'wire' | 'spawn' = 'disabled'\n let mcpCliPath: string | null = null\n let mcpChild: ChildProcess | null = null\n const failOnWarning = options.failOnWarning === true\n const disabledWarnings = new Set<string>(options.disabledWarnings ?? [])\n const verbose = options.verbose === true\n\n // File-based handshake with @llui/mcp. The MCP server writes a marker\n // file when its bridge starts; we watch it and send a Vite HMR custom\n // event so the browser can call __lluiConnect() automatically — without\n // retry spam, regardless of whether MCP or Vite started first.\n const activeFilePath = resolve(findWorkspaceRoot(), 'node_modules/.cache/llui-mcp/active.json')\n let mcpWatcher: FSWatcher | null = null\n let dirWatcher: FSWatcher | null = null\n // Cached once Vite's HTTP server emits `listening`. `stampDevUrl()`\n // uses this to write the URL into the marker file — either immediately\n // (if MCP already started and wrote one) or later when the marker\n // appears via the directory watcher (MCP-starts-after-Vite path).\n let cachedDevUrl: string | null = null\n\n function readMcpMarker(): { port: number; devUrl?: string } | null {\n try {\n if (!existsSync(activeFilePath)) return null\n const data = JSON.parse(readFileSync(activeFilePath, 'utf8')) as {\n port?: number\n devUrl?: string\n }\n if (typeof data.port !== 'number') return null\n return { port: data.port, ...(data.devUrl ? { devUrl: data.devUrl } : {}) }\n } catch {\n return null\n }\n }\n\n /**\n * Idempotently write `cachedDevUrl` into the marker file. No-op if the\n * URL hasn't been captured yet (Vite hasn't emitted `listening`) or if\n * the marker file doesn't exist (MCP hasn't started yet). Covers both\n * orderings — the listening hook calls this after caching, and the\n * directory watcher calls it when the marker appears later.\n */\n function stampDevUrl(): void {\n if (cachedDevUrl === null) return\n if (!existsSync(activeFilePath)) return\n try {\n const marker = JSON.parse(readFileSync(activeFilePath, 'utf8')) as Record<string, unknown>\n if (marker.devUrl === cachedDevUrl) return\n marker.devUrl = cachedDevUrl\n writeFileSync(activeFilePath, JSON.stringify(marker))\n } catch {\n // Best-effort — failure to update the marker should not crash Vite\n }\n }\n\n function notifyMcpReady(server: ViteDevServer): void {\n const marker = readMcpMarker()\n if (marker === null) return\n server.ws.send({ type: 'custom', event: 'llui:mcp-ready', data: marker })\n }\n\n function notifyMcpOffline(server: ViteDevServer): void {\n server.ws.send({ type: 'custom', event: 'llui:mcp-offline', data: {} })\n }\n\n return {\n name: 'llui',\n enforce: 'pre',\n\n configResolved(config) {\n devMode = config.command === 'serve' || config.mode === 'development'\n if (options.mcpPort === false) {\n mcpMode = 'disabled'\n mcpPort = null\n } else if (typeof options.mcpPort === 'number') {\n mcpMode = 'wire'\n mcpPort = options.mcpPort\n } else if (hasMcpPackage(config.root)) {\n mcpCliPath = resolveMcpCliPath(config.root)\n if (mcpCliPath) {\n mcpMode = 'spawn'\n mcpPort = 5200\n } else {\n mcpMode = 'wire'\n mcpPort = 5200\n }\n } else {\n mcpMode = 'disabled'\n mcpPort = null\n }\n },\n\n configureServer(server) {\n if (mcpPort === null) {\n // #3 diagnostic: MCP server is running but the plugin is opted\n // out. Users in this state usually don't realize the mismatch —\n // loud-and-early log saves the \"why isn't my MCP attached\" hunt.\n if (existsSync(activeFilePath)) {\n console.warn(\n `[llui] @llui/mcp server is running (marker at ${activeFilePath}) ` +\n `but the Vite plugin is opted out (mcpPort: false, or @llui/mcp ` +\n `isn't a dep of this project). Add \\`llui({ mcpPort: 5200 })\\` ` +\n `to vite.config to wire them up, or remove the marker file and ` +\n `stop the MCP server if the mismatch was unintended.`,\n )\n }\n return\n }\n\n // Spawn mode: plugin launches llui-mcp as a child process so\n // `pnpm dev` handles the whole stack. Skip spawning when a marker\n // already exists — something (usually a separate llui-mcp process\n // started before Vite) is already listening. The existing wire\n // behavior takes over from there.\n if (mcpMode === 'spawn' && mcpCliPath !== null && !existsSync(activeFilePath)) {\n mcpChild = spawn(process.execPath, [mcpCliPath, '--http', String(mcpPort)], {\n stdio: ['ignore', 'pipe', 'pipe'],\n env: { ...process.env, LLUI_MCP_PORT: String(mcpPort) },\n })\n mcpChild.stdout?.on('data', (buf: Buffer) => {\n process.stdout.write(`[mcp] ${buf.toString()}`)\n })\n mcpChild.stderr?.on('data', (buf: Buffer) => {\n process.stderr.write(`[mcp] ${buf.toString()}`)\n })\n mcpChild.on('exit', (code) => {\n if (code !== 0 && code !== null) {\n console.warn(`[llui] @llui/mcp child exited with code ${code}`)\n }\n mcpChild = null\n })\n const killChild = (): void => {\n if (mcpChild && !mcpChild.killed) mcpChild.kill('SIGTERM')\n }\n server.httpServer?.on('close', killChild)\n process.once('exit', killChild)\n }\n\n // HTTP endpoint: the browser fetches this on load to discover the\n // current MCP port. Avoids the race where HMR events sent before\n // the import.meta.hot listener registers get dropped — and lets\n // the browser connect to the actual port (which may differ from\n // the compile-time default if MCP was started with LLUI_MCP_PORT).\n server.middlewares.use('/__llui_mcp_status', (_req, res) => {\n const marker = readMcpMarker()\n if (marker === null) {\n res.statusCode = 404\n res.end()\n return\n }\n res.statusCode = 200\n res.setHeader('content-type', 'application/json')\n res.end(JSON.stringify({ port: marker.port }))\n })\n\n // Watch the marker file for create/delete. fs.watch on the parent\n // directory catches both events; the file itself may not exist\n // when we start watching.\n const dir = dirname(activeFilePath)\n try {\n // Watch the parent directory for the marker file appearing/disappearing\n const watchDir = (): void => {\n if (!existsSync(dir)) return\n dirWatcher = fsWatch(dir, (_event, filename) => {\n if (filename !== 'active.json') return\n if (existsSync(activeFilePath)) {\n // Stamp BEFORE notifying so the `llui:mcp-ready` payload\n // carries the cached devUrl. This is the MCP-after-Vite\n // path: listening already fired and cached the URL; the\n // marker is only now appearing.\n stampDevUrl()\n notifyMcpReady(server)\n } else {\n notifyMcpOffline(server)\n }\n })\n }\n if (existsSync(dir)) {\n watchDir()\n } else {\n // Parent directory doesn't exist yet — poll for it briefly\n const poll = setInterval(() => {\n if (existsSync(dir)) {\n clearInterval(poll)\n watchDir()\n }\n }, 1000)\n // Clean up the poller if vite shuts down before MCP starts\n server.httpServer?.on('close', () => clearInterval(poll))\n }\n } catch {\n // fs.watch can fail on some filesystems — degrade silently\n }\n\n // Re-send the ready event when a new HMR client connects, in case\n // the page loads while MCP is already running.\n server.ws.on('connection', () => {\n if (existsSync(activeFilePath)) notifyMcpReady(server)\n })\n\n server.httpServer?.on('close', () => {\n mcpWatcher?.close()\n dirWatcher?.close()\n mcpWatcher = null\n dirWatcher = null\n })\n\n // Once Vite's HTTP server is listening, cache our dev URL and stamp\n // it into the marker file. Two orderings are possible:\n // (a) MCP started FIRST → marker exists now → stampDevUrl() writes\n // it, and we broadcast llui:mcp-ready so the browser picks up\n // the devUrl without relying on an incidental fs.watch tick\n // (which can miss on NFS/SMB).\n // (b) MCP will start LATER → marker doesn't exist yet → stamp is a\n // no-op. When MCP eventually writes the marker, the directory\n // watcher fires, calls stampDevUrl(), and notifies.\n server.httpServer?.once('listening', () => {\n const address = server.httpServer?.address()\n if (!address || typeof address !== 'object') return\n const host =\n address.address === '::' || address.address === '0.0.0.0' ? 'localhost' : address.address\n cachedDevUrl = `http://${host}:${address.port}`\n stampDevUrl()\n // Broadcast after stamping so the payload carries devUrl. Only\n // fires in case (a) — notifyMcpReady no-ops when the marker is\n // absent.\n notifyMcpReady(server)\n })\n },\n\n transform(code, id) {\n if (!id.endsWith('.ts') && !id.endsWith('.tsx')) return\n\n const diagnostics = diagnose(code)\n if (diagnostics.length > 0) {\n // Prefix every diagnostic with `<file>:<line>:<col>` plus the\n // `[rule-name]` tag so consumers logging `warning.message` in a\n // custom onwarn handler see both the location and the rule they\n // could silence via `disabledWarnings`.\n const cwd = process.cwd()\n const rel = relative(cwd, id)\n const display = rel.startsWith('..') ? id : rel\n for (const d of diagnostics) {\n if (disabledWarnings.has(d.rule)) continue\n const message = `${display}:${d.line}:${d.column}: [${d.rule}] ${d.message}`\n if (failOnWarning) {\n this.error({ message, loc: { line: d.line, column: d.column, file: id } })\n } else {\n this.warn(message, { line: d.line, column: d.column })\n }\n }\n }\n\n const result = transformLlui(code, id, devMode, mcpPort, verbose)\n if (!result) return undefined\n\n // Apply per-statement edits via MagicString for accurate source maps.\n // Untouched statements keep their original positions.\n const s = new MagicString(code)\n for (const edit of result.edits) {\n if (edit.start === edit.end) {\n // Insert at position — appendRight for middle, append for end-of-file\n if (edit.start === code.length) s.append(edit.replacement)\n else s.appendRight(edit.start, edit.replacement)\n } else {\n s.overwrite(edit.start, edit.end, edit.replacement)\n }\n }\n\n return {\n code: s.toString(),\n map: s.generateMap({ source: id, includeContent: true, hires: true }),\n }\n },\n }\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Plugin, ViteDevServer } from 'vite'\nimport MagicString from 'magic-string'\nimport { existsSync, readFileSync, writeFileSync, watch as fsWatch, type FSWatcher } from 'node:fs'\nimport { dirname, relative, resolve } from 'node:path'\nimport { createRequire } from 'node:module'\nimport { spawn, type ChildProcess } from 'node:child_process'\nimport { transformLlui, transformUseClientSsr, hasUseClientDirective } from './transform.js'\nimport { diagnose, type DiagnosticRule } from './diagnostics.js'\n\nexport type { DiagnosticRule } from './diagnostics.js'\n\n/**\n * Locate the workspace root so we share the MCP active marker file\n * with @llui/mcp regardless of which subdirectory the dev server runs in.\n * Mirrors `findWorkspaceRoot` from @llui/mcp — duplicated to avoid a\n * vite-plugin → mcp dependency cycle. The contract must stay in sync.\n */\nfunction findWorkspaceRoot(start: string = process.cwd()): string {\n let dir = resolve(start)\n let lastPackageJson: string | null = null\n while (true) {\n if (existsSync(resolve(dir, 'pnpm-workspace.yaml'))) return dir\n if (existsSync(resolve(dir, '.git'))) return dir\n if (existsSync(resolve(dir, 'package.json'))) lastPackageJson = dir\n const parent = dirname(dir)\n if (parent === dir) return lastPackageJson ?? start\n dir = parent\n }\n}\n\nexport interface LluiPluginOptions {\n /**\n * Port for the MCP debug bridge. In dev mode, the runtime relay connects\n * to `ws://127.0.0.1:<port>` so an external `llui-mcp` server can forward\n * tool calls into the running app.\n *\n * When omitted, the plugin checks whether `@llui/mcp` is resolvable from\n * the Vite project root. If yes → defaults to `5200`. If no → stays\n * disabled. This means installing `@llui/mcp` (+ starting its server)\n * Just Works without an explicit config entry. Pass an explicit `false`\n * to opt out even when `@llui/mcp` is installed; pass a number to use\n * a non-default port. When enabled but the MCP server isn't running,\n * the plugin returns 404 from its discovery endpoint and the browser\n * silently skips the connection — no retry noise.\n */\n mcpPort?: number | false\n\n /**\n * Treat every compiler diagnostic as a build error.\n *\n * Default `false` — diagnostics are emitted via rollup's `this.warn` and\n * can be ignored. Set to `true` in CI so lint-style regressions (namespace\n * imports, bitmask overflow, spread-in-children, `.map()` on state, etc.)\n * fail the build without requiring a custom `build.rollupOptions.onwarn`\n * handler.\n */\n failOnWarning?: boolean\n\n /**\n * Silence specific diagnostic rules without disabling the whole lint\n * pass. Each message is tagged with a rule name (shown in brackets at\n * the start of every warning, e.g. `[spread-in-children]`). Listing\n * a rule here drops all diagnostics with that tag before rollup sees\n * them — so they don't fire via `this.warn` and don't fail the build\n * even when `failOnWarning` is enabled.\n *\n * The valid rule names are enumerated by the `DiagnosticRule` type\n * re-exported from this module. Unknown rule names are ignored.\n */\n disabledWarnings?: readonly DiagnosticRule[]\n\n /**\n * Emit `[llui]`-prefixed `console.info` logs for every transformed\n * component file — state-path bit assignments, mask injections, and\n * helper compile/bail counts. Useful when diagnosing why a binding\n * isn't gated the way you expect, or why a call fell back from\n * template-clone to `elSplit`. Off by default.\n */\n verbose?: boolean\n}\n\n/**\n * Does `@llui/mcp` resolve from `root`'s node_modules? Uses\n * `require.resolve` so monorepo workspaces and hoisted installs both\n * work. Catches failures silently — the only consequence is that we\n * leave `mcpPort` disabled, which is the safe default.\n */\nfunction hasMcpPackage(root: string): boolean {\n try {\n const req = createRequire(resolve(root, 'package.json'))\n req.resolve('@llui/mcp/package.json')\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Resolve the path to the llui-mcp CLI entry. Reads `bin.llui-mcp`\n * from @llui/mcp's package.json and joins it against the package\n * directory. Returns null if @llui/mcp isn't resolvable.\n */\nfunction resolveMcpCliPath(root: string): string | null {\n try {\n const req = createRequire(resolve(root, 'package.json'))\n const pkgJsonPath = req.resolve('@llui/mcp/package.json')\n const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf8')) as {\n bin?: string | Record<string, string>\n }\n const binEntry = typeof pkgJson.bin === 'string' ? pkgJson.bin : pkgJson.bin?.['llui-mcp']\n if (!binEntry) return null\n return resolve(dirname(pkgJsonPath), binEntry)\n } catch {\n return null\n }\n}\n\nexport default function llui(options: LluiPluginOptions = {}): Plugin {\n let devMode = false\n // `mcpPort` + `mcpMode` are resolved lazily in `configResolved` so we\n // can check for @llui/mcp in the consuming project's node_modules.\n // - `options.mcpPort === false` → disabled\n // - explicit number → wire-only (user manages the server)\n // - undefined + @llui/mcp found → spawn (plugin starts llui-mcp --http)\n // - undefined + no @llui/mcp → disabled\n let mcpPort: number | null = null\n let mcpMode: 'disabled' | 'wire' | 'spawn' = 'disabled'\n let mcpCliPath: string | null = null\n let mcpChild: ChildProcess | null = null\n const failOnWarning = options.failOnWarning === true\n const disabledWarnings = new Set<string>(options.disabledWarnings ?? [])\n const verbose = options.verbose === true\n\n // File-based handshake with @llui/mcp. The MCP server writes a marker\n // file when its bridge starts; we watch it and send a Vite HMR custom\n // event so the browser can call __lluiConnect() automatically — without\n // retry spam, regardless of whether MCP or Vite started first.\n const activeFilePath = resolve(findWorkspaceRoot(), 'node_modules/.cache/llui-mcp/active.json')\n let mcpWatcher: FSWatcher | null = null\n let dirWatcher: FSWatcher | null = null\n // Cached once Vite's HTTP server emits `listening`. `stampDevUrl()`\n // uses this to write the URL into the marker file — either immediately\n // (if MCP already started and wrote one) or later when the marker\n // appears via the directory watcher (MCP-starts-after-Vite path).\n let cachedDevUrl: string | null = null\n\n function readMcpMarker(): { port: number; devUrl?: string } | null {\n try {\n if (!existsSync(activeFilePath)) return null\n const data = JSON.parse(readFileSync(activeFilePath, 'utf8')) as {\n port?: number\n devUrl?: string\n }\n if (typeof data.port !== 'number') return null\n return { port: data.port, ...(data.devUrl ? { devUrl: data.devUrl } : {}) }\n } catch {\n return null\n }\n }\n\n /**\n * Idempotently write `cachedDevUrl` into the marker file. No-op if the\n * URL hasn't been captured yet (Vite hasn't emitted `listening`) or if\n * the marker file doesn't exist (MCP hasn't started yet). Covers both\n * orderings — the listening hook calls this after caching, and the\n * directory watcher calls it when the marker appears later.\n */\n function stampDevUrl(): void {\n if (cachedDevUrl === null) return\n if (!existsSync(activeFilePath)) return\n try {\n const marker = JSON.parse(readFileSync(activeFilePath, 'utf8')) as Record<string, unknown>\n if (marker.devUrl === cachedDevUrl) return\n marker.devUrl = cachedDevUrl\n writeFileSync(activeFilePath, JSON.stringify(marker))\n } catch {\n // Best-effort — failure to update the marker should not crash Vite\n }\n }\n\n function notifyMcpReady(server: ViteDevServer): void {\n const marker = readMcpMarker()\n if (marker === null) return\n server.ws.send({ type: 'custom', event: 'llui:mcp-ready', data: marker })\n }\n\n function notifyMcpOffline(server: ViteDevServer): void {\n server.ws.send({ type: 'custom', event: 'llui:mcp-offline', data: {} })\n }\n\n return {\n name: 'llui',\n enforce: 'pre',\n\n configResolved(config) {\n devMode = config.command === 'serve' || config.mode === 'development'\n if (options.mcpPort === false) {\n mcpMode = 'disabled'\n mcpPort = null\n } else if (typeof options.mcpPort === 'number') {\n mcpMode = 'wire'\n mcpPort = options.mcpPort\n } else if (hasMcpPackage(config.root)) {\n mcpCliPath = resolveMcpCliPath(config.root)\n if (mcpCliPath) {\n mcpMode = 'spawn'\n mcpPort = 5200\n } else {\n mcpMode = 'wire'\n mcpPort = 5200\n }\n } else {\n mcpMode = 'disabled'\n mcpPort = null\n }\n },\n\n configureServer(server) {\n if (mcpPort === null) {\n // #3 diagnostic: MCP server is running but the plugin is opted\n // out. Users in this state usually don't realize the mismatch —\n // loud-and-early log saves the \"why isn't my MCP attached\" hunt.\n if (existsSync(activeFilePath)) {\n console.warn(\n `[llui] @llui/mcp server is running (marker at ${activeFilePath}) ` +\n `but the Vite plugin is opted out (mcpPort: false, or @llui/mcp ` +\n `isn't a dep of this project). Add \\`llui({ mcpPort: 5200 })\\` ` +\n `to vite.config to wire them up, or remove the marker file and ` +\n `stop the MCP server if the mismatch was unintended.`,\n )\n }\n return\n }\n\n // Spawn mode: plugin launches llui-mcp as a child process so\n // `pnpm dev` handles the whole stack. Skip spawning when a marker\n // already exists — something (usually a separate llui-mcp process\n // started before Vite) is already listening. The existing wire\n // behavior takes over from there.\n if (mcpMode === 'spawn' && mcpCliPath !== null && !existsSync(activeFilePath)) {\n mcpChild = spawn(process.execPath, [mcpCliPath, '--http', String(mcpPort)], {\n stdio: ['ignore', 'pipe', 'pipe'],\n env: { ...process.env, LLUI_MCP_PORT: String(mcpPort) },\n })\n mcpChild.stdout?.on('data', (buf: Buffer) => {\n process.stdout.write(`[mcp] ${buf.toString()}`)\n })\n mcpChild.stderr?.on('data', (buf: Buffer) => {\n process.stderr.write(`[mcp] ${buf.toString()}`)\n })\n mcpChild.on('exit', (code) => {\n if (code !== 0 && code !== null) {\n console.warn(`[llui] @llui/mcp child exited with code ${code}`)\n }\n mcpChild = null\n })\n const killChild = (): void => {\n if (mcpChild && !mcpChild.killed) mcpChild.kill('SIGTERM')\n }\n server.httpServer?.on('close', killChild)\n process.once('exit', killChild)\n }\n\n // HTTP endpoint: the browser fetches this on load to discover the\n // current MCP port. Avoids the race where HMR events sent before\n // the import.meta.hot listener registers get dropped — and lets\n // the browser connect to the actual port (which may differ from\n // the compile-time default if MCP was started with LLUI_MCP_PORT).\n server.middlewares.use('/__llui_mcp_status', (_req, res) => {\n const marker = readMcpMarker()\n if (marker === null) {\n res.statusCode = 404\n res.end()\n return\n }\n res.statusCode = 200\n res.setHeader('content-type', 'application/json')\n res.end(JSON.stringify({ port: marker.port }))\n })\n\n // Watch the marker file for create/delete. fs.watch on the parent\n // directory catches both events; the file itself may not exist\n // when we start watching.\n const dir = dirname(activeFilePath)\n try {\n // Watch the parent directory for the marker file appearing/disappearing\n const watchDir = (): void => {\n if (!existsSync(dir)) return\n dirWatcher = fsWatch(dir, (_event, filename) => {\n if (filename !== 'active.json') return\n if (existsSync(activeFilePath)) {\n // Stamp BEFORE notifying so the `llui:mcp-ready` payload\n // carries the cached devUrl. This is the MCP-after-Vite\n // path: listening already fired and cached the URL; the\n // marker is only now appearing.\n stampDevUrl()\n notifyMcpReady(server)\n } else {\n notifyMcpOffline(server)\n }\n })\n }\n if (existsSync(dir)) {\n watchDir()\n } else {\n // Parent directory doesn't exist yet — poll for it briefly\n const poll = setInterval(() => {\n if (existsSync(dir)) {\n clearInterval(poll)\n watchDir()\n }\n }, 1000)\n // Clean up the poller if vite shuts down before MCP starts\n server.httpServer?.on('close', () => clearInterval(poll))\n }\n } catch {\n // fs.watch can fail on some filesystems — degrade silently\n }\n\n // Re-send the ready event when a new HMR client connects, in case\n // the page loads while MCP is already running.\n server.ws.on('connection', () => {\n if (existsSync(activeFilePath)) notifyMcpReady(server)\n })\n\n server.httpServer?.on('close', () => {\n mcpWatcher?.close()\n dirWatcher?.close()\n mcpWatcher = null\n dirWatcher = null\n })\n\n // Once Vite's HTTP server is listening, cache our dev URL and stamp\n // it into the marker file. Two orderings are possible:\n // (a) MCP started FIRST → marker exists now → stampDevUrl() writes\n // it, and we broadcast llui:mcp-ready so the browser picks up\n // the devUrl without relying on an incidental fs.watch tick\n // (which can miss on NFS/SMB).\n // (b) MCP will start LATER → marker doesn't exist yet → stamp is a\n // no-op. When MCP eventually writes the marker, the directory\n // watcher fires, calls stampDevUrl(), and notifies.\n server.httpServer?.once('listening', () => {\n const address = server.httpServer?.address()\n if (!address || typeof address !== 'object') return\n const host =\n address.address === '::' || address.address === '0.0.0.0' ? 'localhost' : address.address\n cachedDevUrl = `http://${host}:${address.port}`\n stampDevUrl()\n // Broadcast after stamping so the payload carries devUrl. Only\n // fires in case (a) — notifyMcpReady no-ops when the marker is\n // absent.\n notifyMcpReady(server)\n })\n },\n\n transform(code, id, options) {\n if (!id.endsWith('.ts') && !id.endsWith('.tsx')) return\n\n // `'use client'` directive — SSR builds replace the module with a\n // stub so top-level imports and side effects never run on the\n // server. Client builds pass through to the normal transform; the\n // directive is effectively a no-op on the client.\n if (options?.ssr && hasUseClientDirective(code)) {\n const result = transformUseClientSsr(code, id)\n if (result) {\n const cwd = process.cwd()\n const rel = relative(cwd, id)\n const display = rel.startsWith('..') ? id : rel\n for (const warning of result.warnings) {\n this.warn(`${display}: ${warning}`)\n }\n return { code: result.output, map: { mappings: '' } }\n }\n }\n\n const diagnostics = diagnose(code)\n if (diagnostics.length > 0) {\n // Prefix every diagnostic with `<file>:<line>:<col>` plus the\n // `[rule-name]` tag so consumers logging `warning.message` in a\n // custom onwarn handler see both the location and the rule they\n // could silence via `disabledWarnings`.\n const cwd = process.cwd()\n const rel = relative(cwd, id)\n const display = rel.startsWith('..') ? id : rel\n for (const d of diagnostics) {\n if (disabledWarnings.has(d.rule)) continue\n const message = `${display}:${d.line}:${d.column}: [${d.rule}] ${d.message}`\n if (failOnWarning) {\n this.error({ message, loc: { line: d.line, column: d.column, file: id } })\n } else {\n this.warn(message, { line: d.line, column: d.column })\n }\n }\n }\n\n const result = transformLlui(code, id, devMode, mcpPort, verbose)\n if (!result) return undefined\n\n // Apply per-statement edits via MagicString for accurate source maps.\n // Untouched statements keep their original positions.\n const s = new MagicString(code)\n for (const edit of result.edits) {\n if (edit.start === edit.end) {\n // Insert at position — appendRight for middle, append for end-of-file\n if (edit.start === code.length) s.append(edit.replacement)\n else s.appendRight(edit.start, edit.replacement)\n } else {\n s.overwrite(edit.start, edit.end, edit.replacement)\n }\n }\n\n return {\n code: s.toString(),\n map: s.generateMap({ source: id, includeContent: true, hires: true }),\n }\n },\n }\n}\n"]}
@@ -11,4 +11,40 @@ export declare function transformLlui(source: string, _filename: string, devMode
11
11
  output: string;
12
12
  edits: TransformEdit[];
13
13
  } | null;
14
+ export interface UseClientTransformResult {
15
+ output: string;
16
+ warnings: string[];
17
+ }
18
+ /**
19
+ * If `source` begins with a `'use client'` directive, generate a stub
20
+ * replacement for the SSR build. Every `export const X = <expr>` becomes
21
+ * `export const X = __clientOnlyStub('X')`, every `export function X`
22
+ * becomes a stub, and `export default <expr>` becomes a default stub.
23
+ * Returns `null` if the directive is absent (caller should fall through
24
+ * to the normal compiler pass).
25
+ *
26
+ * The client build is expected to skip this path entirely — Vite passes
27
+ * `{ ssr: false }` there, and the plugin checks that before invoking
28
+ * this function.
29
+ *
30
+ * Shapes this v1 does NOT handle (emits a warning + leaves them out of
31
+ * the stub output):
32
+ *
33
+ * - `export function foo() {}` and `export class Foo {}` — rewritten
34
+ * as stubs but the caller may be surprised that `foo` and `Foo` are
35
+ * ComponentDef-shaped objects during SSR.
36
+ * - `export { a, b } from './other'` — re-export forms are not
37
+ * detected; they pass through and will still pull `./other` into
38
+ * the SSR graph.
39
+ * - `export * from './other'` — same as above.
40
+ * - `export type ...` — type exports are erased by TS so nothing to
41
+ * stub; left untouched.
42
+ */
43
+ export declare function transformUseClientSsr(source: string, _filename: string): UseClientTransformResult | null;
44
+ /**
45
+ * Check whether `source`'s first statement is a `'use client'` directive.
46
+ * Cheap string scan so the caller can decide which transform to run
47
+ * without parsing the whole file twice.
48
+ */
49
+ export declare function hasUseClientDirective(source: string): boolean;
14
50
  //# sourceMappingURL=transform.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../src/transform.ts"],"names":[],"mappings":"AA6KA;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,UAAQ,EACf,OAAO,GAAE,MAAM,GAAG,IAAW,EAC7B,OAAO,UAAQ,GACd;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,aAAa,EAAE,CAAA;CAAE,GAAG,IAAI,CAmQnD"}
1
+ {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../src/transform.ts"],"names":[],"mappings":"AA6KA;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,UAAQ,EACf,OAAO,GAAE,MAAM,GAAG,IAAW,EAC7B,OAAO,UAAQ,GACd;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,aAAa,EAAE,CAAA;CAAE,GAAG,IAAI,CAuQnD;AA++HD,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,wBAAwB,GAAG,IAAI,CAoGjC;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CA4B7D"}
package/dist/transform.js CHANGED
@@ -209,6 +209,7 @@ export function transformLlui(source, _filename, devMode = false, mcpPort = 5200
209
209
  let usesElSplit = false;
210
210
  let usesMemo = false;
211
211
  let usesApplyBinding = false;
212
+ let usesCloneStaticTemplate = false;
212
213
  const f = ts.factory;
213
214
  const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
214
215
  // Collect source positions of transformed nodes for source mapping
@@ -268,6 +269,8 @@ export function transformLlui(source, _filename, devMode = false, mcpPort = 5200
268
269
  usesElTemplate = true;
269
270
  else if (transformed.expression.text === 'elSplit')
270
271
  usesElSplit = true;
272
+ else if (transformed.expression.text === '__cloneStaticTemplate')
273
+ usesCloneStaticTemplate = true;
271
274
  }
272
275
  if (hasPos)
273
276
  edits.push({ start: origStart, end: origEnd, replacement: '' });
@@ -320,7 +323,7 @@ export function transformLlui(source, _filename, devMode = false, mcpPort = 5200
320
323
  // Pass 3: Clean up imports — use the old cleanupImports approach
321
324
  // which operates on the transformed SourceFile safely
322
325
  const safeToRemove = new Set([...compiledHelpers].filter((h) => !bailedHelpers.has(h)));
323
- transformed = cleanupImports(transformed, lluiImport, importedHelpers, safeToRemove, usesElSplit, usesElTemplate, usesMemo, usesApplyBinding, f);
326
+ transformed = cleanupImports(transformed, lluiImport, importedHelpers, safeToRemove, usesElSplit, usesElTemplate, usesMemo, usesApplyBinding, usesCloneStaticTemplate, f);
324
327
  if (edits.length === 0)
325
328
  return null;
326
329
  // Find component declarations for HMR
@@ -1947,8 +1950,13 @@ function buildAccess(f, root, parts) {
1947
1950
  return expr;
1948
1951
  }
1949
1952
  // ── Pass 3: Import cleanup ───────────────────────────────────────
1950
- function cleanupImports(sf, lluiImport, _helpers, compiled, usesElSplit, usesElTemplate, usesMemo, usesApplyBinding, f) {
1951
- if (compiled.size === 0 && !usesElTemplate && !usesElSplit && !usesMemo && !usesApplyBinding)
1953
+ function cleanupImports(sf, lluiImport, _helpers, compiled, usesElSplit, usesElTemplate, usesMemo, usesApplyBinding, usesCloneStaticTemplate, f) {
1954
+ if (compiled.size === 0 &&
1955
+ !usesElTemplate &&
1956
+ !usesElSplit &&
1957
+ !usesMemo &&
1958
+ !usesApplyBinding &&
1959
+ !usesCloneStaticTemplate)
1952
1960
  return sf;
1953
1961
  const clause = lluiImport.importClause;
1954
1962
  if (!clause?.namedBindings || !ts.isNamedImports(clause.namedBindings))
@@ -1962,6 +1970,10 @@ function cleanupImports(sf, lluiImport, _helpers, compiled, usesElSplit, usesElT
1962
1970
  if (!hasElTemplate && usesElTemplate) {
1963
1971
  remaining.push(f.createImportSpecifier(false, undefined, f.createIdentifier('elTemplate')));
1964
1972
  }
1973
+ const hasCloneStaticTemplate = clause.namedBindings.elements.some((s) => s.name.text === '__cloneStaticTemplate');
1974
+ if (!hasCloneStaticTemplate && usesCloneStaticTemplate) {
1975
+ remaining.push(f.createImportSpecifier(false, undefined, f.createIdentifier('__cloneStaticTemplate')));
1976
+ }
1965
1977
  const hasMemo = clause.namedBindings.elements.some((s) => s.name.text === 'memo');
1966
1978
  if (!hasMemo && usesMemo) {
1967
1979
  remaining.push(f.createImportSpecifier(false, undefined, f.createIdentifier('memo')));
@@ -2836,7 +2848,7 @@ function emitSubtreeTemplate(analyzed, fieldBits, f) {
2836
2848
  }
2837
2849
  stmts.push(f.createVariableStatement(undefined, f.createVariableDeclarationList([f.createVariableDeclaration(cVar, undefined, undefined, walk)], ts.NodeFlags.Const)));
2838
2850
  stmts.push(f.createVariableStatement(undefined, f.createVariableDeclarationList([
2839
- f.createVariableDeclaration(tVar, undefined, undefined, f.createCallExpression(f.createPropertyAccessExpression(f.createIdentifier('document'), 'createTextNode'), undefined, [f.createStringLiteral('')])),
2851
+ f.createVariableDeclaration(tVar, undefined, undefined, f.createCallExpression(f.createPropertyAccessExpression(f.createIdentifier('__dom'), 'createTextNode'), undefined, [f.createStringLiteral('')])),
2840
2852
  ], ts.NodeFlags.Const)));
2841
2853
  stmts.push(f.createExpressionStatement(f.createCallExpression(f.createPropertyAccessExpression(f.createPropertyAccessExpression(f.createIdentifier(cVar), 'parentNode'), 'replaceChild'), undefined, [f.createIdentifier(tVar), f.createIdentifier(cVar)])));
2842
2854
  }
@@ -2879,10 +2891,11 @@ function emitSubtreeTemplate(analyzed, fieldBits, f) {
2879
2891
  // root.addEventListener(eventName, handler)
2880
2892
  stmts.push(f.createExpressionStatement(f.createCallExpression(f.createPropertyAccessExpression(f.createIdentifier('root'), 'addEventListener'), undefined, [f.createStringLiteral(eventName), delegateHandler])));
2881
2893
  }
2882
- // (root, __bind) => { ... }
2894
+ // (root, __bind, __dom) => { ... }
2883
2895
  const patchFn = f.createArrowFunction(undefined, undefined, [
2884
2896
  f.createParameterDeclaration(undefined, undefined, 'root'),
2885
2897
  f.createParameterDeclaration(undefined, undefined, '__bind'),
2898
+ f.createParameterDeclaration(undefined, undefined, '__dom'),
2886
2899
  ], undefined, f.createToken(ts.SyntaxKind.EqualsGreaterThanToken), f.createBlock(stmts, true));
2887
2900
  const call = f.createCallExpression(f.createIdentifier('elTemplate'), undefined, [
2888
2901
  f.createStringLiteral(html),
@@ -2963,19 +2976,16 @@ function escapeHTML(s) {
2963
2976
  function escapeAttr(s) {
2964
2977
  return s.replace(/&/g, '&amp;').replace(/"/g, '&quot;');
2965
2978
  }
2966
- let templateCounter = 0;
2967
2979
  function emitTemplateClone(html, f) {
2968
- const varName = `__tmpl${templateCounter++}`;
2969
- // Emit: (() => { const t = document.createElement('template'); t.innerHTML = 'html'; return t.content.cloneNode(true).firstChild })()
2970
- // Simplified: we use an IIFE that creates and caches a template
2971
- // Actually, for simplicity, just emit the cloneNode inline
2972
- const tmplCreate = f.createCallExpression(f.createPropertyAccessExpression(f.createIdentifier('document'), 'createElement'), undefined, [f.createStringLiteral('template')]);
2973
- const iife = f.createCallExpression(f.createParenthesizedExpression(f.createArrowFunction(undefined, undefined, [], undefined, f.createToken(ts.SyntaxKind.EqualsGreaterThanToken), f.createBlock([
2974
- f.createVariableStatement(undefined, f.createVariableDeclarationList([f.createVariableDeclaration(varName, undefined, undefined, tmplCreate)], ts.NodeFlags.Const)),
2975
- f.createExpressionStatement(f.createBinaryExpression(f.createPropertyAccessExpression(f.createIdentifier(varName), 'innerHTML'), ts.SyntaxKind.EqualsToken, f.createStringLiteral(html))),
2976
- f.createReturnStatement(f.createPropertyAccessExpression(f.createCallExpression(f.createPropertyAccessExpression(f.createPropertyAccessExpression(f.createIdentifier(varName), 'content'), 'cloneNode'), undefined, [f.createTrue()]), 'firstChild')),
2977
- ], true))), undefined, []);
2978
- return iife;
2980
+ // Emits: __cloneStaticTemplate("<html>")
2981
+ //
2982
+ // The helper lives in `@llui/dom` and threads through `ctx.dom` so SSR
2983
+ // under jsdom/linkedom works without touching globalThis. The import
2984
+ // cleanup pass (see cleanupImports) auto-injects the import when this
2985
+ // emission fires.
2986
+ return f.createCallExpression(f.createIdentifier('__cloneStaticTemplate'), undefined, [
2987
+ f.createStringLiteral(html),
2988
+ ]);
2979
2989
  }
2980
2990
  function isPerItemCall(node) {
2981
2991
  // Matches: item(t => t.field) or item(t => expr)
@@ -3137,4 +3147,150 @@ function resolveChain(node, paramName) {
3137
3147
  return parts.slice(0, 2).join('.');
3138
3148
  return parts.join('.');
3139
3149
  }
3150
+ /**
3151
+ * If `source` begins with a `'use client'` directive, generate a stub
3152
+ * replacement for the SSR build. Every `export const X = <expr>` becomes
3153
+ * `export const X = __clientOnlyStub('X')`, every `export function X`
3154
+ * becomes a stub, and `export default <expr>` becomes a default stub.
3155
+ * Returns `null` if the directive is absent (caller should fall through
3156
+ * to the normal compiler pass).
3157
+ *
3158
+ * The client build is expected to skip this path entirely — Vite passes
3159
+ * `{ ssr: false }` there, and the plugin checks that before invoking
3160
+ * this function.
3161
+ *
3162
+ * Shapes this v1 does NOT handle (emits a warning + leaves them out of
3163
+ * the stub output):
3164
+ *
3165
+ * - `export function foo() {}` and `export class Foo {}` — rewritten
3166
+ * as stubs but the caller may be surprised that `foo` and `Foo` are
3167
+ * ComponentDef-shaped objects during SSR.
3168
+ * - `export { a, b } from './other'` — re-export forms are not
3169
+ * detected; they pass through and will still pull `./other` into
3170
+ * the SSR graph.
3171
+ * - `export * from './other'` — same as above.
3172
+ * - `export type ...` — type exports are erased by TS so nothing to
3173
+ * stub; left untouched.
3174
+ */
3175
+ export function transformUseClientSsr(source, _filename) {
3176
+ const sourceFile = ts.createSourceFile('input.ts', source, ts.ScriptTarget.Latest, true);
3177
+ // Find the first non-comment, non-directive-whitespace statement.
3178
+ // 'use client' should be the literal first statement in the file.
3179
+ const first = sourceFile.statements[0];
3180
+ if (!first)
3181
+ return null;
3182
+ if (!ts.isExpressionStatement(first))
3183
+ return null;
3184
+ if (!ts.isStringLiteral(first.expression))
3185
+ return null;
3186
+ if (first.expression.text !== 'use client')
3187
+ return null;
3188
+ const warnings = [];
3189
+ const namedExports = [];
3190
+ let hasDefaultExport = false;
3191
+ for (const stmt of sourceFile.statements) {
3192
+ // The `'use client'` directive itself — skip.
3193
+ if (stmt === first)
3194
+ continue;
3195
+ // `export const NAME = ...` and `export let NAME = ...`
3196
+ if (ts.isVariableStatement(stmt) &&
3197
+ stmt.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) {
3198
+ for (const decl of stmt.declarationList.declarations) {
3199
+ if (ts.isIdentifier(decl.name)) {
3200
+ namedExports.push(decl.name.text);
3201
+ }
3202
+ else {
3203
+ warnings.push('[llui/use-client] destructured `export const { ... }` is not supported; each binding would have to be stubbed individually. Refactor to one `export const` per value.');
3204
+ }
3205
+ }
3206
+ continue;
3207
+ }
3208
+ // `export function NAME() {}`
3209
+ if (ts.isFunctionDeclaration(stmt) &&
3210
+ stmt.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) &&
3211
+ stmt.name) {
3212
+ namedExports.push(stmt.name.text);
3213
+ continue;
3214
+ }
3215
+ // `export class NAME {}`
3216
+ if (ts.isClassDeclaration(stmt) &&
3217
+ stmt.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) &&
3218
+ stmt.name) {
3219
+ namedExports.push(stmt.name.text);
3220
+ continue;
3221
+ }
3222
+ // `export default ...`
3223
+ if (ts.isExportAssignment(stmt) ||
3224
+ (ts.isFunctionDeclaration(stmt) &&
3225
+ stmt.modifiers?.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword))) {
3226
+ hasDefaultExport = true;
3227
+ continue;
3228
+ }
3229
+ // `export { a, b }` / `export { a } from './x'` / `export * from './x'`
3230
+ if (ts.isExportDeclaration(stmt)) {
3231
+ if (stmt.moduleSpecifier) {
3232
+ warnings.push("[llui/use-client] `export ... from '...'` re-export forms still pull the source module into the SSR graph and bypass stubbing. Either drop the re-export or move the 'use client' directive to the source module.");
3233
+ }
3234
+ else if (stmt.exportClause && ts.isNamedExports(stmt.exportClause)) {
3235
+ for (const spec of stmt.exportClause.elements) {
3236
+ namedExports.push((spec.name ?? spec.propertyName).text);
3237
+ }
3238
+ }
3239
+ continue;
3240
+ }
3241
+ // Type-only statements are erased at runtime — nothing to stub.
3242
+ if (ts.isTypeAliasDeclaration(stmt) || ts.isInterfaceDeclaration(stmt))
3243
+ continue;
3244
+ // Imports, `import type`, enum declarations, plain (non-export)
3245
+ // variable statements — dropped from the stub output.
3246
+ }
3247
+ // Build the generated module source.
3248
+ const lines = ["import { __clientOnlyStub } from '@llui/dom'", ''];
3249
+ for (const name of namedExports) {
3250
+ lines.push(`export const ${name} = __clientOnlyStub(${JSON.stringify(name)})`);
3251
+ }
3252
+ if (hasDefaultExport) {
3253
+ lines.push('export default __clientOnlyStub("default")');
3254
+ }
3255
+ return {
3256
+ output: lines.join('\n') + '\n',
3257
+ warnings,
3258
+ };
3259
+ }
3260
+ /**
3261
+ * Check whether `source`'s first statement is a `'use client'` directive.
3262
+ * Cheap string scan so the caller can decide which transform to run
3263
+ * without parsing the whole file twice.
3264
+ */
3265
+ export function hasUseClientDirective(source) {
3266
+ // Skip leading whitespace and block/line comments; look for the
3267
+ // first token. A full parse is overkill here — users who write
3268
+ // `'use client'` in any other position (inside a function, after
3269
+ // imports) aren't using the directive as React/Vercel define it.
3270
+ let i = 0;
3271
+ const len = source.length;
3272
+ while (i < len) {
3273
+ const ch = source[i];
3274
+ if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') {
3275
+ i++;
3276
+ continue;
3277
+ }
3278
+ if (source.startsWith('//', i)) {
3279
+ const nl = source.indexOf('\n', i);
3280
+ if (nl === -1)
3281
+ return false;
3282
+ i = nl + 1;
3283
+ continue;
3284
+ }
3285
+ if (source.startsWith('/*', i)) {
3286
+ const end = source.indexOf('*/', i + 2);
3287
+ if (end === -1)
3288
+ return false;
3289
+ i = end + 2;
3290
+ continue;
3291
+ }
3292
+ break;
3293
+ }
3294
+ return source.startsWith("'use client'", i) || source.startsWith('"use client"', i);
3295
+ }
3140
3296
  //# sourceMappingURL=transform.js.map