vite-plugin-pretty-module-classnames
Version:
Make your scoped CSS module class names clear and readable — this plugin automatically adds the module filename and other useful info to class names for easier development.
98 lines (94 loc) • 4.42 kB
JavaScript
import { createHash } from "crypto";
//#region src/constants.ts
const GENERATE_SCOPED_NAME_WARNING = "[vite-plugin-pretty-module-classnames]:: The 'generateScopedName' configuration has already been set. Your vite.config configuration or other plugins might be attempting to override this setting, which could affect the proper functioning of vite-plugin-pretty-module-classnames.";
const defaultOptions = {
lineNumber: false,
separator: {
beforeHash: "_",
beforeClassName: "__",
beforeLineNumber: "-"
}
};
//#endregion
//#region src/utils.ts
function getHash(input) {
return createHash("sha256").update(input).digest("hex").slice(0, 5);
}
function getLineNumber(cssData, className) {
const lines = cssData.split("\n");
const match = new RegExp(`\\.${className}\\b`);
return lines.findIndex((line) => match.test(line)) + 1;
}
function deepMerge(defaultOptions$1, userOptions) {
const result = { ...defaultOptions$1 };
for (const key in userOptions) {
const value = userOptions[key];
if (value != null && typeof value === "object" && !Array.isArray(value)) result[key] = deepMerge(defaultOptions$1[key], value);
else if (value !== void 0) result[key] = value;
}
return result;
}
/**
* Generates a unique class name for CSS modules based on the file name and the specified class name.
* The file name is processed to remove specific parts such as ".module" and file extensions.
* If `lineNumber` is provided, it's added to the generated class name.
*
* @param name - The name of the CSS class that will be part of the generated unique name.
* @param filename is the full path to the style file, which can be undefined. If the parameter is undefined, the function throws an exception.
* @param lineNumber is the line number where the CSS class is defined, optional.
* @returns A string with a unique class name, including a sanitized file name, the original class name, a randomly generated hash, and optionally the line number.
* @throws Error if `filename` is not provided or is not a string.
*/
function sanitizeModuleClassname(name, filename, separator, lineNumber) {
if (typeof filename !== "string") throw new Error("The filename must be string and cannot be undefined.");
const parts = filename.split("?")[0].split("/");
const lastSegment = parts.pop();
if (!lastSegment) throw new Error("Filename must include a valid file name.");
const baseFilename = lastSegment.replace(/(\.vue|\.module)?(\.\w+)$/, "");
const pathHash = getHash(parts.join("/"));
const classname = `${baseFilename}${separator.beforeClassName}${name}`;
const hash = `${separator.beforeHash}${getHash(`${pathHash}-${classname}`)}`;
const lineInfo = lineNumber !== void 0 ? `${separator.beforeLineNumber}${lineNumber}` : "";
return `${classname}${hash}${lineInfo}`;
}
//#endregion
//#region src/index.ts
/**
* Adds the filename without the `-module` suffix to the class names of CSS modules.
* It customizes the generateScopedName function to use a sanitized version of the filename, class name, and a hash.
* If the `lineNumber` option is set to true, the line number is added to the generated class name.
*
* @prop {Object} `options` - Plugin options.
* @prop {boolean} `options.lineNumber` - Whether to include the line number in the generated class name. @default false
* @prop {string} `options.separator.beforeHash` - @default '_'
* @prop {string} `options.separator.beforeClassName` - @default '__'
* @prop {string} `options.separator.beforeLineNumber` - @default '-'
* @returns {Plugin} A Vite plugin object with a custom configuration for CSS modules.
*/
function prettyModuleClassnames(userOptions = {}) {
return {
name: "vite-plugin-pretty-module-classnames",
config(config) {
const options = deepMerge(defaultOptions, userOptions);
const cssModules = config.css?.modules;
if (process.env.VITEST) return {};
if (cssModules && "generateScopedName" in cssModules && cssModules.generateScopedName) console.warn(GENERATE_SCOPED_NAME_WARNING);
const newCssConfig = {
...config.css,
modules: {
...cssModules,
generateScopedName: (name, filename, css) => {
const lineNumber = options.lineNumber ? getLineNumber(css, name) : void 0;
return sanitizeModuleClassname(name, filename, options.separator, lineNumber);
}
}
};
return {
...config,
css: newCssConfig
};
}
};
}
//#endregion
export { prettyModuleClassnames as default };