簡體   English   中英

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

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

有一些關於style-loadercss-loader的 SO 帖子,但盡管如此,我仍然無法找到解決問題的方法。

簡而言之,當我在其他css 8CBA22E28EB17B5F5C6AE2A266AZ 文件中@import css 8CBA22E28EB17B5F5C6AE2A266AZ 文件,並且導入的css包含具有相對路徑的url()時,路徑無法正確解析。

基本上,錯誤消息顯示 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');
}

錯誤:

錯誤 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)

未找到模塊:錯誤:無法解析 ' 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

我試過的:

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

我花了大約 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
        }),
    ]
};

我能夠自己解決問題。 如果將來可以幫助其他人,請在下面找到解決方案。


  1. 首先,如果您同時使用postcss-loaderpostcss-import插件,並且css-loader ,請關閉/刪除postcss-import插件。 您不需要多個工具來解析@import規則。 如果加載程序的順序正確,這並不是真正的問題,但您不妨將其刪除。
  2. sass-loader文檔中,您可以閱讀以下內容

由於 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配置中的sass-loader上方添加resolve-url-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 }
        }
      ]
    },

旁注

  1. 我注意到 Chrome 調試器中“無域”下的源映射路徑是重復的。 如果有人知道原因,請分享
  2. 請記住在package.json包含以下副作用,因此在生產模式下發生的搖樹不會刪除提取的 css

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

您可以關閉對url()規則的處理,順便說一句。 我不知道為什么這是默認行為。

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

如果有人在使用 Webpack 5 時遇到此問題並嘗試從 css-loader 5 升級到 css-loader 6,您可能需要檢查此問題,其中海報與 OP 存在類似問題:

在 css-loader 5.2.7 中,輸入手寫筆中的圖像作為 data-URL 嵌入到輸出 CSS 中。 使用 css-loader 6,圖像被移動到輸出目錄。

有關如何升級到 css-loader 6 的說明,請參閱此處的說明 - 要點是:

當啟用 esModules 選項時,不推薦使用 ~ 並且可以從您的代碼中刪除

file-loader 和 url-loader 已棄用,請遷移資產模塊,因為 v6 css-loader 正在生成新的 URL(...) 語法,它默認啟用內置資產模塊,即 type: 'asset' for all網址()


因此,我做了以下工作:

  1. 刪除了我的 .scss 文件中的任何“~”

IE

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

變成

$font-path: "/src/fonts" !default;
  1. 我還完全刪除了“文件加載器”模塊。

所有 NPM 包現在都是最新的,CSS 中的 URL 工作正常。

IE 與新 URL() 不兼容

我的問題是 webpack 5 顯然用“資產模塊”改變了一切並破壞了文件加載器。

官方文檔解釋得很好: https://webpack.js.org/guides/asset-modules/

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