noway / htb

Htb.js — a 50-line HTML template engine that uses JavaScript syntax.
https://www.npmjs.com/package/htb
MIT License
13 stars 0 forks source link

Dynamically loading Webpack assets using loader plugin #1

Open Joaqim opened 4 months ago

Joaqim commented 4 months ago

I would like a functionality to import static assets using webpack like simple-pug-loader - Webpack Plugin

Found this Blog post - Adding asset files to webpack for use-case. See under heading: Adding asset files directly to HTML file where the author uses 'pug', a similar html template library to yours.

I'm currently using your library like so:

// index.htb.js
const Htb = require("htb");

module.exports = (title, withBundleAnalyzer = false) => ((
  Htb('!DOCTYPE', { html: true })
  ('html', {}, () => [
    Htb('head', {}, () => [
      Htb('meta', { charset: 'utf-8' }),
      Htb('link', { rel: "icon", href: "/favicon.ico" }),
      Htb('meta', { 
        name: 'viewport',
        content: 'width=device-width, initial-scale=1',
       }),
      Htb('title', {}, title),
    ]),
    Htb('body', {}, () => [
      Htb('noscript', {}, "You need to enable JavaScript to run this app."),
      withBundleAnalyzer ? 
          Htb('a', {href: '/report.html' ,style: "background: pink;" }, 'Bundle Analyzer') 
        :  Htb('a'),
      Htb('div', {id: 'app'}, ''),
    ]),
    Htb('a', {href: '/bundle.js.LICENSE.txt'}, 'LICENSES')
  ])
).html);

Where, unless I'm using CopyWebpackPlugin there is no guarantee that bundle.js.LICENSES.txt would exists, furthermore, I'm looking to replace a static bundle.js.LICENSE.txt to using the dynamically created one from Webpack, which could maybe be passed as an asset from webpack config inte index.htb.js, that might be the solution I'm looking at currently.

NOTE: The current usage of LICENSE.txt as a pure link is just temporary, I could solve this particular issue by creating another htb.js template for 'license.html', but the problem remains in dynamically loading the content or getting the src of LICENSE.txt in the htb template.

Also, for conditionals, how would I add an empty Htb() element as a fallback?


Aside from that, my immediate usage of htb has been positive; I previously used this snippet in my webpack config:

      new HtmlWebpackPlugin({
        templateContent: ({ htmlWebpackPlugin }) =>
          '<!DOCTYPE html><html><head><meta charset="utf-8"><title>' +
          htmlWebpackPlugin.options.title +
          '</title></head><body>' +
            '<a href="/report.html" style="background: pink;">Bundle Analyzer</a>' +
          '<div id="app"></div>' +
          '</body></html>',
        filename: "index.html",
      }),

For now though, I'm still using the HtmlWebpackPlugin:

const createIndexHTML = require("./index.htb.js");

...

      new HtmlWebpackPlugin({
        templateContent: ({ htmlWebpackPlugin }) => createIndexHTML(htmlWebpackPlugin.options.title, true),
        filename: "index.html"
     })

But I would like to see a more streamlined integration.

noway commented 4 months ago

hi @joaqim this is great.

Where, unless I'm using CopyWebpackPlugin there is no guarantee that bundle.js.LICENSES.txt would exists, furthermore, I'm looking to replace a static bundle.js.LICENSE.txt to using the dynamically created one from Webpack, which could maybe be passed as an asset from webpack config inte index.htb.js, that might be the solution I'm looking at currently.

Yeah, sounds like you'd want to pass in licenseTxtPath into your createIndexHTML function. I would suggest doing something like this:

// index.htb.js
const Htb = require("htb");

module.exports = (title, licenseTxtPath, withBundleAnalyzer = false) => ((
  Htb('!DOCTYPE', { html: true })
  ('html', {}, () => [
    Htb('head', {}, () => [
      Htb('meta', { charset: 'utf-8' }),
      Htb('link', { rel: "icon", href: "/favicon.ico" }),
      Htb('meta', { 
        name: 'viewport',
        content: 'width=device-width, initial-scale=1',
       }),
      Htb('title', {}, title),
    ]),
    Htb('body', {}, () => [
      Htb('noscript', {}, "You need to enable JavaScript to run this app."),
      withBundleAnalyzer ? 
          Htb('a', {href: '/report.html' ,style: "background: pink;" }, 'Bundle Analyzer') 
        :  Htb('a'),
      Htb('div', {id: 'app'}, ''),
    ]),
    Htb('a', {href: licenseTxtPath}, 'LICENSES')
  ])
).html);

You could perhaps also wrap createIndexHTML into a function that checks that licenseTxtPath exists. Something like that:

function createIndexHTMLWithCheck(title, licenseTxtPath) {
  if (fs.existsSync(licenseTxtPath)) { // check that licenseTxtPath exists
    createIndexHTML(title, licenseTxtPath);
  }
  else {
    throw new Error(`${licenseTxtPath} was not found`)
  }
}

Also, for conditionals, how would I add an empty Htb() element as a fallback?

I'd recommend to use an empty string. So in your case, it would be something like:

withBundleAnalyzer ? 
  Htb('a', {href: '/report.html' ,style: "background: pink;" }, 'Bundle Analyzer') 
  :  '',

Thanks a lot for the feedback!

Joaqim commented 4 months ago

Yes, hbs is functional enough for my usage, and when used with HtmlWebpackPlugin it seems to work very well with their related plugins, specifically this one which solves most of my issues with dynamic assets: HtmlWebpackTagsPlugin

Here's a simple adaption of their example:

plugins: [
  new CopyWebpackPlugin([
    { from: 'node_modules/bootstrap/dist/js', to: 'js/'},
    { from: 'node_modules/bootstrap/dist/css', to: 'css/'},
    { from: 'node_modules/bootstrap/dist/fonts', to: 'fonts/'}
  ]),
  new HtmlWebpackPlugin({
     templateContent: htb.html,
     filename: 'index.html'
  }),
  new HtmlWebpackTagsPlugin({
    append: true,
    tags: [
      '/js/bootstrap.min.js',
      '/css/bootstrap.min.css',
      '/css/bootstrap-theme.min.css',
      {
        path: 'https://fonts.googleapis.com/css?family=Material+Icons',
        type: 'css'
      }
    ]
  })

Where the resulting header would presumably contain:

...
<script defer src="/js/bootstrap.min.js"></script>
<link href="/css/bootstrap.min.css" rel="stylesheet" >
<link href="/css/bootstrap-theme.min.css" rel="stylesheet" >
<link href="https://fonts.googleapis.com/css?family=Material+Icons" rel="stylesheet" >
...

While still retaining the initial structure from the html generated by hbs.


While these examples are quite simple and could be done in our hbs template directly, the specific requirement I had was that Webpack should be aware of the assets that hbs use and therefore ensures that they are included in any extending plugins that look at webpack.compilation.assets.

noway commented 4 months ago

@joaqim that's cool! I want to keep htb.ts lightweight, so I'm not sure it should have any "extensions" or "hooks" which check output. I think it's best you wrap your template generator function in a checker function that checks data before feeding it to Htb.

Or perhaps you want Htb to output extra metadata alongside .html? That's kinda interesting.

Feel free to prototype something! Htb.js is open source and forks are welcome. https://github.com/noway/htb/blob/main/htb.ts If you hack something together that implements your idea I would love to have a look!

Thank you :-)