UNPKG

scoped-rem

Version:

Makes CSS rem units relative to a custom root font size.

81 lines (79 loc) 3.01 kB
import postcss from "postcss"; import valueParser from "postcss-value-parser"; import { z } from "zod"; //#region src/index.ts const VARNAME_DEFAULT = "rem-relative-base"; const VARSELECTOR_DEFAULT = ":root"; const ScopedRemOptionsSchema = z.object({ varname: z.string().optional().default(VARNAME_DEFAULT), rootval: z.string().optional(), varselector: z.string().optional().default(VARSELECTOR_DEFAULT), precision: z.coerce.number().int().min(0).max(100).optional() }); function parseQueryOptions(query) { const params = new URLSearchParams(query); if (!params.has("rem-scoped")) return null; const rawOptions = { varname: params.get("varname") || void 0, rootval: params.get("rootval") || void 0, varselector: params.get("varselector") || void 0, precision: params.get("precision") || void 0 }; try { return ScopedRemOptionsSchema.parse(rawOptions); } catch (error) { if (error instanceof z.ZodError) throw new Error(`[scoped-rem] Invalid query options: ${error.issues.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ")}`); throw error; } } function generateCssVarDeclaration(options) { const { rootval } = options; if (!rootval) return ""; const varname = options.varname || VARNAME_DEFAULT; return `${options.varselector || VARSELECTOR_DEFAULT} { --${varname}: ${rootval}; }`; } const remRegex = /"[^"]+"|'[^']+'|url\([^)]+\)|--[\w-]+|(-?\d*\.?\d+)rem/gi; /** * @param css CSS source code * @param filename will be passed to PostCSS * @returns The transformed CSS */ function transformRemWithPostCSS(css, filename, options) { const varname = options.varname || VARNAME_DEFAULT; return postcss([{ postcssPlugin: "scoped-rem-transform", Declaration(decl) { const parsed = valueParser(decl.value); let modified = false; parsed.walk((node) => { if (node.type === "word" && node.value.endsWith("rem")) { const numStr = node.value.match(remRegex)?.[0]; if (!numStr) return; const numValue = (() => { if (options.precision !== void 0) { if (options.precision < 0 || options.precision > 100) throw new Error(`[scoped-rem] Invalid precision value: ${options.precision}. It should be between 0 and 100.`); return parseFloat((parseFloat(numStr) || 0).toFixed(options.precision)); } else return parseFloat(numStr) || 0; })(); if (numValue === 0) { node.value = "0"; modified = true; return; } node.value = `calc(${numValue} * var(--${varname}))`; modified = true; } }); if (modified) decl.value = parsed.toString(); } }]).process(css, { from: filename }).css; } function transformCss(css, filename, options) { const varDeclaration = generateCssVarDeclaration(options); const transformedCss = transformRemWithPostCSS(css, filename, options); if (!varDeclaration) return transformedCss; return `${varDeclaration}\n${transformedCss}`; } //#endregion export { VARNAME_DEFAULT, VARSELECTOR_DEFAULT, parseQueryOptions, transformCss }; //# sourceMappingURL=index.js.map