Need help fixing webpack.config with relative/absolute paths - javascript

I am trying to make my webpack.config work with images in subfolders.
And I am having troubles with it.
I spent the last day and a half scouring the internet and reading various solutions to no avail.
My src files are:
/src/images/data/
/src/containers
My problem is:
I have a Route that is: http://localhost:7771/games/O that load from /src/containers
On that page, I am trying to load /src/images/data/NotFound.png
If I call the image using:
<img src="../images/data/NotFound.png"/> then the image shows without any problem.
But, if I change the path to <img src={require("../images/data/NotFound.png")}/> then the image does not show.
When I inspect the image using Chrome developer Tools I see that the element appears as:
<img src="images/data/NotFound.png">
If I hover the src link I see: http://localhost:7771/games/images/data/NotFound.png
But if I try to open that link, the image does not load.
If I navigate to http://localhost:7771/images/data/NotFound.png instead, then the image loads.
I tried using an alias for resolve and changed the image to <img src={require("~/data/NotFound.png")}> but the result is the same, the image does not load.
That's why I think my webpack.config is messed up and I would like some help to figure out what is wrong with it so that my image can show.
var webpack = require('webpack');
var path = require('path');
var CopyWebpackPlugin = require('copy-webpack-plugin');
var CircularDependencyPlugin = require('circular-dependency-plugin');
var BUILD_DIR = path.resolve(__dirname,'htmlhot');
var APP_DIR = path.resolve(__dirname, 'src');
// Load environment variables from .env file. Suppress warnings using silent
// if this file is missing. dotenv will never modify any environment variables
// that have already been set.
// https://github.com/motdotla/dotenv
require('dotenv').config({silent: true});
var config = {
context: path.join(__dirname, "src"),
devtool: 'source-map',
entry: [
//'webpack/hot/dev-server',
// reload controls falling back to page refresh if hot reload fails ( rare ).
// change to false to debug hot reloading, so you can see the errors before it refreshes the page.
'webpack-hot-middleware/client?reload=true',
// 'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000',
APP_DIR + '/index.js'
],
output: {
path: path.join(__dirname, "src"),
filename: 'bundle-hot.js'
},
resolve: {
modules: [
path.join(__dirname, 'src/'),
'node_modules/'
],
alias: {
'~': APP_DIR + '/images/'
}
},
watch: true,
watchOptions: {
poll: true,
aggregateTimeout: 300,
number: 1000
},
module : {
loaders : [
{
test : /\.jsx?/,
include : APP_DIR,
exclude: /node_modules/,
loaders: ['react-hot-loader', 'babel-loader?' + JSON.stringify({
cacheDirectory: true,
plugins: [
'transform-runtime',
'react-html-attrs',
'transform-class-properties',
'transform-decorators-legacy'
],
presets: ['es2015', 'react', 'stage-2']
})]
},
// CSS
// "css" loader resolves paths in CSS and adds assets as dependencies.
// "style" loader turns CSS into JS modules that inject <style> tags.
// In production, we use a plugin to extract that CSS to a file, but
// in development "style" loader enables hot editing of CSS.
{
test: /\.css$/,
include: path.join(__dirname, 'src/style'),
loader: 'style-loader!css-loader'
},
// "file" loader makes sure those assets get served by WebpackDevServer.
// When you `import` an asset, you get its (virtual) filename.
// In production, they would get copied to the `build` folder.
{
test: /\.(ico|jpg|png|gif|eot|otf|webp|svg|ttf|woff|woff2)(\?.*)?$/,
exclude: /\/favicon.ico$/,
loader: 'file-loader',
query: {
name: '[path][name].[ext]'
}
},
{
test: /\.(ico)(\?.*)?$/,
exclude: /node_modules/,
loader: 'file-loader',
query: {
name: '.images/[name].[ext]'
}
}
]
},
// use EnableCircularDependencyPlugin=true|false to check the option
plugins: (function() {
var plugins = [
new CopyWebpackPlugin([
{ from: APP_DIR + '/index.html', to: BUILD_DIR + '/index.html' },
{ from: APP_DIR + '/images/', to: BUILD_DIR + '/images/' }
]),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
new webpack.DefinePlugin({
__DEVTOOLS__: true // <-------- DISABLE redux-devtools HERE
})
];
// HERE IS OPTION CONDITION
// edit .env file change to EnableCircularDependencyPlugin=false will bypass it
if (process.env.EnableCircularDependencyPlugin=="true") {
plugins.push(new CircularDependencyPlugin({
// exclude detection of files based on a RegExp
exclude: /a\.js|node_modules/,
// add errors to webpack instead of warnings
failOnError: true
}));
}
return plugins;
})(),
node: {
net: 'empty',
dns: 'empty'
}
};
module.exports = config;

file-loader respects output.publicPath, as you've not set one, it uses the path relative to the output.path and that won't work when using a different route. To fix it just set the public path to /:
output: {
path: path.join(__dirname, "src"),
filename: 'bundle-hot.js',
publicPath: '/'
},
file-loader also has an option publicPath if you don't want to set output.publicPath, as it will affect other loaders as well, but that's usually what you want and is therefore recommended. With this you'll get:
<img src="/images/data/NotFound.png">
You also don't need to copy your images directory because file-loader will copy the images you import. In fact it uses the URL to the copied file. So you should remove it from the CopyWebpackPlugin, unless you have images that are not processed by webpack.

Related

How to stop the Vue compiler from handling static assets

My Vue 3 app references a number of static assets. The static assets are already set up with their own file structure, which happens to be outside of the app source directory. My web server has been configured for this and happily serves files from both the Vue build directory and the static assets directory.
The Vue compiler, however, is not so happy. When it sees a path, it tries to resolve it and copy the asset to the build directory. If I use an absolute path it cannot find the asset, and crashes and dies.
Requirement
I would like the Vue compiler to only output css and js files. I do not need the static files in the build directory. If the Vue compiler sees a reference to a static file - eg:
<style lang="scss">
.hero {
background-image: url('/images/background.jpg');
}
</style>
it should not try to resolve /images/background.jpg and copy it to the build directory; it should assume that I have already put the file where it needs to be.
Current setup
I'm using Vue 3 and Webpack 5. My webpack.config.js is
const MiniCssExtractPlugin = require( 'mini-css-extract-plugin' )
const { VueLoaderPlugin } = require( 'vue-loader' )
const path = require( 'path' )
module.exports = {
entry: './src/index.ts',
mode: 'production',
module: {
rules: [ {
test: /\.vue$/,
loader: 'vue-loader'
}, {
test: /\.ts$/,
loader: 'ts-loader',
exclude: /node_modules/,
options: {
appendTsSuffixTo: [ /\.vue$/ ]
}
}, {
test: /\.s?css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: "sass-loader",
options: {
additionalData: '#import "#/util/sass/_index.scss";' // import _index.scss to all Vue components
}
}
]
} ]
},
plugins: [
new MiniCssExtractPlugin( {
filename: 'style.css'
} ),
new VueLoaderPlugin(),
],
resolve: {
alias: {
'#': path.resolve( __dirname, 'src' )
},
extensions: [ '.ts', '.scss', '.vue' ],
},
output: {
filename: 'app.js',
path: path.resolve( __dirname, 'build' ),
},
}
You need to tell Webpack to disable asset emission.
By default, asset/resource modules are emitting with [hash][ext][query] filename into output directory.
Since you haven't specified anything concerning asset resources in your webpack config, it will emit a file for each ressource.
As a side note, your vue-loader will not modify absolute paths :
If your static asset URL is specified with an absolute path, Vue will preserve as-is
So, for your concern, make sure to add a rule in your webpack config targeting your asset types. Make sure this rule is the first one run (the last one in your rules array).
//...
module: {
rules: [
//...
{
test: /\.(png|jpe?g|gif|svg|eot|ttf|woff|woff2)$/i,
type: 'asset/resource',
generator: {
emit: false,
},
},
],
},
//...
Here is the webpack reference for more information

Webpack-dev-server HMR not working with multiple entry points

today I've noticed a strange bug (or I am to dumb?) with my webpack-dev-server.
I've got a Spring Boot App with thymleaf templates. Some pages may only load one others may have more than one js-file:
// main.js
import "../style.scss";
single.html:
<body>
<script th:src="#{/myapp/js/main.js}"></script>
</body>
multiple.html
<body>
<script th:src="#{/myapp/js/main.js}"></script>
<script th:src="#{/myapp/js/other.js}"></script>
</body>
I've splitted my config into a dev, production and common part:
webpack.common.js:
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
entry: {
main: path.resolve(__dirname + "/src/main/js/main.js"),
other: path.resolve(__dirname + "/src/main/js/other.js"),
},
output: {
path: path.resolve(__dirname, "./src/main/resources/static/myapp"),
filename: "js/[name].js",
},
module: {
rules: [
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"postcss-loader",
"sass-loader",
],
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: [
// prettier-ignore
["#babel/preset-env", {
corejs: "3.6.4",
// debug: true,
useBuiltIns: "usage"
}
],
],
},
},
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: "css/[name].css",
chunkFilename: "css/[name].css",
}),
],
};
webpack.dev.js
const common = require("./webpack.common");
const { merge } = require("webpack-merge");
module.exports = merge(common, {
mode: "development",
devtool: "inline-source-map",
devServer: {
proxy: {
"/": "http://localhost:8081",
},
port: 8083,
devMiddleware: {
publicPath: "/myapp",
},
},
});
The strange behaviour: If I'm editing files for the page which only a single script has been loaded (single.html), changes are applied immediately. For example changing the background color in the css file is displayed without pagereload. If I'm editing a page where multiple scripts (entry points) are used this is not working anymore. My dev-console logs the following:
[HMR] Update failed: Loading hot update chunk global failed.
(missing: http://localhost:8083/myapp/main.618757b0411fc5552e94.hot-update.js)
The first entry point / chunk (main.js) cannot be loaded, caused by the hash? I need to manually refresh the whole page, to apply changes. I've already searched for solutions and tried to apply this tip
optimization: {
runtimeChunk: {
name: 'single',
},
}
However my dev console does not log any HMR output anymore and nothing happens. It seems like HMR has stopped working in my browser. Webpack is running and bundling it correctly!
Any ideas? Thanks so far and apologizes for this wall of text.
The optimization.runtimeChunk option should be true or 'single' and not an object with name:
optimization: {
runtimeChunk: 'single',
},
As the documentation explains this is an alias for:
optimization: {
runtimeChunk: {
name: 'runtime',
},
},
Also, you'll want to include the runtime.js file on the page, however you need to do that for your set up. It should only be served in the dev environment (so for example, if NODE_ENV is "development").
In my case I have production, server-side script files alongside webpack-dev-server, and needed this option enabled so it would correctly serve runtime.js from the manifest.json:
devServer: {
devMiddleware: {
writeToDisk: true,
},
}

Get list of webpack output files and use in another module for PWA

I'm trying to build a progressive web app, with support for offline usage.
According to MDN, the way to make PWAs work offline is to add the required resources to a cache in the service worker. This requires that the service worker code knows each of the output files. Ideally, this shouldn't be harcoded, and should be generated by webpack, since it knows what files it generates.
I'm struggling to actually generate this list. From my search, there are two plugins that can generate a json file containing a list of the files - webpack-assets-manifest and webpack-manifest-plugin. I can use these in combination with separate targets to generate a manifest with the page files. But I can't import the manifest, since webpack doesn't actually write the manifest until everything is done.
How can I import a list of files that one entry point generates and use them in another entry point/module?
webpack.config.js:
const path = require('path');
const WebpackAssetsManifest = require('webpack-assets-manifest');
const frontend = {
mode: "development",
entry: {
page:"./src/page/page.tsx",
},
devtool: 'inline-source-map',
output: {
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.html?$|\.png$/,
type: "asset/resource",
generator: {
filename: "[name][ext]",
},
},
{
test: /\.tsx?$/,
loader: "ts-loader",
exclude: /node_modules/,
},
{
test: /\.json$/,
type: "asset/resource",
exclude: /node_modules/,
}
],
},
resolve: {
extensions: [".html", ".tsx", ".ts", ".js"],
},
plugins: [
new WebpackAssetsManifest({
output: "page-files.json",
writeToDisk: true,
}),
]
};
const serviceworker = {
mode: "development",
entry: {
serviceworker: "./src/serviceworker/serviceworker.ts",
},
devtool: 'inline-source-map',
output: {
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.html?$|\.png$/,
type: "asset/resource",
generator: {
filename: "[name][ext]",
},
},
{
test: /\.tsx?$/,
loader: "ts-loader",
exclude: /node_modules/,
},
{
test: /\.json$/,
resourceQuery: /link/,
type: "asset/resource",
exclude: /node_modules/,
},
{
test: /\.json$/,
resourceQuery: /str/,
type: "asset/source",
exclude: /node_modules/,
}
],
},
resolve: {
extensions: [".html", ".tsx", ".ts", ".js"],
},
};
module.exports = [frontend, serviceworker];
serviceworker.ts:
import files from "../../dist/page-files.json?str";
console.log(files);
Error is:
Module not found: Error: Can't resolve '../../dist/page-files.json?str' in '<REDACTED>/src/serviceworker'
(When I build it again, it will find the file from the previous build)
Rather than relying on pre-existing webpack plugins to generate assets, I think you're going to need to write your own plugin for this use case. And if you want that plugin to write the manifest to an entry that itself needs to be bundled/compiled, creating a child compilation in that plugin would be the way to do it.
This is unfortunately not a straightforward task, but you can refer to the source code for the workbox-webpack-plugin's InjectManifest plugin, which more or does what you describe, as inspiration.
Alternatively... you can just use InjectManifest directly, if that meets your use case. While it's part of the Workbox family of libraries, InjectManifest will only actually do two things: process the entry file you pass in as swSrc via a child compilation, and replace the symbol self.__WB_MANIFEST anywhere in that swSrc file with an array of {url: '...', revision: '...'} entries generated based on the assets in the main configuration, filtered by any include/exclude parameters.
So if you don't plan on using Workbox, you can just make use of that self.__WB_MANIFEST value from your own code.
// service-worker.ts
const manifest = self.__WB_MANIFEST || [];
self.addEventListener('install', (event) => {
// Your code to cache the contents of manifest goes here.
});
// webpack.config.js
const {InjectManifest} = require('workbox-webpack-plugin');
module.exports = {
// ...other webpack config...
plugins: [
new InjectManifest({
swSrc: 'src/service-worker.ts',
swDest: 'service-worker.js',
// ...exclude/include config here...
}),
],
};
The reason behind this behavior is that webpack runs these 2 configurations parallelly. By forcing webpack to run sequentially we can fix the problem.
To do serial processing, add module.exports.parallelism = 1; at end of your webpack config.
module.exports = [frontend, serviceworker];
module.exports.parallelism = 1;
Here is the documentation from webpack, https://webpack.js.org/configuration/configuration-types/#parallelism

Images not displaying on production build, only in dev (webpack/sass-loader)

When running my app on webpack dev server, all my image files are working whether as img tags in my index.html or background-image: url()..
Running my project though in production build, I am getting file reference errors that they cannot be found.
GET file:///img/featured.png net::ERR_FILE_NOT_FOUND
GET file:///img/header-img1-1.jpg net::ERR_FILE_NOT_FOUND
I added the copy webpack plugin as I thought this would move all images from my src/img folder to my img folder inside dist.
Should I be using the contentBase option for webpack-dev-server? Or is the copy-webpack-plugin not getting the correct reference? Super confused
Project tree:
- webpack.config.js
- package.json
- .babelrc
- src
- js
- index.js
- ...
- img
- ALL IMAGES LOCATED HERE
- scss
- layout
- landing.scss
- brands.scss
- base
- index.html
inside landing.scss
i have used
background: url('~/img/img-title.png')
same in other files like brands.scss
background: url('~/img/img-title.png')
Which has all worked fine, and I think I've confused myself with how images are referenced with webpack/sass loader, and can't seem to work out how to get the image paths to work for both dev/production, i can only seem to get one working at a time.
production tree:
- dist
- css
- img
- js
- index.html
webpack.config.js:
const path = require('path');
const autoprefixer = require('autoprefixer');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const extractPlugin = new ExtractTextPlugin({
filename: 'css/main.css'
});
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ScriptExtPlugin = require('script-ext-html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = (env) => {
const isProduction = env.production === true
return {
entry: './src/js/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/bundle.js',
},
module: {
rules: [
{
test: /\.js$/,
include: path.resolve(__dirname, 'src'),
use: 'babel-loader'
},
{
test: /\.css$|\.scss$/,
include: path.resolve(__dirname, 'src'),
use: extractPlugin.extract({
fallback: "style-loader",
use: [
{ loader: 'css-loader', options: { importLoaders: 2, sourceMap: true }},
{ loader: 'postcss-loader', options: { sourceMap: true, plugins: () => [autoprefixer] }},
{ loader: 'sass-loader', options: { sourceMap: true }},
],
})
},
{
test: /\.html$/,
use: ['html-loader']
},
{
test: /\.(jpe?g|png|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 1000,
name: 'img/[name].[ext]',
}
}
]
}
]
},
plugins: [
extractPlugin,
new HtmlWebpackPlugin({
filename: 'index.html',
inject: true,
template: './src/index.html'
}),
new ScriptExtPlugin({
defaultAttribute: 'async'
}),
new CleanWebpackPlugin(['dist']),
new CopyWebpackPlugin([
{from:'src/img',to:'img'}
]),
]
}
}
I think you're using different folder structure on production than on local, i.e. on local, it's just http://localhost:PORT/app, but on prod, it must be similar to http://produrl/Some_Folder/app
Now coming to actual issue - It's your CSS loader.
By default css-loader, has url=true, causing all URLs to be mapped relative to root. Hence this works for you -
background: url('~/img/img-title.png')
but this doesn't
background: url('../../img/img-title.png')
Just set url=false, and you'll be able to provide relative URL and it'll load for all enviorments correctly.
While the accepted answer may works in your specific scenario, I think there is a better solution that do not involve disabling css-loader url() handling and will works better in most of situation.
Tilde ~ problem
When you use ~ to import something in css, css-loader will search for that file inside node_modules. Your images are inside src/img folder so you do not need tilde ~ to import your images.
url() problem
sass-loader doesn't not resolve url() correctly if they are not in the same directory of the entry-file.
In your specific example you import some urls inside src/scss/layout/landing.scss and src/scss/layout/brands.scss but I guess your main scss entry point is inside src/scss folder.
Note: for "main scss entry point" I mean the scss file that you import inside your javascript entry point src/js/index.js
So, in your example, every images imported within a scss file that is not inside src/scss folder will trow an error.
To solve this problem use resolve-url-loader which resolves relative paths in url() statements based on the original source file.
[
{ loader: 'css-loader'}, // set `importLoaders: 3` if needed but should works fine without it
{ loader: 'postcss-loader'},
{ loader: 'resolve-url-loader' }, // add this
{ loader: 'sass-loader',
// sourceMap is required for 'resolve-url-loader' to work
options: { sourceMap: true }
}
]
CopyWebpackPlugin is optional
Based on your configuration you do not need CopyWebpackPlugin because your images are already handle by url-loader.
Note: url-loader doesn't output your images but inline them, images are outputted by file-loader when the file is greater than the limit (in bytes).
file-loader will output your images in dist/img folder, because you set name: 'img/[name].[ext]', and you set output.path: path.resolve(__dirname, 'dist').
Hey I think your issue is just in how you're referencing the image in your scss.
Based on your folder structure it should be:
background: url('../../img/img-title.png')
You could modify the "publicPath" URL (which is relative to the server root) to match the file structure.
//webpack.config.js
...
module: {
rules: [
{
test: /\.(jpe?g|png|gif|svg)$/i,
use: [
{
loader: "file-loader",
options: {
name: "[name].[ext]",
outputPath: "assets/images/",
publicPath: "/other-dir/assets/images/"
}
}
]
}
]
}

webpack-dev-server watches and compiles files correctly, but browser can't access them

EDIT: Link to github repo where this example is hosted is here in case someone wants to run it
I'm getting the near exact same problem as another user (you can find the question here), in that running the webpack-dev-server does actually compile and watch files correctly (seeing the console output in the terminal), but the browser still can't view my site correctly. This is my webpack.config.js file:
var webpack = require('webpack'),
path = require('path'),
// webpack plugins
CopyWebpackPlugin = require('copy-webpack-plugin');
var config = {
context: path.join(__dirname,'app'),
entry: './index.js',
output: {
path: path.join(__dirname, 'public'),
filename: 'bundle.js',
publicPath: path.join(__dirname, 'public')
},
devServer: {
// contentBase: './public/'
},
plugins: [
// copies html to public directory
new CopyWebpackPlugin([
{ from: path.join(__dirname, 'app', 'index.html'),
to: path.join(__dirname, 'public')}
]),
// required bugfix for current webpack version
new webpack.OldWatchingPlugin()
],
module: {
loaders: [
// uses babel-loader which allows usage of ECMAScript 6 (requires installing babel-preset-es2015)
{test: /\.js$/, loader: 'babel', exclude: /node_modules/, query: { presets: ['es2015']}},
// uses the css-loader (loads css content) and style-loader (inserts css from css-loader into html)
{test: /\.css$/, loader: 'style!css', exclude: /node_modules/}
]
}
};
module.exports = config;
And this is my directory structure:
+--- webpack/
+--- app/
+--- index.html
+--- index.js
+--- styles.css
+--- package.json
+--- webpack.config.js
Currently, running webpack-dev-server outputs the following in the browser (note the lack of the public/ directory which is where webpack normally outputs my html and javascript bundle):
EDIT: Adding the devServer.contentBase property and setting it to public gets the browser to return a 403 error not found as shown here:
Okay, so I was able to reproduce the issue that you have on my project. To fix the issue I changed some things.
Here is what I have set up. I'm defining a bit less in the output and using jsx instead of js, but the results should be the same. You can replace my src with wherever your source code is.
const config = {
entry: './src/App.jsx',
output: {
filename: 'app.js'
},
module: {
loaders: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015', 'react', 'stage-0'],
plugins: ['add-module-exports']
}
},
{
include: /\.json$/, loaders: ['json-loader']
},
{
test: /\.scss$/,
loaders: ['style', 'css?modules', 'sass']
},
{
test: /\.(eot|svg|ttf|woff|woff2)$/,
loader: 'file?name=fonts/[name].[ext]'
}
]
},
plugins: [
new webpack.ProvidePlugin({
'Promise': 'exports?module.exports.Promise!es6-promise',
'fetch': 'imports?self=>global!exports?global.fetch!isomorphic-fetch'
}),
new webpack.IgnorePlugin(/^\.\/locale$/, [/moment$/]),
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.optimize.DedupePlugin(),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
})
],
resolve: {
root: path.resolve('./src')
},
devServer: {
contentBase: 'src'
}
};
So basically you would want this output in terminal:
webpack result is served from / - tells us that whatever we build from will be at the root
content is served from src - tells us that it's building from that directory
Hope this helps

Categories

Resources