defuss-ssg 0.6.0 → 0.6.2

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/README.md CHANGED
@@ -19,6 +19,7 @@ Use Bun for package management. The published package targets Node `^20.19.0 ||
19
19
  - defuss components imported into MDX and HTML-like pages
20
20
  - Automatic hydration boundaries for components rendered from `components/`, `src/components/`, `csr/`, or `src/csr/`
21
21
  - Static assets copied from `assets/` or `src/assets/`
22
+ - Vite-style `public/` assets copied to the output root and served at `/<file>`
22
23
  - File-based API routes from `pages/**/*.ts` and `pages/**/*.js`
23
24
  - Root `index.mdx`, `index.md`, or `index.html` fallback when no pages directory exists
24
25
  - Pre-rendered endpoints via `prerender = true` and `getStaticPaths()`
@@ -282,7 +283,7 @@ Single command -> <command> .
282
283
  Command + folder -> <command> <folder>
283
284
  ```
284
285
 
285
- When `pages`, `components`, or `assets` are not configured explicitly, `defuss-ssg` prefers `src/pages`, `src/components`, `src/csr`, and `src/assets` before falling back to their project-root equivalents. If no pages directory exists, it falls back to root `index.mdx`, then `index.md`, then `index.html`.
286
+ When `pages`, `components`, or `assets` are not configured explicitly, `defuss-ssg` prefers `src/pages`, `src/components`, `src/csr`, and `src/assets` before falling back to their project-root equivalents. If no pages directory exists, it falls back to root `index.mdx`, then `index.md`, then `index.html`. A project-root `public/` directory is treated like Vite/Astro `public`: its files are available at the site root.
286
287
 
287
288
  Commands:
288
289
 
package/dist/cli.mjs CHANGED
@@ -1,17 +1,18 @@
1
1
  #!/usr/bin/env node
2
- import { v as validateProjectDir, b as build } from './vite-CaqPNORI.mjs';
3
- import { d as dev, s as serve } from './serve-Cd4Pw98E.mjs';
2
+ import { v as validateProjectDir, b as build } from './vite-DdJcWQGP.mjs';
3
+ import { d as dev, s as serve } from './serve-CTYzPwKQ.mjs';
4
4
  import { join, dirname, resolve } from 'node:path';
5
5
  import { existsSync, readFileSync } from 'node:fs';
6
6
  import { spawn } from 'node:child_process';
7
+ import 'fast-glob';
7
8
  import 'node:fs/promises';
8
- import 'node:url';
9
9
  import 'defuss/server';
10
10
  import 'node:crypto';
11
11
  import '@mdx-js/rollup';
12
- import 'fast-glob';
13
12
  import 'vite';
14
13
  import 'defuss-vite';
14
+ import 'node:module';
15
+ import 'node:url';
15
16
  import 'node:os';
16
17
  import 'esbuild';
17
18
  import 'rehype-katex';
package/dist/index.cjs CHANGED
@@ -1,17 +1,18 @@
1
1
  'use strict';
2
2
 
3
- var vite = require('./vite-CBAZNNYA.cjs');
3
+ var vite = require('./vite-CkpguLfK.cjs');
4
4
  var mdx = require('@mdx-js/rollup');
5
5
  var vite$1 = require('vite');
6
6
  var defuss = require('defuss-vite');
7
7
  var node_fs = require('node:fs');
8
8
  var node_path = require('node:path');
9
9
  var defussExpress = require('defuss-express');
10
+ require('fast-glob');
10
11
  require('node:fs/promises');
11
- require('node:url');
12
12
  require('defuss/server');
13
13
  require('node:crypto');
14
- require('fast-glob');
14
+ require('node:module');
15
+ require('node:url');
15
16
  require('node:os');
16
17
  require('esbuild');
17
18
  require('rehype-katex');
package/dist/index.mjs CHANGED
@@ -1,15 +1,16 @@
1
- export { H as HTTP_METHODS, b as build, a as buildEndpoints, c as compileEndpoints, d as compileRpcModule, e as configDefaults, f as defussSsg, g as discoverEndpointSourceFiles, h as discoverRpcFile, i as endpointFileToRoute, j as handleEndpointRoute, k as handleRpcRequest, l as initializeRpc, m as loadEndpointModule, n as matchRoutePattern, r as readConfig, o as registerEndpoints, p as resolveEndpoints, q as routeToExpressPattern } from './vite-CaqPNORI.mjs';
2
- export { d as dev, s as serve } from './serve-Cd4Pw98E.mjs';
1
+ export { H as HTTP_METHODS, b as build, a as buildEndpoints, c as compileEndpoints, d as compileRpcModule, e as configDefaults, f as defussSsg, g as discoverEndpointSourceFiles, h as discoverRpcFile, i as endpointFileToRoute, j as handleEndpointRoute, k as handleRpcRequest, l as initializeRpc, m as loadEndpointModule, n as matchRoutePattern, r as readConfig, o as registerEndpoints, p as resolveEndpoints, q as routeToExpressPattern } from './vite-DdJcWQGP.mjs';
2
+ export { d as dev, s as serve } from './serve-CTYzPwKQ.mjs';
3
+ import 'fast-glob';
3
4
  import 'node:fs';
4
5
  import 'node:fs/promises';
5
6
  import 'node:path';
6
- import 'node:url';
7
7
  import 'defuss/server';
8
8
  import 'node:crypto';
9
9
  import '@mdx-js/rollup';
10
- import 'fast-glob';
11
10
  import 'vite';
12
11
  import 'defuss-vite';
12
+ import 'node:module';
13
+ import 'node:url';
13
14
  import 'node:os';
14
15
  import 'esbuild';
15
16
  import 'rehype-katex';
package/dist/runtime.cjs CHANGED
@@ -7,6 +7,22 @@ const setupLiveReload = () => {
7
7
  };
8
8
  const runtimeWindow = window;
9
9
  const pageCache = runtimeWindow.__defuss_pageCache ?? /* @__PURE__ */ new Map();
10
+ const isHydratableComponent = (value) => typeof value === "function";
11
+ const pickHydrationComponent = (moduleExports, preferredExportName) => {
12
+ if (isHydratableComponent(moduleExports.default)) {
13
+ return moduleExports.default;
14
+ }
15
+ if (preferredExportName && isHydratableComponent(moduleExports[preferredExportName])) {
16
+ return moduleExports[preferredExportName];
17
+ }
18
+ const functionExports = Object.entries(moduleExports).filter(
19
+ ([key, value]) => key !== "default" && isHydratableComponent(value)
20
+ );
21
+ if (functionExports.length === 1) {
22
+ return functionExports[0][1];
23
+ }
24
+ return null;
25
+ };
10
26
  const HYDRATION_ATTRIBUTE_NAMES = [
11
27
  "data-hydrate-id",
12
28
  "data-hydrate",
@@ -514,6 +530,7 @@ const hydrateBoundary = async (boundary, options = {}) => {
514
530
  return;
515
531
  }
516
532
  const id = boundary.getAttribute("data-hydrate-id");
533
+ const exportName = boundary.getAttribute("data-hydrate-export");
517
534
  const src = boundary.getAttribute("data-hydrate-src");
518
535
  const propsStr = boundary.getAttribute("data-hydrate-props");
519
536
  const runtimeUrl = boundary.getAttribute("data-hydrate-runtime");
@@ -530,15 +547,20 @@ const hydrateBoundary = async (boundary, options = {}) => {
530
547
  /* @vite-ignore */
531
548
  `${runtimeUrl}${cacheBust}`
532
549
  );
533
- const exports$1 = await import(
550
+ const moduleExports = await import(
534
551
  /* @vite-ignore */
535
552
  `${src}${cacheBust}`
536
553
  );
537
- if (!exports$1 || typeof exports$1.default !== "function") {
538
- console.error(`[hydrate:${id}] No default export in ${src}`);
554
+ const Component = pickHydrationComponent(
555
+ moduleExports,
556
+ exportName
557
+ );
558
+ if (!Component) {
559
+ console.error(
560
+ `[hydrate:${id}] No hydratable export in ${src}${exportName ? ` (expected ${exportName})` : ""}`
561
+ );
539
562
  return;
540
563
  }
541
- const Component = exports$1.default;
542
564
  const props = JSON.parse(propsStr);
543
565
  console.log(
544
566
  `[hydrate:${id}] Rendering component ${Component.name || "(anon)"} with props:`,
package/dist/runtime.mjs CHANGED
@@ -5,6 +5,22 @@ const setupLiveReload = () => {
5
5
  };
6
6
  const runtimeWindow = window;
7
7
  const pageCache = runtimeWindow.__defuss_pageCache ?? /* @__PURE__ */ new Map();
8
+ const isHydratableComponent = (value) => typeof value === "function";
9
+ const pickHydrationComponent = (moduleExports, preferredExportName) => {
10
+ if (isHydratableComponent(moduleExports.default)) {
11
+ return moduleExports.default;
12
+ }
13
+ if (preferredExportName && isHydratableComponent(moduleExports[preferredExportName])) {
14
+ return moduleExports[preferredExportName];
15
+ }
16
+ const functionExports = Object.entries(moduleExports).filter(
17
+ ([key, value]) => key !== "default" && isHydratableComponent(value)
18
+ );
19
+ if (functionExports.length === 1) {
20
+ return functionExports[0][1];
21
+ }
22
+ return null;
23
+ };
8
24
  const HYDRATION_ATTRIBUTE_NAMES = [
9
25
  "data-hydrate-id",
10
26
  "data-hydrate",
@@ -512,6 +528,7 @@ const hydrateBoundary = async (boundary, options = {}) => {
512
528
  return;
513
529
  }
514
530
  const id = boundary.getAttribute("data-hydrate-id");
531
+ const exportName = boundary.getAttribute("data-hydrate-export");
515
532
  const src = boundary.getAttribute("data-hydrate-src");
516
533
  const propsStr = boundary.getAttribute("data-hydrate-props");
517
534
  const runtimeUrl = boundary.getAttribute("data-hydrate-runtime");
@@ -528,15 +545,20 @@ const hydrateBoundary = async (boundary, options = {}) => {
528
545
  /* @vite-ignore */
529
546
  `${runtimeUrl}${cacheBust}`
530
547
  );
531
- const exports$1 = await import(
548
+ const moduleExports = await import(
532
549
  /* @vite-ignore */
533
550
  `${src}${cacheBust}`
534
551
  );
535
- if (!exports$1 || typeof exports$1.default !== "function") {
536
- console.error(`[hydrate:${id}] No default export in ${src}`);
552
+ const Component = pickHydrationComponent(
553
+ moduleExports,
554
+ exportName
555
+ );
556
+ if (!Component) {
557
+ console.error(
558
+ `[hydrate:${id}] No hydratable export in ${src}${exportName ? ` (expected ${exportName})` : ""}`
559
+ );
537
560
  return;
538
561
  }
539
- const Component = exports$1.default;
540
562
  const props = JSON.parse(propsStr);
541
563
  console.log(
542
564
  `[hydrate:${id}] Rendering component ${Component.name || "(anon)"} with props:`,
@@ -1,7 +1,7 @@
1
1
  import mdx from '@mdx-js/rollup';
2
2
  import { createServer } from 'vite';
3
3
  import defuss from 'defuss-vite';
4
- import { v as validateProjectDir, r as readConfig, f as defussSsg, o as registerEndpoints, l as initializeRpc, s as readIncomingBody, t as createWebRequest, k as handleRpcRequest, u as sendWebResponse } from './vite-CaqPNORI.mjs';
4
+ import { v as validateProjectDir, r as readConfig, f as defussSsg, o as registerEndpoints, l as initializeRpc, s as readIncomingBody, t as createWebRequest, k as handleRpcRequest, u as sendWebResponse } from './vite-DdJcWQGP.mjs';
5
5
  import { existsSync } from 'node:fs';
6
6
  import { join } from 'node:path';
7
7
  import { express, startServer } from 'defuss-express';
@@ -1,15 +1,16 @@
1
1
  'use strict';
2
2
 
3
+ var glob = require('fast-glob');
3
4
  var node_fs = require('node:fs');
4
5
  var promises = require('node:fs/promises');
5
6
  var node_path = require('node:path');
6
- var node_url = require('node:url');
7
7
  var server = require('defuss/server');
8
8
  var node_crypto = require('node:crypto');
9
9
  var mdx = require('@mdx-js/rollup');
10
- var glob = require('fast-glob');
11
10
  var vite = require('vite');
12
11
  var defuss = require('defuss-vite');
12
+ var node_module = require('node:module');
13
+ var node_url = require('node:url');
13
14
  var node_os = require('node:os');
14
15
  var esbuild = require('esbuild');
15
16
  var rehypeKatex = require('rehype-katex');
@@ -78,6 +79,13 @@ const toStableHydrateId = (seed) => {
78
79
  const digest = node_crypto.createHash("sha1").update(seed).digest("hex").slice(0, 12);
79
80
  return `dh_${digest}`;
80
81
  };
82
+ const toPascalCase = (value) => value.split(/[^A-Za-z0-9]+/).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join("");
83
+ const toComponentExportName = (componentFilePath) => {
84
+ const extension = node_path.extname(componentFilePath);
85
+ const fileStem = node_path.basename(componentFilePath, extension);
86
+ const exportBaseName = fileStem.toLowerCase() === "index" ? node_path.basename(node_path.dirname(componentFilePath)) : fileStem;
87
+ return toPascalCase(exportBaseName);
88
+ };
81
89
  function applyAutoHydrate(vdom, componentsDir, componentsPublicPrefix, relativeOutputHtmlFilePath) {
82
90
  const componentsRoot = node_path.resolve(componentsDir);
83
91
  let hydrateIndex = 0;
@@ -93,6 +101,9 @@ function applyAutoHydrate(vdom, componentsDir, componentsPublicPrefix, relativeO
93
101
  if (vnode.sourceInfo && typeof vnode.sourceInfo === "object" && typeof vnode.sourceInfo.fileName === "string" && vnode.sourceInfo.fileName.includes(componentsDir) && vnode.type !== "script" && vnode.type !== "head" && vnode.type !== "link" && vnode.type !== "meta" && vnode.type !== "title") {
94
102
  const sourceFile = node_path.resolve(vnode.sourceInfo.fileName);
95
103
  const relativeComponentPath = node_path.relative(componentsRoot, sourceFile);
104
+ const componentExportName = toComponentExportName(
105
+ relativeComponentPath
106
+ );
96
107
  if (relativeComponentPath.startsWith("..") || relativeComponentPath.length === 0) {
97
108
  throw new Error(
98
109
  `[auto-hydrate] Component source file is outside the components directory: ${sourceFile}`
@@ -126,6 +137,7 @@ function applyAutoHydrate(vdom, componentsDir, componentsPublicPrefix, relativeO
126
137
  attributes: {
127
138
  "data-hydrate-id": id,
128
139
  "data-hydrate": "true",
140
+ "data-hydrate-export": componentExportName,
129
141
  "data-hydrate-src": clientSrcFile,
130
142
  "data-hydrate-props": JSON.stringify(componentProps),
131
143
  "data-hydrate-runtime": `/${componentsPublicPrefix}/runtime.js`
@@ -643,8 +655,8 @@ const registerEndpoints = async (registrar, projectDir, _config, debug = false)
643
655
  }
644
656
  };
645
657
 
646
- const __filename$2 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('vite-CBAZNNYA.cjs', document.baseURI).href)));
647
- const __dirname$2 = node_path.dirname(__filename$2);
658
+ const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('vite-CkpguLfK.cjs', document.baseURI).href)));
659
+ const __dirname$1 = node_path.dirname(__filename$1);
648
660
  const isHtmlLikePageSource = (filePath) => {
649
661
  const extension = node_path.extname(filePath).toLowerCase();
650
662
  return extension === "" || extension === ".md" || extension === ".mdx" || extension === ".html";
@@ -658,16 +670,103 @@ const isPathInOrUnder$1 = (filePath, dirPath) => {
658
670
  const isRootIndexPageSource$1 = (filePath) => /^index(?:\.mdx?|\.html)?$/i.test(normalizePath$1(filePath));
659
671
  const resolveLocalHelperFile = (sourceRelativePath, builtRelativePath) => {
660
672
  const candidates = [
661
- node_path.resolve(__dirname$2, sourceRelativePath),
662
- node_path.resolve(__dirname$2, "..", "src", sourceRelativePath),
663
- node_path.resolve(__dirname$2, builtRelativePath)
673
+ node_path.resolve(__dirname$1, sourceRelativePath),
674
+ node_path.resolve(__dirname$1, "..", "src", sourceRelativePath),
675
+ node_path.resolve(__dirname$1, builtRelativePath)
664
676
  ];
665
677
  for (const candidate of candidates) {
666
678
  if (node_fs.existsSync(candidate)) {
667
679
  return candidate;
668
680
  }
669
681
  }
670
- return node_path.resolve(__dirname$2, builtRelativePath);
682
+ return node_path.resolve(__dirname$1, builtRelativePath);
683
+ };
684
+ const loadProjectTailwindVitePlugins = async (projectDir, debug) => {
685
+ const requireFromBuild = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('vite-CkpguLfK.cjs', document.baseURI).href)));
686
+ try {
687
+ const resolvedPluginPath = requireFromBuild.resolve("@tailwindcss/vite", {
688
+ paths: [projectDir]
689
+ });
690
+ const tailwindModule = await import(node_url.pathToFileURL(resolvedPluginPath).href);
691
+ if (typeof tailwindModule.default !== "function") {
692
+ return [];
693
+ }
694
+ const plugin = tailwindModule.default();
695
+ if (!plugin) {
696
+ return [];
697
+ }
698
+ return Array.isArray(plugin) ? plugin : [plugin];
699
+ } catch (error) {
700
+ const message = error instanceof Error ? error.message : String(error);
701
+ const isMissingPlugin = message.includes("@tailwindcss/vite");
702
+ if (!isMissingPlugin && debug) {
703
+ console.warn("[build] Failed to load @tailwindcss/vite from project:", error);
704
+ }
705
+ return [];
706
+ }
707
+ };
708
+ const injectStylesheetLinks$1 = (html, hrefs) => {
709
+ const missingHrefs = hrefs.filter((href) => !html.includes(`href="${href}"`));
710
+ if (missingHrefs.length === 0) {
711
+ return html;
712
+ }
713
+ const linkTags = missingHrefs.map((href) => `<link rel="stylesheet" href="${href}">`).join("");
714
+ if (html.includes("</head>")) {
715
+ return html.replace("</head>", `${linkTags}</head>`);
716
+ }
717
+ return `${linkTags}${html}`;
718
+ };
719
+ const normalizeComponentStylesheet = (css) => {
720
+ const compiledTailwindBanner = "/*! tailwindcss";
721
+ const compiledTailwindIndex = css.indexOf(compiledTailwindBanner);
722
+ if (compiledTailwindIndex <= 0) {
723
+ return css;
724
+ }
725
+ const prefix = css.slice(0, compiledTailwindIndex).trimStart();
726
+ const hasRawTailwindPrelude = prefix.startsWith("@layer theme, base, components, utilities;") && prefix.includes("@theme default");
727
+ if (!hasRawTailwindPrelude) {
728
+ return css;
729
+ }
730
+ return css.slice(compiledTailwindIndex);
731
+ };
732
+ const normalizeComponentStylesheets = async (componentsOutputDir) => {
733
+ const cssFiles = await glob.async(node_path.join(componentsOutputDir, "**/*.css"));
734
+ for (const cssFile of cssFiles) {
735
+ const currentCss = await promises.readFile(cssFile, "utf-8");
736
+ const normalizedCss = normalizeComponentStylesheet(currentCss);
737
+ if (normalizedCss !== currentCss) {
738
+ await promises.writeFile(cssFile, normalizedCss);
739
+ }
740
+ }
741
+ };
742
+ const getComponentStylesheetHrefs$1 = async (componentsOutputDir, componentsPublicDir) => {
743
+ if (!node_fs.existsSync(componentsOutputDir)) {
744
+ return [];
745
+ }
746
+ const cssFiles = await glob.async(node_path.join(componentsOutputDir, "**/*.css"));
747
+ return cssFiles.sort((left, right) => left.localeCompare(right)).map(
748
+ (cssFile) => `/${normalizePath$1(node_path.join(componentsPublicDir, node_path.relative(componentsOutputDir, cssFile)))}`
749
+ );
750
+ };
751
+ const injectComponentStylesheetsIntoOutputHtml = async (outputProjectDir, componentsOutputDir, componentsPublicDir) => {
752
+ if (!node_fs.existsSync(outputProjectDir)) {
753
+ return;
754
+ }
755
+ const stylesheetHrefs = await getComponentStylesheetHrefs$1(
756
+ componentsOutputDir,
757
+ componentsPublicDir
758
+ );
759
+ if (stylesheetHrefs.length === 0) {
760
+ return;
761
+ }
762
+ const htmlFiles = await glob.async(node_path.join(outputProjectDir, "**/*.html"));
763
+ for (const htmlFile of htmlFiles) {
764
+ const currentHtml = await promises.readFile(htmlFile, "utf8");
765
+ const nextHtml = injectStylesheetLinks$1(currentHtml, stylesheetHrefs);
766
+ if (nextHtml !== currentHtml) {
767
+ await promises.writeFile(htmlFile, nextHtml);
768
+ }
769
+ }
671
770
  };
672
771
  const build = async ({
673
772
  projectDir,
@@ -703,6 +802,7 @@ const build = async ({
703
802
  const inputPagesDir = projectPaths.pagesSourceDirAbsolute;
704
803
  const inputComponentsDir = projectPaths.componentsSourceDirAbsolute;
705
804
  const inputAssetsDir = projectPaths.assetsSourceDirAbsolute;
805
+ const inputPublicDir = node_path.join(projectDir, "public");
706
806
  const hasInputPagesDir = projectPaths.hasPagesSourceDir;
707
807
  const tmpPagesDir = node_path.join(config.tmp, projectPaths.pagesSourceDir);
708
808
  const tmpComponentsDir = node_path.join(config.tmp, projectPaths.componentsSourceDir);
@@ -723,6 +823,7 @@ const build = async ({
723
823
  console.log("Input pages dir:", inputPagesDir);
724
824
  console.log("Input components dir:", inputComponentsDir);
725
825
  console.log("Input assets dir:", inputAssetsDir);
826
+ console.log("Input public dir:", inputPublicDir);
726
827
  console.log("Temp pages dir:", tmpPagesDir);
727
828
  console.log("Temp components dir:", tmpComponentsDir);
728
829
  console.log("Output pages dir:", outputPagesDir);
@@ -741,6 +842,8 @@ const build = async ({
741
842
  changeKind = "component";
742
843
  } else if (isPathInOrUnder$1(changedRelative, projectPaths.assetsSourceDir)) {
743
844
  changeKind = "asset";
845
+ } else if (isPathInOrUnder$1(changedRelative, "public")) {
846
+ changeKind = "asset";
744
847
  } else if (changedRelative === "config.ts" || changedRelative === "config.js") {
745
848
  changeKind = "config";
746
849
  }
@@ -758,7 +861,7 @@ const build = async ({
758
861
  const shouldCopyToTemp = (src) => {
759
862
  const relative2 = src.startsWith(projectDir) ? normalizePath$1(src.slice(projectDir.length).replace(/^[\\/]+/, "")) : src;
760
863
  const firstSegment = relative2.split(/[\\/]/)[0];
761
- return !(firstSegment === "node_modules" || firstSegment === config.output || firstSegment === ".endpoints" || firstSegment === ".ssg-temp" || firstSegment === ".git" || projectPaths.assetsSourceDirCandidates.some(
864
+ return !(firstSegment === "node_modules" || firstSegment === config.output || firstSegment === ".endpoints" || firstSegment === ".ssg-temp" || firstSegment === ".git" || firstSegment === "public" || projectPaths.assetsSourceDirCandidates.some(
762
865
  (candidateDir) => isPathInOrUnder$1(relative2, candidateDir)
763
866
  ));
764
867
  };
@@ -1015,11 +1118,15 @@ const build = async ({
1015
1118
  node_path.resolve(entryFile)
1016
1119
  ])
1017
1120
  );
1121
+ const componentBuildPlugins = [
1122
+ defuss(),
1123
+ ...await loadProjectTailwindVitePlugins(projectDir, debug)
1124
+ ];
1018
1125
  await vite.build({
1019
1126
  root: node_path.resolve(tmpComponentsDir),
1020
1127
  configFile: false,
1021
1128
  publicDir: false,
1022
- plugins: [defuss()],
1129
+ plugins: componentBuildPlugins,
1023
1130
  build: {
1024
1131
  lib: {
1025
1132
  entry: componentEntries,
@@ -1037,6 +1144,7 @@ const build = async ({
1037
1144
  }
1038
1145
  }
1039
1146
  });
1147
+ await normalizeComponentStylesheets(tmpComponentOutDir);
1040
1148
  timeEndDebug("[build] vite-components");
1041
1149
  }
1042
1150
  if (isFullBuild || changeKind === "page" || changeKind === "endpoint") {
@@ -1052,6 +1160,16 @@ const build = async ({
1052
1160
  if (node_fs.existsSync(inputAssetsDir)) {
1053
1161
  await promises.cp(inputAssetsDir, outputAssetsDir, { recursive: true });
1054
1162
  }
1163
+ if (node_fs.existsSync(inputPublicDir)) {
1164
+ await promises.cp(inputPublicDir, outputProjectDir, { recursive: true });
1165
+ }
1166
+ }
1167
+ if (isFullBuild || changeKind === "page" || changeKind === "component") {
1168
+ await injectComponentStylesheetsIntoOutputHtml(
1169
+ outputProjectDir,
1170
+ outputComponentsDir,
1171
+ projectPaths.componentsPublicDir
1172
+ );
1055
1173
  }
1056
1174
  timeEndDebug("[build] copy-outputs");
1057
1175
  if (isFullBuild || changeKind === "component") {
@@ -1280,8 +1398,6 @@ const handleRpcRequest = async (ctx) => {
1280
1398
  }
1281
1399
  };
1282
1400
 
1283
- const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('vite-CBAZNNYA.cjs', document.baseURI).href)));
1284
- const __dirname$1 = node_path.dirname(__filename$1);
1285
1401
  const MIME_TYPES = {
1286
1402
  ".css": "text/css; charset=utf-8",
1287
1403
  ".gif": "image/gif",
@@ -1680,18 +1796,25 @@ const injectHmrClientModule = (html) => {
1680
1796
  }
1681
1797
  return `${importTag}${html}`;
1682
1798
  };
1683
- const resolveRuntimeHelperFile = () => {
1684
- const candidates = [
1685
- node_path.join(__dirname$1, "runtime.ts"),
1686
- node_path.join(__dirname$1, "..", "src", "runtime.ts"),
1687
- node_path.join(__dirname$1, "runtime.mjs")
1688
- ];
1689
- for (const candidate of candidates) {
1690
- if (node_fs.existsSync(candidate)) {
1691
- return candidate;
1692
- }
1799
+ const injectStylesheetLinks = (html, hrefs) => {
1800
+ const missingHrefs = hrefs.filter((href) => !html.includes(`href="${href}"`));
1801
+ if (missingHrefs.length === 0) {
1802
+ return html;
1693
1803
  }
1694
- return null;
1804
+ const linkTags = missingHrefs.map((href) => `<link rel="stylesheet" href="${href}">`).join("");
1805
+ if (html.includes("</head>")) {
1806
+ return html.replace("</head>", `${linkTags}</head>`);
1807
+ }
1808
+ return `${linkTags}${html}`;
1809
+ };
1810
+ const getComponentStylesheetHrefs = async (componentsOutputDir, componentsPublicDir) => {
1811
+ if (!node_fs.existsSync(componentsOutputDir)) {
1812
+ return [];
1813
+ }
1814
+ const cssFiles = await glob.async(node_path.join(componentsOutputDir, "**/*.css"));
1815
+ return cssFiles.sort((left, right) => left.localeCompare(right)).map(
1816
+ (cssFile) => `/${normalizePath(node_path.join(componentsPublicDir, node_path.relative(componentsOutputDir, cssFile)))}`
1817
+ );
1695
1818
  };
1696
1819
  const isHtmlLikeFile = (filePath) => {
1697
1820
  const extension = node_path.extname(filePath);
@@ -1741,6 +1864,9 @@ const classifyChangedFile = (file, projectDir, config, rpcFile) => {
1741
1864
  if (isPathInOrUnder(relativeFile, paths.assetsSourceDir)) {
1742
1865
  return "asset";
1743
1866
  }
1867
+ if (isPathInOrUnder(relativeFile, "public")) {
1868
+ return "asset";
1869
+ }
1744
1870
  return "dependency";
1745
1871
  };
1746
1872
  const filePathToComponentPublicPath = (file, componentsSourceDir, componentsPublicDir) => {
@@ -2129,6 +2255,13 @@ function defussSsg(options = {}) {
2129
2255
  );
2130
2256
  const el = server.renderSync(vdom, document.documentElement, { browserGlobals });
2131
2257
  let html = server.renderToString(el);
2258
+ html = injectStylesheetLinks(
2259
+ html,
2260
+ await getComponentStylesheetHrefs(
2261
+ node_path.join(projectDir, config.output, currentPaths.componentsPublicDir),
2262
+ currentPaths.componentsPublicDir
2263
+ )
2264
+ );
2132
2265
  html = injectHmrClientModule(html);
2133
2266
  html = await server$1.transformIndexHtml(requestUrl.pathname, html);
2134
2267
  res.setHeader("Cache-Control", "no-store");
@@ -2143,27 +2276,14 @@ function defussSsg(options = {}) {
2143
2276
  const maybeServeComponent = async (server, req, res) => {
2144
2277
  const requestUrl = new URL(req.url || "/", "http://localhost");
2145
2278
  const pathname = requestUrl.pathname;
2146
- if (!pathname.startsWith(`/${config.components}/`)) {
2147
- return false;
2148
- }
2149
- const componentRoutePrefix = `/${config.components}/`;
2150
- if (pathname === `/${config.components}/runtime.js`) {
2151
- const runtimePath = resolveRuntimeHelperFile();
2152
- if (!runtimePath) {
2153
- return false;
2154
- }
2155
- const transformed2 = await server.transformRequest(runtimePath);
2156
- if (transformed2) {
2157
- res.setHeader("Cache-Control", "no-store");
2158
- res.setHeader("Content-Type", "text/javascript; charset=utf-8");
2159
- res.statusCode = 200;
2160
- res.end(transformed2.code);
2161
- return true;
2162
- }
2279
+ const currentPaths = resolveSsgPaths(projectDir, config);
2280
+ const componentRoutePrefix = `/${currentPaths.componentsPublicDir}/`;
2281
+ if (!pathname.startsWith(componentRoutePrefix)) {
2163
2282
  return false;
2164
2283
  }
2165
2284
  const relativeComponentPath = pathname.slice(componentRoutePrefix.length);
2166
- const isBuiltComponentAsset = relativeComponentPath.startsWith("chunks/") || /^chunk-|^client-/.test(relativeComponentPath);
2285
+ const componentExtension = node_path.extname(relativeComponentPath).toLowerCase();
2286
+ const isBuiltComponentAsset = relativeComponentPath === "runtime.js" || relativeComponentPath.startsWith("chunks/") || /^chunk-|^client-/.test(relativeComponentPath) || componentExtension !== ".js";
2167
2287
  if (isBuiltComponentAsset) {
2168
2288
  return false;
2169
2289
  }
@@ -2171,7 +2291,6 @@ function defussSsg(options = {}) {
2171
2291
  node_path.extname(relativeComponentPath),
2172
2292
  ""
2173
2293
  );
2174
- const currentPaths = resolveSsgPaths(projectDir, config);
2175
2294
  const extensions = [".tsx", ".ts", ".jsx", ".js"];
2176
2295
  let sourceFile = null;
2177
2296
  for (const ext of extensions) {
@@ -2185,10 +2304,7 @@ function defussSsg(options = {}) {
2185
2304
  }
2186
2305
  }
2187
2306
  if (!sourceFile) {
2188
- res.setHeader("Cache-Control", "no-store");
2189
- res.statusCode = 404;
2190
- res.end();
2191
- return true;
2307
+ return false;
2192
2308
  }
2193
2309
  const transformed = await server.transformRequest(sourceFile);
2194
2310
  if (transformed) {
@@ -2226,9 +2342,18 @@ function defussSsg(options = {}) {
2226
2342
  MIME_TYPES[node_path.extname(outputFile)] ?? "application/octet-stream"
2227
2343
  );
2228
2344
  if (outputFile.endsWith(".html")) {
2345
+ const currentPaths = resolveSsgPaths(projectDir, config);
2229
2346
  const html = await server.transformIndexHtml(
2230
2347
  requestUrl.pathname,
2231
- injectHmrClientModule(body.toString("utf8"))
2348
+ injectHmrClientModule(
2349
+ injectStylesheetLinks(
2350
+ body.toString("utf8"),
2351
+ await getComponentStylesheetHrefs(
2352
+ node_path.join(projectDir, config.output, currentPaths.componentsPublicDir),
2353
+ currentPaths.componentsPublicDir
2354
+ )
2355
+ )
2356
+ )
2232
2357
  );
2233
2358
  res.statusCode = 200;
2234
2359
  res.end(method === "HEAD" ? void 0 : html);
@@ -2283,6 +2408,7 @@ function defussSsg(options = {}) {
2283
2408
  ...paths.assetsSourceDirCandidates.map(
2284
2409
  (candidateDir) => node_path.join(projectDir, candidateDir)
2285
2410
  ),
2411
+ node_path.join(projectDir, "public"),
2286
2412
  node_path.join(projectDir, "config.ts"),
2287
2413
  node_path.join(projectDir, "config.js")
2288
2414
  ];
@@ -1,13 +1,14 @@
1
+ import glob from 'fast-glob';
1
2
  import { existsSync, statSync, mkdirSync, rmSync, readdirSync, symlinkSync } from 'node:fs';
2
3
  import { writeFile, readFile, cp, stat } from 'node:fs/promises';
3
- import { join, resolve, relative, sep, extname, dirname } from 'node:path';
4
- import { fileURLToPath } from 'node:url';
4
+ import { join, resolve, relative, sep, extname, basename, dirname } from 'node:path';
5
5
  import { getBrowserGlobals, getDocument, renderSync, renderToString } from 'defuss/server';
6
6
  import { createHash } from 'node:crypto';
7
7
  import mdx from '@mdx-js/rollup';
8
- import glob from 'fast-glob';
9
8
  import { createServer, build as build$1 } from 'vite';
10
9
  import defuss from 'defuss-vite';
10
+ import { createRequire } from 'node:module';
11
+ import { pathToFileURL, fileURLToPath } from 'node:url';
11
12
  import { tmpdir } from 'node:os';
12
13
  import esbuild from 'esbuild';
13
14
  import rehypeKatex from 'rehype-katex';
@@ -75,6 +76,13 @@ const toStableHydrateId = (seed) => {
75
76
  const digest = createHash("sha1").update(seed).digest("hex").slice(0, 12);
76
77
  return `dh_${digest}`;
77
78
  };
79
+ const toPascalCase = (value) => value.split(/[^A-Za-z0-9]+/).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join("");
80
+ const toComponentExportName = (componentFilePath) => {
81
+ const extension = extname(componentFilePath);
82
+ const fileStem = basename(componentFilePath, extension);
83
+ const exportBaseName = fileStem.toLowerCase() === "index" ? basename(dirname(componentFilePath)) : fileStem;
84
+ return toPascalCase(exportBaseName);
85
+ };
78
86
  function applyAutoHydrate(vdom, componentsDir, componentsPublicPrefix, relativeOutputHtmlFilePath) {
79
87
  const componentsRoot = resolve(componentsDir);
80
88
  let hydrateIndex = 0;
@@ -90,6 +98,9 @@ function applyAutoHydrate(vdom, componentsDir, componentsPublicPrefix, relativeO
90
98
  if (vnode.sourceInfo && typeof vnode.sourceInfo === "object" && typeof vnode.sourceInfo.fileName === "string" && vnode.sourceInfo.fileName.includes(componentsDir) && vnode.type !== "script" && vnode.type !== "head" && vnode.type !== "link" && vnode.type !== "meta" && vnode.type !== "title") {
91
99
  const sourceFile = resolve(vnode.sourceInfo.fileName);
92
100
  const relativeComponentPath = relative(componentsRoot, sourceFile);
101
+ const componentExportName = toComponentExportName(
102
+ relativeComponentPath
103
+ );
93
104
  if (relativeComponentPath.startsWith("..") || relativeComponentPath.length === 0) {
94
105
  throw new Error(
95
106
  `[auto-hydrate] Component source file is outside the components directory: ${sourceFile}`
@@ -123,6 +134,7 @@ function applyAutoHydrate(vdom, componentsDir, componentsPublicPrefix, relativeO
123
134
  attributes: {
124
135
  "data-hydrate-id": id,
125
136
  "data-hydrate": "true",
137
+ "data-hydrate-export": componentExportName,
126
138
  "data-hydrate-src": clientSrcFile,
127
139
  "data-hydrate-props": JSON.stringify(componentProps),
128
140
  "data-hydrate-runtime": `/${componentsPublicPrefix}/runtime.js`
@@ -640,8 +652,8 @@ const registerEndpoints = async (registrar, projectDir, _config, debug = false)
640
652
  }
641
653
  };
642
654
 
643
- const __filename$2 = fileURLToPath(import.meta.url);
644
- const __dirname$2 = dirname(__filename$2);
655
+ const __filename$1 = fileURLToPath(import.meta.url);
656
+ const __dirname$1 = dirname(__filename$1);
645
657
  const isHtmlLikePageSource = (filePath) => {
646
658
  const extension = extname(filePath).toLowerCase();
647
659
  return extension === "" || extension === ".md" || extension === ".mdx" || extension === ".html";
@@ -655,16 +667,103 @@ const isPathInOrUnder$1 = (filePath, dirPath) => {
655
667
  const isRootIndexPageSource$1 = (filePath) => /^index(?:\.mdx?|\.html)?$/i.test(normalizePath$1(filePath));
656
668
  const resolveLocalHelperFile = (sourceRelativePath, builtRelativePath) => {
657
669
  const candidates = [
658
- resolve(__dirname$2, sourceRelativePath),
659
- resolve(__dirname$2, "..", "src", sourceRelativePath),
660
- resolve(__dirname$2, builtRelativePath)
670
+ resolve(__dirname$1, sourceRelativePath),
671
+ resolve(__dirname$1, "..", "src", sourceRelativePath),
672
+ resolve(__dirname$1, builtRelativePath)
661
673
  ];
662
674
  for (const candidate of candidates) {
663
675
  if (existsSync(candidate)) {
664
676
  return candidate;
665
677
  }
666
678
  }
667
- return resolve(__dirname$2, builtRelativePath);
679
+ return resolve(__dirname$1, builtRelativePath);
680
+ };
681
+ const loadProjectTailwindVitePlugins = async (projectDir, debug) => {
682
+ const requireFromBuild = createRequire(import.meta.url);
683
+ try {
684
+ const resolvedPluginPath = requireFromBuild.resolve("@tailwindcss/vite", {
685
+ paths: [projectDir]
686
+ });
687
+ const tailwindModule = await import(pathToFileURL(resolvedPluginPath).href);
688
+ if (typeof tailwindModule.default !== "function") {
689
+ return [];
690
+ }
691
+ const plugin = tailwindModule.default();
692
+ if (!plugin) {
693
+ return [];
694
+ }
695
+ return Array.isArray(plugin) ? plugin : [plugin];
696
+ } catch (error) {
697
+ const message = error instanceof Error ? error.message : String(error);
698
+ const isMissingPlugin = message.includes("@tailwindcss/vite");
699
+ if (!isMissingPlugin && debug) {
700
+ console.warn("[build] Failed to load @tailwindcss/vite from project:", error);
701
+ }
702
+ return [];
703
+ }
704
+ };
705
+ const injectStylesheetLinks$1 = (html, hrefs) => {
706
+ const missingHrefs = hrefs.filter((href) => !html.includes(`href="${href}"`));
707
+ if (missingHrefs.length === 0) {
708
+ return html;
709
+ }
710
+ const linkTags = missingHrefs.map((href) => `<link rel="stylesheet" href="${href}">`).join("");
711
+ if (html.includes("</head>")) {
712
+ return html.replace("</head>", `${linkTags}</head>`);
713
+ }
714
+ return `${linkTags}${html}`;
715
+ };
716
+ const normalizeComponentStylesheet = (css) => {
717
+ const compiledTailwindBanner = "/*! tailwindcss";
718
+ const compiledTailwindIndex = css.indexOf(compiledTailwindBanner);
719
+ if (compiledTailwindIndex <= 0) {
720
+ return css;
721
+ }
722
+ const prefix = css.slice(0, compiledTailwindIndex).trimStart();
723
+ const hasRawTailwindPrelude = prefix.startsWith("@layer theme, base, components, utilities;") && prefix.includes("@theme default");
724
+ if (!hasRawTailwindPrelude) {
725
+ return css;
726
+ }
727
+ return css.slice(compiledTailwindIndex);
728
+ };
729
+ const normalizeComponentStylesheets = async (componentsOutputDir) => {
730
+ const cssFiles = await glob.async(join(componentsOutputDir, "**/*.css"));
731
+ for (const cssFile of cssFiles) {
732
+ const currentCss = await readFile(cssFile, "utf-8");
733
+ const normalizedCss = normalizeComponentStylesheet(currentCss);
734
+ if (normalizedCss !== currentCss) {
735
+ await writeFile(cssFile, normalizedCss);
736
+ }
737
+ }
738
+ };
739
+ const getComponentStylesheetHrefs$1 = async (componentsOutputDir, componentsPublicDir) => {
740
+ if (!existsSync(componentsOutputDir)) {
741
+ return [];
742
+ }
743
+ const cssFiles = await glob.async(join(componentsOutputDir, "**/*.css"));
744
+ return cssFiles.sort((left, right) => left.localeCompare(right)).map(
745
+ (cssFile) => `/${normalizePath$1(join(componentsPublicDir, relative(componentsOutputDir, cssFile)))}`
746
+ );
747
+ };
748
+ const injectComponentStylesheetsIntoOutputHtml = async (outputProjectDir, componentsOutputDir, componentsPublicDir) => {
749
+ if (!existsSync(outputProjectDir)) {
750
+ return;
751
+ }
752
+ const stylesheetHrefs = await getComponentStylesheetHrefs$1(
753
+ componentsOutputDir,
754
+ componentsPublicDir
755
+ );
756
+ if (stylesheetHrefs.length === 0) {
757
+ return;
758
+ }
759
+ const htmlFiles = await glob.async(join(outputProjectDir, "**/*.html"));
760
+ for (const htmlFile of htmlFiles) {
761
+ const currentHtml = await readFile(htmlFile, "utf8");
762
+ const nextHtml = injectStylesheetLinks$1(currentHtml, stylesheetHrefs);
763
+ if (nextHtml !== currentHtml) {
764
+ await writeFile(htmlFile, nextHtml);
765
+ }
766
+ }
668
767
  };
669
768
  const build = async ({
670
769
  projectDir,
@@ -700,6 +799,7 @@ const build = async ({
700
799
  const inputPagesDir = projectPaths.pagesSourceDirAbsolute;
701
800
  const inputComponentsDir = projectPaths.componentsSourceDirAbsolute;
702
801
  const inputAssetsDir = projectPaths.assetsSourceDirAbsolute;
802
+ const inputPublicDir = join(projectDir, "public");
703
803
  const hasInputPagesDir = projectPaths.hasPagesSourceDir;
704
804
  const tmpPagesDir = join(config.tmp, projectPaths.pagesSourceDir);
705
805
  const tmpComponentsDir = join(config.tmp, projectPaths.componentsSourceDir);
@@ -720,6 +820,7 @@ const build = async ({
720
820
  console.log("Input pages dir:", inputPagesDir);
721
821
  console.log("Input components dir:", inputComponentsDir);
722
822
  console.log("Input assets dir:", inputAssetsDir);
823
+ console.log("Input public dir:", inputPublicDir);
723
824
  console.log("Temp pages dir:", tmpPagesDir);
724
825
  console.log("Temp components dir:", tmpComponentsDir);
725
826
  console.log("Output pages dir:", outputPagesDir);
@@ -738,6 +839,8 @@ const build = async ({
738
839
  changeKind = "component";
739
840
  } else if (isPathInOrUnder$1(changedRelative, projectPaths.assetsSourceDir)) {
740
841
  changeKind = "asset";
842
+ } else if (isPathInOrUnder$1(changedRelative, "public")) {
843
+ changeKind = "asset";
741
844
  } else if (changedRelative === "config.ts" || changedRelative === "config.js") {
742
845
  changeKind = "config";
743
846
  }
@@ -755,7 +858,7 @@ const build = async ({
755
858
  const shouldCopyToTemp = (src) => {
756
859
  const relative2 = src.startsWith(projectDir) ? normalizePath$1(src.slice(projectDir.length).replace(/^[\\/]+/, "")) : src;
757
860
  const firstSegment = relative2.split(/[\\/]/)[0];
758
- return !(firstSegment === "node_modules" || firstSegment === config.output || firstSegment === ".endpoints" || firstSegment === ".ssg-temp" || firstSegment === ".git" || projectPaths.assetsSourceDirCandidates.some(
861
+ return !(firstSegment === "node_modules" || firstSegment === config.output || firstSegment === ".endpoints" || firstSegment === ".ssg-temp" || firstSegment === ".git" || firstSegment === "public" || projectPaths.assetsSourceDirCandidates.some(
759
862
  (candidateDir) => isPathInOrUnder$1(relative2, candidateDir)
760
863
  ));
761
864
  };
@@ -1012,11 +1115,15 @@ const build = async ({
1012
1115
  resolve(entryFile)
1013
1116
  ])
1014
1117
  );
1118
+ const componentBuildPlugins = [
1119
+ defuss(),
1120
+ ...await loadProjectTailwindVitePlugins(projectDir, debug)
1121
+ ];
1015
1122
  await build$1({
1016
1123
  root: resolve(tmpComponentsDir),
1017
1124
  configFile: false,
1018
1125
  publicDir: false,
1019
- plugins: [defuss()],
1126
+ plugins: componentBuildPlugins,
1020
1127
  build: {
1021
1128
  lib: {
1022
1129
  entry: componentEntries,
@@ -1034,6 +1141,7 @@ const build = async ({
1034
1141
  }
1035
1142
  }
1036
1143
  });
1144
+ await normalizeComponentStylesheets(tmpComponentOutDir);
1037
1145
  timeEndDebug("[build] vite-components");
1038
1146
  }
1039
1147
  if (isFullBuild || changeKind === "page" || changeKind === "endpoint") {
@@ -1049,6 +1157,16 @@ const build = async ({
1049
1157
  if (existsSync(inputAssetsDir)) {
1050
1158
  await cp(inputAssetsDir, outputAssetsDir, { recursive: true });
1051
1159
  }
1160
+ if (existsSync(inputPublicDir)) {
1161
+ await cp(inputPublicDir, outputProjectDir, { recursive: true });
1162
+ }
1163
+ }
1164
+ if (isFullBuild || changeKind === "page" || changeKind === "component") {
1165
+ await injectComponentStylesheetsIntoOutputHtml(
1166
+ outputProjectDir,
1167
+ outputComponentsDir,
1168
+ projectPaths.componentsPublicDir
1169
+ );
1052
1170
  }
1053
1171
  timeEndDebug("[build] copy-outputs");
1054
1172
  if (isFullBuild || changeKind === "component") {
@@ -1277,8 +1395,6 @@ const handleRpcRequest = async (ctx) => {
1277
1395
  }
1278
1396
  };
1279
1397
 
1280
- const __filename$1 = fileURLToPath(import.meta.url);
1281
- const __dirname$1 = dirname(__filename$1);
1282
1398
  const MIME_TYPES = {
1283
1399
  ".css": "text/css; charset=utf-8",
1284
1400
  ".gif": "image/gif",
@@ -1677,18 +1793,25 @@ const injectHmrClientModule = (html) => {
1677
1793
  }
1678
1794
  return `${importTag}${html}`;
1679
1795
  };
1680
- const resolveRuntimeHelperFile = () => {
1681
- const candidates = [
1682
- join(__dirname$1, "runtime.ts"),
1683
- join(__dirname$1, "..", "src", "runtime.ts"),
1684
- join(__dirname$1, "runtime.mjs")
1685
- ];
1686
- for (const candidate of candidates) {
1687
- if (existsSync(candidate)) {
1688
- return candidate;
1689
- }
1796
+ const injectStylesheetLinks = (html, hrefs) => {
1797
+ const missingHrefs = hrefs.filter((href) => !html.includes(`href="${href}"`));
1798
+ if (missingHrefs.length === 0) {
1799
+ return html;
1690
1800
  }
1691
- return null;
1801
+ const linkTags = missingHrefs.map((href) => `<link rel="stylesheet" href="${href}">`).join("");
1802
+ if (html.includes("</head>")) {
1803
+ return html.replace("</head>", `${linkTags}</head>`);
1804
+ }
1805
+ return `${linkTags}${html}`;
1806
+ };
1807
+ const getComponentStylesheetHrefs = async (componentsOutputDir, componentsPublicDir) => {
1808
+ if (!existsSync(componentsOutputDir)) {
1809
+ return [];
1810
+ }
1811
+ const cssFiles = await glob.async(join(componentsOutputDir, "**/*.css"));
1812
+ return cssFiles.sort((left, right) => left.localeCompare(right)).map(
1813
+ (cssFile) => `/${normalizePath(join(componentsPublicDir, relative(componentsOutputDir, cssFile)))}`
1814
+ );
1692
1815
  };
1693
1816
  const isHtmlLikeFile = (filePath) => {
1694
1817
  const extension = extname(filePath);
@@ -1738,6 +1861,9 @@ const classifyChangedFile = (file, projectDir, config, rpcFile) => {
1738
1861
  if (isPathInOrUnder(relativeFile, paths.assetsSourceDir)) {
1739
1862
  return "asset";
1740
1863
  }
1864
+ if (isPathInOrUnder(relativeFile, "public")) {
1865
+ return "asset";
1866
+ }
1741
1867
  return "dependency";
1742
1868
  };
1743
1869
  const filePathToComponentPublicPath = (file, componentsSourceDir, componentsPublicDir) => {
@@ -2126,6 +2252,13 @@ function defussSsg(options = {}) {
2126
2252
  );
2127
2253
  const el = renderSync(vdom, document.documentElement, { browserGlobals });
2128
2254
  let html = renderToString(el);
2255
+ html = injectStylesheetLinks(
2256
+ html,
2257
+ await getComponentStylesheetHrefs(
2258
+ join(projectDir, config.output, currentPaths.componentsPublicDir),
2259
+ currentPaths.componentsPublicDir
2260
+ )
2261
+ );
2129
2262
  html = injectHmrClientModule(html);
2130
2263
  html = await server.transformIndexHtml(requestUrl.pathname, html);
2131
2264
  res.setHeader("Cache-Control", "no-store");
@@ -2140,27 +2273,14 @@ function defussSsg(options = {}) {
2140
2273
  const maybeServeComponent = async (server, req, res) => {
2141
2274
  const requestUrl = new URL(req.url || "/", "http://localhost");
2142
2275
  const pathname = requestUrl.pathname;
2143
- if (!pathname.startsWith(`/${config.components}/`)) {
2144
- return false;
2145
- }
2146
- const componentRoutePrefix = `/${config.components}/`;
2147
- if (pathname === `/${config.components}/runtime.js`) {
2148
- const runtimePath = resolveRuntimeHelperFile();
2149
- if (!runtimePath) {
2150
- return false;
2151
- }
2152
- const transformed2 = await server.transformRequest(runtimePath);
2153
- if (transformed2) {
2154
- res.setHeader("Cache-Control", "no-store");
2155
- res.setHeader("Content-Type", "text/javascript; charset=utf-8");
2156
- res.statusCode = 200;
2157
- res.end(transformed2.code);
2158
- return true;
2159
- }
2276
+ const currentPaths = resolveSsgPaths(projectDir, config);
2277
+ const componentRoutePrefix = `/${currentPaths.componentsPublicDir}/`;
2278
+ if (!pathname.startsWith(componentRoutePrefix)) {
2160
2279
  return false;
2161
2280
  }
2162
2281
  const relativeComponentPath = pathname.slice(componentRoutePrefix.length);
2163
- const isBuiltComponentAsset = relativeComponentPath.startsWith("chunks/") || /^chunk-|^client-/.test(relativeComponentPath);
2282
+ const componentExtension = extname(relativeComponentPath).toLowerCase();
2283
+ const isBuiltComponentAsset = relativeComponentPath === "runtime.js" || relativeComponentPath.startsWith("chunks/") || /^chunk-|^client-/.test(relativeComponentPath) || componentExtension !== ".js";
2164
2284
  if (isBuiltComponentAsset) {
2165
2285
  return false;
2166
2286
  }
@@ -2168,7 +2288,6 @@ function defussSsg(options = {}) {
2168
2288
  extname(relativeComponentPath),
2169
2289
  ""
2170
2290
  );
2171
- const currentPaths = resolveSsgPaths(projectDir, config);
2172
2291
  const extensions = [".tsx", ".ts", ".jsx", ".js"];
2173
2292
  let sourceFile = null;
2174
2293
  for (const ext of extensions) {
@@ -2182,10 +2301,7 @@ function defussSsg(options = {}) {
2182
2301
  }
2183
2302
  }
2184
2303
  if (!sourceFile) {
2185
- res.setHeader("Cache-Control", "no-store");
2186
- res.statusCode = 404;
2187
- res.end();
2188
- return true;
2304
+ return false;
2189
2305
  }
2190
2306
  const transformed = await server.transformRequest(sourceFile);
2191
2307
  if (transformed) {
@@ -2223,9 +2339,18 @@ function defussSsg(options = {}) {
2223
2339
  MIME_TYPES[extname(outputFile)] ?? "application/octet-stream"
2224
2340
  );
2225
2341
  if (outputFile.endsWith(".html")) {
2342
+ const currentPaths = resolveSsgPaths(projectDir, config);
2226
2343
  const html = await server.transformIndexHtml(
2227
2344
  requestUrl.pathname,
2228
- injectHmrClientModule(body.toString("utf8"))
2345
+ injectHmrClientModule(
2346
+ injectStylesheetLinks(
2347
+ body.toString("utf8"),
2348
+ await getComponentStylesheetHrefs(
2349
+ join(projectDir, config.output, currentPaths.componentsPublicDir),
2350
+ currentPaths.componentsPublicDir
2351
+ )
2352
+ )
2353
+ )
2229
2354
  );
2230
2355
  res.statusCode = 200;
2231
2356
  res.end(method === "HEAD" ? void 0 : html);
@@ -2280,6 +2405,7 @@ function defussSsg(options = {}) {
2280
2405
  ...paths.assetsSourceDirCandidates.map(
2281
2406
  (candidateDir) => join(projectDir, candidateDir)
2282
2407
  ),
2408
+ join(projectDir, "public"),
2283
2409
  join(projectDir, "config.ts"),
2284
2410
  join(projectDir, "config.js")
2285
2411
  ];
package/dist/vite.cjs CHANGED
@@ -1,16 +1,17 @@
1
1
  'use strict';
2
2
 
3
+ require('fast-glob');
3
4
  require('node:fs');
4
5
  require('node:fs/promises');
5
6
  require('node:path');
6
- require('node:url');
7
7
  require('defuss/server');
8
- var vite = require('./vite-CBAZNNYA.cjs');
8
+ var vite = require('./vite-CkpguLfK.cjs');
9
9
  require('node:crypto');
10
10
  require('@mdx-js/rollup');
11
- require('fast-glob');
12
11
  require('vite');
13
12
  require('defuss-vite');
13
+ require('node:module');
14
+ require('node:url');
14
15
  require('node:os');
15
16
  require('esbuild');
16
17
  require('rehype-katex');
package/dist/vite.mjs CHANGED
@@ -1,14 +1,15 @@
1
+ import 'fast-glob';
1
2
  import 'node:fs';
2
3
  import 'node:fs/promises';
3
4
  import 'node:path';
4
- import 'node:url';
5
5
  import 'defuss/server';
6
- export { f as defussSsg } from './vite-CaqPNORI.mjs';
6
+ export { f as defussSsg } from './vite-DdJcWQGP.mjs';
7
7
  import 'node:crypto';
8
8
  import '@mdx-js/rollup';
9
- import 'fast-glob';
10
9
  import 'vite';
11
10
  import 'defuss-vite';
11
+ import 'node:module';
12
+ import 'node:url';
12
13
  import 'node:os';
13
14
  import 'esbuild';
14
15
  import 'rehype-katex';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "defuss-ssg",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"