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

Bug: React not defined (with TypeScript) #1199

Closed
felixrabe opened this issue Apr 16, 2018 · 31 comments
Closed

Bug: React not defined (with TypeScript) #1199

felixrabe opened this issue Apr 16, 2018 · 31 comments

Comments

@felixrabe
Copy link

@felixrabe felixrabe commented Apr 16, 2018

I'm aware that this is very likely a duplicate of some other bug. I'll research and close ASAP, just dumping my findings here for reference.


Edit – Related issues:

  • Typescript + Preact doesn't work right now #1095

Edit – Using Node v9.11.1 btw. (Forgot to mention it below.)


Creating a new project like this (I've copy and pasted these lines to verify): (macOS 10.13.4, yarn 1.6.0, Parcel 1.7.1)

mkdir 054-parcel-typescript-react
cd 054-parcel-typescript-react

yarn add --dev parcel-bundler

echo '<div id="app"></div> <script src="./app.tsx"></script>' > index.html

echo $'import React from "react"\nimport {render} from "react-dom"\nrender(<h1>Hi there</h1>, document.getElementById("app"))' > app.tsx

... running Parcel:

./node_modules/.bin/parcel index.html

😯 Current Behavior

... leads to an empty page on http://localhost:1234/ and this console message: (Firefox 59)

ReferenceError: React is not defined

🤔 Expected Behavior

I'd expect the page to contain a big fat Hi there and no console error.

Resulting files from above process

(plus a .gitignore can be found here: https://github.com/felixrabe/e-2018-054-parcel-typescript-react)

package.json

{
  "devDependencies": {
    "parcel-bundler": "^1.7.1",
    "typescript": "^2.8.1"
  },
  "dependencies": {
    "react": "^16.0.0",
    "react-dom": "^16.3.2"
  }
}

index.html

<div id="app"></div> <script src="./app.tsx"></script>

app.tsx

import React from "react"
import {render} from "react-dom"
render(<h1>Hi there</h1>, document.getElementById("app"))
@felixrabe
Copy link
Author

@felixrabe felixrabe commented Apr 16, 2018

Just TypeScript (leaving out React) works

mkdir 055-parcel-just-typescript-works
cd 055-parcel-just-typescript-works
yarn add --dev parcel-bundler
echo '<body><script src="app.tsx"></script></body>' > index.html
echo 'const s: string = "hi there" ; document.body.appendChild(document.createElement("div")).textContent = s' > app.tsx
./node_modules/.bin/parcel index.html

This gives the expected result at http://localhost:1234/: "hi there" and no console error.

(Repo: https://github.com/felixrabe/e-2018-055-parcel-just-typescript-works)

@felixrabe
Copy link
Author

@felixrabe felixrabe commented Apr 16, 2018

(This is not a duplicate of the above comment, but the opposite variant – trying out just React instead of just TypeScript.)

Just React (leaving out TypeScript) works

mkdir 056-parcel-just-react-works
cd 056-parcel-just-react-works
yarn add --dev parcel-bundler
echo '<div id="app"></div> <script src="app.jsx"></script>' > index.html
echo $'import React from "react"\nimport {render} from "react-dom"\nrender(<h1>Hi there</h1>, document.getElementById("app"))' > app.jsx
./node_modules/.bin/parcel index.html

This gives the expected result at http://localhost:1234/: big "Hi there" and no console error.

(Repo: https://github.com/felixrabe/e-2018-056-parcel-just-react-works)

@felixrabe
Copy link
Author

@felixrabe felixrabe commented Apr 16, 2018

I've just read in the 1.7 blog post something about a rewritten resolver. So I tried again using Parcel 1.6.2 to compare former behavior:

First example: TypeScript AND React in Parcel 1.6.2

mkdir 054-parcel-typescript-react
cd 054-parcel-typescript-react
yarn add --dev parcel-bundler@1.6.2  # <= NEW: added '@1.6.2'
echo '<div id="app"></div> <script src="./app.tsx"></script>' > index.html
echo $'import React from "react"\nimport {render} from "react-dom"\nrender(<h1>Hi there</h1>, document.getElementById("app"))' > app.tsx
./node_modules/.bin/parcel index.html

This already fails in the Terminal (which to me is an improvement compared to 1.7's silent non-Terminal console-only failure):

Felixs-iMac:054-parcel-typescript-react fr$ ./node_modules/.bin/parcel index.html
Server running at http://localhost:1234 
⏳  Building app.tsx...
yarn add v1.6.0
warning package.json: No license field
warning No license field
[1/4] Resolving packages...
yarn add v1.6.0
warning package.json: No license field
warning No license field
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Saved lockfile.
success Saved 1 new dependency.
info Direct dependencies
└─ typescript@2.8.1
info All dependencies
└─ typescript@2.8.1
warning No license field
Done in 1.83s.
🚨  /Users/fr/j/2018/experiments/p-1.6/054-parcel-typescript-react/app.tsx:4:26: Cannot resolve dependency 'react-dom'
  2 | import {render} from "react-dom"
  3 | render(<h1>Hi there</h1>, document.getElementById("app"))
> 4 | 
    | ^
success Saved lockfile.
success Saved 1 new dependency.
info Direct dependencies
🚨  /Users/fr/j/2018/experiments/p-1.6/054-parcel-typescript-react/app.tsx:3:22: Cannot resolve dependency 'react'
  1 | import React from "react"
  2 | import {render} from "react-dom"
> 3 | render(<h1>Hi there</h1>, document.getElementById("app"))
    |                       ^
  4 | 

(I did not bother checking the page in the browser after that message. Just ^C-ed the server.)

Second example: TypeScript ONLY in Parcel 1.6.2

mkdir 055-parcel-just-typescript-works
cd 055-parcel-just-typescript-works
yarn add --dev parcel-bundler@1.6.2  # <= NEW: added '@1.6.2'
echo '<body><script src="app.tsx"></script></body>' > index.html
echo 'const s: string = "hi there" ; document.body.appendChild(document.createElement("div")).textContent = s' > app.tsx
./node_modules/.bin/parcel index.html

Works correctly as with Parcel 1.7.1.

Third example: React ONLY in Parcel 1.6.2

mkdir 056-parcel-just-react-works
cd 056-parcel-just-react-works
yarn add --dev parcel-bundler@1.6.2  # <= NEW: added '@1.6.2'
echo '<div id="app"></div> <script src="app.jsx"></script>' > index.html
echo $'import React from "react"\nimport {render} from "react-dom"\nrender(<h1>Hi there</h1>, document.getElementById("app"))' > app.jsx
./node_modules/.bin/parcel index.html

This time, Parcel 1.6.2 behaves worse than Parcel 1.7.1 and gives me this Terminal error message, similar to the first example above:

Felixs-iMac:056-parcel-just-react-works fr$ ./node_modules/.bin/parcel index.html
Server running at http://localhost:1234 
🚨  /Users/fr/j/2018/experiments/p-1.6/056-parcel-just-react-works/app.jsx:1:18: Cannot resolve dependency 'react'
> 1 | import React from "react"
    |                   ^
  2 | import {render} from "react-dom"
  3 | render(<h1>Hi there</h1>, document.getElementById("app"))
  4 | 

The browser page at http://localhost:1234/ shows:

🚨 Build error, check the console for details.

But the Browser console is empty (probably referring to the system console here).

To fix it, I can ^C the server, manually install the missing deps, and restart the server:

yarn add react react-dev
./node_modules/.bin/parcel index.html

... and http://localhost:1234/ correctly greets me with a big fat "Hi there" and no error.

@gnijuohz
Copy link
Contributor

@gnijuohz gnijuohz commented Apr 17, 2018

I encountered this as well recently. Using import * as React from 'react' worked for me. Maybe you can use it as a workaround for now.

@shunia
Copy link

@shunia shunia commented Apr 17, 2018

@felixrabe Use what @gnijuohz suggests and it will be fine.
BTW, this is not a parcel issue, the way react output it's definition file and to export a general(cjs and umd and es compatible export) namespace caused this.

@marvinhagemeister
Copy link

@marvinhagemeister marvinhagemeister commented Apr 17, 2018

@felixrabe This is a known difference between how babel and typescript handle es5 code when imported via es6 imports respectively. If you want the babel behaviour in TypeScript you can enable it via the --allowSyntheticDefaultImports option.

@gnijuohz
Copy link
Contributor

@gnijuohz gnijuohz commented Apr 17, 2018

Thanks @shunia and @marvinhagemeister, I'm new to Typescript :)
I tried using the following tsconfig.json,

{
    "compilerOptions": {
        "allowSyntheticDefaultImports": true
    }
}

and that didn't fix it. Went back to the docs for this option and it says,

Allow default imports from modules with no default export. This does not affect code emit, just typechecking.

Since it doesn't affect code emit, then it's expected nothing changes in our case...

@dtinth
Copy link

@dtinth dtinth commented Apr 18, 2018

This tsconfig.json solved it for me:

{
  "compilerOptions": {
    "esModuleInterop": true,
    "jsx": "react"
  }
}

(Make sure to make a small change to the .jsx file to invalidate the compilation cache.)

esModuleInterop makes TypeScript module resolution from ES→CommonJs behave like webpack’s and Babel’s behavior (create a synthetic namespace with only default export). This allows import React from 'react' to work fine. In my opinion, Parcel should turn this option on by default.

@gnijuohz
Copy link
Contributor

@gnijuohz gnijuohz commented Apr 18, 2018

@dtinth esModuleInterop: true is in fact the default in Parcel. If you just use tsx: react in the config, it works as well.

So currently two approaches work:

  • no tsconfig.json, use import * as React from 'React'
  • tsconfig.json, with the following,
{
  "compilerOptions": {
    "jsx": "react"
  }
}
@gnijuohz
Copy link
Contributor

@gnijuohz gnijuohz commented Apr 18, 2018

Hi @DeMoorJasper currently Parcel's default for jsx is preserve, what are the downsides of changing it to 'react'? (then react native users will have to override this option?)

@DeMoorJasper
Copy link
Member

@DeMoorJasper DeMoorJasper commented Apr 18, 2018

@gnijuohz the original thought behind it was the whole pipeline concept inside parcel. Where it starts with typescript and than goes through ts-compiler than babel, therefore I thought this was a good default as babel would take care of JSX

@DeMoorJasper
Copy link
Member

@DeMoorJasper DeMoorJasper commented Apr 20, 2018

I'll close this off as there are several responses that answer the question

@felixrabe
Copy link
Author

@felixrabe felixrabe commented Apr 25, 2018

Yes, this is fine to be closed. I'm still investigating, but the key to resolve my issue seems to be (exactly as @gnijuohz stated above) to add tsconfig.json with

{
  "compilerOptions": {
    "jsx": "react"
  }
}

This seems to be all that is required, and it makes sense to require it. The option esModuleInterop is already activated by default in Parcel itself, which also makes sense to me as it is "highly recommended" by the TypeScript project itself.

@fitfab
Copy link

@fitfab fitfab commented May 1, 2018

clearing the cache rm .cache/* and adding this to the tsconfig.json worked for me.

{
  "compilerOptions": {
    "jsx": "react"
  }
}
@dtinth
Copy link

@dtinth dtinth commented May 1, 2018

IMO, "jsx": "react" should also be the default in parcel-bundler to maintain the spirit of zero configuration. I would expect import React from 'react' to just work regardless of whether I use TypeScript or JavaScript.

@DeMoorJasper
Copy link
Member

@DeMoorJasper DeMoorJasper commented May 1, 2018

@dtinth Feel free to open a PR, the original thought behind the configuration of ts-compiler was that react should be processed by babel, as we use something we call the processing pipeline, which basically processes, the asset as follows
.ts => ts-compiler => .js => babel => js packager => bundle

But after thinking about it, it might not make so much sense after all, because using the original concept you would have to config babel to process react (although in theory parcel should detect react code automatic and change babel config accordingly)

Feel free to open a PR with the improved config

@ryansmith94
Copy link

@ryansmith94 ryansmith94 commented Aug 13, 2018

@felixrabe thanks for this bit in particular...

The option esModuleInterop is already activated by default in Parcel itself, which also makes sense to me as it is "highly recommended" by the TypeScript project itself.

Just caught us out when importing "react-focus-trap" in our component library because we were using Parcel to preview the component in development and the TypeScript compiler without that option for production.

@Offirmo
Copy link

@Offirmo Offirmo commented Jan 4, 2019

This should be in the doc. Will try to add it.

@tj
Copy link

@tj tj commented Jan 28, 2019

I had to do:

import * as React from 'react'
import _, { PureComponent } from 'react'

not super clean haha

@Offirmo
Copy link

@Offirmo Offirmo commented Jan 30, 2019

@tj did you try the solutions above?

{
  "compilerOptions": {
    "esModuleInterop": true,
    "jsx": "react"
  }
}
@denkristoffer
Copy link

@denkristoffer denkristoffer commented Mar 28, 2019

We ran into this even though the TS config we extend already included "jsx": "react", so I guess Parcel overrides it. Setting it again worked:

{
  // This config already sets "jsx" to "react"
  "extends": "shared/tsconfig",
  // But we still have to override Parcel's default config
  "compilerOptions": {
    "jsx": "react"
  }
}
@JounQin
Copy link

@JounQin JounQin commented Aug 31, 2019

@denkristoffer Thx for saving my life! And it seems a bug to me.

@SleepWalker
Copy link

@SleepWalker SleepWalker commented Jan 11, 2020

One more important thing. You'll need to rm -rf .cache after changing tsconfig.json

szhu added a commit to Recidiviz/covid19-dashboard that referenced this issue Apr 24, 2020
szhu added a commit to Recidiviz/covid19-dashboard that referenced this issue Apr 24, 2020
@StarpTech
Copy link
Contributor

@StarpTech StarpTech commented May 21, 2020

I have the same issue and I don't use typescript at all. This already produces the error:

index.jsx

import { useState } from "react"
@StarpTech
Copy link
Contributor

@StarpTech StarpTech commented May 21, 2020

I could fix it by simply adding import React from "react" to the top of each imported React Component.

@MeliodasSAMAi
Copy link

@MeliodasSAMAi MeliodasSAMAi commented Jun 2, 2020

When I install both parcel and typescript on my computer, how can I make parcel use the global typescript instead of downloading a copy when I run the parcel command

@mvasin
Copy link

@mvasin mvasin commented Aug 4, 2020

You'll need to rm -rf .cache after changing tsconfig.json

That's the main catch here. Why doesn't this happen automatically?

@margox
Copy link

@margox margox commented Nov 29, 2020

 "jsx": "react"

You saved me!!

@adjenks
Copy link

@adjenks adjenks commented Apr 14, 2021

So a general question @marvinhagemeister , or anyone else who can explain. If typescript requires --allowSyntheticDefaultImports, and babel does not do this by default, does this mean that babel does something "synthetic" by default? In the future, if you were to disable babel and use the latest browser, would the code not run? What I mean is, is this just a feature to encourage some sort of bad pattern? It's making up code that isn't there, but that doesn't seem to be a "spec" of any sort. I don't really want babel to allow me to do anything that browsers wouldn't allow me to do later on, if you get what I mean.

@marvinhagemeister
Copy link

@marvinhagemeister marvinhagemeister commented Apr 14, 2021

@adjenks The topic here only affects modules authored in commonjs or any other module system than es modules. They are not compatible out of the box and both Typescript and babel try to do their best to translate those to es modules. And that's where the slight difference in behavior of these tools resides. There is no right or better way to do the translation, hence the difference in babel and TS.

Browsers straight up refuse to load anything other than es modules (or classic script tags). The issue here only pops up if you mix and match different module systems which is common in the react ecosystem as react isn't authored in es modules.

Will one be able to stop transpiling module formats and have the code continue to work? That's the hope here, but it requires cooperation from library authors.

@adjenks
Copy link

@adjenks adjenks commented Apr 14, 2021

@marvinhagemeister Okay, that makes sense, thank you for clarifying that. 👍

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

Successfully merging a pull request may close this issue.

None yet