├── .editorconfig ├── .gitignore ├── .npmignore ├── README.md ├── example ├── index.js ├── package.json └── src │ └── math │ ├── sub.js │ └── sum.js ├── global-require.js ├── package.json └── src ├── __tests__ └── resolveConflict-test.js ├── fetchAllFiles.js ├── findPotentialConflictList.js ├── generateGlobalMap.js └── resolveConflict.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | output.js 4 | npm-debug.log 5 | example/transform.js 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | __tests__ 2 | example/ 3 | example-babelrc/ 4 | example-webpack/ 5 | README.md 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## `babel-plugin-global-require` [![npm version](https://badge.fury.io/js/babel-plugin-global-require.svg)](http://badge.fury.io/js/babel-plugin-global-require) 2 | 3 | ### Installation 4 | ``` 5 | npm install --save-dev babel-plugin-global-require 6 | ``` 7 | 8 | Tell the plugin where's your *root* (.babelrc): 9 | 10 | ```JSON 11 | { 12 | "plugins": [ 13 | ["global-require", { 14 | "root": "src" 15 | }] 16 | ] 17 | } 18 | ``` 19 | 20 | ### What are the benefits? 21 | - You'll use the same require statement from anywhere in your project; 22 | - You'll avoid path hell `../../../../..`; 23 | - You'll write a more concise code. 24 | 25 | This plugin is convention based: the alias is always the name of the file. For example: 26 | ``` 27 | src (root) 28 | util 29 | queue 30 | InMemoryAcknowledgingQueue.js 31 | PriorityQueue.js 32 | CallbackQueue.js 33 | ... 34 | io 35 | NuclearEventEmitter.js 36 | user 37 | UserActions.js 38 | UserStore.js 39 | security 40 | authorization 41 | rbac 42 | Role.js 43 | ... 44 | ``` 45 | 46 | Then: 47 | ```JS 48 | import NuclearEventEmitter from 'NuclearEventEmitter' 49 | 50 | // (...) 51 | import UserStore from 'UserStore' 52 | 53 | // require 54 | var Role = require('Role') 55 | ``` 56 | 57 | It will translate `'NuclearEventEmitter'` to `src/io/NuclearEventEmitter.js` for you. And the same happens to `UserStore` and `Role`. 58 | 59 | #### Requiring `index.js` by parent folder name is supported! 60 | It is common to require an `index.js` file by the name of its parent directory, for example: 61 | 62 | src 63 | security 64 | authorisation 65 | index.js 66 | 67 | Instead of: 68 | 69 | ```JS 70 | require('./security/authorisation') 71 | ``` 72 | 73 | You'll write: 74 | 75 | ```JS 76 | require('authorisation') 77 | ``` 78 | 79 | ### But what about conflicts? 80 | Given the following structure: 81 | ``` 82 | src 83 | security 84 | authorization 85 | rbac 86 | hasAccessTo.js 87 | acl 88 | hasAccessTo.js 89 | ``` 90 | 91 | You can't require `hasAccessTo.js` only by its name because it would result in a require of undesired file. So for this specific case, the conflict is resolved simply by going up one directory (and it keeps going until there's no conflict): 92 | 93 | ```JS 94 | import { hasAccessTo as ... } from 'rbac/hasAccessTo' 95 | import { hasAccessTo as ... } from 'acl/hasAccessTo' 96 | ``` 97 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | import sub from 'sub' 2 | import sum from 'sum' 3 | 4 | console.log(sub(10, 5)) 5 | console.log(sum(10, 10)) 6 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "babel": "^6.3.26", 4 | "babel-core": "^6.4.5", 5 | "babel-plugin-add-module-exports": "^0.1.2", 6 | "babel-plugin-transform-runtime": "^6.5.2", 7 | "babel-preset-es2015": "^6.5.0" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/src/math/sub.js: -------------------------------------------------------------------------------- 1 | export default function sub(x, y) { 2 | return x - y 3 | } 4 | -------------------------------------------------------------------------------- /example/src/math/sum.js: -------------------------------------------------------------------------------- 1 | export default function sum(x, y) { 2 | return x + y 3 | } 4 | -------------------------------------------------------------------------------- /global-require.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var path = require('path') 3 | var generateGlobalMap = require('./src/generateGlobalMap.js') 4 | 5 | module.exports = function(babel) { 6 | var t = babel.types 7 | var globalMap = {} 8 | 9 | return { 10 | visitor: { 11 | Program(_path, state) { 12 | var opts = state.opts 13 | 14 | if (opts.exclude && opts.exclude != '') { 15 | opts.exclude = new RegExp(opts.exclude) 16 | } 17 | 18 | var node_modules 19 | = opts.node_modules ? path.resolve(opts.node_modules) : null 20 | 21 | if (opts.root) { 22 | globalMap = generateGlobalMap( 23 | path.resolve(opts.root), 24 | node_modules, 25 | opts.exclude 26 | ) 27 | } 28 | }, 29 | ImportDeclaration(path, parent) { 30 | var what = path.get('source').node.value 31 | 32 | if (globalMap[what]) { 33 | path.get('source').node.value = globalMap[what].path 34 | } 35 | }, 36 | CallExpression(path, state) { 37 | var callee = path.get('callee') 38 | 39 | var isRequire = t.isIdentifier(callee.node, { 40 | name: 'require' 41 | }) 42 | 43 | if (! isRequire) { 44 | return 45 | } 46 | 47 | var args = path.get('arguments') 48 | 49 | if (args && args.length === 0) { 50 | return 51 | } 52 | 53 | if (t.isStringLiteral(args[0])) { 54 | var what = args[0].node.value 55 | 56 | if (! globalMap[what]) { 57 | return 58 | } 59 | 60 | path.replaceWith( 61 | t.callExpression(callee.node, [ 62 | t.stringLiteral(globalMap[what].path) 63 | ]) 64 | ) 65 | 66 | return 67 | } 68 | 69 | if (! t.isArrayExpression(args[0]) && t.isFunctionExpression(args[1])) { 70 | return 71 | } 72 | 73 | if (! args[0].node.elements) { 74 | return 75 | } 76 | 77 | // TODO: path.replaceWith(...) 78 | path.node.arguments[0] = t.arrayExpression( 79 | args[0].node.elements.map(node => { 80 | return t.stringLiteral( 81 | globalMap[node.value] ? globalMap[node.value].path : what 82 | ) 83 | }) 84 | ) 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-plugin-global-require", 3 | "version": "1.1.0", 4 | "description": "A simple plugin that allows you to require globally", 5 | "main": "global-require.js", 6 | "scripts": { 7 | "test": "mocha $npm_package_options_mocha" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/CentaurWarchief/babel-plugin-global-require" 12 | }, 13 | "keywords": [ 14 | "babel", 15 | "plugin", 16 | "global", 17 | "require", 18 | "import" 19 | ], 20 | "author": "Andrey K. Vital ", 21 | "license": "MIT", 22 | "devDependencies": { 23 | "chai": "^3.3.0", 24 | "mocha": "^2.3.3" 25 | }, 26 | "options": { 27 | "mocha": "src/__tests__/**/*-test.js -R spec" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/__tests__/resolveConflict-test.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai') 2 | var resolveConflict = require('../resolveConflict') 3 | var path = require('path') 4 | 5 | var expect = chai.expect 6 | var sep = path.sep 7 | 8 | function p(path) { 9 | return path.replace(/\//g, sep); 10 | } 11 | 12 | describe('resolveConflict', function() { 13 | it('resolves conflict respecting dirname', function() { 14 | expect( 15 | resolveConflict([ 16 | { 17 | name: 'sum', 18 | path: p('src/deep/sum.js') 19 | }, { 20 | name: 'sum', 21 | path: p('src/deep/deep/sum.js') 22 | }, { 23 | name: 'divide', 24 | path: p('src/deep/deep/divide.js') 25 | }, { 26 | name: 'index', 27 | path: p('src/deep/deep/divide/index.js') 28 | } 29 | ]) 30 | ).to.eql([ 31 | { 32 | name: 'deep/sum', 33 | path: p('src/deep/sum.js') 34 | }, { 35 | name: 'deep/deep/sum', 36 | path: p('src/deep/deep/sum.js') 37 | }, 38 | { 39 | name: 'deep/divide', 40 | path: p('src/deep/deep/divide.js') 41 | }, 42 | { 43 | name: 'deep/deep/divide', 44 | path: p('src/deep/deep/divide/index.js') 45 | } 46 | ]) 47 | }) 48 | 49 | it('resolves the conflict with the minimal required path to file', function() { 50 | expect( 51 | resolveConflict([ 52 | { 53 | name: 'conflict', 54 | path: p('src/foo/deep/conflict.js') 55 | }, { 56 | name: 'conflict', 57 | path: p('src/foo/deep/bar/conflict.js') 58 | }, { 59 | name: 'conflict', 60 | path: p('src/foo/bar/deep/deep/conflict.js') 61 | }, { 62 | name: 'index', 63 | path: p('src/foo/bar/baz/deep/deep/conflict/index.js') 64 | } 65 | ]) 66 | ).to.eql([ 67 | { 68 | name: 'deep/conflict', 69 | path: p('src/foo/deep/conflict.js') 70 | }, { 71 | name: 'bar/conflict', 72 | path: p('src/foo/deep/bar/conflict.js') 73 | }, { 74 | name: 'deep/deep/conflict', 75 | path: p('src/foo/bar/deep/deep/conflict.js') 76 | }, { 77 | name: 'baz/deep/deep/conflict', 78 | path: p('src/foo/bar/baz/deep/deep/conflict/index.js') 79 | } 80 | ]) 81 | }) 82 | 83 | it('resolves the conflict of multiple files', function() { 84 | expect( 85 | resolveConflict([ 86 | { 87 | name: 'sum', 88 | path: p('src/math/sum.js') 89 | }, { 90 | name: 'sum', 91 | path: p('src/deep/math/sum.js') 92 | }, { 93 | name: 'sum', 94 | path: p('src/foo/cool/math/sum.js') 95 | }, { 96 | name: 'index', 97 | path: p('src/foo/cool/math/sum/index.js') 98 | }, { 99 | name: 'div', 100 | path: p('src/math/div.js') 101 | }, { 102 | name: 'div', 103 | path: p('src/deep/math/div.js') 104 | }, { 105 | name: 'div', 106 | path: p('src/foo/cool/math/div.js') 107 | } 108 | ]) 109 | ).to.eql([ 110 | { 111 | name: 'math/sum', 112 | path: p('src/math/sum.js') 113 | }, { 114 | name: 'deep/math/sum', 115 | path: p('src/deep/math/sum.js') 116 | }, { 117 | name: 'cool/math/sum', 118 | path: p('src/foo/cool/math/sum.js') 119 | }, { 120 | name: 'foo/cool/math/sum', 121 | path: p('src/foo/cool/math/sum/index.js') 122 | }, { 123 | name: 'math/div', 124 | path: p('src/math/div.js') 125 | }, { 126 | name: 'deep/math/div', 127 | path: p('src/deep/math/div.js') 128 | }, { 129 | name: 'cool/math/div', 130 | path: p('src/foo/cool/math/div.js') 131 | } 132 | ]) 133 | }) 134 | 135 | it('takes in consideration the given "conflict map"', function() { 136 | expect( 137 | resolveConflict( 138 | [ 139 | { 140 | name: 'sum', 141 | path: p('src/sum.js') 142 | }, { 143 | name: 'index', 144 | path: p('src/sum/index.js') 145 | }, { 146 | name: 'index', 147 | path: p('src/bar/divide/index.js') 148 | } 149 | ], 150 | ['sum', 'divide'] 151 | ) 152 | ).to.eql([ 153 | { 154 | name: 'bar/divide', 155 | path: p('src/bar/divide/index.js') 156 | }, { 157 | name: 'src/sum', 158 | path: p('src/sum.js') 159 | }, { 160 | name: 'sum', 161 | path: p('src/sum/index.js') 162 | } 163 | ]) 164 | }) 165 | 166 | it('resolves conflicts with multiple index.js', function() { 167 | expect( 168 | resolveConflict([ 169 | { 170 | name: 'index', 171 | path: p('src/deep/sum/index.js') 172 | }, { 173 | name: 'index', 174 | path: p('src/deep/deep/sum/index.js') 175 | }, { 176 | name: 'index', 177 | path: p('src/deep/deep/divide/index.js') 178 | } 179 | ]) 180 | ).to.eql([ 181 | { 182 | name: 'divide', 183 | path: p('src/deep/deep/divide/index.js') 184 | }, { 185 | name: 'deep/sum', 186 | path: p('src/deep/sum/index.js') 187 | }, { 188 | name: 'deep/deep/sum', 189 | path: p('src/deep/deep/sum/index.js') 190 | } 191 | ]) 192 | }) 193 | }) 194 | -------------------------------------------------------------------------------- /src/fetchAllFiles.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var path = require('path') 3 | 4 | /** 5 | * @param {String} root 6 | * @param {RegExp} exclude 7 | * @returns {String[]} 8 | */ 9 | module.exports = function fetchAllFiles(root, exclude) { 10 | var results = [] 11 | 12 | fs.readdirSync(root).filter(function(file) { 13 | return exclude && exclude.test(file) ? false : true 14 | }).forEach(function(file) { 15 | var absolute = path.join(root, file) 16 | var stat = fs.statSync(absolute) 17 | 18 | if (stat.isDirectory()) { 19 | results = results.concat(fetchAllFiles(absolute)) 20 | return 21 | } 22 | 23 | results.push(absolute) 24 | }) 25 | 26 | return results 27 | } 28 | -------------------------------------------------------------------------------- /src/findPotentialConflictList.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var path = require('path') 3 | 4 | /** 5 | * "node_modules": "package.json" (devDependencies + dependencies) 6 | * "node_modules": "node_modules" (shallow scan) 7 | * "node_modules": ["module", "module", "module"] 8 | * 9 | * @param {?String|String[]} node_modules 10 | * @returns {String[]} 11 | */ 12 | module.exports = function findPotentialConflictList(node_modules) { 13 | if (! node_modules) { 14 | return [] 15 | } 16 | 17 | if (node_modules.constructor.name === 'Array') { 18 | return node_modules 19 | } 20 | 21 | if (/package\.json/.test(node_modules)) { 22 | try { 23 | var packageJson = JSON.parse(fs.readFileSync(node_modules)) 24 | 25 | var devDependencies = packageJson.devDependencies || [] 26 | var dependencies = packageJson.dependencies || [] 27 | 28 | return [].concat( 29 | Object.keys(devDependencies), 30 | Object.keys(dependencies) 31 | ) 32 | } catch (e) { 33 | return [] 34 | } 35 | } 36 | 37 | return fs.readdirSync(node_modules).filter(function(file) { 38 | return fs.statSync(path.join(node_modules, file)).isDirectory() 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /src/generateGlobalMap.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var path = require('path') 3 | var resolveConflict = require('./resolveConflict') 4 | var findPotentialConflictList = require('./findPotentialConflictList') 5 | var fetchAllFiles = require('./fetchAllFiles') 6 | 7 | function generateGlobalMap( 8 | root, 9 | node_modules, 10 | exclude 11 | ) { 12 | return resolveConflict( 13 | fetchAllFiles(root, exclude).map(function(file) { 14 | return { 15 | path: file, 16 | name: path.basename(file, path.extname(file)) 17 | } 18 | }), 19 | findPotentialConflictList(node_modules) 20 | ).reduce( 21 | function(previous, current) { 22 | if (! previous[current.name]) { 23 | previous[current.name] = current 24 | } 25 | 26 | return previous 27 | }, 28 | {} 29 | ) 30 | } 31 | 32 | module.exports = generateGlobalMap 33 | -------------------------------------------------------------------------------- /src/resolveConflict.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | 3 | /** 4 | * @param {Object[]} map 5 | * @param {String[]} [potentialConflictMap] 6 | * @returns {Object[]} 7 | */ 8 | module.exports = function resolveConflict( 9 | map, 10 | potentialConflictMap 11 | ) { 12 | var known = findConflictsInMap(fixIndexImportsInMap(map)) 13 | var resolved = resolveConflictsInMap(known) 14 | 15 | if (potentialConflictMap && potentialConflictMap.length) { 16 | var resolvedNames = resolved.map(function(candidate) { 17 | return candidate.name 18 | }) 19 | 20 | var conflictNames = potentialConflictMap.filter(function(name) { 21 | return resolvedNames.indexOf(name) === -1 22 | }) 23 | 24 | var unresolved = map.filter(function(candidate) { 25 | return conflictNames.indexOf(candidate.name) !== -1 26 | }) 27 | 28 | map = resolveConflictsInMap( 29 | unresolved, 30 | resolvedNames.concat(conflictNames) 31 | ).concat(resolved) 32 | } 33 | 34 | return map.filter(function(candidate) { 35 | return known.indexOf(candidate) === -1 36 | }).concat(resolved) 37 | } 38 | 39 | /** 40 | * @param {Object[]} map 41 | * @returns {Array} 42 | */ 43 | function fixIndexImportsInMap(map) { 44 | return map.map(function(obj) { 45 | var basename = path.basename(obj.path) 46 | 47 | if (basename === 'index.js') { 48 | var moduleName = obj.path.split('.')[0].split(path.sep) 49 | obj.name = moduleName[moduleName.length - 2] 50 | } 51 | 52 | return obj 53 | }) 54 | } 55 | 56 | /** 57 | * @param {Object[]} map 58 | * @param {String[]} [reservedNames] 59 | * @returns {Object[]} 60 | */ 61 | function resolveConflictsInMap(map, reservedNames) { 62 | reservedNames = reservedNames || [] 63 | 64 | return map.map(function(current) { 65 | var previous = [] 66 | var parts = path.dirname(current.path).split(path.sep) 67 | 68 | if (path.basename(current.path) === 'index.js') { 69 | parts = parts.splice(0, parts.length - 1) 70 | } 71 | 72 | while (parts.length) { 73 | var pop = parts.pop() 74 | var proposal 75 | 76 | if (previous.length) { 77 | proposal = [pop].concat(previous, current.name).join('/') 78 | } else { 79 | proposal = pop.concat('/', current.name) 80 | } 81 | 82 | if (reservedNames.indexOf(proposal) === -1) { 83 | reservedNames.push(proposal) 84 | current.name = proposal 85 | break 86 | } 87 | 88 | previous.unshift(pop) 89 | } 90 | 91 | return current 92 | }) 93 | } 94 | 95 | /** 96 | * @param {Object[]} map 97 | * @returns {Object[]} 98 | */ 99 | function findConflictsInMap(map) { 100 | return map.filter(function(module, moduleIndex) { 101 | return map.some(function(conflict, conflictIndex) { 102 | return ( 103 | module.name === conflict.name && 104 | moduleIndex !== conflictIndex 105 | ) 106 | }) 107 | }) 108 | } 109 | --------------------------------------------------------------------------------