From 35765c75ada3f47608af97fd52ef494f564f8684 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sun, 18 Sep 2022 13:29:49 -0700 Subject: [PATCH 1/5] Cleanup existing code --- commonjs-extension-resolution-loader/test.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/commonjs-extension-resolution-loader/test.js b/commonjs-extension-resolution-loader/test.js index a0802f5..0d23178 100644 --- a/commonjs-extension-resolution-loader/test.js +++ b/commonjs-extension-resolution-loader/test.js @@ -1,13 +1,13 @@ -import { ok } from 'assert'; +import { match } from 'assert'; import { spawn } from 'child_process'; import { execPath } from 'process'; // Run this test yourself with debugging mode via: -// node --inspect-brk --experimental-loader ./loader.js ./fixtures/index.js +// node --inspect-brk --loader ./loader.js ./fixtures/index.js const child = spawn(execPath, [ - '--experimental-loader', + '--loader', './loader.js', './fixtures/index.js' ]); @@ -20,6 +20,6 @@ child.stdout.on('data', (data) => { child.on('close', (code, signal) => { stdout = stdout.toString(); - ok(stdout.includes('hello from file.js')); - ok(stdout.includes('hello from folder/index.js')); + match(stdout, /hello from file\.js/); + match(stdout, /hello from folder\/index\.js/); }); From d17c0ea0386cd7b59f21e9ef1fff78423e370074 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sat, 24 Sep 2022 18:13:28 -0700 Subject: [PATCH 2/5] Get commonjs-extension-resolution-loader working --- .../loader.js | 39 +++--- .../package-lock.json | 118 ++++++++++++++++++ .../package.json | 7 +- commonjs-extension-resolution-loader/test.js | 6 +- 4 files changed, 149 insertions(+), 21 deletions(-) create mode 100644 commonjs-extension-resolution-loader/package-lock.json diff --git a/commonjs-extension-resolution-loader/loader.js b/commonjs-extension-resolution-loader/loader.js index 1ba18cd..cdf0fc8 100644 --- a/commonjs-extension-resolution-loader/loader.js +++ b/commonjs-extension-resolution-loader/loader.js @@ -1,27 +1,34 @@ -import { existsSync } from 'fs'; -import { createRequire } from 'module'; +import { builtinModules } from 'node:module'; import { dirname } from 'path'; -import { URL, fileURLToPath, pathToFileURL } from 'url'; +import { cwd } from 'process'; +import { fileURLToPath, pathToFileURL } from 'url'; +import { promisify } from 'util'; -const require = createRequire(import.meta.url); -const baseURL = pathToFileURL(process.cwd() + '/').href; +import resolveCallback from 'resolve/async.js'; -export function resolve(specifier, context, defaultResolve) { +const resolveAsync = promisify(resolveCallback); + +const baseURL = pathToFileURL(cwd() + '/').href; + + +export async function resolve(specifier, context, next) { const { parentURL = baseURL } = context; - // `require.resolve` works with paths, not URLs, so convert to and from + if (specifier.startsWith('node:') || builtinModules.includes(specifier)) { + return next(specifier, context); + } + + // `resolveAsync` works with paths, not URLs if (specifier.startsWith('file://')) { specifier = fileURLToPath(specifier); } - const basePath = dirname(fileURLToPath(parentURL)); - const resolvedPath = require.resolve(specifier, {paths: [basePath]}); + const parentPath = fileURLToPath(parentURL); - if (existsSync(resolvedPath)) { - return { - url: pathToFileURL(resolvedPath).href - }; - } + const resolution = await resolveAsync(specifier, { + basedir: dirname(parentPath), + extensions: ['.js', '.json', '.node'], + }); + const url = pathToFileURL(resolution).href; - // Let Node.js handle all other specifiers, such as package names - return defaultResolve(specifier, context, defaultResolve); + return next(url, context); } diff --git a/commonjs-extension-resolution-loader/package-lock.json b/commonjs-extension-resolution-loader/package-lock.json new file mode 100644 index 0000000..2e22ac1 --- /dev/null +++ b/commonjs-extension-resolution-loader/package-lock.json @@ -0,0 +1,118 @@ +{ + "name": "commonjs-extension-resolution-loader", + "version": "0.1.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "commonjs-extension-resolution-loader", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "resolve": "^1.22.1" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/is-core-module": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", + "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + } + }, + "dependencies": { + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "is-core-module": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", + "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "requires": { + "has": "^1.0.3" + } + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + } + } +} diff --git a/commonjs-extension-resolution-loader/package.json b/commonjs-extension-resolution-loader/package.json index 9cbd4ca..d1b47ad 100644 --- a/commonjs-extension-resolution-loader/package.json +++ b/commonjs-extension-resolution-loader/package.json @@ -8,6 +8,9 @@ "start": "npm test", "test": "node test.js" }, - "author": "Geoffrey Booth ", - "license": "MIT" + "author": "Geoffrey Booth ", + "license": "MIT", + "dependencies": { + "resolve": "^1.22.1" + } } diff --git a/commonjs-extension-resolution-loader/test.js b/commonjs-extension-resolution-loader/test.js index 0d23178..6a17e47 100644 --- a/commonjs-extension-resolution-loader/test.js +++ b/commonjs-extension-resolution-loader/test.js @@ -14,12 +14,12 @@ const child = spawn(execPath, [ let stdout = ''; child.stdout.setEncoding('utf8'); -child.stdout.on('data', (data) => { +child.stdout.on('data', data => { stdout += data; }); -child.on('close', (code, signal) => { - stdout = stdout.toString(); +child.on('close', (_code, _signal) => { + stdout = stdout.toString(); match(stdout, /hello from file\.js/); match(stdout, /hello from folder\/index\.js/); }); From fea414b5d029fd7d3431ef6912e4ae0ed8baf4a4 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sat, 24 Sep 2022 18:47:52 -0700 Subject: [PATCH 3/5] Make way for porting Node's tests --- commonjs-extension-resolution-loader/package.json | 2 +- .../{fixtures => test/basic-fixtures}/file.js | 0 .../{fixtures => test/basic-fixtures}/folder/index.js | 0 .../{fixtures => test/basic-fixtures}/index.js | 0 commonjs-extension-resolution-loader/{test.js => test/basic.js} | 2 +- 5 files changed, 2 insertions(+), 2 deletions(-) rename commonjs-extension-resolution-loader/{fixtures => test/basic-fixtures}/file.js (100%) rename commonjs-extension-resolution-loader/{fixtures => test/basic-fixtures}/folder/index.js (100%) rename commonjs-extension-resolution-loader/{fixtures => test/basic-fixtures}/index.js (100%) rename commonjs-extension-resolution-loader/{test.js => test/basic.js} (94%) diff --git a/commonjs-extension-resolution-loader/package.json b/commonjs-extension-resolution-loader/package.json index d1b47ad..4181509 100644 --- a/commonjs-extension-resolution-loader/package.json +++ b/commonjs-extension-resolution-loader/package.json @@ -6,7 +6,7 @@ "main": "./loader.js", "scripts": { "start": "npm test", - "test": "node test.js" + "test": "node test/basic.js" }, "author": "Geoffrey Booth ", "license": "MIT", diff --git a/commonjs-extension-resolution-loader/fixtures/file.js b/commonjs-extension-resolution-loader/test/basic-fixtures/file.js similarity index 100% rename from commonjs-extension-resolution-loader/fixtures/file.js rename to commonjs-extension-resolution-loader/test/basic-fixtures/file.js diff --git a/commonjs-extension-resolution-loader/fixtures/folder/index.js b/commonjs-extension-resolution-loader/test/basic-fixtures/folder/index.js similarity index 100% rename from commonjs-extension-resolution-loader/fixtures/folder/index.js rename to commonjs-extension-resolution-loader/test/basic-fixtures/folder/index.js diff --git a/commonjs-extension-resolution-loader/fixtures/index.js b/commonjs-extension-resolution-loader/test/basic-fixtures/index.js similarity index 100% rename from commonjs-extension-resolution-loader/fixtures/index.js rename to commonjs-extension-resolution-loader/test/basic-fixtures/index.js diff --git a/commonjs-extension-resolution-loader/test.js b/commonjs-extension-resolution-loader/test/basic.js similarity index 94% rename from commonjs-extension-resolution-loader/test.js rename to commonjs-extension-resolution-loader/test/basic.js index 6a17e47..540133d 100644 --- a/commonjs-extension-resolution-loader/test.js +++ b/commonjs-extension-resolution-loader/test/basic.js @@ -9,7 +9,7 @@ import { execPath } from 'process'; const child = spawn(execPath, [ '--loader', './loader.js', - './fixtures/index.js' + './test/basic-fixtures/index.js' ]); let stdout = ''; From c6f86f3d507963eededb2b1cddeee924de385ae0 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sat, 24 Sep 2022 22:07:29 -0700 Subject: [PATCH 4/5] Port experimental-specifier-resolution tests from Node codebase --- .../package.json | 2 +- .../test/common.js | 34 +++++++++ .../fixtures/es-module-specifiers/index.mjs | 11 +++ .../package-type-commonjs/a.js | 1 + .../package-type-commonjs/b.mjs | 1 + .../package-type-commonjs/c.cjs | 5 ++ .../package-type-commonjs/index.mjs | 21 ++++++ .../package-type-commonjs/package.json | 3 + .../package-type-module/a.js | 1 + .../package-type-module/b.mjs | 1 + .../package-type-module/c.cjs | 5 ++ .../package-type-module/index.js | 21 ++++++ .../package-type-module/package.json | 3 + .../es-module-specifiers/package.json | 1 + .../test/test-esm-specifiers-symlink.js | 54 ++++++++++++++ .../test/test-esm-specifiers.js | 70 +++++++++++++++++++ 16 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 commonjs-extension-resolution-loader/test/common.js create mode 100644 commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/index.mjs create mode 100644 commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-commonjs/a.js create mode 100644 commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-commonjs/b.mjs create mode 100644 commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-commonjs/c.cjs create mode 100644 commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-commonjs/index.mjs create mode 100644 commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-commonjs/package.json create mode 100644 commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-module/a.js create mode 100644 commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-module/b.mjs create mode 100644 commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-module/c.cjs create mode 100644 commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-module/index.js create mode 100644 commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-module/package.json create mode 100644 commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package.json create mode 100644 commonjs-extension-resolution-loader/test/test-esm-specifiers-symlink.js create mode 100644 commonjs-extension-resolution-loader/test/test-esm-specifiers.js diff --git a/commonjs-extension-resolution-loader/package.json b/commonjs-extension-resolution-loader/package.json index 4181509..78e9c88 100644 --- a/commonjs-extension-resolution-loader/package.json +++ b/commonjs-extension-resolution-loader/package.json @@ -6,7 +6,7 @@ "main": "./loader.js", "scripts": { "start": "npm test", - "test": "node test/basic.js" + "test": "node test/basic.js && node test/test-esm-specifiers.js && node test/test-esm-specifiers-symlink.js" }, "author": "Geoffrey Booth ", "license": "MIT", diff --git a/commonjs-extension-resolution-loader/test/common.js b/commonjs-extension-resolution-loader/test/common.js new file mode 100644 index 0000000..85c2641 --- /dev/null +++ b/commonjs-extension-resolution-loader/test/common.js @@ -0,0 +1,34 @@ +// Ported from https://github.com/nodejs/node/blob/45f2258f74117e27ffced434a98ad6babc14f7fa/test/common/index.js + +import { spawn } from 'node:child_process'; + + +export function spawnPromisified(...args) { + let stderr = ''; + let stdout = ''; + + const child = spawn(...args); + child.stderr.setEncoding('utf8'); + child.stderr.on('data', (data) => { stderr += data; }); + child.stdout.setEncoding('utf8'); + child.stdout.on('data', (data) => { stdout += data; }); + + return new Promise((resolve, reject) => { + child.on('close', (code, signal) => { + resolve({ + code, + signal, + stderr, + stdout, + }); + }); + child.on('error', (code, signal) => { + reject({ + code, + signal, + stderr, + stdout, + }); + }); + }); +} diff --git a/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/index.mjs b/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/index.mjs new file mode 100644 index 0000000..8c361af --- /dev/null +++ b/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/index.mjs @@ -0,0 +1,11 @@ +import explicit from 'explicit-main'; +import implicit from 'implicit-main'; +import implicitModule from 'implicit-main-type-module'; +import noMain from 'no-main-field'; + +function getImplicitCommonjs () { + return import('implicit-main-type-commonjs'); +} + +export {explicit, implicit, implicitModule, getImplicitCommonjs, noMain}; +export default 'success'; diff --git a/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-commonjs/a.js b/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-commonjs/a.js new file mode 100644 index 0000000..2e7700b --- /dev/null +++ b/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-commonjs/a.js @@ -0,0 +1 @@ +module.exports = 'a'; diff --git a/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-commonjs/b.mjs b/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-commonjs/b.mjs new file mode 100644 index 0000000..137b8ce --- /dev/null +++ b/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-commonjs/b.mjs @@ -0,0 +1 @@ +export const b = 'b'; diff --git a/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-commonjs/c.cjs b/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-commonjs/c.cjs new file mode 100644 index 0000000..2d53129 --- /dev/null +++ b/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-commonjs/c.cjs @@ -0,0 +1,5 @@ +module.exports = { + one: 1, + two: 2, + three: 3 +}; diff --git a/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-commonjs/index.mjs b/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-commonjs/index.mjs new file mode 100644 index 0000000..ef2b30b --- /dev/null +++ b/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-commonjs/index.mjs @@ -0,0 +1,21 @@ +// js file that is common.js +import a from './a.js'; +// ESM with named export +import {b} from './b.mjs'; +// import 'c.cjs'; +import cjs from './c.cjs'; +// proves cross boundary fun bits +import jsAsEsm from '../package-type-module/a.js'; + +// named export from core +import {strictEqual, deepStrictEqual} from 'assert'; + +strictEqual(a, jsAsEsm); +strictEqual(b, 'b'); +deepStrictEqual(cjs, { + one: 1, + two: 2, + three: 3 +}); + +export default 'commonjs'; diff --git a/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-commonjs/package.json b/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-commonjs/package.json new file mode 100644 index 0000000..5bbefff --- /dev/null +++ b/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-commonjs/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-module/a.js b/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-module/a.js new file mode 100644 index 0000000..90bd54c --- /dev/null +++ b/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-module/a.js @@ -0,0 +1 @@ +export default 'a' diff --git a/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-module/b.mjs b/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-module/b.mjs new file mode 100644 index 0000000..137b8ce --- /dev/null +++ b/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-module/b.mjs @@ -0,0 +1 @@ +export const b = 'b'; diff --git a/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-module/c.cjs b/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-module/c.cjs new file mode 100644 index 0000000..2d53129 --- /dev/null +++ b/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-module/c.cjs @@ -0,0 +1,5 @@ +module.exports = { + one: 1, + two: 2, + three: 3 +}; diff --git a/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-module/index.js b/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-module/index.js new file mode 100644 index 0000000..a8baacb --- /dev/null +++ b/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-module/index.js @@ -0,0 +1,21 @@ +// ESM with only default +import a from './a.js'; +// ESM with named export +import {b} from './b.mjs'; +// import 'c.cjs'; +import cjs from './c.cjs'; +// import across boundaries +import jsAsCjs from '../package-type-commonjs/a.js' + +// named export from core +import {strictEqual, deepStrictEqual} from 'assert'; + +strictEqual(a, jsAsCjs); +strictEqual(b, 'b'); +deepStrictEqual(cjs, { + one: 1, + two: 2, + three: 3 +}); + +export default 'module'; diff --git a/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-module/package.json b/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-module/package.json new file mode 100644 index 0000000..3dbc1ca --- /dev/null +++ b/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package-type-module/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package.json b/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/commonjs-extension-resolution-loader/test/fixtures/es-module-specifiers/package.json @@ -0,0 +1 @@ +{} diff --git a/commonjs-extension-resolution-loader/test/test-esm-specifiers-symlink.js b/commonjs-extension-resolution-loader/test/test-esm-specifiers-symlink.js new file mode 100644 index 0000000..e670076 --- /dev/null +++ b/commonjs-extension-resolution-loader/test/test-esm-specifiers-symlink.js @@ -0,0 +1,54 @@ +// Ported from https://github.com/nodejs/node/blob/45f2258f74117e27ffced434a98ad6babc14f7fa/test/es-module/test-esm-specifiers-symlink.mjs + +import { strictEqual } from 'node:assert'; +import fs from 'node:fs/promises'; +import { join } from 'node:path'; +import { cwd, execPath, exit } from 'node:process'; + +import { spawnPromisified } from './common.js'; + + +const tmpDir = join(cwd(), `.tmp.${Date.now()}`); +await fs.rm(tmpDir, { maxRetries: 3, recursive: true, force: true }); +await fs.mkdir(tmpDir); + +// Create the following file structure: +// ├── index.mjs +// ├── subfolder +// │ ├── index.mjs +// │ └── node_modules +// │ └── package-a +// │ └── index.mjs +// └── symlink.mjs -> ./subfolder/index.mjs +const entry = join(tmpDir, 'index.mjs'); +const symlink = join(tmpDir, 'symlink.mjs'); +const real = join(tmpDir, 'subfolder', 'index.mjs'); +const packageDir = join(tmpDir, 'subfolder', 'node_modules', 'package-a'); +const packageEntry = join(packageDir, 'index.mjs'); +try { + await fs.symlink(real, symlink); +} catch (err) { + await cleanup(); + if (err.code !== 'EPERM') throw err; + console.log('insufficient privileges for symlinks'); + exit(0); +} +await fs.mkdir(packageDir, { recursive: true }); +await Promise.all([ + fs.writeFile(entry, 'import "./symlink.mjs";'), + fs.writeFile(real, 'export { a } from "package-a/index.mjs"'), + fs.writeFile(packageEntry, 'export const a = 1;'), +]); + +const { code } = await spawnPromisified(execPath, [ + '--no-warnings', + '--loader=./loader.js', + entry, +]); +await cleanup(); +strictEqual(code, 0); + + +async function cleanup() { + return fs.rm(tmpDir, { maxRetries: 3, recursive: true, force: true }); +} diff --git a/commonjs-extension-resolution-loader/test/test-esm-specifiers.js b/commonjs-extension-resolution-loader/test/test-esm-specifiers.js new file mode 100644 index 0000000..38da8ef --- /dev/null +++ b/commonjs-extension-resolution-loader/test/test-esm-specifiers.js @@ -0,0 +1,70 @@ +// Adapted from https://github.com/nodejs/node/blob/45f2258f74117e27ffced434a98ad6babc14f7fa/test/es-module/test-esm-specifiers.mjs + +import { match, strictEqual } from 'node:assert'; +import { join } from 'node:path'; +import { execPath } from 'node:process'; +import { describe, it } from 'node:test'; + +import { spawnPromisified } from './common.js'; + + +describe('ESM: specifier-resolution=node', { concurrency: true }, () => { + it(async () => { + const { code, stderr, stdout } = await spawnPromisified(execPath, [ + '--no-warnings', + '--loader=./loader.js', + '--input-type=module', + '--eval', + [ + 'import { strictEqual } from "node:assert";', + // commonJS index.js + `import commonjs from ${JSON.stringify('./test/fixtures/es-module-specifiers/package-type-commonjs')};`, + // esm index.js + `import module from ${JSON.stringify('./test/fixtures/es-module-specifiers/package-type-module')};`, + // Notice the trailing slash + `import success, { explicit, implicit, implicitModule } from ${JSON.stringify('./test/fixtures/es-module-specifiers/')};`, + 'strictEqual(commonjs, "commonjs");', + 'strictEqual(module, "module");', + 'strictEqual(success, "success");', + 'strictEqual(explicit, "esm");', + 'strictEqual(implicit, "cjs");', + 'strictEqual(implicitModule, "cjs");', + ].join('\n'), + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, ''); + strictEqual(code, 0); + }); + + it('should throw when the omitted file extension is .mjs (legacy loader doesn\'t support it)', async () => { + const { code, stderr, stdout } = await spawnPromisified(execPath, [ + '--no-warnings', + '--loader=./loader.js', + '--input-type=module', + '--eval', + `import whatever from ${JSON.stringify('./test/fixtures/es-module-specifiers/implicit-main-type-commonjs')};`, + ]); + + match(stderr, /ERR_MODULE_NOT_FOUND/); + strictEqual(stdout, ''); + strictEqual(code, 1); + }); + + for ( + const item of [ + 'package-type-commonjs', + 'package-type-module', + '/', + '/index', + ] + ) it('should ', async () => { + const { code } = await spawnPromisified(execPath, [ + '--no-warnings', + '--loader=./loader.js', + join('./test/fixtures/es-module-specifiers', item), + ]); + + strictEqual(code, 0); + }); +}); From ed9a48ee2823816916f429fbce9553f2e8d3dad4 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sat, 24 Sep 2022 22:08:08 -0700 Subject: [PATCH 5/5] Update loader to throw same error code as Node for module not found; follow experimental-specifier-resolution behavior of searching for .mjs extensions in addition to legacy CommonJS ones --- .../loader.js | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/commonjs-extension-resolution-loader/loader.js b/commonjs-extension-resolution-loader/loader.js index cdf0fc8..71ee813 100644 --- a/commonjs-extension-resolution-loader/loader.js +++ b/commonjs-extension-resolution-loader/loader.js @@ -24,11 +24,22 @@ export async function resolve(specifier, context, next) { } const parentPath = fileURLToPath(parentURL); - const resolution = await resolveAsync(specifier, { - basedir: dirname(parentPath), - extensions: ['.js', '.json', '.node'], - }); - const url = pathToFileURL(resolution).href; + let url; + try { + const resolution = await resolveAsync(specifier, { + basedir: dirname(parentPath), + // For whatever reason, --experimental-specifier-resolution=node doesn't search for .mjs extensions + // but it does search for index.mjs files within directories + extensions: ['.js', '.json', '.node', '.mjs'], + }); + url = pathToFileURL(resolution).href; + } catch (error) { + if (error.code === 'MODULE_NOT_FOUND') { + // Match Node's error code + error.code = 'ERR_MODULE_NOT_FOUND'; + } + throw error; + } return next(url, context); }