purgecss can't recognize conditional classes - wordpress-theming

So I'm using TailwindCSS for a WP theme I'm developing.
I ran into an issue in creating the production grade theme files because, from how I understand the problem, purgecss can't recognize conditional classes used on template parts. For example, let's say I created a template part called "business-card.php" where I pass it a variable type (using set_query_var / get_query_var):
page-about.php
set_query_var('type', 'A');
get_template_part('template-parts/content/business', 'card');
set_query_var('type', 'B');
get_template_part('template-parts/content/business', 'card');
businesss-card.php
$type = get_query_var('type')
<div class="<?php echo type == 'A' ? 'text-color-A' : 'text-color-B' ?>">
--- insert some content here ---
</div>
So there will be two divs, one will have a text-color-A class, the other will have a text-color-B, both were created using a config file(rather than included in the base tailwind theme). This is fine in development -- since tailwind does actually create the utility color classes from the config file. But for some reason, when it's in production (i.e. purged & minified), it doesn't have those utility classes -- which were only used in the template part as conditional classes (and not in any other file).

PurgeCSS is intentionally very naive in the way it looks for classes in your HTML. It doesn't try to parse your HTML and look for class attributes or dynamically execute your JavaScript — it simply looks for any strings in the entire file that match this regular expression:
/[^<>"'`\s]*[^<>"'`\s:]/g
That means that it is important to avoid dynamically creating class strings in your templates with string concatenation, otherwise PurgeCSS won't know to preserve those classes.
So do not use string concatenation to create class names:
<div :class="text-{{ error ? 'red' : 'green' }}-600"></div>
Instead, dynamically select a complete class name:
<div :class="{{ error ? 'text-red-600' : 'text-green-600' }}"></div>
As long as a class name appears in your template in its entirety, PurgeCSS will not remove it.
See the docs for more details:
Writing purgeable HTML

If you are using Tailwind 2.0+ you can configure whitelisted classes that will be ignored by purge CSS directly inside of your tailwind.config.js file.
An example of my own code where I whitelist the class text-ingido-400 can be seen below
// tailwind.config.js
module.exports = {
purge: {
content: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
options: {
safelist: ['text-indigo-400']
}
} ,
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [],
}
For more information you can check out the relevant documentation located at:
https://tailwindcss.com/docs/optimizing-for-production

You can use the PurgeCSS whitelist option to add those classes.
const purgecss = new Purgecss({
//... Your config
whitelist: ['text-color-A', 'text-color-B', 'etc']
})
Or the whitelistPatterns (Regex match)
whitelistPatterns: [/^text-color/], // All classes starting with text-color
You can find more information here

If you define your class names in a variable above where you want to use them it works akin to safe listing them in Tailwind or whitelisting them in PurgeCSS, is potentially easier to maintain, and works good in a pinch if you have a small number of possbile dynamic class names.
var classes =`grid-cols-1 grid-cols-2 grid-cols-3 grid-cols-4`
return (
<div className={`grid grid-cols-${items['grid-cols']}`}>
</div>
)

For some tailwindcss class, you can use inline style instead.
Inline style allow you to use dynamic value, like <div style="padding-left:{indent}rem">.
I think it works in php also. More details can be found here

Related

Is it possible to create a bundle with Rollup that injects HTML from a template?

I'm trying to build a IIFE (immediately-invoked function expression) bundle using Rollup.
I would like to bundle all together my JS, CSS, and then some HTML which the Javascript depends on. Check this image to get an idea:
Is there a way to tell Rollup that my input (entry point) is seguro-vida-hipoteca.js, and have this file import my SCSS and HTML so it will be automatically injected when somebody uses my library?
That would be, the resulting CSS in the head, and the HTML to some div that I would expect to exist in the dom, or just at the end of body.
Is there a way to tell Rollup that my input (entry point) is seguro-vida-hipoteca.js?
Sure, that's what the input option is for.
Injecting Sass in the head is easily accomplishable with plugins such as rollup-plugin-postcss:
// rollup.config.js
import postcss from 'rollup-plugin-postcss';
export default {
// ...
plugins: [
postcss(),
],
}
import './style.scss'; // Now each stylesheet you import will
// be injected to <head>
About injecting/appending html, that is something you would normally do in your code and not through a plugin, although you can take advantage of Rollup to load template.html as a string (for example by using rollup-plugin-html):
import html from "rollup-plugin-html";
export default {
// ...
plugins: [
html({
include: "**/*.html",
}),
],
}
import template from './template.html';
document.querySelector('#mydiv').innerHTML = template;
Side note
This seems to be a good use case for WebComponents. More info here.

How would you embed atomic partials into a template data object

I'm using handlebars and assemble with yeoman and gulp.
I want to have some globalized partials that are able to be nested or injected into another partial by calling it within the context of a data object.
A simple example of that would be having a list of links that I could reference inside content throughout the site. The reason behind this, is the need for consistency. If for example, if I have a link within text on a page that I reference a 15 times throughout an entire website, but then realize I need to add a trade mark or modify the text, I want to update it once, not 15 times.
This is an example of what I want to do. Define global data inside a json file:
links.json
{
"apple": {
"linktext": "apple",
"target": "_blank",
"href": "http://www.apple.com"
},
"blog-article-foo-bar": {
"linktext": "foo bar",
"href": "http://www.foobar.com"
},
"dell": {
"linktext": "dell",
"target": "_parent",
"href": "http://www.dell.com"
}
}
Generate a partial from that content using a simple or complex template:
links.hbs
<a href="{{href}}" {{#if target}}target="{{target}}"{{/target}}>{{linktext}}</a>
And be able to embed that partial into another one by referencing it some how. This didn't work, but I've been reading about custom helpers, but can't figure out how I would intercept the partial and bind it into the other partial.
text.json
{
"text": "If you need a computer, go to {{> link link.apple}}."
}
text.hbs
<p>
{{text}}
</p>
compiled.html
<p>
If you need a computer, go to apple.
</p>
If you have suggestions or examples that might help me understand how to achieve this, I'd really appreciate the support. Thanks in advance.
There is some information about Handlebars helpers in their docs but not that much.
Since you're trying to use handlebars syntax in the value of a property on the context (e.g. text), handlebars won't render the value since it's already rendering the template. You can create your own helper that can render the value like this:
Handlebars.registerHelper('render', function(template, options) {
// first compile the template
const fn = Handlebars.compile(template);
// render the compiled template passing the current context (this) to
// ensure the same context is use
const str = fn(this);
// SafeString is used to allow HTML to be returned without escaping it
return new Handlebars.SafeString(str);
});
Then you would use the helper in your templates like this:
{{render text}}
Thanks for the example #doowb, your code did work but not for what I was trying to do. I really wanted something more complicated but I simplified my question not knowing it would be an issue. The code you provided worked (I think after a slight tweak) for a simple render of a template, but my templates use helpers such as #each and #if which caused the issue. Where the helpers were in my template, I ended up getting async placeholders. For example: <a $ASYNC$1$3...> I later learned this has to do with how partials are rendered. Understanding that lead me to subexpressions and the below solution.
Keeping my example above with some modifications, this is how I was able to merge partials.
First, I simplified the placeholder in text.json to basically a unique ID, instead of trying to render the partial there.
On the hbs template that I'm rendering to, such as a page or whatever, I included the insert helper with 3 arguments. The first two are subexpressions, each return a flattened partials as strings. The key here is that subexpressions process and return a result before finishing the current process with the helper. So two flattened templates are then sent to the helper along with the placeholder to search for.
The helper uses the third argument in a regex pattern. It searches the second argument (flattened parent template) for this pattern. When found, it replaces each instance of the pattern with the first argument (yes its a global fine replace).
So, the flattened child string gets inserted into parent each time placeholder is found.
First argument
(partial "link" link.apple)
Returns
'apple'
Second argument
(partial "text" text.text-example)
Returns
'<p class="text font--variant">If you need a computer, go to {{linkToApple}}.</p>'
Third argument
'linkToApple'
text.json
{
"text-example": {
"elm": "quote",
"classes": [
"text",
"font--variant"
],
"text": "If you need a computer, go to {{linkToApple}}."
}
}
text.hbs
<{{elm}} class="{{#eachIndex classes}}{{#isnt index 0}} {{/isnt}}{{item}}{{/eachIndex}}">{{text}}</{{elm}}>
compile.hbs
{{insert (partial "link" link.apple) (partial "text" text) 'linkToApple' }}
compile.html
<p class="text font--variant">If you need a computer, go to apple.</p>
gulpfile.js
app.helper('insert', function(child, parent, name) {
const merged = parent.replace(new RegExp('\{\{(?:\\s+)?(' + name + ')(?:\\s+)?\}\}', 'g'), child);
const html = new handlebars.SafeString(merged);
return html;
});
Hope this helps someone else. I know this can use improvements, I'll try to update it when I get back to cleaning up my gulp file.

How to execute different sass files based on a css class?

I am wondering how can I can include a different sass file (theme) based on a certain class?
Now we got 3 apps which all have an unique css class for its styling (.app1 {.background-color: red;})
Now i want to include sass framework and seperate all the css/sass per app label. In order to achieve this we define a base.scss. In this scss we want to reach this:
if .app1 then execute app1.scss
else if .app2 then execute app2.scss
else if .app3 then executr app3.scss
else empty
Anyone an idea?
Use a namespace and apply it as a class to the root element. Wrap SASS statements in the appropriate namespaces. You can separate them into different files (partials) and combine them when they get compiled.
// in file _app1.scss
.app1 {
// rules you want applied in the "app1" case
}
// in file _app2.scss
.app2 {
// rules you want applied in the "app2" case
}
...
If you separate files into _app1.scss, _app2.scss, etc. then base.scss will include:
#import 'app1';
#import 'app2';
...
This combines the partial files into one when you compile. (You don't have to separate them, but it might be cleaner to do so.) Then, you do something like <body class="app1">... to use the app1 namespace, and only rules from _app1.scss will apply.

Render different partial templates in EpiServer

I have a page partial that is supposed to render inside a ContentArea when the page is added there. This works perfectly, but now I have two different ContentAreas on two different pages and I want the same child page added to those to render different on each parent page.
I get that I could in some way use a Tag when rendering the partial to differentiate between the ContentAreas:
#Html.PropertyFor(m => m.MyBlockProperty, new { Tag = RenderingTags.Sidebar })
#Html.PropertyFor(m => m.MyContentArea, new { Tag = RenderingTags.Sidebar })
But then, in my SomePage.cshtml (which is my partial view), do I get a varaible or something here so I know which Tag was asked for? Or is there some naming convention like SidebarSomePage.cshtml so that I can define multiple partial templates? Do I have to create a controller to deal with this? It seems unneccessary to me, I just want to change the html a bit depending on page...
Create a PartialContentController<T> and then use the TemplateDescriptorAttribute to specify the tags you wan't to use. Then use PropertyFor as Johan explained in the view.
From the EPiServer documentation
The template you choose to render a content instance depends on the specific context such as channel and tagging. For a template to be automatically registered it has to implement EPiServer.Web.IRenderTemplate (where T states which model it can render). If you use a base class for your template like PageBase, ContentControlBase, BlockControlBase, PageController, PartialContentController or BlockController, then you do not need to explicitly implement the interface because that is done by the base class. In addition, you can use the TemplateDescriptorAttribute to specify more details about the template such as tags and inheritance, more information on that topic later.
I'm pretty sure you can access the tag from the ViewData dictionary in your view (or controller) like this:
#ViewData["Tag"]
You can also pass any other setting to the view
#Html.PropertyFor(m => m.MyContentArea, new { Tag = RenderingTags.Sidebar, RenderThisPartialDifferently = true, ShowHeading = false })
And then access them:
#ViewData["RenderThisPartialDifferently"]
#ViewData["ShowHeading "]
And then you have the option to have a controller in between and render a completely different view.
Pretty sure there is a naming convention for tag views as well. What I do know for sure though, is that you can put a view with the same name as the tag in /shared/displaytemplates. But that's not what you're asking for now.
Also addition to all answers, you can use template registrator to register additional templates for specific tags.
[ServiceConfiguration(typeof(IViewTemplateModelRegistrator))]
public class TemplateCoordinator : IViewTemplateModelRegistrator
{
public void Register(TemplateModelCollection viewTemplateModelRegistrator)
{
viewTemplateModelRegistrator.Add(typeof(MyBlock), new TemplateModel
{
Tags = new[] { RenderingTags.Sidebar },
AvailableWithoutTag = false,
Path = BlockPath("Some-Other-Template.cshtml")
});
}
}
This will make sure that if block is rendered "inside" RenderingTags.Sidebar context (for instance via Html.PropertyFor(...., new { tag = RenderingTags.Sidebar })) file Some-Other-Template.cshtml will be used.
AlloyTech has sample code there.

Can I create links with 'target="_blank"' in Markdown?

Is there a way to create a link in Markdown that opens in a new window? If not, what syntax do you recommend to do this? I'll add it to the markdown compiler I use. I think it should be an option.
As far as the Markdown syntax is concerned, if you want to get that detailed, you'll just have to use HTML.
Hello, world!
Most Markdown engines I've seen allow plain old HTML, just for situations like this where a generic text markup system just won't cut it. (The StackOverflow engine, for example.) They then run the entire output through an HTML whitelist filter, regardless, since even a Markdown-only document can easily contain XSS attacks. As such, if you or your users want to create _blank links, then they probably still can.
If that's a feature you're going to be using often, it might make sense to create your own syntax, but it's generally not a vital feature. If I want to launch that link in a new window, I'll ctrl-click it myself, thanks.
Kramdown supports it. It's compatible with standard Markdown syntax, but has many extensions, too. You would use it like this:
[link](url){:target="_blank"}
I don't think there is a markdown feature, although there may be other options available if you want to open links which point outside your own site automatically with JavaScript.
Array.from(javascript.links)
.filter(link => link.hostname != window.location.hostname)
.forEach(link => link.target = '_blank');
jsFiddle.
If you're using jQuery:
$(document.links).filter(function() {
return this.hostname != window.location.hostname;
}).attr('target', '_blank');
jsFiddle.
With Markdown v2.5.2, you can use this:
[link](URL){:target="_blank"}
So, it isn't quite true that you cannot add link attributes to a Markdown URL. To add attributes, check with the underlying markdown parser being used and what their extensions are.
In particular, pandoc has an extension to enable link_attributes, which allow markup in the link. e.g.
[Hello, world!](http://example.com/){target="_blank"}
For those coming from R (e.g. using rmarkdown, bookdown, blogdown and so on), this is the syntax you want.
For those not using R, you may need to enable the extension in the call to pandoc with +link_attributes
Note: This is different than the kramdown parser's support, which is one the accepted answers above. In particular, note that kramdown differs from pandoc since it requires a colon -- : -- at the start of the curly brackets -- {}, e.g.
[link](http://example.com){:hreflang="de"}
In particular:
# Pandoc
{ attribute1="value1" attribute2="value2"}
# Kramdown
{: attribute1="value1" attribute2="value2"}
^
^ Colon
One global solution is to put <base target="_blank">
into your page's <head> element. That effectively adds a default target to every anchor element. I use markdown to create content on my Wordpress-based web site, and my theme customizer will let me inject that code into the top of every page. If your theme doesn't do that, there's a plug-in
Not a direct answer, but may help some people ending up here.
If you are using GatsbyJS there is a plugin that automatically adds target="_blank" to external links in your markdown.
It's called gatsby-remark-external-links and is used like so:
yarn add gatsby-remark-external-links
plugins: [
{
resolve: `gatsby-transformer-remark`,
options: {
plugins: [{
resolve: "gatsby-remark-external-links",
options: {
target: "_blank",
rel: "noopener noreferrer"
}
}]
}
},
It also takes care of the rel="noopener noreferrer".
Reference the docs if you need more options.
For ghost markdown use:
[Google](https://google.com" target="_blank)
Found it here:
https://cmatskas.com/open-external-links-in-a-new-window-ghost/
I'm using Grav CMS and this works perfectly:
Body/Content:
Some text[1]
Body/Reference:
[1]: http://somelink.com/?target=_blank
Just make sure that the target attribute is passed first, if there are additional attributes in the link, copy/paste them to the end of the reference URL.
Also work as direct link:
[Go to this page](http://somelink.com/?target=_blank)
You can do this via native javascript code like so:
var pattern = /a href=/g;
var sanitizedMarkDownText = rawMarkDownText.replace(pattern,"a target='_blank' href=");
JSFiddle Code
In my project I'm doing this and it works fine:
[Link](https://example.org/ "title" target="_blank")
Link
But not all parsers let you do that.
There's no easy way to do it, and like #alex has noted you'll need to use JavaScript. His answer is the best solution but in order to optimize it, you might want to filter only to the post-content links.
<script>
var links = document.querySelectorAll( '.post-content a' );
for (var i = 0, length = links.length; i < length; i++) {
if (links[i].hostname != window.location.hostname) {
links[i].target = '_blank';
}
}
</script>
The code is compatible with IE8+ and you can add it to the bottom of your page. Note that you'll need to change the ".post-content a" to the class that you're using for your posts.
As seen here: http://blog.hubii.com/target-_blank-for-links-on-ghost/
If someone is looking for a global rmarkdown (pandoc) solution.
Using Pandoc Lua Filter
You could write your own Pandoc Lua Filter which adds target="_blank" to all links:
Write a Pandoc Lua Filter, name it for example links.lua
function Link(element)
if
string.sub(element.target, 1, 1) ~= "#"
then
element.attributes.target = "_blank"
end
return element
end
Then update your _output.yml
bookdown::gitbook:
pandoc_args:
- --lua-filter=links.lua
Inject <base target="_blank"> in Header
An alternative solution would be to inject <base target="_blank"> in the HTML head section using the includes option:
Create a new HTML file, name it for example links.html
<base target="_blank">
Then update your _output.yml
bookdown::gitbook:
includes:
in_header: links.html
Note: This solution may also open new tabs for hash (#) pointers/URLs. I have not tested this solution with such URLs.
In Laravel I solved it this way:
$post->text= Str::replace('<a ', '<a target="_blank"', $post->text);
Not works for a specific link. Edit all links in the Markdown text. (In my case it's fine)
I ran into this problem when trying to implement markdown using PHP.
Since the user generated links created with markdown need to open in a new tab but site links need to stay in tab I changed markdown to only generate links that open in a new tab. So not all links on the page link out, just the ones that use markdown.
In markdown I changed all the link output to be <a target='_blank' href="..."> which was easy enough using find/replace.
I do not agree that it's a better user experience to stay within one browser tab. If you want people to stay on your site, or come back to finish reading that article, send them off in a new tab.
Building on #davidmorrow's answer, throw this javascript into your site and turn just external links into links with target=_blank:
<script type="text/javascript" charset="utf-8">
// Creating custom :external selector
$.expr[':'].external = function(obj){
return !obj.href.match(/^mailto\:/)
&& (obj.hostname != location.hostname);
};
$(function(){
// Add 'external' CSS class to all external links
$('a:external').addClass('external');
// turn target into target=_blank for elements w external class
$(".external").attr('target','_blank');
})
</script>
You can add any attributes using {[attr]="[prop]"}
For example [Google] (http://www.google.com){target="_blank"}
For completed alex answered (Dec 13 '10)
A more smart injection target could be done with this code :
/*
* For all links in the current page...
*/
$(document.links).filter(function() {
/*
* ...keep them without `target` already setted...
*/
return !this.target;
}).filter(function() {
/*
* ...and keep them are not on current domain...
*/
return this.hostname !== window.location.hostname ||
/*
* ...or are not a web file (.pdf, .jpg, .png, .js, .mp4, etc.).
*/
/\.(?!html?|php3?|aspx?)([a-z]{0,3}|[a-zt]{0,4})$/.test(this.pathname);
/*
* For all link kept, add the `target="_blank"` attribute.
*/
}).attr('target', '_blank');
You could change the regexp exceptions with adding more extension in (?!html?|php3?|aspx?) group construct (understand this regexp here: https://regex101.com/r/sE6gT9/3).
and for a without jQuery version, check code below:
var links = document.links;
for (var i = 0; i < links.length; i++) {
if (!links[i].target) {
if (
links[i].hostname !== window.location.hostname ||
/\.(?!html?)([a-z]{0,3}|[a-zt]{0,4})$/.test(links[i].pathname)
) {
links[i].target = '_blank';
}
}
}
Automated for external links only, using GNU sed & make
If one would like to do this systematically for all external links, CSS is no option. However, one could run the following sed command once the (X)HTML has been created from Markdown:
sed -i 's|href="http|target="_blank" href="http|g' index.html
This can be further automated by adding above sed command to a makefile. For details, see GNU make or see how I have done that on my website.
If you just want to do this in a specific link, just use the inline attribute list syntax as others have answered, or just use HTML.
If you want to do this in all generated <a> tags, depends on your Markdown compiler, maybe you need an extension of it.
I am doing this for my blog these days, which is generated by pelican, which use Python-Markdown. And I found an extension for Python-Markdown Phuker/markdown_link_attr_modifier, it works well. Note that an old extension called newtab seems not work in Python-Markdown 3.x.
For React + Markdown environment:
I created a reusable component:
export type TargetBlankLinkProps = {
label?: string;
href?: string;
};
export const TargetBlankLink = ({
label = "",
href = "",
}: TargetBlankLinkProps) => (
<a href={href} target="__blank">
{label}
</a>
);
And I use it wherever I need a link that open in a new window.
For "markdown-to-jsx" with MUI v5
This seem to work for me:
import Markdown from 'markdown-to-jsx';
...
const MarkdownLink = ({ children, ...props }) => (
<Link {...props}>{children}</Link>
);
...
<Markdown
options={{
forceBlock: true,
overrides: {
a: {
component: MarkdownLink,
props: {
target: '_blank',
},
},
},
}}
>
{description}
</Markdown>
This works for me: [Page Link](your url here "(target|_blank)")