dilanx / craco

Create React App Configuration Override, an easy and comprehensible configuration layer for Create React App.
https://craco.js.org
Apache License 2.0
7.44k stars 498 forks source link

Adding a loader results in Webpack "Invalid configuration object" error in some cases #486

Open ajani2001 opened 1 year ago

ajani2001 commented 1 year ago

I want to add html-loader and I do it according to the recipe:

import {CracoConfig} from "@craco/types";
import {addBeforeLoader, loaderByName} from "@craco/craco";

const config: CracoConfig = {
    webpack: {
        configure: (webpackConfig) => {
            webpackConfig.resolve!.extensions!.push('.html');

            const htmlLoader = {
                loader: require.resolve('html-loader'),
                test: /\.html$/,
                exclude: /node_modules/,
            };

            addBeforeLoader(webpackConfig, loaderByName('file-loader'), htmlLoader);

            return webpackConfig;
        },
    }
};

export default config;

But my application even doesn`t starts:

Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
 - configuration.module.rules[1] should be one of these:
   ["..." | object { assert?, compiler?, dependency?, descriptionData?, enforce?, exclude?, generator?, include?, issuer?, issuerLayer?, layer?, loader?, mimetype?, oneOf?, options?, parser?, realResource?, resolve?, resource?, resourceFragment?, resourceQuery?, rules?, scheme?, sideEffects?, test?, type?, use? }, ...]
   -> A rule.
   Details:
    * configuration.module.rules[1].oneOf[2].use[1] has an unknown property 'test'. These properties are valid:
      object { ident?, loader?, options? }
    * configuration.module.rules[1].oneOf[2].use[1] has an unknown property 'exclude'. These properties are valid:
      object { ident?, loader?, options? }

It can be reproduced on a blank app just created by create-react-app (with typescript in my case).

"@craco/craco": "^7.0.0",
"@craco/types": "^7.0.0",
"react-scripts": "5.0.1",
"typescript": "^4.9.4"

I suppose, this is a rare bug, that emerge, when a target loader for addBeforeLoader`s matcher becomes a RuleSetUseItem located in the 'use' array. New loader argument for addBeforeLoader has the type of RuleSetRule, thus, a RuleSetRule is inserted in the 'use' array and breaks the consistency.

The root of problem might me in this cast: https://github.com/dilanx/craco/blob/47fb53b43c355099c2f381cb91693b94be4ee4c8/packages/craco/src/lib/loaders.ts#L49

tylandercasper commented 1 year ago

did you ever find a fix for this? I was a little suprised that copying and pasting the provided code doesn't work.

alfonsoar commented 1 year ago

I was able to work around this issue by using a diffrent loader as the starting point, doing this seems to place the loader in the correct place of the webpack config.

const txtLoader = {
  test: /\.txt$/i,
  use: 'raw-loader',
};

addAfterLoader(webpackConfig, loaderByName('source-map-loader'), txtLoader);
lotorvik commented 1 year ago

Have anyone found a fix for this? I have same issue just with ejs-loader instead of html-loader.

ajani2001 commented 1 year ago

Have anyone found a fix for this? I have same issue just with ejs-loader instead of html-loader.

I just inspected the stricture of my existing loader rules via console.dir and inserted the new loader manually in the right place. This is a temporary fix and it might be broken if you change the version of create-react-app or other packages.

This is the corresponding fragment of my config: (ofc you shouldn't remove resolve-url-loader, this is for my project)

configure: (webpackConfig) => {
    const htmlLoader = {
        test: /\.html$/,
        loader: require.resolve('html-loader'),
        exclude: [/node_modules/, /public/],
        options: { minimize: true, sources: false },
    };

    // FIXME AFTER THE ISSUE IS FIXED
    // https://github.com/dilanx/craco/issues/486
    (webpackConfig.module.rules[0] as any).oneOf.splice(0, 0, htmlLoader);
    removeLoaders(webpackConfig, loaderByName('resolve-url-loader'));

    return webpackConfig;
}
dotamir commented 1 year ago

I could fix this issue by adding the loader before another loader instead of file-loader|raw-loader like babel-loader. It seems that issue is adding before or after file-loader and raw-loader but adding before/after babel-loader working well.

configure: (webpackConfig) => {
    webpackConfig.resolve.extensions.push('.graphql', '.gql');

    const graphqlLoader = {
      test: /\.(graphql|gql)$/,
      exclude: /node_modules/,
      loader: require.resolve('graphql-tag/loader'),
    }

    addBeforeLoader(webpackConfig, loaderByName('babel-loader'), graphqlLoader);

    return webpackConfig;
}
AlexanderBaumgertner commented 1 year ago

@dotamir your solution works for men thank you!

imdanteking commented 1 year ago

@dotamir Thank you! It works!