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

CSS Rules for @media and :hover are stripped out in v4 #728

Closed
is-jonreeves opened this issue Jul 26, 2022 · 10 comments
Closed

CSS Rules for @media and :hover are stripped out in v4 #728

is-jonreeves opened this issue Jul 26, 2022 · 10 comments

Comments

@is-jonreeves
Copy link

is-jonreeves commented Jul 26, 2022

  • Maizzle Version: 4.0.2
  • Node.js Version: 18.5.0

While upgrading from @maizzle/framework@3.7.3 and @maizzle/cli@1.4.0 I noticed the build output was different in v4. It seems like any @media {} or :hover rules were being stripped out.

Example

I've tested in a minimal new project to confirm. This is the project:

package.json

{
  "private": true,
  "scripts": {
    "build": "maizzle build production"
  },
  "devDependencies": {
    "@maizzle/framework": "^4.0.2"
  }
}

config.production.js

module.exports = {
  build: {
    templates: {
      destination: {
        path: 'build_production',
      },
    },
  },
  inlineCSS: true,
  removeUnusedCSS: true,
}

src/layouts/main.html

<!DOCTYPE {{{ page.doctype || 'html' }}}>
<html lang="{{ page.language || 'en' }}" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
  <meta charset="{{ page.charset || 'utf-8' }}">
</head>
<body class="{{ page.class }}">
  <div role="article" aria-roledescription="email" aria-label="{{{ page.title || '' }}}" lang="{{ page.language || 'en' }}">
    <block name="template"></block>
  </div>
</body>
</html>

src/templates/helloworld.html

---
title: "Hello World"
---

<extends src="src/layouts/main.html">
  <block name="template">
    <style>
      .content {
        width: 640px;
      }
      @media (max-width: 600px) {
        .content {
          width: 100%;
          min-width: 100%;
        }
      }
      .action {
        color: red;
      }
      .action:hover {
        color: blue;
      }
    </style>
    <div class="content">
      <button class="action">Hello World</button>
    </div>
  </block>
</extends>

Results

build_production/helloworld.html (v4 -- missing styles)

<!DOCTYPE html>
<html lang="en" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
  <meta charset="utf-8">
</head>
<body>
  <div role="article" aria-roledescription="email" aria-label="Hello World" lang="en">
    <div style="width: 640px">
      <button style="color: red">Hello World</button>
    </div>  </div>
</body>
</html>

build_production/helloworld.html (v3)

<!DOCTYPE html>
<html lang="en" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
  <meta charset="utf-8">
</head>
<body>
  <div role="article" aria-roledescription="email" aria-label="Hello World" lang="en">    <style>
@media (max-width: 600px) {
  .content {
    width: 100%;
    min-width: 100%;
  }
}
.action:hover {
  color: blue;
}
</style>
    <div class="content" style="width: 640px;">
      <button class="action" style="color: red;">Hello World</button>
    </div>  </div>
</body>
</html>

It's possible I'm missing some new option/configuration, but after trying a bunch I am still unsuccessful in producing a valid (v3 like) output.

@cossssmin
Copy link
Member

You’re not outputting page.css in your layout style tag, so there’s no Tailwind CSS added to the HTML.

https://maizzle.com/docs/tailwindcss#workflow

@is-jonreeves
Copy link
Author

We aren't utilizing tailwind in our project. Is this now a requirement for using Maizzle v4?


I tried using the official starter https://github.com/maizzle/maizzle which does use tailwind and added the same css rules but they are still stripped out:

<!-- transactional.html -->
...
<block name="template">
  <style>
    .content {
      width: 640px;
    }

    @media (max-width: 600px) {
      .content {
        width: 100%;
        min-width: 100%;
      }
    }

    .action {
      color: red;
    }

    .action:hover {
      color: blue;
    }
  </style>
  <table class="content w-full font-sans">
...
      <a class="action" href="https://maizzle.com">
...

@cossssmin
Copy link
Member

cossssmin commented Aug 2, 2022

Right, my bad, misunderstood. Is there a specific reason you're outputting that <style> in the body instead of the head? Gmail for example doesn't support it there.

I'll need to run some tests, but in the meanwhile does it work if you add data-embed to the <style> tag, i.e. <style data-embed>?

@is-jonreeves
Copy link
Author

is-jonreeves commented Aug 2, 2022

I did try out the data-embed option and I believe it worked in the above example, but unfortunately in my real-world project it didn't work out as it indiscriminately keeps "all rules" rather than distilling down to just the ones that should be de-classed. I suspect I could refactor into multiple style tags with only the media/:hover I need in the one with data-embed but it splits/complicates the sass a little. I have a feeling I might have tried that and don't think it was successful, otherwise I probably would have just run with it. But can't be sure, but will check for you.

Also as mentioned above, we are using a custom transformer/filter <style sass> if that helps for context at all.

Regarding the placement of the <style> that was just for simplicity of this example. In my actual project the layout and templates look more like this.

<!-- Layout -->
<!DOCTYPE {{{ page.doctype || 'html' }}}>
<html lang="{{ page.language || 'en' }}" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
  ...
  <if condition="page.title">
    <title>{{{ page.title }}}</title>
  </if>
  <style sass>
    @import 'src/styles/main';
  </style>
  <block name="head"></block>
</head>
<body class="{{ page.class }}">
  <div role="article" aria-roledescription="email" aria-label="{{{ page.title || '' }}}" lang="{{ page.language || 'en' }}">
    <block name="template"></block>
  </div>
</body>
</html>
<!-- Template -->
<extends src="src/layouts/main.html">
  <block name="head">
    <style sass>
      ...
    </style>
  </block>

  <block name="template">
    ...
  </block>
</extends>

Basically each template has few of their own styles that need to be transported to the Head and combined with the ones in the layout. Works really well in V3.

@cossssmin
Copy link
Member

Maizzle doesn't have a Sass CSS compiler, is that something custom that you're implementing?

@is-jonreeves
Copy link
Author

Maizzle doesn't have a Sass CSS compiler, is that something custom that you're implementing?

Yeah we just added a transform in v3 (filter when testing v4):

// config.js / config.production.js
const sass = require('sass');

module.exports = {
...
  transform: {
    sass: (content) => sass.compileString(content, { loadPaths: ['./'] }).css,
  },
};

The issue exists with or without sass, which is why I avoided mentioning it in the original issue description.

I suspect I could refactor into multiple style tags with only the media/:hover I need in the one with data-embed but it splits/complicates the sass a little. I have a feeling I might have tried that and don't think it was successful, otherwise I probably would have just run with it. But can't be sure, but will check for you.

So yeah, you'd have to split up the stylesheet quite carefully to avoid retaining unneeded classes by accident. Assuming I'm using sass via a transform/filter as described above, and I have this layout and template:

<!-- Layout -->
<!DOCTYPE {{{ page.doctype || 'html' }}}>
<html lang="{{ page.language || 'en' }}" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
  ...
  <if condition="page.title">
    <title>{{{ page.title }}}</title>
  </if>
  <style sass>
    @import 'src/styles/main';
  </style>
  <block name="head"></block>
</head>
<body class="{{ page.class }}">
  <div role="article" aria-roledescription="email" aria-label="{{{ page.title || '' }}}" lang="{{ page.language || 'en' }}">
    <block name="template"></block>
  </div>
</body>
</html>
<!-- Template -->
---
title: "Hello World"
---

<extends src="src/layouts/main.html">
  <block name="head">
    <style sass>
      .content {
        width: 640px;

        @media (max-width: 600px) {
          width: 100%;
          min-width: 100%;
        }
      }

      .heading {
        text-transform: uppercase;
      }

      .action {
        color: red;

        &:hover {
          color: blue;
        }
      }
    </style>
  </block>
  <block name="template">
    <div class="content">
      <h1 class="heading">{{page.title}}</h1>
      <button class="action">Action</button>
    </div>
  </block>
</extends>

In v4 (as originally mentioned) we lose all class attributes and therefore also the :hover and @media:

<!-- v4 output (without data-embed) -->
<!DOCTYPE html>
<html lang="en" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
  <meta charset="utf-8">
</head>
<body>
  <div role="article" aria-roledescription="email" aria-label="Hello World" lang="en">
    <div style="width: 640px">
      <h1 style="text-transform: uppercase">Hello World</h1>
      <button style="color: red">Action</button>
    </div>
  </div>
</body>
</html>

If we use data-embed we are forced to keep all class usages:

<!-- v4 output (with data-embed) -->
<!DOCTYPE html>
<html lang="en" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
  <meta charset="utf-8">
  <style>
    .content {
      width: 640px;
    }
    @media (max-width: 600px) {
      .content {
        width: 100%;
        min-width: 100%;
      }
    }
    .heading {
      text-transform: uppercase;
    }
    .action {
      color: red;
    }
    .action:hover {
      color: blue;
    }
  </style>
</head>
<body>
  <div role="article" aria-roledescription="email" aria-label="Hello World" lang="en">
    <div class="content">
      <h1 class="heading">Hello World</h1>
      <button class="action">Action</button>
    </div>
  </div>
</body>
</html>

We would have to carefully restructure the css/sass into separate <style> tags with and without data-embed:

<!-- Example of separated input styles to make it work -->
...
<block name="head">
  <style sass>
    .heading {
      text-transform: uppercase;
    }
  </style>
  <style sass data-embed>
    .content {
      width: 640px;

      @media (max-width: 600px) {
        width: 100%;
        min-width: 100%;
      }
    }

    .action {
      color: red;

      &:hover {
        color: blue;
      }
    }
  </style>
</block>
...

Then in v4 you can achieve this output (which is not ideal, but will work):

<!-- v4 output (with multiple style tags with and without data-embed) -->
<!DOCTYPE html>
<html lang="en" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
  <meta charset="utf-8">
  <style>
    .content {
      width: 640px;
    }
    @media (max-width: 600px) {
      .content {
        width: 100%;
        min-width: 100%;
      }
    }
    .action {
      color: red;
    }
    .action:hover {
      color: blue;
    }
  </style>
</head>
<body>
  <div role="article" aria-roledescription="email" aria-label="Hello World" lang="en">
    <div class="content">
      <h1 style="text-transform: uppercase">Hello World</h1>
      <button class="action">Action</button>
    </div>
  </div>
</body>
</html>

By contrast in v3 using the original input with one <style> and no data-embed this is the output:

<!-- v3 output (without data-embed) -->
<!DOCTYPE html>
<html lang="en" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
  <meta charset="utf-8">
  <style>
    @media (max-width: 600px) {
      .content {
        width: 100%;
        min-width: 100%;
      }
    }
    .action:hover {
      color: blue;
    }
  </style>
</head>
<body>
  <div role="article" aria-roledescription="email" aria-label="Hello World" lang="en">
    <div class="content" style="width: 640px;">
      <h1 style="text-transform: uppercase;">Hello World</h1>
      <button class="action" style="color: red;">Action</button>
    </div>
  </div>
</body>
</html>

@cossssmin
Copy link
Member

Hey sorry this took some time, the issue is with removeInlinedClasses, which is needed to be able to write selectors that don't exist in the HTML, like those targeting certain email clients.

If you don't need that, the solution for now is to disable it:

// config.production.js

module.exports = {
  // ...,
  inlineCSS: {
    removeStyleTags: true,
  },
  removeUnusedCSS: true,
  removeInlinedClasses: false,
}

What happens is that removeInlinedClasses naively removes classes from the HTML that it shouldn't, like content in your case. This is because although it knows to not touch @media queries, the .content class exists in the CSS outside of a query so it still takes effect. We need to improve that transformer so that if it finds a selector inside a query or a pseudo, it doesn't remove it at all.

@Frolipon
Copy link

Hello,

Same problem here. I tried with the data-embed, but even with that I can't make it to keep the class for the hover.
Here is how I did it (code is simplified for readability):
In the layout file:

<!DOCTYPE {{{ page.doctype || 'html' }}}>
<html lang="fr" xmlns:v="urn:schemas-microsoft-com:vml">

<head>
	(...)
	<style tailwindcss data-embed>
	
	.footerButton {
		transition: 0.3s;
	}
	.footerButton:hover {
		background-color:#d9d9d9 !important;
		transition: 0.3s;
	}
	</style>
	(...)
</head>
<body>
(...)
</body>

and in the template file

(...)
<table class="w-full">
	<tr>
		<td class="rounded-3 p-10 sm:px-0 bg-[#f3f2f5] footerButton sm:w-full" align="center">
			<table>
				<tr>
					<td>
						<p class="text-center text-black text-14 leading-[100%]">
							<a href="" class="no-underline text-black">
								Hello world
							</a>
						</p>
					</td>
				</tr>
			</table>
		</td>
	</tr>
</table>
(...)

I tried removeInlinedClasses: false, and it does the trick, but yeah, it does keep every class which isn't ideal.
The dream solution for me would be to have something like:

removeInlinedClasses: {
	whitelist: ['.footerButton']
},

Otherwise, I would be glad if you have a workaround to keep specific class names and not all of them.

@Frolipon
Copy link

I found a workaround for my problem. I post it here in case it helps someone:

I add an attribute called classtokeep with the class(es) I want to keep:

(...)
<table class="w-full">
	<tr>
		<td classtokeep="footerButton" class="rounded-3 p-10 sm:px-0 bg-[#f3f2f5] footerButton sm:w-full" align="center">
			<table>
				<tr>
					<td>
						<p class="text-center text-black text-14 leading-[100%]">
							<a href="" class="no-underline text-black">
								Hello world
							</a>
						</p>
					</td>
				</tr>
			</table>
		</td>
	</tr>
</table>
(...)

And then I add in the config.production.js file

module.exports = {
(...)
	  replaceStrings: {
		'classtokeep': 'class',
	  },
  }

@cossssmin
Copy link
Member

OK, this should be fixed in 4.1.1, give it a try 👍

https://github.com/maizzle/framework/releases/tag/v4.1.1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants