diff --git a/README.md b/README.md index f4de421..d027aaa 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,14 @@ export const barBaz: string; `css-loader` exports mappings to `exports.locals` which is incompatible with the `namedExport`-option unless paired with `extract-text-webpack-plugin` or `style-loader`. They move the exported properties from `exports.locals` to `exports` making them required for `namedExport` to work, and `namedExport` required for them to work. *Always combine usage of `extract-text-webpack-plugin` or `style-loader` with the `namedExport`-option.* +### `orderAlphabetically`-option +Orders generated exports or interface properties alphabetically. This is useful when committing the .d.ts files as the default ordering is not always consistent and can change from commit to commit. +e.g.: + +```js + { test: /\.css$/, loader: 'typings-for-css-modules-loader?modules&orderAlphabetically' } +``` + ### `silent`-option To silence the loader because you get annoyed by its warnings or for other reasons, you can simply pass the "silent" query to the loader and it will shut up. e.g.: diff --git a/src/cssModuleToInterface.js b/src/cssModuleToInterface.js index 272e431..cb3dd00 100644 --- a/src/cssModuleToInterface.js +++ b/src/cssModuleToInterface.js @@ -6,18 +6,24 @@ const filenameToInterfaceName = (filename) => { .replace(/\W+(\w)/g, (_, c) => c.toUpperCase()); }; -const cssModuleToTypescriptInterfaceProperties = (cssModuleKeys, indent = ' ') => { - return cssModuleKeys +const cssModuleToTypescriptInterfaceProperties = (cssModuleKeys, orderAlphabetically, indent = ' ') => { + return sortCssModuleKeys(cssModuleKeys, orderAlphabetically) .map((key) => `${indent}'${key}': string;`) .join('\n'); }; -const cssModuleToNamedExports = (cssModuleKeys) => { - return cssModuleKeys +const cssModuleToNamedExports = (cssModuleKeys, orderAlphabetically) => { + return sortCssModuleKeys(cssModuleKeys, orderAlphabetically) .map((key) => `export const ${key}: string;`) .join('\n'); }; +const sortCssModuleKeys = (cssModuleKeys, orderAlphabetically) => { + return orderAlphabetically + ? [...cssModuleKeys].sort() + : [...cssModuleKeys] +}; + const allWordsRegexp = /^\w+$/i; export const filterNonWordClasses = (cssModuleKeys) => { const filteredClassNames = cssModuleKeys.filter(classname => allWordsRegexp.test(classname)); @@ -80,15 +86,15 @@ export const filenameToTypingsFilename = (filename) => { return path.join(dirName, `${baseName}.d.ts`); }; -export const generateNamedExports = (cssModuleKeys) => { - const namedExports = cssModuleToNamedExports(cssModuleKeys); +export const generateNamedExports = (cssModuleKeys, orderAlphabetically) => { + const namedExports = cssModuleToNamedExports(cssModuleKeys, orderAlphabetically); return (`${namedExports} `); }; -export const generateGenericExportInterface = (cssModuleKeys, filename, indent) => { +export const generateGenericExportInterface = (cssModuleKeys, filename, orderAlphabetically, indent) => { const interfaceName = filenameToInterfaceName(filename); - const interfaceProperties = cssModuleToTypescriptInterfaceProperties(cssModuleKeys, indent); + const interfaceProperties = cssModuleToTypescriptInterfaceProperties(cssModuleKeys, orderAlphabetically, indent); return ( `export interface ${interfaceName} { ${interfaceProperties} diff --git a/src/index.js b/src/index.js index 269c2d8..208b857 100644 --- a/src/index.js +++ b/src/index.js @@ -51,9 +51,11 @@ module.exports = function(...input) { } } + query.orderAlphabetically = !!query.orderAlphabetically; + let cssModuleDefinition; if (!query.namedExport) { - cssModuleDefinition = generateGenericExportInterface(cssModuleKeys, filename); + cssModuleDefinition = generateGenericExportInterface(cssModuleKeys, filename, query.orderAlphabetically); } else { const [cleanedDefinitions, skippedDefinitions,] = filterNonWordClasses(cssModuleKeys); if (skippedDefinitions.length > 0 && !query.camelCase) { @@ -72,7 +74,7 @@ These can be accessed using the object literal syntax; eg styles['delete'] inste `.yellow); } - cssModuleDefinition = generateNamedExports(nonReservedWordDefinitions); + cssModuleDefinition = generateNamedExports(nonReservedWordDefinitions, query.orderAlphabetically); } if (cssModuleDefinition.trim() === '') { // Ensure empty CSS modules export something diff --git a/test/entry.ts b/test/entry.ts index 6c7341e..9e5f878 100644 --- a/test/entry.ts +++ b/test/entry.ts @@ -1,10 +1,13 @@ import {locals as stylesBase} from './example.css'; import {locals as stylesCamelCase} from './example-camelcase.css'; +import {locals as stylesOrderAlphabetically} from './example-orderalphabetically.css'; import * as stylesNamedExport from './example-namedexport.css'; import * as stylesCamelCasedNamedExport from './example-camelcase-namedexport.css'; +import * as stylesNamedExportOrderAlphabetically from './example-namedexport-orderalphabetically.css'; import './example-no-css-modules.css'; import * as compose from './example-compose.css'; + const foo = stylesBase.foo; const barBaz = stylesBase['bar-baz']; @@ -17,4 +20,12 @@ const fooNamedExport = stylesNamedExport.foo; const fooCamelCaseNamedExport = stylesCamelCasedNamedExport.foo; const barBazCamelCaseNamedExport = stylesCamelCasedNamedExport.barBaz; +const fooStylesOrderAlphabetically = stylesOrderAlphabetically.foo; +const barStylesOrderAlphabetically = stylesOrderAlphabetically.bar; +const bazStylesOrderAlphabetically = stylesOrderAlphabetically.baz; + +const fooNamedExportOrderAlhpabetically = stylesNamedExportOrderAlphabetically.foo; +const barNamedExportOrderAlhpabetically = stylesNamedExportOrderAlphabetically.bar; +const bazNamedExportOrderAlhpabetically = stylesNamedExportOrderAlphabetically.baz; + const composed = compose.test; diff --git a/test/example-namedexport-orderalphabetically.css b/test/example-namedexport-orderalphabetically.css new file mode 100644 index 0000000..892307a --- /dev/null +++ b/test/example-namedexport-orderalphabetically.css @@ -0,0 +1,12 @@ +.foo { + color: white; +} + +.baz { + color: red; +} + +.bar { + color: green; +} + diff --git a/test/example-orderalphabetically.css b/test/example-orderalphabetically.css new file mode 100644 index 0000000..892307a --- /dev/null +++ b/test/example-orderalphabetically.css @@ -0,0 +1,12 @@ +.foo { + color: white; +} + +.baz { + color: red; +} + +.bar { + color: green; +} + diff --git a/test/expected-example-namedexport-orderalphabetically.css.d.ts b/test/expected-example-namedexport-orderalphabetically.css.d.ts new file mode 100644 index 0000000..e71c92d --- /dev/null +++ b/test/expected-example-namedexport-orderalphabetically.css.d.ts @@ -0,0 +1,3 @@ +export const bar: string; +export const baz: string; +export const foo: string; diff --git a/test/expected-example-orderalphabetically.css.d.ts b/test/expected-example-orderalphabetically.css.d.ts new file mode 100644 index 0000000..bb37115 --- /dev/null +++ b/test/expected-example-orderalphabetically.css.d.ts @@ -0,0 +1,7 @@ +export interface IExampleOrderalphabeticallyCss { + 'bar': string; + 'baz': string; + 'foo': string; +} + +export const locals: IExampleOrderalphabeticallyCss; diff --git a/test/webpack.config.babel.js b/test/webpack.config.babel.js index d69e970..2f40be4 100644 --- a/test/webpack.config.babel.js +++ b/test/webpack.config.babel.js @@ -12,7 +12,9 @@ module.exports = { { test: /example-namedexport\.css$/, loader: '../src/index.js?modules&namedExport' }, { test: /example-camelcase-namedexport\.css$/, loader: '../src/index.js?modules&camelCase&namedExport' }, { test: /example-no-css-modules\.css$/, loader: '../src/index.js' }, - { test: /example-compose\.css$/, loader: '../src/index.js?modules&camelCase&namedExport' } + { test: /example-compose\.css$/, loader: '../src/index.js?modules&camelCase&namedExport' }, + { test: /example-orderalphabetically\.css$/, loader: '../src/index.js?modules&orderAlphabetically' }, + { test: /example-namedexport-orderalphabetically\.css$/, loader: '../src/index.js?modules&namedExport&orderAlphabetically' } ] } };