├── .coveralls.yml ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build └── release.js ├── core.js ├── dist ├── .npmignore ├── NOTE.md ├── README.md ├── core.js ├── index.js └── package.json ├── index.js ├── package.json └── test ├── setup.js └── test.js /.coveralls.yml: -------------------------------------------------------------------------------- 1 | repo_token: JcbotUc2IWIcW2683EmPzn7p2RWkB13Js -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | node_modules/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | node_modules/ 3 | test/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "4" 5 | - "6" 6 | - "8" 7 | - "10" 8 | - "node" 9 | script: 10 | - "npm run cov" 11 | after_script: 12 | - "npm install coveralls@2.11.x && cat coverage/lcov.info | coveralls" 13 | matrix: 14 | fast_finish: true 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/). 6 | 7 | ## [1.1.2] - 2018-12-08 8 | ### Fixed 9 | - fix a bug in order to not resolve `path/to/file` to `path/from/file` with the config `['to', 'from']`. see [#7][issue7] 10 | 11 | ### Changed 12 | - update test case 13 | - update coverage for node 10 & 11 14 | - update changelog 15 | - update usage to readme 16 | 17 | 18 | ## [1.1.1] - 2018-07-30 19 | ### Fixed 20 | - fix the bug that the module mapped to a relative path can not be resolved. see [#5][issue5] 21 | 22 | ### Changed 23 | - update test case 24 | - update changelog 25 | - update usage to readme 26 | - add more package keywords 27 | 28 | 29 | ## [1.1.0] - 2018-05-06 30 | ### Added 31 | - support custom file extensions 32 | 33 | ### Changed 34 | - update test case 35 | - update changelog 36 | - update usage to readme 37 | 38 | 39 | ## [1.0.4] - 2018-04-25 40 | ### Added 41 | - add builtin modules resolve test case and relevant dependencies 42 | 43 | ### Fixed 44 | - fix the bug that inner function `resolveLookupPaths` resolve `/node_modules` as `//node_modules` 45 | - update core modules for Node.js 8/9/... 46 | 47 | ### Changed 48 | - add peerDependencies, repository, bugs, homepage config items to package.json 49 | - update readme 50 | - update changelog 51 | - update travis config 52 | - add release script to complete preparation before publish 53 | 54 | 55 | ## [1.0.3] - 2017-01-12 56 | ### Fixed 57 | - fix the bug that the path like /path/to/node_modules/node_modules is excluded from the lookup paths 58 | 59 | 60 | ## [1.0.2] - 2017-01-05 61 | ### Changed 62 | - fix the bug that it's unable to resolve non-relative path modules when current working directory is subdirectory of the project root directory. 63 | 64 | 65 | ## [1.0.1] - 2016-12-29 66 | ### Changed 67 | - remove package `resolve` to fix the bug caused by format changing of the internal file core.json of resolve 1.1.7 68 | - add bug description and solution to README.md 69 | 70 | 71 | ## [1.0.0] - 2016-12-23 72 | ### Added 73 | - support resolve node modules with alias config 74 | - support resolve normal node modules, including Node.js core modules and other third party modules 75 | - support resolve relative path modules 76 | 77 | 78 | [issue5]: https://github.com/johvin/eslint-import-resolver-alias/issues/5 79 | [issue7]: https://github.com/johvin/eslint-import-resolver-alias/issues/7 80 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 johvin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eslint-import-resolver-alias 2 | 3 | [![Version npm][version]](http://browsenpm.org/package/eslint-import-resolver-alias) 4 | ![Version node][node] 5 | [![Build Status][build]](https://travis-ci.org/johvin/eslint-import-resolver-alias) 6 | [![Download][download]](https://www.npmjs.com/package/eslint-import-resolver-alias) 7 | [![Dependencies][david]](https://david-dm.org/johvin/eslint-import-resolver-alias) 8 | ![peerDependencies][peer] 9 | [![Coverage Status][cover]](https://coveralls.io/github/johvin/eslint-import-resolver-alias?branch=master) 10 | [![Known Vulnerabilities][vulnerabilities]](https://snyk.io/test/npm/eslint-import-resolver-alias) 11 | [![License][license]](https://opensource.org/licenses/MIT) 12 | 13 | [version]: http://img.shields.io/npm/v/eslint-import-resolver-alias.svg?style=flat-square 14 | [node]: https://img.shields.io/node/v/eslint-import-resolver-alias/latest.svg?style=flat-square 15 | [build]: http://img.shields.io/travis/johvin/eslint-import-resolver-alias/master.svg?style=flat-square 16 | [download]: https://img.shields.io/npm/dm/eslint-import-resolver-alias.svg?style=flat-square 17 | [david]: https://img.shields.io/david/johvin/eslint-import-resolver-alias.svg?style=flat-square 18 | [peer]: https://img.shields.io/david/peer/johvin/eslint-import-resolver-alias.svg?style=flat-square 19 | [cover]: http://img.shields.io/coveralls/johvin/eslint-import-resolver-alias/master.svg?style=flat-square 20 | [vulnerabilities]: https://snyk.io/test/npm/eslint-import-resolver-alias/badge.svg?style=flat-square 21 | [license]: https://img.shields.io/badge/License-MIT-brightgreen.svg?style=flat-square 22 | 23 | 24 | This is a simple Node.js module import resolution plugin for [`eslint-plugin-import`](https://www.npmjs.com/package/eslint-plugin-import), which supports native Node.js module resolution, module alias/mapping and custom file extensions. 25 | 26 | 27 | ## Installation 28 | 29 | Prerequisites: Node.js >=4.x and corresponding version of npm. 30 | 31 | ```shell 32 | npm install eslint-plugin-import eslint-import-resolver-alias --save-dev 33 | ``` 34 | 35 | 36 | ## Usage 37 | 38 | Pass this resolver and its parameters to `eslint-plugin-import` using your `eslint` config file, `.eslintrc` or `.eslintrc.js`. 39 | 40 | ```js 41 | // .eslintrc.js 42 | module.exports = { 43 | settings: { 44 | 'import/resolver': { 45 | alias: { 46 | map: [ 47 | ['babel-polyfill', 'babel-polyfill/dist/polyfill.min.js'], 48 | ['helper', './utils/helper'], 49 | ['material-ui/DatePicker', '../custom/DatePicker'], 50 | ['material-ui', 'material-ui-ie10'] 51 | ], 52 | extensions: ['.ts', '.js', '.jsx', '.json'] 53 | } 54 | } 55 | } 56 | }; 57 | ``` 58 | 59 | Note: 60 | 61 | - The alias config object contains two properties, `map` and `extensions`, both of which are array types 62 | - The item of `map` array is also array type which contains 2 string 63 | + The first string represents the alias of module name or path 64 | + The second string represents the actual module name or path 65 | - The `map` item `['helper', './utils/helper']` means that the modules which match `helper` or `helper/*` will be resolved to `./utils/helper` or `./utils/helper/*` which are located relative to the `process current working directory` (almost the project root directory). If you just want to resolve `helper` to `./utils/helper`, use `['^helper$', './utils/helper']` instead. See [issue #3](https://github.com/johvin/eslint-import-resolver-alias/issues/3) 66 | - The order of 'material-ui/DatePicker' and 'material-ui' cannot be reversed, otherwise the alias rule 'material-ui/DatePicker' does not work 67 | - The default value of `extensions` property is `['.js', '.json', '.node']` if it is assigned to an empty array or not specified 68 | 69 | *If the `extensions` property is not specified, the config object can be simplified to the `map` array.* 70 | 71 | ```js 72 | // .eslintrc.js 73 | module.exports = { 74 | settings: { 75 | 'import/resolver': { 76 | alias: [ 77 | ['babel-polyfill', 'babel-polyfill/dist/polyfill.min.js'], 78 | ['helper', './utils/helper'], 79 | ['material-ui/DatePicker', '../custom/DatePicker'], 80 | ['material-ui', 'material-ui-ie10'] 81 | ] 82 | } 83 | } 84 | }; 85 | ``` 86 | 87 | When the config is not a valid object (such as `true`), the resolver falls back to native Node.js module resolution. 88 | 89 | ```js 90 | // .eslintrc.js 91 | module.exports = { 92 | settings: { 93 | 'import/resolver': { 94 | alias: true 95 | } 96 | } 97 | }; 98 | ``` 99 | 100 | ## CHANGELOG 101 | 102 | [`CHANGELOG`](./CHANGELOG.md) 103 | 104 | ## References 105 | 106 | - eslint-plugin-import/no-extraneous-dependencies 107 | - eslint-plugin-import/no-unresolved 108 | - eslint-module-utils/resolve 109 | - resolve 110 | - eslint-import-resolver-node 111 | -------------------------------------------------------------------------------- /build/release.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const adjustMD = require('adjust-md-for-publish'); 3 | 4 | function copyFile(filename) { 5 | fs.writeFileSync(`dist/${filename}`, fs.readFileSync(filename)); 6 | } 7 | 8 | adjustMD({ 9 | filename: 'README.md', 10 | destname: 'dist/README.md', 11 | filterSection: [ 12 | 'CHANGELOG', 13 | 'References' 14 | ] 15 | }); 16 | 17 | [ 18 | 'core.js', 19 | 'index.js', 20 | 'package.json' 21 | ].forEach(copyFile); 22 | 23 | console.log('\n release is ready !!!'); 24 | -------------------------------------------------------------------------------- /core.js: -------------------------------------------------------------------------------- 1 | module.exports = module.constructor.builtinModules || /* istanbul ignore next */ Object.keys(process.binding('natives')).filter(m => m.indexOf('internal') !== 0); -------------------------------------------------------------------------------- /dist/.npmignore: -------------------------------------------------------------------------------- 1 | NOTE.md -------------------------------------------------------------------------------- /dist/NOTE.md: -------------------------------------------------------------------------------- 1 | # NOTE 2 | 3 | The `dist` folder contains the standalone build for guideline, however files here are not checked-in whenever there are changes in the source code. If you are on the `dev` branch, files here are **NOT** up to date. Only the `master` branch contains the built files for the latest stable version. -------------------------------------------------------------------------------- /dist/README.md: -------------------------------------------------------------------------------- 1 | # eslint-import-resolver-alias 2 | 3 | [![Version npm][version]](http://browsenpm.org/package/eslint-import-resolver-alias) 4 | ![Version node][node] 5 | [![Build Status][build]](https://travis-ci.org/johvin/eslint-import-resolver-alias) 6 | [![Download][download]](https://www.npmjs.com/package/eslint-import-resolver-alias) 7 | [![Dependencies][david]](https://david-dm.org/johvin/eslint-import-resolver-alias) 8 | ![peerDependencies][peer] 9 | [![Coverage Status][cover]](https://coveralls.io/github/johvin/eslint-import-resolver-alias?branch=master) 10 | [![Known Vulnerabilities][vulnerabilities]](https://snyk.io/test/npm/eslint-import-resolver-alias) 11 | [![License][license]](https://opensource.org/licenses/MIT) 12 | 13 | [version]: http://img.shields.io/npm/v/eslint-import-resolver-alias.svg?style=flat-square 14 | [node]: https://img.shields.io/node/v/eslint-import-resolver-alias/latest.svg?style=flat-square 15 | [build]: http://img.shields.io/travis/johvin/eslint-import-resolver-alias/master.svg?style=flat-square 16 | [download]: https://img.shields.io/npm/dm/eslint-import-resolver-alias.svg?style=flat-square 17 | [david]: https://img.shields.io/david/johvin/eslint-import-resolver-alias.svg?style=flat-square 18 | [peer]: https://img.shields.io/david/peer/johvin/eslint-import-resolver-alias.svg?style=flat-square 19 | [cover]: http://img.shields.io/coveralls/johvin/eslint-import-resolver-alias/master.svg?style=flat-square 20 | [vulnerabilities]: https://snyk.io/test/npm/eslint-import-resolver-alias/badge.svg?style=flat-square 21 | [license]: https://img.shields.io/badge/License-MIT-brightgreen.svg?style=flat-square 22 | 23 | 24 | This is a simple Node.js module import resolution plugin for [`eslint-plugin-import`](https://www.npmjs.com/package/eslint-plugin-import), which supports native Node.js module resolution, module alias/mapping and custom file extensions. 25 | 26 | 27 | ## Installation 28 | 29 | Prerequisites: Node.js >=4.x and corresponding version of npm. 30 | 31 | ```shell 32 | npm install eslint-plugin-import eslint-import-resolver-alias --save-dev 33 | ``` 34 | 35 | 36 | ## Usage 37 | 38 | Pass this resolver and its parameters to `eslint-plugin-import` using your `eslint` config file, `.eslintrc` or `.eslintrc.js`. 39 | 40 | ```js 41 | // .eslintrc.js 42 | module.exports = { 43 | settings: { 44 | 'import/resolver': { 45 | alias: { 46 | map: [ 47 | ['babel-polyfill', 'babel-polyfill/dist/polyfill.min.js'], 48 | ['helper', './utils/helper'], 49 | ['material-ui/DatePicker', '../custom/DatePicker'], 50 | ['material-ui', 'material-ui-ie10'] 51 | ], 52 | extensions: ['.ts', '.js', '.jsx', '.json'] 53 | } 54 | } 55 | } 56 | }; 57 | ``` 58 | 59 | Note: 60 | 61 | - The alias config object contains two properties, `map` and `extensions`, both of which are array types 62 | - The item of `map` array is also array type which contains 2 string 63 | + The first string represents the alias of module name or path 64 | + The second string represents the actual module name or path 65 | - The `map` item `['helper', './utils/helper']` means that the modules which match `helper` or `helper/*` will be resolved to `./utils/helper` or `./utils/helper/*` which are located relative to the `process current working directory` (almost the project root directory). If you just want to resolve `helper` to `./utils/helper`, use `['^helper$', './utils/helper']` instead. See [issue #3](https://github.com/johvin/eslint-import-resolver-alias/issues/3) 66 | - The order of 'material-ui/DatePicker' and 'material-ui' cannot be reversed, otherwise the alias rule 'material-ui/DatePicker' does not work 67 | - The default value of `extensions` property is `['.js', '.json', '.node']` if it is assigned to an empty array or not specified 68 | 69 | *If the `extensions` property is not specified, the config object can be simplified to the `map` array.* 70 | 71 | ```js 72 | // .eslintrc.js 73 | module.exports = { 74 | settings: { 75 | 'import/resolver': { 76 | alias: [ 77 | ['babel-polyfill', 'babel-polyfill/dist/polyfill.min.js'], 78 | ['helper', './utils/helper'], 79 | ['material-ui/DatePicker', '../custom/DatePicker'], 80 | ['material-ui', 'material-ui-ie10'] 81 | ] 82 | } 83 | } 84 | }; 85 | ``` 86 | 87 | When the config is not a valid object (such as `true`), the resolver falls back to native Node.js module resolution. 88 | 89 | ```js 90 | // .eslintrc.js 91 | module.exports = { 92 | settings: { 93 | 'import/resolver': { 94 | alias: true 95 | } 96 | } 97 | }; 98 | ``` 99 | -------------------------------------------------------------------------------- /dist/core.js: -------------------------------------------------------------------------------- 1 | module.exports = module.constructor.builtinModules || /* istanbul ignore next */ Object.keys(process.binding('natives')).filter(m => m.indexOf('internal') !== 0); -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * this resolver is used as a plugin of eslint-plugin-import 4 | * to solve Node.js package mapping problem 5 | * 6 | */ 7 | const path = require('path'); 8 | const coreModules = Object.create(null); 9 | 10 | require('./core').forEach(function (m) { 11 | this[m] = true 12 | }, coreModules); 13 | 14 | exports.interfaceVersion = 2; 15 | 16 | const Module = module.constructor; 17 | // Node.js native extensions 18 | const originExtensions = Module._extensions; 19 | 20 | function mockModuleLoader () {} 21 | 22 | exports.resolve = (modulePath, sourceFile, config) => { 23 | if (coreModules[modulePath]) { 24 | return { found: true, path: null }; 25 | } 26 | 27 | // compatible with the old array type configuration 28 | if (Array.isArray(config)) { 29 | config = { 30 | map: config 31 | } 32 | } else if (typeof config !== 'object') { 33 | config = {}; 34 | } 35 | 36 | // in order to be compatible with Node.js v4, 37 | // give up destructure syntax 38 | const map = config.map; 39 | const extensions = config.extensions; 40 | const sourceDir = path.dirname(sourceFile); 41 | let resolvePath = modulePath; 42 | 43 | // if modulePath starts with '.' (e.g. '.', '..', './a', '../a', '.ab.js') 44 | // it is a relative path because the path like '.ab.js' is not a valid node package name 45 | // see https://github.com/npm/validate-npm-package-name/blob/master/index.js 46 | if (modulePath[0] === '.') { 47 | // make resolvePath an absolute path 48 | resolvePath = path.resolve(sourceDir, modulePath); 49 | 50 | // actually, it doesn't matter what the second parameter is 51 | // when resolvePath is an absolute path, see detail in 52 | // Module._findPath source code 53 | return findModulePath(resolvePath, null, extensions); 54 | } 55 | 56 | if (Array.isArray(map)) { 57 | for (let i = 0, len = map.length; i < len; i++) { 58 | const re = new RegExp(`^${map[i][0]}($|/)`); 59 | const match = modulePath.match(re); 60 | if (match) { 61 | resolvePath = modulePath.replace(match[0], `${map[i][1]}${match[1]}`); 62 | break; 63 | } 64 | } 65 | } 66 | 67 | // there is a relative path mapping in alias.map, 68 | // the relative path is relative to the project root directory 69 | if (resolvePath[0] === '.') { 70 | resolvePath = path.resolve(process.cwd(), resolvePath); 71 | return findModulePath(resolvePath, null, extensions); 72 | } 73 | 74 | const paths = resolveLookupPaths(sourceDir); 75 | return findModulePath(resolvePath, paths, extensions); 76 | }; 77 | 78 | // get extension object like Module._extensions 79 | function getExtensions(extArray) { 80 | if (Array.isArray(extArray) && extArray.length > 0) { 81 | return extArray.reduce((a, b) => { 82 | a[b] = originExtensions[b] || mockModuleLoader 83 | return a; 84 | }, {}); 85 | } 86 | 87 | return null; 88 | } 89 | 90 | // find module path according to support file extensions. 91 | function findModulePath(request, paths, extArray) { 92 | if (extArray) { 93 | // little trick to make Node.js native `Module._findPath` method 94 | // to find the file with custom file extensions 95 | Module._extensions = getExtensions(extArray) || originExtensions; 96 | } 97 | 98 | // `Module._findPath` use `Module._extensions` to find a module 99 | const filename = Module._findPath(request, paths); 100 | 101 | if (extArray) { 102 | Module._extensions = originExtensions; 103 | } 104 | 105 | return { 106 | found: !!filename, 107 | path: filename || null 108 | }; 109 | } 110 | 111 | // resolve node_modules lookup paths 112 | // node.js native resolveLookupPaths's implementation contains various situation, 113 | // such as sourceFile-located directory, current working directory and etc. 114 | // which is a little more complex for this situation 115 | function resolveLookupPaths(absoluteSourceDir) { 116 | let paths; 117 | 118 | // use Node.js native node_modules lookup paths resolution 119 | /* istanbul ignore else */ 120 | if (Module._nodeModulePaths) { 121 | paths = Module._nodeModulePaths(absoluteSourceDir); 122 | } else { 123 | const moduleDir = 'node_modules'; 124 | let curDir; 125 | let nextDir = absoluteSourceDir; 126 | 127 | paths = []; 128 | 129 | do { 130 | // not append node_modules to a path already ending with node_modules 131 | while (nextDir.slice(-12) === moduleDir) { 132 | nextDir = path.resolve(nextDir, '..'); 133 | } 134 | curDir = nextDir; 135 | paths.push(path.resolve(curDir, moduleDir)); 136 | nextDir = path.resolve(curDir, '..'); 137 | } while(nextDir !== curDir); 138 | } 139 | 140 | return paths.concat(Module.globalPaths); 141 | } 142 | -------------------------------------------------------------------------------- /dist/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-import-resolver-alias", 3 | "version": "1.1.2", 4 | "description": "a simple Node behavior import resolution plugin for eslint-plugin-import, supporting module alias.", 5 | "main": "index.js", 6 | "scripts": { 7 | "release": "node build/release.js", 8 | "test": "mocha test/test.js --require test/setup.js", 9 | "cov": "istanbul cover node_modules/mocha/bin/_mocha -- test/test.js --require test/setup.js" 10 | }, 11 | "keywords": [ 12 | "eslint", 13 | "eslintplugin", 14 | "eslint-plugin-import", 15 | "eslint-import-resolver", 16 | "import-resolver", 17 | "resolver", 18 | "resolve", 19 | "resolution", 20 | "alias", 21 | "mapping", 22 | "rewrite", 23 | "rename", 24 | "webpack", 25 | "module", 26 | "node-native", 27 | "module-resolver", 28 | "import", 29 | "require" 30 | ], 31 | "engines": { 32 | "node": ">= 4" 33 | }, 34 | "author": "johvin", 35 | "license": "MIT", 36 | "devDependencies": { 37 | "adjust-md-for-publish": "^1.0.0", 38 | "builtin-modules": "^2.0.0", 39 | "istanbul": "^1.0.0-alpha.2", 40 | "mocha": "^3.2.0" 41 | }, 42 | "peerDependencies": { 43 | "eslint-plugin-import": ">=1.4.0" 44 | }, 45 | "directories": { 46 | "test": "test" 47 | }, 48 | "repository": { 49 | "type": "git", 50 | "url": "git+https://github.com/johvin/eslint-import-resolver-alias.git" 51 | }, 52 | "bugs": { 53 | "url": "https://github.com/johvin/eslint-import-resolver-alias/issues" 54 | }, 55 | "homepage": "https://github.com/johvin/eslint-import-resolver-alias#readme" 56 | } 57 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * this resolver is used as a plugin of eslint-plugin-import 4 | * to solve Node.js package mapping problem 5 | * 6 | */ 7 | const path = require('path'); 8 | const coreModules = Object.create(null); 9 | 10 | require('./core').forEach(function (m) { 11 | this[m] = true 12 | }, coreModules); 13 | 14 | exports.interfaceVersion = 2; 15 | 16 | const Module = module.constructor; 17 | // Node.js native extensions 18 | const originExtensions = Module._extensions; 19 | 20 | function mockModuleLoader () {} 21 | 22 | exports.resolve = (modulePath, sourceFile, config) => { 23 | if (coreModules[modulePath]) { 24 | return { found: true, path: null }; 25 | } 26 | 27 | // compatible with the old array type configuration 28 | if (Array.isArray(config)) { 29 | config = { 30 | map: config 31 | } 32 | } else if (typeof config !== 'object') { 33 | config = {}; 34 | } 35 | 36 | // in order to be compatible with Node.js v4, 37 | // give up destructure syntax 38 | const map = config.map; 39 | const extensions = config.extensions; 40 | const sourceDir = path.dirname(sourceFile); 41 | let resolvePath = modulePath; 42 | 43 | // if modulePath starts with '.' (e.g. '.', '..', './a', '../a', '.ab.js') 44 | // it is a relative path because the path like '.ab.js' is not a valid node package name 45 | // see https://github.com/npm/validate-npm-package-name/blob/master/index.js 46 | if (modulePath[0] === '.') { 47 | // make resolvePath an absolute path 48 | resolvePath = path.resolve(sourceDir, modulePath); 49 | 50 | // actually, it doesn't matter what the second parameter is 51 | // when resolvePath is an absolute path, see detail in 52 | // Module._findPath source code 53 | return findModulePath(resolvePath, null, extensions); 54 | } 55 | 56 | if (Array.isArray(map)) { 57 | for (let i = 0, len = map.length; i < len; i++) { 58 | const re = new RegExp(`^${map[i][0]}($|/)`); 59 | const match = modulePath.match(re); 60 | if (match) { 61 | resolvePath = modulePath.replace(match[0], `${map[i][1]}${match[1]}`); 62 | break; 63 | } 64 | } 65 | } 66 | 67 | // there is a relative path mapping in alias.map, 68 | // the relative path is relative to the project root directory 69 | if (resolvePath[0] === '.') { 70 | resolvePath = path.resolve(process.cwd(), resolvePath); 71 | return findModulePath(resolvePath, null, extensions); 72 | } 73 | 74 | const paths = resolveLookupPaths(sourceDir); 75 | return findModulePath(resolvePath, paths, extensions); 76 | }; 77 | 78 | // get extension object like Module._extensions 79 | function getExtensions(extArray) { 80 | if (Array.isArray(extArray) && extArray.length > 0) { 81 | return extArray.reduce((a, b) => { 82 | a[b] = originExtensions[b] || mockModuleLoader 83 | return a; 84 | }, {}); 85 | } 86 | 87 | return null; 88 | } 89 | 90 | // find module path according to support file extensions. 91 | function findModulePath(request, paths, extArray) { 92 | if (extArray) { 93 | // little trick to make Node.js native `Module._findPath` method 94 | // to find the file with custom file extensions 95 | Module._extensions = getExtensions(extArray) || originExtensions; 96 | } 97 | 98 | // `Module._findPath` use `Module._extensions` to find a module 99 | const filename = Module._findPath(request, paths); 100 | 101 | if (extArray) { 102 | Module._extensions = originExtensions; 103 | } 104 | 105 | return { 106 | found: !!filename, 107 | path: filename || null 108 | }; 109 | } 110 | 111 | // resolve node_modules lookup paths 112 | // node.js native resolveLookupPaths's implementation contains various situation, 113 | // such as sourceFile-located directory, current working directory and etc. 114 | // which is a little more complex for this situation 115 | function resolveLookupPaths(absoluteSourceDir) { 116 | let paths; 117 | 118 | // use Node.js native node_modules lookup paths resolution 119 | /* istanbul ignore else */ 120 | if (Module._nodeModulePaths) { 121 | paths = Module._nodeModulePaths(absoluteSourceDir); 122 | } else { 123 | const moduleDir = 'node_modules'; 124 | let curDir; 125 | let nextDir = absoluteSourceDir; 126 | 127 | paths = []; 128 | 129 | do { 130 | // not append node_modules to a path already ending with node_modules 131 | while (nextDir.slice(-12) === moduleDir) { 132 | nextDir = path.resolve(nextDir, '..'); 133 | } 134 | curDir = nextDir; 135 | paths.push(path.resolve(curDir, moduleDir)); 136 | nextDir = path.resolve(curDir, '..'); 137 | } while(nextDir !== curDir); 138 | } 139 | 140 | return paths.concat(Module.globalPaths); 141 | } 142 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-import-resolver-alias", 3 | "version": "1.1.2", 4 | "description": "a simple Node behavior import resolution plugin for eslint-plugin-import, supporting module alias.", 5 | "main": "index.js", 6 | "scripts": { 7 | "release": "node build/release.js", 8 | "test": "mocha test/test.js --require test/setup.js", 9 | "cov": "istanbul cover node_modules/mocha/bin/_mocha -- test/test.js --require test/setup.js" 10 | }, 11 | "keywords": [ 12 | "eslint", 13 | "eslintplugin", 14 | "eslint-plugin-import", 15 | "eslint-import-resolver", 16 | "import-resolver", 17 | "resolver", 18 | "resolve", 19 | "resolution", 20 | "alias", 21 | "mapping", 22 | "rewrite", 23 | "rename", 24 | "webpack", 25 | "module", 26 | "node-native", 27 | "module-resolver", 28 | "import", 29 | "require" 30 | ], 31 | "engines": { 32 | "node": ">= 4" 33 | }, 34 | "author": "johvin", 35 | "license": "MIT", 36 | "devDependencies": { 37 | "adjust-md-for-publish": "^1.0.0", 38 | "builtin-modules": "^2.0.0", 39 | "istanbul": "^1.0.0-alpha.2", 40 | "mocha": "^3.2.0" 41 | }, 42 | "peerDependencies": { 43 | "eslint-plugin-import": ">=1.4.0" 44 | }, 45 | "directories": { 46 | "test": "test" 47 | }, 48 | "repository": { 49 | "type": "git", 50 | "url": "git+https://github.com/johvin/eslint-import-resolver-alias.git" 51 | }, 52 | "bugs": { 53 | "url": "https://github.com/johvin/eslint-import-resolver-alias/issues" 54 | }, 55 | "homepage": "https://github.com/johvin/eslint-import-resolver-alias#readme" 56 | } 57 | -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | const moduleDir = 'test/node_modules'; 7 | const files = [ 8 | `${moduleDir}/module1/index.js`, 9 | `${moduleDir}/module1/abc.js`, 10 | `${moduleDir}/module1/happy.ts`, 11 | `${moduleDir}/module1/no_extension_file`, 12 | `${moduleDir}/module1/unsupported_extension.py`, 13 | `${moduleDir}/module2/styles/red.js`, 14 | `${moduleDir}/module2/smile.js`, 15 | `${moduleDir}/module2/nav.jsx`, 16 | `${moduleDir}/polyfill2/polyfill.min.js`, 17 | `${moduleDir}/mod/a.js` 18 | ]; 19 | 20 | files.forEach(file => { 21 | createEmptyFile(file); 22 | }); 23 | // 测试带有 node_modules 的路径的 resolver 24 | fs.writeFileSync( 25 | files[files.length - 1], 26 | ` 27 | var resolver = require('../../..'); 28 | exports.abc = resolver.resolve('module1/abc', __filename); 29 | ` 30 | ); 31 | 32 | function createEmptyFile (file) { 33 | if (fs.existsSync(file)) { 34 | return fs.writeFileSync(file, ''); 35 | } 36 | 37 | const pathArr = file.split('/'); 38 | // 去掉文件名,只保留文件夹名 39 | pathArr.pop(); 40 | 41 | let p = ''; 42 | 43 | while(pathArr.length > 0) { 44 | p = path.join(p, pathArr.shift()); 45 | if (fs.existsSync(p)) { 46 | continue; 47 | } 48 | fs.mkdirSync(p); 49 | } 50 | 51 | fs.writeFileSync(file, ''); 52 | } -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | const resolver = require('..'); 2 | const assert = require('assert'); 3 | 4 | const builtinModules = module.constructor.builtinModules || require('builtin-modules'); 5 | 6 | describe('resolver-alias/index.js', () => { 7 | const sourceFile = module.filename; 8 | const alias = { 9 | map: [ 10 | ['polyfill', 'polyfill2/polyfill.min.js'], 11 | ['module3/heihei', 'module2/smile'], 12 | ['^core$', './dist/core'], 13 | ['core', 'module2/styles'], 14 | ['red', './nothing'], // should not impact the paths which contain red and not starts with red 15 | ['module3', 'module2'], 16 | ['srcCore', './core'], 17 | ['relativeSetup', './test/setup'] 18 | ], 19 | extensions: ['.js', '.ts', '.jsx', '.json'] 20 | }; 21 | 22 | const normalModulePathArr = [ 23 | 'module1', 24 | 'module1/abc', 25 | 'module1/happy', 26 | 'module1/no_extension_file', 27 | '../package', 28 | './test', 29 | 'mocha' 30 | ]; 31 | const UnsetExtensionPathArr = [ 32 | 'module1/unsupported_extension' 33 | ]; 34 | const aliasModulePathArr = [ 35 | 'module3/heihei', 36 | 'module3/styles/red', 37 | 'module3/nav', 38 | 'polyfill', 39 | 'core/red', 40 | 'core', 41 | ]; 42 | const aliasModulePathArrRelativeToProjectRootDir = [ 43 | 'srcCore', 44 | 'relativeSetup' 45 | ]; 46 | const noneExistModulePathArr = [ 47 | 'abc/ggg', 48 | 'module2/bye', 49 | 'module33', 50 | './test.json' 51 | ]; 52 | 53 | beforeEach(() => { 54 | // empty Module path cache 55 | const pathCache = module.constructor._pathCache; 56 | Object.keys(pathCache).forEach(function (key) { 57 | delete this[key]; 58 | }, pathCache); 59 | }); 60 | 61 | it('resolve Node.js builtin modules', () => { 62 | builtinModules.forEach((m) => { 63 | const resolveModule = resolver.resolve(m, sourceFile, alias); 64 | assert(resolveModule.found, `builtin module '${m}' isn't resolved`); 65 | }); 66 | }); 67 | 68 | it('resolve modules with custom file extensions', () => { 69 | normalModulePathArr.forEach((p) => { 70 | const resolveModule = resolver.resolve(p, sourceFile, alias); 71 | assert(resolveModule.found, `modulePath ${p} isn't resolved`); 72 | }); 73 | }); 74 | 75 | it('resolve normal modules which end with native support file extensions without custom file extensions', () => { 76 | const alias2 = Object.assign({}, alias, { 77 | extensions: [] 78 | }); 79 | normalModulePathArr.forEach((p) => { 80 | const resolveModule = resolver.resolve(p, sourceFile, alias.map); 81 | // happy.ts 82 | if (p.indexOf('happy') !== -1) { 83 | assert(!resolveModule.found, `modulePath ${p} with custom file extension is resolved`); 84 | } else { 85 | assert(resolveModule.found, `normal modulePath ${p} isn't resolved`); 86 | } 87 | 88 | const resolveModule2 = resolver.resolve(p, sourceFile, alias2); 89 | // happy.ts 90 | if (p.indexOf('happy') !== -1) { 91 | assert(!resolveModule2.found, `modulePath ${p} with custom file extension is resolved`); 92 | } else { 93 | assert(resolveModule2.found, `normal modulePath ${p} isn't resolved`); 94 | } 95 | 96 | }); 97 | }); 98 | 99 | it('unable to resolve the modules which end with native support file extensions with custom file extensions which do not contains the native support file extensions', () => { 100 | const alias2 = Object.assign({}, alias, { 101 | extensions: ['.ts'] 102 | }); 103 | 104 | normalModulePathArr.forEach((p) => { 105 | const resolveModule = resolver.resolve(p, sourceFile, alias2); 106 | if (/(happy|no_extension_file)$/.test(p)) { 107 | assert(resolveModule.found, `modulePath ${p} with custom file extension isn't resolved`); 108 | } else { 109 | assert(!resolveModule.found, `normal modulePath ${p} with custom file extensions which do not contains the native support file extensions is resolved`); 110 | } 111 | }); 112 | }); 113 | 114 | it('unable to resolve the modules with unset file extension', () => { 115 | UnsetExtensionPathArr.forEach((p) => { 116 | const resolveModule = resolver.resolve(p, sourceFile, alias); 117 | assert(!resolveModule.found, `modulePath ${p} with unset file extension is resolved`); 118 | }); 119 | }); 120 | 121 | it('resolve alias modules', () => { 122 | aliasModulePathArr.forEach((p) => { 123 | const resolveModule = resolver.resolve(p, sourceFile, alias); 124 | assert(resolveModule.found, `alias modulePath ${p} isn't resolved`); 125 | }); 126 | }); 127 | 128 | it('resolve alias modules which are relative to the project root directory', () => { 129 | aliasModulePathArrRelativeToProjectRootDir.forEach((p) => { 130 | const resolveModule = resolver.resolve(p, sourceFile, alias); 131 | assert(resolveModule.found, `alias modulePath ${p} isn't resolved`); 132 | }); 133 | }); 134 | 135 | it('unable to resolve the modules that do not exist', () => { 136 | noneExistModulePathArr.forEach((p) => { 137 | const resolveModule = resolver.resolve(p, sourceFile, alias); 138 | assert(!resolveModule.found, `none exist modulePath ${p} is resolved`); 139 | }); 140 | }); 141 | 142 | it('change current working directory into sub directory of project and resolve exists modules', () => { 143 | process.chdir('test'); 144 | delete require.cache[require.resolve('..')]; 145 | const newResolver = require('..'); 146 | 147 | normalModulePathArr.forEach((p) => { 148 | const resolveModule = newResolver.resolve(p, sourceFile, alias); 149 | assert(resolveModule.found, `normal modulePath ${p} isn't resolved`); 150 | }); 151 | }); 152 | 153 | it('resolve a module which located in parent node_modules directory', () => { 154 | const a = require('mod/a'); 155 | assert(a.abc.found && a.abc.path != null, 'exist module is not resolved'); 156 | }); 157 | 158 | }); 159 | --------------------------------------------------------------------------------