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

Implement file extension resolution for CJS, same as ESM; add exhaustive tests for resolver #1727

Merged
merged 34 commits into from May 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
d1ae934
WIP
cspotcode Nov 8, 2021
bc1d314
Merge remote-tracking branch 'origin/main' into add-cjs-loader-resolve
cspotcode Nov 8, 2021
9a8f87e
restore .d.ts file
cspotcode Nov 8, 2021
4262420
Merge remote-tracking branch 'origin/main' into add-cjs-loader-resolve
cspotcode Apr 15, 2022
1885c1d
Merge remote-tracking branch 'origin/main' into add-cjs-loader-resolve
cspotcode Apr 20, 2022
7da5958
add notes and todos
cspotcode Apr 20, 2022
a61dba4
strip down node-internal-modules-cjs-loader to match main branch; wil…
cspotcode May 2, 2022
df272ab
committing all local stuff
cspotcode May 7, 2022
8782c3f
update
cspotcode May 8, 2022
f6e85a3
it kinda works!
cspotcode May 14, 2022
2140909
Merge remote-tracking branch 'origin/main' into add-cjs-loader-resolve-2
cspotcode May 14, 2022
f166f44
fix CI?
cspotcode May 14, 2022
a239ab4
fix?
cspotcode May 14, 2022
05cee32
fix?
cspotcode May 14, 2022
3c6defe
fix
cspotcode May 14, 2022
9c06277
bump up to node 18 cuz why not
cspotcode May 14, 2022
f4959d8
test matrix: replace node17 with node18
cspotcode May 14, 2022
104f045
fix node12 test failure
cspotcode May 14, 2022
1574a43
fix for prior hooks API
cspotcode May 14, 2022
1bd8f10
tweak
cspotcode May 14, 2022
9cb076c
fix
cspotcode May 14, 2022
9699cee
teach node environment resetter to re-sort require.extensions
cspotcode May 14, 2022
f9b50a7
fix
cspotcode May 14, 2022
d73f7d4
fix
cspotcode May 14, 2022
284d118
fix
cspotcode May 14, 2022
5cf6772
windows fix?
cspotcode May 14, 2022
59dd604
Addressing TODOs
cspotcode May 15, 2022
cc1f36d
sync another raw file with upstream
cspotcode May 15, 2022
c9d34b9
Improve reuse / DI within node ESM stuff
cspotcode May 15, 2022
e34adc0
cleanup node internalBinding stuff
cspotcode May 15, 2022
46096ab
Adding hint when ts-node is ignoring a file that you might want it to…
cspotcode May 15, 2022
f844b39
Add tests for self-imports and empty package.json manifests importing…
cspotcode May 15, 2022
e54686c
fix
cspotcode May 15, 2022
6f3fdd7
fix node12
cspotcode May 15, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 3 additions & 4 deletions .github/workflows/continuous-integration.yml
Expand Up @@ -54,7 +54,6 @@ jobs:
flavor: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
include:
# Node 12.15
# TODO Add comments about why we test 12.15; I think git blame says it's because of an ESM behavioral change that happened at 12.16
- flavor: 1
node: 12.15
nodeFlag: 12_15
Expand Down Expand Up @@ -122,10 +121,10 @@ jobs:
typescript: next
typescriptFlag: next
downgradeNpm: true
# Node 17
# Node 18
- flavor: 12
node: 17
nodeFlag: 17
node: 18
nodeFlag: 18
typescript: latest
typescriptFlag: latest
downgradeNpm: true
Expand Down
13 changes: 7 additions & 6 deletions .gitignore
@@ -1,14 +1,15 @@
/node_modules/
/tests/node_modules/
.nyc_output/
coverage/
/.nyc_output/
/coverage/
.DS_Store
npm-debug.log
/dist/
tsconfig.schema.json
tsconfig.schemastore-schema.json
.idea/
/.vscode/
/tsconfig.schema.json
/tsconfig.schemastore-schema.json
/.idea/
/.vscode/*
!/.vscode/launch.json
/website/static/api
/tsconfig.tsbuildinfo
/temp
12 changes: 11 additions & 1 deletion .vscode/launch.json
Expand Up @@ -12,6 +12,16 @@
"<node_internals>/**/*.js"
],
},
{
"name": "Debug resolver tests (example)",
"type": "pwa-node",
"request": "launch",
"cwd": "${workspaceFolder}/tests/tmp/resolver-0015-preferSrc-typeModule-allowJs-experimentalSpecifierResolutionNode",
"runtimeArgs": [
"--loader", "../../../esm.mjs"
],
"program": "./src/entrypoint-0054-src-to-src.mjs"
},
{
"name": "Debug Example: running a test fixture against local ts-node/esm loader",
"type": "pwa-node",
Expand All @@ -24,5 +34,5 @@
"<node_internals>/**/*.js"
],
}
],
]
}
61 changes: 61 additions & 0 deletions NOTES.md
@@ -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) {...}
```
14 changes: 14 additions & 0 deletions TODO.md
@@ -0,0 +1,14 @@
*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.

Make list of changes from vanilla node in dist-raw/node-internal-modules-cjs-loader-old.js
Apply those changes to dist-raw/node-internal-modules-cjs-loader.js

4 changes: 4 additions & 0 deletions ava.config.cjs
@@ -1,4 +1,5 @@
const expect = require('expect');
const semver = require('semver');
const { createRequire } = require('module');

module.exports = {
Expand All @@ -14,6 +15,9 @@ module.exports = {
NODE_PATH: ''
},
require: ['./src/test/remove-env-var-force-color.js'],
nodeArguments: semver.gte(process.version, '14.0.0')
? ['--loader', './src/test/test-loader.mjs', '--no-warnings']
: [],
timeout: '300s',
concurrency: 1,
};
Expand Down
1 change: 1 addition & 0 deletions child-loader.mjs
Expand Up @@ -2,6 +2,7 @@ import { fileURLToPath } from 'url';
import { createRequire } from 'module';
const require = createRequire(fileURLToPath(import.meta.url));

// TODO why use require() here? I think we can just `import`
/** @type {import('./dist/child-loader')} */
const childLoader = require('./dist/child/child-loader');
export const { resolve, load, getFormat, transformSource } = childLoader;
32 changes: 0 additions & 32 deletions dist-raw/node-errors.js

This file was deleted.

4 changes: 4 additions & 0 deletions dist-raw/node-internal-constants.js
@@ -0,0 +1,4 @@
// Copied from https://github.com/nodejs/node/blob/master/lib/internal/constants.js
module.exports = {
CHAR_FORWARD_SLASH: 47, /* / */
};
34 changes: 31 additions & 3 deletions dist-raw/node-internal-errors.js
Expand Up @@ -2,9 +2,37 @@

const path = require('path');

module.exports = {
createErrRequireEsm
};
exports.codes = {
ERR_INPUT_TYPE_NOT_ALLOWED: createErrorCtor(joinArgs('ERR_INPUT_TYPE_NOT_ALLOWED')),
ERR_INVALID_ARG_VALUE: createErrorCtor(joinArgs('ERR_INVALID_ARG_VALUE')),
ERR_INVALID_MODULE_SPECIFIER: createErrorCtor(joinArgs('ERR_INVALID_MODULE_SPECIFIER')),
ERR_INVALID_PACKAGE_CONFIG: createErrorCtor(joinArgs('ERR_INVALID_PACKAGE_CONFIG')),
ERR_INVALID_PACKAGE_TARGET: createErrorCtor(joinArgs('ERR_INVALID_PACKAGE_TARGET')),
ERR_MANIFEST_DEPENDENCY_MISSING: createErrorCtor(joinArgs('ERR_MANIFEST_DEPENDENCY_MISSING')),
ERR_MODULE_NOT_FOUND: createErrorCtor((path, base, type = 'package') => {
return `Cannot find ${type} '${path}' imported from ${base}`
}),
ERR_PACKAGE_IMPORT_NOT_DEFINED: createErrorCtor(joinArgs('ERR_PACKAGE_IMPORT_NOT_DEFINED')),
ERR_PACKAGE_PATH_NOT_EXPORTED: createErrorCtor(joinArgs('ERR_PACKAGE_PATH_NOT_EXPORTED')),
ERR_UNSUPPORTED_DIR_IMPORT: createErrorCtor(joinArgs('ERR_UNSUPPORTED_DIR_IMPORT')),
ERR_UNSUPPORTED_ESM_URL_SCHEME: createErrorCtor(joinArgs('ERR_UNSUPPORTED_ESM_URL_SCHEME')),
ERR_UNKNOWN_FILE_EXTENSION: createErrorCtor(joinArgs('ERR_UNKNOWN_FILE_EXTENSION')),
}

function joinArgs(name) {
return (...args) => {
return [name, ...args].join(' ')
}
}

function createErrorCtor(errorMessageCreator) {
return class CustomError extends Error {
constructor(...args) {
super(errorMessageCreator(...args))
}
}
}
exports.createErrRequireEsm = createErrRequireEsm;

// Native ERR_REQUIRE_ESM Error is declared here:
// https://github.com/nodejs/node/blob/2d5d77306f6dff9110c1f77fefab25f973415770/lib/internal/errors.js#L1294-L1313
Expand Down
1 change: 0 additions & 1 deletion dist-raw/node-internal-modules-cjs-helpers.d.ts

This file was deleted.

50 changes: 44 additions & 6 deletions dist-raw/node-internal-modules-cjs-helpers.js
@@ -1,11 +1,45 @@
const {ArrayPrototypeForEach, StringPrototypeStartsWith, ObjectPrototypeHasOwnProperty, StringPrototypeIncludes, ObjectDefineProperty} = require('./node-primordials');
// Copied from https://github.com/nodejs/node/blob/v17.0.1/lib/internal/modules/cjs/helpers.js

exports.addBuiltinLibsToObject = addBuiltinLibsToObject;
'use strict';

const {
ArrayPrototypeForEach,
ObjectDefineProperty,
ObjectPrototypeHasOwnProperty,
SafeSet,
StringPrototypeIncludes,
StringPrototypeStartsWith,
} = require('./node-primordials');

const { getOptionValue } = require('./node-options');
const userConditions = getOptionValue('--conditions');

const noAddons = getOptionValue('--no-addons');
const addonConditions = noAddons ? [] : ['node-addons'];

// TODO: Use this set when resolving pkg#exports conditions in loader.js.
const cjsConditions = new SafeSet([
'require',
'node',
...addonConditions,
...userConditions,
]);

// Copied from https://github.com/nodejs/node/blob/21f5a56914a3b24ad77535ef369b93c6b1c11d18/lib/internal/modules/cjs/helpers.js#L133-L178
function addBuiltinLibsToObject(object) {
/**
* @param {any} object
* @param {string} [dummyModuleName]
* @return {void}
*/
function addBuiltinLibsToObject(object, dummyModuleName) {
// Make built-in modules available directly (loaded lazily).
const { builtinModules } = require('module').Module;
const Module = require('module').Module;
const { builtinModules } = Module;

// To require built-in modules in user-land and ignore modules whose
// `canBeRequiredByUsers` is false. So we create a dummy module object and not
// use `require()` directly.
const dummyModule = new Module(dummyModuleName);

ArrayPrototypeForEach(builtinModules, (name) => {
// Neither add underscored modules, nor ones that contain slashes (e.g.,
// 'fs/promises') or ones that are already defined.
Expand All @@ -29,7 +63,8 @@ function addBuiltinLibsToObject(object) {

ObjectDefineProperty(object, name, {
get: () => {
const lib = require(name);
// Node 12 hack; remove when we drop node12 support
const lib = (dummyModule.require || require)(name);

// Disable the current getter/setter and set up a new
// non-enumerable property.
Expand All @@ -49,3 +84,6 @@ function addBuiltinLibsToObject(object) {
});
});
}

exports.addBuiltinLibsToObject = addBuiltinLibsToObject;
exports.cjsConditions = cjsConditions;