Skip to content

Format CSS in template literals with expressions#2102

Merged
vjeux merged 7 commits intoprettier:masterfrom
pomber:css-in-template-literals-with-expressions
Jun 13, 2017
Merged

Format CSS in template literals with expressions#2102
vjeux merged 7 commits intoprettier:masterfrom
pomber:css-in-template-literals-with-expressions

Conversation

@pomber
Copy link
Copy Markdown
Contributor

@pomber pomber commented Jun 11, 2017

This adds support for formatting CSS inside template literals with expressions (#1948). For example:

<style jsx>{`
  div {
    animation: linear ${seconds}s ease-out;
  }
`}</style>;

Currently is only working for styled-jsx, but if you like the approach, I can add styled-components too, it should be easy.

It works like this:

  1. It replace all the expressions from the template literal with @prettier-placeholder
  2. Parse and print the template literal (css) doc
  3. Print the doc for each expression
  4. Traverse the css doc replacing all the placeholders with the expression docs

If it fails to find or replace any of the placeholders it throws an error and abort the formatting of the template literal.

@vjeux
Copy link
Copy Markdown
Contributor

vjeux commented Jun 11, 2017

Currently is only working for styled-jsx, but if you like the approach, I can add styled-components too, it should be easy.

FYI, styled-jsx doesn't support dynamic expressions like this, only styled-components do ;)

function wrapCssTemplate(quasisDoc, expressionDocs) {
const allReplaced = replacePlaceholders(quasisDoc, expressionDocs);
if (!allReplaced) {
throw new Error("Couldn't insert all the expressions");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you use createError with the right location, this way if it fires people won't be as confused.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, but this won't be shown to users, it's there just in case a placeholder was parsed in an odd way and we couldn't find it. Throwing the error aborts the formatting of the template literal but the rest of the file should be formatted normally.
Maybe it should be an assert.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, let's just leave it like this. If it becomes an issue, people will report it and we'll figure out a solution.

) {
// If placeholder is splitted, join it
const [at, placeholder, ...rest] = doc.parts;
doc.parts = [at + placeholder, ...rest];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you re-create a doc tree while you are iterating instead of doing mutations? It's going to bite us in the future I'm sure :(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm having problems trying to find a clean way to re-create the doc. Is the mutation during the iteration what you don't like? If so, I can postpone the mutations until the traverse finishes.

const text = rawQuasis.join("@prettier-placeholder");

return {
parser: "postcss",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should make all the CSS go through the same codepath instead of duplicating this non trivial logic.

@vjeux
Copy link
Copy Markdown
Contributor

vjeux commented Jun 11, 2017

This is so exciting! Thanks for building it <3

If you can build a new doc tree instead of mutating it and merging the CSS codepaths, I'll merge it. I'm so excited :)

@pomber
Copy link
Copy Markdown
Contributor Author

pomber commented Jun 11, 2017

Thanks, I'll try to fix it later today.

Do you know why Node v4 does not like the PR?

For the styled-jsx test cases, I'm using what @giuseppeg recommended in this issue

@vjeux
Copy link
Copy Markdown
Contributor

vjeux commented Jun 11, 2017

Probably crashes on destructuring for node 4. You can use nvm to install node 4 and try it yourself.

Good to know about styled-jsx that supports expressions, I thought it didn't.

@giuseppeg
Copy link
Copy Markdown

Good to know about styled-jsx that supports expressions, I thought it didn't.

We do support expressions (constants) but we don't allow the usage of props or anything that is in the component scope. Basically we try to stop people from putting dynamic code inside of expressions because we don't support them yet. You can find the relevant code in the utils file.

@pomber you may want to update the fixture, I added more use cases.

@vjeux
Copy link
Copy Markdown
Contributor

vjeux commented Jun 11, 2017

Can you use mapDoc to rebuild it?

@pomber pomber force-pushed the css-in-template-literals-with-expressions branch from 3a6fcba to 6050530 Compare June 11, 2017 21:49
animation: 3s
ease-in
1s
\${foo => foo.getIterations()}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😁

@azz
Copy link
Copy Markdown
Member

azz commented Jun 11, 2017

This is so exciting!

Is there a chance that someone will type @prettier-placeholder and break this?

@pomber
Copy link
Copy Markdown
Contributor Author

pomber commented Jun 11, 2017

Mmm, it won't break, but the template won't be formatted. So you could use @prettier-placeholder as a escape hatch :p

(/^[A-Z]/.test(parent.tag.object.name) &&
parent.tag.property.name === "extend"));

if (isStyledJsx || isStyledComponents) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: isStyledComponents and isStyledJsx could be functions that get the current path as input.

Then you can make an helper function isCss:

export const isCss = path => [isStyledJsx, isStyledCompoents].some(test => test(path))

This way you can add tests for other libraries easily.

@azz
Copy link
Copy Markdown
Member

azz commented Jun 12, 2017

Just FYI there's probably going to be merge conflicts with #2109.

@pomber pomber force-pushed the css-in-template-literals-with-expressions branch from fd060c7 to 577d189 Compare June 13, 2017 14:36
@pomber
Copy link
Copy Markdown
Contributor Author

pomber commented Jun 13, 2017

Done with the merge, thanks @azz :p

@vjeux vjeux merged commit 8dd0cb2 into prettier:master Jun 13, 2017
@vjeux
Copy link
Copy Markdown
Contributor

vjeux commented Jun 13, 2017

<3 <3 <3 <3

@pomber pomber deleted the css-in-template-literals-with-expressions branch June 13, 2017 17:29
@lock lock bot added the locked-due-to-inactivity Please open a new issue and fill out the template instead of commenting. label Jan 20, 2019
@lock lock bot locked as resolved and limited conversation to collaborators Jan 20, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

locked-due-to-inactivity Please open a new issue and fill out the template instead of commenting.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants