Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docusaurus/docs/adding-a-css-modules-stylesheet.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ This project supports [CSS Modules](https://github.com/css-modules/css-modules)

> **Tip:** Should you want to preprocess a stylesheet with Sass then make sure to [follow the installation instructions](adding-a-sass-stylesheet.md) and then change the stylesheet file extension as follows: `[name].module.scss` or `[name].module.sass`.

> **Tip:** Should you want to preprocess a stylesheet with Less then make sure to [follow the installation instructions](adding-a-less-stylesheet.md) and then change the stylesheet file extension as follows: `[name].module.less`.

CSS Modules let you use the same CSS class name in different files without worrying about naming clashes. Learn more about CSS Modules [here](https://css-tricks.com/css-modules-part-1-need/).

## `Button.module.css`
Expand Down
60 changes: 60 additions & 0 deletions docusaurus/docs/adding-a-less-stylesheet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
id: adding-a-less-stylesheet
title: Adding a Less Stylesheet
sidebar_label: Adding Less Stylesheets
---

> Note: this feature is available with `react-scripts@4.1.0` and higher.

Generally, we recommend that you don’t reuse the same CSS classes across different components. For example, instead of using a `.Button` CSS class in `<AcceptButton>` and `<RejectButton>` components, we recommend creating a `<Button>` component with its own `.Button` styles, that both `<AcceptButton>` and `<RejectButton>` can render (but [not inherit](https://facebook.github.io/react/docs/composition-vs-inheritance.html)).

Following this rule often makes CSS preprocessors less useful, as features like mixins and nesting are replaced by component composition. You can, however, integrate a CSS preprocessor if you find it valuable.

To use Less, first install `less`:

```sh
$ npm install less --save
$ # or
$ yarn add less
```

Now you can rename `src/App.css` to `src/App.less` and update `src/App.js` to import `src/App.less`.
This file and any other file will be automatically compiled if imported with the extension `.less`.

To share variables between Less files, you can use Less imports. For example, `src/App.less` and other component style files could include `@import "./shared.less";` with variable definitions.

This will allow you to do imports like

```less
@import 'styles/colors.less'; // assuming a styles directory under src/
@import '~nprogress/nprogress'; // importing a css file from the nprogress node module
```

> **Note:** You must prefix imports from `node_modules` with `~` as displayed above.

To configure `less-loader`, add a file `.less-loader.js` to the root of the project, that exports the options you want to use.

For example you could pass the `modifyVars` option to `less` with

```js
module.exports = {
lessOptions: {
modifyVars: {
'primary-color': 'green',
},
},
};
```

> **Tip:** You can opt into using this feature with [CSS modules](adding-a-css-modules-stylesheet.md) too!

> **Note:** If you're using Flow, override the [module.file_ext](https://flow.org/en/docs/config/options/#toc-module-file-ext-string) setting in your `.flowconfig` so it'll recognize `.less` files. You will also need to include the `module.file_ext` default settings for `.js`, `.jsx`, `.mjs` and `.json` files.
>
> ```
> [options]
> module.file_ext=.js
> module.file_ext=.jsx
> module.file_ext=.mjs
> module.file_ext=.json
> module.file_ext=.less
> ```
1 change: 1 addition & 0 deletions docusaurus/website/sidebars.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"adding-a-stylesheet",
"adding-a-css-modules-stylesheet",
"adding-a-sass-stylesheet",
"adding-a-less-stylesheet",
"adding-css-reset",
"post-processing-css",
"adding-images-fonts-and-files",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ const tests = [
resourcePath: '/path/to/file.module.sass',
expected: 'file_class__2KnOB',
},
{
resourcePath: '/path/to/file.module.less',
expected: 'file_class__1yslt',
},
{
resourcePath: '/path/to/file.name.module.css',
expected: 'file_name_class__1OzEh',
Expand Down
7 changes: 7 additions & 0 deletions packages/react-dev-utils/formatWebpackMessages.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@ function formatMessage(message) {
'Run `npm install sass` or `yarn add sass` inside your workspace.';
}

// Add helpful message for users trying to use Less for the first time
if (lines[1] && lines[1].match(/Cannot find module.+less/)) {
lines[1] = 'To import Less files, you first need to install less.\n';
lines[1] +=
'Run `npm install less` or `yarn add less` inside your workspace.';
}

message = lines.join('\n');
// Internal stacks are generally useless so we strip them... with the
// exception of stacks containing `webpack:` because they're normally
Expand Down
4 changes: 2 additions & 2 deletions packages/react-dev-utils/getCSSModuleLocalIdent.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ module.exports = function getLocalIdent(
localName,
options
) {
// Use the filename or folder name, based on some uses the index.js / index.module.(css|scss|sass) project style
// Use the filename or folder name, based on some uses the index.js / index.module.(css|scss|sass|less) project style
const fileNameOrFolder = context.resourcePath.match(
/index\.module\.(css|scss|sass)$/
/index\.module\.(css|scss|sass|less)$/
)
? '[folder]'
: '[name]';
Expand Down
2 changes: 2 additions & 0 deletions packages/react-scripts/config/paths.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ module.exports = {
proxySetup: resolveApp('src/setupProxy.js'),
appNodeModules: resolveApp('node_modules'),
swSrc: resolveModule(resolveApp, 'src/service-worker'),
lessLoaderOptions: resolveApp('.less-loader.js'),
publicUrlOrPath,
};

Expand All @@ -98,6 +99,7 @@ module.exports = {
proxySetup: resolveApp('src/setupProxy.js'),
appNodeModules: resolveApp('node_modules'),
swSrc: resolveModule(resolveApp, 'src/service-worker'),
lessLoaderOptions: resolveApp('.less-loader.js'),
publicUrlOrPath,
// These properties only exist before ejecting:
ownPath: resolveOwn('.'),
Expand Down
50 changes: 49 additions & 1 deletion packages/react-scripts/config/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;
const lessRegex = /\.less$/;
const lessModuleRegex = /\.module\.less$/;

// style loaders options
const lessLoaderOptions = fs.existsSync(paths.lessLoaderOptions)
? require(paths.lessLoaderOptions)
: null;

const hasJsxRuntime = (() => {
if (process.env.DISABLE_NEW_JSX_TRANSFORM === 'true') {
Expand Down Expand Up @@ -107,7 +114,7 @@ module.exports = function (webpackEnv) {
const shouldUseReactRefresh = env.raw.FAST_REFRESH;

// common function to get style loaders
const getStyleLoaders = (cssOptions, preProcessor) => {
const getStyleLoaders = (cssOptions, preProcessor, preProcessorOptions) => {
const loaders = [
isEnvDevelopment && require.resolve('style-loader'),
isEnvProduction && {
Expand Down Expand Up @@ -163,6 +170,7 @@ module.exports = function (webpackEnv) {
loader: require.resolve(preProcessor),
options: {
sourceMap: true,
...(preProcessorOptions || {}),
},
}
);
Expand Down Expand Up @@ -592,6 +600,46 @@ module.exports = function (webpackEnv) {
'sass-loader'
),
},
// Opt-in support for LESS (using .less extension).
// By default we support LESS Modules with the
// extension .module.less
{
test: lessRegex,
exclude: lessModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
},
'less-loader',
lessLoaderOptions
),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
},
// Adds support for CSS Modules, but using LESS
// using the extension .module.less
{
test: lessModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
getLocalIdent: getCSSModuleLocalIdent,
},
},
'less-loader',
lessLoaderOptions
),
},
// "file" loader makes sure those assets get served by WebpackDevServer.
// When you `import` an asset, you get its (virtual) filename.
// In production, they would get copied to the `build` folder.
Expand Down
1 change: 1 addition & 0 deletions packages/react-scripts/fixtures/kitchensink/template.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"dependencies": {
"bootstrap": "4.3.1",
"jest": "26.6.0",
"less": "4.x",
"node-sass": "4.x",
"normalize.css": "7.0.0",
"prop-types": "15.7.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,19 @@ describe('Integration', () => {
]);
});

it('less inclusion', async () => {
doc = await initDOM('less-inclusion');
matchCSS(doc, [/#feature-less-inclusion\{background:.+;color:.+}/]);
});

it('less modules inclusion', async () => {
doc = await initDOM('less-modules-inclusion');
matchCSS(doc, [
/.+less-styles_lessModulesInclusion.+\{background:.+;color:.+}/,
/.+assets_lessModulesIndexInclusion.+\{background:.+;color:.+}/,
]);
});

it('image inclusion', async () => {
doc = await initDOM('image-inclusion');

Expand Down
10 changes: 10 additions & 0 deletions packages/react-scripts/fixtures/kitchensink/template/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,16 @@ class App extends Component {
this.setFeature(f.default)
);
break;
case 'less-inclusion':
import('./features/webpack/LessInclusion').then(f =>
this.setFeature(f.default)
);
break;
case 'less-modules-inclusion':
import('./features/webpack/LessModulesInclusion').then(f =>
this.setFeature(f.default)
);
break;
case 'custom-interpolation':
import('./features/syntax/CustomInterpolation').then(f =>
this.setFeature(f.default)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* 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.
*/

import React from 'react';
import './assets/less-styles.less';

const LessInclusion = () => (
<p id="feature-less-inclusion">We love useless text.</p>
);

export default LessInclusion;
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* 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.
*/

import React from 'react';
import ReactDOM from 'react-dom';
import LessInclusion from './LessInclusion';

describe('less inclusion', () => {
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<LessInclusion />, div);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* 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.
*/

import React from 'react';
import styles from './assets/less-styles.module.less';
import indexStyles from './assets/index.module.less';

const LessModulesInclusion = () => (
<div>
<p className={styles.lessModulesInclusion}>LESS Modules are working!</p>
<p className={indexStyles.lessModulesIndexInclusion}>
LESS Modules with index are working!
</p>
</div>
);

export default LessModulesInclusion;
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* 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.
*/

import React from 'react';
import ReactDOM from 'react-dom';
import LessModulesInclusion from './LessModulesInclusion';

describe('less modules inclusion', () => {
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<LessModulesInclusion />, div);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.lessModulesIndexInclusion {
background: darkblue;
color: lightblue;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#feature-less-inclusion {
background: ghostwhite;
color: crimson;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.lessModulesInclusion {
background: darkblue;
color: lightblue;
}
5 changes: 5 additions & 0 deletions packages/react-scripts/lib/react-app.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,8 @@ declare module '*.module.sass' {
const classes: { readonly [key: string]: string };
export default classes;
}

declare module '*.module.less' {
const classes: { readonly [key: string]: string };
export default classes;
}
1 change: 1 addition & 0 deletions packages/react-scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"jest-circus": "26.6.0",
"jest-resolve": "26.6.0",
"jest-watch-typeahead": "0.6.1",
"less-loader": "^7.3.0",
"mini-css-extract-plugin": "0.11.3",
"optimize-css-assets-webpack-plugin": "5.0.4",
"pnp-webpack-plugin": "1.6.4",
Expand Down
4 changes: 2 additions & 2 deletions packages/react-scripts/scripts/utils/createJestConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,12 @@ module.exports = (resolve, rootDir, isEjecting) => {
},
transformIgnorePatterns: [
'[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$',
'^.+\\.module\\.(css|sass|scss)$',
'^.+\\.module\\.(css|sass|scss|less)$',
],
modulePaths: modules.additionalModulePaths || [],
moduleNameMapper: {
'^react-native$': 'react-native-web',
'^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy',
'^.+\\.module\\.(css|sass|scss|less)$': 'identity-obj-proxy',
...(modules.jestAliases || {}),
},
moduleFileExtensions: [...paths.moduleFileExtensions, 'node'].filter(
Expand Down