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 inject content ? #1325

Closed
Legends opened this issue Dec 29, 2019 · 10 comments
Closed

Dynamically inject content ? #1325

Legends opened this issue Dec 29, 2019 · 10 comments

Comments

@Legends
Copy link

Legends commented Dec 29, 2019

In my project (static multi-page app) I have a custom html template.
This template should be my layout template which should serve as a basis for all pages.
Now instead of putting everything inside this template layout+content and copying this for every page I want to create, I want to be able to tell the plugin to load the specified content html page.

Is this possible?

Something like this:

  new HtmlWebpackPlugin({
            template: './src/pages/layout/layout.ejs', // Reference the same layout in every html-wp plugin
            templateParameters: {
                'title': 'Index',
                'contentUrl':'../../partials/indexBody.html' // <-- something like this, to tell it to inject html into the body or another container
            },            
            inject: true,
            chunks: ['index'],
            filename: 'index.html'
        }),

  new HtmlWebpackPlugin({
            template: './src/pages/layout/layout.ejs', // Reference the same layout in every html-wp plugin
            templateParameters: {
                'title': 'About',
                'contentUrl':'../../partials/aboutBody.html' // <-- just load a different html body
            },            
            inject: true,
            chunks: ['about'],
            filename: 'about.html'
        }),

The layout.ejs:
The following does not work, I guess it first tries to parse and execute the require statements before injecting the template parameters
${require('<%= contentUrl %>')}

So in the end I want to be able to specify a container and the respective content.html file.

@Legends Legends changed the title Dynamically load content ? Dynamically inject content ? Dec 29, 2019
@Legends
Copy link
Author

Legends commented Dec 29, 2019

Ok, I see there is a partials-plugin
which has the requirement to use latest beta version of html-webpack-plugin.

So using this plugin works fine, but now I cannot require anymore inside of my template file:
${require('../../partials/nav.html')}

This doesn't work anymore. It renders the complete require statement as string into the page.

@jantimon
Copy link
Owner

jantimon commented Dec 29, 2019

The html-webpack-plugin is very flexible - you can use any template language see this example which uses react as a template:

Try it on codesandbox

The example is using typescript but of cause you can achieve the same with babel

Webpack config:

const HtmlWebpackPlugin = require("html-webpack-plugin");
const TsConfigWebpackPlugin = require("ts-config-webpack-plugin");

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      template: "src/template.tsx"
    }),
    new TsConfigWebpackPlugin()
  ]
};

template.tsx

//
// This is the entry file which is executed by the html-webpack-plugin
// during the webpack build
//

import React from "react";
import { renderToString } from "react-dom/server";

export default function() {
  return renderToString(
    <html>
      <head />
      <body>
        <div id="main">
        </div>
      </body>
    </html>
  );
}

For other template languages it works the same way - just add a loader to your webpack config and it will be used for your template file.

Just make sure that your template returns one of the following:

  • a string
  • a function which returns a string
  • a function which returns a promise of a string

@Legends
Copy link
Author

Legends commented Dec 29, 2019

Perhaps you didn't get me or probably I didn't get you :-)

I am using html-loader.

My layout.ejs template file, is a simple html document (no JavaScript, exports, imports, etc. inside).
I used layout.html before, but then the supplied templateParameters don't get injected, don't know why.
I have no knowledge about ejs I use the extension only to make the injection of template parameters work.

I usually put ${require('../../partials/nav.html')} in a certain section of the html template to import partials (another.html), this template is then parsed by your plugin and the partial gets injected, but with latest beta version of html-webpack-plugin this does not work anymore.

Importing from a tsx template is not a problem as it is JavaScript, but my template is pure html, where I put the require statement from above.
And inside of an html file the require statement is just text and does nothing on its own.

The parsing of the template and the html import are done by your plugin.

Aaaaa I hope you understand me better now ;-)

image

@Legends
Copy link
Author

Legends commented Dec 29, 2019

This all works fine with the latest release version.

But now I want to be able to tell the HtmlWebpackPlugin which html partial to load for the body.
The layout.ejs template is the same for all.
Example:
For the generated index.html page I want it to render a index-body.html inside of its body.
For the generated about.html page I want it to render a about-body.html inside of its body.

I cannot require a body.html inside of the layout template, the template is for all pages the same.

 new HtmlWebpackPlugin({
            // template: './src/pages/home/layout.html',
            template: './src/pages/layout/layout.ejs',
            templateParameters: {
                'title': 'Index'
            },
            inject: true,
            chunks: ['index'],
            filename: 'index.html'
        }),
        new HtmlWebpackPlugin({
            // template: './src/pages/home/layout.html',
            template: './src/pages/layout/layout.ejs',
            templateParameters: {
                'title': 'About'
            },
            inject: true,
            chunks: ['about'],
            filename: 'about.html'
        }),

When I use the HtmlWebpackPartialsPlugin the I can inject the respective body.html into the page,
but this plugin uses the latest beta version of the html-webpack-plugin which breaks my ${require('../../partials/nav.html')} inside of my layout.ejs.

  new HtmlWebpackPartialsPlugin([
            {
                path: path.join(__dirname, './src/partials/index/index-body.html'),
                location: 'main',
                template_filename: 'index.html'
            }
        ]),

@Legends
Copy link
Author

Legends commented Dec 29, 2019

Forget about everything.
To make it simple, can we make require work again in latest beta version?

Here a related issue.

layout.ejs
${require('../../partials/nav.html')}

@jantimon
Copy link
Owner

As I tried to show with the tsx example you can use any loader.

What you can do for example:

Add the ejs-loader to your project and have the following about.html:

<%= require("./head.ejs") %>
about
<%= require("./footer.ejs") %>

@colbyfayock
Copy link

@jantimon so to explain the use case a little bit here - the plugin i set up to work based on html-webpack-plugin injects "partials" into the html pipeline. the big use case here is that you won't have to maintain a template file, instead, you simply define a partial file and where you want it to be added. this is particularly useful for something like a React app, where instead of having to redefine all of the import bits and pieces of the template itself requiring someone to maintain that through potential changes, they simply have to define this 1 partial with the required code that gets plugged in through the compile chain

ive been trying to figure out though how to get this running in v4 and have been struggling with it a bit. im not super well versed in the webpack ecosystem, but previously, i was successfully able to simple string replace in the partial given the location and some regex magic which would allow the html-webpack-plugin to compile it as i needed it. now, i can't seem to find a good spot to plug that into.

i found in afterTemplateExecution there's a nice headTags and bodyTags that ive considered utilizing, but it seems like it doesn't include ALL potential body and head tags that might process through the pipeline, which could impact the loading order for things like adding a particular tag, like some extra JS-y import, at the end of the file, as well as i can't prioritize the order that the head would load in, such as loading google tag manager at the very top of the head behind the charset

any recommendations on a direction i could go here? i was considering trying to hook into some of the utils you make available, but when i was digging in, i couldn't really find anything public that would help me here. for instance, if there was a method publicly available that let me "compile" each individual partial file, effectively running your processing on the file resolving things like ${require('<%= contentUrl %>')}, that would make of a nice way for me to hook into it.

you can see some of the use cases im providing here: https://github.com/colbyfayock/html-webpack-partials-plugin/tree/master/examples

any thoughts on this?

@colbyfayock
Copy link

and just to clarify, the injection process is working currently as expected, however, it doesn't resolve the requires

@Legends
Copy link
Author

Legends commented Dec 30, 2019

@jantimon

Ok, I get your approach, it's like:

// Index.html
<%= require("./head.ejs") %>
                // <-- page content for index
<%= require("./footer.ejs") %>
--------------------------------------------------------------------------
// About.html
<%= require("./head.ejs") %>
                 // <-- page content for about
<%= require("./footer.ejs") %>
--------------------------------------------------------------------------
// Contact.html
<%= require("./head.ejs") %>
                // <-- page content for contact
<%= require("./footer.ejs") %>

I like to keep it DRY. We have to repeatedly require head and footer in every page with this approach. Perhaps we have more things to require which make up the base for our pages...

What I would like to be able to do:
For every page I want to create, I instantiate HtmlWebpackPlugin and specify a composition property where one could specify objects as key-value pairs where the key is a tag/selector (container where the resource gets injected) and the value is the resource to be injected.

Like this for example:

 new HtmlWebpackPlugin({       
            template: './src/pages/layout/layout.ejs',
            templateParameters: {
                'title': 'Index'
            },
            composition:[
                {'main' : './src/pages/about/about-body.html'},  // body gets injected into main tag             
            ],
            inject: true,
            chunks: ['index'],
            filename: 'index.html'
        }),

Here we always reference the same template for all pages and just tell in composition what we want to inject, most of the time only the body of the page.
The template implements all the requires we need once and that's it.

Question is:
Is this already possible with html-webpack-plugin or needs to be implemented as a separate plugin?
If this should be a separate plugin, which webpack-hook would fit best to inject the resources into the template, especially when considering resources that also can contain require calls in their html, in order for them to get processed correctly?

@Legends
Copy link
Author

Legends commented Jan 1, 2020

Closed in favor of feature request

@Legends Legends closed this as completed Jan 1, 2020
@lock lock bot locked as resolved and limited conversation to collaborators Jan 31, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants