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

Emotion v10 unusable with pure TypeScript

Closed
krzysztofzuraw opened this issue Nov 28, 2018 · 78 comments
Closed

Emotion v10 unusable with pure TypeScript #1046

krzysztofzuraw opened this issue Nov 28, 2018 · 78 comments
Labels

Comments

@krzysztofzuraw
Copy link
Contributor

krzysztofzuraw commented Nov 28, 2018

  • emotion version: 10.0.0
  • react version: 16.6.3
  • typescript version: 3.1.6

Relevant code.

const flexContainer = css`
  display: flex;
  justify-content: space-between;
`;

What you did:
I've tried to use it as:

<div className={flexContainer.styles} />

Or:

<div css={flexContainer} />

What happened:
I've got this in html:

<div class="
  display: flex;
  justify-content: space-between;
"></div>

Or:

<div css="[object Object]"></div>

I'm using only TypeScript without babel.

Problem description:
How can I use css or className with emotion v10 and TypeScript?

@TrySound
Copy link
Contributor

TrySound commented Nov 28, 2018

@krzysztofzuraw Do you have jsx pragma? It's hard to say without reproducing example.

@krzysztofzuraw
Copy link
Contributor Author

krzysztofzuraw commented Nov 28, 2018

@TrySound sure - I will prepare codesandbox 😄

@krzysztofzuraw
Copy link
Contributor Author

krzysztofzuraw commented Nov 28, 2018

@TrySound Codesandbox example: https://codesandbox.io/s/mm0pymo5v8

@Andarist
Copy link
Member

Andarist commented Nov 28, 2018

Working example: https://codesandbox.io/s/2j01mr3m1y . I've added "jsxFactory": "jsx", option, import { jsx } from @emotion/core and jsx pragma (/** @jsx jsx */). This probably can be simplified - I just dont know how to automate it.

Type checking css prop doesnt quite work on this sandbox. Probably @Ailrun will know what to do about it.

@jacoporicare
Copy link

jacoporicare commented Nov 28, 2018

FYI having "jsxFactory": "jsx" means that you would have to add import { jsx } from '@emotion/core'; to every single React component.
To be honest I'm really sad about this change (jsx pragma in every file you need css prop) in Emotion 10 👎.

@Andarist
Copy link
Member

Andarist commented Nov 28, 2018

With babel this all can be automated - no need for any manual wiring up. Im just not that experienced with TS to know how to achieve the same with it (I suspect there is one though).

@jacoporicare
Copy link

jacoporicare commented Nov 28, 2018

I see, that means no jsx pragma with the plugin? I haven't found this information in the docs, sorry ☹️. I'm afraid that with pure TypeScript compiler there's only the js pragma way or jsxFactory option but that would mean the import as I mentioned earlier.

@Ailrun
Copy link
Member

Ailrun commented Dec 7, 2018

@Andarist
TS does not support typechecking based on JSX pragma (we cannot change typing when users change JSX pragma), so current TS typing just have css props when users import @emotion/core.
And this issue actually is not about TS, but about the way to use emotion@10, since @krzysztofzuraw missed JSX pragma, and used className instead of css.

@Ailrun Ailrun added the question label Dec 7, 2018
@Andarist
Copy link
Member

Andarist commented Dec 7, 2018

@Ailrun
Copy link
Member

Ailrun commented Dec 7, 2018

Type check will work with imported @emotion/core, but since emotion@10 requires JSX pragma to be executed, user should put JSX pragma.

@joe-bell
Copy link
Contributor

joe-bell commented Jan 21, 2019

Whilst not pretty, a temporary solution is to state jsx; after declaring the pragma
(no need to set "jsxFactory" and import in each file)

/** @jsx jsx */ jsx;

For example

/** @jsx jsx */ jsx;
import * as React from "react";
import { jsx, css } from "@emotion/core";

const yolo = css({
  color: "lightcoral"
});

and then use like so

<p css={yolo}>swaggins</p>

@bitttttten
Copy link

bitttttten commented Jan 22, 2019

I am struggling to use the css prop and jsx pragma with typescript (using CRA) and I get ReferenceError: jsx is not defined.

/** @jsx jsx */ jsx
import { jsx, css } from '@emotion/core'

@joe-bell this fixes it for me, but aren't you're still having to declare the jsx pragma in each file?

@Ailrun
Copy link
Member

Ailrun commented Jan 23, 2019

  1. If you don't want to repeat JSX pragma, use "jsxFactory" option with "jsx": "React" option.
  2. If you want not to change default jsx factory which is "React.createElement", you can put /** @jsx jsx */ in files those are also importing jsx function and using emotion.
    See https://www.typescriptlang.org/docs/handbook/jsx.html

@joe-bell
Copy link
Contributor

joe-bell commented Jan 23, 2019

@bitttttten yes, but only in the files where you need to enable it! Not every file.

@Ailrun Unfortunately with 2, this doesn't seem to work for unless you call jsx; somewhere in the file.

@Ailrun
Copy link
Member

Ailrun commented Jan 23, 2019

Did you mean webpack treeshakes jsx? Could you give me more info?

@joe-bell
Copy link
Contributor

joe-bell commented Jan 23, 2019

@Ailrun sure, in this instance I'm compiling using tsc.

If I use:

/** @jsx jsx */;
import { css, jsx } from "@emotion/core";

No errors appear in the console during compile time but when I consume the outputted lib in Docz I'm presented with a Webpack error page of "ReferenceError: jsx is not defined"

However, if I add jsx; to the end of the first line pragma comment, everything works perfectly.


Environment:

  • @emotion/core@10.0.6
  • react@16.7.0
  • typescript@3.2.2
  • docz@0.13.7

I'd love to be able to offer some support to investigate this issue further but I'm fairly new to TypeScript

@Ailrun
Copy link
Member

Ailrun commented Jan 23, 2019

Could you give me the result (the output of the compilation of lib) for a single component with the problem? It seems a TS issue or a Webpack issue to me...

@bitttttten
Copy link

bitttttten commented Jan 24, 2019

Using:

/** @jsx jsx */
import { css, jsx } from '@emotion/core'
import { FunctionComponent } from 'react'

const App: FunctionComponent<{ text: string }> = ({ text }) => {
    return (
        <div
            css={css`
                text-align: center;
            `}
        >
            {text}
        </div>
    )
}

Auto saves into

/** @jsx jsx */
import { css } from '@emotion/core'
import { FunctionComponent } from 'react'

const App: FunctionComponent<{ text: string }> = ({ text }) => {
    return (
        <div
            css={css`
                text-align: center;
            `}
        >
            {text}
        </div>
    )
}

(jsx is removed because auto save cleans up unused variables)

It gives, in the browser:

ReferenceError: jsx is not defined
App
src/App.tsx:7
   5 | 
   6 | const App: FunctionComponent<{ text: string }> = ({ text }) => {
>  7 |     return (
   8 |         <div
   9 |             css={css`
  10 |                 text-align: center;

Full dump here: https://pastebin.com/1EBkHRLN

tsconfig is:

{
    "compilerOptions": {
        "target": "es5",
        "lib": ["dom", "dom.iterable", "esnext"],
        "allowJs": true,
        "skipLibCheck": true,
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        "strict": true,
        "forceConsistentCasingInFileNames": true,
        "module": "esnext",
        "moduleResolution": "node",
        "resolveJsonModule": true,
        "isolatedModules": true,
        "noEmit": true,
        "jsx": "preserve"
    },
    "exclude": [
        "node_modules",
        "build",
        "scripts",
        "public",
        "src/setupTests.ts"
    ],
    "include": ["src"]
}

and eslintrc.json:

{
    "extends": ["airbnb", "prettier"],
    "plugins": ["prettier"],
    "rules": {
        "prettier/prettier": ["error"]
    },
    "parser": "babel-eslint",
    "parserOptions": {
        "ecmaFeatures": {
            "jsx": true,
            "modules": true
        }
    }
}

@tkh44
Copy link
Member

tkh44 commented Jan 24, 2019

FWIW the emotion eslint plugins will automatically add pragma and import for you when using the ESLint --fix option.

https://github.com/emotion-js/emotion/blob/master/packages/eslint-plugin-emotion/docs/rules/jsx-import.md

Check out the others here: https://github.com/emotion-js/emotion/tree/master/packages/eslint-plugin-emotion

I'm not sure how this will interact with typescript but it removes the effort of using the pragma import. I just start typing and on save its added for me and I don't think about it again.

@bitttttten thanks for the example. I'm going to ask around.

@Ailrun
Copy link
Member

Ailrun commented Jan 24, 2019

@bitttttten Which editor do you use? Does you editor also remove React import from import React from 'react' if you don't use React directly? If not, is it impossible to set your editor not to remove jsx too?

@sompylasar
Copy link

sompylasar commented Jan 25, 2019

This is the relevant ESLint rule: https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-react.md

And its docs say that it should work with the pragma comment:

If you are using the @jsx pragma this rule will mark the designated variable and not the React one.

@sompylasar
Copy link

sompylasar commented Jan 25, 2019

Maybe the plugins are applied in a wrong order, e.g. the https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-react.md is applied before the pragma comment is added to the source code by https://github.com/emotion-js/emotion/blob/master/packages/eslint-plugin-emotion/docs/rules/jsx-import.md

@ferdaber
Copy link

ferdaber commented Jan 25, 2019

TypeScript will not auto-remove unused variables or imports unless you tell it to (it's not part of its downcompile). @joe-bell can you share your tsconfig.json and what the before/after code looks like?

@joe-bell
Copy link
Contributor

joe-bell commented Jan 25, 2019

@Ailrun apologies for the delay, I'm going to need to try to re-create this issue without using my client's code. Will try to give you some more details on this soon

@bitttttten
Copy link

bitttttten commented Jan 25, 2019

Which editor do you use?

VSCode

Does you editor also remove React import from import React from 'react' if you don't use React directly?

No!

/** @jsx jsx */
import { jsx, css } from '@emotion/core'
import React, { FunctionComponent } from 'react'

Gets turned into:

/** @jsx jsx */
import { css } from '@emotion/core'
import React, { FunctionComponent } from 'react'

If not, it is impossible to set your editor not to remove jsx too?

That's why I came into this thread.. hehe. I even tried installing eslint-plugin-emotion although it seems to have no effect.

I made a repo for this issue, which is just a CRA app. The file I have been talking about is here.

@cameron-martin
Copy link
Contributor

cameron-martin commented Jan 26, 2019

Did you mean webpack treeshakes jsx? Could you give me more info?

I'm guessing that the people who are having this issue where adding jsx; to the start of the file fixes this are compiling using babel, and the typescript babel plugin removes imports that aren't used in a value position because it is assumed that they are type-only imports. This is different from the way that tsc compiles TypeScript, because babel has the limitation that it only looks at a single file when compiling.

I'll open an issue on the babel repo.

EDIT: There already is an issue opened, and the fix is to set the jsxPragma option in the typescript transform.

@bitttttten
Copy link

bitttttten commented Jan 26, 2019

So it's not possible to fix this when using CRA until the latest fix in babel has been released.

@FezVrasta
Copy link
Member

FezVrasta commented Jun 2, 2019

While we are here it would make sense to verify if the same issue happens with Flow so that we can document both.

@IsenrichO
Copy link

IsenrichO commented Jun 3, 2019

The bottom line of this issue is that emotion is usable with TS, but you have to use custom jsx pragma and can't use <></> syntax ( has to be used).

@Andarist What does that "custom jsx pragma" look like? Do you just mean /** @jsx jsx */? Can't you also just get around the jsx pragma using the @emotion/babel-preset-css-prop plugin?

@Andarist
Copy link
Member

Andarist commented Jun 3, 2019

@Andarist What does that "custom jsx pragma" look like? Do you just mean /** @jsx jsx */?

Yes.

Can't you also just get around the jsx pragma using the @emotion/babel-preset-css-prop plugin?

Yes, you can - but the issue here was about usage with TS compiler, not babel.

@paramsinghvc
Copy link

paramsinghvc commented Jun 6, 2019

WTH 😤I've spent hours and this nasty [object Object] thing is not going away anyhow in my typescript react project.
Does anyone know a solution to this sad problem??

@peduarte
Copy link

peduarte commented Jun 6, 2019

I know this isn't helpful, but I ended up switching back to Styled Components :(

@karol-majewski
Copy link
Contributor

karol-majewski commented Jun 6, 2019

@paramsinghvc I'm using Parcel with the following configuration and it works as expected.

.babelrc

{
  "plugins": [
    ["transform-inline-environment-variables"],
    ["babel-plugin-jsx-pragmatic", { "export": "jsx", "module": "@emotion/core", "import": "___EmotionJSX" }],
    ["@babel/plugin-transform-react-jsx", { "pragma": "___EmotionJSX", "pragmaFrag": "React.Fragment" }],
    ["emotion", { "autoLabel": true, "labelFormat": "[local]", "useBuiltIns": false, "throwIfNamespace": true, "sourceMap": true }]
  ]
}

tsconfig.json

{
  "compilerOptions: {
    ...
    "jsx": "preserve"
    ...
  }
}

Make sure you have installed the plugins required in .babelrc and you don't call React.createElement directly.

@paramsinghvc
Copy link

paramsinghvc commented Jun 6, 2019

@karol-majewski Thanks it worked. But I tried using it with css prop babel preset as

{
  "presets": ["@emotion/babel-preset-css-prop"],
  "plugins": [
    ["transform-inline-environment-variables"],
    ["babel-plugin-jsx-pragmatic", { "export": "jsx", "module": "@emotion/core", "import": "___EmotionJSX" }],
    ["@babel/plugin-transform-react-jsx", { "pragma": "___EmotionJSX", "pragmaFrag": "React.Fragment" }],
    ["emotion", { "autoLabel": true, "labelFormat": "[local]", "useBuiltIns": false, "throwIfNamespace": true, "sourceMap": true }]
  ]
}

It doesn't work without me having to specify /** @jsx jsx */ on top of every file and doing import { jsx } from "@emotion/core"; Can't we make it work somehow by using @emotion/babel-preset-css-prop.

@lee-reinhardt
Copy link

lee-reinhardt commented Jun 6, 2019

@paramsinghvc

I have an example repo that's using Babel Typescript, Parcel, Emotion 10 and the CSS prop (no pragma statements required) successfully here:
https://github.com/lee-reinhardt/parcel-ts-emotion

There is a summary of the workarounds with references in the readme.

@IsenrichO
Copy link

IsenrichO commented Jun 6, 2019

@paramsinghvc You shouldn't combine @emotion/babel-preset-css-prop and babel-plugin-emotion. The former already uses the latter internally.

@stkevintan
Copy link

stkevintan commented Jun 13, 2019

WTH 😤I've spent hours and this nasty [object Object] thing is not going away anyhow in my typescript react project.
Does anyone know a solution to this sad problem??

try this : LeetCode-OpenSource/emotion-ts-plugin#26

@jonavila
Copy link

jonavila commented Nov 18, 2019

For anyone still wanting to use Typescript + Emotion 10 without having to import /** @jsx jsx */ on every file, here's what I've done:

  1. In my webpack config, I added babel-loader to my tsx rule. I used this because I wanted the output of ts-loader to be handed over to babel so that the @emotion/babel-preset-css-prop can do its magic.
            {
                test: /\.tsx?$/,
                include,
                exclude,
                use: [
                    {
                        loader: 'babel-loader',
                        options: { cacheDirectory: true },
                    },
                    {
                        loader: 'ts-loader',
                        options: {
                            transpileOnly: true,
                        },
                    },
                ],
            },
        ],

Note: Webpack loaders are applied from right to left, that's why babel-loader is listed first

  1. I added @emotion/babel-preset-css-prop as a babel preset in my babel config.

  2. Instead of having Typescript compile the JSX, I delegate that job to Babel. To do that, make sure you set jsx: preserve in tsconfig.json (https://www.typescriptlang.org/docs/handbook/jsx.html). Without this, babel won't be able to apply the @emotion/babel-preset-css-prop because when it receives the output from ts-loader, the JSX has already been transpiled.

  3. In order to use the css prop on a jsx element with TS, Emotion augments the DOMAttributes interface of react with the css prop in types.

declare module 'react' {
  interface DOMAttributes<T> {
    css?: InterpolationWithTheme<any>
  }
}

The problem is that typescript only sees the types defined in @types/* by default and emotion typings are in @emotion/core. To solve this, add @emotion/core to the types array in tsconfig.json or include the reference in a .d.ts file:

/// <reference types="@emotion/core" />

I elected to use the reference because when you set a types array in tsconfig, only the types listed are used. I rather keep the default of @types directory and add one-offs as needed.

That's all, hope it helps someone.

BTW, I think with this approach, you can continue to use the Fragment shorthand <>/</> since Babel is transpiling the jsx

@Andarist
Copy link
Member

Andarist commented Nov 18, 2019

That's all, hope it helps someone.

Do whatever works for you - but this can hardly be called a minor change to existing setups. Using jsx pragma explicitly means that you don't have to do anything special to satisfy all the tooling that wants to understand JSX etc except adding a custom pragma because support for it is backed into nearly all the tooling. 🤷‍♂

@jonavila
Copy link

jonavila commented Nov 18, 2019

Do whatever works for you - but this can hardly be called a minor change to existing setups

@Andarist Correct me if I'm wrong, but one of the main use cases for the babel preset or the babel plugin is to avoid having to add the jsx import and the pragma at the top of each file where you want to use the css prop right? My comment simply shows a way that you can use the plugin with Typescript. That's all.

@Andarist
Copy link
Member

Andarist commented Nov 18, 2019

As to @emotion/babel-preset-css-prop - yes, but it has been created only because people were complaining about having to add pragma "manually" (where it can even be automated with eslint plugin, but with it it's still there - it's explicitly used).

As to babel-plugin-emotion (the primary one) - no. Its purpose is to generate source maps, labels, optimize few things and similar stuff. It doesn't let you skip writing pragma.

@jonavila
Copy link

jonavila commented Nov 18, 2019

@Andarist Yeah, my bad. Just the preset. Which in my opinion is great and listed as the first option in the docs. It took me a while to figure why it wasn't working with Typescript, but like you said, it's the setup that works for me and may not be the best for everyone. I'm in the process of convincing my team to move from styled-components to emotion and I wanted it to be as simple as possible for them.

My apologies for replying to a closed ticket, it just happens to be the what popped up when I googled and I wanted to provide some info for others that may want to do something similar. Thanks for all the work on Emotion. It rocks!!!

@Andarist
Copy link
Member

Andarist commented Nov 18, 2019

No problem - I'm just always rather surprised how strongly people react to the whole custom pragma thing.

Your comment can be certainly helpful for others trying to do the same 👍 I just don't understand why people want to go through such hoops 😅 IMHO the ROI is just low for this one, but that might be just me.

@jacek213
Copy link

jacek213 commented Nov 27, 2019

any ideas about getting pretty class names (aka autoLabel) and source maps with pure typescript?

@Andarist
Copy link
Member

Andarist commented Nov 27, 2019

@nicmosc
Copy link

nicmosc commented Mar 4, 2020

@jacek213 are you using webpack or just transpiling with tsc?

@MariuzM
Copy link

MariuzM commented May 12, 2020

Any updates about this, so we don't need to use /** @jsx jsx */ and then we can use <></> for our components?

@MariuzM
Copy link

MariuzM commented May 12, 2020

This is for now how i have it working with: Create React App (CRA) + TypeScript + Craco

Using "jsxFactory": "jsx" - you will need to add import { jsx } from '@emotion/core'; to evert components so thats redundant.

This is how it should look when using with <React.Fragment>

/** @jsx jsx */
import React from 'react'
import { css, jsx } from '@emotion/core'

const myTest = css`
  background-color: red;
  width: 400px;
  height: 400px;
  font-size: 10px;
`

function App(): JSX.Element {
  return (
    <React.Fragment>
      <div css={myTest}>Testing Emotion</div>
    </React.Fragment>
  )
}

export default App

And without <React.Fragment>

/** @jsx jsx */
// import React from 'react'
import { css, jsx } from '@emotion/core'

const myTest = css`
  background-color: red;
  width: 400px;
  height: 400px;
  font-size: 10px;
`

function App(): JSX.Element {
  return (
    // <React.Fragment>
    <div css={myTest}>Testing Emotion</div>
    // </React.Fragment>
  )
}

export default App
{
  "name": "my-app",
  "version": "0.1.0",
  "private": true,
  "homepage": "./",
  "dependencies": {
    "@emotion/babel-preset-css-prop": "^10.0.27",
    "@emotion/core": "^10.0.28",
    "@types/react": "^16.9.34",
    "@types/react-dom": "^16.9.7",
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "react-scripts": "3.4.1",
    "typescript": "~3.8.3"
  },
  "devDependencies": {
    "@craco/craco": "^5.6.4",
    "@typescript-eslint/eslint-plugin": "^2.31.0",
    "@typescript-eslint/parser": "2.x",
    "eslint": "7.x",
    "eslint-config-prettier": "^6.11.0",
    "eslint-config-react-app": "^5.2.1",
    "eslint-plugin-flowtype": "4.x",
    "eslint-plugin-import": "2.x",
    "eslint-plugin-jsx-a11y": "6.x",
    "eslint-plugin-prettier": "^3.1.3",
    "eslint-plugin-react": "7.x",
    "eslint-plugin-react-hooks": "4.x",
    "prettier": "^2.0.5"
  },
  "scripts": {
    "dev": "craco start --config config/webpack.dev.js",
    "build": "craco build --config config/webpack.prod.js",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

@stkevintan
Copy link

stkevintan commented May 30, 2020

@MariuzM Hi, you can try this plugin: https://github.com/LeetCode-OpenSource/emotion-ts-plugin
I have made a pull request to auto inject the /** @jsx jsx */ behind each React import expression.
Hope this will help you!

@Andreychikov-Vasiliy
Copy link

Andreychikov-Vasiliy commented Jan 17, 2021

So we have two direction to solve this problem:

  • emotion-ts-plugin
  • babel

For me emotion-ts-plugin was 3x times faster, but i spend much time to found how to declare jsx auto injected to files.
Solution was:

import {createElement} from "@types/react";
declare global {   
    var jsx: typeof createElement = createElement; 
}

through tsconfig.json option "types": ["./typings/index"], /* Type declaration files to be included in compilation. */

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

No branches or pull requests