How can I provide parameters for webpack html-loader interpolation?
Asked Answered
J

7

31

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

Jube answered 7/9, 2016 at 15:41 Comment(1)
The interpolate option was removed in html-loader 1.0.0 github.com/webpack-contrib/html-loader/blob/…Radford
O
19

Solution 1

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.

Solution 2

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.

Obligee answered 12/2, 2018 at 15:36 Comment(5)
Solution 1 worked for me but I had to export it in this way in the variables.js file: module.exports.hello = hello; otherwise it gave me some bindings error.Carberry
You can use HtmlWebpackPlugin variables if you stack up the following loaders in this sequence (they work from right to left): ['ejs-loader', 'extract-loader', 'html-loader']Unnecessarily
I've come to the conclusion that html-loader and the copy-webpack-plugin keep butting heads no matter what is attempted to do in this situation. sucks. If you have images in the same directory where you are trying to use html-loader, all war breaks out.Halliday
Thanks so much, it's exactly what I needed.Fernanda
Regarding #1, interpolate was removed and the docs don't have an equivalent example: github.com/webpack-contrib/html-loader/blob/…Amabel
C
4

You might laugh, but using default loaders provided with HTMLWebpackPlugin you could do string replacement on the HTML-partial file.

  1. index.html is ejs template (ejs is the default loader in HTMLWebpackPlugin)
  2. file.html is just an html string (loaded via html-loader also available by default with HTMLWebpackPlugin or maybe it comes with webpack?)

Setup

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>
Crucifixion answered 26/1, 2019 at 2:6 Comment(1)
This should be the accepted answer: I have also augmented this answer with my own as I feel like it was better addressed in an answer, not a comment as it added essential caveatsApollonius
C
2

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'})}
Copyedit answered 20/2, 2018 at 11:40 Comment(0)
P
1

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

Parallelism answered 14/9, 2019 at 13:53 Comment(0)
A
1

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.

Alight answered 28/2, 2020 at 7:5 Comment(1)
I think this anwers the question in the simplest way. Upvoted :-)Alie
A
0

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

Apollonius answered 30/9, 2019 at 8:23 Comment(0)
G
0

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) + ') + ';
	}) + "};";

}
Gadgetry answered 8/10, 2019 at 7:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.