Reputation: 311
In the html-loader documentation there is this example
require("html?interpolate=require!./file.ftl");
<#list list as list>
<a href="${list.href!}" />${list.name}</a>
</#list>
<img src="${require(`./images/gallery.png`)}">
<div>${require('./components/gallery.html')}</div>
Where does "list" come from? How can I provide parameters to the interpolation scope?
I would like to do something like template-string-loader does:
var template = require("html?interpolate!./file.html")({data: '123'});
and then in file.html
<div>${scope.data}</div>
But it doesn't work. I have try to mix the template-string-loader with the html-loader but it doesn't works. I could only use the template-string-loader but then the images in the HTML are not transformed by webpack.
Any ideas? Thank you
Upvotes: 31
Views: 24631
Reputation: 1919
Using html-loader
with interpolate
, you can import variables from your webpack.config.js
by using DefinePlugin
.
// webpack.config.js:
module.exports = {
module: {
rules: [
{
test: /\.html$/,
loader: 'html-loader',
options: {
interpolate: true
}
}
],
},
plugins: [
new DefinePlugin({
VARNAME: JSON.stringify("here's a value!")
})
]
};
// index.html
<body>${ VARNAME }</body>
html-loader
's interpolations accept any JavaScript expression, but the scope that those expressions are evaluated in aren't populated with any of your configuration options by default. DefinePlugin
adds values to that global scope. EnvironmentPlugin
could also be used to populate values in process.env
.
Upvotes: 1
Reputation: 3842
You might laugh, but using default loaders provided with HTMLWebpackPlugin you could do string replacement on the HTML-partial file.
Just use the default ejs templating provided in HTMLWebpackPlugin
new HtmlWebpackPlugin({
template: 'src/views/index.ejs',
filename: 'index.html',
title: 'Home',
chunks: ['index'],
templateParameters(compilation, assets, options) {
return {
foo: 'bar'
}
}
})
Here's my top level ejs file
// index.html
<html lang="en" dir="ltr">
<head>
<title><%=foo%></title>
</head>
<body>
<%
var template = require("html-loader!./file.html");
%>
<%= template.replace('${foo}',foo) %>
</body>
</html>
Here's file.html, which html-loader
exports as a string.
// file.html
<h1>${foo}</h1>
Upvotes: 4
Reputation: 21
You can make it on your own: In html-loader plugin folder (in index.js) replace code by this
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
var htmlMinifier = require("html-minifier");
var attrParse = require("./lib/attributesParser");
var loaderUtils = require("loader-utils");
var url = require("url");
var assign = require("object-assign");
var compile = require("es6-templates").compile;
function randomIdent() {
return "xxxHTMLLINKxxx" + Math.random() + Math.random() + "xxx";
}
function getLoaderConfig(context) {
var query = loaderUtils.getOptions(context) || {};
var configKey = query.config || 'htmlLoader';
var config = context.options && context.options.hasOwnProperty(configKey) ? context.options[configKey] : {};
delete query.config;
return assign(query, config);
}
module.exports = function(content) {
this.cacheable && this.cacheable();
var config = getLoaderConfig(this);
var attributes = ["img:src"];
if(config.attrs !== undefined) {
if(typeof config.attrs === "string")
attributes = config.attrs.split(" ");
else if(Array.isArray(config.attrs))
attributes = config.attrs;
else if(config.attrs === false)
attributes = [];
else
throw new Error("Invalid value to config parameter attrs");
}
var root = config.root;
var links = attrParse(content, function(tag, attr) {
var res = attributes.find(function(a) {
if (a.charAt(0) === ':') {
return attr === a.slice(1);
} else {
return (tag + ":" + attr) === a;
}
});
return !!res;
});
links.reverse();
var data = {};
content = [content];
links.forEach(function(link) {
if(!loaderUtils.isUrlRequest(link.value, root)) return;
if (link.value.indexOf('mailto:') > -1 ) return;
var uri = url.parse(link.value);
if (uri.hash !== null && uri.hash !== undefined) {
uri.hash = null;
link.value = uri.format();
link.length = link.value.length;
}
do {
var ident = randomIdent();
} while(data[ident]);
data[ident] = link.value;
var x = content.pop();
content.push(x.substr(link.start + link.length));
content.push(ident);
content.push(x.substr(0, link.start));
});
content.reverse();
content = content.join("");
if (config.interpolate === 'require'){
var reg = /\$\{require\([^)]*\)\}/g;
var result;
var reqList = [];
while(result = reg.exec(content)){
reqList.push({
length : result[0].length,
start : result.index,
value : result[0]
})
}
reqList.reverse();
content = [content];
reqList.forEach(function(link) {
var x = content.pop();
do {
var ident = randomIdent();
} while(data[ident]);
data[ident] = link.value.substring(11,link.length - 3)
content.push(x.substr(link.start + link.length));
content.push(ident);
content.push(x.substr(0, link.start));
});
content.reverse();
content = content.join("");
}
if(typeof config.minimize === "boolean" ? config.minimize : this.minimize) {
var minimizeOptions = assign({}, config);
[
"removeComments",
"removeCommentsFromCDATA",
"removeCDATASectionsFromCDATA",
"collapseWhitespace",
"conservativeCollapse",
"removeAttributeQuotes",
"useShortDoctype",
"keepClosingSlash",
"minifyJS",
"minifyCSS",
"removeScriptTypeAttributes",
"removeStyleTypeAttributes",
].forEach(function(name) {
if(typeof minimizeOptions[name] === "undefined") {
minimizeOptions[name] = true;
}
});
content = htmlMinifier.minify(content, minimizeOptions);
}
if(config.interpolate && config.interpolate !== 'require') {
// Double escape quotes so that they are not unescaped completely in the template string
content = content.replace(/\\"/g, "\\\\\"");
content = content.replace(/\\'/g, "\\\\\'");
content = JSON.stringify(content);
content = '`' + content.substring(1, content.length - 1) + '`';
//content = compile('`' + content + '`').code;
} else {
content = JSON.stringify(content);
}
var exportsString = "module.exports = function({...data}){return ";
if (config.exportAsDefault) {
exportsString = "exports.default = function({...data}){return ";
} else if (config.exportAsEs6Default) {
exportsString = "export default function({...data}){return ";
}
return exportsString + content.replace(/xxxHTMLLINKxxx[0-9\.]+xxx/g, function(match) {
if(!data[match]) return match;
var urlToRequest;
if (config.interpolate === 'require') {
urlToRequest = data[match];
} else {
urlToRequest = loaderUtils.urlToRequest(data[match], root);
}
return ' + require(' + JSON.stringify(urlToRequest) + ') + ';
}) + "};";
}
Upvotes: 0
Reputation: 692
I feel that Potench's's answer above should be the accepted one, but it comes with a caveat:
Warning: the answer replaces htmlWebpackPlugin.options
default object. Suggest augmenting, not replacing
function templateParametersGenerator (compilation, assets, options) {
return {
compilation: compilation,
webpack: compilation.getStats().toJson(),
webpackConfig: compilation.options,
htmlWebpackPlugin: {
files: assets,
options: options,
// your extra parameters here
}
};
}
Source(s): 1 - https://github.com/jantimon/html-webpack-plugin/blob/8440e4e3af94ae5dced4901a13001c0628b9af87/index.js#L719-L729 2 - https://github.com/jantimon/html-webpack-plugin/issues/1004#issuecomment-411311939
Upvotes: 0
Reputation: 61
if you use template engine from htmlWebpackPlugin
in partial, you can use like this:
<!-- index.html -->
<body>
<div id="app"></div>
<%= require('ejs-loader!./partial.gtm.html')({ htmlWebpackPlugin }) %>
</body>
<!-- partial.gtm.html -->
<% if (GTM_TOKEN) { %>
<noscript>
<iframe
src="https://www.googletagmanager.com/ns.html?id=<%= GTM_TOKEN %>"
height="0"
width="0"
style="display:none;visibility:hidden"
></iframe>
</noscript>
<% } %>
// webpack.config.json
{
plugins: [
new webpack.DefinePlugin({
GTM_TOKEN: process.env.GTM_TOKEN,
}),
],
}
need npm i ejs-loader
Upvotes: 1
Reputation: 2588
I found another solution, using html-loader
with interpolate
option.
https://github.com/webpack-contrib/html-loader#interpolation
{ test: /\.(html)$/,
include: path.join(__dirname, 'src/views'),
use: {
loader: 'html-loader',
options: {
interpolate: true
}
}
}
And then in html page you can import partials html and javascript variables.
<!-- Importing top <head> section -->
${require('./partials/top.html')}
<title>Home</title>
</head>
<body>
<!-- Importing navbar -->
${require('./partials/nav.html')}
<!-- Importing variable from javascript file -->
<h1>${require('../js/html-variables.js').hello}</h1>
<!-- Importing footer -->
${require('./partials/footer.html')}
</body>
The only downside is that you can't import other variables from HtmlWebpackPlugin
like this <%= htmlWebpackPlugin.options.title %>
(at least I can't find a way to import them) but for me it's not an issue, just write the title in your html or use a separate javascript file for handle variables.
Old answer
Not sure if this is the right solution for you but I'll share my workflow (tested in Webpack 3).
Instead of html-loader
you can use this plugin github.com/bazilio91/ejs-compiled-loader:
{ test: /\.ejs$/, use: 'ejs-compiled-loader' }
Change your .html
files in .ejs
and your HtmlWebpackPlugin
to point to the right .ejs
template:
new HtmlWebpackPlugin({
template: 'src/views/index.ejs',
filename: 'index.html',
title: 'Home',
chunks: ['index']
})
You can import partials, variables, and assets in .ejs
files:
src/views/partials/head.ejs
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
src/js/ejs_variables.js
:
const hello = 'Hello!';
const bye = 'Bye!';
export {hello, bye}
src/views/index.ejs
:
<% include src/views/partials/head.ejs %>
<body>
<h2><%= require("../js/ejs_variables.js").hello %></h2>
<img src=<%= require("../../assets/sample_image.jpg") %> />
<h2><%= require("../js/ejs_variables.js").bye %></h2>
</body>
A note, when you include a partial the path must be relative to the root of your project.
Upvotes: 19
Reputation: 29
mustache-loader did the work for me:
var html = require('mustache-loader!html-loader?interpolate!./index.html')({foo:'bar'});
Then in your template you can use {{foo}}
, and even insert other templates
<h1>{{foo}}</h1>
${require('mustache-loader!html-loader?interpolate!./partial.html')({foo2: 'bar2'})}
Upvotes: 2