1

I need some help understanding the behavior of webpack less-loader.

I'm using webpack 4.5.0.

Here is my webpack config:

const fs = require('fs');
const path = require('path');

const config = {
    entry: './src/index.js',
    mode: 'development',
    output: {
        filename: 'webpack-bundle.js',
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                include: path.resolve(__dirname, 'src'),
                use: {
                    loader: 'babel-loader',
                    options: {
                        cacheDirectory: true,
                        presets: ['react'],
                    }
                }
            },
            {
                test: /\.css$/,
                use: 'css-loader',
            },
            {
                test: /\.less$/,
                use: 'less-loader',
            },
        ],
    },
};

module.exports = config;

Here is my source file structure:

src
 |- index.html
 |- index.js
 |- index.css
 |- index.less
webpack.config.js
package.json

Here is the content of package.json:

{
    "name": "xxx",
    "version": "2.1.0",
    "private": true,
    "scripts": {
        "build": "$(yarn bin)/webpack"
    },
    "devDependencies": {
        "babel-core": "^6.26.0",
        "babel-loader": "^7.1.4",
        "babel-preset-react": "^6.24.1",
        "css-loader": "^0.28.11",
        "less": "^3.0.1",
        "less-loader": "^4.1.0",
        "react": "^16.3.2",
        "react-dom": "^16.3.2",
        "webpack": "^4.5.0",
        "webpack-cli": "^2.0.14"
    }
}

Here is the content of index.less:

body {
    background: #000;
}

When I run yarn build, I get these output:

$ yarn build
yarn run v1.5.1
$ $(yarn bin)/webpack
Hash: deadade0357a0f63a681
Version: webpack 4.5.0
Time: 690ms
Built at: 2018-4-18 17:18:37
            Asset     Size  Chunks             Chunk Names
webpack-bundle.js  695 KiB    main  [emitted]  main
Entrypoint main = webpack-bundle.js
[./src/index.css] 192 bytes {main} [built]
[./src/index.js] 339 bytes {main} [built]
[./src/index.less] 163 bytes {main} [built] [failed] [1 error]
    + 22 hidden modules

ERROR in ./src/index.less
Module parse failed: Unexpected token (1:5)
You may need an appropriate loader to handle this file type.
| body {
|   background: #000;
| }
 @ ./src/index.js 5:0-22

As you can see, js file and css file are compiled, but not less file.

What am I missing here? Why is it telling me I'm missing appropriate loader?


UPDATE:

To verify what @felixmosh said, I update index.less a little bit:

body {
    a {
        text-decoration: none;
    }
}

When I compile again, the error message changes to:

...
[./src/index.less] 170 bytes {main} [built] [failed] [1 error]
    + 22 hidden modules

ERROR in ./src/index.less
Module parse failed: Unexpected token (1:5)
You may need an appropriate loader to handle this file type.
| body a {
|   text-decoration: none;
| }
 @ ./src/index.js 5:0-22

As you can see less code is actually compiled to css (the body a part).

After I chain less-loader with css-loader in webpack.config.js:

const path = require('path');

const config = {
    ...
    module: {
        rules: [
            ...
            {
                test: /\.less$/,
                use: [
                    {
                        loader: 'css-loader',
                    },
                    {
                        loader: 'less-loader',
                    },
                ],
            },
        ],
    },
};

module.exports = config;

The build output changes to:

Built at: 2018-4-19 10:59:58
            Asset     Size  Chunks             Chunk Names
webpack-bundle.js  695 KiB    main  [emitted]  main
Entrypoint main = webpack-bundle.js
[./src/index.css] 192 bytes {main} [built]
[./src/index.js] 339 bytes {main} [built]
[./src/index.less] 198 bytes {main} [built]
    + 22 hidden modules
Done in 1.38s.

All code are compiled and bundled successfully.

In a word, less-loader is only responsible for converting less code to css, but NOT converting css to js which webpack is able to understand and include in the final bundle js.

This is why we need to chain less-loader with css-loader to properly bundle less.

Bruce Sun
  • 631
  • 1
  • 10
  • 26

1 Answers1

3

less-loader is a loader that transforms less into css, webpack not understands css without the proper loader.

You need to concat all of the css related loaders in that case.

{
  test: /\.less$/,
  use: ['css-loader', 'less-loader'],
},
felixmosh
  • 32,615
  • 9
  • 69
  • 88
  • Thanks for your answer. But what on earth is css-loader doing? – Bruce Sun Apr 18 '18 at 15:13
  • 1
    Assume that Webpack is understands only JS. the purpose of loaders is always to convert only one format. So, if you have less type, you need to specify the chain of loaders in order to convert that type to js. less-loader converts less -> css, css-loader converts css->js – felixmosh Apr 18 '18 at 16:39
  • Thanks for your comment! I think your explanation of css-loader is much easier to understand than the official doc: "The css-loader interprets `@import` and `url()` like `import/require()` and will resolve them." What exactly does that mean? – Bruce Sun Apr 19 '18 at 03:12
  • As I said, cssz-loader converts css code to js, on the way it applies import for assets that has url, for example if u have background image, it will try to import is (with the appropriate loader) – felixmosh Apr 19 '18 at 07:45