jantimon / favicons-webpack-plugin

Let webpack generate all your favicons and icons for you
MIT License
1.2k stars 210 forks source link

Inject favicons tags into custom position using ejs template #176

Closed hypernova7 closed 4 years ago

hypernova7 commented 5 years ago

For example:

<head>
    <!-- Seo Tags from Google, Facebook, Twitter, etc-->
    <%- htmlWebpackPlugin.files.favicons.html %>
    <!-- script tags from Google Analytics, etc -->
</head>

Output:

<head>
    <!-- Seo Tags from Google, Facebook, Twitter, etc-->
    <link rel="icon" href="...">
    <meta name="..." content="...">
    <!-- etc -->
    <!-- script tags from Google Analytics, etc -->
</head>
</head>
CoderAmigo commented 4 years ago

Hi Eddy, I managed to solve this problem!

By creating a micro plugin using hooks.

It was required to use webapp-webpack-plugin, this fork favicons-webpack-plugin with implemented support for the hook webappWebpackPluginBeforeEmit, but, unfortunately, without options mode and devMode.

Jan, Bruno, if you are reading this, please give the opportunity to inject it into a custom template from the box or add support for hooks to FaviconsWebpackPlugin or the devMode option in the fork. Bad that you have to use crutches =(

The crutch itself: (set below new HtmlWebpackPlugin({ ... }) and below new WebappWebpackPlugin({ ... }))

new (class WebappTransponderWebpackPlugin {
    apply(compiler) {
        let iconsTags = [];
        compiler.hooks.make.tap("WebappTransponderWebpackPlugin", (compilation) => {
            compilation.hooks.webappWebpackPluginBeforeEmit.tapAsync("WebappTransponderWebpackPlugin", (data, cb) => {
                iconsTags = data.tags.map((tag) => {
                    return {
                        tagName: tag.slice(1, tag.indexOf(' ')),
                        tagAttributes: tag.slice(tag.indexOf(' ') + 1, -2).split('" ').map((attr) => {
                            attr = attr.split('="');
                            return { attrName: attr[0], attrValue: attr[1] };
                        })
                    };
                });
                return cb(null, data);
            });
            compilation.hooks.htmlWebpackPluginBeforeHtmlGeneration.tapAsync('WebappTransponderWebpackPlugin', (data, cb) => {
                data.assets.favicons = (data.assets.favicons || []).concat(iconsTags);
                return cb(null, data);
            });
        });
    }
})()

Add in your .ejs template:

<% if (Array.isArray(htmlWebpackPlugin.files.favicons)) {
  for (var item of htmlWebpackPlugin.files.favicons) { %>
<<%= item.tagName %><% for (var item of item.tagAttributes) { %> <%= item.attrName %>="<%= item.attrValue %>"<% } %>><%
  }
} %>

Only works with webapp-webpack-plugin use new WebappWebpackPlugin ({ otions... }).

Explanations:

Why are html lincs parsed? The first reason, for greater total flexibility, is the ability to calculate the functional by adding modifications to the inserted html tags. The second reason, I tried to get link icon tags in the form of an object with parameters, from compilation.hooks.webappWebpackPluginBeforeEmit.tap..., but there only files names and files themselves are the Buffer object, everything else: tag name, path, media data, etc. only in the form of already assembled html.

This is my first experience in creating the use of webpack hooks and in general the first experience of writing a plugin for webpack, so I could not do everything absolutely right, but everything works! For example, I have a suspicion that I am transferring incorrect names to hooks -> tap, although in this case it does not affect anything.

hypernova7 commented 4 years ago

Hi Eddy, I managed to solve this problem!

By creating a micro plugin using hooks.

It was required to use WebappWebpackPlugin this fork FaviconsWebpackPlugin with implemented support for the hook webappWebpackPluginBeforeEmit but unfortunately with the options mode and devMode remaining. Jan, Bruno if you are reading this, please give the opportunity to inject it into a custom template from the box or add support for hooks to FaviconsWebpackPlugin or the devMode option in the fork. And then you have to use crutches =(

The crutch itself:

new (class FaviconsTransponderWebpackPlugin {
    apply(compiler) {
        let lincIcons = [];
        compiler.hooks.make.tap("FaviconsTransponderWebpackPlugin", (compilation) => {
            compilation.hooks.webappWebpackPluginBeforeEmit.tapAsync("FaviconsTransponderWebpackPlugin", (data, cb) => {
                lincIcons = data.tags.map((tag) => {
                    let tagName = tag.split(' ')[0].slice(1);
                    tag = tag.slice(tagName.length + 1).slice(2, -1);
                    let tagAttributes = tag.split('" ').reduce((attrs, attr) => {
                        attr = attr.split('="');
                        attrs[attr[0]] = attr[1];
                        return attrs;
                    }, {});
                    return tagAttributes;
                });
                return cb(null, data);
            });
        });
        compiler.hooks.make.tap('FaviconsTransponderWebpackPlugin', (compilation) => {
            compilation.hooks.htmlWebpackPluginBeforeHtmlGeneration.tapAsync('FaviconsTransponderWebpackPlugin', (data, cb) => {
                data.assets.icon = (data.assets.icon || []).concat(lincIcons);
                return cb(null, data);
            });
        });
    }
})()

In your .ejs template:

<%if (Array.isArray(htmlWebpackPlugin.files.icon)) {
    for (let icon of htmlWebpackPlugin.files.icon) { %>
    <link<% for (key in icon) { %> <%= key %>="<%= icon[key] %>"<% } %>><%
    }
} %>

Only works with WebappWebpackPlugin use new WebappWebpackPlugin ({ otions... }).

Explanations:

Why are html lincs parsed? The first reason, for greater total flexibility, is the ability to calculate the functional by adding modifications to the inserted html tags. The second reason, I tried to get link icon tags in the form of an object with parameters, but from compilation.hooks.webappWebpackPluginBeforeEmit.tapAsync only html and something came in the format Buffer, I could not figure out why Buffer and what would be kept in it.

This is my first experience in creating the use of webpack hooks and in general the first experience of writing a plugin for webpack, so I could not do everything absolutely right, but everything works! For example, I have a suspicion that I am transferring incorrect names to hooks tap, although in this case it does not affect anything.

@CoderAmigo Not what i was looking for

CoderAmigo commented 4 years ago

And what is wrong?

hypernova7 commented 4 years ago

@CoderAmigo the properties of the meta tags, would be within the link tags, and would result in an incorrect html and would affect the SEO of the page.

CoderAmigo commented 4 years ago

@hypernova7 No, they will not do. Check it out.

hypernova7 commented 4 years ago

The correct thing would be to pass the html that returns favicons-webpack-plungin to an object of html-webpack-plugin.

Something like that:

new HtmlWebpackPlugin({
  htmlFavicons: Object // Object returned from FaviconsWebpackPlugin with html output
})

ejs template

<%- htmlWebpackPlugin.options.htmlFavicons %>

html returned

<meta name="..." content="...">
<link rel="..." href="..." type="...">
<!-- etc -->
CoderAmigo commented 4 years ago

Apparently your .ejs template is a "children's" - a simple variation.

Transmit assembled finished HTML is not true! Look at how advanced templates work. For example html-webpack-template look at index.ejs In principle, implementing it so that the ready-made html is sent to the html-webpack-plugin will not be a serious mistake, but it will not be flexible.

<% - htmlWebpackPlugin.options.htmlFavicons %>

You suggested putting the data received from the favicon plugin into htmlWebpackPlugin.options in options, this is absolutely not right.

And now, with regard to the final result of the assembly, the solution that I gave initially leads to the result of the assembly that you are talking about: meta tags separately, link tags separately and in no way intersect, neither attributes nor how! And in what sequence they will be placed during assembly will depend on the template, just add to the right place:

<% if (Array.isArray(htmlWebpackPlugin.files.favicons)) {
  for (var item of htmlWebpackPlugin.files.favicons) { %>
<<%= item.tagName %><% for (var item of item.tagAttributes) { %> <%= item.attrName %>="<%= item.attrValue %>"<% } %>><%
  }
} %>

In general, to do exactly as you want is a very simple task, just compile the html not in the template but of my WebappTransponderWebpackPlugin

Just change here:

data.assets.favicons = (data.assets.favicons || []).concat(iconsTags);

And put the assembled html wherever you want. For example: data.assets.faviconsHtml will be available in the template like this: <%= htmlWebpackPlugin.files.faviconsHtml %>. But this is not right!

Or remove the html parsing from the code and transfer it in its original form, the original data is in data.tags 6 line in WebappTransponderWebpackPlugin.

hypernova7 commented 4 years ago

@CoderAmigo Thanks, I understood. And excuse me, I'm new to this for webpack plugins xD

hypernova7 commented 4 years ago

@CoderAmigo And thanks for the code, I'll use it if it doesn't bother you.

CoderAmigo commented 4 years ago

@hypernova7 Thanks, I will be glad if my code will help in your work.

CoderAmigo commented 4 years ago

@hypernova7 It seems I initially did not quite understand what you were talking about. I wrote down all the tags, substituting the name of the link tag, I initially did not notice that the favicon plugin generates not only link tags but also meta tags. And it turned out what you were possibly talking about, link tags with meta tags attributes were generated, you originally talk to me about this?

So I fixed it! + made a small but cool code refactoring)

...Updated the code in my posts.

hypernova7 commented 4 years ago

@CoderAmigo If that's why I told you that, I was going to generate a badly formed HTML hahahaha

hypernova7 commented 4 years ago

@CoderAmigo Thank you for modifying the code you showed me before. I will close this post

CoderAmigo commented 4 years ago

@hypernova7 The problem is not properly resolved. You can open this issue.

hypernova7 commented 4 years ago

@CoderAmigo It is done

CoderAmigo commented 4 years ago

Cast summon spell, @brunocodutra, =)

ioulian commented 4 years ago

@CoderAmigo Your code doesn't work anymore (not with 1.0.2 and not with 3.x version). What I'm trying to do is to get all the tags and save them to a file (to be manually injected by PHP). Previously I could tap into "webappWebpackPluginBeforeEmit" hook and get all the tags, but this is not possible anymore as "webappWebpackPluginBeforeEmit" hook doesn't exist enymore.

Have you managed to fix this?

jantimon commented 4 years ago

Here is an example which is using

https://github.com/jantimon/favicons-webpack-plugin/tree/master/example/no-inject

Your <head> might look like this:

https://github.com/jantimon/favicons-webpack-plugin/blob/47bf751d3e9c945c77fbd853d46c8ce70d08b338/example/no-inject/src/index.html#L4-L10

ioulian commented 4 years ago

Here is an example which is using

https://github.com/jantimon/favicons-webpack-plugin/tree/master/example/no-inject

Your <head> might look like this:

https://github.com/jantimon/favicons-webpack-plugin/blob/47bf751d3e9c945c77fbd853d46c8ce70d08b338/example/no-inject/src/index.html#L4-L10

Yes, this is the route I chose to do it, but it does add other tags (like css links or the head tag) that are not needed. You can create an empty .ejs file with this inside:

<%= htmlWebpackPlugin.tags.headTags.filter((tag) => tag.toString().indexOf('.css"') === -1).join('') %>

This will output all tags without .css files. This works ok as a quick hack

jantimon commented 4 years ago

You can also tell the html webpack to exclude all chunks if you don't want it to provide any js or css files.

CoderAmigo commented 4 years ago

@ioulian I assume that you have mixed up favicons-webpack-plugin and webapp-webpack-plugin. Current actual stable version webapp-webpack-plugin is 2.7.1

And I believe that @jantimon perhaps in the favicons-webpack-plugin 3.x version, realized the possibility of solving the problem under discussion in a more correct and concise way.

new FaviconsWebpackPlugin({
    logo: 'logo.svg',
    inject: htmlPlugin => { 
         // Your code that implements adding additional data for the .ejs template, here
         return false
    }
})

But all this will still look like a hack, but not a beautiful concise solution. But I like the option with the ability to use own code in the inject function, but then all the necessary data should go to the function. Or maybe they already go, but I did not find information about this in the documentation for the favicons-webpack-plugin.

Let me explain for what purposes such customization tools may be required. For example, in some projects I use a fairly large, expansible, customizable template similar to the following this. And I need the ability to manage the data used in template.

jantimon commented 4 years ago

We merged webapp back into the 2.x version of the favicons-webpack-plugin

You can disable any html-webpack-plugin if you set favicons:false or provide a custom handler function.

This is the code which handles that logic:

https://github.com/jantimon/favicons-webpack-plugin/blob/47bf751d3e9c945c77fbd853d46c8ce70d08b338/src/index.js#L79-L87

However I am not sure if your question is related to this issue.

hypernova7 commented 4 years ago

Here is an example which is using

* [html-webpack-plugin 4.x](https://www.npmjs.com/package/html-webpack-plugin/v/4.0.0-beta.11)

* favicons-webpack-plugin 3.x

https://github.com/jantimon/favicons-webpack-plugin/tree/master/example/no-inject

Your <head> might look like this:

https://github.com/jantimon/favicons-webpack-plugin/blob/47bf751d3e9c945c77fbd853d46c8ce70d08b338/example/no-inject/src/index.html#L4-L10

This is what I was referring to, thank you very much. (Sorry for replying so late, I mistakenly disabled notifications for this issue and just realized)

jantimon commented 4 years ago

Thanks for the update :)