I'm trying to build static html pages with webpack 4. I'm using html-webpack-plugin. I want to define some header.html and footer.html and import them later in all html pages. But I want also then my output js and css files automatically inject to this header and footer. Something like this:
header.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Some Title</title>
<% for(var i=0; i < htmlWebpackPlugin.files.css.length; i++) {%>
<link type="text/css" rel="stylesheet" href="<%= htmlWebpackPlugin.files.css[i] %>">
<% } %>
</head>
<body>
footer.html:
<% for(var i=0; i < htmlWebpackPlugin.files.js.length; i++) {%>
<script type="text/javascript" src="<%= htmlWebpackPlugin.files.js[i] %>"></script>
<% } %>
</body>
</html>
index.hbs (or index.html):
<%= _.template(require('./../includes/header.html'))() %>
<div class="content">
<img src="img/bg/desktop/bg-index-03.jpg" width="500" height="500"/>
<div class="imgtest"></div>
</div> <!-- .content -->
<%= _.template(require('./../includes/footer.html'))() %>
But I get in dist/index.html some bug. These lines still the same and don't loading header/footer to index.html:
<%= _.template(require('./../includes/header.html'))() %>
...
<%= _.template(require('./../includes/footer.html'))() %>
For some reason lodash doesn't work...
I changed src/html/views/index.hbs
to src/html/views/index.html
(and in plugin)
new HtmlWebpackPlugin({
template: './src/html/views/index.html',
...
Add include
line here:
{
test: /\.html$/,
include: path.resolve(__dirname, 'src/html/includes'),
use: ['html-loader']
},
In header/footer I removed lodash code <% ... %>
, leaving only clean html - so it work! Header/footer imported in index.html, but without css/js.
If I return lodash back in footer.html (or header.html)
<% for(var i=0; i < htmlWebpackPlugin.files.js.length; i++) {%>
<script type="text/javascript" src="<%= htmlWebpackPlugin.files.js[i] %>"></script>
<% } %>
I get error:
ERROR in Template execution failed: ReferenceError: htmlWebpackPlugin is not defined
ERROR in ReferenceError: htmlWebpackPlugin is not defined
- lodash.templateSources[2]:10 eval
lodash.templateSources[2]:10:19
- index.html:102
D:/.../src/html/views/index.html:102:110
- index.html:104 ./node_modules/html-webpack-plugin/lib/loader.js!./src/html/views/index.html.module.exports
D:/.../src/html/views/index.html:104:3
- index.js:393
[project]/[html-webpack-plugin]/index.js:393:16
- runMicrotasks
- task_queues.js:93 processTicksAndRejections
internal/process/task_queues.js:93:5
- async Promise.all
Why it happens? What's wrong? How to make lodash works in header/footer?
Or what is the best way to generate html templates?
file tree:
dist
│ index.html
├───css
│ main.css
│ main.css.map
├───fonts
├───img
├───js
│ main.js
│ main.js.map
│ vendors~main.js
│ vendors~main.js.map
src
├───favicon
├───fonts
├───html
│ ├───includes
│ │ footer.html
│ │ header.html
│ └───views
│ index.hbs
├───img
├───js
│ index.js
├───scss
│ fonts.scss
│ icomoon.scss
│ style.scss
package.json
package-lock.json
webpack.config.js
webpack.config.js:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserWebpackPlugin = require('terser-webpack-plugin');
const OptimizeCssAssetWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const isDev = process.env.NODE_ENV === 'development';
const isProd = !isDev;
const optimization = () => {
const config = {
splitChunks: {
chunks: 'all'
}
}
if (isProd) {
config.minimizer = [
new OptimizeCssAssetWebpackPlugin(),
new TerserWebpackPlugin()
]
}
return config
}
const filename = ext => isDev ? `[name].${ext}` : `[name].[hash].${ext}`;
const cssLoaders = extra => {
const loaders = [
{
loader: MiniCssExtractPlugin.loader,
options: {
hmr: isDev,
reloadAll: true
},
},
{
loader: 'css-loader',
options: {
url: false
}
}
];
if (extra) {
loaders.push(extra)
}
return loaders
}
const babelOptions = preset => {
const opts = {
presets: [
'@babel/preset-env'
],
plugins: [
'@babel/plugin-proposal-class-properties'
]
}
if (preset) {
opts.presets.push(preset)
}
return opts
}
module.exports = {
mode: 'development',
entry: [
'@babel/polyfill',
'./src/js/index.js'
],
output: {
filename: 'js/' + filename('js'),
path: path.resolve(__dirname, 'dist')
},
devServer: {
port: 4250,
hot: isDev
},
devtool: isDev ? 'source-map' : '',
resolve: {
//extensions: ['.js', '.json', '.png'],
alias: {
'@views': path.resolve(__dirname, 'src/html/views'),
'@includes': path.resolve(__dirname, 'src/html/includes'),
'@scss': path.resolve(__dirname, 'src/scss'),
'@': path.resolve(__dirname, 'src'),
}
},
optimization: optimization(),
module: {
rules: [
{
test: /\.html$/,
//include: path.resolve(__dirname, 'src/html/includes'),
use: ['html-loader']
},
{
test: /\.hbs$/,
loader: 'handlebars-loader'
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: {
loader: 'babel-loader',
options: babelOptions()
}
},
{
test: /\.css$/,
use: cssLoaders()
},
{
test: /\.s[ac]ss$/,
use: cssLoaders('sass-loader')
},
{
test: /\.(png|jpg|svg|gif)$/,
exclude: path.resolve(__dirname, 'src/fonts'),
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: path.resolve(__dirname, 'dist/img')
}
}
]
},
{
test: /\.(ttf|otf|svg|woff|woff2|eot)$/,
exclude: path.resolve(__dirname, 'src/img'),
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: path.resolve(__dirname, 'dist/fonts')
}
}
]
},
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/html/views/index.hbs',
minify: {
collapseWhitespace: isProd
},
inject: false
}),
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
filename: 'css/' + filename('css'),
}),
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, 'src/favicon'),
to: path.resolve(__dirname, 'dist')
},
{
from: path.resolve(__dirname, 'src/fonts'),
to: path.resolve(__dirname, 'dist/fonts')
},
{
from: path.resolve(__dirname, 'src/img'),
to: path.resolve(__dirname, 'dist/img')
}
])
]
};
package.json:
{
"name": "Name",
"version": "1.0.0",
"description": "",
"private": true,
"scripts": {
"dev": "cross-env NODE_ENV=development webpack --mode development",
"build": "cross-env NODE_ENV=production webpack --mode production",
"watch": "cross-env NODE_ENV=development webpack --mode development --watch",
"start": "cross-env NODE_ENV=development webpack-dev-server --mode development --open"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.9.0",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/preset-env": "^7.9.5",
"babel-loader": "^8.1.0",
"clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^5.1.1",
"cross-env": "^7.0.2",
"css-loader": "^3.5.2",
"file-loader": "^6.0.0",
"html-loader": "^1.1.0",
"html-webpack-plugin": "^4.2.0",
"mini-css-extract-plugin": "^0.9.0",
"node-sass": "^4.13.1",
"optimize-css-assets-webpack-plugin": "^5.0.3",
"raw-loader": "^4.0.1",
"resolve-url-loader": "^3.1.1",
"sass-loader": "^8.0.2",
"style-loader": "^1.1.4",
"terser-webpack-plugin": "^2.3.5",
"webpack": "^4.42.1",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3"
},
"browserslist": "defaults",
"dependencies": {
"@babel/polyfill": "^7.8.7",
"bootstrap": "^4.4.1",
"handlebars": "^4.7.6",
"handlebars-loader": "^1.7.1",
"jquery": "^3.5.0",
"popper.js": "^1.16.1"
}
}
Solved!
I changed inject
to true
in HtmlWebpackPlugin
.
And I also added function generateHtmlPlugins()
to auto adding all html-files to plugin from ./src/html/views
.
src/html/includes/header.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Some Title</title>
</head>
<body>
<header>header</header>
src/html/includes/footer.html:
<footer>footer</footer>
</body>
</html>
src/html/views/index.html:
<%= _.template(require('./../includes/header-main.html'))() %>
<div class="content">
<img src="img/bg/desktop/bg-index-03.jpg" width="500" height="500"/>
<div class="imgtest"></div>
</div>
<%= _.template(require('./../includes/footer.html'))() %>
dist/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
<title>Some Title</title>
<link href="css/main.43f2075150009f972ed4.css" rel="stylesheet"></head>
<body>
<header>header</header>
<div class="content">
<img src="img/bg/desktop/bg-index-03.jpg" width="500" height="500">
<div class="imgtest"></div>
</div>
<footer>footer</footer>
<script src="js/vendors~main.43f2075150009f972ed4.js"></script><script src="js/main.43f2075150009f972ed4.js"></script></body>
</html>
webpack.config.js:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserWebpackPlugin = require('terser-webpack-plugin');
const OptimizeCssAssetWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const fs = require('fs');
const isDev = process.env.NODE_ENV === 'development';
const isProd = !isDev;
function generateHtmlPlugins(templateDir) {
const templateFiles = fs.readdirSync(path.resolve(__dirname, templateDir));
return templateFiles.map(item => {
const parts = item.split('.');
const name = parts[0];
const extension = parts[1];
return new HtmlWebpackPlugin({
filename: `${name}.html`,
template: path.resolve(__dirname, `${templateDir}/${name}.${extension}`),
minify: {
collapseWhitespace: isProd
},
inject: true,
})
})
}
const htmlPlugins = generateHtmlPlugins('./src/html/views')
const optimization = () => {
const config = {
splitChunks: {
chunks: 'all'
}
}
if (isProd) {
config.minimizer = [
new OptimizeCssAssetWebpackPlugin(),
new TerserWebpackPlugin()
]
}
return config
}
const filename = ext => isDev ? `[name].${ext}` : `[name].[hash].${ext}`;
const cssLoaders = extra => {
const loaders = [
{
loader: MiniCssExtractPlugin.loader,
options: {
hmr: isDev,
reloadAll: true
},
},
{
loader: 'css-loader',
options: {
url: false
}
}
];
if (extra) {
loaders.push(extra)
}
return loaders
}
const babelOptions = preset => {
const opts = {
presets: [
'@babel/preset-env'
],
plugins: [
'@babel/plugin-proposal-class-properties'
]
}
if (preset) {
opts.presets.push(preset)
}
return opts
}
module.exports = {
mode: 'development',
entry: [
'@babel/polyfill',
'./src/js/index.js'
],
output: {
filename: 'js/' + filename('js'),
path: path.resolve(__dirname, 'dist')
},
devServer: {
port: 4250,
hot: isDev
},
devtool: isDev ? 'source-map' : '',
resolve: {
//extensions: ['.js', '.json', '.png'],
alias: {
'@views': path.resolve(__dirname, 'src/html/views'),
'@includes': path.resolve(__dirname, 'src/html/includes'),
'@scss': path.resolve(__dirname, 'src/scss'),
'@': path.resolve(__dirname, 'src'),
}
},
optimization: optimization(),
module: {
rules: [
{
test: /\.html$/,
include: path.resolve(__dirname, 'src/html/includes'),
loader: 'html-loader',
options: {
minimize: false,
}
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: {
loader: 'babel-loader',
options: babelOptions()
}
},
{
test: /\.css$/,
use: cssLoaders()
},
{
test: /\.s[ac]ss$/,
use: cssLoaders('sass-loader')
},
{
test: /\.(png|jpg|svg|gif)$/,
exclude: path.resolve(__dirname, 'src/fonts'),
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: path.resolve(__dirname, 'dist/img')
}
}
]
},
{
test: /\.(ttf|otf|svg|woff|woff2|eot)$/,
exclude: path.resolve(__dirname, 'src/img'),
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: path.resolve(__dirname, 'dist/fonts')
}
}
]
},
]
},
plugins: [
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
filename: 'css/' + filename('css'),
}),
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, 'src/favicon'),
to: path.resolve(__dirname, 'dist')
},
{
from: path.resolve(__dirname, 'src/fonts'),
to: path.resolve(__dirname, 'dist/fonts')
},
{
from: path.resolve(__dirname, 'src/img'),
to: path.resolve(__dirname, 'dist/img')
}
])
].concat(htmlPlugins)
};