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

Patrick Lafrance
  • 311
  • 1
  • 3
  • 5
  • 1
    The `interpolate` option was removed in **html-loader** 1.0.0 https://github.com/webpack-contrib/html-loader/blob/7aa1e4abe23426a9bd14a22fae632a695598bdba/CHANGELOG.md#100-2020-03-19 – Cichy Jun 21 '20 at 17:32

7 Answers7

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.

pldg
  • 2,427
  • 3
  • 22
  • 37
  • 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. – MrAn3 Sep 27 '18 at 15:41
  • 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']` – Ed'ka Apr 28 '19 at 05:01
  • 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. – klewis Oct 18 '19 at 15:20
  • Thanks so much, it's exactly what I needed. – diogo Mar 12 '20 at 14:53
  • 5
    Regarding #1, `interpolate` was removed and the docs don't have an equivalent example: https://github.com/webpack-contrib/html-loader/blob/7aa1e4abe23426a9bd14a22fae632a695598bdba/CHANGELOG.md#100-2020-03-19 – Leonel Sanches da Silva Jun 14 '20 at 18:52
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>
potench
  • 3,802
  • 1
  • 28
  • 39
  • 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 caveats – ortonomy Sep 30 '19 at 08:25
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'})}
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

Nermo
  • 61
  • 6
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.

NReilingh
  • 1,730
  • 17
  • 32
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

ortonomy
  • 653
  • 10
  • 18
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) + ') + ';
 }) + "};";

}