Webpack样式加载器/CSS加载器:url()路径解析不起作用 [英] Webpack style-loader / css-loader: url() path resolution not working

查看:52
本文介绍了Webpack样式加载器/CSS加载器:url()路径解析不起作用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

关于 style-loader css-loader 的一些SO帖子,但是尽管如此,我仍无法找到解决问题的方法.

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.

简而言之,当我在其他 css 文件中的 @import css 文件以及导入的 css 包含具有相对路径的 url() s,路径无法正确解析.

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.

基本上,错误消息表明Webpack最终认为导入的CSS中的 url()路径是相对于 src (主入口点)的,而不是相对于它导入到的 css 文件:

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:

// 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');
}

错误:

./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)

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)

未找到模块:错误:无法在其中解析'../fonts/open-iconic.eot''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

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

我尝试过的事情:

  • 我尝试在样式加载器中使用 convertToAbsoluteUrls 标志
  • 我试图关闭所有源地图(在style-loader文档中提到的 )

我的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
    }
  };
};

推荐答案

我自己能够解决问题.如果将来有可能对其他人有帮助,请在下面找到解决方案.

I was able to solve the problem myself. In case it could help others in the future, please find the solution below.

  1. 首先,如果同时使用 postcss-loader postcss-import 插件和 css-loader /删除 postcss-import 插件.您不需要多个工具来解析 @import 规则.如果装入程序的顺序正确,这并不是真正的问题,但您最好将其删除.
  2. sass-loader 文档中,您可以
  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. You do not need more than one tool that resolves @import rules. 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/libsass不提供url重写,因此所有链接的资产必须相对于输出.

Since Sass/libsass does not provide url rewriting, all linked assets must be relative to the output.

  • 如果仅生成CSS而不将其传递给css-loader,则它必须相对于您的Web根目录.

  • If you're just generating CSS without passing it to the css-loader, it must be relative to your web root.

如果将生成的CSS传递给css-loader,则所有URL必须相对于入口文件(例如main.scss).

If you pass the generated CSS on to the css-loader, all urls must be relative to the entry-file (e.g. main.scss).

第二个问题很可能会打扰您.很自然地期望相对引用将针对在其中指定的.scss文件进行解析(就像在常规.css文件中一样).幸运的是,有两种解决方案可以解决此问题:

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). Thankfully there are two solutions to this problem:

  • 使用resolve-url-loader添加缺少的url重写.将其放在加载程序链中的sass-loader之前.

  • Add the missing url rewriting using the resolve-url-loader. Place it before the sass-loader in the loader chain.

图书馆作者通常提供一个变量来修改资产路径.例如,bootstrap-sass具有$ icon-font-path.看看这个工作的引导示例.

Library authors usually provide a variable to modify the asset path. bootstrap-sass for example has an $icon-font-path. Check out this working bootstrap example.

我决定遵循第二点,并在 Webpack 配置中的 sass-loader 上方添加 resolve-url-loader .现在可以正常工作了.

I decided to follow bullet two, and add in resolve-url-loader above sass-loader in the Webpack config. It now works as expected.

我的最终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 }
        }
      ]
    },

附带说明

  1. 我注意到Chrome调试器中无域"下的源映射路径是重复的.如果有人知道原因,请分享
  2. 请记住在 package.json 中包含以下副作用,因此在生产模式下发生的树抖动不会删除提取的css

  1. I noticed that source map paths under "no domain" in Chrome's debugger are repeated. 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

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

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

这篇关于Webpack样式加载器/CSS加载器:url()路径解析不起作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆