Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
coverage/
.DS_Store
npm-debug.log
dist/
/dist/
tsconfig.schema.json
tsconfig.schemastore-schema.json
.idea/
Expand Down
61 changes: 61 additions & 0 deletions NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
*Delete this file before merging this PR*

## PnP interop

Asked about it here:
https://discord.com/channels/226791405589233664/654372321225605128/957301175609344070

PnP API checks if import specifiers are for dependencies: non-relative, non-absolute
libfoo
@scope/libfoo

When they are, it does `resolveToUnqualified` to map to an unqualified path.
This path points to the module's location on disk (in a zip, perhaps) but does
not handle file extension resolution or stuff like that.

To interop with PnP, we need PnP to *only* `resolveToUnqualified`.
We do everything else.

```typescript
import { Module } from 'module';
import fs from 'fs';

const pathRegExp = /^(?![a-zA-Z]:[\\/]|\\\\|\.{0,2}(?:\/|$))((?:@[^/]+\/)?[^/]+)\/*(.*|)$/;

const originalModuleResolveFilename = Module._resolveFilename;
Module._resolveFilename = function (
request: string,
parent: typeof Module | null | undefined,
isMain: boolean,
options?: { [key: string]: any }
) {
const dependencyNameMatch = request.match(pathRegExp);
if (dependencyNameMatch !== null) {

const [, dependencyName, subPath] = dependencyNameMatch;

const unqualified = pnpapi.resolveToUnqualified(....);

// Do your modified resolution on the unqualified path here

} else {

// Do your modified resolution here; no need for PnP

}

};
```

PnP can be installed at runtime.

To conditionally check if PnP is available at the start of *every* resolution:

```typescript
// Get the pnpapi of either the issuer or the specifier.
// The latter is required when the specifier is an absolute path to a
// zip file and the issuer doesn't belong to a pnpapi
const {findPnPApi} = Module;
const pnpapi = findPnPApi ? (findPnpApi(issuer) ?? (url ? findPnpApi(specifier) : null)) : null;
if (pnpapi) {...}
```
12 changes: 12 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
*Delete this file before merging this PR*

## TODOs

Copy any relevant changes from `add-cjs-loader-resolve`

I forgot exactly where I was in `add-cjs-loader-resolve`
Re-do the renaming and moving that I did in that branch.
Then diff to see that I did it correctly.
Avoid introducing any accidental behavioral changes.


24 changes: 24 additions & 0 deletions dist-raw/NODE-LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
This directory contains portions of Node.js source code which is licensed as follows:

---

Copyright Joyent, Inc. and other Node contributors.

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the
following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
23 changes: 23 additions & 0 deletions dist-raw/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,26 @@ in a factory function, we will not indent the function body, to avoid whitespace
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.

## `raw` directory

Within the `raw` directory, we keep unmodified copies of the node source files. This allows us to use diffing tools to
compare files in `raw` to those in `dist-raw`, which will highlight all of the changes we have made. Hopefully, these
changes are as minimal as possible.

## Naming convention

Not used consistently, but the idea is:

`node-<directory>(...-<directory>)-<filename>.js`

`node-internal-errors.js` -> `github.com/nodejs/node/blob/TAG/lib/internal/errors.js`

So, take the path within node's `lib/` directory, and replace slashes with hyphens.

In the `raw` directory, files are suffixed with the version number or revision from which
they were downloaded.

If they have a `stripped` suffix, this means they have large chunks of code deleted, but no other modifications.
This is useful when diffing. Sometimes our `dist-raw` files only have a small part of a much larger node source file.
It is easier to diff `raw/*-stripped.js` against `dist-raw/*.js`.
50 changes: 50 additions & 0 deletions dist-raw/node-internal-errors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// 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;
}
}

module.exports = {
createErrRequireEsm
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const path = require('path');
const packageJsonReader = require('./node-package-json-reader');
const {JSONParse} = require('./node-primordials');
const {normalizeSlashes} = require('../dist/util');
const {createErrRequireEsm} = require('./node-internal-errors');

module.exports.assertScriptCanLoadAsCJSImpl = assertScriptCanLoadAsCJSImpl;

Expand Down Expand Up @@ -84,50 +85,3 @@ function readPackage(requestPath) {
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;
}
}
Loading