diff --git a/packages/react-dev-utils/README.md b/packages/react-dev-utils/README.md index 42e12c2f91d..f63febe7c65 100644 --- a/packages/react-dev-utils/README.md +++ b/packages/react-dev-utils/README.md @@ -326,3 +326,40 @@ module.exports = { // ... } ``` + +#### `getCSSModuleLocalIdent(context: Object, localIdentName: String, localName: String, options: Object): string` + +Creates a class name for CSS Modules that uses either the filename or folder name if named `index.module.css`. + +For `MyFolder/MyComponent.module.css` and class `MyClass` the output will be `MyComponent.module_MyClass__[hash]` +For `MyFolder/index.module.css` and class `MyClass` the output will be `MyFolder_MyClass__[hash]` + +```js +const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent'); + +// In your webpack config: +// ... +module: { + rules: [ + { + test: /\.module\.css$/, + use: [ + require.resolve('style-loader'), + { + loader: require.resolve('css-loader'), + options: { + importLoaders: 1, + modules: true, + getLocalIdent: getCSSModuleLocalIdent, + }, + }, + { + loader: require.resolve('postcss-loader'), + options: postCSSLoaderOptions, + }, + ], + } + ] +} +``` + diff --git a/packages/react-dev-utils/getCSSModuleLocalIdent.js b/packages/react-dev-utils/getCSSModuleLocalIdent.js new file mode 100644 index 00000000000..d1cd22a4e05 --- /dev/null +++ b/packages/react-dev-utils/getCSSModuleLocalIdent.js @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +const loaderUtils = require('loader-utils'); + +module.exports = function getLocalIdent( + context, + localIdentName, + localName, + options +) { + // Use the filename or folder name, based on some uses the index.js / index.module.css project style + const fileNameOrFolder = context.resourcePath.endsWith('index.module.css') + ? '[folder]' + : '[name]'; + // Create a hash based on a the file location and class name. Will be unique across a project, and close to globally unique. + const hash = loaderUtils.getHashDigest( + context.resourcePath + localName, + 'md5', + 'base64', + 5 + ); + // Use loaderUtils to find the file or folder name + const className = loaderUtils.interpolateName( + context, + fileNameOrFolder + '_' + localName + '__' + hash, + options + ); + // remove the .module that appears in every classname when based on the file. + return className.replace('.module_', '_'); +}; diff --git a/packages/react-dev-utils/package.json b/packages/react-dev-utils/package.json index 424c16f6305..73d6600fd50 100644 --- a/packages/react-dev-utils/package.json +++ b/packages/react-dev-utils/package.json @@ -19,6 +19,7 @@ "eslintFormatter.js", "FileSizeReporter.js", "formatWebpackMessages.js", + "getCSSModuleLocalIdent.js", "getProcessForPort.js", "ignoredFiles.js", "inquirer.js", diff --git a/packages/react-scripts/config/webpack.config.dev.js b/packages/react-scripts/config/webpack.config.dev.js index 398b8bf53b7..fd18d23d255 100644 --- a/packages/react-scripts/config/webpack.config.dev.js +++ b/packages/react-scripts/config/webpack.config.dev.js @@ -17,6 +17,7 @@ const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin'); const eslintFormatter = require('react-dev-utils/eslintFormatter'); const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); +const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent'); const getClientEnvironment = require('./env'); const paths = require('./paths'); @@ -269,7 +270,7 @@ module.exports = { options: { importLoaders: 1, modules: true, - localIdentName: '[path]__[name]___[local]', + getLocalIdent: getCSSModuleLocalIdent, }, }, { diff --git a/packages/react-scripts/config/webpack.config.prod.js b/packages/react-scripts/config/webpack.config.prod.js index b3c4a602cd4..67fa18150a3 100644 --- a/packages/react-scripts/config/webpack.config.prod.js +++ b/packages/react-scripts/config/webpack.config.prod.js @@ -19,6 +19,7 @@ const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin'); const eslintFormatter = require('react-dev-utils/eslintFormatter'); const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); +const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent'); const paths = require('./paths'); const getClientEnvironment = require('./env'); @@ -306,7 +307,7 @@ module.exports = { minimize: true, sourceMap: shouldUseSourceMap, modules: true, - localIdentName: '[path]__[name]___[local]', + getLocalIdent: getCSSModuleLocalIdent, }, }, { @@ -422,7 +423,7 @@ module.exports = { // having to parse `index.html`. new ManifestPlugin({ fileName: 'asset-manifest.json', - publicPath: publicPath + publicPath: publicPath, }), // Generate a service worker script that will precache, and keep up to date, // the HTML & assets that are part of the Webpack build. diff --git a/packages/react-scripts/fixtures/kitchensink/integration/webpack.test.js b/packages/react-scripts/fixtures/kitchensink/integration/webpack.test.js index bba497c49f3..865fb118517 100644 --- a/packages/react-scripts/fixtures/kitchensink/integration/webpack.test.js +++ b/packages/react-scripts/fixtures/kitchensink/integration/webpack.test.js @@ -26,8 +26,11 @@ describe('Integration', () => { expect( doc.getElementsByTagName('style')[0].textContent.replace(/\s/g, '') + ).to.match(/.+style_cssModulesInclusion__.+\{background:.+;color:.+}/); + expect( + doc.getElementsByTagName('style')[1].textContent.replace(/\s/g, '') ).to.match( - /.+__style-module___cssModulesInclusion+\{background:.+;color:.+}/ + /.+assets_cssModulesIndexInclusion__.+\{background:.+;color:.+}/ ); }); diff --git a/packages/react-scripts/fixtures/kitchensink/src/features/webpack/CssModulesInclusion.js b/packages/react-scripts/fixtures/kitchensink/src/features/webpack/CssModulesInclusion.js index 0f96ae161fb..05339e3fae1 100644 --- a/packages/react-scripts/fixtures/kitchensink/src/features/webpack/CssModulesInclusion.js +++ b/packages/react-scripts/fixtures/kitchensink/src/features/webpack/CssModulesInclusion.js @@ -7,7 +7,13 @@ import React from 'react'; import styles from './assets/style.module.css'; +import indexStyles from './assets/index.module.css'; export default () => ( -

CSS Modules are working!

+
+

CSS Modules are working!

+

+ CSS Modules with index are working! +

+
); diff --git a/packages/react-scripts/fixtures/kitchensink/src/features/webpack/assets/index.module.css b/packages/react-scripts/fixtures/kitchensink/src/features/webpack/assets/index.module.css new file mode 100644 index 00000000000..f1c6d7d19be --- /dev/null +++ b/packages/react-scripts/fixtures/kitchensink/src/features/webpack/assets/index.module.css @@ -0,0 +1,4 @@ +.cssModulesIndexInclusion { + background: darkblue; + color: lightblue; +} diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index 352484fe1e8..66359e30661 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -50,6 +50,7 @@ "html-webpack-plugin": "2.30.1", "identity-obj-proxy": "3.0.0", "jest": "22.1.2", + "loader-utils": "^1.1.0", "object-assign": "4.1.1", "postcss-flexbugs-fixes": "3.2.0", "postcss-loader": "2.0.10", diff --git a/packages/react-scripts/template/README.md b/packages/react-scripts/template/README.md index 5f00b482f59..5495e320f06 100644 --- a/packages/react-scripts/template/README.md +++ b/packages/react-scripts/template/README.md @@ -518,23 +518,23 @@ If you are concerned about using Webpack-specific semantics, you can put all you +