Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamically loading Webpack assets using loader plugin #1

Open
Joaqim opened this issue May 16, 2024 · 3 comments
Open

Dynamically loading Webpack assets using loader plugin #1

Joaqim opened this issue May 16, 2024 · 3 comments

Comments

@Joaqim
Copy link

Joaqim commented May 16, 2024

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
Copy link
Owner

noway commented May 16, 2024

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
Copy link
Author

Joaqim commented May 16, 2024

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
Copy link
Owner

noway commented May 16, 2024

@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 :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants