How do I add certain script tags inside <head> and <body> tags when using HtmlWebackPlugin
Asked Answered
F

8

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
        }
    })
Fishhook answered 11/7, 2018 at 12:39 Comment(1)
check another solution mentioned at: https://mcmap.net/q/501733/-how-to-add-a-js-file-with-webpackWashwoman
B
34

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>
Brythonic answered 11/7, 2018 at 15:6 Comment(11)
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 <script> tags with embedded js code in it into my html files. My use case: I have a multi page site and I would like to insert same, <script> snippet say it Google Analyitics into multiple pages at the <head> and <body> tags with js code embedded in it.Fishhook
@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.Brythonic
@sujeetb I've amended my original answer and added a Multi-Page Set-Up section. See if that'll do the trick for you.Brythonic
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 jsbin.com/ruleqekake/2/edit?jsFishhook
@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 <script> entry in the template that just points to a JS file that you can alter when ever.Brythonic
@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.Brythonic
No need to do all this stuff. There's a plugin wich can help in this case. npmjs.com/package/html-webpack-injectorIjssel
@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.Brythonic
@Brythonic based on your suggestion, I tried adding this at the bottom of my ejs template: <link href="<%= htmlWebpackPlugin.files.chunks.app.css[0] %>" rel="stylesheet">. 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?Lanitalank
@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.Brythonic
@Brythonic I've updated my question here with what you might need. Please take a look.Lanitalank
V
14

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

Vauntcourier answered 8/4, 2021 at 8:4 Comment(2)
It needs to be *.ejs file right?Fall
correct, file extension should be ejsVauntcourier
K
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'
    })
  ]
};
Keishakeisling answered 11/7, 2018 at 13:30 Comment(2)
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 <script> tags with embedded js code in it into my html files. My use case: I have a multi page site and I would like to insert same, <script> snippet say it Google Analyitics into multiple pages at the <head> and <body> tags with js code embedded in it.Fishhook
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 </head> tag or before the </body> tag. If you do not have such tags in your template - then simply add them.Keishakeisling
I
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>
Ijssel answered 4/2, 2019 at 19:26 Comment(3)
This did not work for me. All scripts were added to body, even the "_head" oneCoral
@Gordon Did you add new HtmlWebpackInjector() in your plugins array?Ijssel
@ArchitGarg I did, but I didn't seem to recongize the _head file. I've taken a different approach.Keshiakesia
I
3

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

Inelastic answered 8/2, 2021 at 6:56 Comment(1)
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....Fall
A
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!

Aeon answered 21/11, 2019 at 1:2 Comment(0)
E
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>
Erastes answered 19/12, 2020 at 14:56 Comment(0)
S
-3

use this settings.

template: root to your html file

new HtmlWebpackPlugin({
    title: "chucknorris-app",
    template: "./src/template.html",
}),
Spencer answered 18/5, 2020 at 12:52 Comment(1)
you might gave the answer to the wrong post because I have no idea how this answer helps to clear out the questionInspiratory

© 2022 - 2024 — McMap. All rights reserved.