Can Webpack build multiple HTML files from SCSS and Pug? - javascript

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

Related

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

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

How to properly setup webpack config to include common chunks used in multiple page (and entry) app?

Imagine having below structure of html files:
./home.html
./settings.html
./contact.html
Also having below js files
./nav.js <-- common - used in all html files
./home.js <-- used only in home.html, below follow the same rule
./settings.js
./contact.js
And some modules from node_modules:
"jquery"
"moment"
that are being imported as if when required:
./home.js
import $ from "jquery";
(...)
I have setup webpack to have each entry point as each file name. Now what would be the way to include common js files such as `./nav.js" into each file?
entry: {
home: "./resources/js/sections/home.js",
settings: "./resources/js/sections/settings.js",
(...)
}
(...)
output: {
filename: '[name].js',
}
// Option A
Import raw nav.js like another module in every subpage (home.js, contact.js, settings.js)
import nav from ./nav.js
nav();
// Option B
create another entry for ./nav.js and manually add bundled nav.js to each html alongside other bundled files.
entry: {
(...)
nav: "./resources/js/sections/nav.js"
}
You may use HtmlWebPackPlugin in order to append scripts dynamically to your HTML pages.
First of all install the plugin:
npm install html-loader html-webpack-plugin --save-dev
Then use the config:
const HtmlWebPackPlugin = require("html-webpack-plugin");
module.exports = {
entry: {
nav: './resources/js/sections/nav.js',
home: './resources/js/sections/home.js',
settings: './resources/js/sections/settings.js',
contact: './resources/js/sections/contact.js',
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist'), // folder where all tranformed files will be placed
},
rules: [
{
test: /\.html$/,
use: [{
loader: "html-loader",
options: { minimize: true }
}]
}
],
plugins: [
// create an instance of HtmlWebPackPlugin for every page of a multipage website
new HtmlWebPackPlugin({
template: "./resources/html/home.html", // take html from this path
filename: "./home.html", // name it 'home.html' and insert to the root of output folder
chunks: ['nav', 'home'] // insert dymamically nav.js and home.js to home.html
}),
new HtmlWebPackPlugin({
template: "./resources/html/settings.html",
filename: "./settings.html",
chunks: ['nav', 'settings']
}),
new HtmlWebPackPlugin({
template: "./resources/html/contact.html",
filename: "./contact.html",
chunks: ['nav', 'contact']
}),
]
}

How to bundle JS and CSS file independently with Webpack?

I have a few JS and SCSS files. I need Webpack 4 to bundle each JS entry to one JS file and each SCSS entry to one CSS file. The JS files don't import the SCSS files. I try to do it with the following webpack.config.js:
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: {
scriptFoo: './src/js/scriptFoo.js',
scriptBar: './src/js/scriptBar.js',
// ...
styleBaz: './src/css/styleBaz.scss',
styleBaq: './src/css/styleBaq.scss'
// ...
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader'
},
{
test: /\.(scss|sass)$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'sass-loader'
]
}
]
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css'
})
]
};
It works fine, Webpack puts the compiled files to the dist directory. But it also creates an excess dummy JS file for each SCSS file in the dist directory:
webpack.config.js
src/
js/
scriptFoo.js
scriptBar.js
...
css/
styleBaz.scss
styleBaq.scss
...
dist/
scriptFoo.js
scriptBar.js
...
styleBaz.css
styleBaz.js // Excess
styleBaq.css
styleBaq.js // Excess
...
How to make Webpack not to create the excess JS files?
Use the ignore-emit-webpack-plugin Webpack plugin to not create the excess file. First install it by running in a console:
npm install --save-dev ignore-emit-webpack-plugin
Then add it to your Webpack configuration:
const IgnoreEmitPlugin = require('ignore-emit-webpack-plugin');
module.exports = {
// ...
plugins: [
// ...
new IgnoreEmitPlugin(['styleBaz.js', 'styleBaq.js']) // Or simply: new IgnoreEmitPlugin(/^style.*\.js$/)
]
};
It is because for each property in the entry object ,The js file is created in output destinations.
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
},
Webpack creating dummy js when css is an entry point is a known bug, which has not been fixed yet.
Also having multiple entry files in the entry configuration will also affect treeshaking capabilties

How do i prevent a .js file being bundled by webpack

Hi I am currently using webpack to bundle my project files into a single file. However, I do not want webpack to bundle my config.js file where all my config is set. I would like to this remain separate in the output folder but not sure out to achieve this.
my current setup is
//index.js file
#!/usr/bin/env node
'use strict';
let config = require('config.js);
let read = require('read.js);
console.log('i am running through command line');
//read.js file
'use strict'
console.log('read a text file');
//config.js
'use strict';
module.exports = {
name: 'test'
}
//webpack.config.js
let webpack = require('webpack');
let path = require('path');
let fs = require('fs');
let nodeModules = {};
fs.readdirSync('node_modules')
.filter(function (x) {
return [ '.bin' ].indexOf(x) === -1;
})
.forEach(function (mod) {
nodeModules[mod] = 'commonjs ' + mod;
});
module.exports = {
entry: [ 'babel-polyfill', './index.js' ],
target: 'node',
node: {
__dirname: true
},
output: {
path: path.join(__dirname, 'webpack_bundle'),
filename: '[name].js',
libraryTarget: 'commonjs'
},
module: {
loaders: [
{
test: /\.js$/,
loader: 'shebang-loader'
},
{
test: /\.js$/,
loader: 'babel-loader',
query: {
presets: [ 'es2015' ]
}
} ]
},
resolve: {
extensions: [ '.js' ]
},
plugins: [
new webpack.BannerPlugin({banner: '#!/usr/bin/env node', raw: true
})
]
,
externals: nodeModules
};
Note: I have significantly simplified the code example for brevity
Currently when i run the webpack command i get a folder webpack_bundle which contains an index.js file - the index.js file includes the dependencies config.js and read.js. However, what i would like is for the read.js dependency to be bundled into the index.js file but the config.js dependency to stay external in a separate file which gets required by the bundled webpack output. So the folder webpack_bundle should contain two files after running the webpack command - index.js and config.js. I have already tried to modify the externals by adding the following key value to the externals object config: './config.js' but this did not work. I also created an extra entrypoint by specifying config.js as the entrypoint but this also did not work. I can't figure this out and the webpack docs are not that clear on how to achieve this. Please help!
If you want your config in a separate bundle, you can create a split point, by importing dynamically your config.js file with require.ensure:
require.ensure([], function() {
let config = require('./config.js');
});
Your config will then be in a separate bundle.
Documentation about Code splitting (warning: Webpack 1.x is deprecated).
Documentation about Code Splitting (Webpack 2).
Edit:
If you don't want your config file to be bundled by Webpack, I think you can use IgnorePlugin:
module.exports = {
//...
plugins: [new webpack.IgnorePlugin(/^\.\/config\.js$/)]
}
And use copy-webpack-plugin to copy your config.js file.

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

Categories