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 MITIGATION: .mjs should not resolve before .js files #4085

Merged
merged 1 commit into from
Mar 22, 2018

Conversation

leebyron
Copy link
Contributor

Support for .mjs files added in #3239 did not account for npm libraries which ship native mjs files alongside js files. This accounts for this by ensuring .js files resolve before their accompanying .mjs file. Note that this is not an ideal end state since selecting a .mjs over a .js extension should be the result of whether import was used instead of require() in a node environment with native ESM support (currently via --experimental-modules). Instead, this change just always selects a .js extension before the .mjs extension if it exists.

This unbreaks support for using GraphQL (relay, apollo, etc) within create-react-app projects.

@leebyron
Copy link
Contributor Author

cc @gaearon @Timer - it would be great if we could get this into a point release soon

@Timer
Copy link
Contributor

Timer commented Feb 27, 2018

Hmm, is this a new problem (as in did these modules just now start shipping mjs and did not previously)?
This is the first we're hearing of this bug report.

@leebyron
Copy link
Contributor Author

Yes, some graphql packages just started releasing node-esm ready modules to npm for support with node v9's --experimental-modules and in anticipation of node v10.

Node has a new module resolution strategy which will resolve to a .mjs file from a native import statement, but continue to resolve to a .js file from a require(). To support both, npm modules can be published that have both .js and .mjs variations of each file in the bundle.

However this conflicts with react-scripts's jest-config which before this diff always resolves to .mjs if one exists before .js, even if the environment is not using native modules - which results in seeing syntax errors like reported in graphql/graphql-js#1248

Since Jest wraps the require() flow, and not the import flow, I believe Jest doesn't have support for native node-esm just yet, however resolving .mjs files when a .js file isn't found is still a reasonable thing to do since user's source code may start using .mjs along with the babel transform (as opposed to node_modules code which is not transformed by babel)

@iansu
Copy link
Contributor

iansu commented Feb 28, 2018

This seems like an easy fix. We should get this into master as well as next.

@Timer
Copy link
Contributor

Timer commented Feb 28, 2018

From my understanding, this problem most likely arises because imports are happening without specifying file extension.

When Jest loads a package, we look at the main key in package.json, when webpack loads a package, we look at the module key and fallback to main.

In a normal scenario, main would contain index.js and module would contain index.mjs.

These files would respectively import etc.js and etc.mjs files, but instead, specify just etc, which falls back to our module resolve ordering which disregards the means of import.

Given this reasoning, I'd go as far to say that this isn't the fix we want -- we can release this in a 1.x.y release for compatibility reasons, but I'm against porting this to next because it's not the desired behavior.

I'd like to know how Node handles this, given an import specifying just etc, does it prefer etc.js when invoked via require and etc.mjs when invoked via import? What's the fallback look like? What happens when using import()? Does Node itself bomb out in this situation?

We definitely need to figure this out for the 2.0 release.

@leebyron can you retarget this PR to the master branch please? We'll want a different solution for next as described above.

@leebyron leebyron changed the base branch from next to master February 28, 2018 00:27
@leebyron leebyron changed the base branch from master to next February 28, 2018 00:28
@leebyron
Copy link
Contributor Author

Give me a moment to change the base - I can't do that from the github UI without wreaking havoc.

Before I do that, let me try to answer some of your questions.

I'd like to know how Node handles this

Node handles this by never attempting to load a .mjs file from a require() and never loading a .js from an import. So import "./example" may try to look for: ./example, then ./example.mjs, or ./example/index.mjs, but never ./example.js. Similarly require("./example") may look for ./example.js but would never look for ./example.mjs. In other words, the existing module resolution algorithm for require() was not changed at all, however a new module resolution algorithm was introduced for import.

Given this reasoning, I'd go as far to say that this isn't the fix we want

I definitely agree - this is a mitigation. I would argue that .mjs files should not be considered at all from a require statement in Jest - instead Jest should hook Node's new import resolution API in which case it should look for .mjs files. Though I know that we're still in the strange world of babel transpilation and so this isn't always very well defined, and that folks may choose to save their ES6+ESM files as .mjs files.

This mitigation is specifically handling the case where both ./example.js and ./example.mjs exist in the same location. The .mjs file is going to include ESM syntax, and if it happens to be found in the node_modules folder then babel isn't going to transform it. In this scenario instead of resolving the .mjs file first, we should be resolving the .js file first.

Tools like Webpack and Rollup should be doing the opposite - looking for .mjs files before .js files - since they prefer the ESM format if it exists.

When Jest loads a package, we look at the main key in package.json, when webpack loads a package, we look at the module key and fallback to main.

Yes, and node also only looks at the main key in package.json, regardless of whether require() or import was used to arrive at that package.json file - which is why this pattern of .js and .mjs files next to each other is likely to become more common as native ESM moves from experimental to shipping.

Support for .mjs files added in facebook#3239 did not account for npm libraries which ship native mjs files alongside js files. This accounts for this by ensuring .js files resolve before their accompanying .mjs file. Note that this is not an ideal end state since selecting a .mjs over a .js extension should be the result of whether `import` was used instead of `require()` in a node environment with native ESM support (currently via `--experimental-modules`). Instead, this change just *always* selects a .js extension before the .mjs extension if it exists.

This unbreaks support for using GraphQL (relay, apollo, etc) within create-react-app projects.
@Timer
Copy link
Contributor

Timer commented Feb 28, 2018

I'm trying to figure whose court the ball is in to handle this (for 2.0).

I could see arguments for us doing something complex, I could also see arguments for Jest to handle this.

It's almost as if we need a moduleFileExtensions and esModuleFileExtensions key to specify extensions for require/import.

@leebyron
Copy link
Contributor Author

I like the idea of a separate esModuleFileExtensions. I think this should probably be within Jest's domain. If it encounters an npm module using native ESM then Jest should be defining the right behavior. There's a bit of runway to get this right in the meantime. Node v9 only supports native ESM with --experimental-modules, so it's not going to be all that common until the major version that supports it without a flag.

@leebyron
Copy link
Contributor Author

Tools like Webpack and Rollup should be doing the opposite - looking for .mjs files before .js files - since they prefer the ESM format if it exists.

Just checked this out and Webpack does look for .mjs before .js

luqmaan added a commit to cityofaustin/ctxfloods that referenced this pull request Mar 1, 2018
Running into this issue: graphql/graphql-js#1248.

Need to wait for this PR to get merged: facebook/create-react-app#4085.
luqmaan pushed a commit to cityofaustin/ctxfloods that referenced this pull request Mar 1, 2018
* Upgrade frontend dependencies

Major changes:

- react 16 => 16.2
- mapbox-gl-style-spec 9 => 11
- react-apollo 1 => 2
  - required some code changes to the middleware and import path to gql
  - https://www.apollographql.com/docs/react/2.0-migration.html
- react-mapbox-gl 2 => 3
  - required some changes to the layerOptions prop
  - https://github.com/alex3165/react-mapbox-gl/releases/tag/v3.1.0

 #143

* Remove unused dependencies and peer dependency

* Downgrade graphql to avoid test bug

Running into this issue: graphql/graphql-js#1248.

Need to wait for this PR to get merged: facebook/create-react-app#4085.

* Cleanup
@jbaxleyiii
Copy link

This also killed mjs efforts for the Apollo ecosystem 😢

@Timer
Copy link
Contributor

Timer commented Mar 8, 2018

We don't want to rush these changes out since there's not a ton of +1s.

Let me reiterate -- this is completely solvable in user land in the mean time (which shouldn't be long).

Source files just need to be updated to explicitly import their file.js or file.mjs counterpart instead of file.

@jbaxleyiii
Copy link

jbaxleyiii commented Mar 8, 2018

@Timer so you are saying that if foo.mjs changes import bar from ./bar to import bar from ./bar.mjs it would work?

Is that in the lib or in user code?

@leebyron
Copy link
Contributor Author

leebyron commented Mar 8, 2018

To be clear this cannot be mitigated in user land. This blocks using the most recent version of GraphQL.js along with create react app.

The critical issue is loading files from node_modules, where it is not clear how to rewrite import x from “graphql” to explicitly use JS files.

This problem is made worse within cross dependencies. Projects like Apollo and Relay shouldn’t be forced to change their imports to require(“graphql/index.js”)

@leebyron
Copy link
Contributor Author

leebyron commented Mar 8, 2018

I definitely support coming up with the right long term plan, which probably incorporates adding support for native ESM to jest, however in the meantime I still would love to see this merged and released as soon as possible since it’s a low impact bug fix that unblocks using CRA with the graphql ecosystem

@iansu
Copy link
Contributor

iansu commented Mar 8, 2018

This definitely sounds bad. Is this only broken when running tests via Create React App? Is it only the Jest config that needs to be updated or is there also an issue with our webpack config?

@stevensacks
Copy link

@leebyron Please release this.

@gaearon
Copy link
Contributor

gaearon commented Apr 2, 2018

To clarify, Lee just reported and sent a fix for this issue, we're the ones who should release it

@gaearon gaearon mentioned this pull request Apr 2, 2018
@gaearon
Copy link
Contributor

gaearon commented Apr 2, 2018

Out in 1.1.2.

@gaearon
Copy link
Contributor

gaearon commented Apr 2, 2018

(Sorry it took me a long time)

craigmulligan pushed a commit to craigmulligan/create-react-app that referenced this pull request Apr 17, 2018
Support for .mjs files added in facebook#3239 did not account for npm libraries which ship native mjs files alongside js files. This accounts for this by ensuring .js files resolve before their accompanying .mjs file. Note that this is not an ideal end state since selecting a .mjs over a .js extension should be the result of whether `import` was used instead of `require()` in a node environment with native ESM support (currently via `--experimental-modules`). Instead, this change just *always* selects a .js extension before the .mjs extension if it exists.

This unbreaks support for using GraphQL (relay, apollo, etc) within create-react-app projects.
bors bot referenced this pull request in mythmon/corsica-tree-status Apr 23, 2018
3: Update dependency flow-bin to v0.70.0 r=mythmon a=renovate[bot]

This Pull Request updates dependency [flow-bin](https://github.com/flowtype/flow-bin) from `v0.66.0` to `v0.70.0`




<details>
<summary>Commits</summary>

#### v0.67.1
-   [`4255e2d`](flow/flow-bin@4255e2d) v0.67.1
#### v0.68.0
-   [`aea9bb5`](flow/flow-bin@aea9bb5) v0.68.0
#### v0.69.0
-   [`b8a5da6`](flow/flow-bin@b8a5da6) v0.69.0
#### v0.70.0
-   [`0b0bcea`](flow/flow-bin@0b0bcea) v0.70.0

</details>



---

This PR has been generated by [Renovate Bot](https://renovateapp.com).

4: Update dependency react-scripts to v1.1.4 r=mythmon a=renovate[bot]

This Pull Request updates dependency [react-scripts](https://github.com/facebookincubator/create-react-app) from `v1.1.1` to `v1.1.4`



<details>
<summary>Release Notes</summary>

### [`v1.1.2`](https://github.com/facebookincubator/create-react-app/blob/master/CHANGELOG.md#&#8203;112-April-3-2018)

##### 🐛 Bug Fix

* `react-scripts`

  * [#&#8203;4085](`https://github.com/facebook/create-react-app/pull/4085`) Resolve `.js` before `.mjs` files to unbreak dependencies with native ESM support. ([@&#8203;leebyron])
##### 📝 Documentation

* `react-scripts`

  * [#&#8203;4197](`https://github.com/facebook/create-react-app/pull/4197`) Add troubleshooting for Github Pages. ([@&#8203;xnt])
##### Committers: 2
- Lee Byron ([leebyron])
- Vicente Plata ([xnt])
##### Migrating from 1.1.1 to 1.1.2

Inside any created project that has not been ejected, run:

```
npm install --save --save-exact react-scripts@&#8203;1.1.2
```

or

```
yarn add --exact react-scripts@&#8203;1.1.2
```

---

### [`v1.1.3`](https://github.com/facebookincubator/create-react-app/blob/master/CHANGELOG.md#&#8203;113-April-3-2018)

##### 🐛 Bug Fix

* `react-scripts`

  * [#&#8203;4247](`https://github.com/facebook/create-react-app/pull/4247`) Fix `environment.dispose is not a function` error caused by a Jest bug. ([@&#8203;gaearon])
##### Committers: 1
- Dan Abramov ([gaearon])
##### Migrating from 1.1.2 to 1.1.3

Inside any created project that has not been ejected, run:

```
npm install --save --save-exact react-scripts@&#8203;1.1.3
```

or

```
yarn add --exact react-scripts@&#8203;1.1.3
```

---

### [`v1.1.4`](https://github.com/facebookincubator/create-react-app/blob/master/CHANGELOG.md#&#8203;114-April-3-2018)

##### 🐛 Bug Fix

* `react-dev-utils`

  * [#&#8203;4250](`https://github.com/facebook/create-react-app/pull/4250`) Upgrade `detect-port-alt` to fix [#&#8203;4189](`https://github.com/facebook/create-react-app/issues/4189`). ([@&#8203;Timer])
##### Committers: 1
- Joe Haddad ([Timer])
##### Migrating from 1.1.3 to 1.1.4

Inside any created project that has not been ejected, run:

```
npm install --save --save-exact react-scripts@&#8203;1.1.4
```

or

```
yarn add --exact react-scripts@&#8203;1.1.4
```

---

</details>


<details>
<summary>Commits</summary>

#### v1.1.2
-   [`058d03f`](facebook/create-react-app@058d03f) Fix typos in example monorepo documentation. (#&#8203;4164)
-   [`1922f4d`](facebook/create-react-app@1922f4d) Allow ModuleScopePlugin accecpts an array as its appSrc (#&#8203;4138)
-   [`33f1294`](facebook/create-react-app@33f1294) Revert &quot;Change no-unused-vars &#x27;args&#x27; from none to all to show warning on destructured objects&quot;
-   [`8a34b7c`](facebook/create-react-app@8a34b7c) Add ESLint check for incorrect propTypes usage (#&#8203;3840) (#&#8203;4048)
-   [`8db5e33`](facebook/create-react-app@8db5e33) Revert lint-related changes made in #&#8203;4193 (#&#8203;4211)
-   [`06dd17e`](facebook/create-react-app@06dd17e) add `create-react-app --help` info for local file path `--scripts-version` support (#&#8203;4015)
-   [`9c167a4`](facebook/create-react-app@9c167a4) Add some stuff that requires transpilation. (#&#8203;4174)
-   [`da518d2`](facebook/create-react-app@da518d2) Fix floating caret position incorrect while scrolling overlay (#&#8203;4024)
-   [`2824bf2`](facebook/create-react-app@2824bf2) [next] Revert to use ecma 5 in uglifyOptions (#&#8203;4234)
-   [`9a99b5d`](facebook/create-react-app@9a99b5d) Fix typo and be clearer about `ecma` settings in uglifyjs options (#&#8203;4239)
-   [`9c3f03c`](facebook/create-react-app@9c3f03c) use the lastest url of gitignore file (#&#8203;4236)
#### v1.1.3
-   [`061d1e4`](facebook/create-react-app@061d1e4) Add troubleshooting for Github Pages (#&#8203;4197)
-   [`2e690e9`](facebook/create-react-app@2e690e9) Add 1.1.2 changelog (#&#8203;4242)
#### v1.1.4
-   [`3b102fe`](facebook/create-react-app@3b102fe) Work around Jest environment resolving bug (#&#8203;4247)
-   [`90c908e`](facebook/create-react-app@90c908e) Changelog for 1.1.3
-   [`2762924`](facebook/create-react-app@2762924) Update detect-port-alt

</details>



---

This PR has been generated by [Renovate Bot](https://renovateapp.com).

Co-authored-by: Renovate Bot <bot@renovateapp.com>
@nfantone
Copy link

So, in the end, this didn't target next, but master, if I'm not mistaken? Latest 2.0.0-next does not have this fix?

@iansu
Copy link
Contributor

iansu commented Apr 24, 2018

That's correct. This fix is really just a hack. We're hoping that Jest and webpack will properly support .mjs in the near future and that we can incorporate those new versions in 2.0.

@jefflau
Copy link

jefflau commented May 16, 2018

Is there a way of getting this fix into next? I'd like to use this along with babel-plugin-macros by @kentcdodds

@Timer
Copy link
Contributor

Timer commented May 16, 2018

This fix is really just a hack.

We have no plans of merging this into 2.0 unless it's the last remaining blocker for a 2.0-final release.

@leebyron leebyron deleted the patch-1 branch May 17, 2018 13:20
@leebyron
Copy link
Contributor Author

I think it would be a mistake to regress on this bug in CRA v2

That's correct. This fix is really just a hack. We're hoping that Jest and webpack will properly support .mjs in the near future and that we can incorporate those new versions in 2.0.

I don't know that I would agree with this characterization. This PR was a bug fix against CRA for Jest and webpack's semi-hacky implementation of ESM. Jest and webpack will probably require extensive architectural changes requiring major version bumps to properly support native ESM. I wouldn't suggest holding your breath for that. If CRA v2 intends to ship with support for Jest and Webpack's current major version then it should include this bug fix.

@gaearon
Copy link
Contributor

gaearon commented May 17, 2018

I agree with @leebyron here

gaearon pushed a commit that referenced this pull request May 20, 2018
Support for .mjs files added in #3239 did not account for npm libraries which ship native mjs files alongside js files. This accounts for this by ensuring .js files resolve before their accompanying .mjs file. Note that this is not an ideal end state since selecting a .mjs over a .js extension should be the result of whether `import` was used instead of `require()` in a node environment with native ESM support (currently via `--experimental-modules`). Instead, this change just *always* selects a .js extension before the .mjs extension if it exists.

This unbreaks support for using GraphQL (relay, apollo, etc) within create-react-app projects.
Zaccc123 pushed a commit to Zaccc123/create-react-app that referenced this pull request May 21, 2018
Support for .mjs files added in facebook#3239 did not account for npm libraries which ship native mjs files alongside js files. This accounts for this by ensuring .js files resolve before their accompanying .mjs file. Note that this is not an ideal end state since selecting a .mjs over a .js extension should be the result of whether `import` was used instead of `require()` in a node environment with native ESM support (currently via `--experimental-modules`). Instead, this change just *always* selects a .js extension before the .mjs extension if it exists.

This unbreaks support for using GraphQL (relay, apollo, etc) within create-react-app projects.
vojtechsimetka added a commit to Giveth/create-react-app that referenced this pull request May 28, 2018
* 'master' of https://github.com/facebook/create-react-app: (220 commits)
  Publish
  Changelog for 1.1.4
  Update detect-port-alt (facebook#4250)
  Publish
  Changelog for 1.1.3
  Work around Jest environment resolving bug (facebook#4247)
  Publish
  Add 1.1.2 changelog (facebook#4242)
  Add troubleshooting for Github Pages (facebook#4197)
  `.mjs` should not resolve before .js files (facebook#4085)
  Publish
  Revert "Set the public path to the asset manifest entries (facebook#2544)"
  Add 1.1.1 changelog
  Unpin and bump fsevents (for 1.x branch) (facebook#4006)
  Update dotenv-expand to fix bug with environment variables that contain a $. (facebook#4000)
  Update instructions for continuous delivery with Netlify (facebook#3971)
  Include `{json,css}` files in prettier command (facebook#3894)
  Set the public path to the asset manifest entries (facebook#2544)
  1.5.1
  pin envinfo version to 3.4.2 (facebook#3853)
  ...

# Conflicts:
#	packages/react-scripts/config/webpack.config.prod.js
#	packages/react-scripts/package.json
@stellarhoof
Copy link

stellarhoof commented Jun 3, 2018

I see a fix has been merged for this issue in 0e0f260, but the latest release of next I in npm is behind, at 66cc7a9. Will it be updated any time soon?

Pavek pushed a commit to Pavek/create-react-app that referenced this pull request Jul 10, 2018
Support for .mjs files added in facebook#3239 did not account for npm libraries which ship native mjs files alongside js files. This accounts for this by ensuring .js files resolve before their accompanying .mjs file. Note that this is not an ideal end state since selecting a .mjs over a .js extension should be the result of whether `import` was used instead of `require()` in a node environment with native ESM support (currently via `--experimental-modules`). Instead, this change just *always* selects a .js extension before the .mjs extension if it exists.

This unbreaks support for using GraphQL (relay, apollo, etc) within create-react-app projects.
zmitry pushed a commit to zmitry/create-react-app that referenced this pull request Aug 14, 2018
Support for .mjs files added in facebook#3239 did not account for npm libraries which ship native mjs files alongside js files. This accounts for this by ensuring .js files resolve before their accompanying .mjs file. Note that this is not an ideal end state since selecting a .mjs over a .js extension should be the result of whether `import` was used instead of `require()` in a node environment with native ESM support (currently via `--experimental-modules`). Instead, this change just *always* selects a .js extension before the .mjs extension if it exists.

This unbreaks support for using GraphQL (relay, apollo, etc) within create-react-app projects.
zmitry pushed a commit to zmitry/create-react-app that referenced this pull request Sep 30, 2018
…4318)

Support for .mjs files added in facebook#3239 did not account for npm libraries which ship native mjs files alongside js files. This accounts for this by ensuring .js files resolve before their accompanying .mjs file. Note that this is not an ideal end state since selecting a .mjs over a .js extension should be the result of whether `import` was used instead of `require()` in a node environment with native ESM support (currently via `--experimental-modules`). Instead, this change just *always* selects a .js extension before the .mjs extension if it exists.

This unbreaks support for using GraphQL (relay, apollo, etc) within create-react-app projects.
@lock lock bot locked and limited conversation to collaborators Jan 19, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.