Template in HTML, with Webpack, get error "variable" is not defined - javascript

I created template in index.html to generate html-code with js, code below. My Webpack configuration also below. When I run it with webpack-dev-server, I get error: title is not defined. Somehow webpack tries to resolve 'title' by self, instead of delegate it to 'lodash/template'. Please help me fix code, I'm in despair(.
import path from 'path';
import glob from 'glob';
import webpack from 'webpack';
import ExtractTextPlugin from 'extract-text-webpack-plugin';
import HtmlWebpackPlugin from 'html-webpack-plugin';
const inProduction = process.env.mode === 'production';
export default {
entry: {
app: [
'./src/scripts/main.js',
],
},
output: {
path: path.join(__dirname, 'build'),
filename: '[name].[chunkhash].js',
},
module: {
rules: [
{
test: /\.s[ac]ss$/,
use: ExtractTextPlugin.extract({
use: ['css-loader', 'sass-loader'],
fallback: 'style-loader',
}),
},
{
test: /\.js$/,
use: 'babel-loader',
exclude: '/node_modules',
},
],
},
plugins: [
new ExtractTextPlugin('[name].[chunkhash].css'),
new webpack.LoaderOptionsPlugin({
minimize: inProduction,
}),
new HtmlWebpackPlugin({
template: path.join(__dirname, './src/index.html'),
}),
],
};
import temp from 'lodash/template'
import data from './data';
const titlePicDiscHalf = temp(document.getElementById('titlePicDiscHalf').innerHTML);
document.write(titlePicDiscHalf({ title: 'Hello World!' }));
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
</head>
<body>
<script type="text/template" id="titlePicDiscHalf">
<div class="titlePicDiscHalf">
<div class="picture"></div>
<div class="title"><%=title%></div>
<div class="discription"></div>
<div class="buttons"></div>
</div>
</script>
</body>
</html>

The problem is that html-webpack-plugin uses the same template tags <%= %> to insert bundle information into template.
You have two options.
1. Change lodash.template delimiters
You could change delimiters used by client-side lodash/template to something else, so Webpack would ignore it. For example:
_.templateSettings.interpolate = /<\$=([\s\S]+?)\$>/g;
Check out this demo.
_.templateSettings.interpolate = /<\$=([\s\S]+?)\$>/g;
const temp = _.template
const titlePicDiscHalf = temp(document.getElementById('titlePicDiscHalf').innerHTML);
document.write(titlePicDiscHalf({ title: 'Hello World!' }));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>
<script type="text/template" id="titlePicDiscHalf">
<div class="titlePicDiscHalf">
<div class="picture"></div>
<div class="title">
<$=title$>
</div>
<div class="discription"></div>
<div class="buttons"></div>
</div>
</script>
2. Change html-webpack-plugin delimiters
Install ejs-loader separately and configure html-webpack-plugin to use it to load your template. There you can change delimiters to yours. It could look something like this:
plugins: [
new HtmlWebpackPlugin({
template: './index.html.ejs',
})
],
module: {
rules: [
{ test: /\.ejs$/, loader: 'ejs-loader', query: {
interpolate: /<\$=([\s\S]+?)\$>/g,
evaluate: /<\$([\s\S]+?)\$>/g,
}},
]
},
Now, you can configure your template with two different set of delimiters, one for client bundle lodash template and another for html-webpack-plugin:
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title><$= htmlWebpackPlugin.options.title $></title>
</head>
<body>
<script type="text/template" id="titlePicDiscHalf">
<div class="titlePicDiscHalf">
<div class="picture"></div>
<div class="title"><%= title %></div>
<div class="discription"></div>
<div class="buttons"></div>
</div>
</script>
</body>
</html>
Note, <title><$= htmlWebpackPlugin.options.title $></title> is used by webpack, and <div class="title"><%= title %></div> is by client-side lodash.

Just in case someone else falls in the same trap where one simply wants to use a static html file with some <script> inside and subsequently encountering an error like Referenceerror: variable is not defined.
Make sure you are not using JS template strings!
E.g. instead of doing something like
function addLoginStatus(user) {
document.querySelector('.login-status').textContent = `You logged in as user.firstName`;
}
use something like this:
function addLoginStatus(user) {
document.querySelector('.login-status').textContent = 'You logged in as ' + user.firstName;
}
I found that with template strings, the webpack-html-plugin template will naturally try to interpret them, which was not intended in my case.

Related

HTML Webpack Plugin <script> tag generated twice

If I run yarn run build then the HTMLWebpackPlugin will generate the index.html from a template file, but all my code runs twice because the script tag are added two times.
My index.html template file:
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= htmlWebpackPlugin.options.title %></title>
<%= htmlWebpackPlugin.tags.headTags %>
</head>
<body>
<div id="app"></div>
<%= htmlWebpackPlugin.tags.bodyTags %>
</body>
</html>
My index.html that is generated from HTMLWebpackPlugin:
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Website</title>
</head>
<body>
<div id="app"></div>
<script defer src="ecaecb1a919bc0a6e577.main.js"></script>
<script defer src="ecaecb1a919bc0a6e577.main.js"></script>
</body>
</html>
and my webpack.config.js:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: '[fullhash].main.js',
path: path.resolve(__dirname, 'dist'),
},
mode: "development",
devServer: {
contentBase: './dist',
port: 9000,
hot: true
},
plugins: [
new HtmlWebpackPlugin({
title: "My Website",
template: path.join(__dirname, "src/webpack_template.html"),
inject: "body",
hash: false
}),
new CleanWebpackPlugin()
],
devtool: 'inline-source-map',
module: {
rules: [
{
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ['#babel/preset-env']
}
}
}
]
}
};
So, my target is that one script tag will be at the end of the body.
Thank you very much for your help.
If you add tags to HTML, you must disable automatic injection and inject must be false in this case not "body".
Please check documentation
https://github.com/jantimon/html-webpack-plugin#options
true || 'head' || 'body' || false Inject all assets into the given template or templateContent. When passing 'body' all javascript resources will be placed at the bottom of the body element. 'head' will place the scripts in the head element. Passing true will add it to the head/body depending on the scriptLoading option. Passing false will disable automatic injections. - see the

Html Webpack Plugin renders JS module instead of HTML file

I'm trying to use an EJS loader and the Html Webpack Plugin to compile and output an HTML file but it keeps rendering a file like this:
module.exports = "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n </head>\n\n <body>\n <div id=\"root\"></div>\n </body>\n</html>\n"
Here's my webpack config:
module.exports = {
target: 'node',
entry: './src/server.tsx',
output: {
filename: 'server.js'
},
module: {
rules: [
{
test: /\.ejs$/,
use: {
loader: 'ejs-webpack-loader',
options: {
data: { example: 'test' }
}
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
filename: 'output.html',
inject: false,
template: path.join(rootDir, '../compiler/templates/index.ejs'),
{ example: 'test' },
minify: false
})
]
}
Here's my 'index.ejs' file:
<!DOCTYPE html>
<html lang="en">
<head>
<%- include ./test.ejs %>
</head>
<body>
<div id="root"></div>
</body>
</html>
And here's my 'test.ejs' file:
<meta charset="UTF-8">
I expect this to output an output.html file that looks like this:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="root"></div>
</body>
</html>
But instead I get this:
module.exports = "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n </head>\n\n <body>\n <div id=\"root\"></div>\n </body>\n</html>\n"
It looks like the ejs-webpack-loader is working and properly injects the partial, but then the HtmlWebpackPlugin is rendering JS instead of HTML.
How do I get the HtmlWebpackPlugin to render the HTML file properly?
You don't need a special loader (ejs-webpack-loader) in your config, since HtmlWebpackPlugin comes with ejs support out of the box.
Just try to remove it. :]

Injecting seperate script into HTML

say i have a public/index.html file
and then a separate html file called otherScript which is just a file containing <script> tags with a script in
how would I inject this scripts into my public/index.html file?
I want to use new HtmlWebpackPlugin
but in the example it just says:
new HtmlWebpackPlugin({
template: 'public/index.html'
inject: true
})
I can't see anywhere that I should link to the other html file?
If it's an HTML file, you can load it using a Webpack loader called raw-loader or you could use html-loader.
And use it like this:
Webpack.config
{
test: /\.html$/,
use: 'raw-loader'
}
Module
// import file
import htmlFile from 'myfile.html';
// insert the contents of file at end of body
var body = document.getElementsByTagName('body')[0];
body.insertAdjacentHTML('beforeEnd', htmlFile);
However, an HTML file full of script tags sounds like bad practice IMO.
If you want to inject the scripts to the page, I would suggest using a different method entirely.
One way would be to import the scripts as normal using import or require, or you could have an array of dependencies to load and load them all like this:
var dependencies = [
'path/to/script/1.js',
'path/to/script/2.js'
]
for(var i = 0; i < dependencies.length; i++) {
let script = document.createElement('script');
script.type = 'text/javascript';
script.src = dependencies[i];
document.getElementsByTagName('head')[0].appendChild(script);
}
You can use the html-webpack-tags-plugin plugin.
This is helpful if you want to enable cache busting on the script, which was the main reason I didn't want to just use the copy-webpack-plugin and a static script element.
webpack.dev.config.js
new HtmlWebpackTagsPlugin({
// this script must be loaded before all other scripts
append: false,
tags: ['src/myScript.js'],
}),
webpack.prod.config.js
new HtmlWebpackTagsPlugin({
// this script must be loaded before all other scripts
append: false,
tags: ['src/myScript.js'],
useHash: true,
addHash: (assetPath, hash) => {
const parts = assetPath.split('.');
parts[parts.length - 1] = `${hash}.${parts[parts.length - 1]}`;
return parts.join('.');
},
}),
There is no way to do that using webpack, specially with html-webpack-plugin. The property inject refeers to injecting all the scripts created by webpack into that html you refeered on the template property.
Yes, we can do this. I made some example, hope it will help you.
In webpack js:
const path = require('path');
const webpack = require('webpack');
const TerserPluign = require('terser-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
index: './src/assets/js/index.js',
about: './src/assets/js/about.js'
},
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, './build'),
// publicPath: 'my-domain/'
},
mode: 'none',
module: {
rules: [
{
test: /\.(jpg|png)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 60 * 1024 // max size 60 kilobytes.
}
}
},
{
test: /\.txt/,
type: 'asset/source',
},
{
test: /\.(scss|css)$/,
use: [
MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'
]
},
{
test: /\.(less)$/,
use: [
// 'style-loader', 'css-loader', 'less-loader'
MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'
]
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [ '#babel/env'],
plugins: [ '#babel/plugin-proposal-class-properties']
}
}
},
{
test: /\.hbs$/,
use: [
'handlebars-loader'
]
},
]
},
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('development'),
'global': {},
}),
new TerserPluign(), // To use reduce size of bundle files,
new MiniCssExtractPlugin({
filename: "[name].[contenthash].css"
}),
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: [
'**/*',
path.join(process.cwd(), 'dummyBuildFolder/**/*')
]
}),
new HtmlWebpackPlugin({
title: 'Default HTML',
filename: 'index.html',
template: 'src/templates/index.hbs',
description: 'this is meta description for index page',
chunks: ['index']
}),
new HtmlWebpackPlugin({
title: 'About Us HTML',
filename: 'about.html',
template: 'src/templates/about.hbs',
description: 'this is meta description for about us page',
chunks: ['about']
})
]
}
Above will generate two files, index.html and about.html
In index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Default HTML</title>
<meta name="description" content="this is meta description for index page" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<script defer src="index.22a0682faed73cf496ac.js"></script>
<link href="index.2ab526c55a3c67e01bfb.css" rel="stylesheet">
</head>
<body>
<div id="my_root" />
<div id="initialMessage"></div>
<div id="lipsumTxtId"></div>
</body>
</html>
In about.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>About Us HTML</title>
<meta name="description" content="this is meta description for about us page" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<script defer src="about.0d3ba2ce20763dbd16dc.js"></script>
<link href="about.352d245e3219b89be11d.css" rel="stylesheet"></head>
<body />
</html>

How to properly split common dependencies with webpack4

I am having difficulty configuring webpack4 to properly bundle shared dependencies.
I have two pages in my application (Page1 and Page2). Both require bootstrap, jquery as well as a custom JavaScript app called core.
Page 2 requires the same but also a custom JavaScript application called my-app and also lodash.
Since my core app will be included in all pages, I want to have jquery and bootstrap in the same bundle.
Since lodash is only required for pages running my-app, I want to include that dependency in the my-app bundle.
So I setup my app like this:
webpack.config.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
'core': './src/core/index.js',
'my-app': './src/my-app/index.js'
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
resolve: {
alias: {
jquery: 'jquery/src/jquery',
}
},
plugins: [
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery"
}),
],
mode: 'development',
}
page1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Page1</title>
<script src="dist/core.bundle.js"></script>
</head>
<body>
<h1>Page1</h1>
<span id="test"></span>
</body>
<script>
$(document).ready(function() {
$('#test').text('jQuery Works!');
});
</script>
</html>
page2.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Page1</title>
<script src="dist/core.bundle.js"></script>
<script src="dist/my-app.bundle.js"></script>
</head>
<body>
<h1>Page2</h1>
<span id="test"></span>
</body>
<script>
$(document).ready(function() {
$('#test').text('jQuery Works!');
});
</script>
</html>
(Full project: https://github.com/LondonAppDev/webpack-split-example)
When I run npx webpack, it creates core.bundle.js and my-app.bundle.js, however both of these include jquery.
Is it possible to put all "global" dependencies in core.bundle.js?
Just one thing to remember here, with webpack 4 you don't add vendor scripts as an entry to your webpack.config, just real entry scripts to your application.
WP will create an optimized bundle output for your app using the default settings.
You have to add vendor cache group to your config, in order to extract jQuery, Lodash, Bootstrap,Popper into a separate bundle:
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /node_modules/,
name: "vendor",
chunks: "all",
enforce: true
}
}
}
},

Simple Vue.js with webpack

I know basically nothing about javascript/webpack/npm/whatever but I am trying a simple Vue.js app.
It looks like this:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Vue Test</title>
</head>
<body>
<div class="vue-app">
{{ message }}
</div>
<script src="/static/testvue.bundle.js"></script>
</body>
</html>
main.js
import Vue from 'vue';
new Vue({
data: {
message: 'Testing Vue'
},
el: '.vue-app'
});
webpack.config.js
var path = require('path');
module.exports = {
entry: './main.js',
output: {
filename: 'testvue.bundle.js',
path: path.join(__dirname, '')
},
devServer: {
inline: true,
port: 8080
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
presets: ['es2015']
}
}
]
}
};
When I go to the webpage, it is blank and I see this in the "elements" in the console:
<!--function (e,n,r,o){return Ce(t,e,n,r,o,!0)}-->
Any idea what's going on and how to make it work? It's like it's trying to do something, but something doesn't line up. I've tried changing a few things that I have seen differently like using #vue-app instead of .vue-app or changing it to be under 'body' and then putting the {{message}} directly into the body but that doesn't help and I dont know what the difference is.
So, looks like the issue is that webpack wants the "ESM" version of Vue.
You can add some stuff to the webpack.config.js as per: https://v2.vuejs.org/v2/guide/installation.html
And then it seems to start working.
Code snippet
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
}
I am not sure what the difference is or why this matters though.
You need to return message in data.
new Vue({
data() {
return {
message: 'Testing Vue'
}
},
el: '.vue-app'
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.9/vue.min.js"></script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Vue Test</title>
</head>
<body>
<div class="vue-app">
{{ message }}
</div>
<script src="/static/testvue.bundle.js"></script>
</body>
</html>

Categories

Resources