29

I'm using HtmlWebpackPlugin to generate HTML files with javascript.

Now I would like to add custom script at different parts of <head> and <body> tags

Example:

How do I,

  1. Add <script> alert('in head tag') </script> inside the <head> tag as the first child
  2. Add <script> alert('in body tag') </script> inside the <body> tag as the first child

Here is the snippet in my Webpack config

        new HtmlWebpackPlugin({
        hash: true,
        chunks: ["app"],
        filename: path.resolve(__dirname, "./public/pages/app.html"),
        title: "Title of webpage",
        template: path.resolve(__dirname, "./src/pages/app.page.html"),
        minify: {
            collapseWhitespace: true
        }
    })
Greeen Apple
  • 441
  • 1
  • 4
  • 12

8 Answers8

33

Your question is a bit confusing. It implies you want to add static script tags to your template. If that's the case you just need to go into your src/pages/app.page.html file and add those two script tags in the head and body.

What I'm guessing that you're asking is "How do I insert generated bundles in two different areas of my template?". If that's the case there's a section in the docs that mentions what data is passed to the template file:

"htmlWebpackPlugin": {
  "files": {
    "css": [ "main.css" ],
    "js": [ "assets/head_bundle.js", "assets/main_bundle.js"],
    "chunks": {
      "head": {
        "entry": "assets/head_bundle.js",
        "css": [ "main.css" ]
      },
      "main": {
        "entry": "assets/main_bundle.js",
        "css": []
      },
    }
  }
}

So if your entry looked like

entry: {
  head: './src/file1.js',
  body: './src/file2.js',
}

and your plugin was set to

new HtmlWebpackPlugin({
  template: './src/pages/app.page.ejs' // note the .ejs extension
})

then app.page.ejs should be able to access the data from the plugin and you can place those entries where ever you'd like. There's a large ejs example file in their repo. A simpler example, and one more specific to your use case would be:

<!DOCTYPE html>
<head>
  <% if(htmlWebpackPlugin.files.chunks.head) { %>
  <script src="<%= htmlWebpackPlugin.files.chunks.head.entry %>"></script>
  <% } %>
</head>
<body>
  <% if(htmlWebpackPlugin.files.chunks.body) { %>
  <script src="<%= htmlWebpackPlugin.files.chunks.body.entry %>"></script>
  <% } %>
</body>
</html>

Note that I'm not using files.js but rather files.chunks since you can access single files by entry name instead.


Multi-Page Set-Up

For a multi-page set-up your WP config could look like

const pages = [
  'home',
  'about',
];

const conf = {
  entry: {
    // other entries here
  }
  output: {
    path: `${ __dirname }/dist`,
    filename: 'scripts/[name].js'
  },
  plugins: [
    // other plugins here
  ]
};

// dynamically add entries and `HtmlWebpackPlugin`'s for every page
pages.forEach((page) => {
  conf.entry[page] = `./src/pages/${ page }.js`;
  conf.plugins.push(new HtmlWebpackPlugin({
    chunks: [page],
    // named per-page output
    filename: `${ __dirname }/dist/pages/${ page }.html`,
    googleAnalytics: { /* your props */ },
    // shared head scripts
    headScripts: [
      {
        src: 'scripts/jQuery.js'
      },
      {
        content: `
          console.log('hello world');
          alert('huzah!');
        `
      }
    ],
    // per-page html content
    pageContent: fs.readFileSync(`./src/pages/${ page }.html`, 'utf8'),
    // one template for all pages
    template: './src/pages/shell.ejs',
  }));
});

module.exports = conf;

The template would look something like

<!DOCTYPE html>
<head>
  <%
    for (var i=0; i<htmlWebpackPlugin.options.headScripts.length; i++) {
      var script = htmlWebpackPlugin.options.headScripts[i];
  %>
  <script
    <% if(script.src){ %>src="<%= script.src %>"<% } %>
  >
    <% if(script.content){ %><%= script.content %><% } %>
  </script>
  <% } %>
</head>
<body>
  <% if(htmlWebpackPlugin.options.pageContent) { %>
  <%= htmlWebpackPlugin.options.pageContent %>
  <% } %>

  <% for (var chunk in htmlWebpackPlugin.files.chunks) { %>
  <script src="<%= htmlWebpackPlugin.files.chunks[chunk].entry %>"></script>
  <% } %>

  <% if (htmlWebpackPlugin.options.googleAnalytics) { %>
  <script>
    (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
      (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
      m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
    })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
    <% if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
      ga('create', '<%= htmlWebpackPlugin.options.googleAnalytics.trackingId%>', 'auto');
      <% } else { throw new Error("html-webpack-template requires googleAnalytics.trackingId config"); }%>
    <% if (htmlWebpackPlugin.options.googleAnalytics.pageViewOnLoad) { %>
      ga('send', 'pageview');
    <% } %>
  </script>
  <% } %>
</body>
</html>
theOneWhoKnocks
  • 600
  • 6
  • 13
  • I sincerely thank and appreciate the effor you have put in asnwering my question. I'm already been doing this, my usese case is how do I inject snippets of – Greeen Apple Jul 12 '18 at 07:02
  • @sujeetb Perhaps I'm missing something. Your Code snippet implied that you have only one page that's being generated. I and the other replier mentioned how to add a static script entry - just open your template and inline it. It sounds like you want something like Handlebar's `partial` mechanism which would allow for sharing the same content in multiple files. – theOneWhoKnocks Jul 12 '18 at 13:03
  • 1
    @sujeetb I've amended my original answer and added a Multi-Page Set-Up section. See if that'll do the trick for you. – theOneWhoKnocks Jul 12 '18 at 14:17
  • That's a super cool answer and this absolutely works for me. Apologies for the question, but is there a way by which I could inject GoogleAnalyitics script into portions of the page without actually changing the templates. I'm sorry if it doesn't make any sense at call, I just didn't want to change templates all the time and rather change the config file for such purposes. Here is my webpack config https://jsbin.com/ruleqekake/2/edit?js – Greeen Apple Jul 13 '18 at 06:09
  • @sujeetb sure. In the example I posted you'll see a block for `htmlWebpackPlugin.options.googleAnalytics` you can put whatever you want in that Object and then key off of that in the template. For example, in the template you could just have `if( htmlWebpackPlugin.options.googleAnalytics.headScript) ` and then in the WP config you can have a template literal with whatever you want, or use `fs.readFileSync` to read a file in. You can also create a ` – theOneWhoKnocks Jul 13 '18 at 12:40
  • @sujeetb Also, don't forget to mark the answer as accepted if it worked for you, that way others can get usage from it if they have a similar issue to yours. – theOneWhoKnocks Jul 13 '18 at 12:42
  • No need to do all this stuff. There's a plugin wich can help in this case. https://www.npmjs.com/package/html-webpack-injector – Archit Garg Feb 04 '19 at 19:28
  • @ArchitGarg your plugin looks useful for simple use cases, but what does one do if they need control of where the chunks are placed? For example maybe they need a JS file to be in the middle of a couple script tags? Maybe if it could target a string token in the template that matches the chunk name it could be more flexible. – theOneWhoKnocks Feb 05 '19 at 13:47
  • @theOneWhoKnocks based on your suggestion, I tried adding this at the bottom of my `ejs` template: ``. While this works, the link that is automatically injected is still being injected. So now I'm left with this same css reference, twice in the html document. How do prevent HtmlWebpackPlugin from automatically injecting the css reference? – Daniel Birowsky Popeski Jul 12 '19 at 12:26
  • @Birowsky I'd have to see your template to help. Lacking that, I can only assume that you have a loop in your template that automatically adds all chunks, or possibly something in your WP config that automatically inserts chunks into the template. – theOneWhoKnocks Jul 12 '19 at 18:23
  • @theOneWhoKnocks I've updated [my question here](https://stackoverflow.com/q/57004003/592641) with what you might need. Please take a look. – Daniel Birowsky Popeski Jul 13 '19 at 10:56
11

I know I am late but here is how I resolved it. I might help someone else.

  1. I disabled auto inject of assets with inject:false.
new HtmlWebpackPlugin({
  hash: true, // hash for cache bursting
  template: "index.html", // source template
  minify: true, // should html file be minified?
  inject: false,
}) 
  1. Manually rendered assets in the template file.

css in the head

<% for (var css in htmlWebpackPlugin.files.css) { %>
<link href="<%= htmlWebpackPlugin.files.css[css] %>" rel="stylesheet">
<% } %>

js in the body

<% for (var js in htmlWebpackPlugin.files.js) { %>
<script src="<%= htmlWebpackPlugin.files.js[js] %>"></script>
<% } %>

You can literally do any kind of filtering/sorting here.

You can view the available options and how to use them here.

https://github.com/jaketrent/html-webpack-template/blob/master/index.ejs

M Tauqeer
  • 348
  • 2
  • 13
3

You can use template parameters like shown in the official example

var path = require('path');
var HtmlWebpackPlugin = require('../..');
var webpackMajorVersion = require('webpack/package.json').version.split('.')[0];
module.exports = {
  context: __dirname,
  entry: './example.js',
  output: {
    path: path.join(__dirname, 'dist/webpack-' + webpackMajorVersion),
    publicPath: '',
    filename: 'bundle.js'
  },
  plugins: [
    new HtmlWebpackPlugin({
      templateParameters: {
        'foo': 'bar'
      },
      template: 'index.ejs'
    })
  ]
};
IVO GELOV
  • 13,496
  • 1
  • 17
  • 26
  • 1
    I sincerely thank and appreciate the effor you have put in asnwering my question. I'm already been doing this, my usese case is how do I inject snippets of – Greeen Apple Jul 12 '18 at 07:02
  • If you need Google analytics - open the `src/pages/app.page.html` in your favorite editor and put the desired script tag and its js code embedded in it just before the closing `` tag or before the `

    ` tag. If you do not have such tags in your template - then simply add them.

    – IVO GELOV Jul 12 '18 at 08:40
3

I came across the same problem that's why I created a plugin.

  • HtmlWebpackInjector - A HtmlWebpackPlugin helper to inject some chunks to head

  • It works with HtmlWebpackPlugin and by just adding _head in the name of chunk, it automaticlly injects the chunk in the head.

const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackInjector = require('html-webpack-injector');

module.exports = {
  entry: {
    index: "./index.ts",
    index_head: "./index.css" // add "_head" at the end to inject in head.
  },
  output: {
    path: "./dist",
    filename: "[name].bundle.js"
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./index.html",
      filename: "./dist/index.html",
      chunks: ["index", "index_head"]
    }),
    new HtmlWebpackInjector()
  ]
}

This automatically injects index chunk to the body and index_head to the head of the html document. Final html looks like:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Archit's App</title>
    <script type="text/javascript" src="index_head.bundle.js"></script> <--injected in head
  </head>
  </head>
  <body>
    <script src="index_bundle.js"></script> <--injected in body
  </body>
</html>
Archit Garg
  • 2,908
  • 2
  • 14
  • 22
2

I apologize for necro-ing your question, but I had the same problem and was brought here.

So...I made a plugin. And it seems to work.

This(as of 2019-11-20) might require you to uninstall html-webpack-plugin(current stable), then install html-webpack-plugin@next.

TL;DR:

I made a plugin that replaces or inserts text in the htmlWebpackPlugin output. That means any text, anywhere, as long as what you're searching for is unique on the page(like a </body> tag).

Here's how

html-webpack-plugin gives hooks for it's compilation process. I made a plugin that searches the output string(after it's been compiled) and adds a custom string either before, after, or replacing the one that was searched.

Here's why

My problem was creating a minimal-pain Wordpress theme framework with Webpack that automates the more tedious parts. (I know it's a mouthful)

I needed to inject an async script tag for browser-sync to connect with the page. But(like you) I couldn't find a way to universally attach a script to the page without a bit of boilerplate.

So I made a plugin that put the string I needed into each file that contained the string </body>, because that would mean it was a full-page template and I wanted each full page template to automatically refresh when I updated the source file.

It works!

The only issue I've found is having to escape an already escaped backslash, because the output is run through a compiler before actually being html in the browser.

So just be warned you might have to fudge it a bit, and there are no doubt more problems like that someone will run into.

But I'll go out on a limb and say this looks like the solution to what you were originally asking.

Again, the plugin:

https://github.com/smackjax/html-webpack-inject-string-plugin

If it's not what you're looking for or you have problems, let me know!

smackjax
  • 91
  • 1
  • 3
1

Maybe you can use html-webpack-plugin/template-option and raw-loader.

BTW, you need to use default property, if you get [object Module] result.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <%= require('!!raw-loader!./common/file1').default %>
    <title>Document</title>
</head>
<body>
    <%= require('!!raw-loader!./common/file2').default %>
</body>
</html>
feng zhang
  • 1,193
  • 7
  • 8
1

For me the easiest way is to do this:

// webpüack.config.js
new HtmlWebpackPlugin({
    template: 'index.html',
    templateParameters: {
        foo: process.env.BAR,
    },
}),
<!-- index.html -->
<% if (foo) { %>
    <script src="bar.min.js"></script>
<% } %>

Works like a charm

pgalle
  • 216
  • 3
  • 13
  • Looks like it only supports `*.ejs` and some other template files...? It took me some time to figure out why my `index.html` does not work.... – Arst Jul 03 '23 at 10:50
-3

use this settings.

template: root to your html file

new HtmlWebpackPlugin({
    title: "chucknorris-app",
    template: "./src/template.html",
}),
las vega
  • 3
  • 2
  • you might gave the answer to the wrong post because I have no idea how this answer helps to clear out the question – evasyuk May 01 '21 at 09:27