Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

webpack url-loader seems not working when handling the images files imported using url() CSS function #4122

Closed
tianzhich opened this issue Nov 20, 2020 · 19 comments
Labels
outdated scope: react Issues related to React support for Nx type: bug

Comments

@tianzhich
Copy link

Current Behavior

I am using the NX with react, webpack used for building my app. But I found that the url-loader configured in @nrwl/react/plugin/webpack.js not working when images imported using url() CSS function.

But the assets imported by this seem handled by file-loader(i am not sure, just see them in dist), but the hash in the filename is greater than 7 digits, and it smaller than 10000bytes which should have been handled by url-loader.

BTW, the files imported in my .ts file handled by url-loader properly.

The url-loader config:

{
  "rules": [
    {
      "test": /\.(png|jpe?g|gif|webp)$/,
      "loader": require.resolve("url-loader"),
      "options": {
        "limit": 10000,
        "name": "[name].[hash:7].[ext]"
      }
    },
    {
      "test": /\.svg$/,
      "oneOf": [
        // If coming from JS/TS file, then transform into React component using SVGR.
        {
          "issuer": {
            "test": /\.[jt]sx?$/
          },
          "use": [
            "@svgr/webpack?-svgo,+titleProp,+ref![path]",
            {
              "loader": require.resolve("url-loader"),
              "options": {
                "limit": 10000,
                "name": "[name].[hash:7].[ext]",
                "esModule": false
              }
            }
          ]
        },
        // Fallback to plain URL loader.
        {
          "use": [
            {
              "loader": require.resolve("url-loader"),
              "options": {
                "limit": 10000,
                "name": "[name].[hash:7].[ext]"
              }
            }
          ]
        }
      ]
    }
  ]
}

Expected Behavior

These assets should handled by url-loader if the size is smaller than 10000 bytes, otherwise should be handled by file-loader which outputs the file, contain 7 digits hash in the file name.

Steps to Reproduce

You can add a background image in nx-emaples and run nx build --prod. You will see the result.

Failure Logs

Environment

nx : Not Found
@nrwl/angular : 10.3.1-beta.1
@nrwl/cli : 10.3.1-beta.1
@nrwl/cypress : 10.3.1-beta.1
@nrwl/eslint-plugin-nx : Not Found
@nrwl/express : Not Found
@nrwl/jest : 10.3.1-beta.1
@nrwl/linter : 10.3.1-beta.1
@nrwl/nest : Not Found
@nrwl/next : Not Found
@nrwl/node : Not Found
@nrwl/react : 10.3.1-beta.1
@nrwl/schematics : Not Found
@nrwl/tao : 10.3.1-beta.1
@nrwl/web : 10.3.1-beta.1
@nrwl/workspace : 10.3.1-beta.1
typescript : 4.0.3

@tianzhich
Copy link
Author

We lost the css-loader after postcss-loader:

{
  "exclude": [
    "/Users/tianzhi/dev/nx-examples/libs/shared/styles/src/index.scss",
    "/Users/tianzhi/dev/nx-examples/libs/shared/header/index.scss",
    "/Users/tianzhi/dev/nx-examples/node_modules/normalize.css/normalize.css"
  ],
  "test": "/\\.scss$|\\.sass$/",
  "use": [
    {
      "loader": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/node_modules/style-loader/dist/index.js"
    },
    {
      "loader": "/Users/tianzhi/dev/nx-examples/node_modules/postcss-loader/src/index.js",
      "options": { "ident": "embedded", "sourceMap": false }
    },
    {
      "loader": "/Users/tianzhi/dev/nx-examples/node_modules/@nrwl/web/node_modules/sass-loader/dist/cjs.js",
      "options": {
        "implementation": {
          "info": "dart-sass\t1.26.10\t(Sass Compiler)\t[Dart]\ndart2js\t2.8.4\t(Dart Compiler)\t[Dart]",
          "types": {},
          "NULL": {},
          "TRUE": { "value": true },
          "FALSE": { "value": false }
        },
        "sourceMap": false,
        "sassOptions": { "precision": 8, "includePaths": [] }
      }
    }
  ]
},

And we need css-loader to transform the url() to require(). So the url-loader can do the following works.

But there was still another issue which I mentioned here: webpack-contrib/postcss-loader#500. If I just add css-loader after postcss-loader, the postcss-loader will transform the path and it will relative to the root. Then the css-loader can't resolve them.

@tianzhich
Copy link
Author

I asked postcss-loader member they said it doesn't touch the path of url(). I couldn't figure out what touch this. Are there any plugins or something touch and modify this?

@vsavkin vsavkin added the scope: react Issues related to React support for Nx label Nov 20, 2020
@tianzhich
Copy link
Author

tianzhich commented Nov 21, 2020

I found the reason but I don't know why. Can someone guide me?

In @nrwl/web buidler, you created a postcss plugin called postcss-cli-resources. It was used to interprets url(). The logic of how it interprets:

const process = async (
  inputUrl: string,
  context: string,
  resourceCache: Map<string, string>
) => {
  // If root-relative, absolute or protocol relative url, leave as is
  if (/^((?:\w+:)?\/\/|data:|chrome:|#)/.test(inputUrl)) {
    return inputUrl;
  }

  if (!rebaseRootRelative && /^\//.test(inputUrl)) {
    return inputUrl;
  }

  // If starts with a caret, remove and return remainder
  // this supports bypassing asset processing
  if (inputUrl.startsWith("^")) {
    return inputUrl.substr(1);
  }

  const cacheKey = path.resolve(context, inputUrl);
  const cachedUrl = resourceCache.get(cacheKey);
  if (cachedUrl) {
    return cachedUrl;
  }

  if (inputUrl.startsWith("~")) {
    inputUrl = inputUrl.substr(1);
  }

  if (inputUrl.startsWith("/")) {
    let outputUrl = "";
    if (deployUrl.match(/:\/\//) || deployUrl.startsWith("/")) {
      // If deployUrl is absolute or root relative, ignore baseHref & use deployUrl as is.
      outputUrl = `${deployUrl.replace(/\/$/, "")}${inputUrl}`;
    } else if (baseHref.match(/:\/\//)) {
      // If baseHref contains a scheme, include it as is.
      outputUrl =
        baseHref.replace(/\/$/, "") +
        dedupeSlashes(`/${deployUrl}/${inputUrl}`);
    } else {
      // Join together base-href, deploy-url and the original URL.
      outputUrl = dedupeSlashes(`/${baseHref}/${deployUrl}/${inputUrl}`);
    }

    resourceCache.set(cacheKey, outputUrl);

    return outputUrl;
  }

  const { pathname, hash, search } = url.parse(inputUrl.replace(/\\/g, "/"));
  const resolver = (file: string, base: string) =>
    new Promise<string>((resolve, reject) => {
      loader.resolve(base, decodeURI(file), (err, result) => {
        if (err) {
          reject(err);

          return;
        }
        resolve(result);
      });
    });

  const result = await resolve(pathname as string, context, resolver);

  return new Promise<string>((resolve, reject) => {
    loader.fs.readFile(result, (err: Error, content: Buffer) => {
      if (err) {
        reject(err);

        return;
      }

      let outputPath = interpolateName(
        { resourcePath: result } as webpack.loader.LoaderContext,
        filename,
        { content }
      );

      if (resourcesOutputPath) {
        outputPath = path.posix.join(resourcesOutputPath, outputPath);
      }

      loader.addDependency(result);
      loader.emitFile(outputPath, content, undefined);

      let outputUrl = outputPath.replace(/\\/g, "/");
      if (hash || search) {
        outputUrl = url.format({ pathname: outputUrl, hash, search });
      }

      if (
        deployUrl &&
        loader.loaders[loader.loaderIndex].options.ident !== "extracted"
      ) {
        outputUrl = url.resolve(deployUrl, outputUrl);
      }

      resourceCache.set(cacheKey, outputUrl);
      resolve(outputUrl);
    });
  });
};

So the postcss loader intercpets the url() and you also used the community plugin called postcss-import to intercepts @import so css-loader is not necessary. Well, it was fine.

But in @nrwl/react webpack.conf, url-loader added to resolve assets like images to base64 or handled by file-loader if size exceeded:

// Add React-specific configuration
function getWebpackConfig(config: Configuration) {
  config.module.rules.push(
    {
      test: /\.(png|jpe?g|gif|webp)$/,
      loader: 'url-loader',
      options: {
        limit: 10000, // 10kB
        name: '[name].[hash:7].[ext]',
      },
    },
    {
      test: /\.svg$/,
      oneOf: [
        // If coming from JS/TS file, then transform into React component using SVGR.
        {
          issuer: {
            test: /\.[jt]sx?$/,
          },
          use: [
            '@svgr/webpack?-svgo,+titleProp,+ref![path]',
            {
              loader: 'url-loader',
              options: {
                limit: 10000, // 10kB
                name: '[name].[hash:7].[ext]',
              },
            },
          ],
        },
        // Fallback to plain URL loader.
        {
          use: [
            {
              loader: 'url-loader',
              options: {
                limit: 10000, // 10kB
                name: '[name].[hash:7].[ext]',
              },
            },
          ],
        },
      ],
    }
  );

  return config;
}

So I think there are two problems with react users:

  1. I know css-loader just transform like url() to require() so the url-loader can load from it. But what you did in postcss-cli-resources was transform relative path in url(), for example: url('../assets/big.png') to url('./big.[hash:20].png') and this png was moved to dist root. It can't be handled by url-loader.

  2. And if I import a big asset twice, one in my .tsx, and another in my .scss. There will be 2 assets in the final dist. They are the same file but different filenames cause hash was different. So 2 network requests needed for them...

@tianzhich
Copy link
Author

Could NX put url-loader config in @nrwl/web instead of @nrwl/react? And change a little bit of the behavior of the postcss-cli-resources, for example from url() to resolve(). So we can have a consistent load process especially with assets(both can be handled by file-loader). Thanks a lot.

@R4VANG3R
Copy link

R4VANG3R commented Mar 1, 2021

Same issue for fonts loaded via url. They're not being copied over with builds and giving 404 errors during serve.
Also noticing the following error during builds (since at least 11.3.0). This might be related.

(node:1816) [DEP0148] DeprecationWarning: Use of deprecated folder mapping "./" in the "exports" field module resolution of the package at C:\Users\subfolders\node_modules\@nrwl\web\node_modules\postcss\package.json.
Update this package.json to use a subpath pattern like "./*".
(Use `node --trace-deprecation ...` to show where the warning was created)
You did not set any plugins, parser, or stringifier. Right now, PostCSS does nothing. Pick plugins for your case on https://www.postcss.parts/ and use them in postcss.config.js.

@jbojcic1
Copy link
Contributor

jbojcic1 commented Mar 17, 2021

I have the same issue. After nx upgrade from 9.2.2 to 11.4.0 I started getting bunch of the following messages in the prod build:

You did not set any plugins, parser, or stringifier. Right now, PostCSS does nothing. Pick plugins for your case on https://www.postcss.parts/ and use them in postcss.config.js.

Seems to be shown once for each scss file I imported. Also styles are much bigger in the resulting bundle now and my fonts are not copied over anymore to the dist.

@rexebin
Copy link

rexebin commented Mar 17, 2021

I have the same issue. After nx upgrade from 9.2.2 to 11.4.0 I started getting bunch of the following messages in the prod build:

You did not set any plugins, parser, or stringifier. Right now, PostCSS does nothing. Pick plugins for your case on https://www.postcss.parts/ and use them in postcss.config.js.

Seems to be shown once for each scss file I imported. Also styles are much bigger in the resulting bundle now and my fonts are not copied over anymore to the dist.

I too got three warnings with three .css imports.

@jeffgaynorspok
Copy link

Same issue upgrading from v11.2.12 to 11.5.1.

@thiagolinsx
Copy link

Same issue upgrading from 10.3.1 to 11.5.1

@rexebin
Copy link

rexebin commented Mar 19, 2021

I moved my css imports from App.tsx to workspace.json's styles, no more warnings.

@patrikniebur
Copy link

I moved my css imports from App.tsx to workspace.json's styles, no more warnings.

Thanks, this helped my use case. Font icons referenced in the css file I was using did not load. I was importing styles from semantic-ui and adding it into the workspace.json project styles like this fixed my problem:

"styles": ["node_modules/semantic-ui-css/semantic.min.css"],

@stckcrsh
Copy link

I am still running into this issue using 11.4.0 when i try to use an external font from google.

I have moved my css to the styles array under the build but it just ignores any absolute urls.

@shahnawaz
Copy link

Is there any update on this issue?

Unable to load fonts using url.

@github-actions
Copy link

This issue has been automatically marked as stale because it hasn't had any recent activity. It will be closed in 14 days if no further activity occurs.
If we missed this issue please reply to keep it active.
Thanks for being a part of the Nx community! 🙏

@github-actions github-actions bot added the stale label Mar 24, 2022
@idanlo
Copy link

idanlo commented Apr 5, 2022

Not stale

@github-actions github-actions bot removed the stale label Apr 6, 2022
@github-actions
Copy link

github-actions bot commented Oct 3, 2022

This issue has been automatically marked as stale because it hasn't had any recent activity. It will be closed in 14 days if no further activity occurs.
If we missed this issue please reply to keep it active.
Thanks for being a part of the Nx community! 🙏

@github-actions github-actions bot added the stale label Oct 3, 2022
@Genesys-AlexW
Copy link

Not stale

@jaysoo
Copy link
Member

jaysoo commented Feb 23, 2023

This is fixed as we are marking images as asset and letting Webpack handling inlining when over 10 kB.

https://github.com/nrwl/nx/blob/master/packages/webpack/src/utils/with-web.ts#L413-L422

@jaysoo jaysoo closed this as completed Feb 23, 2023
@github-actions
Copy link

This issue has been closed for more than 30 days. If this issue is still occuring, please open a new issue with more recent context.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 25, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
outdated scope: react Issues related to React support for Nx type: bug
Projects
None yet
Development

No branches or pull requests