From ca292fea16425147c13e4394e113818bd11cabf2 Mon Sep 17 00:00:00 2001 From: Tommy Leunen Date: Sun, 14 Aug 2016 11:29:03 -0400 Subject: [PATCH 1/3] feat: Add support for custom root directories BREAKING CHANGE: There's a new way to specify alias in the plugin options. --- .editorconfig | 4 +- .eslintrc | 12 +- codecov.yml | 2 +- package.json | 14 +- src/index.js | 88 +++++------ src/mapToRelative.js | 32 ++++ test/examples/components/c1.js | 0 test/examples/components/c2.js | 0 test/examples/components/sub/sub1.js | 0 test/examples/example-file.js | 0 test/index.js | 224 +++++++++++---------------- test/mapToRelative.js | 32 ++++ 12 files changed, 211 insertions(+), 197 deletions(-) create mode 100644 src/mapToRelative.js create mode 100644 test/examples/components/c1.js create mode 100644 test/examples/components/c2.js create mode 100644 test/examples/components/sub/sub1.js create mode 100644 test/examples/example-file.js create mode 100644 test/mapToRelative.js diff --git a/.editorconfig b/.editorconfig index f65a311..a7d5374 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,5 +6,7 @@ indent_style = space indent_size = 4 [*.{json,yml}] -indent_style = space +indent_size = 2 + +[.*] indent_size = 2 diff --git a/.eslintrc b/.eslintrc index 42df829..c391ddc 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,8 +1,8 @@ { - "extends": "airbnb-base", - "rules": { - "comma-dangle": 0, - "indent": [2, 4, {"SwitchCase": 1}], - "max-len": 0, - } + "extends": "airbnb-base", + "rules": { + "comma-dangle": 0, + "indent": [2, 4, {"SwitchCase": 1}], + "max-len": 0 + } } diff --git a/codecov.yml b/codecov.yml index 8546fd2..687bb73 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,2 +1,2 @@ comment: - layout: "header, diff, tree" + layout: "header, tree" diff --git a/package.json b/package.json index fca1bf6..f381fd3 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "babel", "babel-plugin", "module", + "resolver", "alias", "rewrite", "resolve", @@ -25,19 +26,18 @@ "require", "import" ], - "dependencies": {}, "devDependencies": { "babel-cli": "^6.10.1", "babel-core": "^6.10.4", - "babel-plugin-istanbul": "^1.1.0", + "babel-plugin-istanbul": "^2.0.0", "babel-preset-es2015": "^6.6.0", "babel-register": "^6.8.0", "cross-env": "^2.0.0", - "eslint": "^3.0.1", - "eslint-config-airbnb-base": "^5.0.0", - "eslint-plugin-import": "^1.10.2", - "mocha": "^3.0.0", - "nyc": "^7.0.0", + "eslint": "^3.3.0", + "eslint-config-airbnb-base": "^5.0.2", + "eslint-plugin-import": "^1.13.0", + "mocha": "^3.0.2", + "nyc": "^8.1.0", "standard-version": "^2.4.0" }, "scripts": { diff --git a/src/index.js b/src/index.js index d7f1dcb..b6e19dc 100644 --- a/src/index.js +++ b/src/index.js @@ -1,68 +1,54 @@ -const path = require('path'); - -function createFilesMap(state) { - const result = {}; - const opts = Array.isArray(state.opts) - ? state.opts - : [state.opts]; - - opts.forEach(moduleMapData => { - result[moduleMapData.expose] = moduleMapData.src; - }); - - return result; -} - -function resolve(filename) { - if (path.isAbsolute(filename)) return filename; - return path.resolve(process.cwd(), filename); -} - -function toPosixPath(modulePath) { - return modulePath.replace(/\\/g, '/'); +import path from 'path'; +import mapToRelative from './mapToRelative'; + +function createAliasFileMap(pluginOpts) { + const alias = pluginOpts.alias || {}; + return Object.keys(alias).reduce((memo, expose) => ( + Object.assign(memo, { + [expose]: alias[expose] + }) + ), {}); } -export function mapToRelative(currentFile, module) { - let from = path.dirname(currentFile); - let to = path.normalize(module); - - from = resolve(from); - to = resolve(to); - - let moduleMapped = path.relative(from, to); - - moduleMapped = toPosixPath(moduleMapped); - - // Support npm modules instead of directories - if (moduleMapped.indexOf('npm:') !== -1) { - const [, npmModuleName] = moduleMapped.split('npm:'); - return npmModuleName; +export function mapModule(source, file, pluginOpts) { + // Do not map source starting with a dot + if (source[0] === '.') { + return null; } - if (moduleMapped[0] !== '.') moduleMapped = `./${moduleMapped}`; - - return moduleMapped; -} + // Search the file under the custom root directories + const rootDirs = pluginOpts.root || []; + for (let i = 0; i < rootDirs.length; i++) { + try { + // check if the file exists (will throw if not) + const p = path.resolve(rootDirs[i], source); + require.resolve(p); + return mapToRelative(file, p); + } catch (e) { + // empty... + } + } -export function mapModule(source, file, filesMap) { + // The source file wasn't found in any of the root directories. Lets try the alias + const aliasMapping = createAliasFileMap(pluginOpts); const moduleSplit = source.split('/'); - let src; + let aliasPath; while (moduleSplit.length) { const m = moduleSplit.join('/'); - if ({}.hasOwnProperty.call(filesMap, m)) { - src = filesMap[m]; + if ({}.hasOwnProperty.call(aliasMapping, m)) { + aliasPath = aliasMapping[m]; break; } moduleSplit.pop(); } - if (!moduleSplit.length) { - // no mapping available + // no alias mapping found + if (!aliasPath) { return null; } - const newPath = source.replace(moduleSplit.join('/'), src); + const newPath = source.replace(moduleSplit.join('/'), aliasPath); return mapToRelative(file, newPath); } @@ -81,8 +67,7 @@ export default ({ types: t }) => { const moduleArg = nodePath.node.arguments[0]; if (moduleArg && moduleArg.type === 'StringLiteral') { - const filesMap = createFilesMap(state); - const modulePath = mapModule(moduleArg.value, state.file.opts.filename, filesMap); + const modulePath = mapModule(moduleArg.value, state.file.opts.filename, state.opts); if (modulePath) { nodePath.replaceWith(t.callExpression( nodePath.node.callee, [t.stringLiteral(modulePath)] @@ -94,8 +79,7 @@ export default ({ types: t }) => { function transformImportCall(nodePath, state) { const moduleArg = nodePath.node.source; if (moduleArg && moduleArg.type === 'StringLiteral') { - const filesMap = createFilesMap(state); - const modulePath = mapModule(moduleArg.value, state.file.opts.filename, filesMap); + const modulePath = mapModule(moduleArg.value, state.file.opts.filename, state.opts); if (modulePath) { nodePath.replaceWith(t.importDeclaration( nodePath.node.specifiers, diff --git a/src/mapToRelative.js b/src/mapToRelative.js new file mode 100644 index 0000000..a6b74d0 --- /dev/null +++ b/src/mapToRelative.js @@ -0,0 +1,32 @@ +import path from 'path'; + +function resolve(filename) { + if (path.isAbsolute(filename)) return filename; + return path.resolve(process.cwd(), filename); +} + +function toPosixPath(modulePath) { + return modulePath.replace(/\\/g, '/'); +} + +export default function mapToRelative(currentFile, module) { + let from = path.dirname(currentFile); + let to = path.normalize(module); + + from = resolve(from); + to = resolve(to); + + let moduleMapped = path.relative(from, to); + + moduleMapped = toPosixPath(moduleMapped); + + // Support npm modules instead of directories + if (moduleMapped.indexOf('npm:') !== -1) { + const [, npmModuleName] = moduleMapped.split('npm:'); + return npmModuleName; + } + + if (moduleMapped[0] !== '.') moduleMapped = `./${moduleMapped}`; + + return moduleMapped; +} diff --git a/test/examples/components/c1.js b/test/examples/components/c1.js new file mode 100644 index 0000000..e69de29 diff --git a/test/examples/components/c2.js b/test/examples/components/c2.js new file mode 100644 index 0000000..e69de29 diff --git a/test/examples/components/sub/sub1.js b/test/examples/components/sub/sub1.js new file mode 100644 index 0000000..e69de29 diff --git a/test/examples/example-file.js b/test/examples/example-file.js new file mode 100644 index 0000000..e69de29 diff --git a/test/index.js b/test/index.js index 0ca4720..a185acb 100644 --- a/test/index.js +++ b/test/index.js @@ -1,168 +1,132 @@ /* eslint-env mocha */ -import path from 'path'; import assert from 'assert'; import { transform } from 'babel-core'; // eslint-disable-line import/no-extraneous-dependencies -import plugin, { mapToRelative } from '../src'; +import plugin from '../src'; -describe('Babel plugin module alias', () => { +function testRequireImport(source, output, transformerOpts) { + it('with a require statement', () => { + const code = `var something = require("${source}");`; + const result = transform(code, transformerOpts); + + assert.strictEqual(result.code, `var something = require("${output}");`); + }); + + it('with an import statement', () => { + const code = `import something from "${source}";`; + const result = transform(code, transformerOpts); + + assert.strictEqual(result.code, `import something from "${output}";`); + }); +} + +describe('modulesDirectories', () => { const transformerOpts = { plugins: [ - [plugin, [{ - src: './src/mylib/subfolder/utils', - expose: 'utils' - }, { - src: './src/components', - expose: 'awesome/components' - }, { - src: 'npm:concrete', - expose: 'abstract' - }]] + [plugin, { + root: ['./test/examples/components'] + }] ] }; - describe('should alias a known path', () => { - describe('using a simple exposed name', () => { - describe('when requiring the exact name', () => { - it('with a require statement', () => { - const code = 'var utils = require("utils");'; - const result = transform(code, transformerOpts); + describe('should rewrite the file path inside a root directory', () => { + testRequireImport( + 'c1', + './test/examples/components/c1', + transformerOpts + ); + }); - assert.strictEqual(result.code, 'var utils = require("./src/mylib/subfolder/utils");'); - }); + describe('should rewrite the sub file path inside a root directory', () => { + testRequireImport( + 'sub/sub1', + './test/examples/components/sub/sub1', + transformerOpts + ); + }); - it('with an import statement', () => { - const code = 'import utils from "utils";'; - const result = transform(code, transformerOpts); + describe('should not rewrite a path outisde of the root directory', () => { + testRequireImport( + 'example-file', + 'example-file', + transformerOpts + ); + }); +}); - assert.strictEqual(result.code, 'import utils from "./src/mylib/subfolder/utils";'); - }); +describe('alias', () => { + const transformerOpts = { + plugins: [ + [plugin, { + alias: { + utils: './src/mylib/subfolder/utils', + 'awesome/components': './src/components', + abstract: 'npm:concrete' + } + }] + ] + }; + + describe('should alias a known path', () => { + describe('using a simple exposed name', () => { + describe('when requiring the exact name', () => { + testRequireImport( + 'utils', + './src/mylib/subfolder/utils', + transformerOpts + ); }); describe('when requiring a sub file of the exposed name', () => { - it('with a require statement', () => { - const code = 'var myUtil = require("utils/my-util-file");'; - const result = transform(code, transformerOpts); - - assert.strictEqual(result.code, 'var myUtil = require("./src/mylib/subfolder/utils/my-util-file");'); - }); - - it('with an import statement', () => { - const code = 'import myUtil from "utils/my-util-file";'; - const result = transform(code, transformerOpts); - - assert.strictEqual(result.code, 'import myUtil from "./src/mylib/subfolder/utils/my-util-file";'); - }); + testRequireImport( + 'utils/my-util-file', + './src/mylib/subfolder/utils/my-util-file', + transformerOpts + ); }); }); describe('using a "complex" exposed name', () => { describe('when requiring the exact name', () => { - it('with a require statement', () => { - const code = 'var comps = require("awesome/components");'; - const result = transform(code, transformerOpts); - - assert.strictEqual(result.code, 'var comps = require("./src/components");'); - }); - - it('with an import statement', () => { - const code = 'import comps from "awesome/components";'; - const result = transform(code, transformerOpts); - - assert.strictEqual(result.code, 'import comps from "./src/components";'); - }); + testRequireImport( + 'awesome/components', + './src/components', + transformerOpts + ); }); describe('when requiring a sub file of the exposed name', () => { - it('with a require statement', () => { - const code = 'var myComp = require("awesome/components/my-comp");'; - const result = transform(code, transformerOpts); - - assert.strictEqual(result.code, 'var myComp = require("./src/components/my-comp");'); - }); - - it('with an import statement', () => { - const code = 'import myComp from "awesome/components/my-comp";'; - const result = transform(code, transformerOpts); - - assert.strictEqual(result.code, 'import myComp from "./src/components/my-comp";'); - }); + testRequireImport( + 'awesome/components/my-comp', + './src/components/my-comp', + transformerOpts + ); }); }); }); describe('should not alias a unknown path', () => { describe('when requiring a node module', () => { - it('with a require statement', () => { - const code = 'var otherLib = require("other-lib");'; - const result = transform(code, transformerOpts); - - assert.strictEqual(result.code, 'var otherLib = require("other-lib");'); - }); - - it('with an import statement', () => { - const code = 'import otherLib from "other-lib";'; - const result = transform(code, transformerOpts); - - assert.strictEqual(result.code, 'import otherLib from "other-lib";'); - }); + testRequireImport( + 'other-lib', + 'other-lib', + transformerOpts + ); }); describe('when requiring a specific un-mapped file', () => { - it('with a require statement', () => { - const code = 'var otherLib = require("./l/otherLib");'; - const result = transform(code, transformerOpts); - - assert.strictEqual(result.code, 'var otherLib = require("./l/otherLib");'); - }); - - it('with an import statement', () => { - const code = 'import otherLib from "./l/otherLib";'; - const result = transform(code, transformerOpts); - - assert.strictEqual(result.code, 'import otherLib from "./l/otherLib";'); - }); - }); - }); - - describe('should map to relative path when cwd has been changed', () => { - const cwd = process.cwd(); - - before(() => { - process.chdir('./test'); - }); - - after(() => { - process.chdir(cwd); - }); - - it('with relative filename', () => { - const currentFile = './utils/test/file.js'; - const result = mapToRelative(currentFile, 'utils/dep'); - - assert.strictEqual(result, '../dep'); - }); - - it('with absolute filename', () => { - const currentFile = path.join(process.cwd(), './utils/test/file.js'); - const result = mapToRelative(currentFile, 'utils/dep'); - - assert.strictEqual(result, '../dep'); + testRequireImport( + './l/otherLib', + './l/otherLib', + transformerOpts + ); }); }); describe('should support remapping to node modules with "npm:"', () => { - it('with a require statement', () => { - const code = 'var concrete = require("abstract/thing");'; - const result = transform(code, transformerOpts); - - assert.strictEqual(result.code, 'var concrete = require("concrete/thing");'); - }); - - it('with an import statement', () => { - const code = 'import concrete from "abstract/thing";'; - const result = transform(code, transformerOpts); - - assert.strictEqual(result.code, 'import concrete from "concrete/thing";'); - }); + testRequireImport( + 'abstract/thing', + 'concrete/thing', + transformerOpts + ); }); }); diff --git a/test/mapToRelative.js b/test/mapToRelative.js new file mode 100644 index 0000000..1916fbf --- /dev/null +++ b/test/mapToRelative.js @@ -0,0 +1,32 @@ +/* eslint-env mocha */ +import path from 'path'; +import assert from 'assert'; +import mapToRelative from '../src/mapToRelative'; + +describe('mapToRelative', () => { + describe('should map to relative path when cwd has been changed', () => { + const cwd = process.cwd(); + + before(() => { + process.chdir('./test'); + }); + + after(() => { + process.chdir(cwd); + }); + + it('with relative filename', () => { + const currentFile = './utils/test/file.js'; + const result = mapToRelative(currentFile, 'utils/dep'); + + assert.strictEqual(result, '../dep'); + }); + + it('with absolute filename', () => { + const currentFile = path.join(process.cwd(), './utils/test/file.js'); + const result = mapToRelative(currentFile, 'utils/dep'); + + assert.strictEqual(result, '../dep'); + }); + }); +}); From bbea46c4ec2bf04d3952dbd52307555d21e69a54 Mon Sep 17 00:00:00 2001 From: Tommy Leunen Date: Sun, 14 Aug 2016 13:33:43 -0400 Subject: [PATCH 2/3] Update readme with new name --- README.md | 52 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 2ab2732..35eacf0 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,19 @@ -# babel-plugin-module-alias +# babel-plugin-module-resolver [![npm][npm-version-image]][npm-url] [![Build Status][ci-image]][ci-url] [![Coverage Status][coverage-image]][coverage-url] -A [babel](http://babeljs.io) plugin to rewrite (map, alias, resolve) directories as different directories during the Babel process. It's particularly useful when you have files you don't want to use with relative paths (especially in big projects). +A [babel](http://babeljs.io) plugin to add a new resolver for your modules when compiling your code using Babel. The plugin allows you to add new "root" directories that contains your modules. It also allows your to setup custom alias which can also be directories or specific files, or even other npm modules. ## Description -Instead of using relative paths in your project, you'll be able to use an alias. Here an simple example: +The reason of this plugin is to simplify the require/import paths in your project. Therefore, instead of using complex relative paths like `../../../../utils/my-utils`, you would be able to write `utils/my-utils`. It will allow you to work faster since you won't need to calculate how many levels of directory you have to go up before accessing the file. + +Here's a full example: ```js // Instead of using this; import MyUtilFn from '../../../../utils/MyUtilFn'; // Use that: import MyUtilFn from 'utils/MyUtilFn'; ``` -With this plugin, you'll be able to map files or directories to the path you want. _Note:_ It also work for `require()`. @@ -24,34 +25,45 @@ _Note 2:_ You can use the `npm:` prefix in your plugin configuration to map a no Install the plugin ``` -$ npm install --save-dev babel babel-plugin-module-alias +$ npm install --save-dev babel-plugin-module-resolver ``` -Specify the plugin in your `.babelrc` with the custom mapping. + +Specify the plugin in your `.babelrc` with the custom root or alias. Here's an example: ```json { "plugins": [ - ["module-alias", [ - { "src": "./src/utils", "expose": "utils" }, - { "src": "./src/components", "expose": "awesome/components" }, - { "src": "npm:lodash", "expose": "underscore" } - ]] - ] + "transform-object-rest-spread", + ["module-resolver", { + "root": ["./src"], + "alias": { + "test": "./test" + } + }] + ] } ``` -If you're using [eslint-plugin-import][eslint-plugin-import], you should use [eslint-import-resolver-babel-module-alias][resolver-module-alias] to avoid having false errors. +## ESLint plugin + +If you're using ESLint, you should use the [eslint-plugin-import][eslint-plugin-import], and this [eslint-import-resolver-babel-module-resolver][eslint-import-resolver-babel-module-resolver] in order to remove falsy unresolved modules. + +## Editors autocompletion + +- Atom: Uses [atom-autocomplete-modules][atom-autocomplete-modules] and enable the `babel-plugin-module-resolver` option. +- IntelliJ/WebStorm: You can add custom resources root directories, make sure it matches what you have in this plugin. ## License MIT, see [LICENSE.md](/LICENSE.md) for details. -[ci-image]: https://circleci.com/gh/tleunen/babel-plugin-module-alias.svg?style=shield -[ci-url]: https://circleci.com/gh/tleunen/babel-plugin-module-alias -[coverage-image]: https://codecov.io/gh/tleunen/babel-plugin-module-alias/branch/master/graph/badge.svg -[coverage-url]: https://codecov.io/gh/tleunen/babel-plugin-module-alias -[npm-version-image]: https://img.shields.io/npm/v/babel-plugin-module-alias.svg -[npm-url]: https://www.npmjs.com/package/babel-plugin-module-alias -[resolver-module-alias]: https://github.com/tleunen/eslint-import-resolver-babel-module-alias +[ci-image]: https://circleci.com/gh/tleunen/babel-plugin-module-resolver.svg?style=shield +[ci-url]: https://circleci.com/gh/tleunen/babel-plugin-module-resolver +[coverage-image]: https://codecov.io/gh/tleunen/babel-plugin-module-resolver/branch/master/graph/badge.svg +[coverage-url]: https://codecov.io/gh/tleunen/babel-plugin-module-resolver +[npm-version-image]: https://img.shields.io/npm/v/babel-plugin-module-resolver.svg +[npm-url]: https://www.npmjs.com/package/babel-plugin-module-resolver +[eslint-import-resolver-babel-module-resolver]: https://github.com/tleunen/eslint-import-resolver-babel-module-resolver [eslint-plugin-import]: https://github.com/benmosher/eslint-plugin-import +[atom-autocomplete-modules]: https://github.com/nkt/atom-autocomplete-modules From e77369c343f29f52054f6b2565cee98251f9bf17 Mon Sep 17 00:00:00 2001 From: Tommy Leunen Date: Sun, 14 Aug 2016 13:34:50 -0400 Subject: [PATCH 3/3] Update package.json with new name --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index f381fd3..444ef24 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { - "name": "babel-plugin-module-alias", + "name": "babel-plugin-module-resolver", "version": "1.6.0", "main": "lib/index.js", "description": "Babel plugin to rewrite the path in require() and ES6 import", "repository": { "type": "git", - "url": "https://github.com/tleunen/babel-plugin-module-alias.git" + "url": "https://github.com/tleunen/babel-plugin-module-resolver.git" }, "author": { "name": "Tommy Leunen",