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-loader
和 css-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-loader
和postcss-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() 路径解析不起作用
[js高手之路]深入浅出webpack教程系列7-( babel-loader,css-loader,style-loader)的用法