jantimon / html-webpack-plugin

Simplifies creation of HTML files to serve your webpack bundles
MIT License
10.71k stars 1.31k forks source link

Allow the plugin to support loaders that don't resolve to a single function #1844

Closed ericmorand closed 7 months ago

ericmorand commented 7 months ago

The current approach of the plugin is to consider that a template loader will resolve to either a string, or a function that accepts one single parameter. Even though it works for most template loaders, it restricts the usage of loaders that return structured templates instead of single function - e.g. twing-loader that resolves a Twig template to a structured object of type TwingTemplate and that allows developers to render the template or to execute it (and piping the result) and to pass some custom extensions, tags, and so on.

Proposal

I believe that the plugin could be made more flexible if it allowed to pass an option that allow developers to actually generate and return that single-parameter function that the plugin expect.

Something like that;

new HtmlWebpackPlugin({
    template: resolvePath(__dirname, 'index.twig'),
    templateParameters: {
        foo: 'BAR'
    },
    templateRenderMethod: (template: TwingTemplate) => {
        return (templateParameters: any) => template.render(environment, templateParameters, {
               sandboxed: true
        });
    },
    minify: false
})

Here, the option named templateRenderMethod is a function that receives the resolved template as parameter (i.e. the newSource variable...but more on that later) and is expected to return the single-parameter function that the plugin requires.

The benefit here is that, in the body of this function, we are allowed to run the render function of the Twig template by passing it a custom environment and some custom options.

Implementation

My naive proof of concept consisted at replacing the last lines of the evaluateCompilationResult method with this:

if (this.options.templateRender) {
  return Promise.resolve(this.options.templateRender(newSource));
}
else {
  return typeof newSource === 'string' || typeof newSource === 'function'
    ? Promise.resolve(newSource)
    : Promise.reject(new Error('The loader "' + templateWithoutLoaders + '" didn\'t return html.'));
}

This seems extremely straightforward and I'm ready to provide a PR if this seems acceptable to the community. It should not create a breaking change since it involves passing a new option to the plugin to be enabled.

Here is the list of changes that I foresee:

Alternative solutions

It is perfectly possible that solutions already exist out of the box by using Webpack features that I don't know about. If it is the case, please forgive this proposal.

jantimon commented 7 months ago

you can could use a .js template to import and execute the twig file:

import template from "./index.twig";

export default function() {
   return template(1,2,3)
}

or even with args from templateRender

import template from "./index.twig";

export default function({arg1, arg2 }) {
   return template(arg1, arg2)
}
ericmorand commented 7 months ago

@jantimon well, this is amazing. Thanks a lot.