Search code examples
webpacklessantdpostcsspostcss-loader

Reconciling antd import with postcss


I am trying to use postcss-env-function in order to be able to share variables between javascript and less/css. I get the following error during build. The error disappears if I remove the first line of index.less:

@import '~antd/dist/antd.less';

However, then I will not get antd's styles. (There is no error also if I remove postcss-loader). How to solve this issue?

Minimal repository


Error:

ERROR in ./src/styles/index.less (./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[1].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[1].use[3]!./src/styles/index.less)
Module build failed (from ./node_modules/postcss-loader/dist/cjs.js):
SyntaxError

(1:1) postcss-env-fn: <css input> Unknown word

> 1 | opacity=45

webpack.config.js:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const path = require('path');
const fs = require('fs');

process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';

function buildConfig(args) {
  var rootConfig = {
    entry: './src/index.tsx',
    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: 'app.[fullhash].js'
    },
    resolve: {
      extensions: ['.ts', '.tsx', '.js', '.json']
    },
    devtool: (args && args.production ? false : 'inline-cheap-source-map'),
    devServer: {
      contentBase: './dist',
      disableHostCheck: true,
      hot: true,
      historyApiFallback: true,
      proxy: {
        '/api': {
          target: `https://${process.env.API_HOST}:8081`,
          secure: false,
        }
      },
      https: {
        key: fs.readFileSync(process.env.SSL_KEY_FILE),
        cert: fs.readFileSync(process.env.SSL_CERT_FILE),
      }
    },
    module: {
      rules: [
        {
          test: /\.(ts|js)x?$/,
          exclude: /node_modules/,
          loader: 'babel-loader',
        },
        {
          test: /\.(c|le)ss$/,
          use: [
            { loader: 'style-loader' },
            { loader: 'css-loader', options: { importLoaders: 1 } },
            { loader: 'postcss-loader', options: {
              postcssOptions: {
                ident: 'postcss',
                plugins: { 'postcss-env-function': { importFrom: { environmentVariables: { '--test': '200px' } } } }
              } } },  
            { loader: 'less-loader', options: { lessOptions: { javascriptEnabled: true } } },
          ]
        },
        {
          test: /\.(png|svg|jpg|gif|woff|woff2|eot|ttf|otf|jpeg)$/,
          use: [
            { loader: 'file-loader' },
          ]
        }
      ],
    },
    plugins: [
      new HtmlWebpackPlugin({
        inject: true,
        template: require('html-webpack-template'),
        devServer: (args && args.production) ? undefined : `https://${process.env.BUBI_API_HOST}:8080`,
        meta: [
          {
            name: 'og:title',
            content: 'frontend-react'
          },
          {
            name: 'og:description',
            content: 'React app'
          },
        ],
        mobile: true,
        lang: 'en-GB',
        title: 'frontend-react',
        scripts: [
        ],
      }),
      new CopyPlugin([
        { from: 'static_assets/', to: '' },
      ]),
    ],
  };

  if (args && args.production) {
    rootConfig = {
      ...rootConfig,
      optimization: {
        minimize: true,
        minimizer: [new TerserPlugin({
          extractComments: 'all',
        })],
        usedExports: true,
      },
    };
  }

  return rootConfig;
}

module.exports = buildConfig;

Versions: less-loader: 8.1.1 postcss: 8.3.5 postcss-loader: 6.1.1

Beginning of index.less:

@import '~antd/dist/antd.less'; 

@shadow: 0px 0px 15px rgba(0,0,0,0.08);
@body-background-color: #f7fafc;

.text {
  font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
  font-weight: 300;
}

body {
  background-color: @body-background-color;
  padding: 0; 
  overflow-x: hidden;
}

package.json:

declare module '*.jpg';
declare module '*.jpeg';
declare module '*.png';

.babelrc:

{
    "presets": [
        "@babel/preset-env",
        "@babel/preset-typescript",
        "@babel/preset-react"
    ],
    "plugins": [
        "@babel/plugin-proposal-class-properties"
    ]
}

Solution

  • It looks like your compiled CSS contains filter: alpha(opacity=45); which postcss-env-function doesn't like.

    The "Unknown word" exception is thrown by postcss-values-parser (postcss-env-function uses it to parse CSS declaration values).

    Ideally, in your case, you'd pass the ignoreUnknownWords option to postcss-values-parser to ignore that filter: alpha declaration, but postcss-env-function doesn't let you do that.

    So, a possible solution could be to modify postcss-env-function so you can pass the ignoreUnknownWords option to the parser.

    Another possible solution could be to remove all the filter: alpha(opacity=#) declarations from your CSS since only IE8 and below support it. You can do that, for example, by creating a simple function "plugin" that walks through all the filter: declarations and removes it if its value starts with "alpha":

    webpack.config.js:

    { loader: 'postcss-loader', options: {
      postcssOptions: {
        ident: 'postcss',
        plugins: [
          root => root.walkDecls('filter', decl => {
            if (decl.value.startsWith('alpha')) decl.remove();
          }),
          { 'postcss-env-function': { importFrom: { environmentVariables: { '--test': '200px' } } } },
        ],
      }
    } },