mirror of
https://github.com/10h30/astroplate.git
synced 2026-06-05 15:08:00 +09:00
remove custom tailwind plugins and added a script to generate theme.css
This commit is contained in:
+7
-5
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "astroplate",
|
||||
"version": "5.9.0",
|
||||
"version": "5.10.0",
|
||||
"description": "Astro and Tailwindcss boilerplate",
|
||||
"author": "zeon.studio",
|
||||
"license": "MIT",
|
||||
"packageManager": "yarn@1.22.22",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "yarn generate-json && astro dev",
|
||||
"build": "yarn generate-json && astro build",
|
||||
"dev": "concurrently \"node scripts/themeGenerator.js --watch\" \"yarn generate-json && astro dev\"",
|
||||
"build": "node scripts/themeGenerator.js && yarn generate-json && astro build",
|
||||
"preview": "astro preview",
|
||||
"check": "astro check",
|
||||
"format": "prettier -w ./src",
|
||||
@@ -37,7 +37,8 @@
|
||||
"remark-collapse": "^0.1.2",
|
||||
"remark-toc": "^9.0.0",
|
||||
"swiper": "^12.0.3",
|
||||
"vite": "^7.2.2"
|
||||
"vite": "^7.2.2",
|
||||
"sharp": "^0.34.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
@@ -46,11 +47,12 @@
|
||||
"@types/node": "24.10.1",
|
||||
"@types/react": "19.2.6",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"concurrently": "^9.2.1",
|
||||
"eslint": "^9.39.1",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-astro": "^0.14.1",
|
||||
"prettier-plugin-tailwindcss": "^0.7.1",
|
||||
"sharp": "0.34.5",
|
||||
"tailwind-bootstrap-grid": "^6.0.0",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
/**
|
||||
* Determine the paths based on setup mode (theme vs project)
|
||||
* @returns {Object} Configuration object with paths and mode info
|
||||
*/
|
||||
function determinePaths() {
|
||||
const themePath = path.join(__dirname, "../src/config/theme.json");
|
||||
const outputPath = path.join(__dirname, "../src/styles/generated-theme.css");
|
||||
|
||||
if (!fs.existsSync(themePath)) {
|
||||
throw new Error(`Could not find theme.json at: ${themePath}`);
|
||||
}
|
||||
|
||||
return { themePath, outputPath };
|
||||
}
|
||||
|
||||
const { themePath, outputPath } = determinePaths();
|
||||
|
||||
// Helper to convert color name from snake_case to kebab-case
|
||||
const toKebab = (str) => str.replace(/_/g, "-");
|
||||
|
||||
// Helper to extract a clean font name
|
||||
const findFont = (fontStr) =>
|
||||
fontStr.replace(/\+/g, " ").replace(/:[^:]+/g, "");
|
||||
|
||||
/**
|
||||
* Add color entries to CSS array
|
||||
* @param {Array} cssLines - Array of CSS lines to append to
|
||||
* @param {Object} colors - Color object to process
|
||||
* @param {string} prefix - Optional prefix for color variable names
|
||||
*/
|
||||
function addColorsToCss(cssLines, colors, prefix = "") {
|
||||
Object.entries(colors).forEach(([key, value]) => {
|
||||
const colorName = prefix
|
||||
? `--color-${prefix}-${toKebab(key)}`
|
||||
: `--color-${toKebab(key)}`;
|
||||
cssLines.push(` ${colorName}: ${value};`);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate theme CSS from theme.json configuration
|
||||
* @throws {Error} If theme.json is missing or invalid
|
||||
*/
|
||||
function generateThemeCSS() {
|
||||
// Validate that theme.json exists
|
||||
if (!fs.existsSync(themePath)) {
|
||||
throw new Error(`Theme configuration not found: ${themePath}`);
|
||||
}
|
||||
|
||||
try {
|
||||
// Read and parse theme configuration
|
||||
const themeConfig = JSON.parse(fs.readFileSync(themePath, "utf8"));
|
||||
|
||||
// Validate required theme structure
|
||||
if (!themeConfig.colors || !themeConfig.fonts) {
|
||||
throw new Error(
|
||||
"Invalid theme.json: missing 'colors' or 'fonts' section",
|
||||
);
|
||||
}
|
||||
|
||||
// Build CSS using array for better performance
|
||||
const cssLines = [
|
||||
"/**",
|
||||
' * Auto-generated from "src/config/theme.json"',
|
||||
" * DO NOT EDIT THIS FILE MANUALLY",
|
||||
" * Run: node scripts/themeGenerator.js",
|
||||
" */",
|
||||
"",
|
||||
"@theme {",
|
||||
" /* === Colors === */",
|
||||
];
|
||||
|
||||
// Add default theme colors
|
||||
if (themeConfig.colors.default?.theme_color) {
|
||||
addColorsToCss(cssLines, themeConfig.colors.default.theme_color);
|
||||
}
|
||||
|
||||
// Add default text colors
|
||||
if (themeConfig.colors.default?.text_color) {
|
||||
addColorsToCss(cssLines, themeConfig.colors.default.text_color);
|
||||
}
|
||||
|
||||
// Add darkmode colors (if available)
|
||||
if (themeConfig.colors.darkmode) {
|
||||
cssLines.push("", " /* === Darkmode Colors === */");
|
||||
|
||||
if (themeConfig.colors.darkmode.theme_color) {
|
||||
addColorsToCss(
|
||||
cssLines,
|
||||
themeConfig.colors.darkmode.theme_color,
|
||||
"darkmode",
|
||||
);
|
||||
}
|
||||
|
||||
if (themeConfig.colors.darkmode.text_color) {
|
||||
addColorsToCss(
|
||||
cssLines,
|
||||
themeConfig.colors.darkmode.text_color,
|
||||
"darkmode",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Add font families
|
||||
cssLines.push("", " /* === Font Families === */");
|
||||
const fontFamily = themeConfig.fonts.font_family || {};
|
||||
Object.entries(fontFamily)
|
||||
.filter(([key]) => !key.includes("type"))
|
||||
.forEach(([key, font]) => {
|
||||
const fontFallback = fontFamily[`${key}_type`] || "sans-serif";
|
||||
const fontValue = `${findFont(font)}, ${fontFallback}`;
|
||||
cssLines.push(` --font-${toKebab(key)}: ${fontValue};`);
|
||||
});
|
||||
|
||||
// Add font sizes
|
||||
cssLines.push("", " /* === Font Sizes === */");
|
||||
const baseSize = Number(themeConfig.fonts.font_size?.base || 16);
|
||||
const scale = Number(themeConfig.fonts.font_size?.scale || 1.25);
|
||||
|
||||
cssLines.push(` --text-base: ${baseSize}px;`);
|
||||
cssLines.push(` --text-base-sm: ${baseSize * 0.8}px;`);
|
||||
|
||||
let currentSize = scale;
|
||||
for (let i = 6; i >= 1; i--) {
|
||||
cssLines.push(` --text-h${i}: ${currentSize}rem;`);
|
||||
cssLines.push(` --text-h${i}-sm: ${currentSize * 0.9}rem;`);
|
||||
currentSize *= scale;
|
||||
}
|
||||
|
||||
cssLines.push("}");
|
||||
|
||||
// Ensure output directory exists
|
||||
const outputDir = path.dirname(outputPath);
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Write the file
|
||||
fs.writeFileSync(outputPath, cssLines.join("\n") + "\n");
|
||||
console.log("✅ Theme CSS generated successfully at:", outputPath);
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to generate theme CSS: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate CSS on startup
|
||||
try {
|
||||
generateThemeCSS();
|
||||
} catch (error) {
|
||||
console.error("❌ Error:", error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check for --watch flag
|
||||
if (process.argv.includes("--watch")) {
|
||||
let debounceTimer;
|
||||
|
||||
const watcher = fs.watch(themePath, (eventType) => {
|
||||
if (eventType === "change") {
|
||||
// Debounce to avoid multiple triggers
|
||||
clearTimeout(debounceTimer);
|
||||
debounceTimer = setTimeout(() => {
|
||||
try {
|
||||
generateThemeCSS();
|
||||
} catch (error) {
|
||||
console.error("❌ Error regenerating theme CSS:", error.message);
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle graceful shutdown
|
||||
process.on("SIGINT", () => {
|
||||
clearTimeout(debounceTimer);
|
||||
watcher.close();
|
||||
console.log("\n👋 Watcher stopped");
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
console.log("👁️ Watching for changes to:", themePath);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Auto-generated from "src/config/theme.json"
|
||||
* DO NOT EDIT THIS FILE MANUALLY
|
||||
* Run: node scripts/themeGenerator.js
|
||||
*/
|
||||
|
||||
@theme {
|
||||
/* === Colors === */
|
||||
--color-primary: #121212;
|
||||
--color-body: #fff;
|
||||
--color-border: #eaeaea;
|
||||
--color-light: #f6f6f6;
|
||||
--color-dark: #040404;
|
||||
--color-text: #444444;
|
||||
--color-text-dark: #040404;
|
||||
--color-text-light: #717171;
|
||||
|
||||
/* === Darkmode Colors === */
|
||||
--color-darkmode-primary: #fff;
|
||||
--color-darkmode-body: #1c1c1c;
|
||||
--color-darkmode-border: #3E3E3E;
|
||||
--color-darkmode-light: #222222;
|
||||
--color-darkmode-dark: #fff;
|
||||
--color-darkmode-text: #B4AFB6;
|
||||
--color-darkmode-text-dark: #fff;
|
||||
--color-darkmode-text-light: #B4AFB6;
|
||||
|
||||
/* === Font Families === */
|
||||
--font-primary: Heebo, sans-serif;
|
||||
--font-secondary: Signika, sans-serif;
|
||||
|
||||
/* === Font Sizes === */
|
||||
--text-base: 16px;
|
||||
--text-base-sm: 12.8px;
|
||||
--text-h6: 1.2rem;
|
||||
--text-h6-sm: 1.08rem;
|
||||
--text-h5: 1.44rem;
|
||||
--text-h5-sm: 1.296rem;
|
||||
--text-h4: 1.728rem;
|
||||
--text-h4-sm: 1.5552rem;
|
||||
--text-h3: 2.0736rem;
|
||||
--text-h3-sm: 1.86624rem;
|
||||
--text-h2: 2.48832rem;
|
||||
--text-h2-sm: 2.239488rem;
|
||||
--text-h1: 2.9859839999999997rem;
|
||||
--text-h1-sm: 2.6873856rem;
|
||||
}
|
||||
+4
-3
@@ -1,11 +1,12 @@
|
||||
@import "tailwindcss";
|
||||
@plugin "../tailwind-plugin/tw-theme";
|
||||
@plugin "../tailwind-plugin/tw-bs-grid";
|
||||
@plugin "@tailwindcss/forms";
|
||||
@plugin "@tailwindcss/typography";
|
||||
|
||||
@plugin 'tailwind-bootstrap-grid';
|
||||
@custom-variant dark (&:where(.dark, .dark *));
|
||||
|
||||
/* Auto-generated theme from "theme.json"*/
|
||||
@import "./generated-theme.css";
|
||||
|
||||
@import "./safe.css";
|
||||
@import "./utilities.css";
|
||||
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
const plugin = require("tailwindcss/plugin");
|
||||
|
||||
module.exports = plugin.withOptions(() => {
|
||||
return ({ addComponents }) => {
|
||||
const gridColumns = 12;
|
||||
const gridGutterWidth = "1.5rem";
|
||||
const gridGutters = {
|
||||
0: "0",
|
||||
1: "0.25rem",
|
||||
2: "0.5rem",
|
||||
3: "1rem",
|
||||
4: "1.5rem",
|
||||
5: "3rem",
|
||||
};
|
||||
const respectImportant = true;
|
||||
const columns = Array.from({ length: gridColumns }, (_, i) => i + 1);
|
||||
const rowColsSteps = columns.slice(0, Math.floor(gridColumns / 2));
|
||||
|
||||
// Row
|
||||
addComponents(
|
||||
{
|
||||
".row": {
|
||||
"--bs-gutter-x": gridGutterWidth,
|
||||
"--bs-gutter-y": "0",
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
marginTop: "calc(var(--bs-gutter-y) * -1)",
|
||||
marginRight: "calc(var(--bs-gutter-x) / -2)",
|
||||
marginLeft: "calc(var(--bs-gutter-x) / -2)",
|
||||
"> *": {
|
||||
boxSizing: "border-box",
|
||||
flexShrink: "0",
|
||||
width: "100%",
|
||||
maxWidth: "100%",
|
||||
paddingRight: "calc(var(--bs-gutter-x) / 2)",
|
||||
paddingLeft: "calc(var(--bs-gutter-x) / 2)",
|
||||
marginTop: "var(--bs-gutter-y)",
|
||||
},
|
||||
},
|
||||
},
|
||||
{ respectImportant },
|
||||
);
|
||||
|
||||
// Columns + helper row-cols
|
||||
addComponents([
|
||||
{
|
||||
".col": {
|
||||
flex: "1 0 0%",
|
||||
width: "initial",
|
||||
display: "initial",
|
||||
},
|
||||
".row-cols-auto": {
|
||||
"> *": {
|
||||
flex: "0 0 auto",
|
||||
width: "auto",
|
||||
},
|
||||
},
|
||||
},
|
||||
...rowColsSteps.map((rowCol) => ({
|
||||
[`.row-cols-${rowCol}`]: {
|
||||
"> *": {
|
||||
flex: "0 0 auto",
|
||||
width: `${100 / rowCol}%`,
|
||||
display: "initial",
|
||||
},
|
||||
},
|
||||
})),
|
||||
{
|
||||
".col-auto": {
|
||||
flex: "0 0 auto",
|
||||
width: "auto",
|
||||
},
|
||||
},
|
||||
// explicit sized columns
|
||||
...columns.map((size) => ({
|
||||
[`.col-${size}`]: {
|
||||
flex: "0 0 auto",
|
||||
width: `${(100 / gridColumns) * size}%`,
|
||||
},
|
||||
})),
|
||||
]);
|
||||
|
||||
// Offsets
|
||||
addComponents(
|
||||
[0, ...columns.slice(0, -1)].map((num) => ({
|
||||
[`.offset-${num}`]: { marginLeft: `${(100 / gridColumns) * num}%` },
|
||||
})),
|
||||
{ respectImportant },
|
||||
);
|
||||
|
||||
// Gutters
|
||||
if (Object.keys(gridGutters).length) {
|
||||
const gutterComponents = Object.entries(gridGutters).reduce(
|
||||
(acc, [key, value]) => {
|
||||
acc[`.g-${key}`] = {
|
||||
"--bs-gutter-x": value,
|
||||
"--bs-gutter-y": value,
|
||||
};
|
||||
acc[`.gx-${key}`] = { "--bs-gutter-x": value };
|
||||
acc[`.gy-${key}`] = { "--bs-gutter-y": value };
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
);
|
||||
addComponents(gutterComponents, { respectImportant });
|
||||
}
|
||||
|
||||
// Ordering helpers
|
||||
addComponents(
|
||||
[
|
||||
{
|
||||
".order-first": { order: "-1" },
|
||||
".order-last": { order: String(gridColumns + 1) },
|
||||
},
|
||||
...[0, ...columns].map((num) => ({
|
||||
[`.order-${num}`]: { order: String(num) },
|
||||
})),
|
||||
],
|
||||
{ respectImportant },
|
||||
);
|
||||
};
|
||||
});
|
||||
@@ -1,139 +0,0 @@
|
||||
const plugin = require("tailwindcss/plugin");
|
||||
const themeConfig = require("../config/theme.json");
|
||||
|
||||
// Helper to extract a clean font name.
|
||||
const findFont = (fontStr) =>
|
||||
fontStr.replace(/\+/g, " ").replace(/:[^:]+/g, "");
|
||||
|
||||
// Set font families dynamically, filtering out 'type' keys
|
||||
const fontFamilies = Object.entries(themeConfig.fonts.font_family)
|
||||
.filter(([key]) => !key.includes("type"))
|
||||
.reduce((acc, [key, font]) => {
|
||||
acc[key] =
|
||||
`${findFont(font)}, ${themeConfig.fonts.font_family[`${key}_type`] || "sans-serif"}`;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const defaultColorGroups = [
|
||||
{ colors: themeConfig.colors.default.theme_color, prefix: "" },
|
||||
{ colors: themeConfig.colors.default.text_color, prefix: "" },
|
||||
];
|
||||
const darkColorGroups = [];
|
||||
if (themeConfig.colors.darkmode?.theme_color) {
|
||||
darkColorGroups.push({
|
||||
colors: themeConfig.colors.darkmode.theme_color,
|
||||
prefix: "darkmode-",
|
||||
});
|
||||
}
|
||||
if (themeConfig.colors.darkmode?.text_color) {
|
||||
darkColorGroups.push({
|
||||
colors: themeConfig.colors.darkmode.text_color,
|
||||
prefix: "darkmode-",
|
||||
});
|
||||
}
|
||||
|
||||
const getVars = (groups) => {
|
||||
const vars = {};
|
||||
groups.forEach(({ colors, prefix }) => {
|
||||
Object.entries(colors).forEach(([k, v]) => {
|
||||
const cssKey = k.replace(/_/g, "-");
|
||||
vars[`--color-${prefix}${cssKey}`] = v;
|
||||
});
|
||||
});
|
||||
return vars;
|
||||
};
|
||||
|
||||
const defaultVars = getVars(defaultColorGroups);
|
||||
const darkVars = getVars(darkColorGroups);
|
||||
|
||||
const baseSize = Number(themeConfig.fonts.font_size.base);
|
||||
const scale = Number(themeConfig.fonts.font_size.scale);
|
||||
const calculateFontSizes = (base, scale) => {
|
||||
const sizes = {};
|
||||
let currentSize = scale;
|
||||
for (let i = 6; i >= 1; i--) {
|
||||
sizes[`h${i}`] = `${currentSize}rem`;
|
||||
sizes[`h${i}-sm`] = `${currentSize * 0.9}rem`;
|
||||
currentSize *= scale;
|
||||
}
|
||||
sizes.base = `${base}px`;
|
||||
sizes["base-sm"] = `${base * 0.8}px`;
|
||||
return sizes;
|
||||
};
|
||||
const fontSizes = calculateFontSizes(baseSize, scale);
|
||||
|
||||
const fontVars = {};
|
||||
Object.entries(fontSizes).forEach(([key, value]) => {
|
||||
fontVars[`--text-${key}`] = value;
|
||||
});
|
||||
Object.entries(fontFamilies).forEach(([key, font]) => {
|
||||
fontVars[`--font-${key}`] = font;
|
||||
});
|
||||
|
||||
const baseVars = { ...fontVars, ...defaultVars };
|
||||
|
||||
// Build a colorsMap including both sets
|
||||
const colorsMap = {};
|
||||
[...defaultColorGroups, ...darkColorGroups].forEach(({ colors, prefix }) => {
|
||||
Object.entries(colors).forEach(([key]) => {
|
||||
const cssKey = key.replace(/_/g, "-");
|
||||
colorsMap[prefix + cssKey] = `var(--color-${prefix}${cssKey})`;
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = plugin.withOptions(() => {
|
||||
return function ({ addBase, addUtilities, matchUtilities }) {
|
||||
// Default vars on :root; dark vars on .dark
|
||||
addBase({
|
||||
":root": baseVars,
|
||||
".dark": darkVars,
|
||||
});
|
||||
|
||||
const fontUtils = {};
|
||||
Object.keys(fontFamilies).forEach((key) => {
|
||||
fontUtils[`.font-${key}`] = { fontFamily: `var(--font-${key})` };
|
||||
});
|
||||
Object.keys(fontSizes).forEach((key) => {
|
||||
fontUtils[`.text-${key}`] = { fontSize: `var(--text-${key})` };
|
||||
});
|
||||
addUtilities(fontUtils, {
|
||||
variants: ["responsive", "hover", "focus", "active", "disabled"],
|
||||
});
|
||||
|
||||
matchUtilities(
|
||||
{
|
||||
bg: (value) => ({ backgroundColor: value }),
|
||||
text: (value) => ({ color: value }),
|
||||
border: (value) => ({ borderColor: value }),
|
||||
fill: (value) => ({ fill: value }),
|
||||
stroke: (value) => ({ stroke: value }),
|
||||
},
|
||||
{ values: colorsMap, type: "color" },
|
||||
);
|
||||
|
||||
matchUtilities(
|
||||
{
|
||||
from: (value) => ({
|
||||
"--tw-gradient-from": value,
|
||||
"--tw-gradient-via-stops":
|
||||
"var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))",
|
||||
"--tw-gradient-stops":
|
||||
"var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))",
|
||||
}),
|
||||
to: (value) => ({
|
||||
"--tw-gradient-to": value,
|
||||
"--tw-gradient-via-stops":
|
||||
"var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))",
|
||||
"--tw-gradient-stops":
|
||||
"var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))",
|
||||
}),
|
||||
via: (value) => ({
|
||||
"--tw-gradient-via": value,
|
||||
"--tw-gradient-via-stops":
|
||||
"var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position)",
|
||||
}),
|
||||
},
|
||||
{ values: colorsMap, type: "color" },
|
||||
);
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user