From 54fd701572142264a79defd5734cb778822b1af1 Mon Sep 17 00:00:00 2001
From: sndyuk
Date: Mon, 29 Mar 2021 14:57:41 +0900
Subject: [PATCH] Add classGenerator option
---
README.md | 13 +++++++++++++
lib/classGenerator.js | 22 +++++++++++++++++-----
lib/optimizer.js | 12 +++++-------
spec/BasicSpec.js | 26 +++++++++++++++++++++++++-
spec/fixtures/case4.js | 1 +
spec/support/jasmine.json | 4 ++--
6 files changed, 63 insertions(+), 15 deletions(-)
create mode 100644 spec/fixtures/case4.js
diff --git a/README.md b/README.md
index 5df0b61..989aef7 100644
--- a/README.md
+++ b/README.md
@@ -74,6 +74,19 @@ ignorePrefixRegExp: '((hover|focus|xs|md|sm|lg|xl)[\\\\]*:)*',
```
In this case, `hover\:xs\:c-textbox__input` becomes `hover\:xs\:a`.
+#### classGenerator
+Override the default class name generator.
+
+```js
+// original: original class name
+// opts: options of the plugin
+// context: own context of the class generator(initial value is just an empty object)
+classGenerator: (original, opts, context) => {
+ // return custom generated class name.
+ // Or return undefined if you want to leave it to the original behavior.
+}
+```
+
### Example
#### Source code
```html
diff --git a/lib/classGenerator.js b/lib/classGenerator.js
index 2c450af..303fc34 100644
--- a/lib/classGenerator.js
+++ b/lib/classGenerator.js
@@ -6,6 +6,7 @@ const acceptChars = 'abcdefghijklmnopqrstuvwxyz_-0123456789'.split('');
function ClassGenerator() {
this.newClassMap = {};
this.newClassSize = 0;
+ this.context = {}
}
function stripEscapeSequence(words) {
@@ -13,11 +14,7 @@ function stripEscapeSequence(words) {
}
ClassGenerator.prototype = {
- generateClassName: function(original, opts) {
- original = stripEscapeSequence(original);
- const cn = this.newClassMap[original];
- if (cn) return cn;
-
+ defaultClassGenerator: function() {
const chars = []
let rest = (this.newClassSize - (this.newClassSize % acceptPrefix.length)) / acceptPrefix.length
if (rest > 0) {
@@ -36,6 +33,21 @@ ClassGenerator.prototype = {
let prefixIndex = this.newClassSize % acceptPrefix.length
const newClassName = `${acceptPrefix[prefixIndex]}${chars.join('')}`
+ return newClassName;
+ },
+ generateClassName: function(original, opts) {
+ original = stripEscapeSequence(original);
+ const cn = this.newClassMap[original];
+ if (cn) return cn;
+
+ let newClassName;
+ if (opts.classGenerator) {
+ newClassName = opts.classGenerator(original, opts, this.context);
+ }
+ if (!newClassName) {
+ newClassName = this.defaultClassGenerator();
+ }
+
if (opts.reserveClassName && opts.reserveClassName.includes(newClassName)) {
if (opts.log) {
console.log(`The class name has been reserved. ${chalk.green(newClassName)}`);
diff --git a/lib/optimizer.js b/lib/optimizer.js
index 028c503..582adf4 100644
--- a/lib/optimizer.js
+++ b/lib/optimizer.js
@@ -2,9 +2,7 @@ const { ReplaceSource } = require('webpack-sources');
const chalk = require('./chalk');
const ClassGenerator = require('./classGenerator');
-const classGenerator = new ClassGenerator()
-
-const validate = (opts) => {
+const validate = (opts, classGenerator) => {
if (!opts.log) return;
for (let className in classGenerator.newClassMap) {
const c = classGenerator.newClassMap[className];
@@ -19,7 +17,7 @@ const validate = (opts) => {
}
};
-const optimize = (chunk, compilation, opts) => chunk.files.forEach((file) => {
+const optimize = (chunk, compilation, opts, classGenerator) => chunk.files.forEach((file) => {
let classnameRegex;
if (file.match(/.+\.css.*$/)) {
classnameRegex = new RegExp(`\\\.(${opts.classNameRegExp})`, 'g');
@@ -85,9 +83,9 @@ const optimize = (chunk, compilation, opts) => chunk.files.forEach((file) => {
const optimizer = (compiler, compilation, opts) => (chunks) => {
if (!opts.classNameRegExp) throw new Error("'classNameRegExp' option is required. e.g. '[c]-[a-z][a-zA-Z0-9_]*'");
-
- chunks.forEach((chunk) => optimize(chunk, compilation, opts));
- validate(opts);
+ const classGenerator = new ClassGenerator();
+ chunks.forEach((chunk) => optimize(chunk, compilation, opts, classGenerator));
+ validate(opts, classGenerator);
}
module.exports = optimizer;
diff --git a/spec/BasicSpec.js b/spec/BasicSpec.js
index b27461f..886067c 100644
--- a/spec/BasicSpec.js
+++ b/spec/BasicSpec.js
@@ -1,7 +1,7 @@
var path = require('path');
var fs = require('fs');
-var webpack = require('webpack');
var rimraf = require('rimraf');
+var webpack = require('webpack');
var webpackMajorVersion = Number(require('webpack/package.json').version.split('.')[0]);
if (webpackMajorVersion < 4) {
var CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
@@ -197,4 +197,28 @@ describe('MangleCssClassPlugin', () => {
expect(classNameWithEscape.name).toBe(classNameWithoutEscape.name);
done();
});
+
+ it('override class name generator', (done) => {
+ testPlugin({
+ entry: [path.join(__dirname, 'fixtures/case4.js')],
+ output: {
+ path: OUTPUT_DIR,
+ filename: 'case4.js',
+ },
+ plugins: [new MangleCssClassPlugin({
+ classNameRegExp: defaultCssClassRegExp,
+ log: true,
+ classGenerator: (original, opts, context) => {
+ if (!context.id) {
+ context.id = 1;
+ }
+ if (original.startsWith('c-')) {
+ const className = `c${context.id}`;
+ context.id++;
+ return className;
+ }
+ }
+ })]
+ }, ["hoge-a
CASE 4
"], done);
+ });
});
diff --git a/spec/fixtures/case4.js b/spec/fixtures/case4.js
new file mode 100644
index 0000000..7334578
--- /dev/null
+++ b/spec/fixtures/case4.js
@@ -0,0 +1 @@
+const a = 'hoge-a
CASE 4
';
diff --git a/spec/support/jasmine.json b/spec/support/jasmine.json
index 54750e1..0b22611 100644
--- a/spec/support/jasmine.json
+++ b/spec/support/jasmine.json
@@ -3,6 +3,6 @@
"spec_files": [
"**/*[sS]pec.js"
],
- "stopSpecOnExpectationFailure": false,
- "random": true
+ "stopSpecOnExpectationFailure": true,
+ "random": false
}