webpack - scss cannot resolve background-image url - javascript

In my scss file, I use background-image: url("../../../assets/images/home/banner-mobile.png");
The application runs successfully, but no background image is shown:
The background image URL is not resolved.
webpack/webpack.base.js
const webpack = require("webpack");
const path = require("path");
const utils = require("./utils");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/index.jsx",
resolve: {
alias: {
"#": utils.resolve("src")
},
extensions: ["*", ".js", ".jsx"],
fallback: {...},
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
},
},
{
test: /\.(sa|sc|c)ss$/,
use: [
{
loader: "style-loader",
},
{
loader: "css-loader",
},
{
loader: "sass-loader"
},
{
loader: "sass-resources-loader",
options: {
resources: ["./src/assets/scss/main.scss"],
},
},
],
},
{
test: /\.(png|jpg|gif)$/i,
use: [
{
loader: "url-loader",
options: {
limit: 8192
},
},
],
}
],
},
plugins: [
new HtmlWebpackPlugin({
template: "public/index.html",
filename: "index.html",
inject: true,
})
],
};
webpack/webpack.dev.js
const { merge } = require("webpack-merge");
const base = require("./webpack.base");
const Dotenv = require("dotenv-webpack");
module.exports = merge(base, {
mode: "development",
devtool: "inline-source-map",
output: {
publicPath: '/',
},
devServer: {
port: 3000,
static: true,
static: 'dist'
},
plugins: [new Dotenv({ path: "./.env.development" })],
});
Update 1
When I view the png in Web Inspector > Sources:
When I open the image with its URL in the browser:
Update 2:
When I build and view the image via VSCode, it shows below:
Not sure if the below file-is related
webpack/Util.js
const path = require('path')
module.exports = {
resolve: function(dir) {
return path.join(__dirname, '..', dir)
}
}

Since you're using Webpack 5, I'd recommend using Asset Modules instead of the deprecated loaders
module: {
rules: [
// ...
{
test: /\.(png|jpg|gif)$/i,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 8192
}
}
}
]
}
I suspect you were running into a resource handling duplication issue as noted in the documentation...
When using the old assets loaders (i.e. file-loader / url-loader / raw-loader) along with Asset Module in webpack 5, you might want to stop Asset Module from processing your assets again as that would result in asset duplication. This can be done by setting asset's module type to 'javascript/auto'.

I use file-loader as shown in this rule, it allow me to preserve the file name and relative path, so having my folder structure like assets > images, I just have to strip "assets" from the path:
{
test: /\.(gif|png|jpg|jpeg)$/,
loader: 'file-loader',
options: {
name: '[path][name].[ext]',
outputPath: (file) => {
const p = file.split('assets/')[1];
return p;
},
},
},
This will let you with all images within "assets" folder right into the root bundle and every path will be replicated (ex. assets/images/home/banner.png will be on images/home/banner.png in your dist directory), just be sure all your images are inside your assets folder to avoid getting name conflicts

Solution
After a long search, the only solution I found is to use ~ to reference the initial project directory and accessing public folder from there.
background-image: url("~/public/images/icon.png");
And this is working fine for me :)
OR And the other alternative would be to move images to the src directory, and this won't be a problem since webpack would pack them in a folder called static/media as static files

Related

Use public path of css image with webpack instead of copying over the image or using inline encoding

I am trying to set up webpack so I can have it with wordpress. I have a file structure like the following:
-assets/
-img
-dist/
-src/
-js
index.js
-scss
webpack.config.js
...etc
I have webpack configured to build into the dist file.
I want to be able to import background images into my css in the src file from the assets file which I can do. But then with webpack's assets module found here https://webpack.js.org/guides/asset-modules/
{
test: /\.(jpg|png|svg|gif)$/,
type: 'asset',
},
when it builds it either copys the image over with asset/resource so then I basically have 2 of the same images in the project. Or it encodes it directly into the css in base64 with assets/inline or source with assets/source. This all seems pointless in my case.
Is there a way to have webpack just use the relative path in the assets folder instead of duplicating the image or encoding it? So instead of compiling to something like:
body: {
background: url(/[hashed-from-webpack].jpg);
}
it just compiles to
body: {
background: url(../assets/img/myimage.jpg);
}
Here is the entire webpack config file:
const path = require('path')
const rootDir = path.join(__dirname, '')
const BrowserSyncPlugin = require('browser-sync-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
entry: {
app: path.join(rootDir, 'src/js', 'app'),
},
output: {
filename: '[name].js',
path: path.join(rootDir, 'dist'),
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.s[ac]ss$/i,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'sass-loader',
],
},
],
},
plugins: [
new MiniCssExtractPlugin(),
new BrowserSyncPlugin({
host: 'localhost',
port: 3000,
proxy: 'http://localhost/wordpress/',
files: ['**/*.php'],
}),
],
devServer: {
compress: true,
port: 3000,
open: true,
},
}

Webpack multiple entries under certain urls

Hey I have this project which has this structure:
bundler -> webpack.common.js
src -> main.js, index.html, styles.css
package.json
node_modules... the rest doesnt matter really
It is set up like when I do: npm run dev webpack creates a live server using my current main.js for my index.html
The webpack.common.js looks like this:
const CopyWebpackPlugin = require("copy-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCSSExtractPlugin = require("mini-css-extract-plugin");
const path = require("path");
module.exports = {
entry: path.resolve(__dirname, "../src/current_script.js"),
output: {
hashFunction: "xxhash64",
filename: "bundle.[contenthash].js",
path: path.resolve(__dirname, "../dist"),
},
devtool: "source-map",
plugins: [
new CopyWebpackPlugin({
patterns: [{ from: path.resolve(__dirname, "../static") }],
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "../src/index.html"),
minify: true,
}),
new MiniCSSExtractPlugin(),
],
module: {
rules: [
// HTML
{
test: /\.(html)$/,
use: ["html-loader"],
},
// JS
{
test: /\.js$/,
exclude: /node_modules/,
use: ["babel-loader"],
},
// CSS
{
test: /\.css$/,
use: [MiniCSSExtractPlugin.loader, "css-loader"],
},
// Images
{
test: /\.(jpg|png|gif|svg)$/,
type: "asset/resource",
generator: {
filename: "assets/images/[hash][ext]",
},
},
// Fonts
{
test: /\.(ttf|eot|woff|woff2)$/,
type: "asset/resource",
generator: {
filename: "assets/fonts/[hash][ext]",
},
},
],
},
};
```
Now my question is how can I define multiple entries under certain URLs.
So for example now under localhost/ it will redirect me to index.html using my main.js
Is there a way of having localhost/2 use my index.html with another js file like main2.js

Font urls path is wrong and includes css path after WebPack Build

I can't figure out the right combo here to make this all happy. I'm having MiniCssExtractPlugin create css or less files encountered.
I am importing a less and a css file in src/client/index.tsx:
import './less/master.less';
import '../../ext/ink-3.1.10/css/ink.min.css';
webpack.config.js is then doing its magic with MiniCssExtractPlugin:
Note: the reasons I have publicPath: '' for the css loader is because when I had it as publicPath: '/lib/css', it was appending that to the font url so by removing that the font-urls no longer had the issue at least after webpack created dist of the underlying problem I still see at runtime. Honestly I don't know if I even need to set publicPath here anyway because it defaults to "" anyway.
Changing it then to publicPath: '', stripped out the /lib/css part from /lib/css/lib/assets/fonts and I thought that fixed my issue but then during runtime it's still trying to hit fonts via /lib/css/lib/assets/fonts which baffles me
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const TerserJSPlugin = require('terser-webpack-plugin');
const HtmlWebPackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');
const isProduction = process.env.NODE_ENV === 'production';
const html = () => {
return new HtmlWebPackPlugin({
template: path.resolve(__dirname, 'src/client', 'index.html'),
filename: 'index.html',
hash: true,
});
};
const copyAllOtherDistFiles = () => {
return new CopyPlugin({
patterns: [
{ from: 'src/client/assets', to: 'lib/assets' },
{ from: 'package.json', to: './' },
{ from: 'ext/ink-3.1.10/js/ink-all.min.js', to: 'lib/js' },
{ from: 'ext/ink-3.1.10/js/autoload.min.js', to: 'lib/js' },
{ from: 'ext/js/jquery-2.2.3.min.js', to: 'lib/js' },
{ from: 'feed.xml', to: './' },
{
from: 'src/shared',
to: './shared',
globOptions: {
ignore: ['**/*suppressed.json'],
},
},
],
});
};
module.exports = {
entry: './src/client/index.tsx',
output: {
filename: 'scripts/app.[hash].bundle.js',
publicPath: '',
path: path.resolve(__dirname, 'dist'),
},
resolve: {
extensions: ['.ts', '.tsx', '.js'],
},
devtool: isProduction ? '' : 'eval-cheap-source-map',
devServer: {
writeToDisk: true,
contentBase: path.resolve(__dirname, 'dist'),
port: 8080,
},
optimization: {
minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
splitChunks: {
cacheGroups: {
styles: {
name: 'styles',
test: /\.css$/,
chunks: 'all',
enforce: true,
},
},
},
},
module: {
rules: [
{
test: /\.(js)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.(tsx|ts)?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.html$/,
use: [
{
loader: 'html-loader',
},
],
},
{
test: /\.less$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'],
},
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '',
},
},
'css-loader',
],
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
loader: 'file-loader',
options: {
outputPath: 'lib/assets/fonts',
},
},
{
test: /\.(png|svg|jpg|gif)$/,
use: ['url-loader'],
},
],
},
plugins: [
new CleanWebpackPlugin(),
copyAllOtherDistFiles(),
new MiniCssExtractPlugin({
filename: isProduction ? 'lib/css/[name].[hash].css' : 'lib/css/main.css',
}),
html(),
],
};
api.js
const compression = require('compression'),
express = require('express'),
historyApi = require('connect-history-api-fallback'),
oneYear = 31536000;
module.exports = express()
.use(compression())
.on('error', (err: string) => {
console.log(err);
})
.use(historyApi())
.use(
express.static('dist', {
maxage: oneYear,
})
)
.use((_req: any, res: any) => {
res.send('Sorry, Page Not Found');
});
So the issue comes up during runtime with ink.min.css because it's referencing some fonts that I also processed with file-loader as you can see above. The problem is when I run the site in Dev mode via NODE_ENV=development webpack-dev-server --open I end up with a few requests for those fonts because it's appending /lib/css/lib/assets/fonts instead of just /lib/assets/fonts during runtime:
The weird thing about this though is when I go to my dist folder, when I look at styles.main.css (which apparently it renamed ink.min.css to this for some odd reason, the url to the fonts don't have that extra /lib/css so I don't understand why when my browser loads it's trying to reference it with that extra part still:
Another thing that's kinda weird is I do see images loading but when I look at the source in the browser, I don't see my assets folder. I know dist definitely contains it, but it doesn't show it here:
I don't understand why this fixed it. This entire thing feels hacky to me.
I changed it to publicPath: '../../'
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../../',
},
},
'css-loader',
],
I guess I'm loading my .css file and then I'm under the context of /lib/css already so my font urls have /lib/css I guess? and I need to for some reason have MiniCssExtractPlugin have a public path at the root? I don't get it.
Also now I see the assets folder, wt???

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

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.
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.
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');
}
Error:
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)
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
What I Have Tried:
I have tried to use the convertToAbsoluteUrls flag in style-loader
I have tried to turn off all source maps (mentioned in style-loader docs)
My Webpack Config File (loaders are at the bottom):
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
}
};
};
it took me around 5 days of work to understand how this webpack mess works. I have to be honest I can say that this is one of those things that I really do not understand why they are "defacto" tools of the moment. I can't understand how difficult it can be just to make the config files work as it should, in gulp took me 1 hour to do the same.
My problem was that all the url() rules (including fonts and images) were being loaded by css-loader as [object Module], and they where exported by file-loader but never loaded, so if I added ?url=false to the css-loader it never copied the files and export them. I have to say this was a totally PITA, but I got it working, and I hope it works for somebody else in the world, this was made with 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
}),
]
};
You can turn off processing of url() rules, btw. I have no idea, why this is a default behavior.
{
loader: 'css-loader',
options: {
...
url: false,
}
},
I was able to solve the problem myself. In case it could help others in the future, please find the solution below.
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.
In the sass-loader docs, you can read the following:
Since Sass/libsass does not provide url rewriting, all linked assets
must be relative to the output.
If you're just generating CSS without passing it to the css-loader, it must be relative to your web root.
If you pass the generated CSS on to the css-loader, all urls must be relative to the entry-file (e.g. main.scss).
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:
Add the missing url rewriting using the resolve-url-loader. Place it before the sass-loader in the loader chain.
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.
I decided to follow bullet two, and add in resolve-url-loader above sass-loader in the Webpack config. It now works as expected.
My final Webpack config (for now) looks like this:
{
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 }
}
]
},
Side Notes
I noticed that source map paths under "no domain" in Chrome's debugger are repeated. If anyone figures out why, please do share
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"
],
In webpack 5, you should refrain from using raw-loader and file-loader, instead use asset/source and asset/resource.
{
test: /\.txt$/,
type: 'asset/source',
},
{
test: /\.png$/,
type: 'asset/resource',
},
You can read more about assets on: https://webpack.js.org/guides/asset-modules/. It's described how for Webpack 4 and earlier you should use file-loader, and for Webpack5 and later you should use asset modules.
If anyone is struggling with this using Webpack 5 and is trying to upgrade from css-loader 5 to css-loader 6, you might need to check this issue where the poster has a similar problem to the OP:
With css-loader 5.2.7 the images in the input stylus were embedded as data-
URL in the output CSS. With css-loader 6, the images are instead moved
to the output directory.
See Notes here for how to upgrade to css-loader 6 - the salient points are:
using ~ is deprecated when the esModules option is enabled ... and can
be removed from your code
and
file-loader and url-loader are deprecated, please migrate on asset
modules, since v6 css-loader is generating new URL(...) syntax, it
enables by default built-in assets modules, i.e. type: 'asset' for all
url()
I've therefore done the following:
removed any '~' in my .scss files
i.e.
$font-path: "~/src/fonts" !default;
becomes
$font-path: "/src/fonts" !default;
I've also removed the 'file-loader' module completely.
All NPM packages are now up to date and URLs in CSS are working correctly.
IE is not compatible with new URL()
My issue was that webpack 5 changed everything with "Asset modules" apparently and broke file-loader.
Official docs explain it well: https://webpack.js.org/guides/asset-modules/
TL;DR you can fix it by adding type: 'javascript/auto' to the rule/loader
{
test: /\.(eot|ttf|woff2?)($|\?)/i,
loader: 'file-loader',
options: {
name: '[name]-[sha1:hash:hex:10].[ext]',
esModule: false,
},
type: 'javascript/auto'
},

push assets folder to public directory with webpack

I'm using Webpack for the first time. Currently everything is being served up quite nicely. My problem is when I'm trying to build a dist folder. Currently I get my index.html and bundle.js files but I can't figure out how to push my assets to the dist folder.
I have file-loader loaded up but it doesn't actually seem to do what I want it to and none of the google searches I've run are telling me what I need to know. Below is my config file. Can someone lead a horse to water? Also once I get it running do I need to import all of images to my React components?
var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './app/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: '/'
},
module: {
rules: [
{ test: /\.(js)$/, use: 'babel-loader' },
{ test: /\.scss$/, use: [ 'style-loader', 'css-loader', 'sass-loader' ]},
{ test: /\.(png|jpe?g|svg|)$/, use: { loader: 'file-loader', options: }}
]
},
devServer: {
historyApiFallback: true,
},
plugins: [
new HtmlWebpackPlugin({
template: 'app/index.html'
})
]
};
Looks like I just needed to use Copy-Webpack-Plugin.
To copy all the assets from 'app/assets/' to 'dist/assets/' I just needed to do:
plugins: [
new CopyWebpackPlugin([
{ from: 'app/assets', to: 'assets' }
])
]
First install copy webpack plugin:
npm i copy-webpack-plugin
Then in the webpack.config.json:
const CopyPlugin = require("copy-webpack-plugin");
module.exports = {
plugins: [
new CopyPlugin({
patterns: [
{ from: "src/public", to: "" } //to the dist root directory
],
}),
],
};

Categories

Resources