UNPKG

@theemo/build

Version:
211 lines (204 loc) 5.96 kB
import fs from "node:fs"; import path from "node:path"; import { transform } from "lightningcss"; import { readPackageSync } from "read-pkg"; import { writePackageSync } from "write-package"; //#region ../theme/dist/index.js /** * Color Contrasts * * @see https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-contrast */ const ColorContrast = { NoPreference: "no-preference", More: "more", Less: "less", Custom: "custom" }; /** * Color Scheme * * @see https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme */ const ColorScheme = { Light: "light", Dark: "dark" }; /** * Motion * * @see https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion */ const Motion = { NoPreference: "no-preference", Reduce: "reduce" }; const BrowserMechanic = { ColorScheme: "color-scheme", ColorContrast: "color-contrast", Motion: "motion" }; /** * Checks wether the given feature uses mode behavior * * @param feature the feature in question * @returns `true` for a mode behavior, otherwise `false` */ function isModalFeature(feature) { return feature.defaultOption !== void 0; } const queries = { [BrowserMechanic.ColorScheme]: { [ColorScheme.Dark]: "(prefers-color-scheme: dark)", [ColorScheme.Light]: "(prefers-color-scheme: light)" }, [BrowserMechanic.ColorContrast]: { [ColorContrast.NoPreference]: "(prefers-color-contrast: no-preference)", [ColorContrast.More]: "(prefers-color-contrast: more)", [ColorContrast.Less]: "(prefers-color-contrast: less)", [ColorContrast.Custom]: "(prefers-color-contrast: custom)" }, [BrowserMechanic.Motion]: { [Motion.NoPreference]: "(prefers-reduced-motion: no-preference)", [Motion.Reduce]: "(prefers-reduced-motion)" } }; //#endregion //#region src/config.ts const DEFAULT_CONFIG = { outDir: "dist", features: [], lightningcss: true }; function configWithDefaults(config) { return { ...DEFAULT_CONFIG, ...config }; } //#endregion //#region src/features.ts function isModalBuildFeature(feature) { return isModalFeature(feature); } function isColorSchemeFeature(feature) { return feature.browserFeature === BrowserMechanic.ColorScheme; } function isMediaQueryFeature(feature) { return feature.browserFeature === BrowserMechanic.ColorContrast || feature.browserFeature === BrowserMechanic.Motion; } //#endregion //#region src/build.ts const MEDIA_QUERY = { [BrowserMechanic.ColorScheme]: "prefers-color-scheme", [BrowserMechanic.ColorContrast]: "prefers-contrast", [BrowserMechanic.Motion]: "prefers-reduced-motion" }; function readFile(file) { const contents = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : ""; return contents; } function combineFiles(config) { const contents = []; for (const file of config.files ?? []) contents.push(readFile(file).trim()); return contents; } function buildColorSchemeFeature(feature) { return ` :root { color-scheme: ${feature.options.join(" ")}; } [data-theemo-color-scheme="light"] { color-scheme: light; } [data-theemo-color-scheme="dark"] { color-scheme: dark; } `.trim(); } function buildFeature(feature) { const contents = []; for (const [option, file] of Object.entries(feature.options)) { const fileContents = readFile(file); const selectors = option === feature.defaultOption ? [":root"] : []; selectors.push(`[data-theemo-${feature.name}="${option}"]`); const css = fileContents.replace(":root", selectors.join(", ")).trim(); contents.push(css); } return contents; } function buildMediaQueryBrowserFeature(feature) { const contents = []; const mediaQuery = MEDIA_QUERY[feature.browserFeature]; for (const [option, file] of Object.entries(feature.options)) { const fileContents = readFile(file); const css = `@media (${mediaQuery}: ${option}) { ${fileContents} }`; contents.push(css); } return contents; } function buildFeatures(config) { const contents = []; for (const feature of config.features) { if (isColorSchemeFeature(feature)) contents.push(buildColorSchemeFeature(feature)); else if (isModalBuildFeature(feature)) contents.push(...buildFeature(feature)); if (isMediaQueryFeature(feature)) contents.push(...buildMediaQueryBrowserFeature(feature)); } return contents; } function getThemeName() { const data = readPackageSync(); return data.theemo?.name ?? data.name; } function wrapInLayer(contents, layerName) { return [ `@layer ${layerName} {`, ...contents.map((line) => ` ${line}`), "}" ]; } function buildFiles(config) { const name = getThemeName(); let contents = [...combineFiles(config), ...buildFeatures(config)]; if (!fs.existsSync(config.outDir)) fs.mkdirSync(config.outDir); if (config.layerName) contents = wrapInLayer(contents, config.layerName); const outFile = path.join(config.outDir, `${name}.css`); const css = contents.join("\n\n"); fs.writeFileSync(outFile, css); if (config.lightningcss !== false) { const options = typeof config.lightningcss === "object" ? config.lightningcss : {}; const { code } = transform({ code: Buffer.from(css), filename: outFile, ...options }); fs.writeFileSync(outFile, code); } } function updatePackage(config) { const packageJson = readPackageSync({ normalize: false }); const theemo = packageJson.theemo; theemo.file = path.join(config.outDir, `${theemo.name}.css`); theemo.features = config.features.map((f) => ({ ...f, options: Array.isArray(f.options) ? f.options : Object.keys(f.options) })); if (!Array.isArray(packageJson.keywords)) packageJson.keywords = []; if (!packageJson.keywords.includes("theemo-theme")) packageJson.keywords.push("theemo-theme"); writePackageSync(packageJson); } /** * Build a theme, which can be managed by theemo * * @param config The configuration for the theme and its behavior */ function build(config) { const defaultConfig = configWithDefaults(config); buildFiles(defaultConfig); updatePackage(defaultConfig); } //#endregion export { build }; //# sourceMappingURL=index.js.map