@vojtaholik/static-kit-core 1.0.0
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/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/plugins/build-plugins.d.ts +8 -0
- package/dist/plugins/build-plugins.d.ts.map +1 -0
- package/dist/plugins/build-plugins.js +150 -0
- package/dist/plugins/index.d.ts +7 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +3 -0
- package/dist/plugins/pages-preview.d.ts +4 -0
- package/dist/plugins/pages-preview.d.ts.map +1 -0
- package/dist/plugins/pages-preview.js +292 -0
- package/dist/plugins/svg-sprite.d.ts +8 -0
- package/dist/plugins/svg-sprite.d.ts.map +1 -0
- package/dist/plugins/svg-sprite.js +98 -0
- package/dist/types.d.ts +25 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/utils/config.d.ts +5 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +34 -0
- package/dist/utils/file-scanner.d.ts +9 -0
- package/dist/utils/file-scanner.d.ts.map +1 -0
- package/dist/utils/file-scanner.js +40 -0
- package/dist/utils/html-imports.d.ts +6 -0
- package/dist/utils/html-imports.d.ts.map +1 -0
- package/dist/utils/html-imports.js +69 -0
- package/dist/vite-config.d.ts +5 -0
- package/dist/vite-config.d.ts.map +1 -0
- package/dist/vite-config.js +75 -0
- package/package.json +62 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { createStaticKitConfig, staticKitConfig } from "./vite-config.js";
|
|
2
|
+
export { svgSpritePlugin, pagesPreviewPlugin, buildPlugins } from "./plugins/index.js";
|
|
3
|
+
export { loadStaticKitConfig, normalizeBase, timeStamp } from "./utils/config.js";
|
|
4
|
+
export { processHtmlImports } from "./utils/html-imports.js";
|
|
5
|
+
export { scanDirectory, getInputEntries } from "./utils/file-scanner.js";
|
|
6
|
+
export type { StaticKitConfig, StaticKitOptions, PagesPreviewOptions } from "./types.js";
|
|
7
|
+
export type { SvgSpriteOptions, BuildPluginsOptions } from "./plugins/index.js";
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAG1E,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,YAAY,EACb,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EACL,mBAAmB,EACnB,aAAa,EACb,SAAS,EACV,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,kBAAkB,EACnB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,aAAa,EACb,eAAe,EAChB,MAAM,yBAAyB,CAAC;AAGjC,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAEpB,YAAY,EACV,gBAAgB,EAChB,mBAAmB,EACpB,MAAM,oBAAoB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Main exports
|
|
2
|
+
export { createStaticKitConfig, staticKitConfig } from "./vite-config.js";
|
|
3
|
+
// Plugins
|
|
4
|
+
export { svgSpritePlugin, pagesPreviewPlugin, buildPlugins } from "./plugins/index.js";
|
|
5
|
+
// Utilities
|
|
6
|
+
export { loadStaticKitConfig, normalizeBase, timeStamp } from "./utils/config.js";
|
|
7
|
+
export { processHtmlImports } from "./utils/html-imports.js";
|
|
8
|
+
export { scanDirectory, getInputEntries } from "./utils/file-scanner.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
import type { StaticKitConfig } from "../types.js";
|
|
3
|
+
export interface BuildPluginsOptions {
|
|
4
|
+
config: StaticKitConfig;
|
|
5
|
+
publicDir?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function buildPlugins(options: BuildPluginsOptions): Plugin[];
|
|
8
|
+
//# sourceMappingURL=build-plugins.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-plugins.d.ts","sourceRoot":"","sources":["../../src/plugins/build-plugins.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAInC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,eAAe,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AASD,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,EAAE,CAoKnE"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fg from "fast-glob";
|
|
4
|
+
import { processHtmlImports } from "../utils/html-imports.js";
|
|
5
|
+
import { normalizeBase, timeStamp } from "../utils/config.js";
|
|
6
|
+
async function createHtaccessFile(distPath) {
|
|
7
|
+
await fs.writeFile(path.join(distPath, ".htaccess"), "DirectoryIndex index.html\nRewriteEngine On\nRewriteCond %{REQUEST_FILENAME} !-f\nRewriteCond %{REQUEST_FILENAME} !-d\nRewriteRule ^([^.]+)$ $1.html [L]");
|
|
8
|
+
}
|
|
9
|
+
export function buildPlugins(options) {
|
|
10
|
+
const { config, publicDir = "public" } = options;
|
|
11
|
+
const normalizedBase = normalizeBase(config.build?.base);
|
|
12
|
+
return [
|
|
13
|
+
// Create .htaccess file
|
|
14
|
+
{
|
|
15
|
+
name: "create-htaccess-file",
|
|
16
|
+
apply: "build",
|
|
17
|
+
writeBundle: async () => {
|
|
18
|
+
await createHtaccessFile("dist");
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
// Copy static assets
|
|
22
|
+
{
|
|
23
|
+
name: "copy-static-assets",
|
|
24
|
+
apply: "build",
|
|
25
|
+
writeBundle: async () => {
|
|
26
|
+
try {
|
|
27
|
+
const srcPublicPath = path.resolve(publicDir);
|
|
28
|
+
const destPublicPath = path.resolve(`dist/${normalizedBase}`);
|
|
29
|
+
// Create dist/public directory
|
|
30
|
+
await fs.mkdir(destPublicPath, { recursive: true });
|
|
31
|
+
// Copy files from public/ to dist/public/ using fast-glob
|
|
32
|
+
const allFiles = await fg("**/*", {
|
|
33
|
+
cwd: srcPublicPath,
|
|
34
|
+
onlyFiles: true,
|
|
35
|
+
dot: true,
|
|
36
|
+
});
|
|
37
|
+
for (const file of allFiles) {
|
|
38
|
+
const srcPath = path.join(srcPublicPath, file);
|
|
39
|
+
const destPath = path.join(destPublicPath, file);
|
|
40
|
+
// Ensure destination directory exists
|
|
41
|
+
await fs.mkdir(path.dirname(destPath), { recursive: true });
|
|
42
|
+
await fs.copyFile(srcPath, destPath);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
// Public directory doesn't exist or can't be copied
|
|
47
|
+
console.warn("Could not copy public directory:", error);
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
// Process HTML files
|
|
52
|
+
{
|
|
53
|
+
name: "html-processor",
|
|
54
|
+
apply: "build",
|
|
55
|
+
writeBundle: async () => {
|
|
56
|
+
const distPath = path.resolve("dist");
|
|
57
|
+
// Find all HTML files in the dist directory using fast-glob
|
|
58
|
+
const findHtmlFiles = async (dir) => {
|
|
59
|
+
try {
|
|
60
|
+
return await fg("**/*.html", {
|
|
61
|
+
cwd: dir,
|
|
62
|
+
onlyFiles: true,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
try {
|
|
70
|
+
const htmlFiles = await findHtmlFiles(distPath);
|
|
71
|
+
for (const htmlFile of htmlFiles) {
|
|
72
|
+
const fullPath = path.join(distPath, htmlFile);
|
|
73
|
+
// Check if this is a file that should be moved (in src/pages structure)
|
|
74
|
+
if (htmlFile.startsWith("src/pages/")) {
|
|
75
|
+
const cleanFileName = htmlFile.replace(/^src\/pages\//, "");
|
|
76
|
+
const newPath = path.join(distPath, cleanFileName);
|
|
77
|
+
// Read the file, process it, and write to new location
|
|
78
|
+
const content = await fs.readFile(fullPath, "utf-8");
|
|
79
|
+
const sourceDir = path.dirname(path.resolve("src/pages", cleanFileName));
|
|
80
|
+
const processedContent = await processHtmlImports(content, sourceDir);
|
|
81
|
+
// Remove any existing CSS and JS links that Vite injected
|
|
82
|
+
const cleanedContent = processedContent
|
|
83
|
+
.replace(/<link[^>]*rel=["']stylesheet["'][^>]*>/g, "")
|
|
84
|
+
.replace(/<script[^>]*src=[^>]*><\/script>/g, "")
|
|
85
|
+
.replace(/<use href=\"\/sprite.svg/g, () => {
|
|
86
|
+
return `<use href=\"${normalizedBase}images/sprite.svg\?v=${timeStamp()}`;
|
|
87
|
+
})
|
|
88
|
+
// Normalize asset paths: Convert absolute paths to use configured base
|
|
89
|
+
// Transform /public/images/... → public/images/... (based on normalizedBase)
|
|
90
|
+
// Transform /images/... → public/images/... (shorthand syntax)
|
|
91
|
+
// This ensures all asset references work with the custom build structure
|
|
92
|
+
.replace(/src="\/public\//g, `src="${normalizedBase}`)
|
|
93
|
+
.replace(/href="\/public\//g, `href="${normalizedBase}`)
|
|
94
|
+
.replace(/src="\/images\//g, `src="${normalizedBase}images/`)
|
|
95
|
+
.replace(/href="\/images\//g, `href="${normalizedBase}images/`)
|
|
96
|
+
.trim();
|
|
97
|
+
// Calculate asset paths based on page depth and base config
|
|
98
|
+
const pathSegments = cleanFileName.split("/");
|
|
99
|
+
const depth = pathSegments.length - 1;
|
|
100
|
+
// For relative paths, go up directories then into configured base
|
|
101
|
+
const relativePath = depth > 0 ? "../".repeat(depth) : "";
|
|
102
|
+
const stylesPath = `${normalizedBase}css/styles.css?v=${timeStamp()}`;
|
|
103
|
+
const jsPath = `${normalizedBase}js/index.js?v=${timeStamp()}`;
|
|
104
|
+
// Create full HTML with correct paths
|
|
105
|
+
const fullHtml = `<!DOCTYPE html>
|
|
106
|
+
<html lang="${config.templates?.language || "en"}">
|
|
107
|
+
<head>
|
|
108
|
+
<meta charset="UTF-8">
|
|
109
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
110
|
+
<title>${cleanFileName.replace(".html", "")}</title>
|
|
111
|
+
<link rel="stylesheet" href="${stylesPath}">
|
|
112
|
+
</head>
|
|
113
|
+
<body>
|
|
114
|
+
${cleanedContent}
|
|
115
|
+
<script type="module" src="${jsPath}"></script>
|
|
116
|
+
</body>
|
|
117
|
+
</html>`;
|
|
118
|
+
// Create directory if needed
|
|
119
|
+
await fs.mkdir(path.dirname(newPath), { recursive: true });
|
|
120
|
+
// Write to new location
|
|
121
|
+
await fs.writeFile(newPath, fullHtml);
|
|
122
|
+
// Remove old file
|
|
123
|
+
await fs.unlink(fullPath);
|
|
124
|
+
// Remove empty directories
|
|
125
|
+
let dirToCheck = path.dirname(fullPath);
|
|
126
|
+
while (dirToCheck !== distPath) {
|
|
127
|
+
try {
|
|
128
|
+
const items = await fs.readdir(dirToCheck);
|
|
129
|
+
if (items.length === 0) {
|
|
130
|
+
await fs.rmdir(dirToCheck);
|
|
131
|
+
dirToCheck = path.dirname(dirToCheck);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
console.error("Error processing HTML files:", error);
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
];
|
|
150
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { svgSpritePlugin } from "./svg-sprite.js";
|
|
2
|
+
export { pagesPreviewPlugin } from "./pages-preview.js";
|
|
3
|
+
export { buildPlugins } from "./build-plugins.js";
|
|
4
|
+
export type { SvgSpriteOptions } from "./svg-sprite.js";
|
|
5
|
+
export type { PagesPreviewOptions } from "../types.js";
|
|
6
|
+
export type { BuildPluginsOptions } from "./build-plugins.js";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/plugins/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACvD,YAAY,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pages-preview.d.ts","sourceRoot":"","sources":["../../src/plugins/pages-preview.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAGnC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAwMvD,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,mBAAwB,GAAG,MAAM,CAuJ5E"}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fg from "fast-glob";
|
|
4
|
+
import { processHtmlImports } from "../utils/html-imports.js";
|
|
5
|
+
async function scanPagesDirectory(pagesDir) {
|
|
6
|
+
try {
|
|
7
|
+
const htmlFiles = await fg("**/*.html", {
|
|
8
|
+
cwd: pagesDir,
|
|
9
|
+
onlyFiles: true,
|
|
10
|
+
ignore: ["node_modules/**"],
|
|
11
|
+
});
|
|
12
|
+
const pages = htmlFiles.map((file) => file.replace(".html", ""));
|
|
13
|
+
return pages.sort();
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
// Directory doesn't exist or can't be read
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function generatePagesIndex(pages, components, routePrefix, componentsRoutePrefix) {
|
|
21
|
+
// Group pages by directory
|
|
22
|
+
const pagesByDir = {};
|
|
23
|
+
const componentsByDir = {};
|
|
24
|
+
for (const page of pages) {
|
|
25
|
+
const parts = page.split(path.sep);
|
|
26
|
+
if (parts.length === 1) {
|
|
27
|
+
// Root level page
|
|
28
|
+
if (!pagesByDir[""])
|
|
29
|
+
pagesByDir[""] = [];
|
|
30
|
+
pagesByDir[""].push(page);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
// Nested page
|
|
34
|
+
const dir = parts.slice(0, -1).join("/");
|
|
35
|
+
if (!pagesByDir[dir])
|
|
36
|
+
pagesByDir[dir] = [];
|
|
37
|
+
pagesByDir[dir].push(page);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
for (const component of components) {
|
|
41
|
+
const parts = component.split(path.sep);
|
|
42
|
+
if (parts.length === 1) {
|
|
43
|
+
// Root level component
|
|
44
|
+
if (!componentsByDir[""])
|
|
45
|
+
componentsByDir[""] = [];
|
|
46
|
+
componentsByDir[""].push(component);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
// Nested component
|
|
50
|
+
const dir = parts.slice(0, -1).join("/");
|
|
51
|
+
if (!componentsByDir[dir])
|
|
52
|
+
componentsByDir[dir] = [];
|
|
53
|
+
componentsByDir[dir].push(component);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Generate HTML sections
|
|
57
|
+
let sectionsHtml = "";
|
|
58
|
+
// Root pages first
|
|
59
|
+
if (pagesByDir[""]) {
|
|
60
|
+
sectionsHtml += `
|
|
61
|
+
<section>
|
|
62
|
+
<h2>📄 Pages</h2>
|
|
63
|
+
<ul>
|
|
64
|
+
${pagesByDir[""]
|
|
65
|
+
.map((page) => `<li><a href="${routePrefix}/${page}">${page}</a></li>`)
|
|
66
|
+
.join("\n ")}
|
|
67
|
+
</ul>
|
|
68
|
+
</section>`;
|
|
69
|
+
}
|
|
70
|
+
// Then subdirectory pages
|
|
71
|
+
const sortedPageDirs = Object.keys(pagesByDir)
|
|
72
|
+
.filter((dir) => dir !== "")
|
|
73
|
+
.sort();
|
|
74
|
+
for (const dir of sortedPageDirs) {
|
|
75
|
+
sectionsHtml += `
|
|
76
|
+
<section>
|
|
77
|
+
<h2>📁 pages/${dir}/</h2>
|
|
78
|
+
<ul>
|
|
79
|
+
${pagesByDir[dir]
|
|
80
|
+
.map((page) => {
|
|
81
|
+
const fileName = page.split("/").pop();
|
|
82
|
+
return `<li><a href="${routePrefix}/${page}">${fileName}</a></li>`;
|
|
83
|
+
})
|
|
84
|
+
.join("\n ")}
|
|
85
|
+
</ul>
|
|
86
|
+
</section>`;
|
|
87
|
+
}
|
|
88
|
+
// Root components
|
|
89
|
+
if (componentsByDir[""]) {
|
|
90
|
+
sectionsHtml += `
|
|
91
|
+
<section>
|
|
92
|
+
<h2>🧩 Components</h2>
|
|
93
|
+
<ul>
|
|
94
|
+
${componentsByDir[""]
|
|
95
|
+
.map((component) => `<li><a href="${componentsRoutePrefix}/${component}">${component}</a></li>`)
|
|
96
|
+
.join("\n ")}
|
|
97
|
+
</ul>
|
|
98
|
+
</section>`;
|
|
99
|
+
}
|
|
100
|
+
// Then subdirectory components
|
|
101
|
+
const sortedComponentDirs = Object.keys(componentsByDir)
|
|
102
|
+
.filter((dir) => dir !== "")
|
|
103
|
+
.sort();
|
|
104
|
+
for (const dir of sortedComponentDirs) {
|
|
105
|
+
sectionsHtml += `
|
|
106
|
+
<section>
|
|
107
|
+
<h2>📁 components/${dir}/</h2>
|
|
108
|
+
<ul>
|
|
109
|
+
${componentsByDir[dir]
|
|
110
|
+
.map((component) => {
|
|
111
|
+
const fileName = component.split("/").pop();
|
|
112
|
+
return `<li><a href="${componentsRoutePrefix}/${component}">${fileName}</a></li>`;
|
|
113
|
+
})
|
|
114
|
+
.join("\n ")}
|
|
115
|
+
</ul>
|
|
116
|
+
</section>`;
|
|
117
|
+
}
|
|
118
|
+
return `<!DOCTYPE html>
|
|
119
|
+
<html lang="en">
|
|
120
|
+
<head>
|
|
121
|
+
<meta charset="UTF-8">
|
|
122
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
123
|
+
<title>Pages & Components Preview</title>
|
|
124
|
+
<script type="module" src="/@vite/client"></script>
|
|
125
|
+
<link rel="stylesheet" href="/style.css">
|
|
126
|
+
<style>
|
|
127
|
+
body {
|
|
128
|
+
font-family: system-ui, sans-serif;
|
|
129
|
+
max-width: 800px;
|
|
130
|
+
margin: 2rem auto;
|
|
131
|
+
padding: 0 1rem;
|
|
132
|
+
line-height: 1.6;
|
|
133
|
+
}
|
|
134
|
+
h1 {
|
|
135
|
+
color: #333;
|
|
136
|
+
border-bottom: 2px solid #eee;
|
|
137
|
+
padding-bottom: 0.5rem;
|
|
138
|
+
}
|
|
139
|
+
ul {
|
|
140
|
+
list-style: none;
|
|
141
|
+
padding: 0;
|
|
142
|
+
}
|
|
143
|
+
li {
|
|
144
|
+
margin: 0.5rem 0;
|
|
145
|
+
}
|
|
146
|
+
a {
|
|
147
|
+
display: block;
|
|
148
|
+
padding: 0.75rem 1rem;
|
|
149
|
+
text-decoration: none;
|
|
150
|
+
color: #0066cc;
|
|
151
|
+
border: 1px solid #ddd;
|
|
152
|
+
border-radius: 4px;
|
|
153
|
+
transition: all 0.2s;
|
|
154
|
+
}
|
|
155
|
+
a:hover {
|
|
156
|
+
background: #f5f5f5;
|
|
157
|
+
border-color: #0066cc;
|
|
158
|
+
}
|
|
159
|
+
</style>
|
|
160
|
+
</head>
|
|
161
|
+
<body>
|
|
162
|
+
<h1>📄 Pages & Components Preview</h1>
|
|
163
|
+
<p>Available pages and components in your file system:</p>
|
|
164
|
+
${sectionsHtml}
|
|
165
|
+
</body>
|
|
166
|
+
</html>`;
|
|
167
|
+
}
|
|
168
|
+
function generateFullPage(pageName, pageContent) {
|
|
169
|
+
return `<!DOCTYPE html>
|
|
170
|
+
<html lang="en">
|
|
171
|
+
<head>
|
|
172
|
+
<meta charset="UTF-8">
|
|
173
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
174
|
+
<title>${pageName}</title>
|
|
175
|
+
<script type="module" src="/@vite/client"></script>
|
|
176
|
+
<link rel="icon" href="/favicon.ico">
|
|
177
|
+
<link rel="stylesheet" href="/src/styles/main.scss">
|
|
178
|
+
<script type="module" src="/src/js/index.ts"></script>
|
|
179
|
+
<script type="module" src="/svg-sprite-hmr.ts"></script>
|
|
180
|
+
|
|
181
|
+
</head>
|
|
182
|
+
<body>
|
|
183
|
+
|
|
184
|
+
${pageContent}
|
|
185
|
+
|
|
186
|
+
<!-- <a href="/" class="back-link">← Back to all pages</a> -->
|
|
187
|
+
</body>
|
|
188
|
+
</html>`;
|
|
189
|
+
}
|
|
190
|
+
export function pagesPreviewPlugin(options = {}) {
|
|
191
|
+
const { pagesDir = "src/pages", componentsDir = "src/components", routePrefix = "/pages", componentsRoutePrefix = "/components", } = options;
|
|
192
|
+
return {
|
|
193
|
+
name: "vite-pages-preview",
|
|
194
|
+
apply: "serve",
|
|
195
|
+
configureServer(server) {
|
|
196
|
+
// Watch the pages and components directories for changes and force browser reload
|
|
197
|
+
const pagesPath = path.resolve(pagesDir);
|
|
198
|
+
const componentsPath = path.resolve(componentsDir);
|
|
199
|
+
server.watcher.add(pagesPath);
|
|
200
|
+
server.watcher.add(componentsPath);
|
|
201
|
+
const reloadBrowser = () => {
|
|
202
|
+
// Force a hard refresh of the browser
|
|
203
|
+
server.ws.send({
|
|
204
|
+
type: "full-reload",
|
|
205
|
+
});
|
|
206
|
+
// Also send update message for good measure
|
|
207
|
+
server.ws.send({
|
|
208
|
+
type: "update",
|
|
209
|
+
updates: [],
|
|
210
|
+
});
|
|
211
|
+
};
|
|
212
|
+
server.watcher.on("change", (filePath) => {
|
|
213
|
+
const resolvedPath = path.resolve(filePath);
|
|
214
|
+
if (resolvedPath.startsWith(pagesPath) ||
|
|
215
|
+
resolvedPath.startsWith(componentsPath)) {
|
|
216
|
+
console.log(`[pages-preview] File changed: ${filePath}`);
|
|
217
|
+
reloadBrowser();
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
server.watcher.on("add", (filePath) => {
|
|
221
|
+
const resolvedPath = path.resolve(filePath);
|
|
222
|
+
if ((resolvedPath.startsWith(pagesPath) ||
|
|
223
|
+
resolvedPath.startsWith(componentsPath)) &&
|
|
224
|
+
filePath.endsWith(".html")) {
|
|
225
|
+
console.log(`[pages-preview] File added: ${filePath}`);
|
|
226
|
+
reloadBrowser();
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
server.watcher.on("unlink", (filePath) => {
|
|
230
|
+
const resolvedPath = path.resolve(filePath);
|
|
231
|
+
if ((resolvedPath.startsWith(pagesPath) ||
|
|
232
|
+
resolvedPath.startsWith(componentsPath)) &&
|
|
233
|
+
filePath.endsWith(".html")) {
|
|
234
|
+
console.log(`[pages-preview] File deleted: ${filePath}`);
|
|
235
|
+
reloadBrowser();
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
server.middlewares.use(async (req, res, next) => {
|
|
239
|
+
const url = req.url;
|
|
240
|
+
// Handle preview index route
|
|
241
|
+
if (url === `/`) {
|
|
242
|
+
const pages = await scanPagesDirectory(pagesDir);
|
|
243
|
+
const components = await scanPagesDirectory(componentsDir);
|
|
244
|
+
const indexHtml = generatePagesIndex(pages, components, routePrefix, componentsRoutePrefix);
|
|
245
|
+
res.setHeader("Content-Type", "text/html");
|
|
246
|
+
res.end(indexHtml);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
// Handle individual page routes
|
|
250
|
+
if (url?.startsWith(`${routePrefix}/`) && url !== `${routePrefix}/`) {
|
|
251
|
+
const pageName = url
|
|
252
|
+
.replace(`${routePrefix}/`, "")
|
|
253
|
+
.replace(/\/$/, "");
|
|
254
|
+
const pageFile = path.join(pagesDir, `${pageName}.html`);
|
|
255
|
+
try {
|
|
256
|
+
const rawPageContent = await fs.readFile(pageFile, "utf-8");
|
|
257
|
+
// Process HTML imports before generating full page
|
|
258
|
+
const processedPageContent = await processHtmlImports(rawPageContent, path.dirname(pageFile));
|
|
259
|
+
const fullPageHtml = generateFullPage(pageName, processedPageContent);
|
|
260
|
+
res.setHeader("Content-Type", "text/html");
|
|
261
|
+
res.end(fullPageHtml);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
// Page not found, continue to next middleware
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
// Handle individual component routes
|
|
269
|
+
if (url?.startsWith(`${componentsRoutePrefix}/`) &&
|
|
270
|
+
url !== `${componentsRoutePrefix}/`) {
|
|
271
|
+
const componentName = url
|
|
272
|
+
.replace(`${componentsRoutePrefix}/`, "")
|
|
273
|
+
.replace(/\/$/, "");
|
|
274
|
+
const componentFile = path.join(componentsDir, `${componentName}.html`);
|
|
275
|
+
try {
|
|
276
|
+
const rawComponentContent = await fs.readFile(componentFile, "utf-8");
|
|
277
|
+
// Process HTML imports before generating full page
|
|
278
|
+
const processedComponentContent = await processHtmlImports(rawComponentContent, path.dirname(componentFile));
|
|
279
|
+
const fullPageHtml = generateFullPage(`component: ${componentName}`, processedComponentContent);
|
|
280
|
+
res.setHeader("Content-Type", "text/html");
|
|
281
|
+
res.end(fullPageHtml);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
// Component not found, continue to next middleware
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
next();
|
|
289
|
+
});
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
export interface SvgSpriteOptions {
|
|
3
|
+
iconsDir?: string;
|
|
4
|
+
outputPath?: string;
|
|
5
|
+
publicPath?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function svgSpritePlugin(options?: SvgSpriteOptions): Plugin[];
|
|
8
|
+
//# sourceMappingURL=svg-sprite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"svg-sprite.d.ts","sourceRoot":"","sources":["../../src/plugins/svg-sprite.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAInC,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AA4DD,wBAAgB,eAAe,CAAC,OAAO,GAAE,gBAAqB,GAAG,MAAM,EAAE,CA0DxE"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { optimize } from "svgo";
|
|
4
|
+
import { scanDirectory } from "../utils/file-scanner.js";
|
|
5
|
+
async function generateSvgSprite(iconsDir, outputPath) {
|
|
6
|
+
const svgFiles = await scanDirectory(iconsDir, [".svg"]);
|
|
7
|
+
if (svgFiles.length === 0) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const symbols = [];
|
|
11
|
+
for (const file of svgFiles) {
|
|
12
|
+
try {
|
|
13
|
+
const filePath = path.join(iconsDir, file);
|
|
14
|
+
const svgContent = await fs.readFile(filePath, "utf-8");
|
|
15
|
+
// Optimize with SVGO
|
|
16
|
+
const result = optimize(svgContent, {
|
|
17
|
+
plugins: [
|
|
18
|
+
{
|
|
19
|
+
name: "removeAttrs",
|
|
20
|
+
params: { attrs: "data-name" },
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
});
|
|
24
|
+
// Extract the inner content of the SVG and create a symbol
|
|
25
|
+
const optimizedSvg = result.data;
|
|
26
|
+
const svgMatch = optimizedSvg.match(/<svg[^>]*>([\s\S]*?)<\/svg>/);
|
|
27
|
+
if (svgMatch) {
|
|
28
|
+
const iconName = path.basename(file, ".svg");
|
|
29
|
+
const innerContent = svgMatch[1];
|
|
30
|
+
// Extract viewBox from the original SVG
|
|
31
|
+
const viewBoxMatch = optimizedSvg.match(/viewBox="([^"]*)"/);
|
|
32
|
+
const viewBox = viewBoxMatch ? ` viewBox="${viewBoxMatch[1]}"` : "";
|
|
33
|
+
const symbol = `<symbol id="${iconName}"${viewBox}>${innerContent}</symbol>`;
|
|
34
|
+
symbols.push(symbol);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
console.warn(`Failed to process ${file}:`, error);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (symbols.length > 0) {
|
|
42
|
+
const sprite = `<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
|
|
43
|
+
${symbols.join("\n")}
|
|
44
|
+
</svg>`;
|
|
45
|
+
// Ensure the output directory exists
|
|
46
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
47
|
+
await fs.writeFile(outputPath, sprite);
|
|
48
|
+
console.log(`📦 Generated sprite with ${symbols.length} icons at ${outputPath}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function svgSpritePlugin(options = {}) {
|
|
52
|
+
const { iconsDir = "src/icons", outputPath = "public/images/sprite.svg", publicPath = "public/" } = options;
|
|
53
|
+
return [
|
|
54
|
+
// Development plugin
|
|
55
|
+
{
|
|
56
|
+
name: "svg-sprite-dev",
|
|
57
|
+
apply: "serve",
|
|
58
|
+
configureServer(server) {
|
|
59
|
+
// Generate sprite on server start
|
|
60
|
+
generateSvgSprite(iconsDir, outputPath);
|
|
61
|
+
// Watch for changes in icons directory
|
|
62
|
+
server.watcher.add(`${iconsDir}/**/*.svg`);
|
|
63
|
+
const handleSpriteChange = async (file) => {
|
|
64
|
+
console.log("📁 File change detected:", file);
|
|
65
|
+
if (file.includes(iconsDir) && file.endsWith(".svg")) {
|
|
66
|
+
console.log("🎨 SVG file changed, regenerating sprite...");
|
|
67
|
+
await generateSvgSprite(iconsDir, outputPath);
|
|
68
|
+
const timestamp = Date.now();
|
|
69
|
+
console.log("📡 Sending HMR event with timestamp:", timestamp);
|
|
70
|
+
// Send a custom HMR update to invalidate SVG references
|
|
71
|
+
server.ws.send({
|
|
72
|
+
type: "custom",
|
|
73
|
+
event: "svg-sprite-updated",
|
|
74
|
+
data: { timestamp },
|
|
75
|
+
});
|
|
76
|
+
console.log("✅ SVG sprite updated and HMR event sent");
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
console.log("⏭️ Not an SVG file, skipping");
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
server.watcher.on("change", handleSpriteChange);
|
|
83
|
+
server.watcher.on("add", handleSpriteChange);
|
|
84
|
+
server.watcher.on("unlink", handleSpriteChange);
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
// Build plugin
|
|
88
|
+
{
|
|
89
|
+
name: "svg-sprite-build",
|
|
90
|
+
apply: "build",
|
|
91
|
+
buildStart() {
|
|
92
|
+
// Generate sprite at build start
|
|
93
|
+
const buildOutputPath = outputPath.replace("public/", `dist/${publicPath}`);
|
|
94
|
+
return generateSvgSprite(iconsDir, buildOutputPath);
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
];
|
|
98
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface StaticKitConfig {
|
|
2
|
+
build?: {
|
|
3
|
+
base?: string;
|
|
4
|
+
output?: string;
|
|
5
|
+
};
|
|
6
|
+
templates?: {
|
|
7
|
+
language?: string;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export interface StaticKitOptions {
|
|
11
|
+
config?: StaticKitConfig;
|
|
12
|
+
pagesDir?: string;
|
|
13
|
+
componentsDir?: string;
|
|
14
|
+
iconsDir?: string;
|
|
15
|
+
stylesEntry?: string;
|
|
16
|
+
jsEntry?: string;
|
|
17
|
+
publicDir?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface PagesPreviewOptions {
|
|
20
|
+
pagesDir?: string;
|
|
21
|
+
componentsDir?: string;
|
|
22
|
+
routePrefix?: string;
|
|
23
|
+
componentsRoutePrefix?: string;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE;QACN,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,SAAS,CAAC,EAAE;QACV,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { StaticKitConfig } from "../types.js";
|
|
2
|
+
export declare function loadStaticKitConfig(root?: string): Promise<StaticKitConfig>;
|
|
3
|
+
export declare function normalizeBase(input?: string): string;
|
|
4
|
+
export declare function timeStamp(): string;
|
|
5
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,wBAAsB,mBAAmB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAqBjF;AAED,wBAAgB,aAAa,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAKpD;AAED,wBAAgB,SAAS,IAAI,MAAM,CAElC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
export async function loadStaticKitConfig(root) {
|
|
4
|
+
const projectRoot = root || process.cwd();
|
|
5
|
+
const baseConfigPath = path.join(projectRoot, "static-kit.config.json");
|
|
6
|
+
const localConfigPath = path.join(projectRoot, "static-kit.local.json");
|
|
7
|
+
const result = {};
|
|
8
|
+
try {
|
|
9
|
+
const raw = await fs.readFile(baseConfigPath, "utf8");
|
|
10
|
+
Object.assign(result, JSON.parse(raw));
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
// Config file doesn't exist or can't be read
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const rawLocal = await fs.readFile(localConfigPath, "utf8");
|
|
17
|
+
Object.assign(result, JSON.parse(rawLocal));
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// Local config file doesn't exist or can't be read
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
export function normalizeBase(input) {
|
|
25
|
+
if (!input)
|
|
26
|
+
return "public/";
|
|
27
|
+
let base = input.trim();
|
|
28
|
+
if (!base.endsWith("/"))
|
|
29
|
+
base = base + "/";
|
|
30
|
+
return base;
|
|
31
|
+
}
|
|
32
|
+
export function timeStamp() {
|
|
33
|
+
return Date.now().toString().slice(0, 10);
|
|
34
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scan directory for files with specified extensions using fast-glob
|
|
3
|
+
*/
|
|
4
|
+
export declare function scanDirectory(dir: string, extensions: string[]): Promise<string[]>;
|
|
5
|
+
/**
|
|
6
|
+
* Get input entries for Vite build
|
|
7
|
+
*/
|
|
8
|
+
export declare function getInputEntries(pagesDir?: string, jsDir?: string, stylesEntry?: string): Promise<Record<string, string>>;
|
|
9
|
+
//# sourceMappingURL=file-scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-scanner.d.ts","sourceRoot":"","sources":["../../src/utils/file-scanner.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAAE,GACnB,OAAO,CAAC,MAAM,EAAE,CAAC,CAanB;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,QAAQ,GAAE,MAAoB,EAC9B,KAAK,GAAE,MAAiB,EACxB,WAAW,GAAE,MAA+B,mCAsB7C"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import fg from "fast-glob";
|
|
2
|
+
/**
|
|
3
|
+
* Scan directory for files with specified extensions using fast-glob
|
|
4
|
+
*/
|
|
5
|
+
export async function scanDirectory(dir, extensions) {
|
|
6
|
+
try {
|
|
7
|
+
const patterns = extensions.map((ext) => `**/*${ext}`);
|
|
8
|
+
const files = await fg(patterns, {
|
|
9
|
+
cwd: dir,
|
|
10
|
+
onlyFiles: true,
|
|
11
|
+
ignore: ["node_modules/**"],
|
|
12
|
+
});
|
|
13
|
+
return files;
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
// Directory doesn't exist or can't be read
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Get input entries for Vite build
|
|
22
|
+
*/
|
|
23
|
+
export async function getInputEntries(pagesDir = "src/pages", jsDir = "src/js", stylesEntry = "src/styles/main.scss") {
|
|
24
|
+
const entries = {};
|
|
25
|
+
// Always include main SCSS
|
|
26
|
+
entries.main = stylesEntry;
|
|
27
|
+
// Dynamically scan for JS/TS files (including nested)
|
|
28
|
+
const jsFiles = await scanDirectory(jsDir, [".ts", ".js"]);
|
|
29
|
+
for (const file of jsFiles) {
|
|
30
|
+
const name = file.replace(/\.(ts|js)$/, "");
|
|
31
|
+
entries[`js/${name}`] = `${jsDir}/${file}`;
|
|
32
|
+
}
|
|
33
|
+
// Dynamically scan for HTML pages (including nested)
|
|
34
|
+
const pageFiles = await scanDirectory(pagesDir, [".html"]);
|
|
35
|
+
for (const file of pageFiles) {
|
|
36
|
+
const name = file.replace(".html", "");
|
|
37
|
+
entries[name] = `${pagesDir}/${file}`;
|
|
38
|
+
}
|
|
39
|
+
return entries;
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-imports.d.ts","sourceRoot":"","sources":["../../src/utils/html-imports.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,CAoEjB"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
/**
|
|
4
|
+
* Process HTML imports in the given HTML string
|
|
5
|
+
* Supports both relative paths and @components/ prefix
|
|
6
|
+
*/
|
|
7
|
+
export async function processHtmlImports(html, dir) {
|
|
8
|
+
const importRegex = /<!--\s*@import:\s*([\w./@-]+)\s*-->/g;
|
|
9
|
+
// Collect all matches first to avoid issues with changing string length
|
|
10
|
+
const matches = Array.from(html.matchAll(importRegex));
|
|
11
|
+
// Process matches in reverse order to maintain correct indices
|
|
12
|
+
for (let i = matches.length - 1; i >= 0; i--) {
|
|
13
|
+
const match = matches[i];
|
|
14
|
+
let importPath = match[1];
|
|
15
|
+
// Handle @components/ prefix for cleaner component imports
|
|
16
|
+
if (importPath.startsWith("@components/")) {
|
|
17
|
+
// Find the project root (go up from current dir until we find src/)
|
|
18
|
+
let currentDir = dir;
|
|
19
|
+
while (!currentDir.endsWith("src") &&
|
|
20
|
+
currentDir !== path.dirname(currentDir)) {
|
|
21
|
+
currentDir = path.dirname(currentDir);
|
|
22
|
+
}
|
|
23
|
+
if (currentDir.endsWith("src") || currentDir.includes("src")) {
|
|
24
|
+
const srcDir = currentDir.endsWith("src")
|
|
25
|
+
? currentDir
|
|
26
|
+
: path.join(currentDir, "src");
|
|
27
|
+
importPath = importPath.replace("@components/", "components/");
|
|
28
|
+
const filePath = path.resolve(srcDir, importPath);
|
|
29
|
+
try {
|
|
30
|
+
const fileContent = await fs.readFile(filePath, "utf8");
|
|
31
|
+
html =
|
|
32
|
+
html.slice(0, match.index) +
|
|
33
|
+
fileContent +
|
|
34
|
+
html.slice(match.index + match[0].length);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
const errorMessage = `<!-- Import Error: Could not find component "${match[1]}"
|
|
39
|
+
Looked for: ${filePath}
|
|
40
|
+
Check the path and make sure the file exists -->`;
|
|
41
|
+
html =
|
|
42
|
+
html.slice(0, match.index) +
|
|
43
|
+
errorMessage +
|
|
44
|
+
html.slice(match.index + match[0].length);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Standard relative path resolution
|
|
50
|
+
const filePath = path.resolve(dir, importPath);
|
|
51
|
+
try {
|
|
52
|
+
const fileContent = await fs.readFile(filePath, "utf8");
|
|
53
|
+
html =
|
|
54
|
+
html.slice(0, match.index) +
|
|
55
|
+
fileContent +
|
|
56
|
+
html.slice(match.index + match[0].length);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
const errorMessage = `<!-- Import Error: Could not find file "${match[1]}"
|
|
60
|
+
Looked for: ${filePath}
|
|
61
|
+
Check the path and make sure the file exists -->`;
|
|
62
|
+
html =
|
|
63
|
+
html.slice(0, match.index) +
|
|
64
|
+
errorMessage +
|
|
65
|
+
html.slice(match.index + match[0].length);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return html;
|
|
69
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { UserConfigFnPromise } from "vite";
|
|
2
|
+
import type { StaticKitOptions } from "./types.js";
|
|
3
|
+
export declare function createStaticKitConfig(options?: StaticKitOptions): Promise<UserConfigFnPromise>;
|
|
4
|
+
export declare function staticKitConfig(options?: StaticKitOptions): Promise<UserConfigFnPromise>;
|
|
5
|
+
//# sourceMappingURL=vite-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite-config.d.ts","sourceRoot":"","sources":["../src/vite-config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAc,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAI5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,wBAAsB,qBAAqB,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA8ExG;AAGD,wBAAgB,eAAe,CAAC,OAAO,GAAE,gBAAqB,gCAE7D"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { defineConfig } from "vite";
|
|
2
|
+
import { loadStaticKitConfig, normalizeBase } from "./utils/config.js";
|
|
3
|
+
import { getInputEntries } from "./utils/file-scanner.js";
|
|
4
|
+
import { svgSpritePlugin, pagesPreviewPlugin, buildPlugins } from "./plugins/index.js";
|
|
5
|
+
export async function createStaticKitConfig(options = {}) {
|
|
6
|
+
const { config: userProvidedConfig, pagesDir = "src/pages", componentsDir = "src/components", iconsDir = "src/icons", stylesEntry = "src/styles/main.scss", jsEntry = "src/js", publicDir = "public" } = options;
|
|
7
|
+
// Load config from file system if not provided
|
|
8
|
+
const config = userProvidedConfig || await loadStaticKitConfig();
|
|
9
|
+
const normalizedBase = normalizeBase(config.build?.base);
|
|
10
|
+
return defineConfig(async ({ command }) => {
|
|
11
|
+
if (command === "serve") {
|
|
12
|
+
return {
|
|
13
|
+
plugins: [
|
|
14
|
+
pagesPreviewPlugin({
|
|
15
|
+
pagesDir,
|
|
16
|
+
componentsDir,
|
|
17
|
+
}),
|
|
18
|
+
...svgSpritePlugin({
|
|
19
|
+
iconsDir,
|
|
20
|
+
outputPath: `${publicDir}/images/sprite.svg`,
|
|
21
|
+
publicPath: normalizedBase
|
|
22
|
+
}),
|
|
23
|
+
],
|
|
24
|
+
publicDir, // Static assets during dev
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
// Build configuration with dynamic inputs
|
|
28
|
+
const inputEntries = await getInputEntries(pagesDir, jsEntry, stylesEntry);
|
|
29
|
+
console.log("📦 Building with entries:", Object.keys(inputEntries));
|
|
30
|
+
return {
|
|
31
|
+
build: {
|
|
32
|
+
outDir: config.build?.output || "dist",
|
|
33
|
+
emptyOutDir: true,
|
|
34
|
+
cssCodeSplit: false, // Single CSS file
|
|
35
|
+
rollupOptions: {
|
|
36
|
+
input: inputEntries,
|
|
37
|
+
output: {
|
|
38
|
+
entryFileNames: (chunkInfo) => {
|
|
39
|
+
if (chunkInfo.name?.startsWith("js/")) {
|
|
40
|
+
return `${normalizedBase}[name].js`;
|
|
41
|
+
}
|
|
42
|
+
return `${normalizedBase}[name].js`;
|
|
43
|
+
},
|
|
44
|
+
assetFileNames: (assetInfo) => {
|
|
45
|
+
if (assetInfo.name?.endsWith(".css")) {
|
|
46
|
+
return `${normalizedBase}css/styles.css`;
|
|
47
|
+
}
|
|
48
|
+
if (assetInfo.name === "sprite.svg") {
|
|
49
|
+
// Ensure sprite lands under public if emitted by Rollup
|
|
50
|
+
return `${normalizedBase}images/sprite.svg`;
|
|
51
|
+
}
|
|
52
|
+
return `${normalizedBase}assets/[name]-[hash][extname]`;
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
publicDir: "", // Disable default public dir copying
|
|
58
|
+
plugins: [
|
|
59
|
+
...svgSpritePlugin({
|
|
60
|
+
iconsDir,
|
|
61
|
+
outputPath: `dist/${normalizedBase}images/sprite.svg`,
|
|
62
|
+
publicPath: normalizedBase
|
|
63
|
+
}),
|
|
64
|
+
...buildPlugins({
|
|
65
|
+
config,
|
|
66
|
+
publicDir
|
|
67
|
+
}),
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
// Convenience function that matches the current API
|
|
73
|
+
export function staticKitConfig(options = {}) {
|
|
74
|
+
return createStaticKitConfig(options);
|
|
75
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vojtaholik/static-kit-core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Core library for Static Kit - simple static site framework",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./vite": {
|
|
14
|
+
"types": "./dist/vite-config.d.ts",
|
|
15
|
+
"import": "./dist/vite-config.js"
|
|
16
|
+
},
|
|
17
|
+
"./plugins": {
|
|
18
|
+
"types": "./dist/plugins/index.d.ts",
|
|
19
|
+
"import": "./dist/plugins/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist/",
|
|
24
|
+
"templates/"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsc",
|
|
28
|
+
"dev": "tsc --watch",
|
|
29
|
+
"clean": "rm -rf dist"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"vite": "^7.0.0"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"fast-glob": "^3.3.3",
|
|
36
|
+
"svgo": "^4.0.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^24.2.1",
|
|
40
|
+
"typescript": "~5.9.2",
|
|
41
|
+
"vite": "^7.1.1"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"static-site",
|
|
45
|
+
"vite",
|
|
46
|
+
"html",
|
|
47
|
+
"scss",
|
|
48
|
+
"typescript",
|
|
49
|
+
"components"
|
|
50
|
+
],
|
|
51
|
+
"author": "Vojta Holik <vojta@hey.com>",
|
|
52
|
+
"license": "MIT",
|
|
53
|
+
"repository": {
|
|
54
|
+
"type": "git",
|
|
55
|
+
"url": "https://github.com/vojtaholik/static-kit.git",
|
|
56
|
+
"directory": "packages/static-kit-core"
|
|
57
|
+
},
|
|
58
|
+
"homepage": "https://github.com/vojtaholik/static-kit#readme",
|
|
59
|
+
"bugs": {
|
|
60
|
+
"url": "https://github.com/vojtaholik/static-kit/issues"
|
|
61
|
+
}
|
|
62
|
+
}
|