Django webpack loader: how to refer to static images compiled by webpack - javascript

I'm setting up webpack for a a Django application, for which django-webpack-loader appears to be the obvious choice. I can compile my js and scss files just fine, but I've hit a wall when it comes to loading in the images.
My webpack.config.js file looks like this (I removed the scss, minifiers, babel, etc. for conciseness):
const path = require('path');
const webpack = require('webpack');
const BundleTracker = require('webpack-bundle-tracker');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
context: __dirname,
entry: {
main: ['#babel/polyfill', './ledger/static/ledger/js/index.js']
},
output: {
path: path.resolve('./ledger/static/ledger/compiled_assets/'),
filename: "[name]-[hash].js"
},
module: {
rules: [
{
test: /\.(png|jpe?g|gif|svg)$/,
use: [
{
loader: "file-loader",
options: {
outputPath: 'images'
}
}
]
}
]
}
mode: 'development'
}
As far as I've been led to believe, file-loader is what I need to load in the static image files.
When I run the build, the image file happily sits in my compiled_assets/images directory with its hash added.
The problem seems to be in how to refer to the file. While the js files load fine by using the
{% render_bundle 'main' 'js' 'DEFAULT' %}
tag, I can't get the image files to appear. The docs suggest that I need to use something like
{% load webpack_static from webpack_loader %}`
...
<img src="{% webpack_static 'images/logo.jpg' %}"/>
in my html file but no luck, it just tries to render the actual asset as
<img src="/static/logo.jpg">
I think the issue might be somewhere in my settings.py file. This is the relevant part of it:
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, "static")
WEBPACK_LOADER = {
'DEFAULT': {
'CACHE': not DEBUG,
'BUNDLE_DIR_NAME': 'ledger/compiled_assets/', # must end with slash
'STATS_FILE': os.path.join(BASE_DIR, 'webpack-stats.json'),
'POLL_INTERVAL': 0.1,
'TIMEOUT': None,
'IGNORE': ['.+\.hot-update.js', '.+\.map']
}
}
Am I missing something? Something along the lines of STATICFILES_DIRS, STATIC_FILES, or maybe some image related setting perhaps?

have the same issue. Looks like {% load webpack_static from webpack_loader %} ignores ebpack-stats.json where all images are in "assets" key:
"assets":{"images/logo-9af925ecf75862678220bae8cad387ad.png":{"name":"images/logo-9af925ecf75862678220bae8cad387ad.png","publicPath":"static/images/logo-9af925ecf75862678220bae8cad387ad.png"}...}
I'll try to manage it by merge Django Webpack Loader for css/js files and collectstatic for images

Related

Webpack does not save images when a relative image URL is created by Javascript but saves them when the same relative imageURL is in the HTML

When I create an HTML file and I use an image with a relative path, webpack does a good job of saving the file in the dist/images folder as it's instructed to.
However when my JS script adds the same relative image URL then webpack can't seem to find the image and hence I guess is not able to save the image in the dist/images folder
So my question is
Am I doing something wrong in the code below? (I've double checked the paths to the images and even used the original paths in the JS to ensure it's not the path that's wrong)
OR
As the image are loaded by javascript, does this mean that webpack does not save images that are not loaded in the inital HTML file? (I hope this is not true...)
Any advise would be appreciated.
This is my 'template' section in the HTML
<template>
<div class="card product">
<div class="card__header">
<figure class="product__image">
<picture>
<img
src="./assets/images/product/eyeglass/luxury/chopard/chopard-schf11s.jpg"
alt=""
/>
</picture>
</figure>
</div>
<div class="card__body">
<h4 class="product__brand">Chopard</h4>
<p class="product__name">Name of the product</p>
<!-- <p class="product__description">Description of the product</p> -->
<div class="product__rating" data-rating="05">
<span class="material-icons" data-rating="05"
>star star star star star</span
>
<span class="product__votes" data-votes="100"
>from 100 reviews</span
>
</div>
</div>
<div class="card__footer">
<div class="product__price-old">
<span class="product__price-delete"
><span class="product__price-inactive">$120</span></span
>
<span class="product__price">$120</span>
</div>
<button>Buy Now</button>
</div>
</div>
</template>
and this is my JS
const cardTemplate = document.querySelector("template");
const cards = document.querySelector(".cards");
// deleted the text where the productObject was created to save space
export function displayProductCards() {
console.log(cards);
productObjects.forEach((product) => {
const card = cardTemplate.content.cloneNode(true).children[0];
const productName = card.querySelector(".product__name");
const productBrand = card.querySelector(".product__brand");
const productImage = card.querySelector(".product__image>picture>img");
// const productDescription = card.querySelector(".product__description");
const productPrice = card.querySelector(".product__price");
const productRating = card.querySelector(
".product__rating>span.material-icons"
);
const productVotes = card.querySelector(".product__votes");
// const imgUrl = `./assets/images/product/${product.category.toLowerCase()}/${product.subCategory.toLowerCase()}/${
// product.brandShortname
// }/${product.image}`;
const imgUrl = `./assets/images/product/eyeglass/luxury/chopard/chopard-schf11s.jpg`;
productName.textContent = product.name;
productBrand.textContent = product.brand;
productImage.src = imgUrl;
productPrice.textContent = product.price;
productRating.setAttribute("data-rating", product.rating);
productRating.textContent = setRating(product.rating);
productVotes.textContent = `from ${product.votes} votes`;
cards.appendChild(card);
});
}
function setRating(x) {
let stars = "";
for (let i = 0; i < +x; i++) {
stars += "star ";
}
return stars.trim();
}
and this is my webpack config file
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
mode: "development",
entry: "./src/main.js",
output: {
publicPath: "",
filename: "[name].js",
path: path.resolve(__dirname, "dist"),
clean: true,
assetModuleFilename: "./images/[name][ext][query]",
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
new MiniCssExtractPlugin({ filename: "[name].css" }),
],
module: {
rules: [
{
test: /\.s?css$/,
// use: ["style-loader", "css-loader", "sass-loader"],
use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
},
{
test: /\.html$/,
use: ["html-loader"],
},
{
test: /\.(svg|png|jpg|jpeg|gif)$/,
type: "asset/resource",
},
],
},
};
This answer is mainly for me in the future if I run into this issue again and for those like me who might be on the same learning path sometime in the future.
So my understanding was that webpack would automatically save all images in the dist/images folder as I had defined in webpack config file
output: {
publicPath: "",
filename: "[name].js",
path: path.resolve(__dirname, "dist"),
clean: true,
assetModuleFilename: "./images/[name][ext][query]",
},
However what I was doing in the JS file was simulating a fetch action where I'd get JSON data and populate the src attribute of all images in the product cards. Expecting these images to be stored in the dist/images folder does not seem logical now in hindsight so basically my expectation was not right. If webpack were to really store all such images in the dist/images folder it would be a huge folder each time I would dynamically fetch a JSON to show all my images on the online store.
So firstly my expectations were not logical and secondly I missed out on this message that was displayed when webpack-dev-server was run ..
<i> [webpack-dev-server] Content not from webpack is served from '/Applications/MAMP/htdocs/sunshyn-optical-store/public' directory
So basically the reason for my images to not show up on the displayed site on localhost was NOT because the images were not being stored in the dist/images folder (which anyways should not be stored there) but because the public path for all relative images was a public folder path that webpack was generating.
So I created a public folder and added the assets folder in the public folder and the missing link was fixed. The images showed up fine.
So when expecting to see images that are dynamically updated on the html, ensure that the path exists either by providing absolute paths or ensuring that the relative path is in relation to the public folder.

Lack of javascript error messages with webpack when missing a dependency

I've been recently frustrated when developing using Webpack, because whenever I introuduce a new dependency there are no printed errors if I forget to include the depending JavaScript file(s).
I have created an example project: https://github.com/manstie/test-django-webpack
In the example you can see I have purposely set up the home module to depend on the utils module.
When you run the server and load localhost:8000 it is expected that you get a console log of Here and You have successfully depended on utils.js if you import all the necessary script files.
When utils.js is not included in index.html, base.js does not run at all - it silently fails, with no errors.
I am hoping there is a way to have errors show in the javascript console in these cases? I just can't find any resources or related questions on this issue.
Here is the webpack.common.js config:
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const { WebpackManifestPlugin } = require('webpack-manifest-plugin');
const dist = path.resolve(__dirname, 'static/bundles');
module.exports = {
entry: {
utils: './home/js/utils.js',
home: {
import: './home/js/index.js',
dependOn: ['utils']
}
},
resolve: {
modules: ['node_modules']
},
output: {
filename: '[name].[chunkhash].js',
path: dist,
publicPath: 'bundles/',
},
plugins: [
new CleanWebpackPlugin(), // deletes files inside of output path folder
new WebpackManifestPlugin({ fileName: "../../manifest.json" }),
],
optimization: {
moduleIds: 'deterministic', // so that file hashes don't change unexpectedly?
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
maxInitialRequests: Infinity,
minSize: 0,
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name(module) {
// get the name. E.g. node_modules/packageName/not/this/part.js
// or node_modules/packageName
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
// npm package names are URL-safe, but some servers don't like # symbols
return `npm.${packageName.replace('#', '')}`;
},
},
},
},
},
};
Here is the html with a missing dependency - I am using django-manifest-loader in order to prevent JavaScript caching issues
...
{% load manifest %}
<script src="{% manifest 'runtime.js' %}"></script>
<!-- Is there a way to have webpack / js tell you about the missing dependency? -->
<!-- <script src="{% manifest 'utils.js' %}"></script> -->
<script src="{% manifest 'home.js' %}"></script>
...
And here are the js files:
index.js
import { testFunc } from "./utils.js";
console.log("Here");
testFunc();
utils.js
export function testFunc()
{
console.log("You have successfully depended on utils.js");
}
Thanks in advance.

Can Webpack build multiple HTML files from SCSS and Pug?

I've read quite a few tutorials on webpack, but it seems more for creating web apps then for what I'm trying to do so I didn't want to waste any more time if the following isn't possible.
I'm creating websites on a third-party eCommerce system and they have a system of coding out templates that can be used to change the structure of their website. Below is an example of one of their templates I would be creating (although there are many types & variations I will need to make, not just a few).
My idea to streamline the creation of these templates is to create a bunch of pug components and place them in the components/ directory. Outside of the components directory I want to make higher level pug templates that utilize the components. Once these have been created, I would build it with NPM and the template files need to be converted to HTML and placed within the /dist folder.
Is this hard to do with webpack?
Structure of the project:
src/
..components/
....header/
......header1.pug
......header1.scss
..navcustom-template.pug
..customfooter-template.pug
..non-template-specific.scss
and once built:
dist/
..navcustom-template.html
..customfooter-template.html
..non-template-specific.css
src/
..components/
....header/
......header1.pug
......header1.scss
..navcustom-template.pug
..customfooter-template.pug
..non-template-specific.scss
Sample of a template:
<!--
Section: NavCustom
-->
<style>
//Template Speific CSS Imports Here
</style>
<script type="text/javascript">
//Template Speific JS Imports Here
</script>
<header>
<div class="col-md-4">
// Social Media Code
</div>
<div class="col-md-4">
// Logo Code
</div>
<div class="col-md-4">
// Call to Action Code
</div>
</header>
<nav>
</nav>
You can use these packages (--save-dev for all):
raw-loader to load the Pug files
pug-html-loader to read the Pug files
html-webpack-pug-plugin to generate HTML from Pug
html-webpack-plugin (standard HTML plugin loader)
Then configure Webpack similar to the following, where index.js is a dummy file you should create if you don't already have an entry. Each Pug template you need to process is written in a separate HtmlWebpackPlugin object at the bottom.
var HtmlWebpackPlugin = require('html-webpack-plugin');
var HtmlWebpackPugPlugin = require('html-webpack-pug-plugin');
module.exports = {
entry: ['./src/index.js'],
output: {
path: __dirname + '/dist',
publicPath: '/'
},
module: {
rules: [
{
test: /\.pug$/,
use: [
"raw-loader",
"pug-html-loader"
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/navcustom-template.pug',
filename: 'navcustom-template.html'
}),
new HtmlWebpackPlugin({
template: 'src/customfooter-template.pug',
filename: 'customfooter-template.html'
}),
new HtmlWebpackPugPlugin()
]
}
All Pug mixins (your src/components files) will be included in the output. This example is tested and working.
Edit: To dynamically process all Pug files in the src directory use this config:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackPugPlugin = require('html-webpack-pug-plugin');
const fs = require('fs');
let templates = [];
let dir = 'src';
let files = fs.readdirSync(dir);
files.forEach(file => {
if (file.match(/\.pug$/)) {
let filename = file.substring(0, file.length - 4);
templates.push(
new HtmlWebpackPlugin({
template: dir + '/' + filename + '.pug',
filename: filename + '.html'
})
);
}
});
module.exports = {
entry: ['./src/index.js'],
output: {
path: __dirname + '/dist',
publicPath: '/'
},
module: {
rules: [
{
test: /\.pug$/,
use: [
"raw-loader",
"pug-html-loader"
]
}
]
},
plugins: [
...templates,
new HtmlWebpackPugPlugin()
]
}
This uses fs.readdirSync to get all Pug files in a directory. The synchronous version is used (as opposed to fs.readdir) because the module.exports function will return before the files are processed.
in 2022 is appeared the Pug plugin for Webpack that compiles Pug to static HTML, extracts CSS and JS from their source files defined directly in Pug.
It is enough to install the pug-plugin only:
npm install pug-plugin --save-dev
webpack.config.js
const path = require('path');
const PugPlugin = require('pug-plugin');
module.exports = {
output: {
path: path.join(__dirname, 'dist/'),
filename: 'assets/js/[name].[contenthash:8].js'
},
entry: {
// Note: a Pug file is the entry-point for all scripts and styles.
// All scripts and styles must be specified in Pug.
// Define Pug files in entry:
index: './src/views/index.pug', // => dist/index.html
'navcustom-template': './src/navcustom-template.pug', // => dist/navcustom-template.html
'customfooter-template': './src/customfooter-template.pug', // => dist/customfooter-template
// etc ...
},
plugins: [
// enable using Pug files as entry-point
new PugPlugin({
extractCss: {
filename: 'assets/css/[name].[contenthash:8].css' // output filename of CSS files
},
})
],
module: {
rules: [
{
test: /\.pug$/,
loader: PugPlugin.loader, // the Pug loader
},
{
test: /\.(css|sass|scss)$/,
use: ['css-loader', 'sass-loader']
},
],
},
};
Of cause, the entry can be dynamically generated like in the answer above.
In your Pug file use the source files of styles and scripts via require():
html
head
//- add source SCSS styles using a path relative to Pug file
link(href=require('./styles.scss') rel='stylesheet')
body
h1 Hello Pug!
//- add source JS/TS scripts
script(src=require('./main.js'))
Generated HTML:
<html>
<head>
<link href="/assets/css/styles.f57966f4.css" rel="stylesheet">
</head>
<body>
<h1>Hello Pug!</h1>
<script src="/assets/js/main.b855d8f4.js"></script>
</body>
</html>
Just one Pug plugin replaces the functionality of many plugins and loaders used with Pug:
pug-html-loader
html-webpack-pug-plugin
html-webpack-plugin
mini-css-extract-plugin
resolve-url-loader
svg-url-loader
posthtml-inline-svg

Webpack - Best way to update HTML to include latest [hashed] bundles

I'm using webpack to generate hashed bundle filenames.
Assuming I'm using static HTML, CSS & JS, what is the best way to automatically update index.html to point to the latest bundles?
For example,
update:
<script src="e8e839c3a189c25de178.app.js"></script>
<script src="c353591725524074032b.vendor.js"></script>
to:
<script src="d6cba2f2e2fb3f9d98aa.app.js"></script>
<script src="c353591725524074032b.vendor.js"></script> // no change
automatically everytime a new bundle version is available.
Amazingly, this is what the html-webpack-plugin is for.
var webpack = require('webpack');
var path = require('path');
var HTMLWebpackPlugin = require('html-webpack-plugin');
module.exports = function(env) {
return {
entry: {
main: './src/index.js',
vendor: 'moment'
},
output: {
filename: '[chunkhash].[name].js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
names: ['vendor', 'manifest']
}),
new HTMLWebpackPlugin({
tempate: path.join(__dirname, './src/index.html')
})
]
}
};
This generates an index.html in the dist directory that includes the script's in the correct order.
youtube example

webpack - compile every scss to css file with same name

I'd like to have structure like this
-Styles
--Main.scss
--SomeComponent.scss
-CompiledStyles
--Main.css
--SomeComponent.css
Actually I can only do this
-Styles
--Main.scss
--SomeComponent.scss
--All.scss (import all scss from file)
-CompiledStyles
--Main.css ( all css)
This is my webpack config
var Path = require('path');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var extractCSS2 = new ExtractTextPlugin('[name].css');
module.exports = {
devtool: 'eval',
entry: './Client/Styles/All.scss',
output: {
path: Path.join(__dirname, 'CompiledStyles'),
filename: 'page.js',
publicPath: '/CompiledStyles/'
},
module: {
loaders: [
{
test: /\.scss$/,
loader: extractCSS2.extract("style-loader", "css-loader!autoprefixer-loader!sass-loader")
},
{
//IMAGE LOADER
test: /\.(jpe?g|png|gif|svg)$/i,
loader: 'file-loader'
},
{
test: /\.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
loader: 'file-loader?name=fonts/[name].[ext]'
}
]
},
plugins: [
extractCSS2
]
};
Is it possible to compile this scss files to single css files ?
I really don't know how to manage this case. I've tried to assign entry: './Client/Styles' but it occures error.
EDIT:
I solved this with gulp.
The idea of webpack is to put everything that is needed in some JavaScript-files. So it's the intention to not build a css-file for every css-file.
If you want to still use webpack, try this in your webpack config:
module.exports = {
// ...
entry: {
'Main': './Client/Styles/Main.scss',
'SomeComponents': './Client/Styles/SomeComponents.scss',
},
// ...
}
I have updated the answer after adamo94 noted that he used gulp, so just for everybody else some more information. To convert scss files you need a sass/scss-processor. You can easily call that processor with a single call but as you usually do more with your sources it's likely to use some further processing.
Usually you would use gulp or grunt. Those can be configured to build everything that you need. They have different pros and cons, there are also further tools, but those are probably the ones that you'd like to take a look.

Categories