├── test ├── fixtures │ ├── no.js │ ├── abc.txt │ ├── file.load1 │ ├── file.load2 │ ├── foo │ │ ├── index.js │ │ └── package.json │ ├── yield │ │ ├── a │ │ │ ├── foo │ │ │ │ ├── a │ │ │ │ └── b │ │ │ └── foo-2 │ │ │ │ ├── b │ │ │ │ └── c │ │ ├── b │ │ │ └── foo │ │ │ │ └── a │ │ └── c │ │ │ └── foo │ │ │ ├── a │ │ │ └── package.json │ ├── extensions │ │ ├── foo.js │ │ ├── foo.ts │ │ ├── index.js │ │ ├── index.ts │ │ ├── dir │ │ │ ├── index.js │ │ │ └── index.ts │ │ ├── node_modules │ │ │ ├── module.js │ │ │ └── module │ │ │ │ └── index.ts │ │ └── package.json │ ├── imports-field │ │ ├── b.js │ │ ├── dir │ │ │ └── b.js │ │ ├── node_modules │ │ │ ├── c │ │ │ │ └── index.js │ │ │ └── a │ │ │ │ ├── lib │ │ │ │ ├── index.js │ │ │ │ ├── main.js │ │ │ │ ├── browser.js │ │ │ │ └── lib2 │ │ │ │ │ └── main.js │ │ │ │ ├── x.js │ │ │ │ ├── main.js │ │ │ │ └── package.json │ │ ├── a.js │ │ └── package.json │ ├── no#fragment │ │ └── # │ │ │ └── #.js │ ├── pnp │ │ ├── pkg │ │ │ ├── index.js │ │ │ ├── main.js │ │ │ ├── dir │ │ │ │ └── index.js │ │ │ ├── typescript │ │ │ │ └── index.ts │ │ │ ├── package-alias │ │ │ │ ├── browser.js │ │ │ │ └── index.js │ │ │ └── package.json │ │ └── pkg3 │ │ │ ├── a.js │ │ │ └── package.json │ ├── shortcutdir.js │ │ └── a.js │ ├── decorated-fs │ │ └── exists.js │ ├── extension-alias │ │ ├── index.js │ │ ├── index.ts │ │ ├── dir │ │ │ ├── index.js │ │ │ └── index.ts │ │ ├── dir2 │ │ │ ├── index.js │ │ │ └── index.mts │ │ ├── index.mjs │ │ └── index.mts.js │ ├── main-field-self │ │ ├── index.js │ │ └── package.json │ ├── browser-module │ │ ├── lib │ │ │ ├── browser.js │ │ │ ├── ignore.js │ │ │ ├── main.js │ │ │ ├── replaced.js │ │ │ └── toString.js │ │ ├── browser │ │ │ └── module-a.js │ │ ├── node_modules │ │ │ ├── module-a.js │ │ │ ├── module-b.js │ │ │ └── module-c.js │ │ └── package.json │ ├── incorrect-package │ │ ├── pack1 │ │ │ ├── a.js │ │ │ └── package.json │ │ └── pack2 │ │ │ ├── a.js │ │ │ └── package.json │ ├── issue-238 │ │ ├── src │ │ │ ├── a │ │ │ │ └── config.js │ │ │ └── common │ │ │ │ └── config │ │ │ │ └── myObjectFile.js │ │ └── package.json │ ├── main-field-self2 │ │ ├── index.js │ │ └── package.json │ ├── directory-default │ │ └── directory-default.js │ ├── node_modules │ │ ├── recursive-module │ │ │ ├── file.js │ │ │ └── index.js │ │ ├── invalidPackageJson │ │ │ └── package.json │ │ ├── m2 │ │ │ └── b.js │ │ ├── complexm │ │ │ ├── step2.js │ │ │ ├── node_modules │ │ │ │ └── m1 │ │ │ │ │ ├── a.js │ │ │ │ │ └── index.js │ │ │ └── step1.js │ │ └── m1 │ │ │ ├── a.js │ │ │ └── b.js │ ├── prefer-pnp │ │ └── alternative-modules │ │ │ └── m1 │ │ │ └── b.js │ ├── restrictions │ │ └── node_modules │ │ │ ├── pck1 │ │ │ ├── index.js │ │ │ ├── index.css │ │ │ └── package.json │ │ │ └── pck2 │ │ │ ├── index.css │ │ │ ├── module.js │ │ │ └── package.json │ ├── dirOrFile.js │ ├── exports-field │ │ ├── a.js │ │ ├── node_modules │ │ │ ├── exports-field │ │ │ │ ├── lib │ │ │ │ │ ├── index.js │ │ │ │ │ ├── browser.js │ │ │ │ │ ├── main.js │ │ │ │ │ └── lib2 │ │ │ │ │ │ └── main.js │ │ │ │ ├── x.js │ │ │ │ ├── main.js │ │ │ │ └── package.json │ │ │ └── invalid-exports-field │ │ │ │ ├── index.js │ │ │ │ ├── umd.js │ │ │ │ └── package.json │ │ └── package.json │ ├── scoped │ │ └── node_modules │ │ │ └── @scope │ │ │ ├── pack1 │ │ │ ├── main.js │ │ │ └── package.json │ │ │ └── pack2 │ │ │ ├── main.js │ │ │ ├── lib │ │ │ └── index.js │ │ │ └── package.json │ ├── dirOrFile │ │ └── index.js │ ├── exports-field-error │ │ └── node_modules │ │ │ ├── pack1 │ │ │ └── index.js │ │ │ └── exports-field │ │ │ └── package.json │ ├── exports-field3 │ │ └── node_modules │ │ │ └── exports-field │ │ │ ├── index │ │ │ ├── main.js │ │ │ └── package.json │ ├── imports-field-different │ │ ├── a.js │ │ └── package.json │ ├── lib │ │ └── complex1.js │ ├── imports-exports-wildcard │ │ └── node_modules │ │ │ └── m │ │ │ ├── src │ │ │ ├── features │ │ │ │ ├── f.js │ │ │ │ ├── y │ │ │ │ │ └── y.js │ │ │ │ └── internal │ │ │ │ │ └── file.js │ │ │ ├── internal │ │ │ │ └── i.js │ │ │ ├── middle-1 │ │ │ │ ├── f.js │ │ │ │ └── nested │ │ │ │ │ └── f.js │ │ │ ├── middle-4 │ │ │ │ └── f │ │ │ │ │ └── f.js │ │ │ ├── middle-5 │ │ │ │ ├── f │ │ │ │ │ └── $.js │ │ │ │ └── f$ │ │ │ │ │ └── $.js │ │ │ ├── middle │ │ │ │ ├── f.js │ │ │ │ └── nested │ │ │ │ │ └── f.js │ │ │ ├── middle-3 │ │ │ │ └── nested │ │ │ │ │ └── f │ │ │ │ │ └── nested │ │ │ │ │ └── f.js │ │ │ └── middle-2 │ │ │ │ └── nested │ │ │ │ └── f.js │ │ │ └── package.json │ ├── imports-slash-pattern │ │ ├── src │ │ │ ├── utils.js │ │ │ └── nested │ │ │ │ └── deep.js │ │ └── package.json │ ├── exports-field-invalid-package-target │ │ ├── a.js │ │ └── package.json │ ├── a.js │ ├── b.js │ ├── exports-field2 │ │ └── node_modules │ │ │ └── exports-field │ │ │ ├── index.js │ │ │ ├── main.js │ │ │ ├── lib │ │ │ ├── main.js │ │ │ ├── browser.js │ │ │ └── lib2 │ │ │ │ └── main.js │ │ │ └── package.json │ ├── exports-field-and-extension-alias │ │ └── node_modules │ │ │ ├── pkg │ │ │ ├── dist │ │ │ │ ├── string.test.d.ts │ │ │ │ └── string.js │ │ │ └── package.json │ │ │ └── @org │ │ │ └── pkg │ │ │ ├── dist │ │ │ ├── string.test.d.ts │ │ │ └── string.js │ │ │ └── package.json │ ├── c.js │ ├── pnp-a │ │ └── m2 │ │ │ └── a.js │ ├── main3.js │ ├── multiple_modules │ │ └── node_modules │ │ │ └── m1 │ │ │ └── a.js │ ├── main1.js │ ├── main2.js │ └── complex.js ├── pr-53.test.js ├── getPaths.test.js ├── __snapshots__ │ ├── exportsField.test.js.snap │ ├── importsField.test.js.snap │ ├── alias.test.js.snap │ ├── fallback.test.js.snap │ └── restrictions.test.js.snap ├── simple.test.js ├── scoped-packages.test.js ├── forEachBail.test.js ├── plugins.test.js ├── incorrect-description-file.test.js ├── identifier.test.js ├── SyncAsyncFileSystemDecorator.test.js ├── browserField.test.js ├── extension-alias.test.js ├── dependencies.test.js ├── missing.test.js ├── restrictions.test.js ├── extensions.test.js ├── roots.test.js ├── fallback.test.js ├── fullSpecified.test.js ├── unsafe-cache.test.js └── alias.test.js ├── .husky └── pre-commit ├── tsconfig.types.json ├── .prettierignore ├── generate-types-config.js ├── jest.config.js ├── .gitignore ├── lib ├── util │ ├── module-browser.js │ ├── process-browser.js │ ├── memoize.js │ ├── identifier.js │ └── path.js ├── ModulesInHierachicDirectoriesPlugin.js ├── NextPlugin.js ├── TryNextPlugin.js ├── ResultPlugin.js ├── createInnerContext.js ├── forEachBail.js ├── getInnerRequest.js ├── ModulesInRootPlugin.js ├── getPaths.js ├── AppendPlugin.js ├── JoinRequestPlugin.js ├── UseFilePlugin.js ├── CloneBasenamePlugin.js ├── LogInfoPlugin.js ├── FileExistsPlugin.js ├── RootsPlugin.js ├── ConditionalPlugin.js ├── DirectoryExistsPlugin.js ├── RestrictionsPlugin.js ├── JoinRequestPartPlugin.js ├── SelfReferencePlugin.js ├── ParsePlugin.js ├── MainFieldPlugin.js ├── ModulesInHierarchicalDirectoriesPlugin.js ├── DescriptionFilePlugin.js ├── SymlinkPlugin.js ├── ExtensionAliasPlugin.js ├── AliasFieldPlugin.js ├── UnsafeCachePlugin.js ├── PnpPlugin.js ├── AliasPlugin.js ├── DescriptionFileUtils.js ├── ExportsFieldPlugin.js └── SyncAsyncFileSystemDecorator.js ├── eslint.config.mjs ├── tsconfig.types.test.json ├── .editorconfig ├── tsconfig.json ├── .prettierrc.js ├── .cspell.json ├── LICENSE ├── .github └── workflows │ └── test.yml └── package.json /test/fixtures/no.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/abc.txt: -------------------------------------------------------------------------------- 1 | abc -------------------------------------------------------------------------------- /test/fixtures/file.load1: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/file.load2: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/foo/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/yield/a/foo/a: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/yield/a/foo/b: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/yield/b/foo/a: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/yield/c/foo/a: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/extensions/foo.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/extensions/foo.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/extensions/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/extensions/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/imports-field/b.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/no#fragment/#/#.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/pnp/pkg/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/pnp/pkg/main.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/shortcutdir.js/a.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/yield/a/foo-2/b: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/yield/a/foo-2/c: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/decorated-fs/exists.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/extension-alias/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/extension-alias/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/extensions/dir/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/extensions/dir/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/imports-field/dir/b.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/main-field-self/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/pnp/pkg/dir/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/browser-module/lib/browser.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/browser-module/lib/ignore.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/browser-module/lib/main.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/extension-alias/dir/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/extension-alias/dir/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/extension-alias/dir2/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/extension-alias/index.mjs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/extension-alias/index.mts.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/incorrect-package/pack1/a.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/incorrect-package/pack2/a.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/issue-238/src/a/config.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/main-field-self2/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/pnp/pkg/typescript/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/browser-module/browser/module-a.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/browser-module/lib/replaced.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/browser-module/lib/toString.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/extension-alias/dir2/index.mts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/extensions/node_modules/module.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/pnp/pkg/package-alias/browser.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/pnp/pkg/package-alias/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npx --no-install lint-staged 2 | -------------------------------------------------------------------------------- /test/fixtures/browser-module/node_modules/module-a.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/browser-module/node_modules/module-b.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/browser-module/node_modules/module-c.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/directory-default/directory-default.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/extensions/node_modules/module/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/imports-field/node_modules/c/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/incorrect-package/pack2/package.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/node_modules/recursive-module/file.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/node_modules/recursive-module/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/pnp/pkg3/a.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /test/fixtures/prefer-pnp/alternative-modules/m1/b.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/restrictions/node_modules/pck1/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/dirOrFile.js: -------------------------------------------------------------------------------- 1 | module.exports = "file"; 2 | -------------------------------------------------------------------------------- /test/fixtures/exports-field/a.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /test/fixtures/imports-field/a.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /test/fixtures/imports-field/node_modules/a/lib/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/issue-238/src/common/config/myObjectFile.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/node_modules/invalidPackageJson/package.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/restrictions/node_modules/pck1/index.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/restrictions/node_modules/pck2/index.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/restrictions/node_modules/pck2/module.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/scoped/node_modules/@scope/pack1/main.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/scoped/node_modules/@scope/pack2/main.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/dirOrFile/index.js: -------------------------------------------------------------------------------- 1 | module.exports = "dir"; 2 | -------------------------------------------------------------------------------- /test/fixtures/exports-field-error/node_modules/pack1/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/exports-field3/node_modules/exports-field/index: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/scoped/node_modules/@scope/pack2/lib/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tsconfig.types.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/exports-field/node_modules/exports-field/lib/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/imports-field-different/a.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /test/fixtures/lib/complex1.js: -------------------------------------------------------------------------------- 1 | module.exports = "lib complex1"; 2 | -------------------------------------------------------------------------------- /test/fixtures/exports-field/node_modules/invalid-exports-field/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/exports-field/node_modules/invalid-exports-field/umd.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/imports-exports-wildcard/node_modules/m/src/features/f.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/imports-exports-wildcard/node_modules/m/src/features/y/y.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/imports-exports-wildcard/node_modules/m/src/internal/i.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/imports-exports-wildcard/node_modules/m/src/middle-1/f.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/imports-exports-wildcard/node_modules/m/src/middle-4/f/f.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/imports-exports-wildcard/node_modules/m/src/middle-5/f/$.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/imports-exports-wildcard/node_modules/m/src/middle/f.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/imports-field/node_modules/a/x.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /test/fixtures/issue-238/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/main-field-self/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "./" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/main-field-self2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "." 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/node_modules/m2/b.js: -------------------------------------------------------------------------------- 1 | module.exports = "This is m2/b"; 2 | -------------------------------------------------------------------------------- /test/fixtures/extensions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "./index.js" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/imports-exports-wildcard/node_modules/m/src/middle-1/nested/f.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/imports-exports-wildcard/node_modules/m/src/middle-5/f$/$.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/imports-exports-wildcard/node_modules/m/src/middle/nested/f.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/imports-field/node_modules/a/lib/main.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /test/fixtures/imports-field/node_modules/a/main.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /test/fixtures/imports-slash-pattern/src/utils.js: -------------------------------------------------------------------------------- 1 | module.exports = "utils"; 2 | -------------------------------------------------------------------------------- /test/fixtures/node_modules/complexm/step2.js: -------------------------------------------------------------------------------- 1 | module.exports = "Step2"; 2 | -------------------------------------------------------------------------------- /test/fixtures/exports-field-invalid-package-target/a.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /test/fixtures/exports-field/node_modules/exports-field/x.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /test/fixtures/imports-exports-wildcard/node_modules/m/src/features/internal/file.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/imports-field/node_modules/a/lib/browser.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /test/fixtures/imports-field/node_modules/a/lib/lib2/main.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /test/fixtures/imports-slash-pattern/src/nested/deep.js: -------------------------------------------------------------------------------- 1 | module.exports = "deep"; 2 | -------------------------------------------------------------------------------- /test/fixtures/incorrect-package/pack1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "./a.js", 3 | -------------------------------------------------------------------------------- /test/fixtures/a.js: -------------------------------------------------------------------------------- 1 | module.exports = function a() { 2 | return "This is a"; 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/b.js: -------------------------------------------------------------------------------- 1 | module.exports = function b() { 2 | return "This is b"; 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/exports-field/node_modules/exports-field/main.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /test/fixtures/exports-field2/node_modules/exports-field/index.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /test/fixtures/exports-field2/node_modules/exports-field/main.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /test/fixtures/exports-field3/node_modules/exports-field/main.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /test/fixtures/foo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "foo", 3 | "version": "1.0.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/imports-exports-wildcard/node_modules/m/src/middle-3/nested/f/nested/f.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/exports-field/node_modules/exports-field/lib/browser.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /test/fixtures/exports-field/node_modules/exports-field/lib/main.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /test/fixtures/exports-field2/node_modules/exports-field/lib/main.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /test/fixtures/exports-field/node_modules/exports-field/lib/lib2/main.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /test/fixtures/exports-field2/node_modules/exports-field/lib/browser.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /test/fixtures/exports-field2/node_modules/exports-field/lib/lib2/main.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /test/fixtures/node_modules/complexm/node_modules/m1/a.js: -------------------------------------------------------------------------------- 1 | module.exports = "the correct a.js"; 2 | -------------------------------------------------------------------------------- /test/fixtures/node_modules/complexm/step1.js: -------------------------------------------------------------------------------- 1 | module.exports = require("m1/a") + require("m1"); 2 | -------------------------------------------------------------------------------- /test/fixtures/restrictions/node_modules/pck1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "index.js" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/scoped/node_modules/@scope/pack2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "./main.js" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/exports-field-and-extension-alias/node_modules/pkg/dist/string.test.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | test/**/*.* 3 | !test/*.js 4 | 5 | # Generated files 6 | types.d.ts 7 | -------------------------------------------------------------------------------- /test/fixtures/c.js: -------------------------------------------------------------------------------- 1 | module.exports = function b() { 2 | require("./a"); 3 | return "This is c"; 4 | }; 5 | -------------------------------------------------------------------------------- /test/fixtures/exports-field-and-extension-alias/node_modules/@org/pkg/dist/string.test.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /test/fixtures/exports-field-and-extension-alias/node_modules/pkg/dist/string.js: -------------------------------------------------------------------------------- 1 | export default "string"; 2 | -------------------------------------------------------------------------------- /test/fixtures/node_modules/m1/a.js: -------------------------------------------------------------------------------- 1 | module.exports = function a() { 2 | return "This is m1/a"; 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/node_modules/m1/b.js: -------------------------------------------------------------------------------- 1 | module.exports = function a() { 2 | return "This is m1/b"; 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/pnp-a/m2/a.js: -------------------------------------------------------------------------------- 1 | module.exports = function a() { 2 | return "This is nested m1/a"; 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/exports-field-and-extension-alias/node_modules/@org/pkg/dist/string.js: -------------------------------------------------------------------------------- 1 | export default "string"; 2 | -------------------------------------------------------------------------------- /test/fixtures/node_modules/complexm/node_modules/m1/index.js: -------------------------------------------------------------------------------- 1 | module.exports = " :) " + require("m2/b.js"); 2 | -------------------------------------------------------------------------------- /test/fixtures/imports-exports-wildcard/node_modules/m/src/middle-2/nested/f.js: -------------------------------------------------------------------------------- 1 | module.exports = { nested: "nested" } 2 | -------------------------------------------------------------------------------- /test/fixtures/main3.js: -------------------------------------------------------------------------------- 1 | var a = require("./a"); 2 | require.ensure([], function(require) { 3 | require("./c.js"); 4 | }); 5 | -------------------------------------------------------------------------------- /test/fixtures/multiple_modules/node_modules/m1/a.js: -------------------------------------------------------------------------------- 1 | module.exports = function a() { 2 | return "This is nested m1/a"; 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/scoped/node_modules/@scope/pack1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "browser": { 3 | "./index.js": "./main.js" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/exports-field/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@exports-field/core", 3 | "version": "1.0.0", 4 | "exports": "./a.js" 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/imports-slash-pattern/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "imports-slash-pattern", 3 | "imports": { 4 | "#/*": "./src/*" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/yield/c/foo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "foo", 3 | "version": "1.0.0", 4 | "browser": { 5 | "./a": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/restrictions/node_modules/pck2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "../../../c.js", 3 | "module": "module.js", 4 | "style": "index.css" 5 | } 6 | -------------------------------------------------------------------------------- /generate-types-config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | typeMapping: { 5 | "^signal in Abortable events Interface": "AbortSignal", 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | prettierPath: require.resolve("prettier-2"), 5 | moduleFileExtensions: ["js", "mjs", "cjs", "ts"], 6 | }; 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /coverage 3 | /.nyc_output 4 | *.log 5 | .eslintcache 6 | .cspellcache 7 | 8 | # IDE 9 | .idea 10 | .vscode 11 | 12 | .DS_Store 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /test/fixtures/pnp/pkg3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@exports-field/core", 3 | "version": "1.0.0", 4 | "exports": { 5 | ".": "./a.js", 6 | "./x": "./a.js" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lib/util/module-browser.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /test/fixtures/exports-field3/node_modules/exports-field/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "exports-field", 3 | "exportsField": { 4 | "exports": "./main.js" 5 | }, 6 | "ex": "./index" 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/pnp/pkg/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "main.js", 3 | "browser": { 4 | "./package-alias/index.js": "./package-alias/browser.js", 5 | "module": "pkg/dir/index" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "eslint/config"; 2 | import config from "eslint-config-webpack"; 3 | 4 | export default defineConfig([ 5 | { 6 | extends: [config], 7 | }, 8 | ]); 9 | -------------------------------------------------------------------------------- /test/fixtures/exports-field-error/node_modules/exports-field/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "exports-field", 3 | "version": "1.0.0", 4 | "exports": { 5 | ".": "./a/../b/../../pack1/index.js" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/main1.js: -------------------------------------------------------------------------------- 1 | var a = require("./a"); 2 | if (x) { 3 | for (var i = 0; i < 100; i++) { 4 | while (true) require("./b"); 5 | do { 6 | i++; 7 | } while (require("m1/a")()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/exports-field-and-extension-alias/node_modules/@org/pkg/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@org/pkg", 3 | "exports": { 4 | "./*.js": { 5 | "types": "./dist/*.d.ts", 6 | "default": "./dist/*.js" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/exports-field-and-extension-alias/node_modules/pkg/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@raviqqe/hidash", 3 | "exports": { 4 | "./*.js": { 5 | "types": "./dist/*.d.ts", 6 | "default": "./dist/*.js" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/main2.js: -------------------------------------------------------------------------------- 1 | var a = require("./a"); 2 | with (x) { 3 | switch (a) { 4 | case 1: 5 | require("./b"); 6 | default: 7 | require.ensure(["m1/a"], function() { 8 | var a = require("m1/a"), 9 | b = require("m1/b"); 10 | }); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/ModulesInHierachicDirectoriesPlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | // TODO remove in next major 9 | module.exports = require("./ModulesInHierarchicalDirectoriesPlugin"); 10 | -------------------------------------------------------------------------------- /tsconfig.types.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "compilerOptions": { 4 | "strict": false, 5 | "noImplicitThis": true, 6 | "alwaysStrict": true, 7 | "strictNullChecks": true, 8 | "types": ["node", "jest"] 9 | }, 10 | "include": ["test/*.js"] 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | max_line_length = 80 10 | 11 | [*.{yml,yaml,json}] 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /test/fixtures/exports-field/node_modules/invalid-exports-field/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "exports-field", 3 | "version": "1.0.0", 4 | "main": "./main.js", 5 | "browser": { 6 | "./lib/main.js": "./lib/browser.js", 7 | "./dist/main.js": "./lib/browser.js" 8 | }, 9 | "exports": { 10 | ".": "./index.js", 11 | "umd": "./umd.js" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "module": "commonjs", 5 | "lib": ["es2018"], 6 | "allowJs": true, 7 | "checkJs": true, 8 | "noEmit": true, 9 | "strict": true, 10 | "alwaysStrict": true, 11 | "types": ["node"], 12 | "esModuleInterop": true 13 | }, 14 | "include": ["lib/**/*.js"] 15 | } 16 | -------------------------------------------------------------------------------- /test/fixtures/imports-field/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "imports-field", 3 | "version": "1.0.0", 4 | "exports": "./a.js", 5 | "imports": { 6 | "#imports-field": "./b.js", 7 | "#b": "../b.js", 8 | "#ccc/": "c/", 9 | "#c": "c", 10 | "#a/": "a/" 11 | }, 12 | "other": { 13 | "imports": { 14 | "#b": "./a.js" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | printWidth: 80, 5 | useTabs: true, 6 | tabWidth: 2, 7 | trailingComma: "all", 8 | arrowParens: "always", 9 | overrides: [ 10 | { 11 | files: "*.json", 12 | options: { 13 | parser: "json", 14 | useTabs: false, 15 | }, 16 | }, 17 | { 18 | files: "*.{cts,mts,ts}", 19 | options: { 20 | parser: "typescript", 21 | }, 22 | }, 23 | ], 24 | }; 25 | -------------------------------------------------------------------------------- /test/fixtures/complex.js: -------------------------------------------------------------------------------- 1 | var complex1 = require("./lib/complex1"); 2 | require.ensure(["./lib/complex1", "complexm/step2"], function(require) { 3 | require("./lib/complex1"); 4 | var a = function() {}; 5 | require.ensure(["complexm/step1"], function(require) { 6 | require("./lib/complex1"); 7 | var s1 = require("complexm/step1"); 8 | var s2 = require("complexm/step2"); 9 | console.log(s1); 10 | console.log(s2); 11 | }); 12 | }); 13 | console.log(complex1); 14 | -------------------------------------------------------------------------------- /test/fixtures/exports-field2/node_modules/exports-field/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "exports-field", 3 | "version": "1.0.0", 4 | "main": "./main.js", 5 | "browser": { 6 | "./lib/main.js": "./lib/browser.js", 7 | "./dist/main.js": "./lib/browser.js" 8 | }, 9 | "exports": { 10 | ".": "./index.js", 11 | "./dist/": { 12 | "webpack": ["./lib/", "./lib/lib2/"], 13 | "node": "./lib/", 14 | "default": "./lib/" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/fixtures/imports-field/node_modules/a/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "a", 3 | "version": "1.0.0", 4 | "main": "./main.js", 5 | "browser": { 6 | "./lib/main.js": "./lib/browser.js", 7 | "./dist/main.js": "./lib/browser.js" 8 | }, 9 | "exports": { 10 | ".": "./x.js", 11 | "./dist/": { 12 | "webpack": ["./lib/", "./lib/lib2/"], 13 | "node": "./lib/", 14 | "default": "./lib/" 15 | }, 16 | "./dist/a.js": "./../../a.js" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/fixtures/exports-field/node_modules/exports-field/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "exports-field", 3 | "version": "1.0.0", 4 | "main": "./main.js", 5 | "browser": { 6 | "./lib/main.js": "./lib/browser.js", 7 | "./dist/main.js": "./lib/browser.js" 8 | }, 9 | "exports": { 10 | ".": "./x.js", 11 | "./dist/": { 12 | "custom": ["./lib/lib2/", "./lib/"], 13 | "webpack": ["./lib/", "./lib/lib2/"], 14 | "node": "./lib/", 15 | "default": "./lib/" 16 | }, 17 | "./dist/a.js": "./../../a.js" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/fixtures/browser-module/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "browser": { 3 | "./lib/ignore.js": false, 4 | "./lib/replaced.js": "./lib/browser", 5 | "module-a": "./browser/module-a.js", 6 | "module-b": "module-c", 7 | "./toString": "./lib/toString.js", 8 | ".": false 9 | }, 10 | "innerBrowser1": { 11 | "field": { 12 | "browser": { 13 | "./lib/main1.js": "./lib/main.js" 14 | } 15 | } 16 | }, 17 | "innerBrowser2": { 18 | "browser": { 19 | "./lib/main2.js": "./lib/replaced.js" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/pr-53.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { CachedInputFileSystem } = require("../"); 4 | 5 | describe("pr-53", () => { 6 | it("should allow to readJsonSync in CachedInputFileSystem", () => { 7 | const cfs = new CachedInputFileSystem( 8 | { 9 | // @ts-expect-error for tests 10 | readFileSync(path) { 11 | return JSON.stringify(`abc${path}`); 12 | }, 13 | }, 14 | 1000, 15 | ); 16 | if (!cfs.readJsonSync) throw new Error("readJsonSync must be available"); 17 | expect(cfs.readJsonSync("xyz")).toBe("abcxyz"); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /lib/util/process-browser.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | module.exports = { 9 | /** 10 | * @type {Record} 11 | */ 12 | versions: {}, 13 | // eslint-disable-next-line jsdoc/reject-function-type 14 | /** @param {Function} fn function */ 15 | nextTick(fn) { 16 | // eslint-disable-next-line prefer-rest-params 17 | const args = Array.prototype.slice.call(arguments, 1); 18 | Promise.resolve().then(() => { 19 | // eslint-disable-next-line prefer-spread 20 | fn.apply(null, args); 21 | }); 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /test/fixtures/imports-exports-wildcard/node_modules/m/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "m", 3 | "exports": { 4 | "./features-no-ext/*": "./src/features/*", 5 | "./features/*.js": "./src/features/*.js", 6 | "./features/internal/*": null, 7 | "./middle/nested/f.js": "./src/middle/nested/f.js", 8 | "./middle-1/nested/*.js": "./src/middle-1/nested/*.js", 9 | "./middle-2/*/f.js": "./src/middle-2/*/f.js", 10 | "./middle-3/*": "./src/middle-3/*/*.js", 11 | "./middle-4/*/nested": "./src/middle-4/*/*.js", 12 | "./middle-5/*/$": "./src/middle-5/*/$.js" 13 | }, 14 | "imports": { 15 | "#internal/*.js": "./src/internal/*.js" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/getPaths.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const getPaths = require("../lib/getPaths"); 4 | 5 | /** 6 | * @type {[string,{paths: string[], segments: string[]}][]} 7 | */ 8 | const cases = [ 9 | ["/a", { paths: ["/a", "/"], segments: ["a", "/"] }], 10 | ["/a/", { paths: ["/a/", "/a", "/"], segments: ["", "a", "/"] }], 11 | ["/a/b", { paths: ["/a/b", "/a", "/"], segments: ["b", "a", "/"] }], 12 | [ 13 | "/a/b/", 14 | { paths: ["/a/b/", "/a/b", "/a", "/"], segments: ["", "b", "a", "/"] }, 15 | ], 16 | ["/", { paths: ["/"], segments: [""] }], 17 | ]; 18 | 19 | describe("get paths", () => { 20 | for (const case_ of cases) { 21 | it(case_[0], () => { 22 | const { paths, segments } = getPaths(case_[0]); 23 | expect(paths).toEqual(case_[1].paths); 24 | expect(segments).toEqual(case_[1].segments); 25 | }); 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /.cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2", 3 | "words": [ 4 | "abcxyz", 5 | "addrs", 6 | "abortable", 7 | "anotherhashishere", 8 | "arcanis", 9 | "Builtins", 10 | "camelcase", 11 | "complexm", 12 | "endregion", 13 | "entrypoints", 14 | "filesystems", 15 | "hashishere", 16 | "jsons", 17 | "Kopeykin", 18 | "Koppers", 19 | "Maël", 20 | "memfs", 21 | "mergeable", 22 | "Nison", 23 | "opensource", 24 | "pjson", 25 | "pnpapi", 26 | "sokra", 27 | "subpaths", 28 | "tapable", 29 | "undescribed", 30 | "unreviewed", 31 | "vankop", 32 | "wpck", 33 | "zapp", 34 | "zipp", 35 | "zippi", 36 | "zizizi", 37 | "codecov" 38 | ], 39 | "ignorePaths": [ 40 | "package.json", 41 | "yarn.lock", 42 | "coverage", 43 | "*.log", 44 | ".gitignore" 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /lib/util/memoize.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | /** 9 | * @template T 10 | * @typedef {() => T} FunctionReturning 11 | */ 12 | 13 | /** 14 | * @template T 15 | * @param {FunctionReturning} fn memorized function 16 | * @returns {FunctionReturning} new function 17 | */ 18 | const memoize = (fn) => { 19 | let cache = false; 20 | /** @type {T | undefined} */ 21 | let result; 22 | return () => { 23 | if (cache) { 24 | return /** @type {T} */ (result); 25 | } 26 | 27 | result = fn(); 28 | cache = true; 29 | // Allow to clean up memory for fn 30 | // and all dependent resources 31 | /** @type {FunctionReturning | undefined} */ 32 | (fn) = undefined; 33 | return /** @type {T} */ (result); 34 | }; 35 | }; 36 | 37 | module.exports = memoize; 38 | -------------------------------------------------------------------------------- /lib/NextPlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | /** @typedef {import("./Resolver")} Resolver */ 9 | /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ 10 | 11 | module.exports = class NextPlugin { 12 | /** 13 | * @param {string | ResolveStepHook} source source 14 | * @param {string | ResolveStepHook} target target 15 | */ 16 | constructor(source, target) { 17 | this.source = source; 18 | this.target = target; 19 | } 20 | 21 | /** 22 | * @param {Resolver} resolver the resolver 23 | * @returns {void} 24 | */ 25 | apply(resolver) { 26 | const target = resolver.ensureHook(this.target); 27 | resolver 28 | .getHook(this.source) 29 | .tapAsync("NextPlugin", (request, resolveContext, callback) => { 30 | resolver.doResolve(target, request, null, resolveContext, callback); 31 | }); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /test/__snapshots__/exportsField.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`exportsFieldPlugin should log the correct info 1`] = ` 4 | Array [ 5 | "resolve 'exports-field/dist/browser.js' in '...'", 6 | " Parsed request is a module", 7 | " using description file: .../package.json (relative path: .)", 8 | " resolve as module", 9 | " looking for modules in .../node_modules", 10 | " existing directory .../node_modules/exports-field", 11 | " using description file: .../node_modules/exports-field/package.json (relative path: .)", 12 | " using exports field: ./lib/browser.js", 13 | " using description file: .../node_modules/exports-field/package.json (relative path: ./lib/browser.js)", 14 | " existing file: .../node_modules/exports-field/lib/browser.js", 15 | " reporting result .../node_modules/exports-field/lib/browser.js", 16 | ] 17 | `; 18 | -------------------------------------------------------------------------------- /lib/TryNextPlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | /** @typedef {import("./Resolver")} Resolver */ 9 | /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ 10 | 11 | module.exports = class TryNextPlugin { 12 | /** 13 | * @param {string | ResolveStepHook} source source 14 | * @param {string} message message 15 | * @param {string | ResolveStepHook} target target 16 | */ 17 | constructor(source, message, target) { 18 | this.source = source; 19 | this.message = message; 20 | this.target = target; 21 | } 22 | 23 | /** 24 | * @param {Resolver} resolver the resolver 25 | * @returns {void} 26 | */ 27 | apply(resolver) { 28 | const target = resolver.ensureHook(this.target); 29 | resolver 30 | .getHook(this.source) 31 | .tapAsync("TryNextPlugin", (request, resolveContext, callback) => { 32 | resolver.doResolve( 33 | target, 34 | request, 35 | this.message, 36 | resolveContext, 37 | callback, 38 | ); 39 | }); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright JS Foundation and other contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | 'Software'), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /test/__snapshots__/importsField.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`importsFieldPlugin should log the correct info 1`] = ` 4 | Array [ 5 | "resolve '#a/dist/index.js' in '...'", 6 | " using description file: .../package.json (relative path: .)", 7 | " resolve as internal import", 8 | " using imports field: a/dist/index.js", 9 | " Parsed request is a module", 10 | " using description file: .../package.json (relative path: .)", 11 | " resolve as module", 12 | " looking for modules in .../node_modules", 13 | " existing directory .../node_modules/a", 14 | " using description file: .../node_modules/a/package.json (relative path: .)", 15 | " using exports field: ./lib/index.js", 16 | " using description file: .../node_modules/a/package.json (relative path: ./lib/index.js)", 17 | " no extension", 18 | " existing file: .../node_modules/a/lib/index.js", 19 | " reporting result .../node_modules/a/lib/index.js", 20 | ] 21 | `; 22 | -------------------------------------------------------------------------------- /lib/ResultPlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | /** @typedef {import("./Resolver")} Resolver */ 9 | /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ 10 | 11 | module.exports = class ResultPlugin { 12 | /** 13 | * @param {ResolveStepHook} source source 14 | */ 15 | constructor(source) { 16 | this.source = source; 17 | } 18 | 19 | /** 20 | * @param {Resolver} resolver the resolver 21 | * @returns {void} 22 | */ 23 | apply(resolver) { 24 | this.source.tapAsync( 25 | "ResultPlugin", 26 | (request, resolverContext, callback) => { 27 | const obj = { ...request }; 28 | if (resolverContext.log) { 29 | resolverContext.log(`reporting result ${obj.path}`); 30 | } 31 | resolver.hooks.result.callAsync(obj, resolverContext, (err) => { 32 | if (err) return callback(err); 33 | if (typeof resolverContext.yield === "function") { 34 | resolverContext.yield(obj); 35 | callback(null, null); 36 | } else { 37 | callback(null, obj); 38 | } 39 | }); 40 | }, 41 | ); 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /lib/createInnerContext.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | /** @typedef {import("./Resolver").ResolveContext} ResolveContext */ 9 | 10 | /** 11 | * @param {ResolveContext} options options for inner context 12 | * @param {null|string} message message to log 13 | * @returns {ResolveContext} inner context 14 | */ 15 | module.exports = function createInnerContext(options, message) { 16 | let messageReported = false; 17 | let innerLog; 18 | if (options.log) { 19 | if (message) { 20 | /** 21 | * @param {string} msg message 22 | */ 23 | innerLog = (msg) => { 24 | if (!messageReported) { 25 | /** @type {((str: string) => void)} */ 26 | (options.log)(message); 27 | messageReported = true; 28 | } 29 | 30 | /** @type {((str: string) => void)} */ 31 | (options.log)(` ${msg}`); 32 | }; 33 | } else { 34 | innerLog = options.log; 35 | } 36 | } 37 | 38 | return { 39 | log: innerLog, 40 | yield: options.yield, 41 | fileDependencies: options.fileDependencies, 42 | contextDependencies: options.contextDependencies, 43 | missingDependencies: options.missingDependencies, 44 | stack: options.stack, 45 | }; 46 | }; 47 | -------------------------------------------------------------------------------- /test/simple.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const path = require("path"); 4 | const resolve = require("../"); 5 | 6 | describe("simple", () => { 7 | const pathsToIt = [ 8 | [__dirname, "../lib/index", "direct"], 9 | [__dirname, "..", "as directory"], 10 | [path.join(__dirname, "..", ".."), "./enhanced-resolve", "as module"], 11 | [ 12 | path.join(__dirname, "..", ".."), 13 | "./enhanced-resolve/lib/index", 14 | "in module", 15 | ], 16 | ]; 17 | for (const pathToIt of pathsToIt) { 18 | it(`should resolve itself ${pathToIt[2]}`, (done) => { 19 | resolve(pathToIt[0], pathToIt[1], (err, filename) => { 20 | if (err) { 21 | return done( 22 | new Error([err.message, err.stack, err.details].join("\n")), 23 | ); 24 | } 25 | 26 | expect(filename).toBeDefined(); 27 | expect(typeof filename).toBe("string"); 28 | expect(filename).toEqual(path.join(__dirname, "..", "lib", "index.js")); 29 | done(); 30 | }); 31 | }); 32 | 33 | it(`should resolve itself sync ${pathToIt[2]}`, () => { 34 | const filename = resolve.sync(pathToIt[0], pathToIt[1]); 35 | 36 | expect(filename).toBeDefined(); 37 | expect(typeof filename).toBe("string"); 38 | expect(filename).toEqual(path.join(__dirname, "..", "lib", "index.js")); 39 | }); 40 | } 41 | }); 42 | -------------------------------------------------------------------------------- /test/fixtures/imports-field-different/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@exports-field/bad-specifier", 3 | "version": "1.0.0", 4 | "imports": { 5 | "/utils/": "./a/", 6 | "/utils1/": { 7 | "browser": "./a/", 8 | "default": "./b/" 9 | }, 10 | "a": "a.js", 11 | "#": "a.js", 12 | "#/dep": "a.js", 13 | "#dep/": "a.js", 14 | "#dep": [ 15 | "./a/../b/../../pack1/index.js", 16 | "././-bad-specifier-", 17 | "./a.js?foo=../" 18 | ], 19 | 20 | "#dep/foo/*": [ 21 | "./a/../b/../../pack1/index.js", 22 | "././-bad-specifier-", 23 | "./a.js?foo=../#../" 24 | ], 25 | "#dep/bar": "-bad-specifier-", 26 | "#dep/baz": ["-bad-specifier-"], 27 | "#dep/baz-multi": ["-bad-specifier-", "not-found"], 28 | "#dep/pattern/*.js": { 29 | "default": ["-bad-specifier-", "./*.js"] 30 | }, 31 | "#dep/array": ["./a.js", "-bad-specifier-"], 32 | "#dep/array2": ["-bad-specifier-", "./a.js"], 33 | "#dep/array3": ["./a.js"], 34 | "#dep/empty": "", 35 | "#dep/with-bad": ["../foo", "./a.js"], 36 | "#dep/with-bad2": ["./a.js", "../foo"], 37 | "#timezones/": "./data/timezones", 38 | "#dep/multi": ["../../test", "./a.js"], 39 | "#dep/multi1": ["../../test", "../../test/foo"], 40 | "#dep/multi2": ["../../test"] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/forEachBail.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */ 9 | 10 | /** 11 | * @template T 12 | * @template Z 13 | * @callback Iterator 14 | * @param {T} item item 15 | * @param {(err?: null|Error, result?: null|Z) => void} callback callback 16 | * @param {number} i index 17 | * @returns {void} 18 | */ 19 | 20 | /** 21 | * @template T 22 | * @template Z 23 | * @param {T[]} array array 24 | * @param {Iterator} iterator iterator 25 | * @param {(err?: null|Error, result?: null|Z, i?: number) => void} callback callback after all items are iterated 26 | * @returns {void} 27 | */ 28 | module.exports = function forEachBail(array, iterator, callback) { 29 | if (array.length === 0) return callback(); 30 | 31 | let i = 0; 32 | const next = () => { 33 | /** @type {boolean|undefined} */ 34 | let loop; 35 | iterator( 36 | array[i++], 37 | (err, result) => { 38 | if (err || result !== undefined || i >= array.length) { 39 | return callback(err, result, i); 40 | } 41 | if (loop === false) while (next()); 42 | loop = true; 43 | }, 44 | i, 45 | ); 46 | if (!loop) loop = false; 47 | return loop; 48 | }; 49 | while (next()); 50 | }; 51 | -------------------------------------------------------------------------------- /test/__snapshots__/alias.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`alias should log the correct info 1`] = ` 4 | Array [ 5 | "resolve 'aliasA/dir' in '/'", 6 | " Parsed request is a module", 7 | " No description file found in / or above", 8 | " aliased with mapping 'aliasA': 'a' to 'a/dir'", 9 | " Parsed request is a module", 10 | " No description file found in / or above", 11 | " resolve as module", 12 | " looking for modules in /", 13 | " existing directory /a", 14 | " No description file found in /a or above", 15 | " No description file found in /a or above", 16 | " no extension", 17 | " /a/dir is not a file", 18 | " .js", 19 | " /a/dir.js doesn't exist", 20 | " .json", 21 | " /a/dir.json doesn't exist", 22 | " .node", 23 | " /a/dir.node doesn't exist", 24 | " as directory", 25 | " existing directory /a/dir", 26 | " No description file found in /a/dir or above", 27 | " using path: /a/dir/index", 28 | " No description file found in /a/dir or above", 29 | " no extension", 30 | " existing file: /a/dir/index", 31 | " reporting result /a/dir/index", 32 | ] 33 | `; 34 | -------------------------------------------------------------------------------- /lib/getInnerRequest.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | /** @typedef {import("./Resolver")} Resolver */ 9 | /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */ 10 | 11 | /** 12 | * @param {Resolver} resolver resolver 13 | * @param {ResolveRequest} request string 14 | * @returns {string} inner request 15 | */ 16 | module.exports = function getInnerRequest(resolver, request) { 17 | if ( 18 | typeof request.__innerRequest === "string" && 19 | request.__innerRequest_request === request.request && 20 | request.__innerRequest_relativePath === request.relativePath 21 | ) { 22 | return request.__innerRequest; 23 | } 24 | /** @type {string|undefined} */ 25 | let innerRequest; 26 | if (request.request) { 27 | innerRequest = request.request; 28 | if (/^\.\.?(?:\/|$)/.test(innerRequest) && request.relativePath) { 29 | innerRequest = resolver.join(request.relativePath, innerRequest); 30 | } 31 | } else { 32 | innerRequest = request.relativePath; 33 | } 34 | // eslint-disable-next-line camelcase 35 | request.__innerRequest_request = request.request; 36 | // eslint-disable-next-line camelcase 37 | request.__innerRequest_relativePath = request.relativePath; 38 | return (request.__innerRequest = /** @type {string} */ (innerRequest)); 39 | }; 40 | -------------------------------------------------------------------------------- /lib/ModulesInRootPlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | /** @typedef {import("./Resolver")} Resolver */ 9 | /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */ 10 | /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ 11 | 12 | module.exports = class ModulesInRootPlugin { 13 | /** 14 | * @param {string | ResolveStepHook} source source 15 | * @param {string} path path 16 | * @param {string | ResolveStepHook} target target 17 | */ 18 | constructor(source, path, target) { 19 | this.source = source; 20 | this.path = path; 21 | this.target = target; 22 | } 23 | 24 | /** 25 | * @param {Resolver} resolver the resolver 26 | * @returns {void} 27 | */ 28 | apply(resolver) { 29 | const target = resolver.ensureHook(this.target); 30 | resolver 31 | .getHook(this.source) 32 | .tapAsync("ModulesInRootPlugin", (request, resolveContext, callback) => { 33 | /** @type {ResolveRequest} */ 34 | const obj = { 35 | ...request, 36 | path: this.path, 37 | request: `./${request.request}`, 38 | module: false, 39 | }; 40 | resolver.doResolve( 41 | target, 42 | obj, 43 | `looking for modules in ${this.path}`, 44 | resolveContext, 45 | callback, 46 | ); 47 | }); 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /lib/getPaths.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | /** 9 | * @param {string} path path 10 | * @returns {{paths: string[], segments: string[]}}} paths and segments 11 | */ 12 | module.exports = function getPaths(path) { 13 | if (path === "/") return { paths: ["/"], segments: [""] }; 14 | const parts = path.split(/(.*?[\\/]+)/); 15 | const paths = [path]; 16 | const segments = [parts[parts.length - 1]]; 17 | let part = parts[parts.length - 1]; 18 | path = path.slice(0, Math.max(0, path.length - part.length - 1)); 19 | for (let i = parts.length - 2; i > 2; i -= 2) { 20 | paths.push(path); 21 | part = parts[i]; 22 | path = path.slice(0, Math.max(0, path.length - part.length)) || "/"; 23 | segments.push(part.slice(0, -1)); 24 | } 25 | [, part] = parts; 26 | segments.push(part); 27 | paths.push(part); 28 | return { 29 | paths, 30 | segments, 31 | }; 32 | }; 33 | 34 | /** 35 | * @param {string} path path 36 | * @returns {string|null} basename or null 37 | */ 38 | module.exports.basename = function basename(path) { 39 | const i = path.lastIndexOf("/"); 40 | const j = path.lastIndexOf("\\"); 41 | const resolvedPath = i < 0 ? j : j < 0 ? i : i < j ? j : i; 42 | if (resolvedPath < 0) return null; 43 | const basename = path.slice(resolvedPath + 1); 44 | return basename; 45 | }; 46 | -------------------------------------------------------------------------------- /lib/AppendPlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | /** @typedef {import("./Resolver")} Resolver */ 9 | /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */ 10 | /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ 11 | 12 | module.exports = class AppendPlugin { 13 | /** 14 | * @param {string | ResolveStepHook} source source 15 | * @param {string} appending appending 16 | * @param {string | ResolveStepHook} target target 17 | */ 18 | constructor(source, appending, target) { 19 | this.source = source; 20 | this.appending = appending; 21 | this.target = target; 22 | } 23 | 24 | /** 25 | * @param {Resolver} resolver the resolver 26 | * @returns {void} 27 | */ 28 | apply(resolver) { 29 | const target = resolver.ensureHook(this.target); 30 | resolver 31 | .getHook(this.source) 32 | .tapAsync("AppendPlugin", (request, resolveContext, callback) => { 33 | /** @type {ResolveRequest} */ 34 | const obj = { 35 | ...request, 36 | path: request.path + this.appending, 37 | relativePath: 38 | request.relativePath && request.relativePath + this.appending, 39 | }; 40 | resolver.doResolve( 41 | target, 42 | obj, 43 | this.appending, 44 | resolveContext, 45 | callback, 46 | ); 47 | }); 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /lib/JoinRequestPlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | /** @typedef {import("./Resolver")} Resolver */ 9 | /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */ 10 | /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ 11 | 12 | module.exports = class JoinRequestPlugin { 13 | /** 14 | * @param {string | ResolveStepHook} source source 15 | * @param {string | ResolveStepHook} target target 16 | */ 17 | constructor(source, target) { 18 | this.source = source; 19 | this.target = target; 20 | } 21 | 22 | /** 23 | * @param {Resolver} resolver the resolver 24 | * @returns {void} 25 | */ 26 | apply(resolver) { 27 | const target = resolver.ensureHook(this.target); 28 | resolver 29 | .getHook(this.source) 30 | .tapAsync("JoinRequestPlugin", (request, resolveContext, callback) => { 31 | const requestPath = /** @type {string} */ (request.path); 32 | const requestRequest = /** @type {string} */ (request.request); 33 | /** @type {ResolveRequest} */ 34 | const obj = { 35 | ...request, 36 | path: resolver.join(requestPath, requestRequest), 37 | relativePath: 38 | request.relativePath && 39 | resolver.join(request.relativePath, requestRequest), 40 | request: undefined, 41 | }; 42 | resolver.doResolve(target, obj, null, resolveContext, callback); 43 | }); 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /test/__snapshots__/fallback.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`fallback should log the correct info 1`] = ` 4 | Array [ 5 | "resolve 'aliasA/dir' in '/'", 6 | " Parsed request is a module", 7 | " No description file found in / or above", 8 | " resolve as module", 9 | " looking for modules in /", 10 | " /aliasA doesn't exist", 11 | " aliased with mapping 'aliasA': 'a' to 'a/dir'", 12 | " Parsed request is a module", 13 | " No description file found in / or above", 14 | " resolve as module", 15 | " looking for modules in /", 16 | " existing directory /a", 17 | " No description file found in /a or above", 18 | " No description file found in /a or above", 19 | " no extension", 20 | " /a/dir is not a file", 21 | " .js", 22 | " /a/dir.js doesn't exist", 23 | " .json", 24 | " /a/dir.json doesn't exist", 25 | " .node", 26 | " /a/dir.node doesn't exist", 27 | " as directory", 28 | " existing directory /a/dir", 29 | " No description file found in /a/dir or above", 30 | " using path: /a/dir/index", 31 | " No description file found in /a/dir or above", 32 | " no extension", 33 | " existing file: /a/dir/index", 34 | " reporting result /a/dir/index", 35 | ] 36 | `; 37 | -------------------------------------------------------------------------------- /lib/UseFilePlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | /** @typedef {import("./Resolver")} Resolver */ 9 | /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */ 10 | /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ 11 | 12 | module.exports = class UseFilePlugin { 13 | /** 14 | * @param {string | ResolveStepHook} source source 15 | * @param {string} filename filename 16 | * @param {string | ResolveStepHook} target target 17 | */ 18 | constructor(source, filename, target) { 19 | this.source = source; 20 | this.filename = filename; 21 | this.target = target; 22 | } 23 | 24 | /** 25 | * @param {Resolver} resolver the resolver 26 | * @returns {void} 27 | */ 28 | apply(resolver) { 29 | const target = resolver.ensureHook(this.target); 30 | resolver 31 | .getHook(this.source) 32 | .tapAsync("UseFilePlugin", (request, resolveContext, callback) => { 33 | const filePath = resolver.join( 34 | /** @type {string} */ (request.path), 35 | this.filename, 36 | ); 37 | 38 | /** @type {ResolveRequest} */ 39 | const obj = { 40 | ...request, 41 | path: filePath, 42 | relativePath: 43 | request.relativePath && 44 | resolver.join(request.relativePath, this.filename), 45 | }; 46 | resolver.doResolve( 47 | target, 48 | obj, 49 | `using path: ${filePath}`, 50 | resolveContext, 51 | callback, 52 | ); 53 | }); 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /test/scoped-packages.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const fs = require("fs"); 4 | const path = require("path"); 5 | const { CachedInputFileSystem, ResolverFactory } = require("../"); 6 | 7 | const fixture = path.join(__dirname, "fixtures", "scoped"); 8 | 9 | const nodeFileSystem = new CachedInputFileSystem(fs, 4000); 10 | 11 | const resolver = ResolverFactory.createResolver({ 12 | aliasFields: ["browser"], 13 | fileSystem: nodeFileSystem, 14 | }); 15 | 16 | describe("scoped-packages", () => { 17 | it("main field should work", (done) => { 18 | resolver.resolve({}, fixture, "@scope/pack1", {}, (err, result) => { 19 | if (err) return done(err); 20 | if (!result) return done(new Error("No result")); 21 | expect(result).toEqual( 22 | path.resolve(fixture, "./node_modules/@scope/pack1/main.js"), 23 | ); 24 | done(); 25 | }); 26 | }); 27 | 28 | it("browser field should work", (done) => { 29 | resolver.resolve({}, fixture, "@scope/pack2", {}, (err, result) => { 30 | if (err) return done(err); 31 | if (!result) return done(new Error("No result")); 32 | expect(result).toEqual( 33 | path.resolve(fixture, "./node_modules/@scope/pack2/main.js"), 34 | ); 35 | done(); 36 | }); 37 | }); 38 | 39 | it("folder request should work", (done) => { 40 | resolver.resolve({}, fixture, "@scope/pack2/lib", {}, (err, result) => { 41 | if (err) return done(err); 42 | if (!result) return done(new Error("No result")); 43 | expect(result).toEqual( 44 | path.resolve(fixture, "./node_modules/@scope/pack2/lib/index.js"), 45 | ); 46 | done(); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /lib/CloneBasenamePlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | const { basename } = require("./getPaths"); 9 | 10 | /** @typedef {import("./Resolver")} Resolver */ 11 | /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */ 12 | /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ 13 | 14 | module.exports = class CloneBasenamePlugin { 15 | /** 16 | * @param {string | ResolveStepHook} source source 17 | * @param {string | ResolveStepHook} target target 18 | */ 19 | constructor(source, target) { 20 | this.source = source; 21 | this.target = target; 22 | } 23 | 24 | /** 25 | * @param {Resolver} resolver the resolver 26 | * @returns {void} 27 | */ 28 | apply(resolver) { 29 | const target = resolver.ensureHook(this.target); 30 | resolver 31 | .getHook(this.source) 32 | .tapAsync("CloneBasenamePlugin", (request, resolveContext, callback) => { 33 | const requestPath = /** @type {string} */ (request.path); 34 | const filename = /** @type {string} */ (basename(requestPath)); 35 | const filePath = resolver.join(requestPath, filename); 36 | /** @type {ResolveRequest} */ 37 | const obj = { 38 | ...request, 39 | path: filePath, 40 | relativePath: 41 | request.relativePath && 42 | resolver.join(request.relativePath, filename), 43 | }; 44 | resolver.doResolve( 45 | target, 46 | obj, 47 | `using path: ${filePath}`, 48 | resolveContext, 49 | callback, 50 | ); 51 | }); 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /test/forEachBail.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { forEachBail } = require("../"); 4 | 5 | describe("forEachBail", () => { 6 | it("should iterate correctly", (done) => { 7 | const log = []; 8 | forEachBail( 9 | [0, 1, 2, 3, 4, 5, 6], 10 | (value, callback) => { 11 | log.push(value); 12 | if (value % 4 === 0) return callback(null, undefined); 13 | if (value % 2 === 0) return callback(); 14 | if (value === 5) return callback(null, { path: "test" }); 15 | process.nextTick(callback); 16 | }, 17 | (err, result) => { 18 | if (err) return done(err); 19 | if (!result) return done(new Error("Should have result")); 20 | expect(result).toEqual({ path: "test" }); 21 | expect(log).toEqual([0, 1, 2, 3, 4, 5]); 22 | done(); 23 | }, 24 | ); 25 | }); 26 | 27 | it("should handle empty array", (done) => { 28 | forEachBail( 29 | [], 30 | () => { 31 | done(new Error("Should not be called")); 32 | }, 33 | (err, result) => { 34 | expect(err).toBeUndefined(); 35 | expect(result).toBeUndefined(); 36 | done(); 37 | }, 38 | ); 39 | }); 40 | 41 | it("should sync finish with undefined", (done) => { 42 | forEachBail( 43 | [2, 3, 4, 5, 6], 44 | (value, callback) => callback(), 45 | (err, result) => { 46 | expect(err).toBeUndefined(); 47 | expect(result).toBeUndefined(); 48 | done(); 49 | }, 50 | ); 51 | }); 52 | 53 | it("should async finish with undefined", (done) => { 54 | forEachBail( 55 | [2, 3, 4, 5, 6], 56 | (value, callback) => { 57 | process.nextTick(callback); 58 | }, 59 | (err, result) => { 60 | expect(err).toBeUndefined(); 61 | expect(result).toBeUndefined(); 62 | done(); 63 | }, 64 | ); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /lib/LogInfoPlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | /** @typedef {import("./Resolver")} Resolver */ 9 | /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ 10 | 11 | module.exports = class LogInfoPlugin { 12 | /** 13 | * @param {string | ResolveStepHook} source source 14 | */ 15 | constructor(source) { 16 | this.source = source; 17 | } 18 | 19 | /** 20 | * @param {Resolver} resolver the resolver 21 | * @returns {void} 22 | */ 23 | apply(resolver) { 24 | const { source } = this; 25 | resolver 26 | .getHook(this.source) 27 | .tapAsync("LogInfoPlugin", (request, resolveContext, callback) => { 28 | if (!resolveContext.log) return callback(); 29 | const { log } = resolveContext; 30 | const prefix = `[${source}] `; 31 | if (request.path) { 32 | log(`${prefix}Resolving in directory: ${request.path}`); 33 | } 34 | if (request.request) { 35 | log(`${prefix}Resolving request: ${request.request}`); 36 | } 37 | if (request.module) log(`${prefix}Request is an module request.`); 38 | if (request.directory) log(`${prefix}Request is a directory request.`); 39 | if (request.query) { 40 | log(`${prefix}Resolving request query: ${request.query}`); 41 | } 42 | if (request.fragment) { 43 | log(`${prefix}Resolving request fragment: ${request.fragment}`); 44 | } 45 | if (request.descriptionFilePath) { 46 | log( 47 | `${prefix}Has description data from ${request.descriptionFilePath}`, 48 | ); 49 | } 50 | if (request.relativePath) { 51 | log( 52 | `${prefix}Relative path from description file is: ${request.relativePath}`, 53 | ); 54 | } 55 | callback(); 56 | }); 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /lib/FileExistsPlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | /** @typedef {import("./Resolver")} Resolver */ 9 | /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ 10 | 11 | module.exports = class FileExistsPlugin { 12 | /** 13 | * @param {string | ResolveStepHook} source source 14 | * @param {string | ResolveStepHook} target target 15 | */ 16 | constructor(source, target) { 17 | this.source = source; 18 | this.target = target; 19 | } 20 | 21 | /** 22 | * @param {Resolver} resolver the resolver 23 | * @returns {void} 24 | */ 25 | apply(resolver) { 26 | const target = resolver.ensureHook(this.target); 27 | const fs = resolver.fileSystem; 28 | resolver 29 | .getHook(this.source) 30 | .tapAsync("FileExistsPlugin", (request, resolveContext, callback) => { 31 | const file = request.path; 32 | if (!file) return callback(); 33 | fs.stat(file, (err, stat) => { 34 | if (err || !stat) { 35 | if (resolveContext.missingDependencies) { 36 | resolveContext.missingDependencies.add(file); 37 | } 38 | if (resolveContext.log) resolveContext.log(`${file} doesn't exist`); 39 | return callback(); 40 | } 41 | if (!stat.isFile()) { 42 | if (resolveContext.missingDependencies) { 43 | resolveContext.missingDependencies.add(file); 44 | } 45 | if (resolveContext.log) resolveContext.log(`${file} is not a file`); 46 | return callback(); 47 | } 48 | if (resolveContext.fileDependencies) { 49 | resolveContext.fileDependencies.add(file); 50 | } 51 | resolver.doResolve( 52 | target, 53 | request, 54 | `existing file: ${file}`, 55 | resolveContext, 56 | callback, 57 | ); 58 | }); 59 | }); 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /test/plugins.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const path = require("path"); 4 | const { CloneBasenamePlugin, ResolverFactory } = require("../"); 5 | 6 | describe("plugins", () => { 7 | it("should resolve with the CloneBasenamePlugin", (done) => { 8 | const resolver = ResolverFactory.createResolver({ 9 | fileSystem: require("fs"), 10 | plugins: [ 11 | new CloneBasenamePlugin( 12 | "after-existing-directory", 13 | "undescribed-raw-file", 14 | ), 15 | ], 16 | }); 17 | 18 | resolver.resolve( 19 | {}, 20 | __dirname, 21 | "./fixtures/directory-default", 22 | {}, 23 | (err, result) => { 24 | if (err) return done(err); 25 | if (!result) return done(new Error("No result")); 26 | expect(result).toEqual( 27 | path.resolve( 28 | __dirname, 29 | "fixtures/directory-default/directory-default.js", 30 | ), 31 | ); 32 | done(); 33 | }, 34 | ); 35 | }); 36 | 37 | it("should ignore 'false'/'null'/'undefined' plugins", (done) => { 38 | const FailedPlugin = class { 39 | apply() { 40 | throw new Error("FailedPlugin"); 41 | } 42 | }; 43 | const falsy = false; 44 | const resolver = ResolverFactory.createResolver({ 45 | fileSystem: require("fs"), 46 | plugins: [ 47 | 0, 48 | "", 49 | false, 50 | null, 51 | undefined, 52 | falsy && new FailedPlugin(), 53 | new CloneBasenamePlugin( 54 | "after-existing-directory", 55 | "undescribed-raw-file", 56 | ), 57 | ], 58 | }); 59 | 60 | resolver.resolve( 61 | {}, 62 | __dirname, 63 | "./fixtures/directory-default", 64 | {}, 65 | (err, result) => { 66 | if (err) return done(err); 67 | if (!result) return done(new Error("No result")); 68 | expect(result).toEqual( 69 | path.resolve( 70 | __dirname, 71 | "fixtures/directory-default/directory-default.js", 72 | ), 73 | ); 74 | done(); 75 | }, 76 | ); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /lib/RootsPlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Ivan Kopeykin @vankop 4 | */ 5 | 6 | "use strict"; 7 | 8 | const forEachBail = require("./forEachBail"); 9 | 10 | /** @typedef {import("./Resolver")} Resolver */ 11 | /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */ 12 | /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ 13 | 14 | class RootsPlugin { 15 | /** 16 | * @param {string | ResolveStepHook} source source hook 17 | * @param {Set} roots roots 18 | * @param {string | ResolveStepHook} target target hook 19 | */ 20 | constructor(source, roots, target) { 21 | this.roots = [...roots]; 22 | this.source = source; 23 | this.target = target; 24 | } 25 | 26 | /** 27 | * @param {Resolver} resolver the resolver 28 | * @returns {void} 29 | */ 30 | apply(resolver) { 31 | const target = resolver.ensureHook(this.target); 32 | 33 | resolver 34 | .getHook(this.source) 35 | .tapAsync("RootsPlugin", (request, resolveContext, callback) => { 36 | const req = request.request; 37 | if (!req) return callback(); 38 | if (!req.startsWith("/")) return callback(); 39 | 40 | forEachBail( 41 | this.roots, 42 | /** 43 | * @param {string} root root 44 | * @param {(err?: null|Error, result?: null|ResolveRequest) => void} callback callback 45 | * @returns {void} 46 | */ 47 | (root, callback) => { 48 | const path = resolver.join(root, req.slice(1)); 49 | /** @type {ResolveRequest} */ 50 | const obj = { 51 | ...request, 52 | path, 53 | relativePath: request.relativePath && path, 54 | }; 55 | resolver.doResolve( 56 | target, 57 | obj, 58 | `root path ${root}`, 59 | resolveContext, 60 | callback, 61 | ); 62 | }, 63 | callback, 64 | ); 65 | }); 66 | } 67 | } 68 | 69 | module.exports = RootsPlugin; 70 | -------------------------------------------------------------------------------- /lib/ConditionalPlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | /** @typedef {import("./Resolver")} Resolver */ 9 | /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */ 10 | /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ 11 | 12 | module.exports = class ConditionalPlugin { 13 | /** 14 | * @param {string | ResolveStepHook} source source 15 | * @param {Partial} test compare object 16 | * @param {string | null} message log message 17 | * @param {boolean} allowAlternatives when false, do not continue with the current step when "test" matches 18 | * @param {string | ResolveStepHook} target target 19 | */ 20 | constructor(source, test, message, allowAlternatives, target) { 21 | this.source = source; 22 | this.test = test; 23 | this.message = message; 24 | this.allowAlternatives = allowAlternatives; 25 | this.target = target; 26 | } 27 | 28 | /** 29 | * @param {Resolver} resolver the resolver 30 | * @returns {void} 31 | */ 32 | apply(resolver) { 33 | const target = resolver.ensureHook(this.target); 34 | const { test, message, allowAlternatives } = this; 35 | const keys = /** @type {(keyof ResolveRequest)[]} */ (Object.keys(test)); 36 | resolver 37 | .getHook(this.source) 38 | .tapAsync("ConditionalPlugin", (request, resolveContext, callback) => { 39 | for (const prop of keys) { 40 | if (request[prop] !== test[prop]) return callback(); 41 | } 42 | resolver.doResolve( 43 | target, 44 | request, 45 | message, 46 | resolveContext, 47 | allowAlternatives 48 | ? callback 49 | : (err, result) => { 50 | if (err) return callback(err); 51 | 52 | // Don't allow other alternatives 53 | if (result === undefined) return callback(null, null); 54 | callback(null, result); 55 | }, 56 | ); 57 | }); 58 | } 59 | }; 60 | -------------------------------------------------------------------------------- /test/fixtures/exports-field-invalid-package-target/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@exports-field/bad-specifier", 3 | "version": "1.0.0", 4 | "exports": { 5 | ".": [ 6 | "", 7 | "./a/../b/../../pack1/index.js", 8 | "././-bad-specifier-", 9 | "-bad-specifier-", 10 | "file.js", 11 | "./a.js?foo=../" 12 | ], 13 | "./foo/*": [ 14 | "", 15 | "./a/../b/../../pack1/index.js", 16 | "././-bad-specifier-", 17 | "-bad-specifier-", 18 | "file.js", 19 | "./a.js?foo=../#../" 20 | ], 21 | "./bar": "-bad-specifier-", 22 | "./baz": ["-bad-specifier-"], 23 | "./baz-multi": ["-bad-specifier-", "foo"], 24 | "./pattern/*.js": { 25 | "default": ["-bad-specifier-", "./*.js"] 26 | }, 27 | "./slash": ["/bar", "./a.js"], 28 | "./no-slash": [".bar", "./a.js"], 29 | "./utils/": { 30 | "browser": "/a/", 31 | "default": "/b/" 32 | }, 33 | "./utils1/": "/a/", 34 | "./utils2/": { 35 | "default": "../this/" 36 | }, 37 | "./utils3/*": { 38 | "default": "../this/*" 39 | }, 40 | "./utils4/*": "../src/*", 41 | "./utils5/": "../src/", 42 | "./*": ".", 43 | "./valid/*.js": { 44 | "default": ["-bad-specifier-", "./*.js"] 45 | }, 46 | "./non-existent.js": [ 47 | "-bad-specifier-", 48 | "./non-existent.js", 49 | "./a.js" 50 | ], 51 | "./bad-specifier.js": [ 52 | "-bad-specifier-", 53 | "../../a.js", 54 | "./a.js" 55 | ], 56 | "./bad-specifier1.js": [ 57 | "-bad-specifier-", 58 | "foo", 59 | "./a.js" 60 | ], 61 | "./dep/multi": ["../../test", "./a.js"], 62 | "./dep/multi1": ["../../test", "../../test/foo"], 63 | "./dep/multi2": ["../../test"], 64 | "./dep/multi3": ["./a/../b/../../pack1/index.js", "./a.js"], 65 | "./dep/multi4": ["./a/../b/../../pack1/index.js", "./c/../b/../../pack1/index.js"], 66 | "./dep/multi5": ["./a/../b/../../pack1/index.js"] 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /test/incorrect-description-file.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const fs = require("fs"); 4 | const path = require("path"); 5 | const { CachedInputFileSystem, ResolverFactory } = require("../"); 6 | 7 | const fixtures = path.join(__dirname, "fixtures", "incorrect-package"); 8 | const nodeFileSystem = new CachedInputFileSystem(fs, 4000); 9 | 10 | /** 11 | * @param {string[]} args args 12 | * @returns {string} paths 13 | */ 14 | function p(...args) { 15 | return path.join(fixtures, ...args); 16 | } 17 | 18 | describe("incorrect description file", () => { 19 | const resolver = ResolverFactory.createResolver({ 20 | useSyncFileSystemCalls: true, 21 | fileSystem: nodeFileSystem, 22 | }); 23 | 24 | it("should not resolve main in incorrect description file #1", (done) => { 25 | let called = false; 26 | const ctx = { 27 | fileDependencies: new Set(), 28 | log: () => { 29 | called = true; 30 | }, 31 | }; 32 | resolver.resolve({}, p("pack1"), ".", ctx, (err, _result) => { 33 | if (!err) return done(new Error("No error")); 34 | expect(err).toBeInstanceOf(Error); 35 | expect(ctx.fileDependencies.has(p("pack1", "package.json"))).toBe(true); 36 | expect(called).toBe(true); 37 | done(); 38 | }); 39 | }); 40 | 41 | it("should not resolve main in incorrect description file #2", (done) => { 42 | let called = false; 43 | const ctx = { 44 | fileDependencies: new Set(), 45 | log: () => { 46 | called = true; 47 | }, 48 | }; 49 | resolver.resolve({}, p("pack2"), ".", ctx, (err, _result) => { 50 | if (!err) return done(new Error("No error")); 51 | expect(ctx.fileDependencies.has(p("pack2", "package.json"))).toBe(true); 52 | expect(called).toBe(true); 53 | done(); 54 | }); 55 | }); 56 | 57 | it("should not resolve main in incorrect description file #3", (done) => { 58 | resolver.resolve({}, p("pack2"), ".", {}, (err, _result) => { 59 | if (!err) return done(new Error("No error")); 60 | expect(err).toBeInstanceOf(Error); 61 | done(); 62 | }); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /lib/DirectoryExistsPlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | /** @typedef {import("./Resolver")} Resolver */ 9 | /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ 10 | 11 | module.exports = class DirectoryExistsPlugin { 12 | /** 13 | * @param {string | ResolveStepHook} source source 14 | * @param {string | ResolveStepHook} target target 15 | */ 16 | constructor(source, target) { 17 | this.source = source; 18 | this.target = target; 19 | } 20 | 21 | /** 22 | * @param {Resolver} resolver the resolver 23 | * @returns {void} 24 | */ 25 | apply(resolver) { 26 | const target = resolver.ensureHook(this.target); 27 | resolver 28 | .getHook(this.source) 29 | .tapAsync( 30 | "DirectoryExistsPlugin", 31 | (request, resolveContext, callback) => { 32 | const fs = resolver.fileSystem; 33 | const directory = request.path; 34 | if (!directory) return callback(); 35 | fs.stat(directory, (err, stat) => { 36 | if (err || !stat) { 37 | if (resolveContext.missingDependencies) { 38 | resolveContext.missingDependencies.add(directory); 39 | } 40 | if (resolveContext.log) { 41 | resolveContext.log(`${directory} doesn't exist`); 42 | } 43 | return callback(); 44 | } 45 | if (!stat.isDirectory()) { 46 | if (resolveContext.missingDependencies) { 47 | resolveContext.missingDependencies.add(directory); 48 | } 49 | if (resolveContext.log) { 50 | resolveContext.log(`${directory} is not a directory`); 51 | } 52 | return callback(); 53 | } 54 | if (resolveContext.fileDependencies) { 55 | resolveContext.fileDependencies.add(directory); 56 | } 57 | resolver.doResolve( 58 | target, 59 | request, 60 | `existing directory ${directory}`, 61 | resolveContext, 62 | callback, 63 | ); 64 | }); 65 | }, 66 | ); 67 | } 68 | }; 69 | -------------------------------------------------------------------------------- /lib/RestrictionsPlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Ivan Kopeykin @vankop 4 | */ 5 | 6 | "use strict"; 7 | 8 | /** @typedef {import("./Resolver")} Resolver */ 9 | /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ 10 | 11 | const slashCode = "/".charCodeAt(0); 12 | const backslashCode = "\\".charCodeAt(0); 13 | 14 | /** 15 | * @param {string} path path 16 | * @param {string} parent parent path 17 | * @returns {boolean} true, if path is inside of parent 18 | */ 19 | const isInside = (path, parent) => { 20 | if (!path.startsWith(parent)) return false; 21 | if (path.length === parent.length) return true; 22 | const charCode = path.charCodeAt(parent.length); 23 | return charCode === slashCode || charCode === backslashCode; 24 | }; 25 | 26 | module.exports = class RestrictionsPlugin { 27 | /** 28 | * @param {string | ResolveStepHook} source source 29 | * @param {Set} restrictions restrictions 30 | */ 31 | constructor(source, restrictions) { 32 | this.source = source; 33 | this.restrictions = restrictions; 34 | } 35 | 36 | /** 37 | * @param {Resolver} resolver the resolver 38 | * @returns {void} 39 | */ 40 | apply(resolver) { 41 | resolver 42 | .getHook(this.source) 43 | .tapAsync("RestrictionsPlugin", (request, resolveContext, callback) => { 44 | if (typeof request.path === "string") { 45 | const { path } = request; 46 | for (const rule of this.restrictions) { 47 | if (typeof rule === "string") { 48 | if (!isInside(path, rule)) { 49 | if (resolveContext.log) { 50 | resolveContext.log( 51 | `${path} is not inside of the restriction ${rule}`, 52 | ); 53 | } 54 | return callback(null, null); 55 | } 56 | } else if (!rule.test(path)) { 57 | if (resolveContext.log) { 58 | resolveContext.log( 59 | `${path} doesn't match the restriction ${rule}`, 60 | ); 61 | } 62 | return callback(null, null); 63 | } 64 | } 65 | } 66 | 67 | callback(); 68 | }); 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /lib/util/identifier.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Ivan Kopeykin @vankop 4 | */ 5 | 6 | "use strict"; 7 | 8 | const memorize = require("./memoize"); 9 | 10 | const getUrl = memorize(() => require("url")); 11 | 12 | const PATH_QUERY_FRAGMENT_REGEXP = 13 | /^(#?(?:\0.|[^?#\0])*)(\?(?:\0.|[^#\0])*)?(#.*)?$/; 14 | const ZERO_ESCAPE_REGEXP = /\0(.)/g; 15 | const FILE_REG_EXP = /file:/i; 16 | 17 | /** 18 | * @param {string} identifier identifier 19 | * @returns {[string, string, string] | null} parsed identifier 20 | */ 21 | function parseIdentifier(identifier) { 22 | if (!identifier) { 23 | return null; 24 | } 25 | 26 | if (FILE_REG_EXP.test(identifier)) { 27 | identifier = getUrl().fileURLToPath(identifier); 28 | } 29 | 30 | const firstEscape = identifier.indexOf("\0"); 31 | 32 | // Handle `\0` 33 | if (firstEscape !== -1) { 34 | const match = PATH_QUERY_FRAGMENT_REGEXP.exec(identifier); 35 | 36 | if (!match) return null; 37 | 38 | return [ 39 | match[1].replace(ZERO_ESCAPE_REGEXP, "$1"), 40 | match[2] ? match[2].replace(ZERO_ESCAPE_REGEXP, "$1") : "", 41 | match[3] || "", 42 | ]; 43 | } 44 | 45 | // Fast path for inputs that don't use \0 escaping. 46 | const queryStart = identifier.indexOf("?"); 47 | // Start at index 1 to ignore a possible leading hash. 48 | const fragmentStart = identifier.indexOf("#", 1); 49 | 50 | if (fragmentStart < 0) { 51 | if (queryStart < 0) { 52 | // No fragment, no query 53 | return [identifier, "", ""]; 54 | } 55 | 56 | // Query, no fragment 57 | return [identifier.slice(0, queryStart), identifier.slice(queryStart), ""]; 58 | } 59 | 60 | if (queryStart < 0 || fragmentStart < queryStart) { 61 | // Fragment, no query 62 | return [ 63 | identifier.slice(0, fragmentStart), 64 | "", 65 | identifier.slice(fragmentStart), 66 | ]; 67 | } 68 | 69 | // Query and fragment 70 | return [ 71 | identifier.slice(0, queryStart), 72 | identifier.slice(queryStart, fragmentStart), 73 | identifier.slice(fragmentStart), 74 | ]; 75 | } 76 | 77 | module.exports.parseIdentifier = parseIdentifier; 78 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | permissions: 10 | contents: read # to fetch code (actions/checkout) 11 | 12 | jobs: 13 | lint: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Use Node.js 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: lts/* 21 | cache: npm 22 | - run: npm ci 23 | - run: npm run lint 24 | test: 25 | strategy: 26 | fail-fast: false 27 | matrix: 28 | os: [ubuntu-latest, windows-latest, macos-latest] 29 | node-version: [10.x, 12.x, 14.x, 16.x, 18.x, 20.x, 22.x, 24.x] 30 | runs-on: ${{ matrix.os }} 31 | steps: 32 | - uses: actions/checkout@v4 33 | - uses: actions/github-script@v7 34 | id: calculate_architecture 35 | with: 36 | result-encoding: string 37 | script: | 38 | if ('${{ matrix.os }}' === 'macos-latest' && ('${{ matrix['node-version'] }}' === '10.x' || '${{ matrix['node-version'] }}' === '12.x' || '${{ matrix['node-version'] }}' === '14.x')) { 39 | return "x64" 40 | } else { 41 | return '' 42 | } 43 | - name: Use Node.js ${{ matrix.node-version }} 44 | uses: actions/setup-node@v4 45 | with: 46 | node-version: ${{ matrix.node-version }} 47 | architecture: ${{ steps.calculate_architecture.outputs.result }} 48 | cache: npm 49 | - name: Install dependencies 50 | run: | 51 | npm install -D typescript@^4 --ignore-scripts 52 | npm install --ignore-scripts 53 | if: matrix.node-version == '10.x' || matrix.node-version == '12.x' || matrix.node-version == '14.x' 54 | - name: Install dependencies 55 | run: npm ci 56 | if: matrix.node-version != '10.x' && matrix.node-version != '12.x' && matrix.node-version != '14.x' 57 | - name: Run tests with coverage 58 | run: npm run test:coverage -- --ci 59 | - uses: codecov/codecov-action@v5 60 | with: 61 | flags: integration 62 | token: ${{ secrets.CODECOV_TOKEN }} 63 | -------------------------------------------------------------------------------- /lib/JoinRequestPartPlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | /** @typedef {import("./Resolver")} Resolver */ 9 | /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */ 10 | /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ 11 | 12 | const namespaceStartCharCode = "@".charCodeAt(0); 13 | 14 | module.exports = class JoinRequestPartPlugin { 15 | /** 16 | * @param {string | ResolveStepHook} source source 17 | * @param {string | ResolveStepHook} target target 18 | */ 19 | constructor(source, target) { 20 | this.source = source; 21 | this.target = target; 22 | } 23 | 24 | /** 25 | * @param {Resolver} resolver the resolver 26 | * @returns {void} 27 | */ 28 | apply(resolver) { 29 | const target = resolver.ensureHook(this.target); 30 | resolver 31 | .getHook(this.source) 32 | .tapAsync( 33 | "JoinRequestPartPlugin", 34 | (request, resolveContext, callback) => { 35 | const req = request.request || ""; 36 | let i = req.indexOf("/", 3); 37 | 38 | if (i >= 0 && req.charCodeAt(2) === namespaceStartCharCode) { 39 | i = req.indexOf("/", i + 1); 40 | } 41 | 42 | /** @type {string} */ 43 | let moduleName; 44 | /** @type {string} */ 45 | let remainingRequest; 46 | /** @type {boolean} */ 47 | let fullySpecified; 48 | if (i < 0) { 49 | moduleName = req; 50 | remainingRequest = "."; 51 | fullySpecified = false; 52 | } else { 53 | moduleName = req.slice(0, i); 54 | remainingRequest = `.${req.slice(i)}`; 55 | fullySpecified = /** @type {boolean} */ (request.fullySpecified); 56 | } 57 | /** @type {ResolveRequest} */ 58 | const obj = { 59 | ...request, 60 | path: resolver.join( 61 | /** @type {string} */ 62 | (request.path), 63 | moduleName, 64 | ), 65 | relativePath: 66 | request.relativePath && 67 | resolver.join(request.relativePath, moduleName), 68 | request: remainingRequest, 69 | fullySpecified, 70 | }; 71 | resolver.doResolve(target, obj, null, resolveContext, callback); 72 | }, 73 | ); 74 | } 75 | }; 76 | -------------------------------------------------------------------------------- /lib/SelfReferencePlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | const DescriptionFileUtils = require("./DescriptionFileUtils"); 9 | 10 | /** @typedef {import("./Resolver")} Resolver */ 11 | /** @typedef {import("./Resolver").JsonObject} JsonObject */ 12 | /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */ 13 | /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ 14 | 15 | const slashCode = "/".charCodeAt(0); 16 | 17 | module.exports = class SelfReferencePlugin { 18 | /** 19 | * @param {string | ResolveStepHook} source source 20 | * @param {string | string[]} fieldNamePath name path 21 | * @param {string | ResolveStepHook} target target 22 | */ 23 | constructor(source, fieldNamePath, target) { 24 | this.source = source; 25 | this.target = target; 26 | this.fieldName = fieldNamePath; 27 | } 28 | 29 | /** 30 | * @param {Resolver} resolver the resolver 31 | * @returns {void} 32 | */ 33 | apply(resolver) { 34 | const target = resolver.ensureHook(this.target); 35 | resolver 36 | .getHook(this.source) 37 | .tapAsync("SelfReferencePlugin", (request, resolveContext, callback) => { 38 | if (!request.descriptionFilePath) return callback(); 39 | 40 | const req = request.request; 41 | if (!req) return callback(); 42 | 43 | // Feature is only enabled when an exports field is present 44 | const exportsField = DescriptionFileUtils.getField( 45 | /** @type {JsonObject} */ (request.descriptionFileData), 46 | this.fieldName, 47 | ); 48 | if (!exportsField) return callback(); 49 | 50 | const name = DescriptionFileUtils.getField( 51 | /** @type {JsonObject} */ (request.descriptionFileData), 52 | "name", 53 | ); 54 | if (typeof name !== "string") return callback(); 55 | 56 | if ( 57 | req.startsWith(name) && 58 | (req.length === name.length || 59 | req.charCodeAt(name.length) === slashCode) 60 | ) { 61 | const remainingRequest = `.${req.slice(name.length)}`; 62 | /** @type {ResolveRequest} */ 63 | const obj = { 64 | ...request, 65 | request: remainingRequest, 66 | path: /** @type {string} */ (request.descriptionFileRoot), 67 | relativePath: ".", 68 | }; 69 | 70 | resolver.doResolve( 71 | target, 72 | obj, 73 | "self reference", 74 | resolveContext, 75 | callback, 76 | ); 77 | } else { 78 | return callback(); 79 | } 80 | }); 81 | } 82 | }; 83 | -------------------------------------------------------------------------------- /test/identifier.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { parseIdentifier } = require("../lib/util/identifier"); 4 | 5 | /** 6 | * @typedef {{input: string, expected: [string, string, string]}} TestSuite 7 | */ 8 | 9 | describe("identifier", () => { 10 | /** 11 | * @param {TestSuite[]} suites suites 12 | */ 13 | function run(suites) { 14 | for (const { input, expected } of suites) { 15 | it(input, () => { 16 | const parsed = parseIdentifier(input); 17 | 18 | if (!parsed) throw new Error("should not be null"); 19 | 20 | expect(parsed).toEqual(expected); 21 | }); 22 | } 23 | } 24 | 25 | describe("parse identifier. edge cases", () => { 26 | /** @type {TestSuite[]} */ 27 | const tests = [ 28 | { 29 | input: "path/#", 30 | expected: ["path/", "", "#"], 31 | }, 32 | { 33 | input: "path/as/?", 34 | expected: ["path/as/", "?", ""], 35 | }, 36 | { 37 | input: "path/#/?", 38 | expected: ["path/", "", "#/?"], 39 | }, 40 | { 41 | input: "path/#repo#hash", 42 | expected: ["path/", "", "#repo#hash"], 43 | }, 44 | { 45 | input: "path/#r#hash", 46 | expected: ["path/", "", "#r#hash"], 47 | }, 48 | { 49 | input: "path/#repo/#repo2#hash", 50 | expected: ["path/", "", "#repo/#repo2#hash"], 51 | }, 52 | { 53 | input: "path/#r/#r#hash", 54 | expected: ["path/", "", "#r/#r#hash"], 55 | }, 56 | { 57 | input: "path/#/not/a/hash?not-a-query", 58 | expected: ["path/", "", "#/not/a/hash?not-a-query"], 59 | }, 60 | { 61 | input: "#\0?\0#ab\0\0c?\0#\0\0query#?#\0fragment", 62 | expected: ["#?#ab\0c", "?#\0query", "#?#\0fragment"], 63 | }, 64 | ]; 65 | 66 | run(tests); 67 | }); 68 | 69 | describe("parse identifier. Windows-like paths", () => { 70 | /** @type {TestSuite[]} */ 71 | const tests = [ 72 | { 73 | input: "path\\#", 74 | expected: ["path\\", "", "#"], 75 | }, 76 | { 77 | input: "C:path\\as\\?", 78 | expected: ["C:path\\as\\", "?", ""], 79 | }, 80 | { 81 | input: "path\\#\\?", 82 | expected: ["path\\", "", "#\\?"], 83 | }, 84 | { 85 | input: "path\\#repo#hash", 86 | expected: ["path\\", "", "#repo#hash"], 87 | }, 88 | { 89 | input: "path\\#r#hash", 90 | expected: ["path\\", "", "#r#hash"], 91 | }, 92 | { 93 | input: "path\\#/not/a/hash?not-a-query", 94 | expected: ["path\\", "", "#/not/a/hash?not-a-query"], 95 | }, 96 | ]; 97 | 98 | run(tests); 99 | }); 100 | }); 101 | -------------------------------------------------------------------------------- /lib/ParsePlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | /** @typedef {import("./Resolver")} Resolver */ 9 | /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */ 10 | /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ 11 | 12 | module.exports = class ParsePlugin { 13 | /** 14 | * @param {string | ResolveStepHook} source source 15 | * @param {Partial} requestOptions request options 16 | * @param {string | ResolveStepHook} target target 17 | */ 18 | constructor(source, requestOptions, target) { 19 | this.source = source; 20 | this.requestOptions = requestOptions; 21 | this.target = target; 22 | } 23 | 24 | /** 25 | * @param {Resolver} resolver the resolver 26 | * @returns {void} 27 | */ 28 | apply(resolver) { 29 | const target = resolver.ensureHook(this.target); 30 | resolver 31 | .getHook(this.source) 32 | .tapAsync("ParsePlugin", (request, resolveContext, callback) => { 33 | const parsed = resolver.parse(/** @type {string} */ (request.request)); 34 | /** @type {ResolveRequest} */ 35 | const obj = { ...request, ...parsed, ...this.requestOptions }; 36 | if (request.query && !parsed.query) { 37 | obj.query = request.query; 38 | } 39 | if (request.fragment && !parsed.fragment) { 40 | obj.fragment = request.fragment; 41 | } 42 | if (parsed && resolveContext.log) { 43 | if (parsed.module) resolveContext.log("Parsed request is a module"); 44 | if (parsed.directory) { 45 | resolveContext.log("Parsed request is a directory"); 46 | } 47 | } 48 | // There is an edge-case where a request with # can be a path or a fragment -> try both 49 | if (obj.request && !obj.query && obj.fragment) { 50 | const directory = obj.fragment.endsWith("/"); 51 | /** @type {ResolveRequest} */ 52 | const alternative = { 53 | ...obj, 54 | directory, 55 | request: 56 | obj.request + 57 | (obj.directory ? "/" : "") + 58 | (directory ? obj.fragment.slice(0, -1) : obj.fragment), 59 | fragment: "", 60 | }; 61 | resolver.doResolve( 62 | target, 63 | alternative, 64 | null, 65 | resolveContext, 66 | (err, result) => { 67 | if (err) return callback(err); 68 | if (result) return callback(null, result); 69 | resolver.doResolve(target, obj, null, resolveContext, callback); 70 | }, 71 | ); 72 | return; 73 | } 74 | resolver.doResolve(target, obj, null, resolveContext, callback); 75 | }); 76 | } 77 | }; 78 | -------------------------------------------------------------------------------- /test/SyncAsyncFileSystemDecorator.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const fs = require("fs"); 4 | const path = require("path"); 5 | const SyncAsyncFileSystemDecorator = require("../lib/SyncAsyncFileSystemDecorator"); 6 | 7 | describe("syncAsyncFileSystemDecorator stat", () => { 8 | it("should use options when they're provided", (done) => { 9 | const decoratedFs = new SyncAsyncFileSystemDecorator(fs); 10 | decoratedFs.stat( 11 | path.join(__dirname, "fixtures", "decorated-fs", "exists.js"), 12 | { bigint: true }, 13 | (error, result) => { 14 | expect(error).toBeNull(); 15 | expect(result).toHaveProperty("size"); 16 | expect(result).toHaveProperty("birthtime"); 17 | expect( 18 | typeof (/** @type {import("fs").BigIntStats} */ (result).size), 19 | ).toBe("bigint"); 20 | done(); 21 | }, 22 | ); 23 | }); 24 | 25 | it("should work correctly when no options provided", (done) => { 26 | const decoratedFs = new SyncAsyncFileSystemDecorator(fs); 27 | decoratedFs.stat( 28 | path.join(__dirname, "fixtures", "decorated-fs", "exists.js"), 29 | (error, result) => { 30 | expect(error).toBeNull(); 31 | expect(result).toHaveProperty("size"); 32 | expect(result).toHaveProperty("birthtime"); 33 | expect(typeof (/** @type {import("fs").Stats} */ (result).size)).toBe( 34 | "number", 35 | ); 36 | done(); 37 | }, 38 | ); 39 | }); 40 | }); 41 | 42 | describe("syncAsyncFileSystemDecorator lstat", () => { 43 | it("should use options when they're provided", (done) => { 44 | const decoratedFs = new SyncAsyncFileSystemDecorator(fs); 45 | if (decoratedFs.lstat) { 46 | decoratedFs.lstat( 47 | path.join(__dirname, "fixtures", "decorated-fs", "exists.js"), 48 | { bigint: true }, 49 | (error, result) => { 50 | expect(error).toBeNull(); 51 | expect(result).toHaveProperty("size"); 52 | expect(result).toHaveProperty("birthtime"); 53 | expect( 54 | typeof (/** @type {import("fs").BigIntStats} */ (result).size), 55 | ).toBe("bigint"); 56 | done(); 57 | }, 58 | ); 59 | } 60 | }); 61 | 62 | it("should work correctly when no options provided", (done) => { 63 | const decoratedFs = new SyncAsyncFileSystemDecorator(fs); 64 | if (decoratedFs.lstat) { 65 | decoratedFs.lstat( 66 | path.join(__dirname, "fixtures", "decorated-fs", "exists.js"), 67 | (error, result) => { 68 | expect(error).toBeNull(); 69 | expect(result).toHaveProperty("size"); 70 | expect(result).toHaveProperty("birthtime"); 71 | expect(typeof (/** @type {import("fs").Stats} */ (result).size)).toBe( 72 | "number", 73 | ); 74 | done(); 75 | }, 76 | ); 77 | } 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /lib/MainFieldPlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | const path = require("path"); 9 | const DescriptionFileUtils = require("./DescriptionFileUtils"); 10 | 11 | /** @typedef {import("./Resolver")} Resolver */ 12 | /** @typedef {import("./Resolver").JsonObject} JsonObject */ 13 | /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */ 14 | /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ 15 | 16 | /** @typedef {{name: string|Array, forceRelative: boolean}} MainFieldOptions */ 17 | 18 | const alreadyTriedMainField = Symbol("alreadyTriedMainField"); 19 | 20 | module.exports = class MainFieldPlugin { 21 | /** 22 | * @param {string | ResolveStepHook} source source 23 | * @param {MainFieldOptions} options options 24 | * @param {string | ResolveStepHook} target target 25 | */ 26 | constructor(source, options, target) { 27 | this.source = source; 28 | this.options = options; 29 | this.target = target; 30 | } 31 | 32 | /** 33 | * @param {Resolver} resolver the resolver 34 | * @returns {void} 35 | */ 36 | apply(resolver) { 37 | const target = resolver.ensureHook(this.target); 38 | resolver 39 | .getHook(this.source) 40 | .tapAsync("MainFieldPlugin", (request, resolveContext, callback) => { 41 | if ( 42 | request.path !== request.descriptionFileRoot || 43 | /** @type {ResolveRequest & { [alreadyTriedMainField]?: string }} */ 44 | (request)[alreadyTriedMainField] === request.descriptionFilePath || 45 | !request.descriptionFilePath 46 | ) { 47 | return callback(); 48 | } 49 | const filename = path.basename(request.descriptionFilePath); 50 | let mainModule = 51 | /** @type {string|null|undefined} */ 52 | ( 53 | DescriptionFileUtils.getField( 54 | /** @type {JsonObject} */ (request.descriptionFileData), 55 | this.options.name, 56 | ) 57 | ); 58 | 59 | if ( 60 | !mainModule || 61 | typeof mainModule !== "string" || 62 | mainModule === "." || 63 | mainModule === "./" 64 | ) { 65 | return callback(); 66 | } 67 | if (this.options.forceRelative && !/^\.\.?\//.test(mainModule)) { 68 | mainModule = `./${mainModule}`; 69 | } 70 | /** @type {ResolveRequest & { [alreadyTriedMainField]?: string }} */ 71 | const obj = { 72 | ...request, 73 | request: mainModule, 74 | module: false, 75 | directory: mainModule.endsWith("/"), 76 | [alreadyTriedMainField]: request.descriptionFilePath, 77 | }; 78 | return resolver.doResolve( 79 | target, 80 | obj, 81 | `use ${mainModule} from ${this.options.name} in ${filename}`, 82 | resolveContext, 83 | callback, 84 | ); 85 | }); 86 | } 87 | }; 88 | -------------------------------------------------------------------------------- /lib/ModulesInHierarchicalDirectoriesPlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | const forEachBail = require("./forEachBail"); 9 | const getPaths = require("./getPaths"); 10 | 11 | /** @typedef {import("./Resolver")} Resolver */ 12 | /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */ 13 | /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ 14 | 15 | module.exports = class ModulesInHierarchicalDirectoriesPlugin { 16 | /** 17 | * @param {string | ResolveStepHook} source source 18 | * @param {string | Array} directories directories 19 | * @param {string | ResolveStepHook} target target 20 | */ 21 | constructor(source, directories, target) { 22 | this.source = source; 23 | this.directories = /** @type {Array} */ [...directories]; 24 | this.target = target; 25 | } 26 | 27 | /** 28 | * @param {Resolver} resolver the resolver 29 | * @returns {void} 30 | */ 31 | apply(resolver) { 32 | const target = resolver.ensureHook(this.target); 33 | resolver 34 | .getHook(this.source) 35 | .tapAsync( 36 | "ModulesInHierarchicalDirectoriesPlugin", 37 | (request, resolveContext, callback) => { 38 | const fs = resolver.fileSystem; 39 | const addrs = getPaths(/** @type {string} */ (request.path)) 40 | .paths.map((path) => 41 | this.directories.map((directory) => 42 | resolver.join(path, directory), 43 | ), 44 | ) 45 | .reduce((array, path) => { 46 | array.push(...path); 47 | return array; 48 | }, []); 49 | forEachBail( 50 | addrs, 51 | /** 52 | * @param {string} addr addr 53 | * @param {(err?: null|Error, result?: null|ResolveRequest) => void} callback callback 54 | * @returns {void} 55 | */ 56 | (addr, callback) => { 57 | fs.stat(addr, (err, stat) => { 58 | if (!err && stat && stat.isDirectory()) { 59 | /** @type {ResolveRequest} */ 60 | const obj = { 61 | ...request, 62 | path: addr, 63 | request: `./${request.request}`, 64 | module: false, 65 | }; 66 | const message = `looking for modules in ${addr}`; 67 | return resolver.doResolve( 68 | target, 69 | obj, 70 | message, 71 | resolveContext, 72 | callback, 73 | ); 74 | } 75 | if (resolveContext.log) { 76 | resolveContext.log( 77 | `${addr} doesn't exist or is not a directory`, 78 | ); 79 | } 80 | if (resolveContext.missingDependencies) { 81 | resolveContext.missingDependencies.add(addr); 82 | } 83 | return callback(); 84 | }); 85 | }, 86 | callback, 87 | ); 88 | }, 89 | ); 90 | } 91 | }; 92 | -------------------------------------------------------------------------------- /test/browserField.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const fs = require("fs"); 4 | const path = require("path"); 5 | const { ResolverFactory } = require("../"); 6 | 7 | const browserModule = path.join(__dirname, "fixtures", "browser-module"); 8 | 9 | /** 10 | * @param {string[]} args args 11 | * @returns {string} path 12 | */ 13 | function p(...args) { 14 | return path.join(browserModule, ...args); 15 | } 16 | 17 | describe("browserField", () => { 18 | let resolver; 19 | 20 | beforeEach(() => { 21 | resolver = ResolverFactory.createResolver({ 22 | aliasFields: [ 23 | "browser", 24 | ["innerBrowser1", "field2", "browser"], // not presented 25 | ["innerBrowser1", "field", "browser"], 26 | ["innerBrowser2", "browser"], 27 | ], 28 | useSyncFileSystemCalls: true, 29 | fileSystem: fs, 30 | }); 31 | }); 32 | 33 | it("should ignore", (done) => { 34 | resolver.resolve({}, p(), "./lib/ignore", {}, (err, result) => { 35 | if (err) throw err; 36 | expect(result).toBe(false); 37 | done(); 38 | }); 39 | }); 40 | 41 | it("should ignore #2", () => { 42 | expect(resolver.resolveSync({}, p(), "./lib/ignore")).toBe(false); 43 | expect(resolver.resolveSync({}, p(), "./lib/ignore.js")).toBe(false); 44 | expect(resolver.resolveSync({}, p("lib"), "./ignore")).toBe(false); 45 | expect(resolver.resolveSync({}, p("lib"), "./ignore.js")).toBe(false); 46 | }); 47 | 48 | it("should replace a file", () => { 49 | expect(resolver.resolveSync({}, p(), "./lib/replaced")).toEqual( 50 | p("lib", "browser.js"), 51 | ); 52 | expect(resolver.resolveSync({}, p(), "./lib/replaced.js")).toEqual( 53 | p("lib", "browser.js"), 54 | ); 55 | expect(resolver.resolveSync({}, p("lib"), "./replaced")).toEqual( 56 | p("lib", "browser.js"), 57 | ); 58 | expect(resolver.resolveSync({}, p("lib"), "./replaced.js")).toEqual( 59 | p("lib", "browser.js"), 60 | ); 61 | }); 62 | 63 | it("should replace a module with a file", () => { 64 | expect(resolver.resolveSync({}, p(), "module-a")).toEqual( 65 | p("browser", "module-a.js"), 66 | ); 67 | expect(resolver.resolveSync({}, p("lib"), "module-a")).toEqual( 68 | p("browser", "module-a.js"), 69 | ); 70 | }); 71 | 72 | it("should replace a module with a module", () => { 73 | expect(resolver.resolveSync({}, p(), "module-b")).toEqual( 74 | p("node_modules", "module-c.js"), 75 | ); 76 | expect(resolver.resolveSync({}, p("lib"), "module-b")).toEqual( 77 | p("node_modules", "module-c.js"), 78 | ); 79 | }); 80 | 81 | it("should resolve in nested property", () => { 82 | expect(resolver.resolveSync({}, p(), "./lib/main1.js")).toEqual( 83 | p("lib", "main.js"), 84 | ); 85 | expect(resolver.resolveSync({}, p(), "./lib/main2.js")).toEqual( 86 | p("lib", "browser.js"), 87 | ); 88 | }); 89 | 90 | it("should check only alias field properties", () => { 91 | expect(resolver.resolveSync({}, p(), "./toString")).toEqual( 92 | p("lib", "toString.js"), 93 | ); 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /test/extension-alias.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const fs = require("fs"); 4 | const path = require("path"); 5 | 6 | const CachedInputFileSystem = require("../lib/CachedInputFileSystem"); 7 | const ResolverFactory = require("../lib/ResolverFactory"); 8 | 9 | /** @typedef {import("../lib/util/entrypoints").ImportsField} ImportsField */ 10 | 11 | describe("extension-alias", () => { 12 | const fixture = path.resolve(__dirname, "fixtures", "extension-alias"); 13 | const nodeFileSystem = new CachedInputFileSystem(fs, 4000); 14 | 15 | const resolver = ResolverFactory.createResolver({ 16 | extensions: [".js"], 17 | fileSystem: nodeFileSystem, 18 | mainFiles: ["index.js"], 19 | extensionAlias: { 20 | ".js": [".ts", ".js"], 21 | ".mjs": ".mts", 22 | }, 23 | }); 24 | 25 | it("should alias fully specified file", (done) => { 26 | resolver.resolve({}, fixture, "./index.js", {}, (err, result) => { 27 | if (err) return done(err); 28 | expect(result).toEqual(path.resolve(fixture, "index.ts")); 29 | done(); 30 | }); 31 | }); 32 | 33 | it("should alias fully specified file when there are two alternatives", (done) => { 34 | resolver.resolve({}, fixture, "./dir/index.js", {}, (err, result) => { 35 | if (err) return done(err); 36 | expect(result).toEqual(path.resolve(fixture, "dir", "index.ts")); 37 | done(); 38 | }); 39 | }); 40 | 41 | it("should also allow the second alternative", (done) => { 42 | resolver.resolve({}, fixture, "./dir2/index.js", {}, (err, result) => { 43 | if (err) return done(err); 44 | expect(result).toEqual(path.resolve(fixture, "dir2", "index.js")); 45 | done(); 46 | }); 47 | }); 48 | 49 | it("should support alias option without an array", (done) => { 50 | resolver.resolve({}, fixture, "./dir2/index.mjs", {}, (err, result) => { 51 | if (err) return done(err); 52 | expect(result).toEqual(path.resolve(fixture, "dir2", "index.mts")); 53 | done(); 54 | }); 55 | }); 56 | 57 | it("should not allow to fallback to the original extension or add extensions", (done) => { 58 | resolver.resolve({}, fixture, "./index.mjs", {}, (err, _result) => { 59 | expect(err).toBeInstanceOf(Error); 60 | done(); 61 | }); 62 | }); 63 | 64 | describe("should not apply extension alias to extensions or mainFiles field", () => { 65 | const resolver = ResolverFactory.createResolver({ 66 | extensions: [".js"], 67 | fileSystem: nodeFileSystem, 68 | mainFiles: ["index.js"], 69 | extensionAlias: { 70 | ".js": [], 71 | }, 72 | }); 73 | 74 | it("directory", (done) => { 75 | resolver.resolve({}, fixture, "./dir2", {}, (err, result) => { 76 | if (err) return done(err); 77 | expect(result).toEqual(path.resolve(fixture, "dir2", "index.js")); 78 | done(); 79 | }); 80 | }); 81 | 82 | it("file", (done) => { 83 | resolver.resolve({}, fixture, "./dir2/index", {}, (err, result) => { 84 | if (err) return done(err); 85 | expect(result).toEqual(path.resolve(fixture, "dir2", "index.js")); 86 | done(); 87 | }); 88 | }); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /lib/DescriptionFilePlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | const DescriptionFileUtils = require("./DescriptionFileUtils"); 9 | 10 | /** @typedef {import("./Resolver")} Resolver */ 11 | /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */ 12 | /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ 13 | 14 | module.exports = class DescriptionFilePlugin { 15 | /** 16 | * @param {string | ResolveStepHook} source source 17 | * @param {string[]} filenames filenames 18 | * @param {boolean} pathIsFile pathIsFile 19 | * @param {string | ResolveStepHook} target target 20 | */ 21 | constructor(source, filenames, pathIsFile, target) { 22 | this.source = source; 23 | this.filenames = filenames; 24 | this.pathIsFile = pathIsFile; 25 | this.target = target; 26 | } 27 | 28 | /** 29 | * @param {Resolver} resolver the resolver 30 | * @returns {void} 31 | */ 32 | apply(resolver) { 33 | const target = resolver.ensureHook(this.target); 34 | resolver 35 | .getHook(this.source) 36 | .tapAsync( 37 | "DescriptionFilePlugin", 38 | (request, resolveContext, callback) => { 39 | const { path } = request; 40 | if (!path) return callback(); 41 | const directory = this.pathIsFile 42 | ? DescriptionFileUtils.cdUp(path) 43 | : path; 44 | if (!directory) return callback(); 45 | DescriptionFileUtils.loadDescriptionFile( 46 | resolver, 47 | directory, 48 | this.filenames, 49 | request.descriptionFilePath 50 | ? { 51 | path: request.descriptionFilePath, 52 | content: request.descriptionFileData, 53 | directory: 54 | /** @type {string} */ 55 | (request.descriptionFileRoot), 56 | } 57 | : undefined, 58 | resolveContext, 59 | (err, result) => { 60 | if (err) return callback(err); 61 | if (!result) { 62 | if (resolveContext.log) { 63 | resolveContext.log( 64 | `No description file found in ${directory} or above`, 65 | ); 66 | } 67 | return callback(); 68 | } 69 | const relativePath = `.${path 70 | .slice(result.directory.length) 71 | .replace(/\\/g, "/")}`; 72 | /** @type {ResolveRequest} */ 73 | const obj = { 74 | ...request, 75 | descriptionFilePath: result.path, 76 | descriptionFileData: result.content, 77 | descriptionFileRoot: result.directory, 78 | relativePath, 79 | }; 80 | resolver.doResolve( 81 | target, 82 | obj, 83 | `using description file: ${result.path} (relative path: ${relativePath})`, 84 | resolveContext, 85 | (err, result) => { 86 | if (err) return callback(err); 87 | 88 | // Don't allow other processing 89 | if (result === undefined) return callback(null, null); 90 | callback(null, result); 91 | }, 92 | ); 93 | }, 94 | ); 95 | }, 96 | ); 97 | } 98 | }; 99 | -------------------------------------------------------------------------------- /lib/SymlinkPlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | const forEachBail = require("./forEachBail"); 9 | const getPaths = require("./getPaths"); 10 | const { PathType, getType } = require("./util/path"); 11 | 12 | /** @typedef {import("./Resolver")} Resolver */ 13 | /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */ 14 | /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ 15 | 16 | module.exports = class SymlinkPlugin { 17 | /** 18 | * @param {string | ResolveStepHook} source source 19 | * @param {string | ResolveStepHook} target target 20 | */ 21 | constructor(source, target) { 22 | this.source = source; 23 | this.target = target; 24 | } 25 | 26 | /** 27 | * @param {Resolver} resolver the resolver 28 | * @returns {void} 29 | */ 30 | apply(resolver) { 31 | const target = resolver.ensureHook(this.target); 32 | const fs = resolver.fileSystem; 33 | resolver 34 | .getHook(this.source) 35 | .tapAsync("SymlinkPlugin", (request, resolveContext, callback) => { 36 | if (request.ignoreSymlinks) return callback(); 37 | const pathsResult = getPaths(/** @type {string} */ (request.path)); 38 | const pathSegments = pathsResult.segments; 39 | const { paths } = pathsResult; 40 | 41 | let containsSymlink = false; 42 | let idx = -1; 43 | forEachBail( 44 | paths, 45 | /** 46 | * @param {string} path path 47 | * @param {(err?: null|Error, result?: null|number) => void} callback callback 48 | * @returns {void} 49 | */ 50 | (path, callback) => { 51 | idx++; 52 | if (resolveContext.fileDependencies) { 53 | resolveContext.fileDependencies.add(path); 54 | } 55 | fs.readlink(path, (err, result) => { 56 | if (!err && result) { 57 | pathSegments[idx] = /** @type {string} */ (result); 58 | containsSymlink = true; 59 | // Shortcut when absolute symlink found 60 | const resultType = getType(result.toString()); 61 | if ( 62 | resultType === PathType.AbsoluteWin || 63 | resultType === PathType.AbsolutePosix 64 | ) { 65 | return callback(null, idx); 66 | } 67 | } 68 | callback(); 69 | }); 70 | }, 71 | /** 72 | * @param {(null | Error)=} err error 73 | * @param {(null|number)=} idx result 74 | * @returns {void} 75 | */ 76 | (err, idx) => { 77 | if (!containsSymlink) return callback(); 78 | const resultSegments = 79 | typeof idx === "number" 80 | ? pathSegments.slice(0, idx + 1) 81 | : [...pathSegments]; 82 | const result = resultSegments.reduceRight((a, b) => 83 | resolver.join(a, b), 84 | ); 85 | /** @type {ResolveRequest} */ 86 | const obj = { 87 | ...request, 88 | path: result, 89 | }; 90 | resolver.doResolve( 91 | target, 92 | obj, 93 | `resolved symlink to ${result}`, 94 | resolveContext, 95 | callback, 96 | ); 97 | }, 98 | ); 99 | }); 100 | } 101 | }; 102 | -------------------------------------------------------------------------------- /test/dependencies.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { Volume } = require("memfs"); 4 | const resolve = require("../"); 5 | 6 | describe("dependencies", () => { 7 | let resolver; 8 | 9 | beforeEach(() => { 10 | const fileSystem = Volume.fromJSON( 11 | { 12 | "/a/b/node_modules/some-module/index.js": "", 13 | "/a/node_modules/module/package.json": JSON.stringify({ 14 | main: "entry.js", 15 | }), 16 | "/a/node_modules/module/file.js": JSON.stringify({ main: "entry.js" }), 17 | "/modules/other-module/file.js": "", 18 | }, 19 | "/", 20 | ); 21 | resolver = resolve.create({ 22 | extensions: [".json", ".js"], 23 | modules: ["/modules", "node_modules"], 24 | // @ts-expect-error for tests 25 | fileSystem, 26 | }); 27 | }); 28 | 29 | const testCases = [ 30 | { 31 | name: "middle module request", 32 | context: "/a/b/c", 33 | request: "module/file", 34 | result: "/a/node_modules/module/file.js", 35 | fileDependencies: [ 36 | // found package.json 37 | "/a/node_modules/module/package.json", 38 | // symlink checks 39 | "/a/node_modules/module/file.js", 40 | "/a/node_modules/module", 41 | "/a/node_modules", 42 | "/a", 43 | "/", 44 | ], 45 | missingDependencies: [ 46 | // missing package.jsons 47 | "/a/b/c/package.json", 48 | "/a/b/package.json", 49 | "/a/package.json", 50 | "/package.json", 51 | // missing modules directories 52 | "/a/b/c/node_modules", 53 | // missing single file modules 54 | "/modules/module", 55 | "/a/b/node_modules/module", 56 | // missing files with alterative extensions 57 | "/a/node_modules/module/file", 58 | "/a/node_modules/module/file.json", 59 | ], 60 | }, 61 | { 62 | name: "fast found module request", 63 | context: "/a/b/c", 64 | request: "other-module/file.js", 65 | result: "/modules/other-module/file.js", 66 | fileDependencies: [ 67 | // symlink checks 68 | "/modules/other-module/file.js", 69 | "/modules/other-module", 70 | "/modules", 71 | "/", 72 | ], 73 | missingDependencies: [ 74 | // missing package.jsons 75 | "/a/b/c/package.json", 76 | "/a/b/package.json", 77 | "/a/package.json", 78 | "/package.json", 79 | "/modules/other-module/package.json", 80 | "/modules/package.json", 81 | ], 82 | }, 83 | ]; 84 | 85 | for (const testCase of testCases) { 86 | // eslint-disable-next-line no-loop-func 87 | it(`should report correct dependencies for ${testCase.name}`, (done) => { 88 | const fileDependencies = new Set(); 89 | const missingDependencies = new Set(); 90 | resolver( 91 | testCase.context, 92 | testCase.request, 93 | { 94 | fileDependencies, 95 | missingDependencies, 96 | }, 97 | (err, result) => { 98 | if (err) return done(err); 99 | 100 | expect(result).toEqual(testCase.result); 101 | expect([...fileDependencies].sort()).toEqual( 102 | testCase.fileDependencies.sort(), 103 | ); 104 | expect([...missingDependencies].sort()).toEqual( 105 | testCase.missingDependencies.sort(), 106 | ); 107 | done(); 108 | }, 109 | ); 110 | }); 111 | } 112 | }); 113 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "enhanced-resolve", 3 | "version": "5.18.4", 4 | "description": "Offers a async require.resolve function. It's highly configurable.", 5 | "homepage": "http://github.com/webpack/enhanced-resolve", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/webpack/enhanced-resolve.git" 9 | }, 10 | "license": "MIT", 11 | "author": "Tobias Koppers @sokra", 12 | "main": "lib/index.js", 13 | "browser": { 14 | "process": "./lib/util/process-browser.js", 15 | "module": "./lib/util/module-browser.js" 16 | }, 17 | "types": "types.d.ts", 18 | "files": [ 19 | "lib", 20 | "types.d.ts", 21 | "LICENSE" 22 | ], 23 | "scripts": { 24 | "prepare": "husky", 25 | "lint": "npm run lint:code && npm run lint:types && npm run lint:types-test && npm run lint:special && npm run fmt:check && npm run lint:spellcheck", 26 | "lint:code": "eslint --cache .", 27 | "lint:special": "node node_modules/tooling/inherit-types && node node_modules/tooling/generate-types", 28 | "lint:types": "tsc", 29 | "lint:types-test": "tsc -p tsconfig.types.test.json", 30 | "lint:spellcheck": "cspell --cache --no-must-find-files --quiet \"**/*.*\"", 31 | "fmt": "npm run fmt:base -- --loglevel warn --write", 32 | "fmt:check": "npm run fmt:base -- --check", 33 | "fmt:base": "node_modules/prettier/bin/prettier.cjs --cache --ignore-unknown .", 34 | "fix": "npm run fix:code && npm run fix:special", 35 | "fix:code": "npm run lint:code -- --fix", 36 | "fix:special": "node node_modules/tooling/inherit-types --write && node node_modules/tooling/generate-types --write", 37 | "type-report": "rimraf coverage && npm run cover:types && npm run cover:report && open-cli coverage/lcov-report/index.html", 38 | "pretest": "npm run lint", 39 | "test": "npm run test:coverage", 40 | "test:only": "jest", 41 | "test:watch": "npm run test:only -- --watch", 42 | "test:coverage": "npm run test:only -- --collectCoverageFrom=\"lib/**/*.js\" --coverage" 43 | }, 44 | "lint-staged": { 45 | "*.{js,cjs,mjs}": [ 46 | "eslint --cache --fix" 47 | ], 48 | "*": [ 49 | "prettier --cache --write --ignore-unknown", 50 | "cspell --cache --no-must-find-files" 51 | ] 52 | }, 53 | "dependencies": { 54 | "graceful-fs": "^4.2.4", 55 | "tapable": "^2.3.0" 56 | }, 57 | "devDependencies": { 58 | "@eslint/js": "^9.39.2", 59 | "@eslint/markdown": "^7.5.1", 60 | "@types/graceful-fs": "^4.1.6", 61 | "@types/jest": "^27.5.1", 62 | "@types/node": "^24.10.4", 63 | "@stylistic/eslint-plugin": "^5.6.1", 64 | "cspell": "9.4.0", 65 | "eslint": "^9.39.2", 66 | "eslint-config-prettier": "^10.1.5", 67 | "eslint-config-webpack": "^4.7.3", 68 | "eslint-plugin-import": "^2.31.0", 69 | "eslint-plugin-jest": "^29.5.0", 70 | "eslint-plugin-jsdoc": "^61.5.0", 71 | "eslint-plugin-n": "^17.23.1", 72 | "eslint-plugin-prettier": "^5.5.3", 73 | "eslint-plugin-unicorn": "^62.0.0", 74 | "globals": "^16.5.0", 75 | "husky": "^9.1.7", 76 | "jest": "^27.5.1", 77 | "lint-staged": "^16.2.7", 78 | "memfs": "^3.5.3", 79 | "prettier": "^3.7.4", 80 | "prettier-2": "npm:prettier@^2", 81 | "tooling": "webpack/tooling#v1.24.4", 82 | "typescript": "^5.9.3" 83 | }, 84 | "engines": { 85 | "node": ">=10.13.0" 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /test/missing.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const path = require("path"); 4 | const resolve = require("../"); 5 | 6 | describe("missing", () => { 7 | /** 8 | * @type {Array<[string, string, Array]>} 9 | */ 10 | const testCases = [ 11 | [ 12 | path.join(__dirname, "fixtures"), 13 | "./missing-file", 14 | [ 15 | path.join(__dirname, "fixtures", "missing-file"), 16 | path.join(__dirname, "fixtures", "missing-file.js"), 17 | path.join(__dirname, "fixtures", "missing-file.node"), 18 | ], 19 | ], 20 | [ 21 | path.join(__dirname, "fixtures"), 22 | "missing-module", 23 | [ 24 | path.join(__dirname, "fixtures", "node_modules", "missing-module"), 25 | path.join(__dirname, "..", "node_modules", "missing-module"), 26 | ], 27 | ], 28 | [ 29 | path.join(__dirname, "fixtures"), 30 | "missing-module/missing-file", 31 | [ 32 | path.join(__dirname, "fixtures", "node_modules", "missing-module"), 33 | path.join(__dirname, "..", "node_modules", "missing-module"), 34 | ], 35 | ], 36 | [ 37 | path.join(__dirname, "fixtures"), 38 | "m1/missing-file", 39 | [ 40 | path.join(__dirname, "fixtures", "node_modules", "m1", "missing-file"), 41 | path.join( 42 | __dirname, 43 | "fixtures", 44 | "node_modules", 45 | "m1", 46 | "missing-file.js", 47 | ), 48 | path.join( 49 | __dirname, 50 | "fixtures", 51 | "node_modules", 52 | "m1", 53 | "missing-file.node", 54 | ), 55 | path.join(__dirname, "..", "node_modules", "m1"), 56 | ], 57 | ], 58 | [ 59 | path.join(__dirname, "fixtures"), 60 | "m1/", 61 | [ 62 | path.join(__dirname, "fixtures", "node_modules", "m1", "index"), 63 | path.join(__dirname, "fixtures", "node_modules", "m1", "index.js"), 64 | path.join(__dirname, "fixtures", "node_modules", "m1", "index.json"), 65 | path.join(__dirname, "fixtures", "node_modules", "m1", "index.node"), 66 | ], 67 | ], 68 | [ 69 | path.join(__dirname, "fixtures"), 70 | "m1/a", 71 | [path.join(__dirname, "fixtures", "node_modules", "m1", "a")], 72 | ], 73 | ]; 74 | for (const testCase of testCases) { 75 | it(`should tell about missing file when trying to resolve ${testCase[1]}`, (done) => { 76 | const missingDependencies = new Set(); 77 | /** 78 | * @param {Error | null} _err err 79 | * @param {string} _filename _filename 80 | */ 81 | function callback(_err, _filename) { 82 | expect([...missingDependencies].sort()).toEqual( 83 | expect.arrayContaining(testCase[2].sort()), 84 | ); 85 | done(); 86 | } 87 | resolve(testCase[0], testCase[1], { missingDependencies }, callback); 88 | }); 89 | 90 | it(`should report error details exactly once when trying to resolve ${testCase[1]}`, (done) => { 91 | /** 92 | * @param {Error & { details: string } | null} err err 93 | * @param {string} _filename _filename 94 | */ 95 | function callback(err, _filename) { 96 | if (err) { 97 | const details = err.details.split("\n"); 98 | const firstDetail = details.shift(); 99 | 100 | expect(firstDetail).toContain(testCase[1]); 101 | expect(details).not.toContain(firstDetail); 102 | } 103 | 104 | done(); 105 | } 106 | 107 | resolve(testCase[0], testCase[1], callback); 108 | }); 109 | } 110 | }); 111 | -------------------------------------------------------------------------------- /test/restrictions.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const fs = require("fs"); 4 | const path = require("path"); 5 | const CachedInputFileSystem = require("../lib/CachedInputFileSystem"); 6 | const ResolverFactory = require("../lib/ResolverFactory"); 7 | 8 | const fixture = path.resolve(__dirname, "fixtures", "restrictions"); 9 | const nodeFileSystem = new CachedInputFileSystem(fs, 4000); 10 | 11 | describe("restrictions", () => { 12 | it("should respect RegExp restriction", (done) => { 13 | const resolver = ResolverFactory.createResolver({ 14 | extensions: [".js"], 15 | fileSystem: nodeFileSystem, 16 | restrictions: [/\.(sass|scss|css)$/], 17 | }); 18 | 19 | resolver.resolve({}, fixture, "pck1", {}, (err, result) => { 20 | if (!err) return done(new Error(`expect error, got ${result}`)); 21 | expect(err).toBeInstanceOf(Error); 22 | done(); 23 | }); 24 | }); 25 | 26 | it("should try to find alternative #1", (done) => { 27 | const resolver = ResolverFactory.createResolver({ 28 | extensions: [".js", ".css"], 29 | fileSystem: nodeFileSystem, 30 | mainFiles: ["index"], 31 | restrictions: [/\.(sass|scss|css)$/], 32 | }); 33 | 34 | resolver.resolve({}, fixture, "pck1", {}, (err, result) => { 35 | if (err) return done(err); 36 | if (!result) return done(new Error("No result")); 37 | expect(result).toEqual( 38 | path.resolve(fixture, "node_modules/pck1/index.css"), 39 | ); 40 | done(); 41 | }); 42 | }); 43 | 44 | it("should respect string restriction", (done) => { 45 | const resolver = ResolverFactory.createResolver({ 46 | extensions: [".js"], 47 | fileSystem: nodeFileSystem, 48 | restrictions: [fixture], 49 | }); 50 | 51 | resolver.resolve({}, fixture, "pck2", {}, (err, result) => { 52 | if (!err) return done(new Error(`expect error, got ${result}`)); 53 | expect(err).toBeInstanceOf(Error); 54 | done(); 55 | }); 56 | }); 57 | 58 | it("should try to find alternative #2", (done) => { 59 | const resolver = ResolverFactory.createResolver({ 60 | extensions: [".js"], 61 | fileSystem: nodeFileSystem, 62 | mainFields: ["main", "style"], 63 | restrictions: [fixture, /\.(sass|scss|css)$/], 64 | }); 65 | 66 | resolver.resolve({}, fixture, "pck2", {}, (err, result) => { 67 | if (err) return done(err); 68 | if (!result) return done(new Error("No result")); 69 | expect(result).toEqual( 70 | path.resolve(fixture, "node_modules/pck2/index.css"), 71 | ); 72 | done(); 73 | }); 74 | }); 75 | 76 | it("should try to find alternative #3", (done) => { 77 | const resolver = ResolverFactory.createResolver({ 78 | extensions: [".js"], 79 | fileSystem: nodeFileSystem, 80 | mainFields: ["main", "module", "style"], 81 | restrictions: [fixture, /\.(sass|scss|css)$/], 82 | }); 83 | 84 | const log = []; 85 | 86 | resolver.resolve( 87 | {}, 88 | fixture, 89 | "pck2", 90 | { log: log.push.bind(log) }, 91 | (err, result) => { 92 | if (err) return done(err); 93 | if (!result) return done(new Error("No result")); 94 | expect(result).toEqual( 95 | path.resolve(fixture, "node_modules/pck2/index.css"), 96 | ); 97 | expect( 98 | log.map((line) => 99 | line 100 | .replace(path.resolve(__dirname, ".."), "...") 101 | .replace(path.resolve(__dirname, ".."), "...") 102 | .replace(/\\/g, "/"), 103 | ), 104 | ).toMatchSnapshot(); 105 | done(); 106 | }, 107 | ); 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /lib/ExtensionAliasPlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Ivan Kopeykin @vankop 4 | */ 5 | 6 | "use strict"; 7 | 8 | const forEachBail = require("./forEachBail"); 9 | 10 | /** @typedef {import("./Resolver")} Resolver */ 11 | /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */ 12 | /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ 13 | /** @typedef {{ alias: string|string[], extension: string }} ExtensionAliasOption */ 14 | 15 | module.exports = class ExtensionAliasPlugin { 16 | /** 17 | * @param {string | ResolveStepHook} source source 18 | * @param {ExtensionAliasOption} options options 19 | * @param {string | ResolveStepHook} target target 20 | */ 21 | constructor(source, options, target) { 22 | this.source = source; 23 | this.options = options; 24 | this.target = target; 25 | } 26 | 27 | /** 28 | * @param {Resolver} resolver the resolver 29 | * @returns {void} 30 | */ 31 | apply(resolver) { 32 | const target = resolver.ensureHook(this.target); 33 | const { extension, alias } = this.options; 34 | resolver 35 | .getHook(this.source) 36 | .tapAsync("ExtensionAliasPlugin", (request, resolveContext, callback) => { 37 | const requestPath = request.request; 38 | if (!requestPath || !requestPath.endsWith(extension)) return callback(); 39 | const isAliasString = typeof alias === "string"; 40 | /** 41 | * @param {string} alias extension alias 42 | * @param {(err?: null | Error, result?: null|ResolveRequest) => void} callback callback 43 | * @param {number=} index index 44 | * @returns {void} 45 | */ 46 | const resolve = (alias, callback, index) => { 47 | const newRequest = `${requestPath.slice( 48 | 0, 49 | -extension.length, 50 | )}${alias}`; 51 | 52 | return resolver.doResolve( 53 | target, 54 | { 55 | ...request, 56 | request: newRequest, 57 | fullySpecified: true, 58 | }, 59 | `aliased from extension alias with mapping '${extension}' to '${alias}'`, 60 | resolveContext, 61 | (err, result) => { 62 | // Throw error if we are on the last alias (for multiple aliases) and it failed, always throw if we are not an array or we have only one alias 63 | if (!isAliasString && index) { 64 | if (index !== this.options.alias.length) { 65 | if (resolveContext.log) { 66 | resolveContext.log( 67 | `Failed to alias from extension alias with mapping '${extension}' to '${alias}' for '${newRequest}': ${err}`, 68 | ); 69 | } 70 | 71 | return callback(null, result); 72 | } 73 | 74 | return callback(err, result); 75 | } 76 | callback(err, result); 77 | }, 78 | ); 79 | }; 80 | /** 81 | * @param {(null | Error)=} err error 82 | * @param {(null | ResolveRequest)=} result result 83 | * @returns {void} 84 | */ 85 | const stoppingCallback = (err, result) => { 86 | if (err) return callback(err); 87 | if (result) return callback(null, result); 88 | // Don't allow other aliasing or raw request 89 | return callback(null, null); 90 | }; 91 | if (isAliasString) { 92 | resolve(alias, stoppingCallback); 93 | } else if (alias.length > 1) { 94 | forEachBail(alias, resolve, stoppingCallback); 95 | } else { 96 | resolve(alias[0], stoppingCallback); 97 | } 98 | }); 99 | } 100 | }; 101 | -------------------------------------------------------------------------------- /lib/AliasFieldPlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | const DescriptionFileUtils = require("./DescriptionFileUtils"); 9 | const getInnerRequest = require("./getInnerRequest"); 10 | 11 | /** @typedef {import("./Resolver")} Resolver */ 12 | /** @typedef {import("./Resolver").JsonPrimitive} JsonPrimitive */ 13 | /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */ 14 | /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ 15 | 16 | module.exports = class AliasFieldPlugin { 17 | /** 18 | * @param {string | ResolveStepHook} source source 19 | * @param {string | Array} field field 20 | * @param {string | ResolveStepHook} target target 21 | */ 22 | constructor(source, field, target) { 23 | this.source = source; 24 | this.field = field; 25 | this.target = target; 26 | } 27 | 28 | /** 29 | * @param {Resolver} resolver the resolver 30 | * @returns {void} 31 | */ 32 | apply(resolver) { 33 | const target = resolver.ensureHook(this.target); 34 | resolver 35 | .getHook(this.source) 36 | .tapAsync("AliasFieldPlugin", (request, resolveContext, callback) => { 37 | if (!request.descriptionFileData) return callback(); 38 | const innerRequest = getInnerRequest(resolver, request); 39 | if (!innerRequest) return callback(); 40 | const fieldData = DescriptionFileUtils.getField( 41 | request.descriptionFileData, 42 | this.field, 43 | ); 44 | if (fieldData === null || typeof fieldData !== "object") { 45 | if (resolveContext.log) { 46 | resolveContext.log( 47 | `Field '${this.field}' doesn't contain a valid alias configuration`, 48 | ); 49 | } 50 | return callback(); 51 | } 52 | /** @type {JsonPrimitive | undefined} */ 53 | const data = Object.prototype.hasOwnProperty.call( 54 | fieldData, 55 | innerRequest, 56 | ) 57 | ? /** @type {{[Key in string]: JsonPrimitive}} */ (fieldData)[ 58 | innerRequest 59 | ] 60 | : innerRequest.startsWith("./") 61 | ? /** @type {{[Key in string]: JsonPrimitive}} */ (fieldData)[ 62 | innerRequest.slice(2) 63 | ] 64 | : undefined; 65 | if (data === innerRequest) return callback(); 66 | if (data === undefined) return callback(); 67 | if (data === false) { 68 | /** @type {ResolveRequest} */ 69 | const ignoreObj = { 70 | ...request, 71 | path: false, 72 | }; 73 | if (typeof resolveContext.yield === "function") { 74 | resolveContext.yield(ignoreObj); 75 | return callback(null, null); 76 | } 77 | return callback(null, ignoreObj); 78 | } 79 | /** @type {ResolveRequest} */ 80 | const obj = { 81 | ...request, 82 | path: /** @type {string} */ (request.descriptionFileRoot), 83 | request: /** @type {string} */ (data), 84 | fullySpecified: false, 85 | }; 86 | resolver.doResolve( 87 | target, 88 | obj, 89 | `aliased from description file ${ 90 | request.descriptionFilePath 91 | } with mapping '${innerRequest}' to '${/** @type {string} */ data}'`, 92 | resolveContext, 93 | (err, result) => { 94 | if (err) return callback(err); 95 | 96 | // Don't allow other aliasing or raw request 97 | if (result === undefined) return callback(null, null); 98 | callback(null, result); 99 | }, 100 | ); 101 | }); 102 | } 103 | }; 104 | -------------------------------------------------------------------------------- /test/__snapshots__/restrictions.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`restrictions should try to find alternative #3 1`] = ` 4 | Array [ 5 | "resolve 'pck2' in '.../test/fixtures/restrictions'", 6 | " Parsed request is a module", 7 | " using description file: .../package.json (relative path: ./test/fixtures/restrictions)", 8 | " resolve as module", 9 | " looking for modules in .../test/fixtures/restrictions/node_modules", 10 | " single file module", 11 | " using description file: .../package.json (relative path: ./test/fixtures/restrictions/node_modules/pck2)", 12 | " no extension", 13 | " .../test/fixtures/restrictions/node_modules/pck2 is not a file", 14 | " .js", 15 | " .../test/fixtures/restrictions/node_modules/pck2.js doesn't exist", 16 | " existing directory .../test/fixtures/restrictions/node_modules/pck2", 17 | " using description file: .../test/fixtures/restrictions/node_modules/pck2/package.json (relative path: .)", 18 | " using description file: .../package.json (relative path: ./test/fixtures/restrictions/node_modules/pck2)", 19 | " no extension", 20 | " .../test/fixtures/restrictions/node_modules/pck2 is not a file", 21 | " .js", 22 | " .../test/fixtures/restrictions/node_modules/pck2.js doesn't exist", 23 | " as directory", 24 | " existing directory .../test/fixtures/restrictions/node_modules/pck2", 25 | " using description file: .../test/fixtures/restrictions/node_modules/pck2/package.json (relative path: .)", 26 | " use ../../../c.js from main in package.json", 27 | " using description file: .../package.json (relative path: ./test/fixtures/c.js)", 28 | " no extension", 29 | " existing file: .../test/fixtures/c.js", 30 | " .../test/fixtures/c.js is not inside of the restriction .../test/fixtures/restrictions", 31 | " .js", 32 | " .../test/fixtures/c.js.js doesn't exist", 33 | " as directory", 34 | " .../test/fixtures/c.js is not a directory", 35 | " use ./module.js from module in package.json", 36 | " using description file: .../test/fixtures/restrictions/node_modules/pck2/package.json (relative path: ./module.js)", 37 | " no extension", 38 | " existing file: .../test/fixtures/restrictions/node_modules/pck2/module.js", 39 | " .../test/fixtures/restrictions/node_modules/pck2/module.js doesn't match the restriction //.(sass|scss|css)$/", 40 | " .js", 41 | " .../test/fixtures/restrictions/node_modules/pck2/module.js.js doesn't exist", 42 | " as directory", 43 | " .../test/fixtures/restrictions/node_modules/pck2/module.js is not a directory", 44 | " use ./index.css from style in package.json", 45 | " using description file: .../test/fixtures/restrictions/node_modules/pck2/package.json (relative path: ./index.css)", 46 | " no extension", 47 | " existing file: .../test/fixtures/restrictions/node_modules/pck2/index.css", 48 | " reporting result .../test/fixtures/restrictions/node_modules/pck2/index.css", 49 | ] 50 | `; 51 | -------------------------------------------------------------------------------- /lib/UnsafeCachePlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | /** @typedef {import("./Resolver")} Resolver */ 9 | /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */ 10 | /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ 11 | /** @typedef {import("./Resolver").ResolveContextYield} ResolveContextYield */ 12 | /** @typedef {{ [k: string]: undefined | ResolveRequest | ResolveRequest[] }} Cache */ 13 | 14 | /** 15 | * @param {string} type type of cache 16 | * @param {ResolveRequest} request request 17 | * @param {boolean} withContext cache with context? 18 | * @returns {string} cache id 19 | */ 20 | function getCacheId(type, request, withContext) { 21 | return JSON.stringify({ 22 | type, 23 | context: withContext ? request.context : "", 24 | path: request.path, 25 | query: request.query, 26 | fragment: request.fragment, 27 | request: request.request, 28 | }); 29 | } 30 | 31 | module.exports = class UnsafeCachePlugin { 32 | /** 33 | * @param {string | ResolveStepHook} source source 34 | * @param {(request: ResolveRequest) => boolean} filterPredicate filterPredicate 35 | * @param {Cache} cache cache 36 | * @param {boolean} withContext withContext 37 | * @param {string | ResolveStepHook} target target 38 | */ 39 | constructor(source, filterPredicate, cache, withContext, target) { 40 | this.source = source; 41 | this.filterPredicate = filterPredicate; 42 | this.withContext = withContext; 43 | this.cache = cache; 44 | this.target = target; 45 | } 46 | 47 | /** 48 | * @param {Resolver} resolver the resolver 49 | * @returns {void} 50 | */ 51 | apply(resolver) { 52 | const target = resolver.ensureHook(this.target); 53 | resolver 54 | .getHook(this.source) 55 | .tapAsync("UnsafeCachePlugin", (request, resolveContext, callback) => { 56 | if (!this.filterPredicate(request)) return callback(); 57 | const isYield = typeof resolveContext.yield === "function"; 58 | const cacheId = getCacheId( 59 | isYield ? "yield" : "default", 60 | request, 61 | this.withContext, 62 | ); 63 | const cacheEntry = this.cache[cacheId]; 64 | if (cacheEntry) { 65 | if (isYield) { 66 | const yield_ = 67 | /** @type {ResolveContextYield} */ 68 | (resolveContext.yield); 69 | if (Array.isArray(cacheEntry)) { 70 | for (const result of cacheEntry) yield_(result); 71 | } else { 72 | yield_(cacheEntry); 73 | } 74 | return callback(null, null); 75 | } 76 | return callback(null, /** @type {ResolveRequest} */ (cacheEntry)); 77 | } 78 | 79 | /** @type {ResolveContextYield | undefined} */ 80 | let yieldFn; 81 | /** @type {ResolveContextYield | undefined} */ 82 | let yield_; 83 | /** @type {ResolveRequest[]} */ 84 | const yieldResult = []; 85 | if (isYield) { 86 | yieldFn = resolveContext.yield; 87 | yield_ = (result) => { 88 | yieldResult.push(result); 89 | }; 90 | } 91 | 92 | resolver.doResolve( 93 | target, 94 | request, 95 | null, 96 | yield_ ? { ...resolveContext, yield: yield_ } : resolveContext, 97 | (err, result) => { 98 | if (err) return callback(err); 99 | if (isYield) { 100 | if (result) yieldResult.push(result); 101 | for (const result of yieldResult) { 102 | /** @type {ResolveContextYield} */ 103 | (yieldFn)(result); 104 | } 105 | this.cache[cacheId] = yieldResult; 106 | return callback(null, null); 107 | } 108 | if (result) return callback(null, (this.cache[cacheId] = result)); 109 | callback(); 110 | }, 111 | ); 112 | }); 113 | } 114 | }; 115 | -------------------------------------------------------------------------------- /test/extensions.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const fs = require("fs"); 4 | const path = require("path"); 5 | const { CachedInputFileSystem, ResolverFactory } = require("../"); 6 | 7 | const nodeFileSystem = new CachedInputFileSystem(fs, 4000); 8 | 9 | const resolver = ResolverFactory.createResolver({ 10 | extensions: [".ts", ".js"], 11 | fileSystem: nodeFileSystem, 12 | }); 13 | 14 | const resolver2 = ResolverFactory.createResolver({ 15 | extensions: [".ts", "", ".js"], 16 | fileSystem: nodeFileSystem, 17 | }); 18 | 19 | const resolver3 = ResolverFactory.createResolver({ 20 | extensions: [".ts", "", ".js"], 21 | enforceExtension: false, 22 | fileSystem: nodeFileSystem, 23 | }); 24 | 25 | const fixture = path.resolve(__dirname, "fixtures", "extensions"); 26 | 27 | describe("extensions", () => { 28 | it("should resolve according to order of provided extensions", (done) => { 29 | resolver.resolve({}, fixture, "./foo", {}, (err, result) => { 30 | if (err) return done(err); 31 | if (!result) return done(new Error("No result")); 32 | expect(result).toEqual(path.resolve(fixture, "foo.ts")); 33 | done(); 34 | }); 35 | }); 36 | 37 | it("should resolve according to order of provided extensions (dir index)", (done) => { 38 | resolver.resolve({}, fixture, "./dir", {}, (err, result) => { 39 | if (err) return done(err); 40 | if (!result) return done(new Error("No result")); 41 | expect(result).toEqual(path.resolve(fixture, "dir/index.ts")); 42 | done(); 43 | }); 44 | }); 45 | 46 | it("should resolve according to main field in module root", (done) => { 47 | resolver.resolve({}, fixture, ".", {}, (err, result) => { 48 | if (err) return done(err); 49 | if (!result) return done(new Error("No result")); 50 | expect(result).toEqual(path.resolve(fixture, "index.js")); 51 | done(); 52 | }); 53 | }); 54 | 55 | it("should resolve single file module before directory", (done) => { 56 | resolver.resolve({}, fixture, "module", {}, (err, result) => { 57 | if (err) return done(err); 58 | if (!result) return done(new Error("No result")); 59 | expect(result).toEqual(path.resolve(fixture, "node_modules/module.js")); 60 | done(); 61 | }); 62 | }); 63 | 64 | it("should resolve trailing slash directory before single file", (done) => { 65 | resolver.resolve({}, fixture, "module/", {}, (err, result) => { 66 | if (err) return done(err); 67 | if (!result) return done(new Error("No result")); 68 | expect(result).toEqual( 69 | path.resolve(fixture, "node_modules/module/index.ts"), 70 | ); 71 | done(); 72 | }); 73 | }); 74 | 75 | it("should not resolve to file when request has a trailing slash (relative)", (done) => { 76 | resolver.resolve({}, fixture, "./foo.js/", {}, (err, _result) => { 77 | if (!err) return done(new Error("No error")); 78 | expect(err).toBeInstanceOf(Error); 79 | done(); 80 | }); 81 | }); 82 | 83 | it("should not resolve to file when request has a trailing slash (module)", (done) => { 84 | resolver.resolve({}, fixture, "module.js/", {}, (err, _result) => { 85 | if (!err) return done(new Error("No error")); 86 | expect(err).toBeInstanceOf(Error); 87 | done(); 88 | }); 89 | }); 90 | 91 | it("should default enforceExtension to true when extensions includes an empty string", (done) => { 92 | const missingDependencies = new Set(); 93 | resolver2.resolve({}, fixture, "./foo", { missingDependencies }, () => { 94 | expect(missingDependencies).not.toContain(path.resolve(fixture, "foo")); 95 | done(); 96 | }); 97 | }); 98 | 99 | it("should respect enforceExtension when extensions includes an empty string", (done) => { 100 | const missingDependencies = new Set(); 101 | resolver3.resolve({}, fixture, "./foo", { missingDependencies }, () => { 102 | expect(missingDependencies).toContain(path.resolve(fixture, "foo")); 103 | done(); 104 | }); 105 | }); 106 | }); 107 | -------------------------------------------------------------------------------- /test/roots.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const fs = require("fs"); 4 | const path = require("path"); 5 | const CachedInputFileSystem = require("../lib/CachedInputFileSystem"); 6 | const ResolverFactory = require("../lib/ResolverFactory"); 7 | 8 | describe("roots", () => { 9 | const fixtures = path.resolve(__dirname, "fixtures"); 10 | const fileSystem = new CachedInputFileSystem(fs, 4000); 11 | const resolver = ResolverFactory.createResolver({ 12 | extensions: [".js"], 13 | alias: { 14 | foo: "/fixtures", 15 | }, 16 | roots: [__dirname, fixtures], 17 | fileSystem, 18 | }); 19 | 20 | const resolverPreferAbsolute = ResolverFactory.createResolver({ 21 | extensions: [".js"], 22 | alias: { 23 | foo: "/fixtures", 24 | }, 25 | roots: [__dirname, fixtures], 26 | fileSystem, 27 | preferAbsolute: true, 28 | plugins: [ 29 | { 30 | apply(resolver) { 31 | resolver.hooks.file.tap("Test", (request) => { 32 | if (/test.fixtures.*test.fixtures/.test(request.path)) { 33 | throw new Error("Simulate a fatal error in root path"); 34 | } 35 | }); 36 | }, 37 | }, 38 | ], 39 | }); 40 | 41 | const contextResolver = ResolverFactory.createResolver({ 42 | roots: [__dirname], 43 | fileSystem, 44 | resolveToContext: true, 45 | }); 46 | 47 | it("should respect roots option", (done) => { 48 | resolver.resolve({}, fixtures, "/fixtures/b.js", {}, (err, result) => { 49 | if (err) return done(err); 50 | if (!result) return done(new Error("No result")); 51 | expect(result).toEqual(path.resolve(fixtures, "b.js")); 52 | done(); 53 | }); 54 | }); 55 | 56 | it("should try another root option, if it exists", (done) => { 57 | resolver.resolve({}, fixtures, "/b.js", {}, (err, result) => { 58 | if (err) return done(err); 59 | if (!result) return done(new Error("No result")); 60 | expect(result).toEqual(path.resolve(fixtures, "b.js")); 61 | done(); 62 | }); 63 | }); 64 | 65 | it("should respect extension", (done) => { 66 | resolver.resolve({}, fixtures, "/fixtures/b", {}, (err, result) => { 67 | if (err) return done(err); 68 | if (!result) return done(new Error("No result")); 69 | expect(result).toEqual(path.resolve(fixtures, "b.js")); 70 | done(); 71 | }); 72 | }); 73 | 74 | it("should resolve in directory", (done) => { 75 | resolver.resolve( 76 | {}, 77 | fixtures, 78 | "/fixtures/extensions/dir", 79 | {}, 80 | (err, result) => { 81 | if (err) return done(err); 82 | if (!result) return done(new Error("No result")); 83 | expect(result).toEqual( 84 | path.resolve(fixtures, "extensions/dir/index.js"), 85 | ); 86 | done(); 87 | }, 88 | ); 89 | }); 90 | 91 | it("should respect aliases", (done) => { 92 | resolver.resolve({}, fixtures, "foo/b", {}, (err, result) => { 93 | if (err) return done(err); 94 | if (!result) return done(new Error("No result")); 95 | expect(result).toEqual(path.resolve(fixtures, "b.js")); 96 | done(); 97 | }); 98 | }); 99 | 100 | it("should support roots options with resolveToContext", (done) => { 101 | contextResolver.resolve( 102 | {}, 103 | fixtures, 104 | "/fixtures/lib", 105 | {}, 106 | (err, result) => { 107 | if (err) return done(err); 108 | if (!result) return done(new Error("No result")); 109 | expect(result).toEqual(path.resolve(fixtures, "lib")); 110 | done(); 111 | }, 112 | ); 113 | }); 114 | 115 | it("should not work with relative path", (done) => { 116 | resolver.resolve({}, fixtures, "fixtures/b.js", {}, (err, result) => { 117 | if (!err) return done(new Error(`expect error, got ${result}`)); 118 | expect(err).toBeInstanceOf(Error); 119 | done(); 120 | }); 121 | }); 122 | 123 | it("should resolve an absolute path (prefer absolute)", (done) => { 124 | resolverPreferAbsolute.resolve( 125 | {}, 126 | fixtures, 127 | path.join(fixtures, "b.js"), 128 | {}, 129 | (err, result) => { 130 | if (err) return done(err); 131 | if (!result) return done(new Error("No result")); 132 | expect(result).toEqual(path.resolve(fixtures, "b.js")); 133 | done(); 134 | }, 135 | ); 136 | }); 137 | }); 138 | -------------------------------------------------------------------------------- /test/fallback.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { Volume } = require("memfs"); 4 | const { ResolverFactory } = require("../"); 5 | 6 | describe("fallback", () => { 7 | let resolver; 8 | 9 | beforeEach(() => { 10 | const fileSystem = Volume.fromJSON( 11 | { 12 | "/a/index": "", 13 | "/a/dir/index": "", 14 | "/recursive/index": "", 15 | "/recursive/dir/index": "", 16 | "/recursive/dir/file": "", 17 | "/recursive/dir/dir/index": "", 18 | "/b/index": "", 19 | "/b/dir/index": "", 20 | "/c/index": "", 21 | "/c/dir/index": "", 22 | "/d/index.js": "", 23 | "/d/dir/.empty": "", 24 | "/e/index": "", 25 | "/e/anotherDir/index": "", 26 | "/e/dir/file": "", 27 | }, 28 | "/", 29 | ); 30 | resolver = ResolverFactory.createResolver({ 31 | fallback: { 32 | aliasA: "a", 33 | b$: "a/index", 34 | c$: "/a/index", 35 | multiAlias: ["b", "c", "d", "e", "a"], 36 | recursive: "recursive/dir", 37 | "/d/dir": "/c/dir", 38 | "/d/index.js": "/c/index", 39 | ignored: false, 40 | }, 41 | modules: "/", 42 | useSyncFileSystemCalls: true, 43 | // @ts-expect-error for test 44 | fileSystem, 45 | }); 46 | }); 47 | 48 | it("should resolve a not aliased module", () => { 49 | expect(resolver.resolveSync({}, "/", "a")).toBe("/a/index"); 50 | expect(resolver.resolveSync({}, "/", "a/index")).toBe("/a/index"); 51 | expect(resolver.resolveSync({}, "/", "a/dir")).toBe("/a/dir/index"); 52 | expect(resolver.resolveSync({}, "/", "a/dir/index")).toBe("/a/dir/index"); 53 | }); 54 | 55 | it("should resolve an fallback module", () => { 56 | expect(resolver.resolveSync({}, "/", "aliasA")).toBe("/a/index"); 57 | expect(resolver.resolveSync({}, "/", "aliasA/index")).toBe("/a/index"); 58 | expect(resolver.resolveSync({}, "/", "aliasA/dir")).toBe("/a/dir/index"); 59 | expect(resolver.resolveSync({}, "/", "aliasA/dir/index")).toBe( 60 | "/a/dir/index", 61 | ); 62 | }); 63 | 64 | it("should resolve an ignore module", () => { 65 | expect(resolver.resolveSync({}, "/", "ignored")).toBe(false); 66 | }); 67 | 68 | it("should resolve a recursive aliased module", () => { 69 | expect(resolver.resolveSync({}, "/", "recursive")).toBe("/recursive/index"); 70 | expect(resolver.resolveSync({}, "/", "recursive/index")).toBe( 71 | "/recursive/index", 72 | ); 73 | expect(resolver.resolveSync({}, "/", "recursive/dir")).toBe( 74 | "/recursive/dir/index", 75 | ); 76 | expect(resolver.resolveSync({}, "/", "recursive/dir/index")).toBe( 77 | "/recursive/dir/index", 78 | ); 79 | expect(resolver.resolveSync({}, "/", "recursive/file")).toBe( 80 | "/recursive/dir/file", 81 | ); 82 | }); 83 | 84 | it("should resolve a file aliased module with a query", () => { 85 | expect(resolver.resolveSync({}, "/", "b?query")).toBe("/b/index?query"); 86 | expect(resolver.resolveSync({}, "/", "c?query")).toBe("/c/index?query"); 87 | }); 88 | 89 | it("should resolve a path in a file aliased module", () => { 90 | expect(resolver.resolveSync({}, "/", "b/index")).toBe("/b/index"); 91 | expect(resolver.resolveSync({}, "/", "b/dir")).toBe("/b/dir/index"); 92 | expect(resolver.resolveSync({}, "/", "b/dir/index")).toBe("/b/dir/index"); 93 | expect(resolver.resolveSync({}, "/", "c/index")).toBe("/c/index"); 94 | expect(resolver.resolveSync({}, "/", "c/dir")).toBe("/c/dir/index"); 95 | expect(resolver.resolveSync({}, "/", "c/dir/index")).toBe("/c/dir/index"); 96 | }); 97 | 98 | it("should resolve a file in multiple aliased dirs", () => { 99 | expect(resolver.resolveSync({}, "/", "multiAlias/dir/file")).toBe( 100 | "/e/dir/file", 101 | ); 102 | expect(resolver.resolveSync({}, "/", "multiAlias/anotherDir")).toBe( 103 | "/e/anotherDir/index", 104 | ); 105 | }); 106 | 107 | it("should log the correct info", (done) => { 108 | const log = []; 109 | resolver.resolve( 110 | {}, 111 | "/", 112 | "aliasA/dir", 113 | { log: (v) => log.push(v) }, 114 | (err, result) => { 115 | if (err) return done(err); 116 | expect(result).toBe("/a/dir/index"); 117 | expect(log).toMatchSnapshot(); 118 | done(); 119 | }, 120 | ); 121 | }); 122 | }); 123 | -------------------------------------------------------------------------------- /test/fullSpecified.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { Volume } = require("memfs"); 4 | const { ResolverFactory } = require("../"); 5 | 6 | describe("fullSpecified", () => { 7 | const fileSystem = Volume.fromJSON( 8 | { 9 | "/a/node_modules/package1/index.js": "", 10 | "/a/node_modules/package1/file.js": "", 11 | "/a/node_modules/package2/package.json": JSON.stringify({ 12 | main: "a", 13 | }), 14 | "/a/node_modules/package2/a.js": "", 15 | "/a/node_modules/package3/package.json": JSON.stringify({ 16 | main: "dir", 17 | }), 18 | "/a/node_modules/package3/dir/index.js": "", 19 | "/a/node_modules/package4/package.json": JSON.stringify({ 20 | browser: { 21 | "./a.js": "./b", 22 | }, 23 | }), 24 | "/a/node_modules/package4/a.js": "", 25 | "/a/node_modules/package4/b.js": "", 26 | "/a/abc.js": "", 27 | "/a/dir/index.js": "", 28 | "/a/index.js": "", 29 | }, 30 | "/", 31 | ); 32 | const resolver = ResolverFactory.createResolver({ 33 | alias: { 34 | alias1: "/a/abc", 35 | alias2: "/a/", 36 | }, 37 | aliasFields: ["browser"], 38 | fullySpecified: true, 39 | useSyncFileSystemCalls: true, 40 | // @ts-expect-error for test 41 | fileSystem, 42 | }); 43 | const contextResolver = ResolverFactory.createResolver({ 44 | alias: { 45 | alias1: "/a/abc", 46 | alias2: "/a/", 47 | }, 48 | aliasFields: ["browser"], 49 | fullySpecified: true, 50 | resolveToContext: true, 51 | useSyncFileSystemCalls: true, 52 | // @ts-expect-error for test 53 | fileSystem, 54 | }); 55 | 56 | const failingResolves = { 57 | "no extensions": "./abc", 58 | "no extensions (absolute)": "/a/abc", 59 | "no extensions in packages": "package1/file", 60 | "no directories": ".", 61 | "no directories 2": "./", 62 | "no directories in packages": "package3/dir", 63 | "no extensions in packages 2": "package3/a", 64 | }; 65 | 66 | const pkg = "/a/node_modules/package"; 67 | const successfulResolves = { 68 | "fully relative": ["./abc.js", "/a/abc.js"], 69 | "fully absolute": ["/a/abc.js", "/a/abc.js"], 70 | "fully relative in package": ["package1/file.js", `${pkg}1/file.js`], 71 | "extensions in mainFiles": ["package1", `${pkg}1/index.js`], 72 | "extensions in mainFields": ["package2", `${pkg}2/a.js`], 73 | "extensions in alias": ["alias1", "/a/abc.js"], 74 | "directories in alias": ["alias2", "/a/index.js"], 75 | "directories in packages": ["package3", `${pkg}3/dir/index.js`], 76 | "extensions in aliasFields": ["package4/a.js", `${pkg}4/b.js`], 77 | }; 78 | 79 | for (const key of Object.keys(failingResolves)) { 80 | const request = failingResolves[key]; 81 | 82 | it(`should fail resolving ${key}`, () => { 83 | expect(() => { 84 | resolver.resolveSync({}, "/a", request); 85 | }).toThrow(/Can't resolve/); 86 | }); 87 | } 88 | 89 | for (const key of Object.keys(successfulResolves)) { 90 | const [request, expected] = successfulResolves[key]; 91 | 92 | it(`should resolve ${key} successfully`, () => { 93 | try { 94 | expect(resolver.resolveSync({}, "/a", request)).toEqual(expected); 95 | } catch (err) { 96 | err.message += `\n${err.details}`; 97 | throw err; 98 | } 99 | }); 100 | } 101 | 102 | const successfulContextResolves = { 103 | "current folder": [".", "/a"], 104 | "current folder 2": ["./", "/a"], 105 | "relative directory": ["./dir", "/a/dir"], 106 | "relative directory 2": ["./dir/", "/a/dir"], 107 | "relative directory with query and fragment": [ 108 | "./dir?123#456", 109 | "/a/dir?123#456", 110 | ], 111 | "relative directory with query and fragment 2": [ 112 | "./dir/?123#456", 113 | "/a/dir?123#456", 114 | ], 115 | "absolute directory": ["/a/dir", "/a/dir"], 116 | "directory in package": ["package3/dir", `${pkg}3/dir`], 117 | }; 118 | 119 | for (const key of Object.keys(successfulContextResolves)) { 120 | const [request, expected] = successfulContextResolves[key]; 121 | 122 | it(`should resolve ${key} successfully to an context`, () => { 123 | try { 124 | expect(contextResolver.resolveSync({}, "/a", request)).toEqual( 125 | expected, 126 | ); 127 | } catch (err) { 128 | err.message += `\n${err.details}`; 129 | throw err; 130 | } 131 | }); 132 | } 133 | }); 134 | -------------------------------------------------------------------------------- /test/unsafe-cache.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const path = require("path"); 4 | const resolve = require("../"); 5 | 6 | describe("unsafe-cache", () => { 7 | let cache; 8 | let cachedResolve; 9 | let context; 10 | let otherContext; 11 | 12 | beforeEach(() => { 13 | context = { 14 | some: "context", 15 | }; 16 | otherContext = { 17 | someOther: "context", 18 | }; 19 | }); 20 | 21 | describe("with no other options", () => { 22 | beforeEach(() => { 23 | cache = {}; 24 | cachedResolve = resolve.create({ 25 | unsafeCache: cache, 26 | }); 27 | }); 28 | 29 | it("should cache request", (done) => { 30 | cachedResolve( 31 | path.join(__dirname, "fixtures"), 32 | "m2/b", 33 | (err, _result) => { 34 | if (err) return done(err); 35 | expect(Object.keys(cache)).toHaveLength(1); 36 | for (const key of Object.keys(cache)) { 37 | cache[key] = { 38 | path: "yep", 39 | }; 40 | } 41 | cachedResolve( 42 | path.join(__dirname, "fixtures"), 43 | "m2/b", 44 | (err, result) => { 45 | if (err) return done(err); 46 | expect(result).toBe("yep"); 47 | done(); 48 | }, 49 | ); 50 | }, 51 | ); 52 | }); 53 | 54 | it("should not return from cache if context does not match", (done) => { 55 | cachedResolve( 56 | context, 57 | path.join(__dirname, "fixtures"), 58 | "m2/b", 59 | (err, _result) => { 60 | if (err) return done(err); 61 | expect(Object.keys(cache)).toHaveLength(1); 62 | for (const key of Object.keys(cache)) { 63 | cache[key] = { 64 | path: "yep", 65 | }; 66 | } 67 | cachedResolve( 68 | otherContext, 69 | path.join(__dirname, "fixtures"), 70 | "m2/b", 71 | (err, result) => { 72 | if (err) return done(err); 73 | expect(result).not.toBe("yep"); 74 | done(); 75 | }, 76 | ); 77 | }, 78 | ); 79 | }); 80 | 81 | it("should not return from cache if query does not match", (done) => { 82 | cachedResolve( 83 | path.join(__dirname, "fixtures"), 84 | "m2/b?query", 85 | (err, _result) => { 86 | if (err) return done(err); 87 | expect(Object.keys(cache)).toHaveLength(1); 88 | for (const key of Object.keys(cache)) { 89 | cache[key] = { 90 | path: "yep", 91 | }; 92 | } 93 | cachedResolve( 94 | path.join(__dirname, "fixtures"), 95 | "m2/b?query2", 96 | (err, result) => { 97 | if (err) return done(err); 98 | expect(result).not.toBe("yep"); 99 | done(); 100 | }, 101 | ); 102 | }, 103 | ); 104 | }); 105 | }); 106 | 107 | describe("with cacheWithContext false", () => { 108 | beforeEach(() => { 109 | cache = {}; 110 | cachedResolve = resolve.create({ 111 | unsafeCache: cache, 112 | cacheWithContext: false, 113 | }); 114 | }); 115 | 116 | it("should cache request", (done) => { 117 | cachedResolve( 118 | context, 119 | path.join(__dirname, "fixtures"), 120 | "m2/b", 121 | (err, _result) => { 122 | if (err) return done(err); 123 | expect(Object.keys(cache)).toHaveLength(1); 124 | for (const key of Object.keys(cache)) { 125 | cache[key] = { 126 | path: "yep", 127 | }; 128 | } 129 | cachedResolve( 130 | context, 131 | path.join(__dirname, "fixtures"), 132 | "m2/b", 133 | (err, result) => { 134 | if (err) return done(err); 135 | expect(result).toBe("yep"); 136 | done(); 137 | }, 138 | ); 139 | }, 140 | ); 141 | }); 142 | 143 | it("should return from cache even if context does not match", (done) => { 144 | cachedResolve( 145 | context, 146 | path.join(__dirname, "fixtures"), 147 | "m2/b", 148 | (err, _result) => { 149 | if (err) return done(err); 150 | expect(Object.keys(cache)).toHaveLength(1); 151 | for (const key of Object.keys(cache)) { 152 | cache[key] = { 153 | path: "yep", 154 | }; 155 | } 156 | cachedResolve( 157 | otherContext, 158 | path.join(__dirname, "fixtures"), 159 | "m2/b", 160 | (err, result) => { 161 | if (err) return done(err); 162 | expect(result).toBe("yep"); 163 | done(); 164 | }, 165 | ); 166 | }, 167 | ); 168 | }); 169 | }); 170 | }); 171 | -------------------------------------------------------------------------------- /lib/PnpPlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Maël Nison @arcanis 4 | */ 5 | 6 | "use strict"; 7 | 8 | /** @typedef {import("./Resolver")} Resolver */ 9 | /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ 10 | /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */ 11 | /** 12 | * @typedef {object} PnpApiImpl 13 | * @property {(packageName: string, issuer: string, options: { considerBuiltins: boolean }) => string | null} resolveToUnqualified resolve to unqualified 14 | */ 15 | 16 | module.exports = class PnpPlugin { 17 | /** 18 | * @param {string | ResolveStepHook} source source 19 | * @param {PnpApiImpl} pnpApi pnpApi 20 | * @param {string | ResolveStepHook} target target 21 | * @param {string | ResolveStepHook} alternateTarget alternateTarget 22 | */ 23 | constructor(source, pnpApi, target, alternateTarget) { 24 | this.source = source; 25 | this.pnpApi = pnpApi; 26 | this.target = target; 27 | this.alternateTarget = alternateTarget; 28 | } 29 | 30 | /** 31 | * @param {Resolver} resolver the resolver 32 | * @returns {void} 33 | */ 34 | apply(resolver) { 35 | /** @type {ResolveStepHook} */ 36 | const target = resolver.ensureHook(this.target); 37 | const alternateTarget = resolver.ensureHook(this.alternateTarget); 38 | resolver 39 | .getHook(this.source) 40 | .tapAsync("PnpPlugin", (request, resolveContext, callback) => { 41 | const req = request.request; 42 | if (!req) return callback(); 43 | 44 | // The trailing slash indicates to PnP that this value is a folder rather than a file 45 | const issuer = `${request.path}/`; 46 | 47 | const packageMatch = /^(@[^/]+\/)?[^/]+/.exec(req); 48 | if (!packageMatch) return callback(); 49 | 50 | const [packageName] = packageMatch; 51 | const innerRequest = `.${req.slice(packageName.length)}`; 52 | 53 | /** @type {string|undefined|null} */ 54 | let resolution; 55 | /** @type {string|undefined|null} */ 56 | let apiResolution; 57 | try { 58 | resolution = this.pnpApi.resolveToUnqualified(packageName, issuer, { 59 | considerBuiltins: false, 60 | }); 61 | 62 | if (resolution === null) { 63 | // This is either not a PnP managed issuer or it's a Node builtin 64 | // Try to continue resolving with our alternatives 65 | resolver.doResolve( 66 | alternateTarget, 67 | request, 68 | "issuer is not managed by a pnpapi", 69 | resolveContext, 70 | (err, result) => { 71 | if (err) return callback(err); 72 | if (result) return callback(null, result); 73 | // Skip alternatives 74 | return callback(null, null); 75 | }, 76 | ); 77 | return; 78 | } 79 | 80 | if (resolveContext.fileDependencies) { 81 | apiResolution = this.pnpApi.resolveToUnqualified("pnpapi", issuer, { 82 | considerBuiltins: false, 83 | }); 84 | } 85 | } catch (/** @type {unknown} */ error) { 86 | if ( 87 | /** @type {Error & { code: string }} */ 88 | (error).code === "MODULE_NOT_FOUND" && 89 | /** @type {Error & { pnpCode: string }} */ 90 | (error).pnpCode === "UNDECLARED_DEPENDENCY" 91 | ) { 92 | // This is not a PnP managed dependency. 93 | // Try to continue resolving with our alternatives 94 | if (resolveContext.log) { 95 | resolveContext.log("request is not managed by the pnpapi"); 96 | for (const line of /** @type {Error} */ (error).message 97 | .split("\n") 98 | .filter(Boolean)) { 99 | resolveContext.log(` ${line}`); 100 | } 101 | } 102 | return callback(); 103 | } 104 | return callback(/** @type {Error} */ (error)); 105 | } 106 | 107 | if (resolution === packageName) return callback(); 108 | 109 | if (apiResolution && resolveContext.fileDependencies) { 110 | resolveContext.fileDependencies.add(apiResolution); 111 | } 112 | /** @type {ResolveRequest} */ 113 | const obj = { 114 | ...request, 115 | path: resolution, 116 | request: innerRequest, 117 | ignoreSymlinks: true, 118 | fullySpecified: request.fullySpecified && innerRequest !== ".", 119 | }; 120 | resolver.doResolve( 121 | target, 122 | obj, 123 | `resolved by pnp to ${resolution}`, 124 | resolveContext, 125 | (err, result) => { 126 | if (err) return callback(err); 127 | if (result) return callback(null, result); 128 | // Skip alternatives 129 | return callback(null, null); 130 | }, 131 | ); 132 | }); 133 | } 134 | }; 135 | -------------------------------------------------------------------------------- /lib/AliasPlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | const forEachBail = require("./forEachBail"); 9 | const { PathType, getType } = require("./util/path"); 10 | 11 | /** @typedef {import("./Resolver")} Resolver */ 12 | /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */ 13 | /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ 14 | /** @typedef {string | Array | false} Alias */ 15 | /** @typedef {{alias: Alias, name: string, onlyModule?: boolean}} AliasOption */ 16 | 17 | module.exports = class AliasPlugin { 18 | /** 19 | * @param {string | ResolveStepHook} source source 20 | * @param {AliasOption | Array} options options 21 | * @param {string | ResolveStepHook} target target 22 | */ 23 | constructor(source, options, target) { 24 | this.source = source; 25 | this.options = Array.isArray(options) ? options : [options]; 26 | this.target = target; 27 | } 28 | 29 | /** 30 | * @param {Resolver} resolver the resolver 31 | * @returns {void} 32 | */ 33 | apply(resolver) { 34 | const target = resolver.ensureHook(this.target); 35 | /** 36 | * @param {string} maybeAbsolutePath path 37 | * @returns {null|string} absolute path with slash ending 38 | */ 39 | const getAbsolutePathWithSlashEnding = (maybeAbsolutePath) => { 40 | const type = getType(maybeAbsolutePath); 41 | if (type === PathType.AbsolutePosix || type === PathType.AbsoluteWin) { 42 | return resolver.join(maybeAbsolutePath, "_").slice(0, -1); 43 | } 44 | return null; 45 | }; 46 | /** 47 | * @param {string} path path 48 | * @param {string} maybeSubPath sub path 49 | * @returns {boolean} true, if path is sub path 50 | */ 51 | const isSubPath = (path, maybeSubPath) => { 52 | const absolutePath = getAbsolutePathWithSlashEnding(maybeSubPath); 53 | if (!absolutePath) return false; 54 | return path.startsWith(absolutePath); 55 | }; 56 | resolver 57 | .getHook(this.source) 58 | .tapAsync("AliasPlugin", (request, resolveContext, callback) => { 59 | const innerRequest = request.request || request.path; 60 | if (!innerRequest) return callback(); 61 | 62 | forEachBail( 63 | this.options, 64 | (item, callback) => { 65 | /** @type {boolean} */ 66 | let shouldStop = false; 67 | 68 | const matchRequest = 69 | innerRequest === item.name || 70 | (!item.onlyModule && 71 | (request.request 72 | ? innerRequest.startsWith(`${item.name}/`) 73 | : isSubPath(innerRequest, item.name))); 74 | 75 | const splitName = item.name.split("*"); 76 | const matchWildcard = !item.onlyModule && splitName.length === 2; 77 | 78 | if (matchRequest || matchWildcard) { 79 | /** 80 | * @param {Alias} alias alias 81 | * @param {(err?: null|Error, result?: null|ResolveRequest) => void} callback callback 82 | * @returns {void} 83 | */ 84 | const resolveWithAlias = (alias, callback) => { 85 | if (alias === false) { 86 | /** @type {ResolveRequest} */ 87 | const ignoreObj = { 88 | ...request, 89 | path: false, 90 | }; 91 | if (typeof resolveContext.yield === "function") { 92 | resolveContext.yield(ignoreObj); 93 | return callback(null, null); 94 | } 95 | return callback(null, ignoreObj); 96 | } 97 | 98 | let newRequestStr; 99 | 100 | const [prefix, suffix] = splitName; 101 | if ( 102 | matchWildcard && 103 | innerRequest.startsWith(prefix) && 104 | innerRequest.endsWith(suffix) 105 | ) { 106 | const match = innerRequest.slice( 107 | prefix.length, 108 | innerRequest.length - suffix.length, 109 | ); 110 | newRequestStr = alias.toString().replace("*", match); 111 | } 112 | 113 | if ( 114 | matchRequest && 115 | innerRequest !== alias && 116 | !innerRequest.startsWith(`${alias}/`) 117 | ) { 118 | /** @type {string} */ 119 | const remainingRequest = innerRequest.slice(item.name.length); 120 | newRequestStr = alias + remainingRequest; 121 | } 122 | 123 | if (newRequestStr !== undefined) { 124 | shouldStop = true; 125 | /** @type {ResolveRequest} */ 126 | const obj = { 127 | ...request, 128 | request: newRequestStr, 129 | fullySpecified: false, 130 | }; 131 | return resolver.doResolve( 132 | target, 133 | obj, 134 | `aliased with mapping '${item.name}': '${alias}' to '${newRequestStr}'`, 135 | resolveContext, 136 | (err, result) => { 137 | if (err) return callback(err); 138 | if (result) return callback(null, result); 139 | return callback(); 140 | }, 141 | ); 142 | } 143 | return callback(); 144 | }; 145 | 146 | /** 147 | * @param {(null | Error)=} err error 148 | * @param {(null | ResolveRequest)=} result result 149 | * @returns {void} 150 | */ 151 | const stoppingCallback = (err, result) => { 152 | if (err) return callback(err); 153 | 154 | if (result) return callback(null, result); 155 | // Don't allow other aliasing or raw request 156 | if (shouldStop) return callback(null, null); 157 | return callback(); 158 | }; 159 | 160 | if (Array.isArray(item.alias)) { 161 | return forEachBail( 162 | item.alias, 163 | resolveWithAlias, 164 | stoppingCallback, 165 | ); 166 | } 167 | return resolveWithAlias(item.alias, stoppingCallback); 168 | } 169 | 170 | return callback(); 171 | }, 172 | callback, 173 | ); 174 | }); 175 | } 176 | }; 177 | -------------------------------------------------------------------------------- /lib/util/path.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | const path = require("path"); 9 | 10 | const CHAR_HASH = "#".charCodeAt(0); 11 | const CHAR_SLASH = "/".charCodeAt(0); 12 | const CHAR_BACKSLASH = "\\".charCodeAt(0); 13 | const CHAR_A = "A".charCodeAt(0); 14 | const CHAR_Z = "Z".charCodeAt(0); 15 | const CHAR_LOWER_A = "a".charCodeAt(0); 16 | const CHAR_LOWER_Z = "z".charCodeAt(0); 17 | const CHAR_DOT = ".".charCodeAt(0); 18 | const CHAR_COLON = ":".charCodeAt(0); 19 | 20 | const posixNormalize = path.posix.normalize; 21 | const winNormalize = path.win32.normalize; 22 | 23 | /** 24 | * @enum {number} 25 | */ 26 | const PathType = Object.freeze({ 27 | Empty: 0, 28 | Normal: 1, 29 | Relative: 2, 30 | AbsoluteWin: 3, 31 | AbsolutePosix: 4, 32 | Internal: 5, 33 | }); 34 | 35 | const deprecatedInvalidSegmentRegEx = 36 | /(^|\\|\/)((\.|%2e)(\.|%2e)?|(n|%6e|%4e)(o|%6f|%4f)(d|%64|%44)(e|%65|%45)(_|%5f)(m|%6d|%4d)(o|%6f|%4f)(d|%64|%44)(u|%75|%55)(l|%6c|%4c)(e|%65|%45)(s|%73|%53))(\\|\/|$)/i; 37 | 38 | const invalidSegmentRegEx = 39 | /(^|\\|\/)((\.|%2e)(\.|%2e)?|(n|%6e|%4e)(o|%6f|%4f)(d|%64|%44)(e|%65|%45)(_|%5f)(m|%6d|%4d)(o|%6f|%4f)(d|%64|%44)(u|%75|%55)(l|%6c|%4c)(e|%65|%45)(s|%73|%53))?(\\|\/|$)/i; 40 | 41 | /** 42 | * @param {string} maybePath a path 43 | * @returns {PathType} type of path 44 | */ 45 | const getType = (maybePath) => { 46 | switch (maybePath.length) { 47 | case 0: 48 | return PathType.Empty; 49 | case 1: { 50 | const c0 = maybePath.charCodeAt(0); 51 | switch (c0) { 52 | case CHAR_DOT: 53 | return PathType.Relative; 54 | case CHAR_SLASH: 55 | return PathType.AbsolutePosix; 56 | case CHAR_HASH: 57 | return PathType.Internal; 58 | } 59 | return PathType.Normal; 60 | } 61 | case 2: { 62 | const c0 = maybePath.charCodeAt(0); 63 | switch (c0) { 64 | case CHAR_DOT: { 65 | const c1 = maybePath.charCodeAt(1); 66 | switch (c1) { 67 | case CHAR_DOT: 68 | case CHAR_SLASH: 69 | return PathType.Relative; 70 | } 71 | return PathType.Normal; 72 | } 73 | case CHAR_SLASH: 74 | return PathType.AbsolutePosix; 75 | case CHAR_HASH: 76 | return PathType.Internal; 77 | } 78 | const c1 = maybePath.charCodeAt(1); 79 | if ( 80 | c1 === CHAR_COLON && 81 | ((c0 >= CHAR_A && c0 <= CHAR_Z) || 82 | (c0 >= CHAR_LOWER_A && c0 <= CHAR_LOWER_Z)) 83 | ) { 84 | return PathType.AbsoluteWin; 85 | } 86 | return PathType.Normal; 87 | } 88 | } 89 | const c0 = maybePath.charCodeAt(0); 90 | switch (c0) { 91 | case CHAR_DOT: { 92 | const c1 = maybePath.charCodeAt(1); 93 | switch (c1) { 94 | case CHAR_SLASH: 95 | return PathType.Relative; 96 | case CHAR_DOT: { 97 | const c2 = maybePath.charCodeAt(2); 98 | if (c2 === CHAR_SLASH) return PathType.Relative; 99 | return PathType.Normal; 100 | } 101 | } 102 | return PathType.Normal; 103 | } 104 | case CHAR_SLASH: 105 | return PathType.AbsolutePosix; 106 | case CHAR_HASH: 107 | return PathType.Internal; 108 | } 109 | const c1 = maybePath.charCodeAt(1); 110 | if (c1 === CHAR_COLON) { 111 | const c2 = maybePath.charCodeAt(2); 112 | if ( 113 | (c2 === CHAR_BACKSLASH || c2 === CHAR_SLASH) && 114 | ((c0 >= CHAR_A && c0 <= CHAR_Z) || 115 | (c0 >= CHAR_LOWER_A && c0 <= CHAR_LOWER_Z)) 116 | ) { 117 | return PathType.AbsoluteWin; 118 | } 119 | } 120 | return PathType.Normal; 121 | }; 122 | 123 | /** 124 | * @param {string} maybePath a path 125 | * @returns {string} the normalized path 126 | */ 127 | const normalize = (maybePath) => { 128 | switch (getType(maybePath)) { 129 | case PathType.Empty: 130 | return maybePath; 131 | case PathType.AbsoluteWin: 132 | return winNormalize(maybePath); 133 | case PathType.Relative: { 134 | const r = posixNormalize(maybePath); 135 | return getType(r) === PathType.Relative ? r : `./${r}`; 136 | } 137 | } 138 | return posixNormalize(maybePath); 139 | }; 140 | 141 | /** 142 | * @param {string} rootPath the root path 143 | * @param {string | undefined} request the request path 144 | * @returns {string} the joined path 145 | */ 146 | const join = (rootPath, request) => { 147 | if (!request) return normalize(rootPath); 148 | const requestType = getType(request); 149 | switch (requestType) { 150 | case PathType.AbsolutePosix: 151 | return posixNormalize(request); 152 | case PathType.AbsoluteWin: 153 | return winNormalize(request); 154 | } 155 | switch (getType(rootPath)) { 156 | case PathType.Normal: 157 | case PathType.Relative: 158 | case PathType.AbsolutePosix: 159 | return posixNormalize(`${rootPath}/${request}`); 160 | case PathType.AbsoluteWin: 161 | return winNormalize(`${rootPath}\\${request}`); 162 | } 163 | switch (requestType) { 164 | case PathType.Empty: 165 | return rootPath; 166 | case PathType.Relative: { 167 | const r = posixNormalize(rootPath); 168 | return getType(r) === PathType.Relative ? r : `./${r}`; 169 | } 170 | } 171 | return posixNormalize(rootPath); 172 | }; 173 | 174 | /** @type {Map>} */ 175 | const joinCache = new Map(); 176 | 177 | /** 178 | * @param {string} rootPath the root path 179 | * @param {string} request the request path 180 | * @returns {string} the joined path 181 | */ 182 | const cachedJoin = (rootPath, request) => { 183 | /** @type {string | undefined} */ 184 | let cacheEntry; 185 | let cache = joinCache.get(rootPath); 186 | if (cache === undefined) { 187 | joinCache.set(rootPath, (cache = new Map())); 188 | } else { 189 | cacheEntry = cache.get(request); 190 | if (cacheEntry !== undefined) return cacheEntry; 191 | } 192 | cacheEntry = join(rootPath, request); 193 | cache.set(request, cacheEntry); 194 | return cacheEntry; 195 | }; 196 | 197 | module.exports.PathType = PathType; 198 | module.exports.cachedJoin = cachedJoin; 199 | module.exports.deprecatedInvalidSegmentRegEx = deprecatedInvalidSegmentRegEx; 200 | module.exports.getType = getType; 201 | module.exports.invalidSegmentRegEx = invalidSegmentRegEx; 202 | module.exports.join = join; 203 | module.exports.normalize = normalize; 204 | -------------------------------------------------------------------------------- /lib/DescriptionFileUtils.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | const forEachBail = require("./forEachBail"); 9 | 10 | /** @typedef {import("./Resolver")} Resolver */ 11 | /** @typedef {import("./Resolver").JsonObject} JsonObject */ 12 | /** @typedef {import("./Resolver").JsonValue} JsonValue */ 13 | /** @typedef {import("./Resolver").ResolveContext} ResolveContext */ 14 | /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */ 15 | 16 | /** 17 | * @typedef {object} DescriptionFileInfo 18 | * @property {JsonObject=} content content 19 | * @property {string} path path 20 | * @property {string} directory directory 21 | */ 22 | 23 | /** 24 | * @callback ErrorFirstCallback 25 | * @param {Error|null=} error 26 | * @param {DescriptionFileInfo=} result 27 | */ 28 | 29 | /** 30 | * @typedef {object} Result 31 | * @property {string} path path to description file 32 | * @property {string} directory directory of description file 33 | * @property {JsonObject} content content of description file 34 | */ 35 | 36 | /** 37 | * @param {string} directory directory 38 | * @returns {string|null} parent directory or null 39 | */ 40 | function cdUp(directory) { 41 | if (directory === "/") return null; 42 | const i = directory.lastIndexOf("/"); 43 | const j = directory.lastIndexOf("\\"); 44 | const path = i < 0 ? j : j < 0 ? i : i < j ? j : i; 45 | if (path < 0) return null; 46 | return directory.slice(0, path || 1); 47 | } 48 | 49 | /** 50 | * @param {Resolver} resolver resolver 51 | * @param {string} directory directory 52 | * @param {string[]} filenames filenames 53 | * @param {DescriptionFileInfo|undefined} oldInfo oldInfo 54 | * @param {ResolveContext} resolveContext resolveContext 55 | * @param {ErrorFirstCallback} callback callback 56 | */ 57 | function loadDescriptionFile( 58 | resolver, 59 | directory, 60 | filenames, 61 | oldInfo, 62 | resolveContext, 63 | callback, 64 | ) { 65 | (function findDescriptionFile() { 66 | if (oldInfo && oldInfo.directory === directory) { 67 | // We already have info for this directory and can reuse it 68 | return callback(null, oldInfo); 69 | } 70 | forEachBail( 71 | filenames, 72 | /** 73 | * @param {string} filename filename 74 | * @param {(err?: null|Error, result?: null|Result) => void} callback callback 75 | * @returns {void} 76 | */ 77 | (filename, callback) => { 78 | const descriptionFilePath = resolver.join(directory, filename); 79 | 80 | /** 81 | * @param {(null | Error)=} err error 82 | * @param {JsonObject=} resolvedContent content 83 | * @returns {void} 84 | */ 85 | function onJson(err, resolvedContent) { 86 | if (err) { 87 | if (resolveContext.log) { 88 | resolveContext.log( 89 | `${descriptionFilePath} (directory description file): ${err}`, 90 | ); 91 | } else { 92 | err.message = `${descriptionFilePath} (directory description file): ${err}`; 93 | } 94 | return callback(err); 95 | } 96 | callback(null, { 97 | content: /** @type {JsonObject} */ (resolvedContent), 98 | directory, 99 | path: descriptionFilePath, 100 | }); 101 | } 102 | 103 | if (resolver.fileSystem.readJson) { 104 | resolver.fileSystem.readJson(descriptionFilePath, (err, content) => { 105 | if (err) { 106 | if ( 107 | typeof (/** @type {NodeJS.ErrnoException} */ (err).code) !== 108 | "undefined" 109 | ) { 110 | if (resolveContext.missingDependencies) { 111 | resolveContext.missingDependencies.add(descriptionFilePath); 112 | } 113 | return callback(); 114 | } 115 | if (resolveContext.fileDependencies) { 116 | resolveContext.fileDependencies.add(descriptionFilePath); 117 | } 118 | return onJson(err); 119 | } 120 | if (resolveContext.fileDependencies) { 121 | resolveContext.fileDependencies.add(descriptionFilePath); 122 | } 123 | onJson(null, content); 124 | }); 125 | } else { 126 | resolver.fileSystem.readFile(descriptionFilePath, (err, content) => { 127 | if (err) { 128 | if (resolveContext.missingDependencies) { 129 | resolveContext.missingDependencies.add(descriptionFilePath); 130 | } 131 | return callback(); 132 | } 133 | if (resolveContext.fileDependencies) { 134 | resolveContext.fileDependencies.add(descriptionFilePath); 135 | } 136 | 137 | /** @type {JsonObject | undefined} */ 138 | let json; 139 | 140 | if (content) { 141 | try { 142 | json = JSON.parse(content.toString()); 143 | } catch (/** @type {unknown} */ err_) { 144 | return onJson(/** @type {Error} */ (err_)); 145 | } 146 | } else { 147 | return onJson(new Error("No content in file")); 148 | } 149 | 150 | onJson(null, json); 151 | }); 152 | } 153 | }, 154 | /** 155 | * @param {(null | Error)=} err error 156 | * @param {(null | Result)=} result result 157 | * @returns {void} 158 | */ 159 | (err, result) => { 160 | if (err) return callback(err); 161 | if (result) return callback(null, result); 162 | const dir = cdUp(directory); 163 | if (!dir) { 164 | return callback(); 165 | } 166 | directory = dir; 167 | return findDescriptionFile(); 168 | }, 169 | ); 170 | })(); 171 | } 172 | 173 | /** 174 | * @param {JsonObject} content content 175 | * @param {string|string[]} field field 176 | * @returns {JsonValue | undefined} field data 177 | */ 178 | function getField(content, field) { 179 | if (!content) return undefined; 180 | if (Array.isArray(field)) { 181 | /** @type {JsonValue} */ 182 | let current = content; 183 | for (let j = 0; j < field.length; j++) { 184 | if (current === null || typeof current !== "object") { 185 | current = null; 186 | break; 187 | } 188 | current = /** @type {JsonValue} */ ( 189 | /** @type {JsonObject} */ 190 | (current)[field[j]] 191 | ); 192 | } 193 | return current; 194 | } 195 | return content[field]; 196 | } 197 | 198 | module.exports.cdUp = cdUp; 199 | module.exports.getField = getField; 200 | module.exports.loadDescriptionFile = loadDescriptionFile; 201 | -------------------------------------------------------------------------------- /test/alias.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const fs = require("fs"); 4 | const path = require("path"); 5 | const { Volume } = require("memfs"); 6 | const { ResolverFactory } = require("../"); 7 | const CachedInputFileSystem = require("../lib/CachedInputFileSystem"); 8 | 9 | const nodeFileSystem = new CachedInputFileSystem(fs, 4000); 10 | 11 | describe("alias", () => { 12 | let resolver; 13 | 14 | beforeEach(() => { 15 | const fileSystem = Volume.fromJSON( 16 | { 17 | "/a/index": "", 18 | "/a/dir/index": "", 19 | "/recursive/index": "", 20 | "/recursive/dir/index": "", 21 | "/b/index": "", 22 | "/b/dir/index": "", 23 | "/c/index": "", 24 | "/c/dir/index": "", 25 | "/d/index.js": "", 26 | "/d/dir/.empty": "", 27 | "/e/index": "", 28 | "/e/anotherDir/index": "", 29 | "/e/dir/file": "", 30 | "/src/utils/a": "", 31 | "/src/components/b": "", 32 | }, 33 | "/", 34 | ); 35 | resolver = ResolverFactory.createResolver({ 36 | alias: { 37 | aliasA: "a", 38 | b$: "a/index", 39 | c$: "/a/index", 40 | multiAlias: ["b", "c", "d", "e", "a"], 41 | recursive: "recursive/dir", 42 | "/d/dir": "/c/dir", 43 | "/d/index.js": "/c/index", 44 | // alias configuration should work 45 | "#": "/c/dir", 46 | "@": "/c/dir", 47 | "@*": "/*", 48 | "@e*": "/e/*", 49 | "@e*file": "/e*file", 50 | "shared/*": ["/src/utils/*", "/src/components/*"], 51 | ignored: false, 52 | }, 53 | modules: "/", 54 | useSyncFileSystemCalls: true, 55 | // @ts-expect-error for tests 56 | fileSystem, 57 | }); 58 | }); 59 | 60 | it("should resolve a not aliased module", () => { 61 | expect(resolver.resolveSync({}, "/", "a")).toBe("/a/index"); 62 | expect(resolver.resolveSync({}, "/", "a/index")).toBe("/a/index"); 63 | expect(resolver.resolveSync({}, "/", "a/dir")).toBe("/a/dir/index"); 64 | expect(resolver.resolveSync({}, "/", "a/dir/index")).toBe("/a/dir/index"); 65 | }); 66 | 67 | it("should resolve an aliased module", () => { 68 | expect(resolver.resolveSync({}, "/", "aliasA")).toBe("/a/index"); 69 | expect(resolver.resolveSync({}, "/", "aliasA/index")).toBe("/a/index"); 70 | expect(resolver.resolveSync({}, "/", "aliasA/dir")).toBe("/a/dir/index"); 71 | expect(resolver.resolveSync({}, "/", "aliasA/dir/index")).toBe( 72 | "/a/dir/index", 73 | ); 74 | }); 75 | 76 | it('should resolve "#" alias', () => { 77 | expect(resolver.resolveSync({}, "/", "#")).toBe("/c/dir/index"); 78 | expect(resolver.resolveSync({}, "/", "#/index")).toBe("/c/dir/index"); 79 | }); 80 | 81 | it('should resolve "@" alias', () => { 82 | expect(resolver.resolveSync({}, "/", "@")).toBe("/c/dir/index"); 83 | expect(resolver.resolveSync({}, "/", "@/index")).toBe("/c/dir/index"); 84 | }); 85 | 86 | it("should resolve wildcard alias", () => { 87 | expect(resolver.resolveSync({}, "/", "@a")).toBe("/a/index"); 88 | expect(resolver.resolveSync({}, "/", "@a/dir")).toBe("/a/dir/index"); 89 | expect(resolver.resolveSync({}, "/", "@e/dir/file")).toBe("/e/dir/file"); 90 | expect(resolver.resolveSync({}, "/", "@e/anotherDir")).toBe( 91 | "/e/anotherDir/index", 92 | ); 93 | expect(resolver.resolveSync({}, "/", "@e/dir/file")).toBe("/e/dir/file"); 94 | }); 95 | 96 | it("should resolve an ignore module", () => { 97 | expect(resolver.resolveSync({}, "/", "ignored")).toBe(false); 98 | }); 99 | 100 | it("should resolve a recursive aliased module", () => { 101 | expect(resolver.resolveSync({}, "/", "recursive")).toBe( 102 | "/recursive/dir/index", 103 | ); 104 | expect(resolver.resolveSync({}, "/", "recursive/index")).toBe( 105 | "/recursive/dir/index", 106 | ); 107 | expect(resolver.resolveSync({}, "/", "recursive/dir")).toBe( 108 | "/recursive/dir/index", 109 | ); 110 | expect(resolver.resolveSync({}, "/", "recursive/dir/index")).toBe( 111 | "/recursive/dir/index", 112 | ); 113 | }); 114 | 115 | it("should resolve a file aliased module", () => { 116 | expect(resolver.resolveSync({}, "/", "b")).toBe("/a/index"); 117 | expect(resolver.resolveSync({}, "/", "c")).toBe("/a/index"); 118 | }); 119 | 120 | it("should resolve a file aliased module with a query", () => { 121 | expect(resolver.resolveSync({}, "/", "b?query")).toBe("/a/index?query"); 122 | expect(resolver.resolveSync({}, "/", "c?query")).toBe("/a/index?query"); 123 | }); 124 | 125 | it("should resolve a path in a file aliased module", () => { 126 | expect(resolver.resolveSync({}, "/", "b/index")).toBe("/b/index"); 127 | expect(resolver.resolveSync({}, "/", "b/dir")).toBe("/b/dir/index"); 128 | expect(resolver.resolveSync({}, "/", "b/dir/index")).toBe("/b/dir/index"); 129 | expect(resolver.resolveSync({}, "/", "c/index")).toBe("/c/index"); 130 | expect(resolver.resolveSync({}, "/", "c/dir")).toBe("/c/dir/index"); 131 | expect(resolver.resolveSync({}, "/", "c/dir/index")).toBe("/c/dir/index"); 132 | }); 133 | 134 | it("should resolve a file aliased file", () => { 135 | expect(resolver.resolveSync({}, "/", "d")).toBe("/c/index"); 136 | expect(resolver.resolveSync({}, "/", "d/dir/index")).toBe("/c/dir/index"); 137 | }); 138 | 139 | it("should resolve a file in multiple aliased dirs", () => { 140 | expect(resolver.resolveSync({}, "/", "multiAlias/dir/file")).toBe( 141 | "/e/dir/file", 142 | ); 143 | expect(resolver.resolveSync({}, "/", "multiAlias/anotherDir")).toBe( 144 | "/e/anotherDir/index", 145 | ); 146 | }); 147 | 148 | it("should log the correct info", (done) => { 149 | const log = []; 150 | resolver.resolve( 151 | {}, 152 | "/", 153 | "aliasA/dir", 154 | { log: (v) => log.push(v) }, 155 | (err, result) => { 156 | if (err) return done(err); 157 | 158 | expect(result).toBe("/a/dir/index"); 159 | expect(log).toMatchSnapshot(); 160 | 161 | done(); 162 | }, 163 | ); 164 | }); 165 | 166 | it("should work with absolute paths", (done) => { 167 | const resolver = ResolverFactory.createResolver({ 168 | alias: { 169 | [path.resolve(__dirname, "fixtures", "foo")]: false, 170 | }, 171 | modules: path.resolve(__dirname, "fixtures"), 172 | fileSystem: nodeFileSystem, 173 | }); 174 | 175 | resolver.resolve({}, __dirname, "foo/index", {}, (err, result) => { 176 | if (err) done(err); 177 | expect(result).toBe(false); 178 | done(); 179 | }); 180 | }); 181 | 182 | it("should resolve a wildcard alias with multiple targets correctly", () => { 183 | expect(resolver.resolveSync({}, "/", "shared/b")).toBe("/src/components/b"); 184 | }); 185 | }); 186 | -------------------------------------------------------------------------------- /lib/ExportsFieldPlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Ivan Kopeykin @vankop 4 | */ 5 | 6 | "use strict"; 7 | 8 | const DescriptionFileUtils = require("./DescriptionFileUtils"); 9 | const forEachBail = require("./forEachBail"); 10 | const { processExportsField } = require("./util/entrypoints"); 11 | const { parseIdentifier } = require("./util/identifier"); 12 | const { 13 | deprecatedInvalidSegmentRegEx, 14 | invalidSegmentRegEx, 15 | } = require("./util/path"); 16 | 17 | /** @typedef {import("./Resolver")} Resolver */ 18 | /** @typedef {import("./Resolver").JsonObject} JsonObject */ 19 | /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */ 20 | /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ 21 | /** @typedef {import("./util/entrypoints").ExportsField} ExportsField */ 22 | /** @typedef {import("./util/entrypoints").FieldProcessor} FieldProcessor */ 23 | 24 | module.exports = class ExportsFieldPlugin { 25 | /** 26 | * @param {string | ResolveStepHook} source source 27 | * @param {Set} conditionNames condition names 28 | * @param {string | string[]} fieldNamePath name path 29 | * @param {string | ResolveStepHook} target target 30 | */ 31 | constructor(source, conditionNames, fieldNamePath, target) { 32 | this.source = source; 33 | this.target = target; 34 | this.conditionNames = conditionNames; 35 | this.fieldName = fieldNamePath; 36 | /** @type {WeakMap} */ 37 | this.fieldProcessorCache = new WeakMap(); 38 | } 39 | 40 | /** 41 | * @param {Resolver} resolver the resolver 42 | * @returns {void} 43 | */ 44 | apply(resolver) { 45 | const target = resolver.ensureHook(this.target); 46 | resolver 47 | .getHook(this.source) 48 | .tapAsync("ExportsFieldPlugin", (request, resolveContext, callback) => { 49 | // When there is no description file, abort 50 | if (!request.descriptionFilePath) return callback(); 51 | if ( 52 | // When the description file is inherited from parent, abort 53 | // (There is no description file inside of this package) 54 | request.relativePath !== "." || 55 | request.request === undefined 56 | ) { 57 | return callback(); 58 | } 59 | 60 | const remainingRequest = 61 | request.query || request.fragment 62 | ? (request.request === "." ? "./" : request.request) + 63 | request.query + 64 | request.fragment 65 | : request.request; 66 | const exportsField = 67 | /** @type {ExportsField|null|undefined} */ 68 | ( 69 | DescriptionFileUtils.getField( 70 | /** @type {JsonObject} */ (request.descriptionFileData), 71 | this.fieldName, 72 | ) 73 | ); 74 | if (!exportsField) return callback(); 75 | 76 | if (request.directory) { 77 | return callback( 78 | new Error( 79 | `Resolving to directories is not possible with the exports field (request was ${remainingRequest}/)`, 80 | ), 81 | ); 82 | } 83 | 84 | /** @type {string[]} */ 85 | let paths; 86 | /** @type {string | null} */ 87 | let usedField; 88 | 89 | try { 90 | // We attach the cache to the description file instead of the exportsField value 91 | // because we use a WeakMap and the exportsField could be a string too. 92 | // Description file is always an object when exports field can be accessed. 93 | let fieldProcessor = this.fieldProcessorCache.get( 94 | /** @type {JsonObject} */ (request.descriptionFileData), 95 | ); 96 | if (fieldProcessor === undefined) { 97 | fieldProcessor = processExportsField(exportsField); 98 | this.fieldProcessorCache.set( 99 | /** @type {JsonObject} */ (request.descriptionFileData), 100 | fieldProcessor, 101 | ); 102 | } 103 | [paths, usedField] = fieldProcessor( 104 | remainingRequest, 105 | this.conditionNames, 106 | ); 107 | } catch (/** @type {unknown} */ err) { 108 | if (resolveContext.log) { 109 | resolveContext.log( 110 | `Exports field in ${request.descriptionFilePath} can't be processed: ${err}`, 111 | ); 112 | } 113 | return callback(/** @type {Error} */ (err)); 114 | } 115 | 116 | if (paths.length === 0) { 117 | const conditions = [...this.conditionNames]; 118 | const conditionsStr = 119 | conditions.length === 1 120 | ? `the condition "${conditions[0]}"` 121 | : `the conditions ${JSON.stringify(conditions)}`; 122 | return callback( 123 | new Error( 124 | `"${remainingRequest}" is not exported under ${conditionsStr} from package ${request.descriptionFileRoot} (see exports field in ${request.descriptionFilePath})`, 125 | ), 126 | ); 127 | } 128 | 129 | forEachBail( 130 | paths, 131 | /** 132 | * @param {string} path path 133 | * @param {(err?: null|Error, result?: null|ResolveRequest) => void} callback callback 134 | * @param {number} i index 135 | * @returns {void} 136 | */ 137 | (path, callback, i) => { 138 | const parsedIdentifier = parseIdentifier(path); 139 | 140 | if (!parsedIdentifier) return callback(); 141 | 142 | const [relativePath, query, fragment] = parsedIdentifier; 143 | 144 | if (relativePath.length === 0 || !relativePath.startsWith("./")) { 145 | if (paths.length === i) { 146 | return callback( 147 | new Error( 148 | `Invalid "exports" target "${path}" defined for "${usedField}" in the package config ${request.descriptionFilePath}, targets must start with "./"`, 149 | ), 150 | ); 151 | } 152 | 153 | return callback(); 154 | } 155 | 156 | if ( 157 | invalidSegmentRegEx.exec(relativePath.slice(2)) !== null && 158 | deprecatedInvalidSegmentRegEx.test(relativePath.slice(2)) 159 | ) { 160 | if (paths.length === i) { 161 | return callback( 162 | new Error( 163 | `Invalid "exports" target "${path}" defined for "${usedField}" in the package config ${request.descriptionFilePath}, targets must start with "./"`, 164 | ), 165 | ); 166 | } 167 | 168 | return callback(); 169 | } 170 | 171 | /** @type {ResolveRequest} */ 172 | const obj = { 173 | ...request, 174 | request: undefined, 175 | path: resolver.join( 176 | /** @type {string} */ (request.descriptionFileRoot), 177 | relativePath, 178 | ), 179 | relativePath, 180 | query, 181 | fragment, 182 | }; 183 | 184 | resolver.doResolve( 185 | target, 186 | obj, 187 | `using exports field: ${path}`, 188 | resolveContext, 189 | (err, result) => { 190 | if (err) return callback(err); 191 | // Don't allow to continue - https://github.com/webpack/enhanced-resolve/issues/400 192 | if (result === undefined) return callback(null, null); 193 | callback(null, result); 194 | }, 195 | ); 196 | }, 197 | /** 198 | * @param {(null | Error)=} err error 199 | * @param {(null | ResolveRequest)=} result result 200 | * @returns {void} 201 | */ 202 | (err, result) => callback(err, result || null), 203 | ); 204 | }); 205 | } 206 | }; 207 | -------------------------------------------------------------------------------- /lib/SyncAsyncFileSystemDecorator.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | /** @typedef {import("./Resolver").FileSystem} FileSystem */ 9 | /** @typedef {import("./Resolver").StringCallback} StringCallback */ 10 | /** @typedef {import("./Resolver").SyncFileSystem} SyncFileSystem */ 11 | 12 | // eslint-disable-next-line jsdoc/reject-function-type 13 | /** @typedef {Function} SyncOrAsyncFunction */ 14 | // eslint-disable-next-line jsdoc/reject-any-type 15 | /** @typedef {any} ResultOfSyncOrAsyncFunction */ 16 | 17 | /** 18 | * @param {SyncFileSystem} fs file system implementation 19 | * @constructor 20 | */ 21 | function SyncAsyncFileSystemDecorator(fs) { 22 | this.fs = fs; 23 | 24 | this.lstat = undefined; 25 | this.lstatSync = undefined; 26 | const { lstatSync } = fs; 27 | if (lstatSync) { 28 | this.lstat = 29 | /** @type {FileSystem["lstat"]} */ 30 | ( 31 | (arg, options, callback) => { 32 | let result; 33 | try { 34 | result = /** @type {SyncOrAsyncFunction | undefined} */ (callback) 35 | ? lstatSync.call(fs, arg, options) 36 | : lstatSync.call(fs, arg); 37 | } catch (err) { 38 | return (callback || options)( 39 | /** @type {NodeJS.ErrnoException | null} */ 40 | (err), 41 | ); 42 | } 43 | 44 | (callback || options)( 45 | null, 46 | /** @type {ResultOfSyncOrAsyncFunction} */ 47 | (result), 48 | ); 49 | } 50 | ); 51 | this.lstatSync = 52 | /** @type {SyncFileSystem["lstatSync"]} */ 53 | ((arg, options) => lstatSync.call(fs, arg, options)); 54 | } 55 | 56 | this.stat = 57 | /** @type {FileSystem["stat"]} */ 58 | ( 59 | (arg, options, callback) => { 60 | let result; 61 | try { 62 | result = /** @type {SyncOrAsyncFunction | undefined} */ (callback) 63 | ? fs.statSync(arg, options) 64 | : fs.statSync(arg); 65 | } catch (err) { 66 | return (callback || options)( 67 | /** @type {NodeJS.ErrnoException | null} */ 68 | (err), 69 | ); 70 | } 71 | 72 | (callback || options)( 73 | null, 74 | /** @type {ResultOfSyncOrAsyncFunction} */ 75 | (result), 76 | ); 77 | } 78 | ); 79 | this.statSync = 80 | /** @type {SyncFileSystem["statSync"]} */ 81 | ((arg, options) => fs.statSync(arg, options)); 82 | 83 | this.readdir = 84 | /** @type {FileSystem["readdir"]} */ 85 | ( 86 | (arg, options, callback) => { 87 | let result; 88 | try { 89 | result = /** @type {SyncOrAsyncFunction | undefined} */ (callback) 90 | ? fs.readdirSync( 91 | arg, 92 | /** @type {Exclude[1], (err: NodeJS.ErrnoException | null, files: string[]) => void>} */ 93 | (options), 94 | ) 95 | : fs.readdirSync(arg); 96 | } catch (err) { 97 | return (callback || options)( 98 | /** @type {NodeJS.ErrnoException | null} */ 99 | (err), 100 | [], 101 | ); 102 | } 103 | 104 | (callback || options)( 105 | null, 106 | /** @type {ResultOfSyncOrAsyncFunction} */ 107 | (result), 108 | ); 109 | } 110 | ); 111 | this.readdirSync = 112 | /** @type {SyncFileSystem["readdirSync"]} */ 113 | ( 114 | (arg, options) => 115 | fs.readdirSync( 116 | arg, 117 | /** @type {Parameters[1]} */ (options), 118 | ) 119 | ); 120 | 121 | this.readFile = 122 | /** @type {FileSystem["readFile"]} */ 123 | ( 124 | (arg, options, callback) => { 125 | let result; 126 | try { 127 | result = /** @type {SyncOrAsyncFunction | undefined} */ (callback) 128 | ? fs.readFileSync(arg, options) 129 | : fs.readFileSync(arg); 130 | } catch (err) { 131 | return (callback || options)( 132 | /** @type {NodeJS.ErrnoException | null} */ 133 | (err), 134 | ); 135 | } 136 | 137 | (callback || options)( 138 | null, 139 | /** @type {ResultOfSyncOrAsyncFunction} */ 140 | (result), 141 | ); 142 | } 143 | ); 144 | this.readFileSync = 145 | /** @type {SyncFileSystem["readFileSync"]} */ 146 | ((arg, options) => fs.readFileSync(arg, options)); 147 | 148 | this.readlink = 149 | /** @type {FileSystem["readlink"]} */ 150 | ( 151 | (arg, options, callback) => { 152 | let result; 153 | try { 154 | result = /** @type {SyncOrAsyncFunction | undefined} */ (callback) 155 | ? fs.readlinkSync( 156 | arg, 157 | /** @type {Exclude[1], StringCallback>} */ 158 | (options), 159 | ) 160 | : fs.readlinkSync(arg); 161 | } catch (err) { 162 | return (callback || options)( 163 | /** @type {NodeJS.ErrnoException | null} */ 164 | (err), 165 | ); 166 | } 167 | 168 | (callback || options)( 169 | null, 170 | /** @type {ResultOfSyncOrAsyncFunction} */ 171 | (result), 172 | ); 173 | } 174 | ); 175 | this.readlinkSync = 176 | /** @type {SyncFileSystem["readlinkSync"]} */ 177 | ( 178 | (arg, options) => 179 | fs.readlinkSync( 180 | arg, 181 | /** @type {Parameters[1]} */ ( 182 | options 183 | ), 184 | ) 185 | ); 186 | 187 | this.readJson = undefined; 188 | this.readJsonSync = undefined; 189 | const { readJsonSync } = fs; 190 | if (readJsonSync) { 191 | this.readJson = 192 | /** @type {FileSystem["readJson"]} */ 193 | ( 194 | (arg, callback) => { 195 | let result; 196 | try { 197 | result = readJsonSync.call(fs, arg); 198 | } catch (err) { 199 | return callback( 200 | /** @type {NodeJS.ErrnoException | Error | null} */ (err), 201 | ); 202 | } 203 | 204 | callback(null, result); 205 | } 206 | ); 207 | this.readJsonSync = 208 | /** @type {SyncFileSystem["readJsonSync"]} */ 209 | ((arg) => readJsonSync.call(fs, arg)); 210 | } 211 | 212 | this.realpath = undefined; 213 | this.realpathSync = undefined; 214 | const { realpathSync } = fs; 215 | if (realpathSync) { 216 | this.realpath = 217 | /** @type {FileSystem["realpath"]} */ 218 | ( 219 | (arg, options, callback) => { 220 | let result; 221 | try { 222 | result = /** @type {SyncOrAsyncFunction | undefined} */ (callback) 223 | ? realpathSync.call( 224 | fs, 225 | arg, 226 | /** @type {Exclude>[1], StringCallback>} */ 227 | (options), 228 | ) 229 | : realpathSync.call(fs, arg); 230 | } catch (err) { 231 | return (callback || options)( 232 | /** @type {NodeJS.ErrnoException | null} */ 233 | (err), 234 | ); 235 | } 236 | 237 | (callback || options)( 238 | null, 239 | /** @type {ResultOfSyncOrAsyncFunction} */ 240 | (result), 241 | ); 242 | } 243 | ); 244 | this.realpathSync = 245 | /** @type {SyncFileSystem["realpathSync"]} */ 246 | ( 247 | (arg, options) => 248 | realpathSync.call( 249 | fs, 250 | arg, 251 | /** @type {Parameters>[1]} */ 252 | (options), 253 | ) 254 | ); 255 | } 256 | } 257 | 258 | module.exports = SyncAsyncFileSystemDecorator; 259 | --------------------------------------------------------------------------------