diff --git a/.editorconfig b/.editorconfig index c270cfa..def8631 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,4 +8,13 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true +[GNUmakefile] +indent_style = tab + +[Makefile] +indent_style = tab + +[makefile] +indent_style = tab + # cheat sheet: http://EditorConfig.org diff --git a/.gitignore b/.gitignore index 905f8e6..f638e40 100644 --- a/.gitignore +++ b/.gitignore @@ -1,30 +1,5 @@ -# Logs -logs -*.log - -# Runtime data -pids -*.pid -*.seed - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release - -# Dependency directory -# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git -node_modules/* - -# added automatically by precommit-hook as defaults -.jshint* +.DS_Store +.npm +node_modules +npm-debug.log* +yarn.lock diff --git a/.travis.yml b/.travis.yml index ca8e5dd..b29cf66 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,4 @@ language: node_js node_js: - - "4.3" + - "4" + - "6" diff --git a/README.md b/README.md index 154923a..505ec8e 100644 --- a/README.md +++ b/README.md @@ -206,10 +206,12 @@ hook({ }); ``` -### `camelCase boolean` +### `camelCase boolean|string` Camelizes exported class names. Similar to [css-loader?camelCase](https://github.com/webpack/css-loader#camel-case). +Available options: `true`, `dashes`, `only`, `dashesOnly`. + ### `append` array Appends custom plugins to the end of the PostCSS pipeline. Since the `require` function is synchronous, you should provide synchronous plugins only. diff --git a/lib/attachHook.js b/lib/attachHook.js index 37e4821..1ea95f5 100644 --- a/lib/attachHook.js +++ b/lib/attachHook.js @@ -1,3 +1,5 @@ +'use strict'; + /** * @param {function} compile * @param {string} extension diff --git a/lib/index.js b/lib/index.js index 42b0526..43dacc4 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,15 +1,13 @@ -const assign = require('lodash').assign; +'use strict'; + +const {assign, identity, negate, camelCase: camelCaseFunc, mapKeys} = require('lodash'); +const {dirname, relative, resolve} = require('path'); +const {readFileSync} = require('fs'); +const {transformTokens} = require('./transformTokens'); + const attachHook = require('./attachHook'); -const dirname = require('path').dirname; const genericNames = require('generic-names'); const globToRegex = require('glob-to-regexp'); -const identity = require('lodash').identity; -const negate = require('lodash').negate; -const camelCaseFunc = require('lodash').camelCase; -const mapKeys = require('lodash').mapKeys; -const readFileSync = require('fs').readFileSync; -const relative = require('path').relative; -const resolve = require('path').resolve; const validate = require('./validate'); const postcss = require('postcss'); @@ -23,13 +21,13 @@ const debugFetch = require('debug')('css-modules:fetch'); const debugSetup = require('debug')('css-modules:setup'); module.exports = function setupHook({ + camelCase, devMode, extensions = '.css', ignore, preprocessCss = identity, processCss, processorOpts, - camelCase, append = [], prepend = [], createImportedName, @@ -89,7 +87,6 @@ module.exports = function setupHook({ ? require.resolve(_to) : resolve(dirname(from), _to); - // checking cache let tokens = tokensByFile[filename]; if (tokens) { @@ -107,10 +104,6 @@ module.exports = function setupHook({ tokens = lazyResult.root.tokens; - if (camelCase) { - tokens = assign(mapKeys(tokens, (value, key) => camelCaseFunc(key)), tokens); - } - if (!debugMode) { // updating cache tokensByFile[filename] = tokens; @@ -132,8 +125,13 @@ module.exports = function setupHook({ const exts = toArray(extensions); const isException = buildExceptionChecker(ignore); + const hook = filename => { + const tokens = fetch(filename, filename); + return camelCase ? transformTokens(tokens, camelCase) : tokens; + }; + // @todo add possibility to specify particular config for each extension - exts.forEach(extension => attachHook(filename => fetch(filename, filename), extension, isException)); + exts.forEach(extension => attachHook(hook, extension, isException)); }; /** diff --git a/lib/transformTokens.js b/lib/transformTokens.js new file mode 100644 index 0000000..5d1089f --- /dev/null +++ b/lib/transformTokens.js @@ -0,0 +1,63 @@ +'use strict'; + +const {assign, camelCase, reduce} = require('lodash'); + +const camelizeKeys = (acc, value, key) => { + const camelizedKey = camelCase(key); + if (camelizedKey !== key) acc[camelizedKey] = value; + return acc; +}; + +const camelizeDashedKeys = (acc, value, key) => { + const camelizedKey = camelizeDashes(key); + if (camelizedKey !== key) acc[camelizedKey] = value; + return acc; +}; + +const camelizeOnlyKeys = (acc, value, key) => { + const camelizedKey = camelCase(key); + if (camelizedKey !== key) acc[camelizedKey] = value + else acc[key] = value; + return acc; +}; + +const camelizeOnlyDashedKeys = (acc, value, key) => { + const camelizedKey = camelizeDashes(key); + if (camelizedKey !== key) acc[camelizedKey] = value + else acc[key] = value; + return acc; +}; + +exports.camelizeDashes = camelizeDashes; +exports.transformTokens = transformTokens; + +/** + * @param {string} str + * @return {string} + */ +function camelizeDashes(str) { + return str.replace(/-(\w)/g, (m, letter) => letter.toUpperCase()); +} + +/** + * @param {object} tokens + * @param {boolean|string} camelCase 'dashes|dashesOnly|only' + * @return {object} + */ +function transformTokens(tokens, camelCase) { + switch (camelCase) { + case true: + return reduce(tokens, camelizeKeys, assign({}, tokens)); + + case 'dashes': + return reduce(tokens, camelizeDashedKeys, assign({}, tokens)); + + case 'dashesOnly': + return reduce(tokens, camelizeOnlyDashedKeys, {}); + + case 'only': + return reduce(tokens, camelizeOnlyKeys, {}); + } + + return tokens; +} diff --git a/lib/validate.js b/lib/validate.js index 68d1efd..a0dbee3 100644 --- a/lib/validate.js +++ b/lib/validate.js @@ -1,16 +1,26 @@ -const difference = require('lodash').difference; -const forEach = require('lodash').forEach; -const keys = require('lodash').keys; +'use strict'; + +const { + difference, + forEach, + isArray, + isBoolean, + isFunction, + isPlainObject, + isRegExp, + isString, + keys, +} = require('lodash'); const rules = { // hook + camelCase: 'boolean|string', devMode: 'boolean', extensions: 'array|string', ignore: 'function|regex|string', preprocessCss: 'function', processCss: 'function', processorOpts: 'object', - camelCase: 'boolean', // plugins append: 'array', prepend: 'array', @@ -23,12 +33,12 @@ const rules = { }; const tests = { - array: require('lodash').isArray, - boolean: require('lodash').isBoolean, - function: require('lodash').isFunction, - object: require('lodash').isPlainObject, - regex: require('lodash').isRegExp, - string: require('lodash').isString, + array: isArray, + boolean: isBoolean, + function: isFunction, + object: isPlainObject, + regex: isRegExp, + string: isString, }; module.exports = function validate(options) { diff --git a/package.json b/package.json index f939277..787497c 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,8 @@ "test": "npm run test:babel", "test:babel": "NODE_PATH=$(pwd)/test/tokens/node_modules $npm_package_scripts_test_unit --compilers js:babel-register", "test:coverage": "NODE_PATH=$(pwd)/test/tokens/node_modules babel-node --presets es2015 `npm bin`/isparta cover --report text --report html `npm bin`/_mocha -- --require test/setup.js --ui tdd test/*/*.js", - "test:node": "NODE_PATH=$(pwd)/test/tokens/node_modules $npm_package_scripts_test_unit --use_strict", - "test:watch": "NODE_PATH=$(pwd)/test/tokens/node_modules $npm_package_scripts_test_unit --watch --use_strict", + "test:node": "NODE_PATH=$(pwd)/test/tokens/node_modules $npm_package_scripts_test_unit", + "test:watch": "NODE_PATH=$(pwd)/test/tokens/node_modules $npm_package_scripts_test_unit --watch", "test:unit": "mocha --require test/setup.js --ui tdd test/*/*.js" }, "repository": { diff --git a/preset.js b/preset.js index b20216c..b5acbc5 100644 --- a/preset.js +++ b/preset.js @@ -1,3 +1,5 @@ +'use strict'; + const basename = require('path').basename; const debug = require('debug')('css-modules:preset'); const dirname = require('path').dirname; diff --git a/test/api/camelCase.js b/test/api/camelCase.js index 98e5e16..6d5faa7 100644 --- a/test/api/camelCase.js +++ b/test/api/camelCase.js @@ -2,18 +2,36 @@ const detachHook = require('../sugar').detachHook; const dropCache = require('../sugar').dropCache; suite('api/camelCase', () => { - test('should add camel case keys in token', () => { - const tokens = require('./fixture/bem.css'); - assert.deepEqual(tokens, { - blockElementModifier: '_test_api_fixture_bem__block__element--modifier', - 'block__element--modifier': '_test_api_fixture_bem__block__element--modifier', + suite('-> `true`', () => { + test('should add camel case keys in token', () => { + const tokens = require('./fixture/bem.css'); + assert.deepEqual(tokens, { + blockElementModifier: '_test_api_fixture_bem__block__element--modifier', + 'block__element--modifier': '_test_api_fixture_bem__block__element--modifier', + }); + }); + + setup(() => hook({ camelCase: true })); + + teardown(() => { + detachHook('.css'); + dropCache('./api/fixture/bem.css'); }); }); - setup(() => hook({ camelCase: true })); + suite('-> `dashesOnly`', () => { + test('should replace keys with dashes by its camel-cased equivalent', () => { + const tokens = require('./fixture/bem.css'); + assert.deepEqual(tokens, { + 'block__element-Modifier': '_test_api_fixture_bem__block__element--modifier', + }); + }); + + setup(() => hook({camelCase: 'dashesOnly'})); - teardown(() => { - detachHook('.css'); - dropCache('./api/fixture/bem.css'); + teardown(() => { + detachHook('.css'); + dropCache('./api/fixture/bem.css'); + }); }); }); diff --git a/test/lib/transformTokens.js b/test/lib/transformTokens.js new file mode 100644 index 0000000..779e9d4 --- /dev/null +++ b/test/lib/transformTokens.js @@ -0,0 +1,65 @@ +'use strict'; + +const {camelizeDashes, transformTokens} = require('../../lib/transformTokens'); + +suite('lib/transformTokens', () => { + test('camelizeDashes', () => { + assert.equal(camelizeDashes(''), ''); + assert.equal(camelizeDashes('a-a'), 'aA'); + assert.equal(camelizeDashes('a-a-b'), 'aAB'); + assert.equal(camelizeDashes('a-'), 'a-'); + }); + + suite('transformTokens', () => { + test('`true -> should transform all the keys to CC, keeps original keys', () => { + assert.deepEqual(transformTokens({ + 'k-e-bab': 'kebab case', + 's_na_ke': 'snake case', + 'simple': 'aaa', + }, true), { + 'k-e-bab': 'kebab case', + 's_na_ke': 'snake case', + 'simple': 'aaa', + 'kEBab': 'kebab case', + 'sNaKe': 'snake case', + }); + }); + + test('`dashes` -> should transform the keys with dashed, keeps original keys', () => { + assert.deepEqual(transformTokens({ + 'k-e-bab': 'kebab case', + 's_na_ke': 'snake case', + 'simple': 'aaa', + }, 'dashes'), { + 'k-e-bab': 'kebab case', + 's_na_ke': 'snake case', + 'simple': 'aaa', + 'kEBab': 'kebab case', + }); + }); + + test('`dashesOnly` -> should transform only keys with dashes', () => { + assert.deepEqual(transformTokens({ + 'k-e-bab': 'kebab case', + 's_na_ke': 'snake case', + 'simple': 'aaa', + }, 'dashesOnly'), { + 's_na_ke': 'snake case', + 'simple': 'aaa', + 'kEBab': 'kebab case', + }); + }); + + test('`only` -> should camelize keys', () => { + assert.deepEqual(transformTokens({ + 'k-e-bab': 'kebab case', + 's_na_ke': 'snake case', + 'simple': 'aaa', + }, 'only'), { + 'simple': 'aaa', + 'kEBab': 'kebab case', + 'sNaKe': 'snake case', + }); + }); + }); +});