-
-
Notifications
You must be signed in to change notification settings - Fork 541
Implement ERR_REQUIRE_ESM when a file is require()
d which should be loaded as ESM
#1031
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
Merged
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
2bee85e
revert addition of package.json "exports" because it is a breaking ch…
cspotcode c2ab1c3
Implement ERR_REQUIRE_ESM error when attempting to require() an ESM file
cspotcode d82df95
fix
cspotcode a10c7a9
test on node 12.15 and 12.16 due to weird ESM handling
cspotcode 7243123
Switch package.json "type" feature detection to a simple version numb…
cspotcode eb07ff6
Remove dead code
cspotcode 567acb6
Only throw ERR_REQUIRE_ESM if experimental ESM loader is enabled, sin…
cspotcode f5fab01
Fix linter failures
cspotcode dcc7e0d
Merge remote-tracking branch 'origin/master' into ab/add-err-require-…
cspotcode 8076998
Merge remote-tracking branch 'origin/master' into ab/add-err-require-…
cspotcode d70658b
Add tests
cspotcode b1c35b8
fix test?
cspotcode 5e8090a
Fix tests
cspotcode da76779
Merge remote-tracking branch 'origin/master' into ab/add-err-require-…
cspotcode 9f9efd6
add comments to explain copy-pasted cjs loader code from node core
cspotcode 6b15230
Merge remote-tracking branch 'origin/master' into ab/add-err-require-…
cspotcode 7a1a55b
fix
cspotcode 1a21a7d
tweak comment
cspotcode File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
The `dist-raw` directory contains JS sources that are distributed verbatim, not compiled nor typechecked via TS. | ||
|
||
To implement ESM support, we unfortunately must duplicate some of node's built-in functionality that is not | ||
exposed via an API. We have copy-pasted the necessary code from https://github.com./nodejs/node/tree/master/lib | ||
then modified it to suite our needs. | ||
|
||
Formatting may be intentionally bad to keep the diff as small as possible, to make it easier to merge | ||
upstream changes and understand our modifications. For example, when we need to wrap node's source code | ||
in a factory function, we will not indent the function body, to avoid whitespace changes in the diff. | ||
|
||
One obvious problem with this approach: the code has been pulled from one version of node, whereas users of ts-node | ||
run multiple versions of node. | ||
Users running node 12 may see that ts-node behaves like node 14, for example. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
// Copied from several files in node's source code. | ||
// https://github.com./nodejs/node/blob/2d5d77306f6dff9110c1f77fefab25f973415770/lib/internal/modules/cjs/loader.js | ||
// Each function and variable below must have a comment linking to the source in node's github repo. | ||
|
||
const path = require('path'); | ||
const fs = require('fs'); | ||
|
||
module.exports.assertScriptCanLoadAsCJSImpl = assertScriptCanLoadAsCJSImpl; | ||
|
||
// copied from Module._extensions['.js'] | ||
// https://github.com./nodejs/node/blob/2d5d77306f6dff9110c1f77fefab25f973415770/lib/internal/modules/cjs/loader.js#L1211-L1217 | ||
function assertScriptCanLoadAsCJSImpl(filename) { | ||
const pkg = readPackageScope(filename); | ||
// Function require shouldn't be used in ES modules. | ||
if (pkg && pkg.data && pkg.data.type === 'module') { | ||
const parentPath = module.parent && module.parent.filename; | ||
const packageJsonPath = path.resolve(pkg.path, 'package.json'); | ||
throw createErrRequireEsm(filename, parentPath, packageJsonPath); | ||
} | ||
} | ||
|
||
// Copied from https://github.com./nodejs/node/blob/2d5d77306f6dff9110c1f77fefab25f973415770/lib/internal/modules/cjs/loader.js#L285-L301 | ||
function readPackageScope(checkPath) { | ||
const rootSeparatorIndex = checkPath.indexOf(path.sep); | ||
let separatorIndex; | ||
while ( | ||
(separatorIndex = checkPath.lastIndexOf(path.sep)) > rootSeparatorIndex | ||
) { | ||
checkPath = checkPath.slice(0, separatorIndex); | ||
if (checkPath.endsWith(path.sep + 'node_modules')) | ||
return false; | ||
const pjson = readPackage(checkPath); | ||
if (pjson) return { | ||
path: checkPath, | ||
data: pjson | ||
}; | ||
} | ||
return false; | ||
} | ||
|
||
// Copied from https://github.com./nodejs/node/blob/2d5d77306f6dff9110c1f77fefab25f973415770/lib/internal/modules/cjs/loader.js#L249 | ||
const packageJsonCache = new Map(); | ||
|
||
// Copied from https://github.com./nodejs/node/blob/2d5d77306f6dff9110c1f77fefab25f973415770/lib/internal/modules/cjs/loader.js#L251-L283 | ||
function readPackage(requestPath) { | ||
const jsonPath = path.resolve(requestPath, 'package.json'); | ||
|
||
const existing = packageJsonCache.get(jsonPath); | ||
if (existing !== undefined) return existing; | ||
|
||
const json = internalModuleReadJSON(path.toNamespacedPath(jsonPath)); | ||
if (json === undefined) { | ||
packageJsonCache.set(jsonPath, false); | ||
return false; | ||
} | ||
|
||
// TODO Related to `--experimental-policy`? Disabling for now | ||
// if (manifest) { | ||
// const jsonURL = pathToFileURL(jsonPath); | ||
// manifest.assertIntegrity(jsonURL, json); | ||
// } | ||
|
||
try { | ||
const parsed = JSON.parse(json); | ||
const filtered = { | ||
name: parsed.name, | ||
main: parsed.main, | ||
exports: parsed.exports, | ||
type: parsed.type | ||
}; | ||
packageJsonCache.set(jsonPath, filtered); | ||
return filtered; | ||
} catch (e) { | ||
e.path = jsonPath; | ||
e.message = 'Error parsing ' + jsonPath + ': ' + e.message; | ||
throw e; | ||
} | ||
} | ||
|
||
// In node's core, this is implemented in C | ||
// https://github.com./nodejs/node/blob/e9f293750760d59243020d0376edf242c9a26b67/src/node_file.cc#L845-L939 | ||
function internalModuleReadJSON(path) { | ||
try { | ||
return fs.readFileSync(path, 'utf8') | ||
} catch (e) { | ||
if (e.code === 'ENOENT') return undefined | ||
throw e | ||
} | ||
} | ||
|
||
// Native ERR_REQUIRE_ESM Error is declared here: | ||
// https://github.com./nodejs/node/blob/2d5d77306f6dff9110c1f77fefab25f973415770/lib/internal/errors.js#L1294-L1313 | ||
// Error class factory is implemented here: | ||
// function E: https://github.com./nodejs/node/blob/2d5d77306f6dff9110c1f77fefab25f973415770/lib/internal/errors.js#L323-L341 | ||
// function makeNodeErrorWithCode: https://github.com./nodejs/node/blob/2d5d77306f6dff9110c1f77fefab25f973415770/lib/internal/errors.js#L251-L278 | ||
// The code below should create an error that matches the native error as closely as possible. | ||
// Third-party libraries which attempt to catch the native ERR_REQUIRE_ESM should recognize our imitation error. | ||
function createErrRequireEsm(filename, parentPath, packageJsonPath) { | ||
const code = 'ERR_REQUIRE_ESM' | ||
const err = new Error(getMessage(filename, parentPath, packageJsonPath)) | ||
// Set `name` to be used in stack trace, generate stack trace with that name baked in, then re-declare the `name` field. | ||
// This trick is copied from node's source. | ||
err.name = `Error [${ code }]` | ||
err.stack | ||
Object.defineProperty(err, 'name', { | ||
value: 'Error', | ||
enumerable: false, | ||
writable: true, | ||
configurable: true | ||
}) | ||
err.code = code | ||
return err | ||
|
||
// Copy-pasted from https://github.com./nodejs/node/blob/b533fb3508009e5f567cc776daba8fbf665386a6/lib/internal/errors.js#L1293-L1311 | ||
// so that our error message is identical to the native message. | ||
function getMessage(filename, parentPath = null, packageJsonPath = null) { | ||
const ext = path.extname(filename) | ||
let msg = `Must use import to load ES Module: ${filename}`; | ||
if (parentPath && packageJsonPath) { | ||
const path = require('path'); | ||
const basename = path.basename(filename) === path.basename(parentPath) ? | ||
filename : path.basename(filename); | ||
msg += | ||
'\nrequire() of ES modules is not supported.\nrequire() of ' + | ||
`${filename} ${parentPath ? `from ${parentPath} ` : ''}` + | ||
`is an ES module file as it is a ${ext} file whose nearest parent ` + | ||
`package.json contains "type": "module" which defines all ${ext} ` + | ||
'files in that package scope as ES modules.\nInstead ' + | ||
'change the requiring code to use ' + | ||
'import(), or remove "type": "module" from ' + | ||
`${packageJsonPath}.\n`; | ||
return msg; | ||
} | ||
return msg; | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
// Log if this file is loaded as ESM or CommonJS | ||
if(typeof module !== 'undefined') | ||
console.log('CommonJS') | ||
else | ||
console.log('ESM') |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"type": "module" | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
require('./esm-package/loaded-as') |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is pretty funky, might want to add a comment on why you do this since the first look makes it seem this line would erase
err.name =
above.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done