@vesk/adapter 0.2.15 → 0.2.17

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":"client-bundle.d.ts","sourceRoot":"","sources":["../src/client-bundle.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,SAAS,EAAE,mBAAmB,EAAE,kBAAkB,EAAqC,MAAM,yBAAyB,CAAC;AAoCrI;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CA8BhE;AAoLD;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAkBxD;AAmCD,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,SAAS,EAAE,EACtB,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC,CA4T7B;AAMD,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CA+B3D;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAUlE;AAYD;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAuCrG"}
1
+ {"version":3,"file":"client-bundle.d.ts","sourceRoot":"","sources":["../src/client-bundle.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,SAAS,EAAE,mBAAmB,EAAE,kBAAkB,EAAqC,MAAM,yBAAyB,CAAC;AAoCrI;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CA8BhE;AAmUD;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAkBxD;AAmCD,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,SAAS,EAAE,EACtB,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC,CAuX7B;AAMD,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CA+B3D;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAUlE;AAYD;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAuCrG"}
@@ -1,4 +1,4 @@
1
- import { readFileSync, existsSync, writeFileSync, unlinkSync, statSync, rmSync } from 'node:fs';
1
+ import { readFileSync, existsSync, writeFileSync, unlinkSync, statSync, rmSync, mkdtempSync } from 'node:fs';
2
2
  import { resolve, join, dirname, relative, sep } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import { build } from './esbuild-fallback.js';
@@ -197,18 +197,160 @@ function demoteExports(code) {
197
197
  code = code.slice(0, e.start) + e.text + code.slice(e.end);
198
198
  return code;
199
199
  }
200
+ function hasChunkImports(code) {
201
+ try {
202
+ const ast = parse(code);
203
+ return (ast.body ?? []).some((n) => n.type === 'ImportDeclaration');
204
+ }
205
+ catch {
206
+ return false;
207
+ }
208
+ }
200
209
  /**
201
- * Code-split chunks execute as classic scripts, so NO import statement may
202
- * survive in a contribution (runtime and `.vsk` imports are stripped
203
- * earlier; npm packages and relative `.ts` values are unresolvable from a
204
- * `/_vesk/static/` classic script). Strips every remaining import and
205
- * demotes value exports — server-only components never execute
206
- * client-side, and islands must receive data through props/serialization
207
- * rather than module imports (a stripped binding referenced by an island
208
- * fails loudly at that island instead of killing the whole chunk).
210
+ * Rewrites a compiled file's source specifier so esbuild can resolve it from
211
+ * the temp bundle entry. Bare package specifiers (e.g. `lucide-vesk`) stay
212
+ * as-is; relative specifiers are resolved to absolute filesystem paths with a
213
+ * `.ts` extension appended so esbuild loads the module as TypeScript and
214
+ * inlines its exported values. Offset-based on the parser AST — never a text
215
+ * scan.
216
+ */
217
+ function resolveImportSource(src, filePath) {
218
+ if (src.startsWith('./') || src.startsWith('../')) {
219
+ return resolve(dirname(filePath), src) + '.ts';
220
+ }
221
+ return src;
222
+ }
223
+ /**
224
+ * Accumulates every import a code-split chunk needs and rebuilds it as a
225
+ * single deduped import block.
226
+ *
227
+ * A chunk is the concatenation of several independently compiled files, and
228
+ * each file's component AND hydrator both keep their imports verbatim. So the
229
+ * same named binding frequently appears multiple times in one chunk — once per
230
+ * (file, comp|hyd) pair, plus once per extra file. acorn's `parse()` rejects
231
+ * that with `Identifier has already been declared`, so `hasChunkImports` used
232
+ * to silently return false and esbuild — which rejects the same duplicates —
233
+ * never ran; the chunk shipped with raw `import` statements inside its
234
+ * classic-script IIFE and hydration died with `SyntaxError: Cannot use import
235
+ * statement outside a module`.
236
+ *
237
+ * Processing each file's contribution in isolation avoids the duplicate
238
+ * bindings entirely (a single compiled file parses cleanly), folds the imports
239
+ * into a per-source map keyed by local binding, strips them from the file
240
+ * body, and later emits one merged import block at the top of the chunk.
209
241
  */
210
- function stripChunkImports(code) {
211
- return demoteExports(removeCompiledNodes(code, isAnyImport));
242
+ class ChunkImports {
243
+ bySource = new Map();
244
+ aborted = false;
245
+ abortReason = null;
246
+ fail(reason, code) {
247
+ if (!this.aborted) {
248
+ this.aborted = true;
249
+ this.abortReason = reason;
250
+ }
251
+ return code;
252
+ }
253
+ /** Folds one file's imports into the merged set and returns the code with its imports stripped. */
254
+ add(code, filePath) {
255
+ if (!code || this.aborted)
256
+ return code;
257
+ let ast;
258
+ try {
259
+ ast = parse(code);
260
+ }
261
+ catch {
262
+ // A single compiled file is always valid ESM; if it somehow is not,
263
+ // stop merging so the assembler falls back to the historical path.
264
+ return this.fail(`unparseable contribution from ${filePath}`, code);
265
+ }
266
+ const body = ast.body ?? [];
267
+ const ranges = [];
268
+ for (const raw of body) {
269
+ const node = raw;
270
+ if (node.type !== 'ImportDeclaration' || typeof node.start !== 'number' || typeof node.end !== 'number')
271
+ continue;
272
+ const src = node.source?.value;
273
+ if (typeof src !== 'string')
274
+ continue;
275
+ const resolved = resolveImportSource(src, filePath);
276
+ let group = this.bySource.get(resolved);
277
+ if (!group) {
278
+ group = { named: new Map() };
279
+ this.bySource.set(resolved, group);
280
+ }
281
+ for (const spec of node.specifiers ?? []) {
282
+ const s = spec;
283
+ const local = s.local?.name;
284
+ if (typeof local !== 'string')
285
+ return this.fail(`nameless specifier from '${resolved}' in ${filePath}`, code);
286
+ if (s.type === 'ImportDefaultSpecifier') {
287
+ if (group.default && group.default.local !== local)
288
+ return this.fail(`conflicting default imports from '${resolved}' in ${filePath}`, code);
289
+ group.default = { kind: 'default', local };
290
+ }
291
+ else if (s.type === 'ImportNamespaceSpecifier') {
292
+ if (group.namespace && group.namespace.local !== local)
293
+ return this.fail(`conflicting namespace imports from '${resolved}' in ${filePath}`, code);
294
+ group.namespace = { kind: 'namespace', local };
295
+ }
296
+ else if (s.type === 'ImportSpecifier') {
297
+ const imported = s.imported?.name;
298
+ if (typeof imported !== 'string')
299
+ return this.fail(`nameless named-import from '${resolved}' in ${filePath}`, code);
300
+ const existing = group.named.get(imported);
301
+ if (existing && existing.local !== local)
302
+ return this.fail(`'${imported}' from '${resolved}' bound to both '${existing.local}' and '${local}' in ${filePath}`, code);
303
+ group.named.set(imported, { kind: 'named', local, imported });
304
+ }
305
+ }
306
+ let cut = node.end;
307
+ if (code[cut] === ';')
308
+ cut++;
309
+ ranges.push([node.start, cut]);
310
+ }
311
+ if (ranges.length === 0)
312
+ return code;
313
+ return removeRanges(code, ranges);
314
+ }
315
+ /** True when every file parsed and merged cleanly (no conflict, no parse abort). */
316
+ get ok() {
317
+ return !this.aborted;
318
+ }
319
+ /** Why the merge aborted (null when `ok`). Surfaced by the assembler. */
320
+ get reason() {
321
+ return this.abortReason;
322
+ }
323
+ /** Renders the deduped import block (empty string when there are no imports). */
324
+ render() {
325
+ if (this.aborted || this.bySource.size === 0)
326
+ return '';
327
+ const lines = [];
328
+ const sources = [...this.bySource.keys()].sort();
329
+ for (const src of sources) {
330
+ const g = this.bySource.get(src);
331
+ const parts = [];
332
+ if (g.default)
333
+ parts.push(`import ${g.default.local} from ${JSON.stringify(src)}`);
334
+ if (g.namespace)
335
+ parts.push(`import * as ${g.namespace.local} from ${JSON.stringify(src)}`);
336
+ const named = [...g.named.values()].sort((a, b) => (a.imported < b.imported ? -1 : 1));
337
+ if (named.length > 0) {
338
+ const specs = named.map((sp) => (sp.imported === sp.local ? sp.local : `${sp.imported} as ${sp.local}`)).join(', ');
339
+ parts.push(`import { ${specs} } from ${JSON.stringify(src)}`);
340
+ }
341
+ lines.push(parts.join('\n'));
342
+ }
343
+ return lines.join('\n');
344
+ }
345
+ }
346
+ /** Removes a set of offset ranges from a source string, high-to-low. */
347
+ function removeRanges(code, ranges) {
348
+ const sorted = [...ranges].sort((a, b) => b[0] - a[0]);
349
+ let out = code;
350
+ for (const [start, end] of sorted) {
351
+ out = out.slice(0, start) + out.slice(end);
352
+ }
353
+ return out.trim();
212
354
  }
213
355
  /**
214
356
  * Scopes one file's contribution to the shared chunk: module-level
@@ -365,7 +507,7 @@ export async function generateClientBundle(routeTree, appDir, componentMap, opti
365
507
  function stripExports(code) {
366
508
  return removeCompiledNodes(code, isComponentExport);
367
509
  }
368
- function compileFile(filePath, resolvedName, output) {
510
+ function compileFile(filePath, resolvedName, output, imports) {
369
511
  if (seen.has(filePath))
370
512
  return;
371
513
  seen.add(filePath);
@@ -390,11 +532,17 @@ export async function generateClientBundle(routeTree, appDir, componentMap, opti
390
532
  if (cached && cacheUsable) {
391
533
  cachedFileHits++;
392
534
  for (const dep of cached.imports)
393
- compileFile(dep, cache?.files.get(dep)?.actualName ?? '', output);
535
+ compileFile(dep, cache?.files.get(dep)?.actualName ?? '', output, imports);
394
536
  // Comp and hyd contributions of one file share its top-level bindings
395
537
  // (e.g. `const links` used by both the component and its hydrator),
396
- // so they must be scoped together in a single block.
397
- const cachedFileCode = [cached.compCode, cached.hydCode].filter(Boolean).join('\n');
538
+ // so they must be scoped together in a single block. Cached codes keep
539
+ // their imports; each side is folded separately because comp+hyd of one
540
+ // file carry the same import twice (joint parse would abort the merge).
541
+ // Mirror the fresh path exactly: strip, rename the hydrator registry,
542
+ // trim — otherwise warm output drifts from cold output.
543
+ const cachedComp = cached.compCode ? imports.add(cached.compCode, filePath).replace(/^\n+/, '').replace(/\n+$/, '') : '';
544
+ const cachedHyd = cached.hydCode ? imports.add(cached.hydCode, filePath).replace(/__components/g, '__hydrators').replace(/^\n+/, '').replace(/\n+$/, '') : '';
545
+ const cachedFileCode = [cachedComp, cachedHyd].filter(Boolean).join('\n');
398
546
  if (cachedFileCode.trim())
399
547
  output.push(scopeFileContribution(cachedFileCode));
400
548
  for (const n of cached.runtimeNames)
@@ -411,13 +559,17 @@ export async function generateClientBundle(routeTree, appDir, componentMap, opti
411
559
  src = inlineMdContentAttrs(src, dirname(filePath), guessProjectRoots(appDir));
412
560
  }
413
561
  const namesBefore = cache ? new Set(runtimeImportNames) : null;
414
- const importedPaths = resolveVskImports(filePath, (p, n) => compileFile(p, n || '', output));
562
+ const importedPaths = resolveVskImports(filePath, (p, n) => compileFile(p, n, output, imports));
415
563
  // One parse/IR pass feeds both client modes AND the component-name
416
564
  // lookup — the dev hot path pays the acorn+TS parse once per edit
417
565
  // instead of three times.
418
566
  const { comp: rawComp, hyd: rawHyd, name: actualName } = compileClientBoth(src, null, filePath);
419
- const compCode = rawComp ? stripChunkImports(stripExports(stripVskImports(stripRuntimeImport(rawComp)))).replace(/^\n+/, '').replace(/\n+$/, '') : '';
420
- const hydCode = rawHyd ? stripChunkImports(stripExports(stripVskImports(stripRuntimeImport(rawHyd)))).replace(/__components/g, '__hydrators').replace(/^\n+/, '').replace(/\n+$/, '') : '';
567
+ // Cache keeps the import-carrying codes so a warm build can re-fold them
568
+ // into its own fresh accumulator; the emitted codes are import-stripped.
569
+ const compWithImports = rawComp ? stripExports(stripVskImports(stripRuntimeImport(rawComp))) : '';
570
+ const hydWithImports = rawHyd ? stripExports(stripVskImports(stripRuntimeImport(rawHyd))) : '';
571
+ const compCode = compWithImports ? imports.add(compWithImports, filePath).replace(/^\n+/, '').replace(/\n+$/, '') : '';
572
+ const hydCode = hydWithImports ? imports.add(hydWithImports, filePath).replace(/__components/g, '__hydrators').replace(/^\n+/, '').replace(/\n+$/, '') : '';
421
573
  if (rawComp)
422
574
  collectRuntimeImports(rawComp);
423
575
  if (rawHyd)
@@ -444,8 +596,8 @@ export async function generateClientBundle(routeTree, appDir, componentMap, opti
444
596
  cache.files.set(filePath, {
445
597
  mtimeMs: st.mtimeMs,
446
598
  size: st.size,
447
- compCode,
448
- hydCode,
599
+ compCode: compWithImports,
600
+ hydCode: hydWithImports,
449
601
  actualName,
450
602
  runtimeNames: [...runtimeImportNames].filter((n) => !namesBefore.has(n)),
451
603
  imports: importedPaths,
@@ -479,57 +631,113 @@ export async function generateClientBundle(routeTree, appDir, componentMap, opti
479
631
  function walkSplit(nodes, _chain) {
480
632
  for (const node of nodes) {
481
633
  const chunkCode = [];
634
+ const chunkImports = new ChunkImports();
482
635
  const pagePath = resolve(appDir, node.sourceDir, 'page.vsk');
483
636
  if (node.page && existsSync(pagePath)) {
484
- compileFile(pagePath, node.page, chunkCode);
637
+ compileFile(pagePath, node.page, chunkCode, chunkImports);
485
638
  }
486
639
  const layoutPath = resolve(appDir, node.sourceDir, 'layout.vsk');
487
640
  if (node.layout && existsSync(layoutPath)) {
488
- compileFile(layoutPath, node.layout, chunkCode);
641
+ compileFile(layoutPath, node.layout, chunkCode, chunkImports);
489
642
  }
490
643
  const errorPath = resolve(appDir, node.sourceDir, 'error.vsk');
491
644
  if (node.error && existsSync(errorPath)) {
492
- compileFile(errorPath, node.error, chunkCode);
645
+ compileFile(errorPath, node.error, chunkCode, chunkImports);
493
646
  }
494
647
  const notFoundPath = resolve(appDir, node.sourceDir, 'not-found.vsk');
495
648
  if (node.notFound && existsSync(notFoundPath)) {
496
- compileFile(notFoundPath, node.notFound, chunkCode);
649
+ compileFile(notFoundPath, node.notFound, chunkCode, chunkImports);
497
650
  }
498
651
  const offlinePath = resolve(appDir, node.sourceDir, 'offline.vsk');
499
652
  if (node.offline && existsSync(offlinePath)) {
500
- compileFile(offlinePath, node.offline, chunkCode);
653
+ compileFile(offlinePath, node.offline, chunkCode, chunkImports);
501
654
  }
502
655
  const networkPath = resolve(appDir, node.sourceDir, 'network.vsk');
503
656
  if (node.network && existsSync(networkPath)) {
504
- compileFile(networkPath, node.network, chunkCode);
657
+ compileFile(networkPath, node.network, chunkCode, chunkImports);
505
658
  }
506
659
  const loadingPath = resolve(appDir, node.sourceDir, 'loading.vsk');
507
660
  if (node.loading && existsSync(loadingPath)) {
508
- compileFile(loadingPath, node.loading, chunkCode);
661
+ compileFile(loadingPath, node.loading, chunkCode, chunkImports);
509
662
  }
510
663
  if (chunkCode.length > 0) {
511
664
  const chunkName = `page-${buildChunkName(node)}.js`;
512
- chunkEntries.push({ name: chunkName, code: chunkCode.join('\n\n'), node });
665
+ // Prepend this chunk's deduped imports (one statement per source, no
666
+ // duplicate bindings) so the classic script exposes the bare
667
+ // identifiers its file bodies reference, and esbuild can parse and
668
+ // inline them.
669
+ if (!chunkImports.ok)
670
+ console.error('[vesk] chunk import merge aborted for', chunkName + ':', chunkImports.reason);
671
+ const head = chunkImports.render();
672
+ const code = (head ? head + '\n\n' : '') + chunkCode.join('\n\n');
673
+ chunkEntries.push({ name: chunkName, code, node });
513
674
  }
514
675
  walkSplit(node.children || [], [..._chain, node]);
515
676
  }
516
677
  }
517
678
  walkSplit(routeTree, []);
518
679
  const sharedCode = [];
680
+ const sharedImports = new ChunkImports();
519
681
  const compMap = componentMap || new Map();
520
682
  for (const [compName, compPath] of compMap) {
521
- compileFile(compPath, compName, sharedCode);
683
+ compileFile(compPath, compName, sharedCode, sharedImports);
522
684
  }
523
685
  if (sharedCode.length > 0) {
524
- chunkEntries.push({ name: 'shared.js', code: sharedCode.join('\n\n'), node: null });
686
+ if (!sharedImports.ok)
687
+ console.error('[vesk] chunk import merge aborted for shared.js:', sharedImports.reason);
688
+ const head = sharedImports.render();
689
+ const code = (head ? head + '\n\n' : '') + sharedCode.join('\n\n');
690
+ chunkEntries.push({ name: 'shared.js', code, node: null });
525
691
  }
692
+ const chunkIIFE = (body) => `(()=>{\nconst __components = globalThis.__components || (globalThis.__components = {});\nconst __hydrators = globalThis.__hydrators || (globalThis.__hydrators = {});\n${body}\n})();\n`;
526
693
  for (const entry of chunkEntries) {
527
- if (entry.code.trim()) {
528
- chunks.push({
529
- name: entry.name,
530
- code: `(()=>{\nconst __components = globalThis.__components || (globalThis.__components = {});\nconst __hydrators = globalThis.__hydrators || (globalThis.__hydrators = {});\n${entry.code}\n})();\n`,
531
- });
694
+ if (!entry.code.trim())
695
+ continue;
696
+ let finalCode;
697
+ if (hasChunkImports(entry.code)) {
698
+ // Bundle the chunk through esbuild so every remaining import (npm
699
+ // packages like lucide-vesk and rewritten relative `.ts` value
700
+ // modules) is inlined into the IIFE — a classic script cannot carry
701
+ // a live `import`, and a stripped binding referenced by a component
702
+ // would otherwise throw `X is not defined` at hydration. Imports were
703
+ // already deduped per file by `ChunkImports`, so esbuild can parse the
704
+ // bundle entry.
705
+ const tmpBase = mkdtempSync(join(resolve(appDir, '..'), 'tmp-vesk-chunk-'));
706
+ const tmpFile = join(tmpBase, 'entry.js');
707
+ const toBundle = `const __components = globalThis.__components || (globalThis.__components = {});\nconst __hydrators = globalThis.__hydrators || (globalThis.__hydrators = {});\n${entry.code}\n`;
708
+ writeFileSync(tmpFile, toBundle);
709
+ try {
710
+ const result = await build({
711
+ entryPoints: [tmpFile],
712
+ bundle: true,
713
+ format: 'iife',
714
+ platform: 'browser',
715
+ write: false,
716
+ logLevel: 'silent',
717
+ loader: { '.js': 'tsx' },
718
+ });
719
+ finalCode = result.outputFiles[0].text;
720
+ }
721
+ catch (e) {
722
+ const err = e;
723
+ console.error('[vesk] chunk bundle failed', entry.name, err?.errors?.map((x) => x.text).join(' | ') || err?.message || String(e));
724
+ // Fall back to the historical behavior: strip every import so the
725
+ // classic script at least parses (referenced values will surface as
726
+ // ReferenceErrors at runtime — the pre-existing failure mode).
727
+ const stripped = demoteExports(removeCompiledNodes(entry.code, isAnyImport));
728
+ finalCode = chunkIIFE(stripped);
729
+ }
730
+ finally {
731
+ try {
732
+ rmSync(tmpBase, { recursive: true, force: true });
733
+ }
734
+ catch { }
735
+ }
736
+ }
737
+ else {
738
+ finalCode = chunkIIFE(entry.code);
532
739
  }
740
+ chunks.push({ name: entry.name, code: finalCode });
533
741
  }
534
742
  function annotate(nodes) {
535
743
  for (const node of nodes) {
package/dist/css.d.ts CHANGED
@@ -1,26 +1,21 @@
1
- export declare const TAILWIND_CSS_URL = "/_vesk/static/_tailwind.css";
2
1
  export declare const GLOBAL_CSS_URL = "/_vesk/static/global.css";
3
2
  /** Resolve the project's user stylesheet source (`src/global.css`, then `src/app.css`). */
4
3
  export declare function resolveUserCssPath(appDir: string): string | null;
5
4
  /** True when the project has a user stylesheet source (`src/global.css` or `src/app.css`). */
6
5
  export declare function hasUserCss(appDir: string): boolean;
7
- /** True when a non-empty `_tailwind.css` was produced for the build (prod disk output). */
8
- export declare function hasBuiltTailwindCss(outDir: string): boolean;
6
+ /** True when a non-empty `global.css` was produced for the build (prod disk output). */
7
+ export declare function hasBuiltGlobalCss(outDir: string): boolean;
9
8
  /** True when the active plugin pipeline includes the tailwind plugin. */
10
9
  export declare function isTailwindPlugin(plugins: Array<{
11
10
  name?: string;
12
11
  }> | undefined | null): boolean;
13
12
  /**
14
13
  * Single source of truth for the `/_vesk/static/*.css` link list. Callers
15
- * supply environment facts — `tailwind`: tailwind output is available
16
- * (built-file present + non-empty in prod, active plugin in dev);
17
- * `userCss`: a user stylesheet exists — so build, request, dev, and prod
18
- * paths share one ordering.
14
+ * supply environment facts — `enabled`: a global stylesheet output is
15
+ * available (built-file present + non-empty in prod, user stylesheet present
16
+ * in dev) — so build, request, dev, and prod paths share one ordering.
19
17
  */
20
18
  export declare function resolveCssUrls(opts: {
21
- tailwind?: boolean;
22
- userCss?: boolean;
19
+ enabled?: boolean;
23
20
  }): string[];
24
- /** Drop tailwind framework directives from user CSS, keeping hand-written rules. */
25
- export declare function stripTailwindDirectives(css: string): string;
26
21
  //# sourceMappingURL=css.d.ts.map
package/dist/css.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"css.d.ts","sourceRoot":"","sources":["../src/css.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,gBAAgB,gCAAgC,CAAC;AAC9D,eAAO,MAAM,cAAc,6BAA6B,CAAC;AAEzD,2FAA2F;AAC3F,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMhE;AAED,8FAA8F;AAC9F,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAElD;AAED,2FAA2F;AAC3F,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAG3D;AAED,yEAAyE;AACzE,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,KAAK,CAAC;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAE9F;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,MAAM,EAAE,CAKxF;AAED,oFAAoF;AACpF,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAkB3D"}
1
+ {"version":3,"file":"css.d.ts","sourceRoot":"","sources":["../src/css.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,cAAc,6BAA6B,CAAC;AAEzD,2FAA2F;AAC3F,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMhE;AAED,8FAA8F;AAC9F,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAElD;AAED,wFAAwF;AACxF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAGzD;AAED,yEAAyE;AACzE,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,KAAK,CAAC;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAE9F;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,MAAM,EAAE,CAGpE"}
package/dist/css.js CHANGED
@@ -1,14 +1,19 @@
1
1
  /**
2
2
  * Shared stylesheet resolution for the adapter build, prod server, generated
3
3
  * SSR functions, dev server, and action handler. Each environment feeds its
4
- * facts (`tailwind` output available, user stylesheet present) into
5
- * `resolveCssUrls`, so the `/_vesk/static/*.css` link list and its ordering
6
- * come from one place instead of six ad-hoc copies.
4
+ * facts (`enabled`: a built global stylesheet exists) into `resolveCssUrls`,
5
+ * so the `/_vesk/static/global.css` link list comes from one place instead of
6
+ * several ad-hoc copies.
7
+ *
8
+ * Vesk follows the single-file CSS convention used by other frameworks
9
+ * (Next.js/Remix/SvelteKit): a single `src/global.css` is the stylesheet
10
+ * source. When the Tailwind plugin is active the whole file is compiled into
11
+ * one `/_vesk/static/global.css`; otherwise the user CSS is served as-is from
12
+ * the same URL. There is no separate `_tailwind.css` anymore — user rules and
13
+ * the compiled Tailwind output live in the same file, in one `<link>`.
7
14
  */
8
15
  import { readFileSync, existsSync } from 'node:fs';
9
16
  import { resolve } from 'node:path';
10
- import { cssBlockEnd } from '@vesk/compiler/src/scan';
11
- export const TAILWIND_CSS_URL = '/_vesk/static/_tailwind.css';
12
17
  export const GLOBAL_CSS_URL = '/_vesk/static/global.css';
13
18
  /** Resolve the project's user stylesheet source (`src/global.css`, then `src/app.css`). */
14
19
  export function resolveUserCssPath(appDir) {
@@ -24,9 +29,9 @@ export function resolveUserCssPath(appDir) {
24
29
  export function hasUserCss(appDir) {
25
30
  return resolveUserCssPath(appDir) !== null;
26
31
  }
27
- /** True when a non-empty `_tailwind.css` was produced for the build (prod disk output). */
28
- export function hasBuiltTailwindCss(outDir) {
29
- const p = resolve(outDir, 'static', '_tailwind.css');
32
+ /** True when a non-empty `global.css` was produced for the build (prod disk output). */
33
+ export function hasBuiltGlobalCss(outDir) {
34
+ const p = resolve(outDir, 'static', 'global.css');
30
35
  return existsSync(p) && readFileSync(p, 'utf-8').trim().length > 0;
31
36
  }
32
37
  /** True when the active plugin pipeline includes the tailwind plugin. */
@@ -35,36 +40,12 @@ export function isTailwindPlugin(plugins) {
35
40
  }
36
41
  /**
37
42
  * Single source of truth for the `/_vesk/static/*.css` link list. Callers
38
- * supply environment facts — `tailwind`: tailwind output is available
39
- * (built-file present + non-empty in prod, active plugin in dev);
40
- * `userCss`: a user stylesheet exists — so build, request, dev, and prod
41
- * paths share one ordering.
43
+ * supply environment facts — `enabled`: a global stylesheet output is
44
+ * available (built-file present + non-empty in prod, user stylesheet present
45
+ * in dev) — so build, request, dev, and prod paths share one ordering.
42
46
  */
43
47
  export function resolveCssUrls(opts) {
44
- const urls = [];
45
- if (opts.tailwind)
46
- urls.push(TAILWIND_CSS_URL);
47
- if (opts.userCss)
48
- urls.push(GLOBAL_CSS_URL);
49
- return urls;
50
- }
51
- /** Drop tailwind framework directives from user CSS, keeping hand-written rules. */
52
- export function stripTailwindDirectives(css) {
53
- const blockStart = /^\s*@(theme\s*\{|layer\s+(components|utilities)\s*\{|utility\s+[\w-]+\s*\{)/;
54
- let result = css.replace(/^\s*@import\s+['"]tailwindcss['"]\s*;?\s*$/gm, '');
55
- result = result.replace(/^\s*@source\s+['"][^'"]+['"]\s*;?\s*$/gm, '');
56
- const output = [];
57
- let pos = 0;
58
- while (pos < result.length) {
59
- const lineEnd = result.indexOf('\n', pos) === -1 ? result.length : result.indexOf('\n', pos) + 1;
60
- const line = result.slice(pos, lineEnd);
61
- if (blockStart.test(line.trim())) {
62
- const end = cssBlockEnd(result, pos);
63
- pos = end;
64
- continue;
65
- }
66
- output.push(line);
67
- pos = lineEnd;
68
- }
69
- return output.join('').trim();
48
+ if (opts.enabled)
49
+ return [GLOBAL_CSS_URL];
50
+ return [];
70
51
  }
@@ -1 +1 @@
1
- {"version":3,"file":"dev-config.d.ts","sourceRoot":"","sources":["../src/dev-config.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,UAAU,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,OAAO,CAAC;CACf;AAED,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,CAMjE;AAQD;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAmC/G;AAED,oEAAoE;AACpE,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAM9E;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAQrG;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAc5F;AAoID,wJAAwJ;AACxJ,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAcxD;AAyYD;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA2EtF;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAqC9F"}
1
+ {"version":3,"file":"dev-config.d.ts","sourceRoot":"","sources":["../src/dev-config.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,UAAU,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,OAAO,CAAC;CACf;AAED,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,CAMjE;AAQD;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAmC/G;AAED,oEAAoE;AACpE,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAM9E;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAQrG;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAc5F;AAgOD,wJAAwJ;AACxJ,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAcxD;AAyYD;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA2EtF;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAqC9F"}