GoogleChromeLabs / prerender-loader

📰 Painless universal pre-rendering for Webpack.
https://npm.im/prerender-loader
Apache License 2.0
1.91k stars 50 forks source link

Not working "templateParameters" when prerender-loader used #50

Open vladlavrik opened 4 years ago

vladlavrik commented 4 years ago

Hi! Properties from "templateParameters" do not appear in html when using "prerender-loader".

Minimal code to reproduce:

webpack.config.js

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

module.exports = {
    mode: 'development',
    context: __dirname,
    entry: {
        bundle: './index.js',
    },
    output: {
        path: path.resolve(__dirname, 'build'),
    },
    plugins: [
        new HtmlWebpackPlugin({
            filename: `index_.html`,
            template: `!!prerender-loader?string!./index.html`,
            templateParameters: {
                injected: 'foo', // <--------
            },
        }),
    ],
};

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta property="x-injected" content="<%= injected %>">
</head>
<body></body>
</html>

html output


<!DOCTYPE html><html lang="en"><head>
    <meta charset="UTF-8">
    <meta property="x-injected" content="<%= injected %>">
     <!--                                    ^^^^^^^  -->
</head>
<body>

<div>PRERENDERED CONTENT</div><script src="bundle.js"></script></body></html>```
vzaidman commented 4 years ago

EDIT: for the updated html-webpack-plugin see: https://github.com/GoogleChromeLabs/prerender-loader/issues/50#issuecomment-630253823

the loader node_modules/html-webpack-plugin/lib/loader.js only runs when no other loader is used to load the html and this is what's responsible for the templateParams.

what I did was to copy the file, but remove the lines:

  const allLoadersButThisOne = this.loaders.filter(function (loader) {
    // Loader API changed from `loader.module` to `loader.normal` in Webpack 2.
    return (loader.module || loader.normal) !== module.exports;
  });
  // This loader shouldn't kick in if there is any other loader
  if (allLoadersButThisOne.length > 0) {
    return source;
  }

so the workaround is:

add temporary-template-params-loader.js near webpack.config.js:

/* eslint-disable */
'use strict';

const _ = require('lodash');
const loaderUtils = require('loader-utils');

// temporary loader to ensure templateParams are used.
// see https://github.com/GoogleChromeLabs/prerender-loader/issues/50
module.exports = function (source) {
  if (this.cacheable) {
    this.cacheable();
  }

  // Skip .js files
  if (/\.js$/.test(this.resourcePath)) {
    return source;
  }

  // The following part renders the tempalte with lodash as aminimalistic loader
  //
  // Get templating options
  const options = this.query !== '' ? loaderUtils.parseQuery(this.query) : {};
  const template = _.template(source, _.defaults(options, { variable: 'data' }));
  // Require !!lodash - using !! will disable all loaders (e.g. babel)
  return 'var _ = require(' + loaderUtils.stringifyRequest(this, '!!' + require.resolve('lodash')) + ');' +
    'module.exports = function (templateParams) { with(templateParams) {' +
    // Execute the lodash template
    'return (' + template.source + ')();' +
    '}}';
};

then in webpack.config.js:

in rules:

{
        test: selfPath('src/index.html'),
        use: [
          {
            loader: './temporary-template-params-loader',
          },
          {
            loader: 'prerender-loader',
            options: {
              entry: './src/index.js',
              // notice prerender-loader is used without "string: true"
            },
          },
        ],
      },

plugin:

    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: './src/index.html',
      templateParameters: (compilation, params) => {
        Object.assign(params, {myParam});
        return params;
      },
      inject: false,
    }),
wojtekmaj commented 4 years ago

Actually, since https://github.com/jantimon/html-webpack-plugin/pull/972 html-webpack-plugin loader has force flag that skips the very if() you deleted. So it could be as easy as to add force: true. 🤔

vzaidman commented 4 years ago

right! it turns out I had an old version (3.2) of the plugin. in newer versions you can use the loader from html-webpack-plugin:

so in webpack.config.js:

in rules:

{
        test: selfPath('src/index.html'),
        use: [
          {
            loader: 'html-webpack-plugin/lib/loader',
            options: {
              force: true,
            },
          },
          {
            loader: 'prerender-loader',
            options: {
              entry: './src/index.js',
              // notice prerender-loader is used without "string: true"
            },
          },
        ],
      },

plugin:

    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: './src/index.html',
      templateParameters: (compilation, params) => {
        Object.assign(params, {myParam});
        return params;
      },
      inject: false,
    }),