scoped-rem
Version:
Makes CSS rem units relative to a custom root font size.
109 lines (106 loc) • 4.23 kB
JavaScript
//#region rolldown:runtime
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
let postcss = require("postcss");
postcss = __toESM(postcss);
let postcss_value_parser = require("postcss-value-parser");
postcss_value_parser = __toESM(postcss_value_parser);
let zod = require("zod");
zod = __toESM(zod);
//#region src/index.ts
const VARNAME_DEFAULT = "rem-relative-base";
const VARSELECTOR_DEFAULT = ":root";
const ScopedRemOptionsSchema = zod.z.object({
varname: zod.z.string().optional().default(VARNAME_DEFAULT),
rootval: zod.z.string().optional(),
varselector: zod.z.string().optional().default(VARSELECTOR_DEFAULT),
precision: zod.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 zod.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 (0, postcss.default)([{
postcssPlugin: "scoped-rem-transform",
Declaration(decl) {
const parsed = (0, postcss_value_parser.default)(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
exports.VARNAME_DEFAULT = VARNAME_DEFAULT;
exports.VARSELECTOR_DEFAULT = VARSELECTOR_DEFAULT;
exports.parseQueryOptions = parseQueryOptions;
exports.transformCss = transformCss;