jantimon/html-webpack-plugin

Pass data to loader

rohit-gohri opened this issue ยท 6 comments

Is your feature request related to a problem? Please describe.
I'm using html-webpack-plugin to generate multiple static pages, all of them using a single template. Currently I'm rendering them differently in a custom loader by passing an id in the query part of the template and using that to render pages with different data.
This seems like a very hacky approach and instead I would like to specify the data where I'm creating pages itself, i.e. when adding a new HtmlWebpackPlugin instance.

Describe the solution you'd like
I would like to be able to pass this data via templateParams or a new option and have it passed to the loader's options/context.

Describe alternatives you've considered
Passing an id in query part of template and doing the data fetching in a custom loader.

Additional context
This is basically like an SSG setup but very simple, and I'm wondering if this is a supported use case or I should look for other tools?

@rohit-gohri
you can try to use new modern html-bundler-webpack-plugin.
Here is described exactly your usage case: How to pass different data into loader by multipage configuration.

@webdiscus Thanks! I didn't know about that, that does simplify my setup by a lot. But I still don't see my usecase which is that I have 1 template file - src/views/pages/[id].template.html and I want to generate 3 pages out of it from diff data - /pages/1.html, /pages/2.html, /pages/3.html. Since my template is same the resourcePath is the same.

Currently I'm passing the id in the query part of the resource and doing something like this (pseudo code):

const template = `src/views/pages/[id].template.html`;
module.exports = {
  output: {
      publicPath: '/',
      clean: true,
      path: './dist/templates',
  },
  plugins: [
	new HtmlWebpackPlugin({
	  template: `${template}?id=1`,
	  filename: 'pages/1.html',
    }),
    new HtmlWebpackPlugin({
	  template: `${template}?id=2`,
	  filename: 'pages/2.html',
    }),
    new HtmlWebpackPlugin({
	  template: `${template}?id=3`,
	  filename: 'pages/3.html',
    }),
  ],
  module: {
    rules: [
      // templates
      {
        test: /\.html/,
        loader: HtmlBundlerPlugin.loader, //  HTML template loader
        options: {
          preprocessor: (content, { resourcePath, resource }) => {
            const [, query] = resource.split('?');
            const data = findData(resourcePath, query);
            return render(content, data);
          },
        },
      },
    ],
  },
};

Does this seem right or is there a better way to do this?

@rohit-gohri

Using the HtmlBundlerPlugin, here is correct config:

const path = require('path');
const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');

const template = `src/views/pages/[id].template.html`;

module.exports = {
  output: {
      publicPath: '/',
      clean: true,
      path: path.join(__dirname, './dist/templates'),
  },
  entry: {
    // define templates here
   'pages/1': `${template}?id=1`, // => dist/templates/pages/1.html
   'pages/2': `${template}?id=2`, // => dist/templates/pages/2.html
   'pages/3': `${template}?id=3`, // => dist/templates/pages/3.html
  },
  plugins: [
    // use the HtmlBundlerPlugin to handle an HTML template as the entry point
    new HtmlBundlerPlugin({
      js: {
        // output filename of extracted JS from source script loaded in HTML via `<script>` tag
        filename: 'assets/js/[name].[contenthash:8].js',
      },
      css: {
        // output filename of extracted CSS from source style loaded in HTML via `<link>` tag
        filename: 'assets/css/[name].[contenthash:8].css',
      },
    }),
  ],
  module: {
    rules: [
      {
        test: /\.html/,
        loader: HtmlBundlerPlugin.loader, //  HTML template loader
        options: {
          preprocessor: (content, { resourcePath, resource }) => {
            const [, query] = resource.split('?');
            const data = findData(resourcePath, query);
            return render(content, data);
          },
        },
      },
    ],
  },
};

@rohit-gohri

to simplify the config above, I have added the new entry option in new version 0.8.0.
This option has the identical Webpack Entry API plus has an additional data property to pass data to the preprocessor for the concrete template .

In the new configuration, you do not need to use a query for data identification.

The original Webpack entry still defines the templates, but without the data property, because it doesn't match the Webpack entry API.

const path = require('path');
const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');

const template = `src/views/pages/[id].template.html`;

module.exports = {
  output: {
      publicPath: '/',
      clean: true,
      path: path.join(__dirname, './dist/templates'),
  },
  // entry: {}, // leave entry empty or undefined
  plugins: [
    // use the HtmlBundlerPlugin to handle an HTML template as the entry point
    new HtmlBundlerPlugin({
      entry: {
        // define templates with custom data here
        // note: this entry has Webpack entry API, plus 'data' property
        'pages/1': { // => dist/templates/pages/1.html
          import: template,
          data: {
            title: 'Home',
          }
        },
        'pages/2': { // => dist/templates/pages/2.html
          import: template,
          data: {
            title: 'Contact',
          }
        },
        'pages/3': { // => dist/templates/pages/3.html
          import: template,
          data: {
            title: 'About',
          }
        },
        // if there is no data you can use simple syntax
        'index': 'src/views/pages/home.html', // => dist/index.html
      },
      // output filenames
      js: {
        // output filename of extracted JS from source script loaded in HTML via `<script>` tag
        filename: 'assets/js/[name].[contenthash:8].js',
      },
      css: {
        // output filename of extracted CSS from source style loaded in HTML via `<link>` tag
        filename: 'assets/css/[name].[contenthash:8].css',
      },
    }),
  ],
  module: {
    rules: [
      {
        test: /\.html/,
        loader: HtmlBundlerPlugin.loader, //  HTML template loader
        options: {
          preprocessor: (content, { resourcePath, data }) => {
            // each template gets its own data passed in the entry data
            return render(content, data);
          },
        },
      },
    ],
  },
};

What do you think about this? Is it better for you?

This is awesome!! Thanks! Will try it out

@rohit-gohri

I have optimized the internal processing of the data passed to the preprocessor.
In new v0.9.0 the 3rd argument data of the preprocessor has been moved to the 2nd argument as a property.
NEW syntax:

preprocessor: (content, { resourcePath, data }) => {}