Skip to content

Commit fc7c991

Browse files
ro-savageTimer
authored andcommitted
Add support for CSS Modules with explicit filename (#2285)
* Add css modules with [name].modules.css file convention * Add e2e for CSS Modules * Updated based on feedback * Change css modules class name to be deterministic and fix licences * Update css modules class naming convention
1 parent 0ddc043 commit fc7c991

File tree

10 files changed

+186
-19
lines changed

10 files changed

+186
-19
lines changed

Diff for: packages/react-scripts/config/webpack.config.dev.js

+35-9
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,20 @@ const publicUrl = '';
3030
// Get environment variables to inject into our app.
3131
const env = getClientEnvironment(publicUrl);
3232

33+
// Options for PostCSS as we reference these options twice
34+
// Adds vendor prefixing to support IE9 and above
35+
const postCSSLoaderOptions = {
36+
// Necessary for external CSS imports to work
37+
// https://github.com/facebookincubator/create-react-app/issues/2677
38+
ident: 'postcss',
39+
plugins: () => [
40+
require('postcss-flexbugs-fixes'),
41+
autoprefixer({
42+
flexbox: 'no-2009',
43+
}),
44+
],
45+
};
46+
3347
// This is the development configuration.
3448
// It is focused on developer experience and fast rebuilds.
3549
// The production configuration is different and lives in a separate file.
@@ -209,8 +223,10 @@ module.exports = {
209223
// "style" loader turns CSS into JS modules that inject <style> tags.
210224
// In production, we use a plugin to extract that CSS to a file, but
211225
// in development "style" loader enables hot editing of CSS.
226+
// By default we support CSS Modules with the extension .module.css
212227
{
213228
test: /\.css$/,
229+
exclude: /\.module\.css$/,
214230
use: [
215231
require.resolve('style-loader'),
216232
{
@@ -221,18 +237,28 @@ module.exports = {
221237
},
222238
{
223239
loader: require.resolve('postcss-loader'),
240+
options: postCSSLoaderOptions,
241+
},
242+
],
243+
},
244+
// Adds support for CSS Modules (https://github.com/css-modules/css-modules)
245+
// using the extension .module.css
246+
{
247+
test: /\.module\.css$/,
248+
use: [
249+
require.resolve('style-loader'),
250+
{
251+
loader: require.resolve('css-loader'),
224252
options: {
225-
// Necessary for external CSS imports to work
226-
// https://github.com/facebookincubator/create-react-app/issues/2677
227-
ident: 'postcss',
228-
plugins: () => [
229-
require('postcss-flexbugs-fixes'),
230-
autoprefixer({
231-
flexbox: 'no-2009',
232-
}),
233-
],
253+
importLoaders: 1,
254+
modules: true,
255+
localIdentName: '[path]__[name]___[local]',
234256
},
235257
},
258+
{
259+
loader: require.resolve('postcss-loader'),
260+
options: postCSSLoaderOptions,
261+
},
236262
],
237263
},
238264
// "file" loader makes sure those assets get served by WebpackDevServer.

Diff for: packages/react-scripts/config/webpack.config.prod.js

+50-9
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,20 @@ const extractTextPluginOptions = shouldUseRelativeAssetPaths
5555
{ publicPath: Array(cssFilename.split('/').length).join('../') }
5656
: {};
5757

58+
// Options for PostCSS as we reference these options twice
59+
// Adds vendor prefixing to support IE9 and above
60+
const postCSSLoaderOptions = {
61+
// Necessary for external CSS imports to work
62+
// https://github.com/facebookincubator/create-react-app/issues/2677
63+
ident: 'postcss',
64+
plugins: () => [
65+
require('postcss-flexbugs-fixes'),
66+
autoprefixer({
67+
flexbox: 'no-2009',
68+
}),
69+
],
70+
};
71+
5872
// This is the production configuration.
5973
// It compiles slowly and is focused on producing a fast and minimal bundle.
6074
// The development configuration is different and lives in a separate file.
@@ -221,8 +235,10 @@ module.exports = {
221235
// tags. If you use code splitting, however, any async bundles will still
222236
// use the "style" loader inside the async code so CSS from them won't be
223237
// in the main CSS file.
238+
// By default we support CSS Modules with the extension .module.css
224239
{
225240
test: /\.css$/,
241+
exclude: /\.module\.css$/,
226242
loader: ExtractTextPlugin.extract(
227243
Object.assign(
228244
{
@@ -243,18 +259,43 @@ module.exports = {
243259
},
244260
{
245261
loader: require.resolve('postcss-loader'),
262+
options: postCSSLoaderOptions,
263+
},
264+
],
265+
},
266+
extractTextPluginOptions
267+
)
268+
),
269+
// Note: this won't work without `new ExtractTextPlugin()` in `plugins`.
270+
},
271+
// Adds support for CSS Modules (https://github.com/css-modules/css-modules)
272+
// using the extension .module.css
273+
{
274+
test: /\.module\.css$/,
275+
loader: ExtractTextPlugin.extract(
276+
Object.assign(
277+
{
278+
fallback: {
279+
loader: require.resolve('style-loader'),
280+
options: {
281+
hmr: false,
282+
},
283+
},
284+
use: [
285+
{
286+
loader: require.resolve('css-loader'),
246287
options: {
247-
// Necessary for external CSS imports to work
248-
// https://github.com/facebookincubator/create-react-app/issues/2677
249-
ident: 'postcss',
250-
plugins: () => [
251-
require('postcss-flexbugs-fixes'),
252-
autoprefixer({
253-
flexbox: 'no-2009',
254-
}),
255-
],
288+
importLoaders: 1,
289+
minimize: true,
290+
sourceMap: shouldUseSourceMap,
291+
modules: true,
292+
localIdentName: '[path]__[name]___[local]',
256293
},
257294
},
295+
{
296+
loader: require.resolve('postcss-loader'),
297+
options: postCSSLoaderOptions,
298+
},
258299
],
259300
},
260301
extractTextPluginOptions

Diff for: packages/react-scripts/fixtures/kitchensink/integration/webpack.test.js

+10
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ describe('Integration', () => {
2121
).to.match(/#feature-css-inclusion\{background:.+;color:.+}/);
2222
});
2323

24+
it('css modules inclusion', async () => {
25+
const doc = await initDOM('css-modules-inclusion');
26+
27+
expect(
28+
doc.getElementsByTagName('style')[0].textContent.replace(/\s/g, '')
29+
).to.match(
30+
/.+__style-module___cssModulesInclusion+\{background:.+;color:.+}/
31+
);
32+
});
33+
2434
it('image inclusion', async () => {
2535
const doc = await initDOM('image-inclusion');
2636

Diff for: packages/react-scripts/fixtures/kitchensink/src/App.js

+5
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ class App extends Component {
8181
this.setFeature(f.default)
8282
);
8383
break;
84+
case 'css-modules-inclusion':
85+
import(
86+
'./features/webpack/CssModulesInclusion'
87+
).then(f => this.setFeature(f.default));
88+
break;
8489
case 'custom-interpolation':
8590
import('./features/syntax/CustomInterpolation').then(f =>
8691
this.setFeature(f.default)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import React from 'react';
9+
import styles from './assets/style.module.css';
10+
11+
export default () => (
12+
<p className={styles.cssModulesInclusion}>CSS Modules are working!</p>
13+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import React from 'react';
9+
import ReactDOM from 'react-dom';
10+
import CssModulesInclusion from './CssModulesInclusion';
11+
12+
describe('css modules inclusion', () => {
13+
it('renders without crashing', () => {
14+
const div = document.createElement('div');
15+
ReactDOM.render(<CssModulesInclusion />, div);
16+
});
17+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.cssModulesInclusion {
2+
background: darkblue;
3+
color: lightblue;
4+
}

Diff for: packages/react-scripts/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"file-loader": "1.1.6",
4646
"fs-extra": "5.0.0",
4747
"html-webpack-plugin": "2.30.1",
48+
"identity-obj-proxy": "3.0.0",
4849
"jest": "22.1.1",
4950
"object-assign": "4.1.1",
5051
"postcss-flexbugs-fixes": "3.2.0",

Diff for: packages/react-scripts/scripts/utils/createJestConfig.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,13 @@ module.exports = (resolve, rootDir, isEjecting) => {
3939
'config/jest/fileTransform.js'
4040
),
4141
},
42-
transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs)$'],
42+
transformIgnorePatterns: [
43+
'[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs)$',
44+
'^.+\\.module\\.css$',
45+
],
4346
moduleNameMapper: {
4447
'^react-native$': 'react-native-web',
48+
'^.+\\.module\\.css$': 'identity-obj-proxy',
4549
},
4650
moduleFileExtensions: [
4751
'web.js',

Diff for: packages/react-scripts/template/README.md

+46
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ You can find the most recent version of this guide [here](https://github.com/fac
2424
- [Importing a Component](#importing-a-component)
2525
- [Code Splitting](#code-splitting)
2626
- [Adding a Stylesheet](#adding-a-stylesheet)
27+
- [Adding a CSS Modules stylesheet](#adding-a-css-modules-stylesheet)
2728
- [Post-Processing CSS](#post-processing-css)
2829
- [Adding a CSS Preprocessor (Sass, Less etc.)](#adding-a-css-preprocessor-sass-less-etc)
2930
- [Adding Images, Fonts, and Files](#adding-images-fonts-and-files)
@@ -513,6 +514,51 @@ In development, expressing dependencies this way allows your styles to be reload
513514

514515
If you are concerned about using Webpack-specific semantics, you can put all your CSS right into `src/index.css`. It would still be imported from `src/index.js`, but you could always remove that import if you later migrate to a different build tool.
515516

517+
## Adding a CSS Modules stylesheet
518+
519+
This project supports [CSS Modules](https://github.com/css-modules/css-modules) alongside regular stylesheets using the **[name].module.css** file naming convention. CSS Modules allows the scoping of CSS by automatically creating a unique classname of the format **[dir]\_\_[filename]___[classname]**.
520+
521+
An advantage of this is the ability to repeat the same classname within many CSS files without worrying about a clash.
522+
523+
### `Button.module.css`
524+
525+
```css
526+
.button {
527+
padding: 20px;
528+
}
529+
```
530+
531+
### `another-stylesheet.css`
532+
533+
```css
534+
.button {
535+
color: green;
536+
}
537+
```
538+
539+
### `Button.js`
540+
541+
```js
542+
import React, { Component } from 'react';
543+
import './another-stylesheet.css'; // Import regular stylesheet
544+
import styles from './Button.module.css'; // Import css modules stylesheet as styles
545+
546+
class Button extends Component {
547+
render() {
548+
// You can use them as regular CSS styles
549+
return <div className={styles.button} />;
550+
}
551+
}
552+
```
553+
### `exported HTML`
554+
No clashes from other `.button` classnames
555+
556+
```html
557+
<div class="src__Button-module___button"></div>
558+
```
559+
560+
**This is an optional feature.** Regular html stylesheets and js imported stylesheets are fully supported. CSS Modules are only added when explictly named as a css module stylesheet using the extension `.module.css`.
561+
516562
## Post-Processing CSS
517563

518564
This project setup minifies your CSS and adds vendor prefixes to it automatically through [Autoprefixer](https://github.com/postcss/autoprefixer) so you don’t need to worry about it.

0 commit comments

Comments
 (0)