Как я могу предоставить параметры для интерполяции webpack html-загрузчика?

В документации html-загрузчика есть этот пример

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>

Откуда берется "список"? Как я могу предоставить параметры для области интерполяции?

Я хотел бы сделать что-то вроде шаблона-строки-загрузчик делает:

var template = require("html?interpolate!./file.html")({data: '123'});

а затем в file.html

<div>${scope.data}</div>

Но это не работает. Я попытался смешать загрузчик шаблонов с загрузчиком html, но он не работает. Я мог использовать только шаблон-загрузчик строк, но тогда изображения в HTML не преобразуются веб-пакетом.

Есть идеи? Спасибо

2 ответа

Решение 1

Я нашел другое решение, используя html-loader с interpolate вариант.

https://github.com/webpack-contrib/html-loader

{ test: /\.(html)$/,
  include: path.join(__dirname, 'src/views'),
  use: {
    loader: 'html-loader',
    options: {
      interpolate: true
    }
  }
}

И затем в html-странице вы можете импортировать переменные html и javascript.

<!-- 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>

Единственным недостатком является то, что вы не можете импортировать другие переменные из HtmlWebpackPlugin как это <%= htmlWebpackPlugin.options.title %> (по крайней мере, я не могу найти способ импортировать их), но для меня это не проблема, просто напишите название в html или используйте отдельный файл javascript для переменных дескриптора.

Решение 2

Старый ответ

Не уверен, что это правильное решение для вас, но я поделюсь своим рабочим процессом (проверено в Webpack 3).

Вместо html-loader Вы можете использовать этот плагин http://github.com/bazilio91/ejs-compiled-loader:

{ test: /\.ejs$/, use: 'ejs-compiled-loader' }

Измени свой .html файлы в .ejs и ваш HtmlWebpackPlugin указать направо .ejs шаблон:

new HtmlWebpackPlugin({
    template: 'src/views/index.ejs',
    filename: 'index.html',
    title: 'Home',
    chunks: ['index']
})

Вы можете импортировать частичные, переменные и активы в .ejs файлы:

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>

Примечание: когда вы включаете частичное, путь должен быть относительно корня вашего проекта.

Вы можете смеяться, но используя загрузчики по умолчанию, предоставляемые с HTMLWebpackPlugin, вы можете выполнить замену строки в частичном файле HTML.

  1. index.html - это шаблон ejs (ejs является загрузчиком по умолчанию в HTMLWebpackPlugin)
  2. file.html - это просто строка html (загружается через html-загрузчик, также доступный по умолчанию с HTMLWebpackPlugin или, может быть, он поставляется с веб-пакетом?)

Настроить

Просто используйте шаблоны ejs по умолчанию, предоставленные в HTMLWebpackPlugin

new HtmlWebpackPlugin({
    template: 'src/views/index.ejs',
    filename: 'index.html',
    title: 'Home',
    chunks: ['index'],
    templateParameters(compilation, assets, options) {
        return {
            foo: 'bar'
        }
    }
})

Вот мой файл ejs верхнего уровня

// 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>

Вот файл file.html, который html-loader экспорт в виде строки.

// file.html 

<h1>${foo}</h1>

Усы-загрузчик сделал работу за меня:

var html = require('mustache-loader!html-loader?interpolate!./index.html')({foo:'bar'});

Тогда в вашем шаблоне вы можете использовать {{foo}}и даже вставить другие шаблоны

<h1>{{foo}}</h1>
${require('mustache-loader!html-loader?interpolate!./partial.html')({foo2: 'bar2'})}

Если вы используете шаблонизатор из htmlWebpackPlugin частично вы можете использовать так:

  <!-- 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,
      }),
    ],
  }

необходимость npm i ejs-loader

С помощью html-loader с interpolate, вы можете импортировать переменные из своегоwebpack.config.js используя 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'' интерполяции принимают любое выражение JavaScript, но область, в которой оцениваются эти выражения, по умолчанию не заполняется какими-либо из ваших параметров конфигурации. DefinePlugin добавляет значения к этой глобальной области. EnvironmentPlugin также может использоваться для заполнения значений в process.env.

Вы можете сделать это самостоятельно: в папке плагина html-loader (в index.js) замените код этим

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

}

Я чувствую, что potench"сек ответ выше должен быть приемлемым один, но он приходит с оговоркой:

Предупреждение: ответ заменяетhtmlWebpackPlugin.optionsобъект по умолчанию. Предложите дополнить, а не заменить

function templateParametersGenerator (compilation, assets, options) {
  return {
    compilation: compilation,
    webpack: compilation.getStats().toJson(),
    webpackConfig: compilation.options,
    htmlWebpackPlugin: {
      files: assets,
      options: options,
      // your extra parameters here
    }
  };
}

Источник (и): 1 - https://github.com/jantimon/html-webpack-plugin/blob/8440e4e3af94ae5dced4901a13001c0628b9af87/index.js2 - https://github.com/jantimon/html-webpack-plugin/issues/1004

Другие вопросы по тегам