简体   繁体   English

Webpack style-loader / css-loader: url() path resolution not working

[英]Webpack style-loader / css-loader: url() path resolution not working

There are a few SO posts about style-loader and css-loader , but despite this I have not been able to find a solution to my problem.有一些关于style-loadercss-loader的 SO 帖子,但尽管如此,我仍然无法找到解决问题的方法。

In short summary, when I @import css files in other css files, and the imported css contains url() s with relative paths, the paths are not resolved correctly.简而言之,当我在其他css 8CBA22E28EB17B5F5C6AE2A266AZ 文件中@import css 8CBA22E28EB17B5F5C6AE2A266AZ 文件,并且导入的css包含具有相对路径的url()时,路径无法正确解析。

Basically, the error message shows that Webpack ends up thinking the url() paths in the imported css are relative to src (main entry point), rather than being relative to the css file it it is imported into:基本上,错误消息显示 Webpack 最终认为导入的 css 中的url()路径是相对于src (主入口点),而不是相对于css它被导入到文件中:

// css-one.scss
@import "./assets/open-iconic-master/font/css/open-iconic-bootstrap.css";

// open-iconic-bootstrap.css
@font-face {
    src: url('../fonts/open-iconic.eot');
}

Error:错误:

ERROR in./src/main.scss (./node_modules/css-loader??ref--5-1.?/node_modules/postcss-loader/src?.ref--5-2.?/node_modules/sass-loader/lib/loader?js..ref--5-3!./src/main.scss)错误 in./src/main.scss (./node_modules/css-loader??ref--5-1.?/node_modules/postcss-loader/src?.ref--5-2.?/node_modules/sass- loader/lib/loader?js..ref--5-3!./src/main.scss)

Module not found: Error: Can't resolve '../fonts/open-iconic.eot' in 'C:\Users\...\src' @./src/main.scss (./node_modules/css-loader??ref--5-1.?/node_modules/postcss-loader/src?.ref--5-2.?/node_modules/sass-loader/lib/loader?js..ref--5-3::/src/main.scss) 7.106-141 7.172-207 @./src/main.scss @ ./src/index.js未找到模块:错误:无法解析 ' C:\Users\...\src' @./src/main.scss (./node_modules/css- loader??ref--5-1.?/node_modules/postcss-loader/src?.ref--5-2.?/node_modules/sass-loader/lib/loader?js..ref--5-3: :/src/main.scss) 7.106-141 7.172-207 @./src/main.scss @ ./src/index.js

What I Have Tried:我试过的:

My Webpack Config File (loaders are at the bottom):我的 Webpack 配置文件(加载器在底部):

 const path = require('path'); const webpack = require('webpack'); // for webpack built-in plugins const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin'); // const WriteFilePlugin = require('write-file-webpack-plugin'); // const ManifestPlugin = require('webpack-manifest-plugin'); // const InlineManifestWebpackPlugin = require('inline-manifest-webpack-plugin'); // const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const PATHS = { // when using __dirname, resolve and join gives same result, // because __dirname is absolute path to directory of this file. // OK to use no slashes, // both resolve and join adds platform-specific separators by default src: path.resolve(__dirname, 'src'), dist: path.resolve(__dirname, 'dist'), build: path.resolve(__dirname, 'build'), test: path.resolve(__dirname, 'test') }; const NAMES = { // JS FILES index: 'index', print: 'print', // Chrome Extension Development popup: 'popup', options: 'options', background: 'background', contentScript: 'contentScript', // FOLDERS assets: 'assets', utilities: 'utilities', images: 'images', fonts: 'fonts', include: 'include' }; const FILE_PATHS = { // JS indexJs: `${path.join(PATHS.src, NAMES.index)}.js`, printJs: `${path.join(PATHS.src, NAMES.print)}.js`, // Chrome Extension Development popupJs: `${path.join(PATHS.src, NAMES.popup)}.js`, optionsJs: `${path.join(PATHS.src, NAMES.options)}.js`, backgroundJs: `${path.join(PATHS.src, NAMES.background)}.js`, contentScriptJs: `${path.join( PATHS.src, NAMES.include, NAMES.contentScript )}.js`, // HTML indexHtml: `${path.join(PATHS.src, NAMES.index)}.html`, printHtml: `${path.join(PATHS.src, NAMES.print)}.html`, // Chrome Extension Development popupHtml: `${path.join(PATHS.src, NAMES.popup)}.html`, optionsHtml: `${path.join(PATHS.src, NAMES.options)}.html`, backgroundHtml: `${path.join(PATHS.src, NAMES.background)}.html` }; // Third-party (vendor) libraries to include // const VENDORS = ['react', 'bootstrap', 'lodash', 'jQuery']; // Relative paths to node_modules // Note: These are relative const ASSETS = { images: path.join(NAMES.assets, NAMES.images), fonts: path.join(NAMES.assets, NAMES.fonts) }; // CleanWebpackPlugin config const pathsToClean = [PATHS.dist, PATHS.build]; const cleanOptions = { root: __dirname, exclude: ['shared.js'], verbose: true, dry: false }; // CopyWebpackPlugin config const copyPattern = [ // { // from: NAMES.assets, // to: NAMES.assets // }, // { // from: path.join(NAMES.include, 'contentScript.css') // }, // { // from: 'manifest.json', // transform(content, copyPath) { // // generates the manifest file using the package.json informations // return Buffer.from( // JSON.stringify({ //...JSON.parse(content.toString()) // // description: env.npm_package_description, // // version: env.npm_package_version // }) // ); // } // } ]; const copyOptions = { // ignore: ['*.js'], context: PATHS.src }; module.exports = (env = {}) => { // webpack injects env variable, into webpack config. // perfect to check for production. // remember to specify --env.production in command // (if in production mode). const isProduction = env.production === true; return { entry: { index: FILE_PATHS.indexJs // Chrome Extension Development // popup: FILE_PATHS.popupJs, // contentScript: FILE_PATHS.contentScriptJs // options: FILE_PATHS.optionsJs, // background: FILE_PATHS.backgroundJs, // vendor: VENDORS }, mode: isProduction? 'production': 'development', devtool: isProduction? 'source-map': 'inline-source-map', optimization: { splitChunks: { chunks: 'all' } }, output: { filename: isProduction? '[name].[chunkhash:8].js': '[name].js', // chunkFilename determine name of non-entry chunk files, // for example dynamic imports in the app chunkFilename: isProduction? '[name].[chunkhash:8].js': '[name].js', path: PATHS.dist }, plugins: [ // new webpack.SourceMapDevToolPlugin({ // filename: '[file].map', // exclude: ['vendor', 'runtime'] // }), new webpack.DefinePlugin({ // specifies environment variable for dependencies. // does not apply to browser runtime environment // (process.env is provisioned by Node) 'process.env.NODE_ENV': isProduction? JSON.stringify('production'): JSON.stringify('development') }), // new BundleAnalyzerPlugin(), new CleanWebpackPlugin(pathsToClean, cleanOptions), new MiniCssExtractPlugin({ // Options similar to the same options in webpackOptions.output // both options are optional // does not work with Hot Module Replacement (HMR) // allows HMR in development (will only use this plugin in production) filename: isProduction? '[name].[contenthash].css': '[name].css', chunkFilename: isProduction? '[id].[contenthash].css': '[id].css' }), new webpack.HashedModuleIdsPlugin(), isProduction? new UglifyJSPlugin({ cache: true, parallel: true, sourceMap: true // set to true if you want JS source maps }): () => {}, new CopyWebpackPlugin(copyPattern, copyOptions), // new WriteFilePlugin(), new HtmlWebpackPlugin({ template: FILE_PATHS.indexHtml, filename: `${NAMES.index}.html` }) // new HtmlWebpackPlugin({ // template: FILE_PATHS.popupHtml, // filename: `${NAMES.popup}.html`, // excludeChunks: [NAMES.contentScript] // In dev mode, chunks excluded vendor chunk (which holds CSS). // Above check fixes it. // }), // new HtmlWebpackPlugin({ // filename: `${NAMES.contentScript}.html`, // excludeChunks: [NAMES.popup, 'runtime'] // Runtime only needed in one HTML // }), // new HtmlWebpackPlugin({ // template: FILE_PATHS.optionsHtml, // filename: `${NAMES.options}.html`, // chunks: isProduction? [NAMES.options]: '' // }), // new HtmlWebpackPlugin({ // template: FILE_PATHS.backgroundHtml, // filename: `${NAMES.background}.html`, // chunks: isProduction? [NAMES.background]: '' // }), // no need for CSS minimization here <-- Done by PostCSS (cssnano) // new InlineManifestWebpackPlugin(), // new ManifestPlugin({fileName: 'webpack-manifest.json'}), ], module: { rules: [{ test: /\.js$/, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } }, { test: /\.s?[ac]ss$/, exclude: /node_modules/, use: [ isProduction? MiniCssExtractPlugin.loader: { // creates style nodes from JS strings loader: 'style-loader', options: { sourceMap: true, convertToAbsoluteUrls: true } }, { // CSS to CommonJS (resolves CSS imports into exported CSS strings) loader: 'css-loader', options: { sourceMap: true, importLoaders: 2 } }, { loader: 'postcss-loader', options: { config: { ctx: { cssnext: {}, cssnano: {}, autoprefixer: {} } }, sourceMap: true } }, { // compiles Sass to CSS loader: 'sass-loader', options: { sourceMap: true } } ] }, { test: /\.(png|svg|jpg|gif)$/, use: [{ loader: 'file-loader', options: { name: '[name].[hash:4].[ext]', outputPath: ASSETS.images } }] }, { test: /\.(woff|woff2|eot|ttf|otf)$/, use: [{ loader: 'file-loader', options: { name: '[name].[hash:4].[ext]', outputPath: ASSETS.fonts } }] }, { test: /\.(csv|tsv)$/, use: ['csv-loader'] }, { test: /\.xml$/, use: ['xml-loader'] }, { test: /\.(html)$/, use: { loader: 'html-loader', options: { interpolate: 'require', minimize: true } } } // { // test: /\.tsx?$/, // exclude: /(node_modules|bower_components)/, // use: 'ts-loader' // } ] }, devServer: { // contentBase: path.join(__dirname, 'dist'), contentBase: PATHS.dist, compress: false, port: 8080, open: false } }; };

it took me around 5 days of work to understand how this webpack mess works.我花了大约 5 天的时间来理解这个 webpack 混乱是如何工作的。 I have to be honest I can say that this is one of those things that I really do not understand why they are "defacto" tools of the moment.老实说,我可以说这是我真的不明白为什么它们是目前“事实上的”工具之一。 I can't understand how difficult it can be just to make the config files work as it should, in gulp took me 1 hour to do the same.我无法理解仅仅让配置文件正常工作有多困难,在 gulp 中我花了 1 小时来做同样的事情。

My problem was that all the url() rules (including fonts and images) were being loaded by css-loader as [object Module], and they where exported by file-loader but never loaded, so if I added ?url=false to the css-loader it never copied the files and export them.我的问题是所有 url() 规则(包括字体和图像)都由 css-loader 作为 [object Module] 加载,并且它们由 file-loader 导出但从未加载,所以如果我添加 ?url=false 到css-loader 它从不复制文件并导出它们。 I have to say this was a totally PITA, but I got it working, and I hope it works for somebody else in the world, this was made with webpack 4.我不得不说这完全是一个 PITA,但我让它工作了,我希望它适用于世界上的其他人,这是用 webpack 4 制作的。

const webpack = require("webpack");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const ImageminPlugin = require('imagemin-webpack-plugin').default;
const CopyPlugin = require('copy-webpack-plugin');
module.exports = {
    entry: "./src/index.js",
    mode: "development",
    module: {
        rules: [
        {
            test: /\.(js|jsx)$/,
            exclude: /(node_modules|bower_components)/,
            loader: "babel-loader",
            options: { presets: ["@babel/env"] }
        },
        {
            test: /\.(gif|png|jpe?g|svg)$/i,
            use: [
            {
                loader: 'image-webpack-loader',
                options: {
                    mozjpeg: {
                        progressive: true,
                        quality: 65
                    },

                    optipng: {
                        enabled: false,
                    },
                    pngquant: {
                        quality: [0.65, 0.90],
                        speed: 4
                    },
                    gifsicle: {
                        interlaced: false,
                    },

                    webp: {
                        quality: 75
                    },
                }
            },
            {
                loader: 'file-loader',
                options:{
                    name: '[name].[ext]',
                    outputPath: 'images/',
                    publicPath: 'images/'
                }
            },
            'url-loader?limit=100000'
            ],
        },
        {
            test: /\.(woff(2)?|ttf|eot)(\?v=\d+\.\d+\.\d+)?$/,
            use: [
            {
                loader: 'file-loader',
                options: {
                    name: '[name].[ext]',
                    outputPath: 'fonts/'
                }
            }
            ]
        },
        {
            test: /\.s[ac]ss$/i,
            use: [
            MiniCssExtractPlugin.loader,
            { loader: 'css-loader?url=false'},
            { loader: 'sass-loader', options: { sourceMap: true } }
            ],
        },
        ]
    },
    resolve: { extensions: ["*", ".js", ".jsx"] },
    output: {
        path: path.resolve(__dirname, "dist/"),
        publicPath: "",
        filename: "bundle.js"
    },
    devServer: {
        contentBase: path.join(__dirname, "dist/"),
        port: 3000,
        publicPath: "http://localhost:3000/dist/",
        hotOnly: true
    },
    plugins: [ new MiniCssExtractPlugin(),
    new CopyPlugin([{ from: 'src/images/', to: 'images/' }]),
    new CopyPlugin([{ from: 'src/fonts/', to: 'fonts/' }]),
    new ImageminPlugin({ test: /\.(jpe?g|png|gif|svg)$/i }),
    new HtmlWebpackPlugin({
        hash: true,
        template: './src/index.html',
            filename: './index.html' //relative to root of the application
        }),
    ]
};

I was able to solve the problem myself.我能够自己解决问题。 In case it could help others in the future, please find the solution below.如果将来可以帮助其他人,请在下面找到解决方案。


  1. First of all, if you are using both postcss-loader with the postcss-import plugin, AND css-loader , turn off / delete the postcss-import plugin.首先,如果您同时使用postcss-loaderpostcss-import插件,并且css-loader ,请关闭/删除postcss-import插件。 You do not need more than one tool that resolves @import rules.您不需要多个工具来解析@import规则。 This is not really a problem if the order of loaders is correct, but you might as well remove it.如果加载程序的顺序正确,这并不是真正的问题,但您不妨将其删除。
  2. In the sass-loader docs, you can read the following :sass-loader文档中,您可以阅读以下内容

Since Sass/libsass does not provide url rewriting, all linked assets must be relative to the output.由于 Sass/libsass 不提供 url 重写,因此所有链接的资产都必须与输出相关。

  • If you're just generating CSS without passing it to the css-loader, it must be relative to your web root.如果您只是生成 CSS 而不将其传递给 css-loader,则它必须与您的 Web 根目录相关。

  • If you pass the generated CSS on to the css-loader, all urls must be relative to the entry-file (eg main.scss).如果您将生成的 CSS 传递给 css-loader,则所有 url 都必须相对于入口文件(例如 main.scss)。

More likely you will be disrupted by this second issue.您更有可能被第二个问题打扰。 It is natural to expect relative references to be resolved against the .scss file in which they are specified (like in regular .css files).很自然地期望相对引用针对指定它们的 .scss 文件(如在常规 .css 文件中)进行解析。 Thankfully there are two solutions to this problem:幸运的是,这个问题有两种解决方案:

  • Add the missing url rewriting using the resolve-url-loader.使用resolve-url-loader 添加丢失的url 重写。 Place it before the sass-loader in the loader chain.将它放在 loader 链中的 sass-loader 之前。

  • Library authors usually provide a variable to modify the asset path.库作者通常提供一个变量来修改资产路径。 bootstrap-sass for example has an $icon-font-path.例如 bootstrap-sass 有一个 $icon-font-path。 Check out this working bootstrap example.查看这个工作引导程序示例。

I decided to follow bullet two, and add in resolve-url-loader above sass-loader in the Webpack config.我决定遵循第二点,并在Webpack配置中的sass-loader上方添加resolve-url-loader It now works as expected.它现在按预期工作。

My final Webpack config (for now) looks like this:我的最终 Webpack 配置(目前)如下所示:

    {
      test: /\.s?[ac]ss$/,
      exclude: /node_modules/,
      use: [
        isProduction
          ? MiniCssExtractPlugin.loader
          : {
              // creates style nodes from JS strings
              loader: 'style-loader',
              options: {
                sourceMap: true,
                // convertToAbsoluteUrls: true
              }
            },
        {
          // CSS to CommonJS (resolves CSS imports into exported CSS strings)
          loader: 'css-loader',
          options: {
            sourceMap: true,
            importLoaders: 2
            // url: false,
            // import: false
          }
        },
        {
          loader: 'postcss-loader',
          options: {
            config: {
              ctx: {
                cssnext: {},
                cssnano: {},
                autoprefixer: {}
              }
            },
            sourceMap: true
          }
        },
        {
          loader: 'resolve-url-loader',
          options: {
            attempts: 1,
            sourceMap: true
          }
        },
        {
          // compiles Sass to CSS
          loader: 'sass-loader',
          options: { sourceMap: true }
        }
      ]
    },

Side Notes旁注

  1. I noticed that source map paths under "no domain" in Chrome's debugger are repeated.我注意到 Chrome 调试器中“无域”下的源映射路径是重复的。 If anyone figures out why, please do share如果有人知道原因,请分享
  2. Remember to include the below side effects in package.json , so tree shaking, which happens in production mode, does not delete the extracted css请记住在package.json包含以下副作用,因此在生产模式下发生的摇树不会删除提取的 css

    "sideEffects": [ " .css", " .scss" ], "sideEffects": [ " .css", " .scss"],

You can turn off processing of url() rules, btw.您可以关闭对url()规则的处理,顺便说一句。 I have no idea, why this is a default behavior.我不知道为什么这是默认行为。

{
  loader: 'css-loader',
  options: {
    ...
    url: false,
  }
},

If anyone is struggling with this using Webpack 5 and is trying to upgrade from css-loader 5 to css-loader 6, you might need to check this issue where the poster has a similar problem to the OP:如果有人在使用 Webpack 5 时遇到此问题并尝试从 css-loader 5 升级到 css-loader 6,您可能需要检查此问题,其中海报与 OP 存在类似问题:

With css-loader 5.2.7 the images in the input stylus were embedded as data- URL in the output CSS.在 css-loader 5.2.7 中,输入手写笔中的图像作为 data-URL 嵌入到输出 CSS 中。 With css-loader 6, the images are instead moved to the output directory.使用 css-loader 6,图像被移动到输出目录。

See Notes here for how to upgrade to css-loader 6 - the salient points are:有关如何升级到 css-loader 6 的说明,请参阅此处的说明 - 要点是:

using ~ is deprecated when the esModules option is enabled ... and can be removed from your code当启用 esModules 选项时,不推荐使用 ~ 并且可以从您的代码中删除

and

file-loader and url-loader are deprecated, please migrate on asset modules, since v6 css-loader is generating new URL(...) syntax, it enables by default built-in assets modules, ie type: 'asset' for all url() file-loader 和 url-loader 已弃用,请迁移资产模块,因为 v6 css-loader 正在生成新的 URL(...) 语法,它默认启用内置资产模块,即 type: 'asset' for all网址()


I've therefore done the following:因此,我做了以下工作:

  1. removed any '~' in my .scss files删除了我的 .scss 文件中的任何“~”

ie IE

$font-path: "~/src/fonts" !default;

becomes变成

$font-path: "/src/fonts" !default;
  1. I've also removed the 'file-loader' module completely.我还完全删除了“文件加载器”模块。

All NPM packages are now up to date and URLs in CSS are working correctly.所有 NPM 包现在都是最新的,CSS 中的 URL 工作正常。

IE 与新 URL() 不兼容

My issue was that webpack 5 changed everything with "Asset modules" apparently and broke file-loader.我的问题是 webpack 5 显然用“资产模块”改变了一切并破坏了文件加载器。

Official docs explain it well: https://webpack.js.org/guides/asset-modules/官方文档解释得很好: https://webpack.js.org/guides/asset-modules/

TL;DR you can fix it by adding type: 'javascript/auto' to the rule/loader TL;DR,您可以通过将type: 'javascript/auto'添加到规则/加载器来修复它

            {
                test: /\.(eot|ttf|woff2?)($|\?)/i,
                loader: 'file-loader',
                options: {
                    name: '[name]-[sha1:hash:hex:10].[ext]',
                    esModule: false,
                },
                type: 'javascript/auto'
            },

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM