Webpack style-loader / css-loader: url() 路径解析不起作用

Posted

技术标签:

【中文标题】Webpack style-loader / css-loader: url() 路径解析不起作用【英文标题】:Webpack style-loader / css-loader: url() path resolution not working 【发布时间】:2019-02-12 14:55:53 【问题描述】:

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

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

基本上,错误消息显示 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');

错误:

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

我的尝试:

我尝试在 style-loader 中使用 convertToAbsoluteUrls 标志 我已尝试关闭所有源映射 (mentioned in style-loader docs)

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

【问题讨论】:

【参考方案1】:

我花了大约 5 天的时间来理解这个 webpack 混乱的工作原理。老实说,我可以说这是我真的不明白为什么它们是当下“事实上的”工具的事情之一。我无法理解仅仅让配置文件按应有的方式工作是多么困难,在 gulp 中我花了 1 个小时来做​​同样的事情。

我的问题是所有 url() 规则(包括字体和图像)都由 css-loader 作为 [object Module] 加载,并且它们由 file-loader 导出但从未加载,所以如果我添加 ?url =false 到 css-loader 它从不复制文件并导出它们。我不得不说这完全是一个 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
        ),
    ]
;

【讨论】:

我完全同意你的看法。从 Gulp 迁移到 Webpack 令人心痛。【参考方案2】:

顺便说一句,您可以关闭url() 规则的处理。我不知道,为什么这是默认行为。


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

【讨论】:

这只有在我重新启动 webpack 后才对我有用。 最简单的解决方案恕我直言 你是天赐之物! 发现这在将 Elixir 与 Phoenix 框架一起使用时很有帮助,因为它默认与 webpack 和 css-loader 捆绑在一起。升级 css-loader 后遇到文件路径问题,最简单的解决方案是如上所述关闭处理。 适用于 webpack5。谢谢!【参考方案3】:

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


    首先,如果您同时使用postcss-loaderpostcss-import 插件和css-loader,请关闭/删除postcss-import 插件。您不需要一个以上的工具来解析@import 规则。如果加载程序的顺序正确,这不是问题,但您不妨将其删除。 在sass-loader 文档中,您可以read the following:

由于 Sass/libsass 不提供 url 重写,所有链接的资产 必须相对于输出。

如果您只是生成 CSS 而不将其传递给 css-loader,则它必须与您的 Web 根目录相关。

如果将生成的 CSS 传递给 css-loader,则所有 url 都必须与入口文件相关(例如 main.scss)。

您很可能会被第二个问题打断。期望相对引用在指定它们的 .scss 文件中解析是很自然的(就像在常规 .css 文件中一样)。谢天谢地,这个问题有两种解决方案:

使用 resolve-url-loader 添加缺失的 url 重写。将其放在 loader 链中的 sass-loader 之前。

库作者通常提供一个变量来修改资产路径。例如 bootstrap-sass 有一个 $icon-font-path。查看这个有效的引导示例。

我决定按照第二条,在 Webpack 配置中添加resolve-url-loader 上方的sass-loader。它现在按预期工作。

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

旁注

    我注意到 Chrome 调试器中“无域”下的源映射路径重复。如果有人知道原因,请分享

    请记住在package.json 中包含以下副作用,因此在生产模式下发生的摇树不会删除提取的 css

    “副作用”:[ ".css", ".scss" ],

【讨论】:

【参考方案4】:

如果有人在使用 Webpack 5 时遇到此问题并尝试从 css-loader 5 升级到 css-loader 6,您可能需要检查 this issue,其中发布者与 OP 有类似问题:

使用 css-loader 5.2.7,输入笔中的图像被嵌入为数据- 输出 CSS 中的 URL。使用 css-loader 6,图像被移动 到输出目录。

请参阅注释here 了解如何升级到 css-loader 6 - 重点是:

在启用 esModules 选项时不推荐使用 ~ ......并且可以 从您的代码中删除

file-loader 和 url-loader 已弃用,请在资产上迁移 模块,因为 v6 css-loader 正在生成新的 URL(...) 语法,它 默认启用内置资产模块,即 type: 'asset' for all 网址()


因此,我做了以下事情:

    删除了我的 .scss 文件中的所有“~”

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

变成

$font-path: "/src/fonts" !default;
    我还完全删除了“文件加载器”模块。

所有 NPM 包现在都是最新的,并且 CSS 中的 URL 可以正常工作。

【讨论】:

【参考方案5】:

IE 与新 URL() 不兼容

【讨论】:

正如目前所写,您的答案尚不清楚。请edit 添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以找到更多关于如何写好答案的信息in the help center。

以上是关于Webpack style-loader / css-loader: url() 路径解析不起作用的主要内容,如果未能解决你的问题,请参考以下文章

为啥 webpack4 生产包总是包含 style-loader、css-loader 和 vue-loader 内容?

Webpack style-loader / css-loader: url() 路径解析不起作用

webpack 常见问题

[js高手之路]深入浅出webpack教程系列7-( babel-loader,css-loader,style-loader)的用法

webpack

webpack--loader