关于style-loader
和css-loader
的一些SO帖子,但是尽管如此,我仍无法找到解决问题的方法。
简而言之,当我将@import
个css
文件放在其他css
个文件中,并且导入的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');
}
错误:
./src/main.scss中的ERROR (./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
我尝试过的事情:
convertToAbsoluteUrls
标志我的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
}
};
};
答案 0 :(得分:2)
我能够自己解决问题。如果将来有可能对其他人有帮助,请在下面找到解决方案。
postcss-loader
和postcss-import
插件和css-loader
,请关闭/删除postcss-import
插件。您不需要使用多个工具来解析@import
规则。如果装入程序的顺序正确,这并不是真正的问题,但您最好将其删除。由于Sass / libsass不提供URL重写,因此所有链接的资产 必须相对于输出。
如果仅生成CSS而不将其传递给css-loader,则它必须相对于您的Web根目录。
如果将生成的CSS传递给css-loader,则所有网址都必须相对于条目文件(例如main.scss)。
第二个问题很可能会打扰您。很自然地期望相对引用将针对在其中指定的.scss文件进行解析(就像在常规.css文件中一样)。幸运的是,有两种解决方案可以解决此问题:
使用resolve-url-loader添加缺少的url重写。将其放在加载程序链中的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 }
}
]
},
附注
请记住,在package.json
中包括以下副作用,因此在生产模式下发生的摇树操作不会删除提取的css
“ sideEffects”:[ “ .css”, “ .scss” ]
答案 1 :(得分:1)
您可以关闭url()
规则的处理。我不知道为什么这是默认行为。
{
loader: 'css-loader',
options: {
...
url: false,
}
},
答案 2 :(得分:0)
我花了大约5天的时间来了解这个webpack的工作原理。老实说,我可以说这是我真的不明白为什么它们现在是“事实”工具的事情之一。我不明白让配置文件按其应有的工作到底有多困难,在gulp中我花了1个小时来做同样的事情。
我的问题是所有url()规则(包括字体和图像)都由css-loader作为[对象模块]加载,并且它们由文件加载器导出但从未加载,所以如果我添加?url对于css-loader = false,它从不复制文件并导出它们。我必须说这完全是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
}),
]
};