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 }