├── tests └── fixtures │ ├── main1.js │ ├── main2.js │ ├── main3.js │ ├── no.js │ ├── 中文.js │ ├── abc.txt │ ├── alias │ ├── a │ │ ├── index │ │ └── dir │ │ │ └── index │ ├── b │ │ ├── index │ │ └── dir │ │ │ └── index │ ├── c │ │ ├── index │ │ └── dir │ │ │ └── index │ ├── e │ │ ├── index │ │ ├── dir │ │ │ └── file │ │ └── anotherDir │ │ │ └── index │ ├── f │ │ └── index │ ├── g │ │ └── index │ ├── h │ │ └── index │ ├── d │ │ ├── dir │ │ │ └── .empty │ │ └── index.js │ ├── recursive │ │ ├── index │ │ └── dir │ │ │ └── index │ └── node_modules │ │ ├── browser │ │ └── index.js │ │ ├── ignore-a │ │ └── index.js │ │ └── @recursive │ │ ├── general │ │ ├── index.js │ │ └── redirect.js │ │ └── pointed │ │ └── index.js │ ├── extensions2 │ ├── a │ ├── b │ ├── a.js │ ├── index │ ├── index.js │ └── package.json │ ├── full │ └── a │ │ ├── abc.js │ │ ├── index.js │ │ ├── dir │ │ └── index.js │ │ └── node_modules │ │ ├── package2 │ │ ├── a.js │ │ └── package.json │ │ ├── package4 │ │ ├── a.js │ │ ├── b.js │ │ └── package.json │ │ ├── package1 │ │ ├── file.js │ │ └── index.js │ │ ├── package5 │ │ ├── file.js │ │ ├── index.js │ │ └── package.json │ │ └── package3 │ │ ├── dir │ │ └── index.js │ │ └── package.json │ ├── cache-fs2 │ └── index.js │ ├── exports-field │ ├── b.js │ ├── a.js │ ├── node_modules │ │ ├── exports-field │ │ │ ├── lib │ │ │ │ ├── index.js │ │ │ │ ├── main.js │ │ │ │ ├── browser.js │ │ │ │ └── lib2 │ │ │ │ │ └── main.js │ │ │ ├── main.js │ │ │ ├── x.js │ │ │ └── package.json │ │ ├── invalid-exports-field │ │ │ ├── index.js │ │ │ ├── umd.js │ │ │ └── package.json │ │ ├── string-side-effects │ │ │ ├── index.js │ │ │ └── package.json │ │ └── @scope │ │ │ └── import-require │ │ │ ├── dist │ │ │ ├── cjs │ │ │ │ ├── a │ │ │ │ │ └── index.js │ │ │ │ └── index.js │ │ │ └── esm │ │ │ │ ├── a │ │ │ │ └── index.js │ │ │ │ └── index.js │ │ │ └── package.json │ └── package.json │ ├── extensions │ ├── a.js │ ├── a.ts │ ├── dir │ │ ├── index.js │ │ └── index.ts │ ├── index.js │ ├── index.ts │ ├── node_modules │ │ ├── m.js │ │ └── m │ │ │ ├── index.js │ │ │ └── index.ts │ └── package.json │ ├── imports-field │ ├── b.js │ ├── dir │ │ └── b.js │ ├── node_modules │ │ ├── c │ │ │ └── index.js │ │ └── a │ │ │ ├── lib │ │ │ ├── index.js │ │ │ ├── browser.js │ │ │ ├── main.js │ │ │ └── lib2 │ │ │ │ └── main.js │ │ │ ├── x.js │ │ │ ├── main.js │ │ │ └── package.json │ ├── a.js │ └── package.json │ ├── no#fragment │ └── # │ │ └── #.js │ ├── cache-fs │ ├── src │ │ ├── index.js │ │ └── module.js │ └── package.json │ ├── exports-field3 │ ├── main.js │ ├── pkg1 │ │ ├── index.js │ │ └── node_modules │ │ │ └── m1 │ │ │ ├── m1.js │ │ │ └── package.json │ └── package.json │ ├── main-field │ ├── index.js │ ├── src │ │ └── index.js │ └── package.json │ ├── node_modules │ ├── m1 │ │ ├── a.js │ │ └── b.js │ ├── recursive-module │ │ ├── file.js │ │ └── index.js │ ├── invalidPackageJson │ │ └── package.json │ ├── m2 │ │ └── b.js │ └── complexm │ │ ├── step2.js │ │ ├── node_modules │ │ └── m1 │ │ │ ├── a.js │ │ │ └── index.js │ │ └── step1.js │ ├── self-is-dep │ ├── src │ │ ├── a.js │ │ ├── b.js │ │ └── index.js │ ├── node_modules │ │ └── @scope │ │ │ └── self │ └── package.json │ ├── simple │ ├── lib │ │ └── index.js │ └── package.json │ ├── symlink │ ├── lib │ │ └── index.js │ └── linked │ │ ├── outer │ │ ├── this │ │ ├── lib │ │ ├── that │ │ ├── index.js │ │ ├── node.relative.js │ │ └── node.relative.sym.js │ ├── tsconfig-paths │ ├── index.ts │ ├── absolute-in.ts │ ├── actual │ │ └── test.ts │ ├── test0-success.ts │ ├── test1-success.ts │ ├── test4-first │ │ └── foo.ts │ ├── absolute-in-star.ts │ ├── test2-success │ │ └── foo.ts │ ├── test5-second │ │ └── foo.ts │ ├── test3-success.ts │ └── tsconfig.json │ ├── browser-after-main │ ├── main.js │ └── package.json │ ├── browser-module │ ├── lib │ │ ├── ignore.js │ │ ├── main.js │ │ ├── sub.js │ │ ├── browser.js │ │ ├── redirect.js │ │ ├── redirect2.js │ │ ├── redirect3.js │ │ ├── replaced.js │ │ ├── toString.js │ │ ├── sub │ │ │ ├── dir │ │ │ │ └── index.js │ │ │ └── package.json │ │ └── redirect3-target │ │ │ ├── dir │ │ │ └── index.js │ │ │ └── package.json │ ├── browser │ │ └── module-a.js │ ├── node_modules │ │ ├── module-a.js │ │ ├── module-b.js │ │ ├── module-c.js │ │ ├── relative │ │ │ ├── lib │ │ │ │ ├── a.js │ │ │ │ └── b.js │ │ │ ├── default │ │ │ │ └── index.js │ │ │ └── package.json │ │ ├── browser-string │ │ │ ├── index.js │ │ │ ├── target.js │ │ │ └── package.json │ │ └── recursive-module │ │ │ └── index.js │ └── package.json │ ├── extension-alias │ ├── dir │ │ ├── index.js │ │ └── index.ts │ ├── index.js │ ├── index.mjs │ ├── index.mts.js │ ├── index.ts │ ├── dir2 │ │ ├── index.js │ │ └── index.mts │ └── node_modules │ │ └── a.js │ │ └── index.js │ ├── incorrect-package │ ├── pack1 │ │ ├── a.js │ │ └── package.json │ ├── pack2 │ │ ├── a.js │ │ └── package.json │ ├── sideeffects-map │ │ ├── index.js │ │ └── package.json │ └── sideeffects-other-in-array │ │ └── package.json │ ├── main-field-inexist │ ├── index.js │ ├── module.js │ └── package.json │ ├── main-field-self │ ├── index.js │ └── package.json │ ├── main-field-self2 │ ├── index.js │ └── package.json │ ├── main-filed-no-relative │ ├── c.js │ ├── index.js │ ├── node_modules │ │ ├── a │ │ │ └── index.js │ │ └── c │ │ │ └── index.js │ └── package.json │ ├── main-field-end-slash │ ├── index.js │ ├── src │ │ └── index.js │ └── package.json │ ├── browser-to-self │ └── node_modules │ │ ├── a.js │ │ ├── a.js │ │ ├── a.d.ts │ │ ├── a.mjs │ │ └── package.json │ │ ├── b.js │ │ ├── b.js │ │ ├── b.d.ts │ │ ├── b.mjs │ │ └── package.json │ │ └── c.js │ │ ├── c.js │ │ ├── c.d.ts │ │ ├── c.mjs │ │ └── package.json │ ├── tsconfig-paths-nested │ ├── absolute-in.ts │ ├── absolute-in-star.ts │ ├── nested │ │ ├── actual │ │ │ └── test.ts │ │ ├── test0-success.ts │ │ ├── test1-success.ts │ │ ├── test4-first │ │ │ └── foo.ts │ │ ├── test5-second │ │ │ └── foo.ts │ │ ├── test2-success │ │ │ └── foo.ts │ │ └── test3-success.ts │ └── tsconfig.json │ ├── dirOrFile.js │ ├── scoped │ └── node_modules │ │ └── @scope │ │ ├── pack1 │ │ ├── main.js │ │ └── package.json │ │ └── pack2 │ │ ├── main.js │ │ ├── lib │ │ └── index.js │ │ └── package.json │ ├── tsconfig-paths-missing-baseURL │ ├── src │ │ └── test.ts │ ├── tsconfig.json │ └── tsconfig.paths.json │ ├── tsconfig-paths-override-baseURL │ ├── src │ │ └── test.ts │ ├── tsconfig.paths.json │ └── tsconfig.json │ ├── tsconfig-paths-without-baseURL │ ├── src │ │ └── a.ts │ ├── should-not-be-imported.ts │ └── tsconfig.json │ ├── dependencies │ ├── node_modules │ │ └── other-module │ │ │ └── file.js │ └── a │ │ ├── b │ │ └── node_modules │ │ │ └── some-module │ │ │ └── index.js │ │ └── node_modules │ │ └── module │ │ ├── file.js │ │ └── package.json │ ├── dirOrFile │ └── index.js │ ├── exports-field-error │ └── node_modules │ │ ├── pack1 │ │ └── index.js │ │ └── exports-field │ │ └── package.json │ ├── exports-field4 │ └── node_modules │ │ └── exports-field │ │ ├── index │ │ ├── main.js │ │ └── package.json │ ├── exports-field5 │ └── node_modules │ │ └── pkgexports │ │ ├── asdf.js │ │ ├── sp ce.js │ │ ├── lib │ │ └── hole.js │ │ ├── addons-entry.js │ │ ├── custom-condition.js │ │ ├── internal │ │ └── test.js │ │ ├── no-addons-entry.js │ │ ├── not-exported.js │ │ ├── resolve-self.js │ │ ├── resolve-self.mjs │ │ ├── subpath │ │ ├── file.js │ │ ├── dir1 │ │ │ ├── dir1.js │ │ │ └── package.json │ │ └── dir2 │ │ │ └── index.js │ │ ├── custom-condition.mjs │ │ ├── resolve-self-invalid.js │ │ ├── resolve-self-invalid.mjs │ │ ├── node_modules │ │ └── internalpkg │ │ │ └── x.js │ │ ├── trailing-pattern-slash │ │ └── index.js │ │ └── package.json │ ├── pnpm-structure │ └── node_modules │ │ ├── module-a │ │ └── index.js │ │ ├── module-b │ │ └── index.js │ │ ├── exports-field-a │ │ ├── lib │ │ │ ├── a.js │ │ │ └── index.js │ │ ├── node_modules │ │ │ └── .bin │ │ │ │ └── placeholder │ │ └── package.json │ │ ├── exports-field-aa │ │ ├── index.js │ │ └── package.json │ │ ├── exports-field-b │ │ ├── index.js │ │ └── package.json │ │ └── exports-field-c │ │ ├── lib │ │ ├── b.js │ │ ├── c.js │ │ └── index.js │ │ ├── node_modules │ │ └── .bin │ │ │ └── placeholder │ │ └── package.json │ ├── tsconfig-paths-extends-from-module │ ├── src │ │ └── test.ts │ ├── node_modules │ │ ├── module │ │ │ └── tsconfig.json │ │ └── @scope │ │ │ └── module │ │ │ └── tsconfig.json │ ├── tsconfig.json │ └── tsconfig.scope.json │ ├── m.js │ └── index.js │ ├── a.js │ ├── b.js │ ├── exports-field2 │ ├── node_modules │ │ └── exports-field │ │ │ ├── main.js │ │ │ ├── index.js │ │ │ ├── lib │ │ │ ├── main.js │ │ │ ├── browser.js │ │ │ └── lib2 │ │ │ │ └── main.js │ │ │ └── package.json │ └── index.js │ ├── c.js │ └── multiple_modules │ └── node_modules │ └── m1 │ └── a.js ├── bench ├── .gitignore ├── README.md ├── package.json └── scripts │ ├── run.sh │ └── gen-bench.js ├── .vscode └── settings.json ├── rust-toolchain.toml ├── .gitignore ├── README.md ├── src ├── cache.rs ├── error.rs ├── plugin │ ├── mod.rs │ ├── prefer_relative.rs │ ├── main_file.rs │ ├── parse.rs │ ├── extension_alias.rs │ ├── symlink.rs │ ├── main_field.rs │ ├── imports_field.rs │ ├── alias.rs │ ├── browser_field.rs │ └── exports_field.rs ├── state.rs ├── context.rs ├── resource.rs ├── log.rs ├── info.rs ├── description.rs ├── fs.rs ├── kind.rs ├── tsconfig.rs ├── options.rs ├── parse.rs ├── entry.rs ├── tsconfig_path.rs ├── lib.rs └── resolve.rs ├── .github └── workflows │ ├── publish.yml │ ├── test.yml │ └── bench.yml ├── examples └── simple.rs └── Cargo.toml /tests/fixtures/main1.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/main2.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/main3.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/no.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/中文.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/abc.txt: -------------------------------------------------------------------------------- 1 | abc -------------------------------------------------------------------------------- /tests/fixtures/alias/a/index: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/alias/b/index: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/alias/c/index: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/alias/e/index: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/alias/f/index: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/alias/g/index: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/alias/h/index: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/extensions2/a: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/extensions2/b: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/full/a/abc.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/alias/a/dir/index: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/alias/b/dir/index: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/alias/c/dir/index: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/alias/d/dir/.empty: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/alias/d/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/alias/e/dir/file: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/cache-fs2/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field/b.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/extensions/a.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/extensions/a.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/extensions2/a.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/extensions2/index: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/full/a/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/imports-field/b.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/no#fragment/#/#.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/alias/recursive/index: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/cache-fs/src/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/cache-fs/src/module.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field3/main.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/extensions/dir/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/extensions/dir/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/extensions/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/extensions/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/extensions2/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/full/a/dir/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/imports-field/dir/b.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/main-field/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/main-field/src/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/node_modules/m1/a.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/node_modules/m1/b.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/self-is-dep/src/a.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/self-is-dep/src/b.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/simple/lib/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/symlink/lib/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/symlink/linked/outer: -------------------------------------------------------------------------------- 1 | .. -------------------------------------------------------------------------------- /tests/fixtures/symlink/linked/this: -------------------------------------------------------------------------------- 1 | . -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/alias/e/anotherDir/index: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/alias/recursive/dir/index: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-after-main/main.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-module/lib/ignore.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-module/lib/main.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-module/lib/sub.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field3/pkg1/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/extension-alias/dir/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/extension-alias/dir/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/extension-alias/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/extension-alias/index.mjs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/extension-alias/index.mts.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/extension-alias/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/extensions/node_modules/m.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/incorrect-package/pack1/a.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/incorrect-package/pack2/a.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/main-field-inexist/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/main-field-inexist/module.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/main-field-self/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/main-field-self2/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/main-filed-no-relative/c.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/self-is-dep/src/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/symlink/linked/lib: -------------------------------------------------------------------------------- 1 | ../lib/ -------------------------------------------------------------------------------- /tests/fixtures/symlink/linked/that: -------------------------------------------------------------------------------- 1 | ./this -------------------------------------------------------------------------------- /bench/.gitignore: -------------------------------------------------------------------------------- 1 | ant-design 2 | node_modules/ -------------------------------------------------------------------------------- /tests/fixtures/browser-module/lib/browser.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-module/lib/redirect.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-module/lib/redirect2.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-module/lib/redirect3.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-module/lib/replaced.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-module/lib/toString.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/extension-alias/dir2/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/extension-alias/dir2/index.mts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/full/a/node_modules/package2/a.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/full/a/node_modules/package4/a.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/full/a/node_modules/package4/b.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/main-field-end-slash/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/main-field-end-slash/src/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/main-filed-no-relative/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths/absolute-in.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths/actual/test.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths/test0-success.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths/test1-success.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths/test4-first/foo.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/alias/node_modules/browser/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/alias/node_modules/ignore-a/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-module/browser/module-a.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-module/lib/sub/dir/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-to-self/node_modules/a.js/a.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-to-self/node_modules/b.js/b.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-to-self/node_modules/c.js/c.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/extensions/node_modules/m/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/extensions/node_modules/m/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/full/a/node_modules/package1/file.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/full/a/node_modules/package1/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/full/a/node_modules/package5/file.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/full/a/node_modules/package5/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/imports-field/node_modules/c/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/incorrect-package/pack2/package.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/node_modules/recursive-module/file.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/node_modules/recursive-module/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/symlink/linked/index.js: -------------------------------------------------------------------------------- 1 | ../lib/index.js -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths-nested/absolute-in.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths/absolute-in-star.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths/test2-success/foo.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths/test5-second/foo.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-module/node_modules/module-a.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-module/node_modules/module-b.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-module/node_modules/module-c.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-to-self/node_modules/a.js/a.d.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-to-self/node_modules/a.js/a.mjs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-to-self/node_modules/b.js/b.d.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-to-self/node_modules/b.js/b.mjs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-to-self/node_modules/c.js/c.d.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-to-self/node_modules/c.js/c.mjs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/dirOrFile.js: -------------------------------------------------------------------------------- 1 | module.exports = "file"; 2 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field/a.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field3/pkg1/node_modules/m1/m1.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/extension-alias/node_modules/a.js/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/full/a/node_modules/package3/dir/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/imports-field/a.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /tests/fixtures/imports-field/node_modules/a/lib/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/incorrect-package/sideeffects-map/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/scoped/node_modules/@scope/pack1/main.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/scoped/node_modules/@scope/pack2/main.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/self-is-dep/node_modules/@scope/self: -------------------------------------------------------------------------------- 1 | ../../ -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths-missing-baseURL/src/test.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths-nested/absolute-in-star.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths-nested/nested/actual/test.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths-override-baseURL/src/test.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths-without-baseURL/src/a.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/alias/node_modules/@recursive/general/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/alias/node_modules/@recursive/pointed/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-module/lib/redirect3-target/dir/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-module/node_modules/relative/lib/a.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-module/node_modules/relative/lib/b.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/cache-fs/package.json: -------------------------------------------------------------------------------- 1 | {"main": "./src/index.js"} -------------------------------------------------------------------------------- /tests/fixtures/dependencies/node_modules/other-module/file.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/dirOrFile/index.js: -------------------------------------------------------------------------------- 1 | module.exports = "dir"; 2 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field-error/node_modules/pack1/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field4/node_modules/exports-field/index: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field5/node_modules/pkgexports/asdf.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field5/node_modules/pkgexports/sp ce.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/main-filed-no-relative/node_modules/a/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/main-filed-no-relative/node_modules/c/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/node_modules/invalidPackageJson/package.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/pnpm-structure/node_modules/module-a/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/pnpm-structure/node_modules/module-b/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/scoped/node_modules/@scope/pack2/lib/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/symlink/linked/node.relative.js: -------------------------------------------------------------------------------- 1 | ../lib/index.js -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths-extends-from-module/src/test.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths-nested/nested/test0-success.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths-nested/nested/test1-success.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths-nested/nested/test4-first/foo.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths-nested/nested/test5-second/foo.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths/test3-success.ts: -------------------------------------------------------------------------------- 1 | export default {} -------------------------------------------------------------------------------- /tests/fixtures/alias/node_modules/@recursive/general/redirect.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-module/node_modules/browser-string/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-module/node_modules/browser-string/target.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-module/node_modules/recursive-module/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-module/node_modules/relative/default/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/dependencies/a/b/node_modules/some-module/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field/node_modules/exports-field/lib/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field5/node_modules/pkgexports/lib/hole.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/pnpm-structure/node_modules/exports-field-a/lib/a.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/pnpm-structure/node_modules/exports-field-aa/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/pnpm-structure/node_modules/exports-field-b/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/pnpm-structure/node_modules/exports-field-c/lib/b.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/pnpm-structure/node_modules/exports-field-c/lib/c.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/symlink/linked/node.relative.sym.js: -------------------------------------------------------------------------------- 1 | ./node.relative.js -------------------------------------------------------------------------------- /tests/fixtures/exports-field/node_modules/invalid-exports-field/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field/node_modules/invalid-exports-field/umd.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field/node_modules/string-side-effects/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field5/node_modules/pkgexports/addons-entry.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field5/node_modules/pkgexports/custom-condition.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field5/node_modules/pkgexports/internal/test.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field5/node_modules/pkgexports/no-addons-entry.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field5/node_modules/pkgexports/not-exported.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field5/node_modules/pkgexports/resolve-self.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field5/node_modules/pkgexports/resolve-self.mjs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field5/node_modules/pkgexports/subpath/file.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/extensions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "./index.js" 3 | } -------------------------------------------------------------------------------- /tests/fixtures/extensions2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "./index.js" 3 | } -------------------------------------------------------------------------------- /tests/fixtures/imports-field/node_modules/a/x.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /tests/fixtures/main-field/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "./src/index" 3 | } -------------------------------------------------------------------------------- /tests/fixtures/node_modules/m2/b.js: -------------------------------------------------------------------------------- 1 | module.exports = "This is m2/b"; 2 | -------------------------------------------------------------------------------- /tests/fixtures/pnpm-structure/node_modules/exports-field-a/lib/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/pnpm-structure/node_modules/exports-field-c/lib/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/simple/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "./lib/index.js" 3 | } -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths-nested/nested/test2-success/foo.ts: -------------------------------------------------------------------------------- 1 | export {} -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths-without-baseURL/should-not-be-imported.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-module/lib/sub/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "dir" 3 | } -------------------------------------------------------------------------------- /tests/fixtures/dependencies/a/node_modules/module/file.js: -------------------------------------------------------------------------------- 1 | {main: "entry.js"} -------------------------------------------------------------------------------- /tests/fixtures/exports-field5/node_modules/pkgexports/custom-condition.mjs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field5/node_modules/pkgexports/resolve-self-invalid.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field5/node_modules/pkgexports/resolve-self-invalid.mjs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field5/node_modules/pkgexports/subpath/dir1/dir1.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field5/node_modules/pkgexports/subpath/dir2/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/imports-field/node_modules/a/main.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /tests/fixtures/m.js/index.js: -------------------------------------------------------------------------------- 1 | // just for test `resolve('extensions', 'm.js/)` -------------------------------------------------------------------------------- /tests/fixtures/main-field-end-slash/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "./src/" 3 | } -------------------------------------------------------------------------------- /tests/fixtures/main-field-self/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "./" 3 | } 4 | -------------------------------------------------------------------------------- /tests/fixtures/main-field-self2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "." 3 | } 4 | -------------------------------------------------------------------------------- /tests/fixtures/node_modules/complexm/step2.js: -------------------------------------------------------------------------------- 1 | module.exports = "Step2"; 2 | -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths-nested/nested/test3-success.ts: -------------------------------------------------------------------------------- 1 | export default {} -------------------------------------------------------------------------------- /tests/fixtures/dependencies/a/node_modules/module/package.json: -------------------------------------------------------------------------------- 1 | {"main": "entry.js"} -------------------------------------------------------------------------------- /tests/fixtures/exports-field/node_modules/@scope/import-require/dist/cjs/a/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field/node_modules/@scope/import-require/dist/cjs/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field/node_modules/@scope/import-require/dist/esm/a/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field/node_modules/@scope/import-require/dist/esm/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field5/node_modules/pkgexports/node_modules/internalpkg/x.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/full/a/node_modules/package2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "a" 3 | } -------------------------------------------------------------------------------- /tests/fixtures/full/a/node_modules/package3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "dir" 3 | } -------------------------------------------------------------------------------- /tests/fixtures/imports-field/node_modules/a/lib/browser.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /tests/fixtures/imports-field/node_modules/a/lib/main.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /tests/fixtures/incorrect-package/pack1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "./a.js", 3 | -------------------------------------------------------------------------------- /tests/fixtures/a.js: -------------------------------------------------------------------------------- 1 | module.exports = function a() { 2 | return "This is a"; 3 | }; 4 | -------------------------------------------------------------------------------- /tests/fixtures/b.js: -------------------------------------------------------------------------------- 1 | module.exports = function b() { 2 | return "This is b"; 3 | }; 4 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field/node_modules/exports-field/main.js: -------------------------------------------------------------------------------- 1 | module.exports = 2; 2 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field/node_modules/exports-field/x.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field2/node_modules/exports-field/main.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field4/node_modules/exports-field/main.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field5/node_modules/pkgexports/trailing-pattern-slash/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/full/a/node_modules/package5/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "package5" 3 | } -------------------------------------------------------------------------------- /tests/fixtures/imports-field/node_modules/a/lib/lib2/main.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /tests/fixtures/pnpm-structure/node_modules/exports-field-a/node_modules/.bin/placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/pnpm-structure/node_modules/exports-field-c/node_modules/.bin/placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/browser-module/lib/redirect3-target/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "dir" 3 | } -------------------------------------------------------------------------------- /tests/fixtures/exports-field/node_modules/exports-field/lib/main.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field2/node_modules/exports-field/index.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field2/node_modules/exports-field/lib/main.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[rust]": { 3 | "editor.formatOnSave": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2023-11-10" 3 | profile = "default" 4 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field/node_modules/exports-field/lib/browser.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field/node_modules/exports-field/lib/lib2/main.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field2/index.js: -------------------------------------------------------------------------------- 1 | console.log(require.resolve("exports-field/dist/main.js")) -------------------------------------------------------------------------------- /tests/fixtures/exports-field2/node_modules/exports-field/lib/browser.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field2/node_modules/exports-field/lib/lib2/main.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | -------------------------------------------------------------------------------- /tests/fixtures/node_modules/complexm/node_modules/m1/a.js: -------------------------------------------------------------------------------- 1 | module.exports = "the correct a.js"; 2 | -------------------------------------------------------------------------------- /tests/fixtures/scoped/node_modules/@scope/pack2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "./main.js" 3 | } 4 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "outer", 3 | "exports": "./main.js" 4 | } -------------------------------------------------------------------------------- /tests/fixtures/main-filed-no-relative/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "c", 3 | "module": "a" 4 | } 5 | -------------------------------------------------------------------------------- /tests/fixtures/node_modules/complexm/step1.js: -------------------------------------------------------------------------------- 1 | module.exports = require("m1/a") + require("m1"); 2 | -------------------------------------------------------------------------------- /tests/fixtures/browser-module/node_modules/browser-string/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "browser": "./target" 3 | } -------------------------------------------------------------------------------- /tests/fixtures/c.js: -------------------------------------------------------------------------------- 1 | module.exports = function b() { 2 | require("./a"); 3 | return "This is c"; 4 | }; 5 | -------------------------------------------------------------------------------- /tests/fixtures/incorrect-package/sideeffects-map/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "sideEffects": { 3 | 4 | } 5 | } -------------------------------------------------------------------------------- /tests/fixtures/incorrect-package/sideeffects-other-in-array/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "sideEffects": [1] 3 | } -------------------------------------------------------------------------------- /tests/fixtures/exports-field5/node_modules/pkgexports/subpath/dir1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "dir1" 3 | } 4 | -------------------------------------------------------------------------------- /tests/fixtures/main-field-inexist/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "./src/index", 3 | "module": "./module.js" 4 | } -------------------------------------------------------------------------------- /tests/fixtures/node_modules/complexm/node_modules/m1/index.js: -------------------------------------------------------------------------------- 1 | module.exports = " :) " + require("m2/b.js"); 2 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field3/pkg1/node_modules/m1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "m1", 3 | "exports": "./m1.js" 4 | } -------------------------------------------------------------------------------- /tests/fixtures/full/a/node_modules/package4/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "browser": { 3 | "./a.js": "./b" 4 | } 5 | } -------------------------------------------------------------------------------- /tests/fixtures/browser-after-main/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "main.js", 3 | "browser": { 4 | "./main.js": false 5 | } 6 | } -------------------------------------------------------------------------------- /tests/fixtures/multiple_modules/node_modules/m1/a.js: -------------------------------------------------------------------------------- 1 | module.exports = function a() { 2 | return "This is nested m1/a"; 3 | }; 4 | -------------------------------------------------------------------------------- /tests/fixtures/scoped/node_modules/@scope/pack1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "browser": { 3 | "./index.js": "./main.js" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tests/fixtures/pnpm-structure/node_modules/exports-field-aa/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "exports-field-aa", 3 | "main": "index.js" 4 | } -------------------------------------------------------------------------------- /bench/README.md: -------------------------------------------------------------------------------- 1 | # benchmark 2 | 3 | Benchmark based on ant design. 4 | 5 | ## Usage 6 | 7 | `sh ./scripts/run.sh [pnpm | yarn | npm]` 8 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@exports-field/core", 3 | "version": "1.0.0", 4 | "exports": "./a.js" 5 | } 6 | -------------------------------------------------------------------------------- /tests/fixtures/self-is-dep/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@scope/self-is-dep", 3 | "exports": { 4 | "./src/a": "./src/b.js" 5 | } 6 | } -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths-missing-baseURL/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.paths.json", 3 | "compilerOptions": { 4 | } 5 | } -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths-missing-baseURL/tsconfig.paths.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "#/*": ["./*"] 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths-override-baseURL/tsconfig.paths.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "#/*": ["./*"] 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /tests/fixtures/browser-to-self/node_modules/c.js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "c.js", 3 | "browser": "c.js", 4 | "module": "c.mjs", 5 | "types": "c.d.ts" 6 | } -------------------------------------------------------------------------------- /tests/fixtures/browser-to-self/node_modules/a.js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "a.js", 3 | "browser": "a.js", 4 | "main": "a", 5 | "module": "a.mjs", 6 | "types": "a.d.ts" 7 | } -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths-override-baseURL/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.paths.json", 3 | "compilerOptions": { 4 | "baseUrl": "./src" // comment 5 | } 6 | } -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths-extends-from-module/node_modules/module/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "foo": ["./test.ts"] 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths-extends-from-module/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "module/tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./src" // comment 5 | } 6 | } -------------------------------------------------------------------------------- /tests/fixtures/browser-module/node_modules/relative/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "relative", 3 | "browser": { 4 | "./lib/a": "./lib/b", 5 | "./lib/c": "./lib/../lib/b" 6 | } 7 | } -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths-extends-from-module/node_modules/@scope/module/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "foo": ["./test.ts"] 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /tests/fixtures/exports-field4/node_modules/exports-field/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "exports-field", 3 | "exportsField": { 4 | "exports": "./main.js" 5 | }, 6 | "ex": "./index" 7 | } 8 | -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths-extends-from-module/tsconfig.scope.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@scope/module/tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./src" // comment 5 | } 6 | } -------------------------------------------------------------------------------- /tests/fixtures/exports-field/node_modules/string-side-effects/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "exports-field", 3 | "version": "1.0.0", 4 | "main": "./index.js", 5 | "sideEffects": "*.js" 6 | } 7 | -------------------------------------------------------------------------------- /tests/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 | -------------------------------------------------------------------------------- /tests/fixtures/pnpm-structure/node_modules/exports-field-b/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "exports-field-b", 3 | "version": "1.0.0", 4 | "main": "./main.js", 5 | "exports": { 6 | "./b": "./index.js" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/fixtures/pnpm-structure/node_modules/exports-field-a/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "exports-field-a", 3 | "version": "1.0.0", 4 | "main": "./main.js", 5 | "exports": { 6 | ".": "./lib/index.js", 7 | "./a": "./lib/a.js" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/fixtures/pnpm-structure/node_modules/exports-field-c/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "exports-field-c", 3 | "version": "1.0.0", 4 | "main": "./main.js", 5 | "exports": { 6 | ".": "./lib/index.js", 7 | "./b": "./lib/b.js", 8 | "./c": "./lib/c.js" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | /target 4 | /Cargo.lock 5 | 6 | /coverage 7 | *.profraw 8 | *.profdata 9 | 10 | tests/bench.rs 11 | bench/esbuildResolve.js 12 | bench/enhanceResolve.js 13 | bench/esbuildResolve_native.go 14 | bench/esbuildResolve_native 15 | bench/go.mod 16 | bench/go.sum 17 | 18 | *.log -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nodejs_resolver 2 | 3 | ## Benchmark 4 | 5 | In the [bench](./bench/README.md), compared the the performance along [esbuild](https://github.com/evanw/esbuild), [enhanced-resolve](https://github.com/webpack/enhanced-resolve) and this project base on [ant-design](https://github.com/ant-design/ant-design) 6 | -------------------------------------------------------------------------------- /tests/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 | -------------------------------------------------------------------------------- /src/cache.rs: -------------------------------------------------------------------------------- 1 | use crate::entry::Entry; 2 | use crate::fs::CachedFS; 3 | use rustc_hash::FxHasher; 4 | use std::{hash::BuildHasherDefault, path::Path, sync::Arc}; 5 | 6 | #[derive(Debug, Default)] 7 | pub struct Cache { 8 | pub fs: CachedFS, 9 | /// File entries keyed by normalized paths 10 | pub entries: dashmap::DashMap, Arc, BuildHasherDefault>, 11 | } 12 | -------------------------------------------------------------------------------- /tests/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 | "#c-redirect/": "#ccc/" 12 | }, 13 | "other": { 14 | "imports": { 15 | "#b": "./a.js" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::{io, path::Path}; 2 | 3 | #[derive(Debug)] 4 | pub enum Error { 5 | Io(io::Error), 6 | UnexpectedJson((Box, serde_json::Error)), 7 | UnexpectedValue(String), 8 | ResolveFailedTag, 9 | Overflow, 10 | CantFindTsConfig(Box), 11 | } 12 | 13 | impl From for Error { 14 | fn from(value: std::io::Error) -> Self { 15 | Self::Io(value) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/fixtures/browser-to-self/node_modules/b.js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "b.js", 3 | "browser": "b.js", 4 | "main": "b", 5 | "module": "b.mjs", 6 | "types": "b.d.ts", 7 | "exports": { 8 | ".": { 9 | "types": "./b.d.ts", 10 | "require": "./b.js", 11 | "import": "./b.mjs", 12 | "browser": "./b.js" 13 | }, 14 | "./b.mjs": "./b.mjs", 15 | "./b.js": "./b.js", 16 | "./package.json": "./package.json" 17 | } 18 | } -------------------------------------------------------------------------------- /tests/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/lib2/", "./lib/"], 13 | "node": "./lib/", 14 | "default": "./lib/" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/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/lib2/", "./lib/"], 13 | "node": "./lib/", 14 | "default": "./lib/" 15 | }, 16 | "./dist/a.js": "./../../a.js" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field/node_modules/@scope/import-require/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "import-require", 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 | ".": "./dist/esm/index.js", 11 | "./a": { 12 | "import": "./dist/esm/a/index.js", 13 | "require": "./dist/cjs/a/index.js" 14 | } 15 | }, 16 | "sideEffects": ["./index.js", "./a.js"] 17 | } 18 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths: 7 | - 'Cargo.toml' 8 | 9 | jobs: 10 | publish: 11 | name: Publish to crate 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions-rs/toolchain@v1 16 | with: 17 | toolchain: stable 18 | override: true 19 | - uses: katyo/publish-crates@v1 20 | with: 21 | registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }} -------------------------------------------------------------------------------- /bench/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@babel/core": "7.18.6", 4 | "@babel/preset-typescript": "7.18.6", 5 | "@babel/plugin-proposal-decorators": "7.18.6", 6 | "enhanced-resolve": "5.10.0", 7 | "esbuild": "0.17.7", 8 | "benchmark": "2.1.4" 9 | }, 10 | "scripts": { 11 | "gen:rs": "node ./scripts/gen-bench.js rs", 12 | "gen:esbuild": "node ./scripts/gen-bench.js esbuild", 13 | "gen:enhanced": "node ./scripts/gen-bench.js enhanced" 14 | }, 15 | "packageManager": "npm@8.5.0" 16 | } 17 | -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "test0": ["./test0-success.ts"], 6 | "test1/*": ["./test1-success.ts"], 7 | "test2/*": ["./test2-success/*"], 8 | "t*t3/foo": ["./test3-succ*s.ts"], 9 | "test4/*": ["./test4-first/*", "./test4-second/*"], 10 | "test5/*": ["./test5-first/*", "./test5-second/*"], 11 | "/virtual-in/test": ["./actual/test"], 12 | "/virtual-in-star/*": ["./actual/*"], 13 | }, 14 | } 15 | } -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths-nested/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "nested", 4 | "paths": { 5 | "test0": ["./test0-success.ts"], 6 | "test1/*": ["./test1-success.ts"], 7 | "test2/*": ["./test2-success/*"], 8 | "t*t3/foo": ["./test3-succ*s.ts"], 9 | "test4/*": ["./test4-first/*", "./test4-second/*"], 10 | "test5/*": ["./test5-first/*", "./test5-second/*"], 11 | "/virtual-in/test": ["./actual/test"], 12 | "/virtual-in-star/*": ["./actual/*"], 13 | } 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/browser-module/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "browser": { 3 | "./lib/ignore.js": false, 4 | "./lib/replaced.js": "./lib/browser", 5 | "./lib/redirect.js": "./lib/sub", 6 | "./lib/redirect2.js": "./lib/sub/", 7 | "./lib/redirect3.js": "./lib/redirect3-target", 8 | "module-a": "./browser/module-a.js", 9 | "module-b": "module-c", 10 | "module-d": "module-b", 11 | "./toString": "./lib/toString.js", 12 | ".": false, 13 | "./util.inspect.js": false, 14 | "recursive-module": "recursive-module" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/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 | "webpack": ["./lib/lib2/", "./lib/"], 13 | "node": "./lib/", 14 | "default": "./lib/" 15 | }, 16 | "./dist/a.js": "./../../a.js", 17 | "./package.json": "./package.json" 18 | }, 19 | "sideEffects": false 20 | } 21 | -------------------------------------------------------------------------------- /tests/fixtures/tsconfig-paths-without-baseURL/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "test": [ 5 | ".", 6 | "..", 7 | "./good", 8 | ".\\good", 9 | "../good", 10 | "..\\good", 11 | "/good", 12 | "\\good", 13 | "c:/good", 14 | "c:\\good", 15 | "C:/good", 16 | "C:\\good", 17 | 18 | "bad", 19 | "@bad/core", 20 | ".*/bad", 21 | "..*/bad", 22 | "c*:\\bad", 23 | "c:*\\bad", 24 | "http://bad" 25 | ], 26 | "alias/*": ["./src/*"] 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | use std::{env, path::PathBuf}; 2 | 3 | use nodejs_resolver::{ResolveResult, Resolver}; 4 | 5 | // cargo run --example simple -- `pwd`/tests/fixtures/simple . 6 | // cargo watch -x 'run --example simple -- `pwd`/tests/fixtures/simple .' 7 | 8 | fn main() { 9 | let path = env::args().nth(1).expect("path"); 10 | let request = env::args().nth(2).expect("request"); 11 | let resolver = Resolver::new(Default::default()); 12 | let path_to_resolve = PathBuf::from(&path); 13 | match resolver.resolve(&path_to_resolve, &request) { 14 | Ok(ResolveResult::Resource(resource)) => println!("{:?}", resource.join()), 15 | Ok(ResolveResult::Ignored) => println!("Ignored"), 16 | Err(err) => println!("{err:?}"), 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/plugin/mod.rs: -------------------------------------------------------------------------------- 1 | mod alias; 2 | mod browser_field; 3 | mod exports_field; 4 | mod extension_alias; 5 | mod imports_field; 6 | mod main_field; 7 | mod main_file; 8 | mod parse; 9 | mod prefer_relative; 10 | mod symlink; 11 | 12 | use crate::{context::Context, Info, Resolver, State}; 13 | 14 | pub use alias::AliasPlugin; 15 | pub use browser_field::BrowserFieldPlugin; 16 | pub use exports_field::ExportsFieldPlugin; 17 | pub use extension_alias::ExtensionAliasPlugin; 18 | pub use imports_field::ImportsFieldPlugin; 19 | pub use main_field::MainFieldPlugin; 20 | pub use main_file::MainFilePlugin; 21 | pub use parse::ParsePlugin; 22 | pub use prefer_relative::PreferRelativePlugin; 23 | pub use symlink::SymlinkPlugin; 24 | 25 | pub(crate) trait Plugin { 26 | fn apply(&self, resolver: &Resolver, info: Info, context: &mut Context) -> State; 27 | } 28 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nodejs-resolver" 3 | version = "0.1.1" 4 | edition = "2021" 5 | license = "MIT" 6 | description = "nodejs resolve" 7 | repository = "https://github.com/bvanjoi/nodejs_resolver" 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | serde_json = { version = "1.0.104", features = ["preserve_order"] } 12 | indexmap = "2.0.0" 13 | dashmap = "5.5.0" 14 | daachorse = "1.0.0" 15 | once_cell = "1.18.0" 16 | tracing = "0.1.37" 17 | jsonc-parser = { version = "0.22.1", features = ["serde"] } 18 | serde = { version = "1.0.183", features = ["derive"] } 19 | tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } 20 | rustc-hash = "1.1.0" 21 | path-absolutize = "3.1.0" 22 | dunce = "1.0.4" 23 | 24 | [dev-dependencies] 25 | tracing-span-tree = "0.1.1" 26 | 27 | [profile.bench] 28 | lto = true 29 | 30 | [profile.release] 31 | lto = true 32 | -------------------------------------------------------------------------------- /src/plugin/prefer_relative.rs: -------------------------------------------------------------------------------- 1 | use crate::{kind::PathKind, log::depth, Context, Info, Resolver, State}; 2 | 3 | #[derive(Default)] 4 | pub struct PreferRelativePlugin; 5 | 6 | impl PreferRelativePlugin { 7 | pub fn apply(resolver: &Resolver, info: Info, context: &mut Context) -> State { 8 | if matches!(info.request().kind(), PathKind::Relative) { 9 | return State::Resolving(info); 10 | } 11 | 12 | if resolver.options.prefer_relative { 13 | tracing::debug!("AliasPlugin works({})", depth(&context.depth)); 14 | let target = format!("./{}", info.request().target()); 15 | let info = info.clone().with_target(&target); 16 | let stats = resolver._resolve(info, context); 17 | if stats.is_finished() { 18 | return stats; 19 | } 20 | tracing::debug!("Leaving AliasPlugin({})", depth(&context.depth)); 21 | } 22 | State::Resolving(info) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/plugin/main_file.rs: -------------------------------------------------------------------------------- 1 | use super::Plugin; 2 | use crate::{log::color, log::depth, Context, Info, Resolver, State}; 3 | 4 | pub struct MainFilePlugin; 5 | 6 | impl Plugin for MainFilePlugin { 7 | fn apply(&self, resolver: &Resolver, info: Info, context: &mut Context) -> State { 8 | let path = info.to_resolved_path(); 9 | for main_file in &resolver.options.main_files { 10 | tracing::debug!( 11 | "MainFile works, it pointed to {}({})", 12 | color::blue(main_file), 13 | depth(&context.depth) 14 | ); 15 | let main_file_info = info 16 | .clone() 17 | .with_path(&path) 18 | .with_target(&format!("./{main_file}")); 19 | let state = resolver._resolve(main_file_info, context); 20 | if state.is_finished() { 21 | return state; 22 | } 23 | tracing::debug!("Leaving MainFile({})", depth(&context.depth)); 24 | } 25 | State::Resolving(info) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/state.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, Info, ResolveResult}; 2 | 3 | #[derive(Debug)] 4 | pub enum State { 5 | Success(ResolveResult), 6 | Resolving(Info), 7 | /// return error directly 8 | Error(Error), 9 | /// forEachBail 10 | Failed(Info), 11 | } 12 | 13 | impl State { 14 | pub fn then State>(self, op: F) -> Self { 15 | match self { 16 | State::Resolving(info) => op(info), 17 | _ => self, 18 | } 19 | } 20 | 21 | pub fn map_success State>(self, op: F) -> Self { 22 | match self { 23 | State::Success(ResolveResult::Resource(info)) => op(info), 24 | _ => self, 25 | } 26 | } 27 | 28 | pub fn map_failed State>(self, op: F) -> Self { 29 | match self { 30 | State::Failed(info) => op(info), 31 | _ => self, 32 | } 33 | } 34 | 35 | pub fn is_finished(&self) -> bool { 36 | matches!(self, State::Success(_) | State::Error(_)) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/context.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub struct Context { 3 | pub depth: Depth, 4 | pub fully_specified: Bool, 5 | pub resolve_to_context: Bool, 6 | } 7 | 8 | impl Context { 9 | pub fn new(fully_specified: bool, resolve_to_context: bool) -> Self { 10 | Self { 11 | depth: Depth::new(), 12 | fully_specified: Bool(fully_specified), 13 | resolve_to_context: Bool(resolve_to_context), 14 | } 15 | } 16 | } 17 | 18 | #[derive(Debug)] 19 | pub struct Bool(bool); 20 | 21 | impl Bool { 22 | pub fn set(&mut self, value: bool) { 23 | self.0 = value 24 | } 25 | pub fn get(&self) -> bool { 26 | self.0 27 | } 28 | } 29 | 30 | #[derive(Debug)] 31 | pub struct Depth(u16); 32 | 33 | impl Depth { 34 | fn new() -> Self { 35 | Self(0) 36 | } 37 | 38 | pub fn increase(&mut self) { 39 | self.0 += 1; 40 | } 41 | 42 | pub fn decrease(&mut self) { 43 | self.0 -= 1; 44 | } 45 | 46 | pub fn cmp(&self, other: u16) -> std::cmp::Ordering { 47 | self.0.cmp(&other) 48 | } 49 | 50 | pub fn value(&self) -> u16 { 51 | self.0 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/plugin/parse.rs: -------------------------------------------------------------------------------- 1 | use crate::{depth, Context, Info, Resolver, State}; 2 | 3 | pub struct ParsePlugin; 4 | 5 | impl ParsePlugin { 6 | pub fn apply(resolver: &Resolver, info: Info, context: &mut Context) -> State { 7 | let request = info.request(); 8 | let had_hash = !request.fragment().is_empty(); 9 | let no_query = request.query().is_empty(); 10 | let had_request = !info.request().target().is_empty(); 11 | if no_query && had_hash && had_request { 12 | tracing::debug!("ParsePlugin works({})", depth(&context.depth)); 13 | let target = format!( 14 | "{}{}{}", 15 | request.target(), 16 | if request.is_directory() { "/" } else { "" }, 17 | request.fragment() 18 | ); 19 | let info = Info::from(info.normalized_path().clone()).with_target(&target); 20 | let state = resolver._resolve(info, context); 21 | if state.is_finished() { 22 | return state; 23 | } 24 | tracing::debug!("Leaving ParsePlugin({})", depth(&context.depth)); 25 | } 26 | State::Resolving(info) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags-ignore: 8 | - "**" 9 | paths-ignore: 10 | - "**/*.md" 11 | - LICENSE 12 | - "**/*.gitignore" 13 | - .editorconfig 14 | pull_request: null 15 | 16 | jobs: 17 | test: 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | os: [ubuntu-latest, windows-latest, macos-latest] 22 | runs-on: ${{ matrix.os }} 23 | steps: 24 | - uses: actions/checkout@v3 25 | 26 | - name: Install toolchain 27 | run: rustup show 28 | 29 | - name: Cache 30 | uses: Swatinem/rust-cache@v2 31 | 32 | - if: ${{ matrix.os == 'ubuntu-latest' }} 33 | name: Clippy 34 | uses: actions-rs/clippy-check@v1 35 | with: 36 | token: ${{ secrets.GITHUB_TOKEN }} 37 | args: --all-targets --all-features -- --deny warnings 38 | 39 | - name: Run cargo check 40 | uses: actions-rs/cargo@v1 41 | with: 42 | command: check 43 | args: --all-features --release 44 | 45 | - name: Test 46 | uses: actions-rs/cargo@v1 47 | with: 48 | command: test 49 | args: --all-features --no-fail-fast 50 | -------------------------------------------------------------------------------- /bench/scripts/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | MANAGER=$1 6 | FIXTURE='ant-design' 7 | 8 | if [ -d "$FIXTURE" ]; then 9 | echo "Directory $FIXTURE already exists." 10 | else 11 | echo "Cloning $FIXTURE (only cloned master branch and without commmites)" 12 | git clone https://github.com/bvanjoi/ant-design.git -b master --single-branch --depth 1 13 | fi 14 | 15 | # insall deps 16 | cd $FIXTURE 17 | 18 | if [[ "$MANAGER" == "npm" ]]; then 19 | npm install --force 20 | elif [[ "$MANAGER" == "yarn" ]]; then 21 | npm install -g yarn 22 | yarn 23 | elif [[ "$MANAGER" == "pnpm" ]]; then 24 | npm install -g pnpm 25 | pnpm i 26 | else 27 | echo "Unexpected package manager" 28 | echo $MANAGER 29 | echo "$MANAGER" 30 | exit 1 31 | fi 32 | 33 | cd - 34 | 35 | # generator benchmark case 36 | npm install 37 | npm run gen:rs 38 | # npm run gen:esbuild 39 | # npm run gen:enhanced 40 | 41 | # run 42 | 43 | RS_OUTPUT="rs_bench.txt" 44 | # ESBUILD_OUTPUT="esbuild_bench.txt" 45 | # ENHANCED_OUTPUT="enhanced_bench.txt" 46 | 47 | # node ../esbuildResolve.js | tee $ESBUILD_OUTPUT 48 | # node ../enhanceResolve.js | tee $ENHANCED_OUTPUT 49 | cargo bench --package nodejs-resolver --test bench --all-features -- bench_test::ant_design_bench | tee $RS_OUTPUT 50 | -------------------------------------------------------------------------------- /src/resource.rs: -------------------------------------------------------------------------------- 1 | use crate::{description::DescriptionData, info::Info, Resolver}; 2 | use std::{path::PathBuf, sync::Arc}; 3 | 4 | #[derive(Debug, Clone)] 5 | pub struct Resource { 6 | pub path: PathBuf, 7 | pub query: Option, 8 | pub fragment: Option, 9 | pub description: Option>, 10 | } 11 | 12 | impl Resource { 13 | pub(crate) fn new(info: Info, resolver: &Resolver) -> Self { 14 | let path = info.normalized_path().as_ref().to_path_buf(); 15 | let query = info.request().query(); 16 | let fragment = info.request().fragment(); 17 | let description = resolver 18 | .load_entry(&path) 19 | .pkg_info(resolver) 20 | .unwrap() 21 | .clone(); 22 | Resource { 23 | path, 24 | query: (!query.is_empty()).then(|| query.into()), 25 | fragment: (!fragment.is_empty()).then(|| fragment.into()), 26 | description, 27 | } 28 | } 29 | 30 | pub fn join(&self) -> PathBuf { 31 | let mut buf = format!("{}", self.path.display()); 32 | if let Some(query) = self.query.as_ref() { 33 | buf.push_str(query); 34 | } 35 | if let Some(fragment) = self.fragment.as_ref() { 36 | buf.push_str(fragment); 37 | } 38 | PathBuf::from(buf) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.github/workflows/bench.yml: -------------------------------------------------------------------------------- 1 | name: bench 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags-ignore: 8 | - "**" 9 | paths-ignore: 10 | - "**/*.md" 11 | - LICENSE 12 | - "**/*.gitignore" 13 | - .editorconfig 14 | pull_request: null 15 | 16 | jobs: 17 | benchmark: 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | manager: [npm, pnpm] 22 | runs-on: ubuntu-latest 23 | permissions: write-all 24 | steps: 25 | - uses: actions/checkout@v3 26 | 27 | - name: Install toolchain 28 | run: rustup show 29 | 30 | - uses: actions/setup-node@v3 31 | with: 32 | node-version: 16 33 | cache: npm 34 | cache-dependency-path: bench/package-lock.json 35 | 36 | - name: Cache 37 | uses: Swatinem/rust-cache@v2 38 | 39 | - name: Generate bench case and run 40 | run: | 41 | cd ./bench 42 | bash ./scripts/run.sh ${{ matrix.manager }} 43 | cd .. 44 | 45 | - name: Download previous benchmark data 46 | uses: actions/cache@v3 47 | with: 48 | path: ./cache 49 | key: ${{ runner.os }}-${{ matrix.manager }}-benchmark 50 | 51 | - name: Store benchmark result 52 | uses: benchmark-action/github-action-benchmark@v1 53 | with: 54 | tool: "cargo" 55 | output-file-path: ./bench/rs_bench.txt 56 | external-data-json-path: ./cache/benchmark-data.json 57 | comment-always: true 58 | github-token: ${{ secrets.GITHUB_TOKEN }} 59 | -------------------------------------------------------------------------------- /src/plugin/extension_alias.rs: -------------------------------------------------------------------------------- 1 | use super::Plugin; 2 | use crate::{kind::PathKind, Context, Info, Resolver, State}; 3 | 4 | pub struct ExtensionAliasPlugin<'a> { 5 | extension: &'a str, 6 | alias_list: &'a Vec, 7 | } 8 | 9 | impl<'a> ExtensionAliasPlugin<'a> { 10 | pub fn new(extension: &'a str, alias_list: &'a Vec) -> Self { 11 | Self { 12 | extension, 13 | alias_list, 14 | } 15 | } 16 | } 17 | 18 | impl<'a> Plugin for ExtensionAliasPlugin<'a> { 19 | fn apply(&self, resolver: &Resolver, info: Info, context: &mut Context) -> State { 20 | let request = info.request(); 21 | let target = request.target(); 22 | if matches!(request.kind(), PathKind::Normal) 23 | || target.is_empty() 24 | || !target.ends_with(self.extension) 25 | { 26 | State::Resolving(info) 27 | } else if !self.alias_list.is_empty() { 28 | for alias in self.alias_list { 29 | let target = &format!( 30 | "{}{}", 31 | &target[0..target.len() - self.extension.len()], 32 | alias 33 | ); 34 | let next = info.clone().with_target(target); 35 | let path = next.to_resolved_path().to_path_buf(); 36 | let next = next.with_path(path).with_target(""); 37 | let origin = context.fully_specified.get(); 38 | context.fully_specified.set(true); 39 | let state = resolver._resolve(next, context); 40 | context.fully_specified.set(origin); 41 | if state.is_finished() { 42 | return state; 43 | } 44 | } 45 | State::Failed(info) 46 | } else { 47 | State::Resolving(info) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/log.rs: -------------------------------------------------------------------------------- 1 | use crate::context::Depth; 2 | use tracing_subscriber::prelude::*; 3 | 4 | pub fn enable_by_env() { 5 | let is_enabled = std::env::var("RESOLVER_TRACE").map_or(false, |var| { 6 | matches!(var.as_str(), "TRACE" | "DEBUG" | "INFO" | "WARN" | "ERROR") 7 | }); 8 | if !is_enabled { 9 | return; 10 | } 11 | let formatter = Formatter::default(); 12 | tracing_subscriber::Registry::default() 13 | .with(formatter) 14 | .with(tracing_subscriber::EnvFilter::from_env("RESOLVER_TRACE")) 15 | .init(); 16 | } 17 | 18 | #[derive(Default)] 19 | struct Formatter {} 20 | 21 | impl tracing_subscriber::Layer for Formatter 22 | where 23 | S: tracing::Subscriber + std::fmt::Debug, 24 | { 25 | fn on_event(&self, event: &tracing::Event<'_>, _: tracing_subscriber::layer::Context<'_, S>) { 26 | event.record(&mut Data); 27 | } 28 | } 29 | 30 | struct Data; 31 | 32 | impl tracing::field::Visit for Data { 33 | fn record_debug(&mut self, _field: &tracing::field::Field, value: &dyn std::fmt::Debug) { 34 | eprintln!("{value:?}"); 35 | } 36 | } 37 | 38 | /// TODO: use marco 39 | pub mod color { 40 | const BOLD: &str = "\u{001b}[1m"; 41 | const RED: &str = "\u{001b}[31m"; 42 | const GREEN: &str = "\u{001b}[32m"; 43 | const BLUE: &str = "\u{001b}[34m"; 44 | const CYAN: &str = "\u{001b}[36m"; 45 | const RESET: &str = "\u{001b}[0m"; 46 | 47 | pub fn bold(s: &T) -> String { 48 | format!("{BOLD}{s}{RESET}") 49 | } 50 | 51 | pub fn red(s: &T) -> String { 52 | format!("{RED}{s}{RESET}") 53 | } 54 | 55 | pub fn green(s: &T) -> String { 56 | format!("{GREEN}{s}{RESET}") 57 | } 58 | 59 | pub fn blue(s: &T) -> String { 60 | format!("{BLUE}{s}{RESET}") 61 | } 62 | 63 | pub fn cyan(s: &T) -> String { 64 | format!("{CYAN}{s}{RESET}") 65 | } 66 | } 67 | 68 | pub fn depth(depth: &Depth) -> String { 69 | format!("Depth: {}", color::bold(&depth.value())) 70 | } 71 | -------------------------------------------------------------------------------- /tests/fixtures/exports-field5/node_modules/pkgexports/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pkg-exports", 3 | "exports": { 4 | "./hole": "./lib/hole.js", 5 | "./space": "./sp%20ce.js", 6 | "./valid-cjs": "./asdf.js", 7 | "./sub/*": "./*", 8 | "./sub/internal/*": null, 9 | "./belowdir/*": "../belowdir/*", 10 | "./belowfile": "../belowfile", 11 | "./null": null, 12 | "./null/": null, 13 | "./invalid1": {}, 14 | "./invalid2": 1234, 15 | "./invalid3": "", 16 | "./invalid4": {}, 17 | "./invalid5": "invalid5.js", 18 | "./fallbackdir/*": [[], null, {}, "builtin:x/*", "./*"], 19 | "./fallbackfile": [[], null, {}, "builtin:x", "./asdf.js"], 20 | "./nofallback1": [], 21 | "./nofallback2": [null, {}, "builtin:x"], 22 | "./nodemodules": "./node_modules/internalpkg/x.js", 23 | "./doubleslash": ".//asdf.js", 24 | "./no-addons": { 25 | "node-addons": "./addons-entry.js", 26 | "default": "./no-addons-entry.js" 27 | }, 28 | "./condition": [{ 29 | "custom-condition": { 30 | "import": "./custom-condition.mjs", 31 | "require": "./custom-condition.js" 32 | }, 33 | "import": "///overridden", 34 | "require": { 35 | "require": { 36 | "nomatch": "./nothing.js" 37 | }, 38 | "default": "./sp ce.js" 39 | }, 40 | "default": "./asdf.js", 41 | "node": "./lib/hole.js", 42 | "import": { 43 | "nomatch": "./nothing.js" 44 | } 45 | }], 46 | "./no-ext": "./asdf", 47 | "./resolve-self": { 48 | "require": "./resolve-self.js", 49 | "import": "./resolve-self.mjs" 50 | }, 51 | "./resolve-self-invalid": { 52 | "require": "./resolve-self-invalid.js", 53 | "import": "./resolve-self-invalid.mjs" 54 | }, 55 | "./*/trailer": "./subpath/*.js", 56 | "./*/*railer": "never", 57 | "./*trailer": "never", 58 | "./*/dir2/trailer": "./subpath/*/index.js", 59 | "./a/*": "./subpath/*.js", 60 | "./a/b/": "./nomatch/", 61 | "./a/b*": "./subpath*.js", 62 | "./subpath/*": "./subpath/*", 63 | "./subpath/sub-*": "./subpath/dir1/*.js", 64 | "./subpath/sub-*.js": "./subpath/dir1/*.js", 65 | "./features/*": "./subpath/*/*.js", 66 | "./trailing-pattern-slash*": "./trailing-pattern-slash*index.js" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/plugin/symlink.rs: -------------------------------------------------------------------------------- 1 | use crate::{log::depth, Context, Info, ResolveResult, Resolver, State}; 2 | use std::path::PathBuf; 3 | 4 | #[derive(Default)] 5 | pub struct SymlinkPlugin; 6 | 7 | impl SymlinkPlugin { 8 | pub fn apply(resolver: &Resolver, info: Info, context: &mut Context) -> State { 9 | debug_assert!(info.request().target().is_empty()); 10 | 11 | if !resolver.options.symlinks { 12 | return State::Success(ResolveResult::Resource(info)); 13 | } 14 | 15 | tracing::debug!("SymlinkPlugin works({})", depth(&context.depth)); 16 | let state = resolve_symlink(resolver, info, context); 17 | tracing::debug!("Leaving SymlinkPlugin({})", depth(&context.depth)); 18 | state 19 | } 20 | } 21 | 22 | fn resolve_symlink(resolver: &Resolver, info: Info, _context: &mut Context) -> State { 23 | let head = resolver.load_entry(info.normalized_path().as_ref()); 24 | 25 | let entry_path = head.path(); 26 | let mut entry = head.as_ref(); 27 | let mut index = 0; 28 | let mut symlink = None; 29 | let mut stack = vec![]; 30 | 31 | loop { 32 | if let Some(real) = entry.real() { 33 | symlink = Some(real.to_path_buf()); 34 | break; 35 | } 36 | 37 | if let Some(link) = entry.symlink() { 38 | symlink = Some(link.to_path_buf()); 39 | break; 40 | } 41 | 42 | stack.push(entry); 43 | 44 | if let Some(e) = entry.parent() { 45 | index += 1; 46 | entry = e; 47 | } else { 48 | break; 49 | } 50 | } 51 | 52 | let path = if let Some(symlink) = symlink { 53 | let mut path = symlink; 54 | let tail = entry_path 55 | .components() 56 | .rev() 57 | .take(index) 58 | .collect::>(); 59 | for c in tail.into_iter().rev() { 60 | path.push(c); 61 | } 62 | head.init_real(path.clone().into_boxed_path()); 63 | path 64 | } else { 65 | stack 66 | .into_iter() 67 | .for_each(|entry| entry.init_real(entry.path().into())); 68 | let mut path = PathBuf::default(); 69 | for c in entry_path.components() { 70 | path.push(c); 71 | } 72 | path 73 | }; 74 | let info = info.with_path(path); 75 | State::Success(ResolveResult::Resource(info)) 76 | } 77 | -------------------------------------------------------------------------------- /src/plugin/main_field.rs: -------------------------------------------------------------------------------- 1 | use super::Plugin; 2 | use crate::{description::DescriptionData, log::color, log::depth, Context, Info, Resolver, State}; 3 | 4 | pub struct MainFieldPlugin<'a> { 5 | pkg_info: &'a DescriptionData, 6 | } 7 | 8 | impl<'a> MainFieldPlugin<'a> { 9 | pub fn new(pkg_info: &'a DescriptionData) -> Self { 10 | Self { pkg_info } 11 | } 12 | } 13 | 14 | impl<'a> Plugin for MainFieldPlugin<'a> { 15 | fn apply(&self, resolver: &Resolver, info: Info, context: &mut Context) -> State { 16 | let resolved = info.to_resolved_path(); 17 | if !self.pkg_info.dir().as_ref().eq(&*resolved) { 18 | return State::Resolving(info); 19 | } 20 | let main_field_info = info.clone().with_path(resolved).with_target("."); 21 | 22 | for user_main_field in &resolver.options.main_fields { 23 | if let Some(main_field) = self 24 | .pkg_info 25 | .data() 26 | .raw() 27 | .get(user_main_field) 28 | .and_then(|value| value.as_str()) 29 | { 30 | if main_field == "." || main_field == "./" { 31 | // if it pointed to itself. 32 | break; 33 | } 34 | tracing::debug!( 35 | "MainField in '{}' works, using {} field({})", 36 | color::blue(&format!("{:?}/package.json", self.pkg_info.dir().as_ref())), 37 | color::blue(user_main_field), 38 | depth(&context.depth) 39 | ); 40 | 41 | let main_field_info = if main_field.starts_with("./") { 42 | main_field_info.clone().with_target(main_field) 43 | } else { 44 | main_field_info 45 | .clone() 46 | .with_target(&format!("./{main_field}")) 47 | }; 48 | 49 | let fully_specified = context.fully_specified.get(); 50 | if fully_specified { 51 | context.fully_specified.set(false); 52 | } 53 | let state = resolver._resolve(main_field_info, context); 54 | if fully_specified { 55 | context.fully_specified.set(true); 56 | } 57 | if state.is_finished() { 58 | return state; 59 | } 60 | tracing::debug!("Leaving MainField({})", depth(&context.depth)); 61 | } 62 | } 63 | State::Resolving(info) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/info.rs: -------------------------------------------------------------------------------- 1 | use crate::parse::Request; 2 | use path_absolutize::Absolutize; 3 | #[cfg(unix)] 4 | use std::os::unix::ffi::OsStrExt; 5 | #[cfg(windows)] 6 | use std::os::windows::ffi::OsStrExt; 7 | use std::{borrow::Cow, path::Path, sync::Arc}; 8 | 9 | #[cfg(windows)] 10 | fn has_trailing_slash(p: &Path) -> bool { 11 | let last = p.as_os_str().encode_wide().last(); 12 | last == Some(b'\\' as u16) || last == Some(b'/' as u16) 13 | } 14 | #[cfg(unix)] 15 | fn has_trailing_slash(p: &Path) -> bool { 16 | p.as_os_str().as_bytes().last() == Some(&b'/') 17 | } 18 | 19 | #[derive(Debug, Clone, PartialEq)] 20 | pub struct NormalizedPath(Arc); 21 | 22 | impl NormalizedPath { 23 | pub fn new>(path: P) -> Self { 24 | // perf: this method does not re-allocate memory if the path does not contain any dots. 25 | let normalized = path.as_ref().absolutize_from(Path::new("")).unwrap(); 26 | let path = if has_trailing_slash(path.as_ref()) { 27 | Path::new(&format!("{}/", normalized.display())).into() 28 | } else { 29 | normalized.into() 30 | }; 31 | NormalizedPath(path) 32 | } 33 | } 34 | 35 | impl AsRef for NormalizedPath { 36 | fn as_ref(&self) -> &Path { 37 | &self.0 38 | } 39 | } 40 | 41 | #[derive(Debug, Clone)] 42 | pub struct Info { 43 | path: NormalizedPath, 44 | request: Request, 45 | } 46 | 47 | impl From for Info { 48 | fn from(value: NormalizedPath) -> Self { 49 | Info { 50 | path: value, 51 | request: Default::default(), 52 | } 53 | } 54 | } 55 | 56 | impl Info { 57 | #[must_use] 58 | pub fn new>(path: P, request: Request) -> Self { 59 | Self { 60 | path: NormalizedPath::new(path), 61 | request, 62 | } 63 | } 64 | 65 | #[must_use] 66 | pub fn with_path>(self, path: P) -> Self { 67 | Self { 68 | path: NormalizedPath::new(path), 69 | ..self 70 | } 71 | } 72 | 73 | #[must_use] 74 | pub fn with_request(self, request: Request) -> Self { 75 | Self { request, ..self } 76 | } 77 | 78 | #[must_use] 79 | pub fn with_target(self, target: &str) -> Self { 80 | let request = self.request.with_target(target); 81 | Self { request, ..self } 82 | } 83 | 84 | #[must_use] 85 | pub fn normalized_path(&self) -> &NormalizedPath { 86 | &self.path 87 | } 88 | 89 | #[must_use] 90 | pub fn request(&self) -> &Request { 91 | &self.request 92 | } 93 | 94 | #[must_use] 95 | pub fn to_resolved_path(&self) -> Cow<'_, Path> { 96 | if self.request.target().is_empty() || self.request.target() == "." { 97 | Cow::Borrowed(&self.path.0) 98 | } else { 99 | let p = NormalizedPath::new(self.path.as_ref().join(self.request.target())); 100 | Cow::Owned(p.0.to_path_buf()) 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/plugin/imports_field.rs: -------------------------------------------------------------------------------- 1 | use super::Plugin; 2 | use crate::{ 3 | context::Context, 4 | description::DescriptionData, 5 | log::color, 6 | log::depth, 7 | map::{Field, ImportsField}, 8 | Error, Info, PathKind, Resolver, State, 9 | }; 10 | 11 | pub struct ImportsFieldPlugin<'a> { 12 | pkg_info: &'a DescriptionData, 13 | } 14 | 15 | impl<'a> ImportsFieldPlugin<'a> { 16 | pub fn new(pkg_info: &'a DescriptionData) -> Self { 17 | Self { pkg_info } 18 | } 19 | 20 | fn check_target(&self, resolver: &Resolver, info: Info) -> State { 21 | let path = info.to_resolved_path(); 22 | if resolver.load_entry(&path).is_file() { 23 | if let Err(msg) = ImportsField::check_target(info.request().target()) { 24 | let msg = format!("{msg} in {:?}/package.json", &self.pkg_info.dir().as_ref()); 25 | State::Error(Error::UnexpectedValue(msg)) 26 | } else { 27 | State::Resolving(info) 28 | } 29 | } else { 30 | State::Error(Error::UnexpectedValue(format!( 31 | "Package path {} can't imported in {:?}", 32 | info.request().target(), 33 | info.normalized_path().as_ref() 34 | ))) 35 | } 36 | } 37 | } 38 | 39 | impl<'a> Plugin for ImportsFieldPlugin<'a> { 40 | fn apply(&self, resolver: &Resolver, info: Info, context: &mut Context) -> State { 41 | if !info.request().target().starts_with('#') { 42 | return State::Resolving(info); 43 | } 44 | 45 | let root = match self.pkg_info.data().raw().get("imports") { 46 | Some(tree) => tree, 47 | None => return State::Resolving(info), 48 | }; 49 | 50 | let list = match ImportsField::field_process( 51 | root, 52 | info.request().target(), 53 | &resolver.options.condition_names, 54 | ) { 55 | Ok(list) => list, 56 | Err(err) => return State::Error(err), 57 | }; 58 | 59 | if let Some(item) = list.first() { 60 | tracing::debug!( 61 | "ImportsField in '{}' works, trigger by '{}', mapped to '{}'({})", 62 | color::blue(&format!("{:?}/package.json", self.pkg_info.dir().as_ref())), 63 | color::blue(&info.request().target()), 64 | color::blue(&item), 65 | depth(&context.depth) 66 | ); 67 | let request = Resolver::parse(item); 68 | let is_relative = !matches!(request.kind(), PathKind::Normal | PathKind::Internal); 69 | let info = Info::from(self.pkg_info.dir().clone()).with_request(request); 70 | if is_relative { 71 | self.check_target(resolver, info) 72 | } else { 73 | let fully_specified = context.fully_specified.get(); 74 | if fully_specified { 75 | context.fully_specified.set(false); 76 | } 77 | let state = resolver._resolve(info, context); 78 | if fully_specified { 79 | context.fully_specified.set(true); 80 | } 81 | state 82 | } 83 | } else { 84 | State::Error(Error::UnexpectedValue(format!( 85 | "Package path {} can't imported in {:?}", 86 | info.request().target(), 87 | info.normalized_path().as_ref() 88 | ))) 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/description.rs: -------------------------------------------------------------------------------- 1 | use crate::info::NormalizedPath; 2 | use crate::{AliasMap, Error, RResult}; 3 | use once_cell::sync::OnceCell; 4 | use std::path::Path; 5 | use std::sync::Arc; 6 | 7 | #[derive(Debug)] 8 | pub struct PkgJSON { 9 | name: Option>, 10 | alias_fields: OnceCell>, 11 | raw: Arc, 12 | } 13 | 14 | impl PkgJSON { 15 | pub(crate) fn parse(content: &str, file_path: &Path) -> RResult { 16 | let json: serde_json::Value = 17 | tracing::debug_span!("serde_json_from_str").in_scope(|| { 18 | serde_json::from_str(content) 19 | .map_err(|error| Error::UnexpectedJson((file_path.into(), error))) 20 | })?; 21 | 22 | let name = json.get("name").and_then(|v| v.as_str()).map(|s| s.into()); 23 | 24 | Ok(Self { 25 | name, 26 | alias_fields: OnceCell::new(), 27 | raw: Arc::from(json), 28 | }) 29 | } 30 | 31 | pub fn alias_fields(&self) -> &Vec<(String, AliasMap)> { 32 | self.alias_fields.get_or_init(|| { 33 | let mut alias_fields = Vec::new(); 34 | 35 | if let Some(value) = self.raw.get("browser") { 36 | // https://github.com/defunctzombie/package-browser-field-spec 37 | if let Some(map) = value.as_object() { 38 | for (key, value) in map { 39 | if let Some(false) = value.as_bool() { 40 | alias_fields.push((key.to_string(), AliasMap::Ignored)); 41 | } else if let Some(s) = value.as_str() { 42 | alias_fields.push((key.to_string(), AliasMap::Target(s.to_string()))); 43 | } 44 | } 45 | } else if let Some(false) = value.as_bool() { 46 | alias_fields.push((String::from("."), AliasMap::Ignored)); 47 | } else if let Some(s) = value.as_str() { 48 | alias_fields.push((String::from("."), AliasMap::Target(s.to_string()))); 49 | } 50 | } 51 | alias_fields 52 | }) 53 | } 54 | 55 | pub(crate) fn get_filed(&self, field: &Vec) -> Option<&serde_json::Value> { 56 | let mut current_value = self.raw().as_ref(); 57 | for current_field in field { 58 | if !current_value.is_object() { 59 | return None; 60 | } 61 | match current_value.get(current_field) { 62 | Some(next) => current_value = next, 63 | None => return None, 64 | }; 65 | } 66 | Some(current_value) 67 | } 68 | 69 | pub fn name(&self) -> Option<&str> { 70 | self.name.as_deref() 71 | } 72 | 73 | pub fn raw(&self) -> &Arc { 74 | &self.raw 75 | } 76 | } 77 | 78 | #[derive(Debug)] 79 | pub struct DescriptionData { 80 | json: PkgJSON, 81 | /// The path to the directory where the description file located. 82 | /// It not a property in package.json. 83 | dir_path: NormalizedPath, 84 | } 85 | 86 | impl DescriptionData { 87 | pub fn new>(json: PkgJSON, dir_path: P) -> Self { 88 | Self { 89 | json, 90 | dir_path: NormalizedPath::new(dir_path), 91 | } 92 | } 93 | 94 | pub fn dir(&self) -> &NormalizedPath { 95 | &self.dir_path 96 | } 97 | 98 | pub fn data(&self) -> &PkgJSON { 99 | &self.json 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/plugin/alias.rs: -------------------------------------------------------------------------------- 1 | use super::Plugin; 2 | use crate::{log::depth, options::Alias, AliasMap, Context, Info, ResolveResult, Resolver, State}; 3 | 4 | pub struct AliasPlugin<'a>(&'a Alias); 5 | 6 | impl<'a> AliasPlugin<'a> { 7 | pub fn new(alias: &'a Alias) -> Self { 8 | Self(alias) 9 | } 10 | 11 | fn alias(&self) -> &Alias { 12 | self.0 13 | } 14 | } 15 | 16 | impl<'a> Plugin for AliasPlugin<'a> { 17 | fn apply(&self, resolver: &Resolver, info: Info, context: &mut Context) -> State { 18 | let inner_target = info.request().target(); 19 | for (from, array) in self.alias() { 20 | let only_module = from.ends_with('$'); 21 | let from_to = from.len(); 22 | let (hit, key) = if only_module { 23 | let sub = &from[0..from_to - 1]; 24 | if inner_target.eq(sub) { 25 | (true, sub) 26 | } else { 27 | (false, sub) 28 | } 29 | } else { 30 | let hit = inner_target 31 | .strip_prefix(from) 32 | .into_iter() 33 | .next() 34 | .map_or(false, |c| c.is_empty() || c.starts_with('/')); 35 | (hit, from.as_str()) 36 | }; 37 | if hit { 38 | tracing::debug!( 39 | "AliasPlugin works, triggered by '{from}'({})", 40 | depth(&context.depth) 41 | ); 42 | for to in array { 43 | match to { 44 | AliasMap::Target(to) => { 45 | if inner_target.starts_with(to) { 46 | // skip `target.starts_with(to)` to prevent infinite loop. 47 | continue; 48 | } 49 | let normalized_target = inner_target.replacen(key, to, 1); 50 | let old_request = info.request(); 51 | let old_query = old_request.query(); 52 | let old_fragment = old_request.fragment(); 53 | let request = Resolver::parse(&normalized_target); 54 | let request = 55 | match (request.query().is_empty(), request.fragment().is_empty()) { 56 | (true, true) => { 57 | request.with_query(old_query).with_fragment(old_fragment) 58 | } 59 | (true, false) => request.with_query(old_query), 60 | (false, true) => request.with_fragment(old_fragment), 61 | (false, false) => request, 62 | }; 63 | let alias_info = info.clone().with_request(request); 64 | let fully_specified = context.fully_specified.get(); 65 | if fully_specified { 66 | context.fully_specified.set(false); 67 | } 68 | let state = resolver._resolve(alias_info, context); 69 | if fully_specified { 70 | context.fully_specified.set(true); 71 | } 72 | if state.is_finished() { 73 | return state; 74 | } 75 | } 76 | AliasMap::Ignored => return State::Success(ResolveResult::Ignored), 77 | } 78 | } 79 | tracing::debug!("Leaving AliasPlugin({})", depth(&context.depth)); 80 | } 81 | } 82 | 83 | State::Resolving(info) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/fs.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | description::{DescriptionData, PkgJSON}, 3 | entry::EntryStat, 4 | tsconfig::TsConfig, 5 | RResult, 6 | }; 7 | use rustc_hash::FxHasher; 8 | use std::{ 9 | fmt::Debug, 10 | fs, 11 | hash::BuildHasherDefault, 12 | path::{Path, PathBuf}, 13 | sync::Arc, 14 | time::SystemTime, 15 | }; 16 | 17 | use dashmap::DashMap; 18 | 19 | use std::time::Duration; 20 | 21 | #[derive(Debug, Default)] 22 | pub struct CachedFS { 23 | /// Caches raw files 24 | entries: CachedMap, 25 | 26 | /// Caches parsed package.json 27 | descriptions: CachedMap, 28 | 29 | /// Caches tsconfig.json 30 | tsconfigs: CachedMap, 31 | } 32 | 33 | pub type CachedMap = DashMap, BuildHasherDefault>; 34 | 35 | #[derive(Debug)] 36 | pub struct CachedEntry { 37 | content: Arc, 38 | stat: EntryStat, 39 | } 40 | 41 | impl Clone for CachedEntry { 42 | fn clone(&self) -> Self { 43 | Self { 44 | content: Arc::clone(&self.content), 45 | stat: self.stat, 46 | } 47 | } 48 | } 49 | 50 | impl CachedEntry { 51 | fn new(content: T, stat: EntryStat) -> Self { 52 | Self { 53 | content: content.into(), 54 | stat, 55 | } 56 | } 57 | 58 | fn content(&self) -> Arc { 59 | self.content.clone() 60 | } 61 | } 62 | 63 | const DEBOUNCE_INTERVAL: Duration = Duration::from_millis(300); 64 | 65 | impl CachedFS { 66 | pub fn read_file(&self, path: &Path, file_stat: EntryStat) -> RResult> { 67 | if let Some(cached) = self.entries.get(path) { 68 | if self.is_modified(file_stat.modified(), cached.stat.modified()) { 69 | return Ok(cached.value().content()); 70 | } 71 | } 72 | let string = fs::read_to_string(path)?; 73 | let entry = CachedEntry::new(string, file_stat); 74 | self.entries.insert(path.to_path_buf(), entry.clone()); 75 | Ok(entry.content()) 76 | } 77 | 78 | pub fn read_description_file( 79 | &self, 80 | path: &Path, 81 | file_stat: EntryStat, 82 | ) -> RResult> { 83 | if let Some(cached) = self.descriptions.get(path) { 84 | if self.is_modified(file_stat.modified(), cached.stat.modified()) { 85 | return Ok(cached.value().content()); 86 | } 87 | } 88 | let string = fs::read_to_string(path)?; 89 | let json = PkgJSON::parse(&string, path)?; 90 | let dir = path.parent().unwrap().to_path_buf(); 91 | let info = DescriptionData::new(json, dir); 92 | let entry = CachedEntry::new(info, file_stat); 93 | self.descriptions.insert(path.to_path_buf(), entry.clone()); 94 | Ok(entry.content()) 95 | } 96 | 97 | pub fn read_tsconfig( 98 | &self, 99 | path: &Path, 100 | file_stat: EntryStat, 101 | ) -> RResult> { 102 | if let Some(cached) = self.tsconfigs.get(path) { 103 | if self.is_modified(file_stat.modified(), cached.stat.modified()) { 104 | return Ok(cached.value().content()); 105 | } 106 | } 107 | let string = fs::read_to_string(path)?; 108 | let serde_json = TsConfig::parse(&string, path)?; 109 | let entry = CachedEntry::new(serde_json, file_stat); 110 | self.tsconfigs.insert(path.to_path_buf(), entry.clone()); 111 | Ok(entry.content()) 112 | } 113 | 114 | fn is_modified(&self, before: Option, after: Option) -> bool { 115 | if let (Some(before), Some(after)) = (before, after) { 116 | if before.duration_since(after).expect("after > before") < DEBOUNCE_INTERVAL { 117 | return true; 118 | } 119 | } 120 | false 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/kind.rs: -------------------------------------------------------------------------------- 1 | use crate::Resolver; 2 | use daachorse::{CharwiseDoubleArrayAhoCorasick, CharwiseDoubleArrayAhoCorasickBuilder, MatchKind}; 3 | use once_cell::sync::Lazy; 4 | 5 | #[derive(Debug, Clone, Copy, PartialEq)] 6 | pub enum PathKind { 7 | Relative, 8 | AbsoluteWin, 9 | AbsolutePosix, 10 | Internal, 11 | Normal, 12 | } 13 | 14 | static ABSOLUTE_WIN_PATTERN_LENGTH_TWO: [&str; 52] = [ 15 | "a:", "b:", "c:", "d:", "e:", "f:", "g:", "h:", "i:", "j:", "k:", "l:", "m:", "n:", "o:", "p:", 16 | "q:", "r:", "s:", "t:", "u:", "v:", "w:", "x:", "y:", "z:", "A:", "B:", "C:", "D:", "E:", "F:", 17 | "G:", "H:", "I:", "J:", "K:", "L:", "M:", "N:", "O:", "P:", "Q:", "R:", "S:", "T:", "U:", "V:", 18 | "W:", "X:", "Y:", "Z:", 19 | ]; 20 | 21 | static ABSOLUTE_WIN_PATTERN_REST: [&str; 104] = [ 22 | "a:\\", "b:\\", "c:\\", "d:\\", "e:\\", "f:\\", "g:\\", "h:\\", "i:\\", "j:\\", "k:\\", "l:\\", 23 | "m:\\", "n:\\", "o:\\", "p:\\", "q:\\", "r:\\", "s:\\", "t:\\", "u:\\", "v:\\", "w:\\", "x:\\", 24 | "y:\\", "z:\\", "A:\\", "B:\\", "C:\\", "D:\\", "E:\\", "F:\\", "G:\\", "H:\\", "I:\\", "J:\\", 25 | "K:\\", "L:\\", "M:\\", "N:\\", "O:\\", "P:\\", "Q:\\", "R:\\", "S:\\", "T:\\", "U:\\", "V:\\", 26 | "W:\\", "X:\\", "Y:\\", "Z:\\", "a:/", "b:/", "c:/", "d:/", "e:/", "f:/", "g:/", "h:/", "i:/", 27 | "j:/", "k:/", "l:/", "m:/", "n:/", "o:/", "p:/", "q:/", "r:/", "s:/", "t:/", "u:/", "v:/", 28 | "w:/", "x:/", "y:/", "z:/", "A:/", "B:/", "C:/", "D:/", "E:/", "F:/", "G:/", "H:/", "I:/", 29 | "J:/", "K:/", "L:/", "M:/", "N:/", "O:/", "P:/", "Q:/", "R:/", "S:/", "T:/", "U:/", "V:/", 30 | "W:/", "X:/", "Y:/", "Z:/", 31 | ]; 32 | 33 | static PMA: Lazy> = Lazy::new(|| { 34 | CharwiseDoubleArrayAhoCorasickBuilder::new() 35 | .match_kind(MatchKind::LeftmostLongest) 36 | .build(ABSOLUTE_WIN_PATTERN_REST) 37 | .unwrap() 38 | }); 39 | 40 | impl Resolver { 41 | pub(crate) fn get_target_kind(target: &str) -> PathKind { 42 | if target.is_empty() { 43 | return PathKind::Relative; 44 | } 45 | 46 | let path_kind = if target.starts_with('#') { 47 | PathKind::Internal 48 | } else if target.starts_with('/') { 49 | PathKind::AbsolutePosix 50 | } else if target == "." 51 | || target.starts_with("./") 52 | || target.starts_with("../") 53 | || target == ".." 54 | { 55 | PathKind::Relative 56 | } else { 57 | if target.len() == 2 && ABSOLUTE_WIN_PATTERN_LENGTH_TWO.contains(&target) { 58 | return PathKind::AbsoluteWin; 59 | } 60 | let mut iter = PMA.leftmost_find_iter(target); 61 | if let Some(mat) = iter.next() { 62 | let match_pattern_len = ABSOLUTE_WIN_PATTERN_REST[mat.value()].len(); 63 | if mat.start() == 0 && mat.end() - mat.start() == match_pattern_len { 64 | return PathKind::AbsoluteWin; 65 | } 66 | } 67 | PathKind::Normal 68 | }; 69 | path_kind 70 | } 71 | } 72 | 73 | #[test] 74 | fn test_resolver() { 75 | assert!(matches!(Resolver::get_target_kind(""), PathKind::Relative)); 76 | assert!(matches!(Resolver::get_target_kind("."), PathKind::Relative)); 77 | assert!(matches!( 78 | Resolver::get_target_kind(".."), 79 | PathKind::Relative 80 | )); 81 | assert!(matches!( 82 | Resolver::get_target_kind("../a.js"), 83 | PathKind::Relative 84 | )); 85 | assert!(matches!( 86 | Resolver::get_target_kind("./a.js"), 87 | PathKind::Relative 88 | )); 89 | assert!(matches!( 90 | Resolver::get_target_kind("D:"), 91 | PathKind::AbsoluteWin 92 | )); 93 | assert!(matches!( 94 | Resolver::get_target_kind("C:path"), 95 | PathKind::Normal 96 | )); 97 | assert!(matches!( 98 | Resolver::get_target_kind("C:\\a"), 99 | PathKind::AbsoluteWin 100 | )); 101 | assert!(matches!( 102 | Resolver::get_target_kind("c:/a"), 103 | PathKind::AbsoluteWin 104 | )); 105 | assert!(matches!( 106 | Resolver::get_target_kind("cc:/a"), 107 | PathKind::Normal 108 | )); 109 | assert!(matches!(Resolver::get_target_kind("fs"), PathKind::Normal)); 110 | } 111 | -------------------------------------------------------------------------------- /src/plugin/browser_field.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | context::Context, description::DescriptionData, log::color, log::depth, AliasMap, Info, 3 | PathKind, Plugin, ResolveResult, Resolver, State, 4 | }; 5 | use path_absolutize::Absolutize; 6 | use std::path::{Path, PathBuf}; 7 | 8 | pub struct BrowserFieldPlugin<'a> { 9 | pkg_info: &'a DescriptionData, 10 | may_request_package_self: bool, 11 | } 12 | 13 | impl<'a> BrowserFieldPlugin<'a> { 14 | pub fn new(pkg_info: &'a DescriptionData, may_request_package_self: bool) -> Self { 15 | Self { 16 | pkg_info, 17 | may_request_package_self, 18 | } 19 | } 20 | 21 | fn request_target_is_module_and_equal_alias_key(alias_key: &String, info: &Info) -> bool { 22 | info.request().target().eq(alias_key) 23 | } 24 | 25 | fn request_path_is_equal_alias_key_path( 26 | alias_path: &Path, 27 | info: &Info, 28 | extensions: &[String], 29 | ) -> bool { 30 | let alias_path = alias_path.absolutize_from(Path::new("")).unwrap(); 31 | let request_path = info.to_resolved_path(); 32 | let mut request_path = request_path 33 | .absolutize_from(Path::new("")) 34 | .unwrap() 35 | .to_path_buf(); 36 | let v = unsafe { &mut *(&mut request_path as *mut PathBuf as *mut Vec) }; 37 | 38 | alias_path.eq(&request_path) 39 | || extensions.iter().any(|ext| { 40 | v.extend_from_slice(ext.as_bytes()); 41 | let result = alias_path.eq(&request_path); 42 | unsafe { 43 | v.set_len(v.len() - ext.len()); 44 | } 45 | result 46 | }) 47 | } 48 | } 49 | 50 | impl<'a> Plugin for BrowserFieldPlugin<'a> { 51 | fn apply(&self, resolver: &Resolver, info: Info, context: &mut Context) -> State { 52 | if !resolver.options.browser_field { 53 | return State::Resolving(info); 54 | } 55 | 56 | for (alias_key, alias_target) in self.pkg_info.data().alias_fields() { 57 | let should_deal_alias = match matches!(info.request().kind(), PathKind::Normal) 58 | && !self.may_request_package_self 59 | { 60 | true => Self::request_target_is_module_and_equal_alias_key(alias_key, &info), 61 | false => Self::request_path_is_equal_alias_key_path( 62 | &self.pkg_info.dir().as_ref().join(alias_key), 63 | &info, 64 | &resolver.options.extensions, 65 | ), 66 | }; 67 | if !should_deal_alias { 68 | continue; 69 | } 70 | tracing::debug!( 71 | "BrowserFiled in '{}' works, trigger by '{}'({})", 72 | color::blue(&format!( 73 | "{}/package.json", 74 | self.pkg_info.dir().as_ref().display() 75 | )), 76 | color::blue(alias_key), 77 | depth(&context.depth) 78 | ); 79 | match alias_target { 80 | AliasMap::Target(converted) => { 81 | if alias_key == converted { 82 | // pointed itself in `browser` field: 83 | // { 84 | // "recursive": "recursive" 85 | // } 86 | return State::Resolving(info); 87 | } 88 | 89 | let alias_info = Info::from(self.pkg_info.dir().clone()) 90 | .with_request(info.request().clone()) 91 | .with_target(converted); 92 | let fully_specified = context.fully_specified.get(); 93 | if fully_specified { 94 | context.fully_specified.set(false); 95 | } 96 | let state = resolver._resolve(alias_info, context); 97 | if fully_specified { 98 | context.fully_specified.set(true); 99 | } 100 | if state.is_finished() { 101 | return state; 102 | } 103 | tracing::debug!("Leaving BrowserFiled({})", depth(&context.depth)); 104 | } 105 | AliasMap::Ignored => return State::Success(ResolveResult::Ignored), 106 | }; 107 | } 108 | State::Resolving(info) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/plugin/exports_field.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | description::DescriptionData, 3 | log::color, 4 | log::depth, 5 | map::{ExportsField, Field}, 6 | resolve::get_path_from_request, 7 | Context, Error, Info, Resolver, State, 8 | }; 9 | 10 | use super::Plugin; 11 | 12 | pub struct ExportsFieldPlugin<'a> { 13 | pkg_info: &'a DescriptionData, 14 | } 15 | 16 | impl<'a> ExportsFieldPlugin<'a> { 17 | pub fn new(pkg_info: &'a DescriptionData) -> Self { 18 | Self { pkg_info } 19 | } 20 | } 21 | 22 | impl<'a> Plugin for ExportsFieldPlugin<'a> { 23 | fn apply(&self, resolver: &Resolver, info: Info, context: &mut Context) -> State { 24 | let request = info.request(); 25 | let target = request.target(); 26 | 27 | for field in &resolver.options.exports_field { 28 | let root = match self.pkg_info.data().get_filed(field) { 29 | Some(exports_tree) => exports_tree, 30 | None => continue, 31 | }; 32 | 33 | if request.is_directory() { 34 | return State::Error(Error::UnexpectedValue(format!( 35 | "Resolving to directories is not possible with the exports field (request was {}/ in {})", 36 | target, info.normalized_path().as_ref().display() 37 | ))); 38 | } 39 | 40 | let query = request.query(); 41 | let fragment = request.fragment(); 42 | let request_path = get_path_from_request(target); 43 | 44 | let normalized_target = match request_path { 45 | Some(target) => format!(".{target}"), 46 | None => { 47 | let path = info.normalized_path().as_ref().join(target); 48 | if resolver.load_entry(&path).exists() 49 | || self 50 | .pkg_info 51 | .data() 52 | .name() 53 | .map_or(false, |name| target == name) 54 | { 55 | ".".to_string() 56 | } else { 57 | return State::Failed(info); 58 | } 59 | } 60 | }; 61 | 62 | let remaining_target = if !query.is_empty() || !fragment.is_empty() { 63 | let normalized_target = if normalized_target == "." { 64 | String::from("./") 65 | } else { 66 | normalized_target 67 | }; 68 | format!("{normalized_target}{query}{fragment}") 69 | } else { 70 | normalized_target 71 | }; 72 | 73 | let list = match ExportsField::field_process( 74 | root, 75 | &remaining_target, 76 | &resolver.options.condition_names, 77 | ) { 78 | Ok(list) => list, 79 | Err(err) => return State::Error(err), 80 | }; 81 | 82 | if list.is_empty() { 83 | return State::Error(Error::UnexpectedValue(format!( 84 | "Package path {target} is not exported in {}/package.json", 85 | self.pkg_info.dir().as_ref().display() 86 | ))); 87 | } 88 | 89 | for item in list { 90 | tracing::debug!( 91 | "ExportsField in '{}' works, trigger by '{}', mapped to '{}'({})", 92 | color::blue(&format!( 93 | "{}/package.json", 94 | self.pkg_info.dir().as_ref().display() 95 | )), 96 | color::blue(&target), 97 | color::blue(&item), 98 | depth(&context.depth) 99 | ); 100 | if !item.starts_with("./") { 101 | return State::Error(Error::UnexpectedValue(format!( 102 | "Invalid \"{item}\" defined in {}/package.json, target must start with \"./\"", 103 | self.pkg_info.dir().as_ref().display() 104 | ))); 105 | } 106 | let request = Resolver::parse(&item); 107 | let info = Info::from(self.pkg_info.dir().clone()).with_request(request); 108 | if let Err(msg) = ExportsField::check_target(info.request().target()) { 109 | let msg = format!("{msg} in {:?}/package.json", &self.pkg_info.dir()); 110 | return State::Error(Error::UnexpectedValue(msg)); 111 | } 112 | let state = resolver._resolve(info, context); 113 | if state.is_finished() { 114 | return state; 115 | } 116 | } 117 | 118 | return State::Failed(info); 119 | } 120 | 121 | State::Resolving(info) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/tsconfig.rs: -------------------------------------------------------------------------------- 1 | // copy from https://github.com/drivasperez/tsconfig 2 | 3 | use crate::context::Context; 4 | use crate::{Error, Info, RResult, ResolveResult, Resolver, State}; 5 | use rustc_hash::FxHashMap; 6 | use std::{path::Path, sync::Arc}; 7 | 8 | #[derive(Debug, Clone, Default)] 9 | pub struct TsConfig { 10 | pub extends: Option, 11 | pub compiler_options: Option, 12 | } 13 | 14 | #[derive(Debug, Clone)] 15 | pub struct CompilerOptions { 16 | pub base_url: Option, 17 | pub paths: Option>>, 18 | } 19 | 20 | impl TsConfig { 21 | pub fn parse(json_str: &str, location: &Path) -> RResult { 22 | let serde_value = jsonc_parser::parse_to_serde_value(json_str, &Default::default()) 23 | .map_err(|err| { 24 | Error::UnexpectedValue(format!("Parse {} failed. Error: {err}", location.display())) 25 | })? 26 | .unwrap_or_else(|| panic!("Transfer {} to serde value failed", location.display())); 27 | Ok(serde_value) 28 | } 29 | } 30 | 31 | impl Resolver { 32 | pub(super) fn parse_ts_file( 33 | &self, 34 | location: &Path, 35 | context: &mut Context, 36 | ) -> RResult { 37 | let json = self.parse_file_to_value(location, context)?; 38 | let compiler_options = json.get("compilerOptions").map(|options| { 39 | // TODO: should optimized 40 | let base_url = options 41 | .get("baseUrl") 42 | .map(|v| v.as_str().unwrap().to_string()); 43 | let paths = options.get("paths").map(|v| { 44 | let mut map = FxHashMap::default(); 45 | // TODO: should optimized 46 | for (key, obj) in v.as_object().unwrap() { 47 | map.insert( 48 | key.to_string(), 49 | obj.as_array() 50 | .unwrap() 51 | .iter() 52 | .map(|v| v.as_str().unwrap().to_string()) 53 | .collect(), 54 | ); 55 | } 56 | map 57 | }); 58 | CompilerOptions { base_url, paths } 59 | }); 60 | let extends: Option = json.get("extends").map(|v| v.to_string()); 61 | Ok(TsConfig { 62 | extends, 63 | compiler_options, 64 | }) 65 | } 66 | 67 | fn parse_file_to_value( 68 | &self, 69 | location: &Path, 70 | context: &mut Context, 71 | ) -> RResult { 72 | let entry = self.load_entry(location); 73 | if !entry.is_file() { 74 | // Its role is to ensure that `stat` exists 75 | return Err(Error::CantFindTsConfig(entry.path().into())); 76 | } 77 | 78 | let value = self.cache.fs.read_tsconfig(location, entry.cached_stat())?; 79 | let mut json = Arc::as_ref(&value).clone(); 80 | 81 | // merge `extends`. 82 | if let serde_json::Value::String(s) = &json["extends"] { 83 | // `location` pointed to `dir/tsconfig.json` 84 | let dir = location.parent().unwrap().to_path_buf(); 85 | let request = Self::parse(s); 86 | let prev_resolve_to_context = context.resolve_to_context.get(); 87 | if prev_resolve_to_context { 88 | context.resolve_to_context.set(false); 89 | } 90 | let state = self._resolve(Info::new(dir, request), context); 91 | if prev_resolve_to_context { 92 | context.resolve_to_context.set(true); 93 | } 94 | // Is it better to use cache? 95 | if let State::Success(result) = state { 96 | let extends_tsconfig_json = match result { 97 | ResolveResult::Resource(info) => { 98 | self.parse_file_to_value(&info.to_resolved_path(), context) 99 | } 100 | ResolveResult::Ignored => { 101 | return Err(Error::UnexpectedValue(format!( 102 | "{s} had been ignored in {}", 103 | location.display() 104 | ))) 105 | } 106 | }?; 107 | merge(&mut json, extends_tsconfig_json); 108 | } 109 | } 110 | Ok(json) 111 | } 112 | } 113 | 114 | fn merge(a: &mut serde_json::Value, b: serde_json::Value) { 115 | match (a, b) { 116 | (&mut serde_json::Value::Object(ref mut a), serde_json::Value::Object(b)) => { 117 | for (k, v) in b { 118 | merge(a.entry(k).or_insert(serde_json::Value::Null), v); 119 | } 120 | } 121 | (a, b) => { 122 | if let serde_json::Value::Null = a { 123 | *a = b; 124 | } 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/options.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashSet, path::PathBuf, sync::Arc}; 2 | 3 | use crate::Cache; 4 | 5 | #[derive(Debug, Clone, Hash, PartialEq, Eq)] 6 | pub enum AliasMap { 7 | Target(String), 8 | Ignored, 9 | } 10 | 11 | #[derive(Debug, Clone, Hash, PartialEq, Eq)] 12 | pub enum EnforceExtension { 13 | Enabled, 14 | Disabled, 15 | Auto, 16 | } 17 | 18 | pub type Alias = Vec<(String, Vec)>; 19 | 20 | #[derive(Debug, Clone)] 21 | pub struct Options { 22 | /// Tried detect file with this extension. 23 | /// Default is `[".js", ".json", ".node"]` 24 | pub extensions: Vec, 25 | /// Enforce that a extension from extensions must be used. 26 | /// Default is `Auto`. 27 | pub enforce_extension: EnforceExtension, 28 | /// Maps key to value. 29 | /// Default is `vec![]`. 30 | /// The reason for using `Vec` instead `HashMap` to keep the order. 31 | pub alias: Alias, 32 | /// Prefer to resolve request as relative request and 33 | /// fallback to resolving as modules. 34 | /// Default is `false` 35 | pub prefer_relative: bool, 36 | /// Use of cache defined external, it designed to shared the info of `description_file` 37 | /// in different resolver. 38 | /// 39 | /// - If `external_cache` is `None`, use default cache in resolver. 40 | /// - If `external_cache.is_some()` is true, use this cache. 41 | /// 42 | /// Default is `None`. 43 | pub external_cache: Option>, 44 | /// Whether to resolve the real path when the result 45 | /// is a symlink. 46 | /// Default is `true`. 47 | pub symlinks: bool, 48 | /// A JSON file to describing this lib information. 49 | /// Default is `"package.json"`. 50 | pub description_file: String, 51 | /// Resolve to a context instead of a file. 52 | /// Default is `false` 53 | pub resolve_to_context: bool, 54 | /// Main file in this directory. 55 | /// Default is `["index"]`. 56 | pub main_files: Vec, 57 | /// Main fields in Description. 58 | /// Default is `["main"]`. 59 | pub main_fields: Vec, 60 | /// Whether read and parse `"browser"` filed 61 | /// in package.json. 62 | /// Default is `false` 63 | pub browser_field: bool, 64 | /// Condition names for exports filed. Note that its type is a `HashSet`, 65 | /// because the priority is related to the order in which the export field 66 | /// fields are written. 67 | /// Default is `[]`. 68 | pub condition_names: HashSet, 69 | /// When this filed exists, it tries to read `baseURL` 70 | /// and `paths` in the corresponding tsconfig, 71 | /// and processes the mappings. 72 | /// Default is `None`. 73 | pub tsconfig: Option, 74 | /// A list of directories to resolve modules from, can be absolute path or folder name. 75 | /// Default is `["node_modules"]` 76 | pub modules: Vec, 77 | /// Same as `alias`, but only used if default resolving fails. 78 | /// Default is `[]`. 79 | pub fallback: Alias, 80 | /// Request passed to resolve is already fully specified and 81 | /// extensions or main files are not resolved for it. 82 | /// Default is `false`. 83 | pub fully_specified: bool, 84 | /// A list of exports fields in descriptions files 85 | /// Default is `[["exports"]]`. 86 | pub exports_field: Vec>, 87 | /// A vector which maps extension to extension aliases. 88 | /// Default is `[]`. 89 | pub extension_alias: Vec<(String, Vec)>, 90 | } 91 | 92 | impl Default for Options { 93 | fn default() -> Self { 94 | let extensions = vec![ 95 | String::from(".js"), 96 | String::from(".json"), 97 | String::from(".node"), 98 | ]; 99 | let main_files = vec![String::from("index")]; 100 | let main_fields = vec![String::from("main")]; 101 | let description_file = String::from("package.json"); 102 | let alias = vec![]; 103 | let symlinks = true; 104 | let browser_field = false; 105 | let condition_names = HashSet::default(); 106 | let prefer_relative = false; 107 | let enforce_extension = EnforceExtension::Auto; 108 | let tsconfig = None; 109 | let external_cache = None; 110 | let resolve_to_context = false; 111 | let modules = vec![String::from("node_modules")]; 112 | let fallback = vec![]; 113 | let fully_specified = false; 114 | let exports_field = vec![vec![String::from("exports")]]; 115 | let extension_alias = vec![]; 116 | Self { 117 | fallback, 118 | modules, 119 | extensions, 120 | enforce_extension, 121 | alias, 122 | prefer_relative, 123 | external_cache, 124 | symlinks, 125 | description_file, 126 | resolve_to_context, 127 | main_files, 128 | main_fields, 129 | browser_field, 130 | condition_names, 131 | tsconfig, 132 | fully_specified, 133 | exports_field, 134 | extension_alias, 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/parse.rs: -------------------------------------------------------------------------------- 1 | use crate::kind::PathKind; 2 | use crate::Resolver; 3 | 4 | #[derive(Clone, Debug)] 5 | pub struct Request { 6 | target: Box, 7 | query: Option>, 8 | fragment: Option>, 9 | kind: PathKind, 10 | is_directory: bool, 11 | } 12 | 13 | impl Default for Request { 14 | fn default() -> Self { 15 | Self { 16 | target: "".into(), 17 | query: None, 18 | fragment: None, 19 | kind: PathKind::Relative, 20 | is_directory: false, 21 | } 22 | } 23 | } 24 | 25 | impl std::fmt::Display for Request { 26 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 27 | write!(f, "{}{}{}", self.target(), self.query(), self.fragment()) 28 | } 29 | } 30 | 31 | impl Request { 32 | #[must_use] 33 | pub fn from_request(request: &str) -> Self { 34 | let (target, query, fragment) = Self::parse_identifier(request); 35 | let is_directory = Self::is_target_directory(&target); 36 | let target = if is_directory { 37 | target[0..target.len() - 1].into() 38 | } else { 39 | target 40 | }; 41 | Request { 42 | kind: Resolver::get_target_kind(&target), 43 | target, 44 | query, 45 | fragment, 46 | is_directory, 47 | } 48 | } 49 | 50 | pub fn target(&self) -> &str { 51 | &self.target 52 | } 53 | 54 | pub fn query(&self) -> &str { 55 | self.query.as_ref().map_or("", |query| query.as_ref()) 56 | } 57 | 58 | pub fn fragment(&self) -> &str { 59 | self.fragment 60 | .as_ref() 61 | .map_or("", |fragment| fragment.as_ref()) 62 | } 63 | 64 | pub fn kind(&self) -> PathKind { 65 | self.kind 66 | } 67 | 68 | pub fn is_directory(&self) -> bool { 69 | self.is_directory 70 | } 71 | 72 | pub fn with_target(self, target: &str) -> Self { 73 | let is_directory = Self::is_target_directory(target); 74 | Self { 75 | kind: Resolver::get_target_kind(target), 76 | target: target.into(), 77 | is_directory, 78 | ..self 79 | } 80 | } 81 | 82 | pub fn with_query(self, query: &str) -> Self { 83 | Self { 84 | query: (!query.is_empty()).then(|| query.into()), 85 | ..self 86 | } 87 | } 88 | 89 | pub fn with_fragment(self, fragment: &str) -> Self { 90 | Self { 91 | fragment: (!fragment.is_empty()).then(|| fragment.into()), 92 | ..self 93 | } 94 | } 95 | 96 | fn parse_identifier(ident: &str) -> (Box, Option>, Option>) { 97 | let mut query: Option = None; 98 | let mut fragment: Option = None; 99 | let mut stats = ParseStats::Start; 100 | for (index, c) in ident.as_bytes().iter().enumerate() { 101 | match c { 102 | b'#' => match stats { 103 | ParseStats::Request | ParseStats::Query => { 104 | stats = ParseStats::Fragment; 105 | fragment = Some(index); 106 | } 107 | ParseStats::Start => { 108 | stats = ParseStats::Request; 109 | } 110 | ParseStats::Fragment => (), 111 | }, 112 | b'?' => match stats { 113 | ParseStats::Request | ParseStats::Query | ParseStats::Start => { 114 | stats = ParseStats::Query; 115 | query = Some(index); 116 | } 117 | ParseStats::Fragment => (), 118 | }, 119 | _ => { 120 | if let ParseStats::Start = stats { 121 | stats = ParseStats::Request; 122 | } 123 | } 124 | } 125 | } 126 | 127 | match (query, fragment) { 128 | (None, None) => (ident.into(), None, None), 129 | (None, Some(j)) => (ident[0..j].into(), None, Some(ident[j..].into())), 130 | (Some(i), None) => (ident[0..i].into(), Some(ident[i..].into()), None), 131 | (Some(i), Some(j)) => ( 132 | ident[0..i].into(), 133 | Some(ident[i..j].into()), 134 | Some(ident[j..].into()), 135 | ), 136 | } 137 | } 138 | 139 | #[inline] 140 | fn is_target_directory(target: &str) -> bool { 141 | target.ends_with('/') 142 | } 143 | } 144 | 145 | impl Resolver { 146 | #[must_use] 147 | pub(crate) fn parse(request: &str) -> Request { 148 | Request::from_request(request) 149 | } 150 | } 151 | 152 | enum ParseStats { 153 | Request, 154 | Query, 155 | Fragment, 156 | Start, 157 | } 158 | 159 | #[test] 160 | fn parse_identifier_test() { 161 | fn should_parsed(input: &str, t: &str, q: &str, f: &str) { 162 | let (target, query, fragment) = Request::parse_identifier(input); 163 | assert_eq!(&*target, t); 164 | assert_eq!(query.as_ref().map_or("", |q| q.as_ref()), q); 165 | assert_eq!(fragment.as_ref().map_or("", |f| f.as_ref()), f); 166 | } 167 | 168 | should_parsed("path/abc", "path/abc", "", ""); 169 | should_parsed("path/#", "path/", "", "#"); 170 | should_parsed("path/as/?", "path/as/", "?", ""); 171 | should_parsed("path/#/?", "path/", "", "#/?"); 172 | should_parsed("path/#repo#hash", "path/", "", "#repo#hash"); 173 | should_parsed("path/#r#hash", "path/", "", "#r#hash"); 174 | should_parsed("path/#repo/#repo2#hash", "path/", "", "#repo/#repo2#hash"); 175 | should_parsed("path/#r/#r#hash", "path/", "", "#r/#r#hash"); 176 | should_parsed( 177 | "path/#/not/a/hash?not-a-query", 178 | "path/", 179 | "", 180 | "#/not/a/hash?not-a-query", 181 | ); 182 | should_parsed("#a?b#c?d", "#a", "?b", "#c?d"); 183 | 184 | // windows like 185 | should_parsed("path\\#", "path\\", "", "#"); 186 | should_parsed("C:path\\as\\?", "C:path\\as\\", "?", ""); 187 | should_parsed("path\\#\\?", "path\\", "", "#\\?"); 188 | should_parsed("path\\#repo#hash", "path\\", "", "#repo#hash"); 189 | should_parsed("path\\#r#hash", "path\\", "", "#r#hash"); 190 | should_parsed( 191 | "path\\#/not/a/hash?not-a-query", 192 | "path\\", 193 | "", 194 | "#/not/a/hash?not-a-query", 195 | ); 196 | } 197 | -------------------------------------------------------------------------------- /src/entry.rs: -------------------------------------------------------------------------------- 1 | use once_cell::sync::OnceCell; 2 | use std::{ 3 | borrow::Cow, 4 | fs::FileType, 5 | path::{Path, PathBuf}, 6 | sync::Arc, 7 | time::SystemTime, 8 | }; 9 | 10 | use crate::{description::DescriptionData, Error, RResult, Resolver}; 11 | 12 | #[derive(Debug, Default, Clone, Copy)] 13 | pub struct EntryStat { 14 | /// `None` for non-existing file 15 | file_type: Option, 16 | 17 | /// `None` for existing file but without system time. 18 | modified: Option, 19 | } 20 | 21 | impl EntryStat { 22 | fn new(file_type: Option, modified: Option) -> Self { 23 | Self { 24 | file_type, 25 | modified, 26 | } 27 | } 28 | 29 | /// Returns `None` for non-existing file 30 | pub fn file_type(&self) -> Option { 31 | self.file_type 32 | } 33 | 34 | /// Returns `None` for existing file but without system time. 35 | pub fn modified(&self) -> Option { 36 | self.modified 37 | } 38 | 39 | fn stat(path: &Path) -> Self { 40 | if let Ok(meta) = path.metadata() { 41 | // This field might not be available on all platforms, 42 | // and will return an Err on platforms where it is not available. 43 | let modified = meta.modified().ok(); 44 | Self::new(Some(meta.file_type()), modified) 45 | } else { 46 | Self::new(None, None) 47 | } 48 | } 49 | } 50 | 51 | #[derive(Debug)] 52 | pub struct Entry { 53 | parent: Option>, 54 | path: Box, 55 | // None: package.json does not exist 56 | pkg_info: OnceCell>>, 57 | stat: OnceCell, 58 | /// None represent the `self.path` is not a symlink 59 | symlink: OnceCell>>, 60 | /// If `self.path` is a symlink, then return canonicalized path, 61 | /// else return `self.path` 62 | real: OnceCell>, 63 | } 64 | 65 | impl Entry { 66 | pub fn path(&self) -> &Path { 67 | &self.path 68 | } 69 | 70 | pub fn parent(&self) -> Option<&Arc> { 71 | self.parent.as_ref() 72 | } 73 | 74 | pub fn pkg_info(&self, resolver: &Resolver) -> RResult<&Option>> { 75 | self.pkg_info.get_or_try_init(|| { 76 | let pkg_name = &resolver.options.description_file; 77 | let path = self.path(); 78 | let is_pkg_suffix = path.ends_with(pkg_name); 79 | if self.is_dir() || is_pkg_suffix { 80 | let pkg_path = if is_pkg_suffix { 81 | Cow::Borrowed(path) 82 | } else { 83 | Cow::Owned(path.join(pkg_name)) 84 | }; 85 | match resolver 86 | .cache 87 | .fs 88 | .read_description_file(&pkg_path, EntryStat::default()) 89 | { 90 | Ok(info) => { 91 | return Ok(Some(info)); 92 | } 93 | Err(error @ (Error::UnexpectedJson(_) | Error::UnexpectedValue(_))) => { 94 | // Return bad json 95 | return Err(error); 96 | } 97 | Err(Error::Io(_)) => { 98 | // package.json not found 99 | } 100 | _ => unreachable!(), 101 | }; 102 | } 103 | if let Some(parent) = &self.parent() { 104 | return parent.pkg_info(resolver).cloned(); 105 | } 106 | Ok(None) 107 | }) 108 | } 109 | 110 | pub fn is_file(&self) -> bool { 111 | self.cached_stat() 112 | .file_type() 113 | .map_or(false, |ft| ft.is_file()) 114 | } 115 | 116 | pub fn is_dir(&self) -> bool { 117 | self.cached_stat() 118 | .file_type() 119 | .map_or(false, |ft| ft.is_dir()) 120 | } 121 | 122 | pub fn exists(&self) -> bool { 123 | self.cached_stat().file_type().is_some() 124 | } 125 | 126 | pub fn cached_stat(&self) -> EntryStat { 127 | *self.stat.get_or_init(|| EntryStat::stat(&self.path)) 128 | } 129 | 130 | pub fn real(&self) -> Option<&Path> { 131 | self.real.get().map(|p| &**p) 132 | } 133 | 134 | pub fn init_real(&self, path: Box) { 135 | self.real.get_or_init(|| path); 136 | } 137 | 138 | /// Returns the canonicalized path of `self.path` if it is a symlink. 139 | /// Returns None if `self.path` is not a symlink. 140 | pub fn symlink(&self) -> &Option> { 141 | self.symlink.get_or_init(|| { 142 | debug_assert!(self.path.is_absolute()); 143 | if self.path.read_link().is_err() { 144 | return None; 145 | } 146 | match dunce::canonicalize(&self.path) { 147 | Ok(symlink_path) => Some(Box::from(symlink_path)), 148 | Err(_) => None, 149 | } 150 | }) 151 | } 152 | } 153 | 154 | impl Resolver { 155 | pub(super) fn load_entry(&self, path: &Path) -> Arc { 156 | if let Some(cached) = self.cache.entries.get(path) { 157 | cached.clone() 158 | } else { 159 | let entry = Arc::new(self.load_entry_uncached(path)); 160 | self.cache 161 | .entries 162 | .entry(path.into()) 163 | .or_insert(entry.clone()); 164 | entry 165 | } 166 | } 167 | 168 | fn load_entry_uncached(&self, path: &Path) -> Entry { 169 | let parent = if let Some(parent) = path.parent() { 170 | let entry = self.load_entry(parent); 171 | Some(entry) 172 | } else { 173 | None 174 | }; 175 | Entry { 176 | parent, 177 | path: path.into(), 178 | pkg_info: OnceCell::default(), 179 | stat: OnceCell::default(), 180 | symlink: OnceCell::default(), 181 | real: OnceCell::default(), 182 | } 183 | } 184 | 185 | // TODO: should put entries as a parament. 186 | pub fn clear_entries(&self) { 187 | self.cache.entries.clear(); 188 | } 189 | 190 | #[must_use] 191 | pub fn get_dependency_from_entry(&self) -> (Vec, Vec) { 192 | todo!("get_dependency_from_entry") 193 | } 194 | } 195 | 196 | #[test] 197 | #[ignore] 198 | fn dependency_test() { 199 | let case_path = super::test_helper::p(vec!["full", "a"]); 200 | let request = "package2"; 201 | let resolver = Resolver::new(Default::default()); 202 | resolver.resolve(&case_path, request).unwrap(); 203 | let (file, missing) = resolver.get_dependency_from_entry(); 204 | assert_eq!(file.len(), 3); 205 | assert_eq!(missing.len(), 1); 206 | } 207 | -------------------------------------------------------------------------------- /src/tsconfig_path.rs: -------------------------------------------------------------------------------- 1 | // Copy from https://github.com/dividab/tsconfig-paths 2 | 3 | use crate::{context::Context, Info, RResult, Resolver, State}; 4 | use rustc_hash::FxHashMap; 5 | use std::path::{Path, PathBuf}; 6 | 7 | #[derive(Default, Debug)] 8 | pub struct TsConfigInfo { 9 | pub paths: Option>>, 10 | pub base_url: Option, 11 | } 12 | 13 | #[derive(Debug, PartialEq)] 14 | struct MappingEntry { 15 | pub(crate) pattern: String, 16 | /// The item in `paths` maybe contains '*' tag 17 | pub(crate) paths: Vec, 18 | } 19 | 20 | impl Resolver { 21 | fn get_absolute_mapping_entries( 22 | absolute_base_url: &Path, 23 | paths: &FxHashMap>, 24 | ) -> Vec { 25 | paths 26 | .iter() 27 | .map(|(key, paths)| { 28 | let pattern = key.to_string(); 29 | let paths = paths 30 | .iter() 31 | .map(|path| absolute_base_url.join(path)) 32 | .collect(); 33 | MappingEntry { pattern, paths } 34 | }) 35 | .collect() 36 | } 37 | 38 | fn parse_tsconfig(&self, location: &Path, context: &mut Context) -> RResult { 39 | let tsconfig = self.parse_ts_file(location, context)?; 40 | let base_url = tsconfig 41 | .compiler_options 42 | .as_ref() 43 | .and_then(|options| options.base_url.clone()); 44 | let paths = tsconfig.compiler_options.and_then(|options| options.paths); 45 | Ok(TsConfigInfo { paths, base_url }) 46 | } 47 | 48 | fn match_star<'a>(pattern: &'a str, search: &'a str) -> Option<&'a str> { 49 | if search.len() < pattern.len() { 50 | return None; 51 | } 52 | if pattern == "*" { 53 | return Some(search); 54 | } 55 | let p = pattern.as_bytes(); 56 | let s = search.as_bytes(); 57 | p.iter() 58 | .enumerate() 59 | .find(|&(_, &c)| c == b'*') 60 | .and_then(|(star_index, _)| { 61 | let part1 = &p[..star_index]; 62 | if &s[0..star_index] != part1 { 63 | return None; 64 | } 65 | let part2 = &p[star_index + 1..]; 66 | if &s[s.len() - part2.len()..] != part2 { 67 | return None; 68 | } 69 | let len = s.len() - part2.len() - part1.len(); 70 | match std::str::from_utf8(&s[star_index..star_index + len]) { 71 | Ok(result) => Some(result), 72 | Err(error) => { 73 | panic!( 74 | "There has unexpected error when matching {pattern} and {search}, error: {error}" 75 | ) 76 | } 77 | } 78 | }) 79 | } 80 | 81 | fn create_match_list( 82 | absolute_base_url: &Path, 83 | paths: &Option>>, 84 | ) -> Vec { 85 | paths 86 | .as_ref() 87 | .map(|paths| Self::get_absolute_mapping_entries(absolute_base_url, paths)) 88 | .unwrap_or_default() 89 | } 90 | 91 | pub(super) fn _resolve_with_tsconfig( 92 | &self, 93 | info: Info, 94 | location: &Path, 95 | context: &mut Context, 96 | ) -> State { 97 | let tsconfig = match self.parse_tsconfig(location, context) { 98 | Ok(tsconfig) => tsconfig, 99 | Err(error) => return State::Error(error), 100 | }; 101 | let location_dir = location.parent().unwrap(); 102 | let absolute_base_url = if let Some(base_url) = tsconfig.base_url.as_ref() { 103 | location_dir.join(base_url) 104 | } else { 105 | location_dir.into() 106 | }; 107 | 108 | // resolve absolute path that relative from base_url 109 | if tsconfig.base_url.is_some() && !info.request().target().starts_with('.') { 110 | let target = absolute_base_url.join(info.request().target()); 111 | let info = info.clone().with_path(target).with_target(""); 112 | let result = self._resolve(info, context); 113 | if result.is_finished() { 114 | return result; 115 | } 116 | } 117 | 118 | let absolute_path_mappings = 119 | Resolver::create_match_list(&absolute_base_url, &tsconfig.paths); 120 | 121 | for entry in absolute_path_mappings { 122 | let star_match = if entry.pattern == info.request().target() { 123 | "" 124 | } else if let Some(s) = Self::match_star(&entry.pattern, info.request().target()) { 125 | s 126 | } else { 127 | continue; 128 | }; 129 | 130 | for physical_path_pattern in &entry.paths { 131 | let physical_path = &physical_path_pattern 132 | .display() 133 | .to_string() 134 | .replace('*', star_match); 135 | let info = info.clone().with_path(physical_path).with_target(""); 136 | let result = self._resolve(info, context); 137 | if result.is_finished() { 138 | return result; 139 | } 140 | } 141 | } 142 | self._resolve(info, context) 143 | } 144 | } 145 | 146 | #[test] 147 | fn test_get_absolute_mapping_entries() { 148 | let result = Resolver::get_absolute_mapping_entries( 149 | Path::new("/absolute/base/url"), 150 | &FxHashMap::from_iter(vec![ 151 | ( 152 | "*".to_string(), 153 | (vec!["/foo1", "./foo2"]) 154 | .into_iter() 155 | .map(String::from) 156 | .collect(), 157 | ), 158 | ( 159 | "longest/pre/fix/*".to_string(), 160 | vec!["./foo2/bar".to_string()], 161 | ), 162 | ("pre/fix/*".to_string(), vec!["/foo3".to_string()]), 163 | ]), 164 | ); 165 | assert!(result.len() == 3); 166 | assert!(result.contains(&MappingEntry { 167 | pattern: "longest/pre/fix/*".to_string(), 168 | paths: vec![PathBuf::from("/absolute/base/url/foo2/bar")], 169 | })); 170 | assert!(result.contains(&MappingEntry { 171 | pattern: "pre/fix/*".to_string(), 172 | paths: vec![PathBuf::from("/foo3")], 173 | },)); 174 | assert!(result.contains(&MappingEntry { 175 | pattern: "*".to_string(), 176 | paths: vec![ 177 | PathBuf::from("/foo1"), 178 | PathBuf::from("/absolute/base/url/foo2") 179 | ], 180 | })); 181 | 182 | let result = Resolver::get_absolute_mapping_entries( 183 | Path::new("/absolute/base/url"), 184 | &FxHashMap::from_iter([]), 185 | ); 186 | assert!(result.is_empty()); 187 | } 188 | 189 | #[test] 190 | fn test_match_star() { 191 | // should not panic 192 | assert_eq!(Resolver::match_star("abc/*", "./中文"), None) 193 | } 194 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # `nodejs_resolver` 2 | //! 3 | //! ## How to use? 4 | //! 5 | //! ```rust 6 | //! // |-- node_modules 7 | //! // |---- foo 8 | //! // |------ index.js 9 | //! // | src 10 | //! // |-- foo.ts 11 | //! // |-- foo.js 12 | //! // | tests 13 | //! 14 | //! use nodejs_resolver::Resolver; 15 | //! 16 | //! let cwd = std::env::current_dir().unwrap(); 17 | //! let resolver = Resolver::new(Default::default()); 18 | //! 19 | //! resolver.resolve(&cwd.join("./src"), "foo"); 20 | //! // -> ResolveResult::Info(ResolveInfo { 21 | //! // path: PathBuf::from("/node_modules/foo/index.js") 22 | //! // request: Request { 23 | //! // target: "", 24 | //! // fragment: "", 25 | //! // query: "" 26 | //! // } 27 | //! // }) 28 | //! // 29 | //! 30 | //! resolver.resolve(&cwd.join("./src"), "./foo"); 31 | //! // -> ResolveResult::Info(ResolveInfo { 32 | //! // path: PathBuf::from("/src/foo.js") 33 | //! // request: Request { 34 | //! // target: "", 35 | //! // fragment: "", 36 | //! // query: "" 37 | //! // } 38 | //! // }) 39 | //! // 40 | //! ``` 41 | //! 42 | 43 | mod cache; 44 | mod context; 45 | mod description; 46 | mod entry; 47 | mod error; 48 | mod fs; 49 | mod info; 50 | mod kind; 51 | mod log; 52 | mod map; 53 | mod options; 54 | mod parse; 55 | mod plugin; 56 | mod resolve; 57 | mod resource; 58 | mod state; 59 | mod tsconfig; 60 | mod tsconfig_path; 61 | 62 | pub use cache::Cache; 63 | use context::Context; 64 | pub use description::DescriptionData; 65 | pub use error::Error; 66 | use info::Info; 67 | use kind::PathKind; 68 | use log::{color, depth}; 69 | use options::EnforceExtension::{Auto, Disabled, Enabled}; 70 | pub use options::{AliasMap, EnforceExtension, Options}; 71 | use plugin::{ 72 | AliasPlugin, BrowserFieldPlugin, ImportsFieldPlugin, ParsePlugin, Plugin, PreferRelativePlugin, 73 | SymlinkPlugin, 74 | }; 75 | pub use resource::Resource; 76 | use state::State; 77 | 78 | #[derive(Debug)] 79 | pub struct Resolver { 80 | pub options: Options, 81 | pub(crate) cache: std::sync::Arc, 82 | } 83 | 84 | #[derive(Debug, Clone)] 85 | pub enum ResolveResult { 86 | Resource(T), 87 | Ignored, 88 | } 89 | 90 | pub type RResult = Result; 91 | 92 | impl Resolver { 93 | #[must_use] 94 | pub fn new(options: Options) -> Self { 95 | log::enable_by_env(); 96 | 97 | let cache = if let Some(external_cache) = options.external_cache.as_ref() { 98 | external_cache.clone() 99 | } else { 100 | std::sync::Arc::new(Cache::default()) 101 | }; 102 | 103 | let enforce_extension = match options.enforce_extension { 104 | Auto => { 105 | if options.extensions.iter().any(|ext| ext.is_empty()) { 106 | Enabled 107 | } else { 108 | Disabled 109 | } 110 | } 111 | _ => options.enforce_extension, 112 | }; 113 | 114 | let options = Options { 115 | enforce_extension, 116 | ..options 117 | }; 118 | Self { options, cache } 119 | } 120 | 121 | pub fn resolve( 122 | &self, 123 | path: &std::path::Path, 124 | request: &str, 125 | ) -> RResult> { 126 | tracing::debug!( 127 | "{:-^30}\nTry to resolve '{}' in '{}'", 128 | color::green(&"[RESOLVER]"), 129 | color::cyan(&request), 130 | color::cyan(&path.display().to_string()) 131 | ); 132 | // let start = std::time::Instant::now(); 133 | let parsed = Self::parse(request); 134 | let info = Info::new(path, parsed); 135 | let mut context = Context::new( 136 | self.options.fully_specified, 137 | self.options.resolve_to_context, 138 | ); 139 | let result = if let Some(tsconfig_location) = self.options.tsconfig.as_ref() { 140 | self._resolve_with_tsconfig(info, tsconfig_location, &mut context) 141 | } else { 142 | self._resolve(info, &mut context) 143 | }; 144 | 145 | let result = result.map_failed(|info| { 146 | type FallbackPlugin<'a> = AliasPlugin<'a>; 147 | FallbackPlugin::new(&self.options.fallback).apply(self, info, &mut context) 148 | }); 149 | let result = result.map_success(|info| SymlinkPlugin::apply(self, info, &mut context)); 150 | 151 | // let duration = start.elapsed().as_millis(); 152 | // println!("time cost: {:?} us", duration); // us 153 | // if duration > 10 { 154 | // println!( 155 | // "{:?}ms, path: {:?}, request: {:?}", 156 | // duration, 157 | // path.display(), 158 | // request, 159 | // ); 160 | // } 161 | 162 | match result { 163 | State::Success(ResolveResult::Ignored) => Ok(ResolveResult::Ignored), 164 | State::Success(ResolveResult::Resource(info)) => { 165 | let resource = Resource::new(info, self); 166 | Ok(ResolveResult::Resource(resource)) 167 | } 168 | State::Error(err) => Err(err), 169 | State::Resolving(_) | State::Failed(_) => Err(Error::ResolveFailedTag), 170 | } 171 | } 172 | 173 | fn _resolve(&self, info: Info, context: &mut Context) -> State { 174 | tracing::debug!( 175 | "Resolving '{request}' in '{path}'", 176 | request = color::cyan(&info.request().target()), 177 | path = color::cyan(&info.normalized_path().as_ref().display()) 178 | ); 179 | 180 | context.depth.increase(); 181 | if context.depth.cmp(127).is_ge() { 182 | return State::Error(Error::Overflow); 183 | } 184 | 185 | let state = ParsePlugin::apply(self, info, context) 186 | .then(|info| AliasPlugin::new(&self.options.alias).apply(self, info, context)) 187 | .then(|info| PreferRelativePlugin::apply(self, info, context)) 188 | .then(|info| { 189 | let request = info.to_resolved_path(); 190 | let entry = self.load_entry(&request); 191 | let pkg_info = match entry.pkg_info(self) { 192 | Ok(pkg_info) => pkg_info, 193 | Err(error) => return State::Error(error), 194 | }; 195 | if let Some(pkg_info) = pkg_info { 196 | ImportsFieldPlugin::new(pkg_info) 197 | .apply(self, info, context) 198 | .then(|info| { 199 | BrowserFieldPlugin::new(pkg_info, false).apply(self, info, context) 200 | }) 201 | } else { 202 | State::Resolving(info) 203 | } 204 | }) 205 | .then(|info| { 206 | if matches!( 207 | info.request().kind(), 208 | PathKind::AbsolutePosix | PathKind::AbsoluteWin | PathKind::Relative 209 | ) { 210 | self.resolve_as_context(info, context) 211 | .then(|info| self.resolve_as_fully_specified(info, context)) 212 | .then(|info| self.resolve_as_file(info, context)) 213 | .then(|info| self.resolve_as_dir(info, context)) 214 | } else { 215 | self.resolve_as_modules(info, context) 216 | } 217 | }); 218 | 219 | context.depth.decrease(); 220 | state 221 | } 222 | } 223 | 224 | #[cfg(debug_assertions)] 225 | pub mod test_helper { 226 | #[must_use] 227 | pub fn p(paths: Vec<&str>) -> std::path::PathBuf { 228 | paths.iter().fold( 229 | std::env::current_dir() 230 | .unwrap() 231 | .join("tests") 232 | .join("fixtures"), 233 | |acc, path| acc.join(path), 234 | ) 235 | } 236 | 237 | #[must_use] 238 | pub fn vec_to_set(vec: Vec<&str>) -> std::collections::HashSet { 239 | std::collections::HashSet::from_iter(vec.into_iter().map(|s| s.to_string())) 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /bench/scripts/gen-bench.js: -------------------------------------------------------------------------------- 1 | const babel = require("@babel/core"); 2 | const path = require("path"); 3 | const fs = require("fs"); 4 | const resolver = require("enhanced-resolve"); 5 | 6 | const root = path.resolve(__dirname, "../ant-design"); 7 | const entryDir = path.resolve(root, "./components"); 8 | const entryFile = "index.tsx"; 9 | // copy from `/ant-design/node_modules/@ant-design/tools/lib/` 10 | const resolve = resolver.create.sync({ 11 | extensions: [ 12 | ".web.tsx", 13 | ".web.ts", 14 | ".web.jsx", 15 | ".web.js", 16 | ".ts", 17 | ".tsx", 18 | ".js", 19 | ".jsx", 20 | ".json", 21 | ], 22 | fileSystem: fs, 23 | alias: { 24 | "@ant-design/tools": path.resolve(__dirname, "../ant-design"), 25 | }, 26 | }); 27 | 28 | /** 29 | * 30 | * @param {string} p 31 | */ 32 | function getDirFromAbsolutePath(p) { 33 | return path.dirname(p); 34 | } 35 | 36 | /** 37 | * 38 | * @param {string} p 39 | */ 40 | function getFileFromAbsolutePath(p) { 41 | return p.split("/").pop(); 42 | } 43 | 44 | function isNotJsFile(file) { 45 | return !( 46 | file.endsWith(".js") || 47 | file.endsWith(".ts") || 48 | file.endsWith(".jsx") || 49 | file.endsWith(".tsx") 50 | ); 51 | } 52 | 53 | /** 54 | * 55 | * @param {string} dir 56 | * @param {string} file 57 | * @param {Set} set 58 | * @param {(dir: string, file: string) => void} callback 59 | */ 60 | function dfs(dir, file, set, callback) { 61 | if (isNotJsFile(file)) { 62 | return; 63 | } 64 | const target = path.resolve(dir, file); 65 | if (set.has(target)) { 66 | // avoid self-reference, 67 | // such as https://github.com/ant-design/ant-design/blob/master/components/version/index.tsx 68 | return; 69 | } else { 70 | set.add(target); 71 | } 72 | 73 | if (set.size % 100 == 0) { 74 | console.log( 75 | `Already processed ${set.size} files, now is deal with ${target}` 76 | ); 77 | } 78 | 79 | const code = fs.readFileSync(target).toString("utf-8"); 80 | const nodeHadRemoveTS = babel.transformSync(code, { 81 | presets: ["@babel/preset-typescript"], 82 | plugins: [ 83 | [ 84 | "@babel/plugin-proposal-decorators", 85 | { 86 | version: "2021-12", 87 | }, 88 | ], 89 | ], 90 | ast: true, 91 | filename: target, 92 | configFile: false, 93 | sourceMaps: false, 94 | code: false, 95 | highlightCode: false, 96 | comments: false, 97 | }); 98 | 99 | if (!nodeHadRemoveTS.ast) { 100 | return; 101 | } 102 | 103 | babel.traverse(nodeHadRemoveTS.ast, { 104 | enter(p) { 105 | let value = ""; 106 | 107 | if (p.isImportDeclaration()) { 108 | value = p.node.source.value; 109 | } else if (p.isExportNamedDeclaration()) { 110 | if (!p.node.source) { 111 | return; 112 | } 113 | value = p.node.source.value; 114 | } else if (p.isCallExpression() && p.node.callee.name === "require") { 115 | if (typeof p.node.arguments?.[0].value === "string") { 116 | value = p.node.arguments[0].value; 117 | } else { 118 | console.log(target); 119 | } 120 | } else if (p.isCallExpression() && p.node.callee?.type === "Import") { 121 | if (typeof p.node.arguments?.[0].value === "string") { 122 | value = p.node.arguments[0].value; 123 | } else { 124 | console.log(target); 125 | } 126 | } else { 127 | return; 128 | } 129 | if (value === "") { 130 | return; 131 | } 132 | 133 | try { 134 | const next = resolve(dir, value); 135 | callback(dir, value); 136 | dfs( 137 | getDirFromAbsolutePath(next), 138 | getFileFromAbsolutePath(next), 139 | set, 140 | callback 141 | ); 142 | } catch (err) { 143 | console.log( 144 | `[IGNORED FAILED] resolve ${value} in ${dir} failed, and ignored it.` 145 | ); 146 | } 147 | }, 148 | }); 149 | } 150 | 151 | /** 152 | * 153 | * @param {(dir: string, file: string) => void} callback 154 | */ 155 | function run(callback) { 156 | dfs(entryDir, entryFile, new Set(), callback); 157 | } 158 | 159 | // ------------------------ 160 | 161 | const HEADER = `// DO NOT EDIT THIS FILE. 162 | // It is auto-generated by /bench/scripts/generator-rs-benchmark.js 163 | `; 164 | 165 | function generatorRsBenchmark() { 166 | let content = 167 | HEADER + 168 | ` 169 | #![feature(test)] 170 | extern crate test; 171 | 172 | #[cfg(test)] 173 | mod bench_test { 174 | 175 | use nodejs_resolver::{Resolver, Options, ResolveResult, Resource, RResult}; 176 | use std::path::PathBuf; 177 | use test::Bencher; 178 | // use std::time::Instant; 179 | 180 | fn is_ok(result: RResult>) { 181 | assert!(result.is_ok()) 182 | } 183 | 184 | #[bench] 185 | fn ant_design_bench(b: &mut Bencher) { 186 | b.iter(|| { 187 | let resolver = Resolver::new(Options { 188 | extensions: vec![ 189 | ".web.tsx", 190 | ".web.ts", 191 | ".web.jsx", 192 | ".web.js", 193 | ".ts", 194 | ".tsx", 195 | ".js", 196 | ".jsx", 197 | ".json", 198 | ].into_iter().map(String::from).collect(), 199 | ..Default::default() 200 | }); 201 | 202 | // let start = Instant::now(); 203 | `; 204 | run(function (dir, file) { 205 | content += ` 206 | is_ok(resolver.resolve( 207 | &PathBuf::from("${dir}"), 208 | "${file}", 209 | )); 210 | `; 211 | }); 212 | content += ` 213 | // println!("time cost: {:?} ms", start.elapsed().as_millis());// ms 214 | }); 215 | } 216 | }\n`; 217 | console.log("length", content.length); 218 | const rsFileStoredPath = path.resolve(__dirname, "../../tests/bench.rs"); 219 | fs.writeFileSync(rsFileStoredPath, content); 220 | } 221 | 222 | function generatorEnhanceResolveBenchmark() { 223 | let content = 224 | HEADER + 225 | ` 226 | console.time('bench'); 227 | const path = require('path'); 228 | const { ResolverFactory } = require("enhanced-resolve"); 229 | const Benchmark = require('benchmark'); 230 | 231 | const root = path.resolve(__dirname, "./ant-design"); 232 | 233 | const resolver = ResolverFactory.createResolver({ 234 | extensions: [ 235 | '.web.tsx', 236 | '.web.ts', 237 | '.web.jsx', 238 | '.web.js', 239 | '.ts', 240 | '.tsx', 241 | '.js', 242 | '.jsx', 243 | '.json', 244 | ], 245 | fileSystem: require('fs'), 246 | alias: { 247 | "@ant-design/tools" : path.resolve(__dirname, '../ant-design'), 248 | }, 249 | }) 250 | 251 | async function resolve(dir, file) { 252 | return new Promise(res => { 253 | resolver.resolve({}, dir, file, {}, (_error, _result) => { 254 | res(undefined) 255 | }) 256 | }) 257 | } 258 | 259 | async function run() { 260 | const tasks = []; 261 | `; 262 | run(function (dir, file) { 263 | content += `tasks.push(resolve('${dir}', '${file}'));\n`; 264 | }); 265 | 266 | content += ` 267 | await Promise.all(tasks); 268 | }; 269 | 270 | // const suite = new Benchmark.Suite(); 271 | // suite 272 | // .add("EnhancedResolve", run) 273 | // .on('cycle', function(event) { 274 | // console.log(String(event.target)); 275 | // }) 276 | // .run(); 277 | 278 | run().then(() => { 279 | console.timeEnd('bench'); 280 | }); 281 | `; 282 | console.log("length", content.length); 283 | const jsFileStoredPath = path.resolve(__dirname, "../enhanceResolve.js"); 284 | fs.writeFileSync(jsFileStoredPath, content); 285 | } 286 | 287 | function generatorESBuildResolveBenchMark() { 288 | let content = 289 | HEADER + 290 | ` 291 | const path = require('path'); 292 | const { build } = require('esbuild'); 293 | const Benchmark = require('benchmark'); 294 | 295 | const suite = new Benchmark.Suite(); 296 | 297 | async function run() { 298 | await build({ 299 | stdin: { 300 | contents: '', 301 | }, 302 | write: false, 303 | bundle: true, 304 | treeShaking: false, 305 | ignoreAnnotations: true, 306 | platform: 'node', 307 | plugins: [{ 308 | name: 'resolve', 309 | setup(build) { 310 | build.onStart(async () => { 311 | console.time('bench') 312 | const tasks = []; 313 | `; 314 | run(function (dir, file) { 315 | content += `tasks.push(build.resolve('${file}', { resolveDir: '${dir}', kind: 'import-statement' } ));\n`; 316 | }); 317 | 318 | content += ` 319 | await Promise.all(tasks); 320 | console.timeEnd('bench') 321 | }) 322 | }, 323 | }], 324 | }) 325 | }; 326 | 327 | run(); 328 | 329 | // suite 330 | // .add("ESBuildResolve", run) 331 | // .on('cycle', function(event) { 332 | // console.log(String(event.target)); 333 | // }) 334 | // .run({ 'async': true }); 335 | `; 336 | console.log("length", content.length); 337 | const jsFileStoredPath = path.resolve(__dirname, "../esbuildResolve.js"); 338 | fs.writeFileSync(jsFileStoredPath, content); 339 | } 340 | 341 | function generatorESBuildNativeResolveBenchMark() { 342 | let content = 343 | HEADER + 344 | ` 345 | package main 346 | 347 | import ( 348 | "github.com/evanw/esbuild/pkg/api" 349 | ) 350 | 351 | func main() { 352 | api.Build(api.BuildOptions{ 353 | Stdin: &api.StdinOptions{ 354 | Contents: "", 355 | }, 356 | Write: false, 357 | TreeShaking: api.TreeShakingFalse, 358 | Bundle: true, 359 | IgnoreAnnotations: true, 360 | Platform: api.PlatformNode, 361 | Plugins: []api.Plugin{{ 362 | Name: "resolve", 363 | Setup: func(build api.PluginBuild) { 364 | build.OnStart(func() (api.OnStartResult, error) { 365 | `; 366 | run(function (dir, file) { 367 | content += `build.Resolve("${file}", api.ResolveOptions{ ResolveDir: "${dir}"})\n`; 368 | }); 369 | 370 | content += ` 371 | return api.OnStartResult{}, nil 372 | }) 373 | }, 374 | }, 375 | }, 376 | }) 377 | } 378 | `; 379 | console.log("length", content.length); 380 | const goFileStoredPath = path.resolve( 381 | __dirname, 382 | "../esbuildResolve_native.go" 383 | ); 384 | fs.writeFileSync(goFileStoredPath, content); 385 | } 386 | 387 | // ------------------------------------------------- 388 | 389 | if (process.argv[2] === "rs") { 390 | generatorRsBenchmark(); 391 | } else if (process.argv[2] === "esbuild") { 392 | generatorESBuildResolveBenchMark(); 393 | } else if (process.argv[2] === "enhanced") { 394 | generatorEnhanceResolveBenchmark(); 395 | } else if (process.argv[2] === "esbuild_native") { 396 | generatorESBuildNativeResolveBenchMark(); 397 | const cp = require("child_process"); 398 | cp.spawnSync("go mod init esbuildResolve_native"); 399 | cp.spawnSync("go get github.com/evanw/esbuild/pkg/api"); 400 | } else { 401 | throw Error("Input the correct argument"); 402 | } 403 | -------------------------------------------------------------------------------- /src/resolve.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | description::DescriptionData, 3 | info::NormalizedPath, 4 | kind::PathKind, 5 | log::color, 6 | plugin::{ 7 | BrowserFieldPlugin, ExportsFieldPlugin, ExtensionAliasPlugin, ImportsFieldPlugin, 8 | MainFieldPlugin, MainFilePlugin, Plugin, 9 | }, 10 | Context, EnforceExtension, Info, ResolveResult, Resolver, State, 11 | }; 12 | use std::{ 13 | borrow::Cow, 14 | path::{Path, PathBuf}, 15 | }; 16 | 17 | impl Resolver { 18 | fn resolve_file_with_ext(&self, mut path: PathBuf, info: Info) -> State { 19 | let v = unsafe { &mut *(&mut path as *mut PathBuf as *mut Vec) }; 20 | for ext in &self.options.extensions { 21 | v.extend_from_slice(ext.as_bytes()); 22 | if self.load_entry(path.as_ref()).is_file() { 23 | return State::Success(ResolveResult::Resource( 24 | info.with_path(path).with_target(""), 25 | )); 26 | } 27 | unsafe { 28 | v.set_len(v.len() - ext.len()); 29 | } 30 | } 31 | tracing::debug!( 32 | "'{}[{}]' is not a file", 33 | color::red(&path.display()), 34 | color::red(&self.options.extensions.join("|")) 35 | ); 36 | State::Resolving(info) 37 | } 38 | 39 | pub(crate) fn resolve_as_context(&self, info: Info, context: &Context) -> State { 40 | if !context.resolve_to_context.get() { 41 | return State::Resolving(info); 42 | } 43 | let path = info.to_resolved_path(); 44 | tracing::debug!( 45 | "Attempting to load '{}' as a context", 46 | color::blue(&path.display()) 47 | ); 48 | if self.load_entry(&path).is_dir() { 49 | State::Success(ResolveResult::Resource(Info::new(path, Default::default()))) 50 | } else { 51 | State::Failed(info) 52 | } 53 | } 54 | 55 | pub(crate) fn resolve_as_fully_specified(&self, info: Info, context: &mut Context) -> State { 56 | let fully_specified = context.fully_specified.get(); 57 | if !fully_specified { 58 | return State::Resolving(info); 59 | } 60 | let path = info.to_resolved_path(); 61 | let request = info.request(); 62 | let target = request.target(); 63 | if self.load_entry(&path).is_file() { 64 | let path = path.to_path_buf(); 65 | State::Success(ResolveResult::Resource( 66 | info.with_path(path).with_target(""), 67 | )) 68 | } else if matches!( 69 | request.kind(), 70 | PathKind::AbsolutePosix | PathKind::AbsoluteWin | PathKind::Relative 71 | ) || split_slash_from_request(target).is_some() 72 | { 73 | State::Failed(info) 74 | } else { 75 | let dir = path.to_path_buf(); 76 | let info = info.with_path(dir).with_target("."); 77 | context.fully_specified.set(false); 78 | let state = self._resolve(info.clone(), context); 79 | context.fully_specified.set(true); 80 | if state.is_finished() { 81 | state 82 | } else { 83 | State::Failed(info) 84 | } 85 | } 86 | } 87 | 88 | pub(crate) fn resolve_as_file(&self, info: Info, context: &mut Context) -> State { 89 | if info.request().is_directory() { 90 | return State::Resolving(info); 91 | } 92 | 93 | self.options 94 | .extension_alias 95 | .iter() 96 | .fold(State::Resolving(info), |state, (extension, alias_list)| { 97 | state.then(|info| { 98 | ExtensionAliasPlugin::new(extension, alias_list).apply(self, info, context) 99 | }) 100 | }) 101 | .then(|info| { 102 | let path = info.to_resolved_path().to_path_buf(); 103 | tracing::debug!( 104 | "Attempting to load '{}' as a file", 105 | color::blue(&path.display()) 106 | ); 107 | if matches!(self.options.enforce_extension, EnforceExtension::Enabled) { 108 | self.resolve_file_with_ext(path, info) 109 | } else if self.load_entry(&path).is_file() { 110 | State::Success(ResolveResult::Resource( 111 | info.with_path(path).with_target(""), 112 | )) 113 | } else { 114 | self.resolve_file_with_ext(path, info) 115 | } 116 | }) 117 | } 118 | 119 | pub(crate) fn resolve_as_dir(&self, info: Info, context: &mut Context) -> State { 120 | let dir = info.to_resolved_path(); 121 | let entry = self.load_entry(&dir); 122 | if !entry.is_dir() { 123 | return State::Failed(info); 124 | } 125 | let pkg_info = match entry.pkg_info(self) { 126 | Ok(pkg_info) => pkg_info, 127 | Err(err) => return State::Error(err), 128 | }; 129 | if let Some(pkg_info) = pkg_info { 130 | MainFieldPlugin::new(pkg_info).apply(self, info, context) 131 | } else { 132 | State::Resolving(info) 133 | } 134 | .then(|info| MainFilePlugin.apply(self, info, context)) 135 | } 136 | 137 | pub(crate) fn resolve_as_modules(&self, info: Info, context: &mut Context) -> State { 138 | let original_dir = info.normalized_path(); 139 | for module in &self.options.modules { 140 | let node_modules_path = Path::new(module); 141 | let (node_modules_path, need_find_up) = if node_modules_path.is_absolute() { 142 | (Cow::Borrowed(node_modules_path), false) 143 | } else { 144 | (Cow::Owned(original_dir.as_ref().join(module)), true) 145 | }; 146 | let state = self 147 | ._resolve_as_modules(info.clone(), original_dir, &node_modules_path, context) 148 | .then(|info| { 149 | if !need_find_up { 150 | State::Resolving(info) 151 | } else if let Some(parent_dir) = original_dir.as_ref().parent() { 152 | self._resolve(info.with_path(parent_dir), context) 153 | } else { 154 | State::Resolving(info) 155 | } 156 | }); 157 | if state.is_finished() { 158 | return state; 159 | } 160 | } 161 | State::Failed(info) 162 | } 163 | 164 | fn _resolve_as_modules( 165 | &self, 166 | info: Info, 167 | original_dir: &NormalizedPath, 168 | node_modules_path: &Path, 169 | context: &mut Context, 170 | ) -> State { 171 | let entry = self.load_entry(node_modules_path); 172 | let pkg_info = match entry.pkg_info(self) { 173 | Ok(pkg_info) => pkg_info.as_ref(), 174 | Err(err) => return State::Error(err), 175 | }; 176 | let state = if entry.is_dir() { 177 | // is there had `node_modules` folder? 178 | self.resolve_node_modules(info, node_modules_path, context) 179 | .then(|info| { 180 | let is_resolve_self = pkg_info.map_or(false, |pkg_info| { 181 | let request_module_name = 182 | get_module_name_from_request(info.request().target()); 183 | is_resolve_self(pkg_info, request_module_name) 184 | }); 185 | if is_resolve_self { 186 | let pkg_info = pkg_info.unwrap(); 187 | ExportsFieldPlugin::new(pkg_info).apply(self, info, context) 188 | } else { 189 | State::Resolving(info) 190 | } 191 | }) 192 | } else if pkg_info.map_or(false, |pkg_info| pkg_info.dir().eq(original_dir)) { 193 | // is `info.path` on the same level as package.json 194 | let request_module_name = get_module_name_from_request(info.request().target()); 195 | if is_resolve_self(pkg_info.unwrap(), request_module_name) { 196 | ExportsFieldPlugin::new(pkg_info.unwrap()).apply(self, info, context) 197 | } else { 198 | State::Resolving(info) 199 | } 200 | } else { 201 | State::Resolving(info) 202 | }; 203 | 204 | state 205 | } 206 | 207 | fn resolve_node_modules( 208 | &self, 209 | info: Info, 210 | node_modules_path: &Path, 211 | context: &mut Context, 212 | ) -> State { 213 | let original_dir = info.normalized_path(); 214 | let request_module_name = get_module_name_from_request(info.request().target()); 215 | let module_path = node_modules_path.join(request_module_name); 216 | let entry = self.load_entry(&module_path); 217 | let module_info = Info::new(node_modules_path, info.request().clone()); 218 | if !entry.is_dir() { 219 | let state = self.resolve_as_file(module_info, context); 220 | if state.is_finished() { 221 | state 222 | } else { 223 | State::Resolving(info) 224 | } 225 | } else { 226 | let pkg_info = match entry.pkg_info(self) { 227 | Ok(pkg_info) => pkg_info, 228 | Err(err) => return State::Error(err), 229 | }; 230 | let state = if let Some(pkg_info) = pkg_info { 231 | let out_node_modules = pkg_info.dir().eq(original_dir); 232 | if !out_node_modules || is_resolve_self(pkg_info, request_module_name) { 233 | ExportsFieldPlugin::new(pkg_info).apply(self, module_info, context) 234 | } else { 235 | State::Resolving(module_info) 236 | } 237 | .then(|info| ImportsFieldPlugin::new(pkg_info).apply(self, info, context)) 238 | .then(|info| MainFieldPlugin::new(pkg_info).apply(self, info, context)) 239 | .then(|info| BrowserFieldPlugin::new(pkg_info, true).apply(self, info, context)) 240 | } else { 241 | State::Resolving(module_info) 242 | } 243 | .then(|info| self.resolve_as_context(info, context)) 244 | .then(|info| self.resolve_as_fully_specified(info, context)) 245 | .then(|info| self.resolve_as_file(info, context)) 246 | .then(|info| self.resolve_as_dir(info, context)); 247 | 248 | match state { 249 | State::Failed(info) => State::Resolving(info), 250 | _ => state, 251 | } 252 | } 253 | } 254 | } 255 | 256 | fn is_resolve_self(pkg_info: &DescriptionData, request_module_name: &str) -> bool { 257 | pkg_info 258 | .data() 259 | .name() 260 | .map(|pkg_name| request_module_name == pkg_name) 261 | .map_or(false, |ans| ans) 262 | } 263 | 264 | /// split the index from `[module-name]/[path]` 265 | pub(crate) fn split_slash_from_request(target: &str) -> Option { 266 | let has_namespace_scope = target.starts_with('@'); 267 | let chars = target.chars().enumerate(); 268 | let slash_index_list: Vec = chars 269 | .filter_map(|(index, char)| if '/' == char { Some(index) } else { None }) 270 | .collect(); 271 | if has_namespace_scope { 272 | slash_index_list.get(1) 273 | } else { 274 | slash_index_list.first() 275 | } 276 | .copied() 277 | } 278 | 279 | fn get_module_name_from_request(target: &str) -> &str { 280 | split_slash_from_request(target).map_or(target, |index| &target[0..index]) 281 | } 282 | 283 | pub(crate) fn get_path_from_request(target: &str) -> Option> { 284 | split_slash_from_request(target).map(|index| Cow::Borrowed(&target[index..])) 285 | } 286 | 287 | #[cfg(test)] 288 | mod test { 289 | use super::{get_module_name_from_request, get_path_from_request, split_slash_from_request}; 290 | 291 | #[test] 292 | fn test_split_slash_from_request() { 293 | assert_eq!(split_slash_from_request("a"), None); 294 | assert_eq!(split_slash_from_request("a/b"), Some(1)); 295 | assert_eq!(split_slash_from_request("@a"), None); 296 | assert_eq!(split_slash_from_request("@a/b"), None); 297 | assert_eq!(split_slash_from_request("@a/b/c"), Some(4)); 298 | } 299 | 300 | #[test] 301 | fn test_get_module_name_from_request() { 302 | assert_eq!(get_module_name_from_request("a"), "a"); 303 | assert_eq!(get_module_name_from_request("a/b"), "a"); 304 | assert_eq!(get_module_name_from_request("@a"), "@a"); 305 | assert_eq!(get_module_name_from_request("@a/b"), "@a/b"); 306 | assert_eq!(get_module_name_from_request("@a/b/c"), "@a/b"); 307 | } 308 | 309 | #[test] 310 | fn test_get_path_from_request() { 311 | assert_eq!(get_path_from_request("a"), None); 312 | assert_eq!(get_path_from_request("a/b"), Some("/b".into())); 313 | assert_eq!(get_path_from_request("@a"), None); 314 | assert_eq!(get_path_from_request("@a/b"), None); 315 | assert_eq!(get_path_from_request("@a/b/c"), Some("/c".into())); 316 | } 317 | } 318 | --------------------------------------------------------------------------------