Parcel: Bug: React not defined (with TypeScript)

Created on 17 Apr 2018  ·  28Comments  ·  Source: parcel-bundler/parcel

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"))
Waiting Question

Most helpful comment

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.

All 28 comments

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)

(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)

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 [email protected]  # <= 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
└─ [email protected]
info All dependencies
└─ [email protected]
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 [email protected]  # <= 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 [email protected]  # <= 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.

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.

@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.

@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.

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...

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.

@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"
  }
}

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?)

@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

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

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.

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

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

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.

@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

@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.

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

I had to do:

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

not super clean haha

@tj did you try the solutions above?

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

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"
  }
}

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

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

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

index.jsx

import { useState } from "react"

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

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

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

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

 "jsx": "react"

You saved me!!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

will-stone picture will-stone  ·  3Comments

medhatdawoud picture medhatdawoud  ·  3Comments

mnn picture mnn  ·  3Comments

oliger picture oliger  ·  3Comments

Niggler picture Niggler  ·  3Comments