├── .babelrc ├── .eslintrc.json ├── .github ├── dependabot.yml └── workflows │ └── npm-publish.yml ├── .gitignore ├── README.md ├── package-lock.json ├── package.json └── src ├── plugin.es5.js └── plugin.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | "plugins": ["add-module-exports"] 4 | } 5 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "parserOptions": { 8 | "sourceType": "module" 9 | }, 10 | "rules": { 11 | "accessor-pairs": "error", 12 | "array-bracket-newline": "off", 13 | "array-bracket-spacing": [ 14 | "error", 15 | "never" 16 | ], 17 | "array-callback-return": "error", 18 | "array-element-newline": "off", 19 | "arrow-body-style": "error", 20 | "arrow-parens": "off", 21 | "arrow-spacing": [ 22 | "error", 23 | { 24 | "after": true, 25 | "before": true 26 | } 27 | ], 28 | "block-scoped-var": "error", 29 | "block-spacing": "error", 30 | "brace-style": [ 31 | "error", 32 | "1tbs" 33 | ], 34 | "callback-return": "error", 35 | "camelcase": "error", 36 | "capitalized-comments": "off", 37 | "class-methods-use-this": "off", 38 | "comma-dangle": "error", 39 | "comma-spacing": [ 40 | "error", 41 | { 42 | "after": true, 43 | "before": false 44 | } 45 | ], 46 | "comma-style": [ 47 | "error", 48 | "last" 49 | ], 50 | "complexity": "error", 51 | "computed-property-spacing": [ 52 | "error", 53 | "never" 54 | ], 55 | "consistent-return": "error", 56 | "consistent-this": "error", 57 | "curly": "off", 58 | "default-case": "error", 59 | "dot-location": "error", 60 | "dot-notation": "error", 61 | "eol-last": "error", 62 | "eqeqeq": "error", 63 | "for-direction": "error", 64 | "func-call-spacing": "error", 65 | "func-name-matching": "error", 66 | "func-names": [ 67 | "error", 68 | "never" 69 | ], 70 | "func-style": [ 71 | "error", 72 | "declaration" 73 | ], 74 | "function-paren-newline": "error", 75 | "generator-star-spacing": "error", 76 | "getter-return": "error", 77 | "global-require": "error", 78 | "guard-for-in": "error", 79 | "handle-callback-err": "error", 80 | "id-blacklist": "error", 81 | "id-length": "off", 82 | "id-match": "error", 83 | "implicit-arrow-linebreak": [ 84 | "error", 85 | "beside" 86 | ], 87 | "indent": "off", 88 | "indent-legacy": "off", 89 | "init-declarations": "off", 90 | "jsx-quotes": "error", 91 | "key-spacing": "error", 92 | "keyword-spacing": [ 93 | "error", 94 | { 95 | "after": true, 96 | "before": true 97 | } 98 | ], 99 | "line-comment-position": "error", 100 | "linebreak-style": [ 101 | "error", 102 | "unix" 103 | ], 104 | "lines-around-comment": "off", 105 | "lines-around-directive": "error", 106 | "lines-between-class-members": [ 107 | "error", 108 | "always" 109 | ], 110 | "max-depth": "error", 111 | "max-len": "off", 112 | "max-lines": "error", 113 | "max-nested-callbacks": "error", 114 | "max-params": "error", 115 | "max-statements": "error", 116 | "max-statements-per-line": "error", 117 | "multiline-comment-style": [ 118 | "error", 119 | "separate-lines" 120 | ], 121 | "new-cap": "error", 122 | "new-parens": "error", 123 | "newline-after-var": "off", 124 | "newline-before-return": "off", 125 | "newline-per-chained-call": "error", 126 | "no-alert": "error", 127 | "no-array-constructor": "error", 128 | "no-await-in-loop": "error", 129 | "no-bitwise": "error", 130 | "no-buffer-constructor": "error", 131 | "no-caller": "error", 132 | "no-catch-shadow": "error", 133 | "no-confusing-arrow": "error", 134 | "no-continue": "error", 135 | "no-div-regex": "error", 136 | "no-duplicate-imports": "error", 137 | "no-else-return": "error", 138 | "no-empty-function": "error", 139 | "no-eq-null": "error", 140 | "no-eval": "error", 141 | "no-extend-native": "error", 142 | "no-extra-bind": "error", 143 | "no-extra-label": "error", 144 | "no-extra-parens": "error", 145 | "no-floating-decimal": "error", 146 | "no-implicit-coercion": "error", 147 | "no-implicit-globals": "error", 148 | "no-implied-eval": "error", 149 | "no-inline-comments": "error", 150 | "no-invalid-this": "error", 151 | "no-iterator": "error", 152 | "no-label-var": "error", 153 | "no-labels": "error", 154 | "no-lone-blocks": "error", 155 | "no-lonely-if": "error", 156 | "no-loop-func": "error", 157 | "no-magic-numbers": "off", 158 | "no-mixed-operators": "error", 159 | "no-mixed-requires": "error", 160 | "no-multi-assign": "error", 161 | "no-multi-spaces": "error", 162 | "no-multi-str": "error", 163 | "no-multiple-empty-lines": "error", 164 | "no-native-reassign": "error", 165 | "no-negated-condition": "error", 166 | "no-negated-in-lhs": "error", 167 | "no-nested-ternary": "error", 168 | "no-new": "error", 169 | "no-new-func": "error", 170 | "no-new-object": "error", 171 | "no-new-require": "error", 172 | "no-new-wrappers": "error", 173 | "no-octal-escape": "error", 174 | "no-param-reassign": "off", 175 | "no-path-concat": "error", 176 | "no-plusplus": [ 177 | "error", 178 | { 179 | "allowForLoopAfterthoughts": true 180 | } 181 | ], 182 | "no-process-env": "error", 183 | "no-process-exit": "error", 184 | "no-proto": "error", 185 | "no-prototype-builtins": "error", 186 | "no-restricted-globals": "error", 187 | "no-restricted-imports": "error", 188 | "no-restricted-modules": "error", 189 | "no-restricted-properties": "error", 190 | "no-restricted-syntax": "error", 191 | "no-return-assign": "error", 192 | "no-return-await": "error", 193 | "no-script-url": "error", 194 | "no-self-compare": "error", 195 | "no-sequences": "error", 196 | "no-shadow": "off", 197 | "no-shadow-restricted-names": "error", 198 | "no-spaced-func": "error", 199 | "no-sync": "off", 200 | "no-tabs": "off", 201 | "no-template-curly-in-string": "error", 202 | "no-ternary": "off", 203 | "no-throw-literal": "error", 204 | "no-trailing-spaces": "error", 205 | "no-undef-init": "error", 206 | "no-undefined": "error", 207 | "no-underscore-dangle": "error", 208 | "no-unmodified-loop-condition": "error", 209 | "no-unneeded-ternary": "error", 210 | "no-unused-expressions": "error", 211 | "no-use-before-define": "off", 212 | "no-useless-call": "error", 213 | "no-useless-computed-key": "error", 214 | "no-useless-concat": "error", 215 | "no-useless-constructor": "error", 216 | "no-useless-rename": "error", 217 | "no-useless-return": "error", 218 | "no-var": "off", 219 | "no-void": "error", 220 | "no-warning-comments": [ 221 | "error", 222 | { 223 | "location": "start" 224 | } 225 | ], 226 | "no-whitespace-before-property": "error", 227 | "no-with": "error", 228 | "nonblock-statement-body-position": "error", 229 | "object-curly-newline": "error", 230 | "object-curly-spacing": "error", 231 | "object-property-newline": "error", 232 | "object-shorthand": "off", 233 | "one-var": "off", 234 | "one-var-declaration-per-line": "error", 235 | "operator-assignment": [ 236 | "error", 237 | "always" 238 | ], 239 | "operator-linebreak": "error", 240 | "padded-blocks": "off", 241 | "padding-line-between-statements": "error", 242 | "prefer-arrow-callback": "off", 243 | "prefer-const": "off", 244 | "prefer-destructuring": "off", 245 | "prefer-numeric-literals": "error", 246 | "prefer-promise-reject-errors": "error", 247 | "prefer-reflect": "error", 248 | "prefer-rest-params": "error", 249 | "prefer-spread": "error", 250 | "prefer-template": "off", 251 | "quote-props": "off", 252 | "quotes": [ 253 | "error", 254 | "single" 255 | ], 256 | "radix": "error", 257 | "require-await": "error", 258 | "require-jsdoc": "off", 259 | "rest-spread-spacing": "error", 260 | "semi": "error", 261 | "semi-spacing": "off", 262 | "semi-style": [ 263 | "error", 264 | "last" 265 | ], 266 | "sort-imports": "off", 267 | "sort-keys": [ 268 | "error", 269 | "desc" 270 | ], 271 | "sort-vars": "error", 272 | "space-before-blocks": "error", 273 | "space-before-function-paren": "off", 274 | "space-in-parens": "off", 275 | "space-infix-ops": "error", 276 | "space-unary-ops": "error", 277 | "spaced-comment": [ 278 | "error", 279 | "always" 280 | ], 281 | "strict": "error", 282 | "switch-colon-spacing": "error", 283 | "symbol-description": "error", 284 | "template-curly-spacing": [ 285 | "error", 286 | "never" 287 | ], 288 | "template-tag-spacing": "error", 289 | "unicode-bom": [ 290 | "error", 291 | "never" 292 | ], 293 | "valid-jsdoc": "off", 294 | "vars-on-top": "error", 295 | "wrap-iife": "error", 296 | "wrap-regex": "error", 297 | "yield-star-spacing": "error", 298 | "yoda": "off" 299 | } 300 | } -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: "npm" 6 | directory: "/" 7 | schedule: 8 | interval: "weekly" 9 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | 14 | - name: Install dependencies 15 | run: npm ci 16 | 17 | - name: Build 18 | run: npm run lint && npm run build 19 | 20 | - name: Semantic Release 21 | uses: cycjimmy/semantic-release-action@v2.5.0 22 | 23 | env: 24 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 25 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rollup-plugin-includepaths 2 | 3 | Let you use relative paths in your import directives, like this: 4 | 5 | ```js 6 | // from src/lib/one/foo.js 7 | import { Foo } from 'one/foo'; 8 | 9 | // from src/other/two/bar.js 10 | import { Bar } from 'two/bar'; 11 | 12 | ``` 13 | 14 | ## Setup 15 | 16 | In your rollup configuration file: 17 | 18 | ```js 19 | 20 | import includePaths from 'rollup-plugin-includepaths'; 21 | 22 | let includePathOptions = { 23 | include: {}, 24 | paths: ['src/lib', 'src/other'], 25 | external: [], 26 | extensions: ['.js', '.json', '.html'] 27 | }; 28 | 29 | export default { 30 | entry: './app.js', 31 | format: 'cjs', 32 | dest: 'public/app.min.js', 33 | plugins: [ includePaths(includePathOptions) ], 34 | }; 35 | 36 | ``` 37 | 38 | ## Options: 39 | 40 | ### paths = `['']` 41 | 42 | An array of source paths in your project where the plugin should look for files 43 | 44 | Example: `['src/lib', 'src/foo']` 45 | 46 | By default, resolve files from working dir 47 | 48 | ### include 49 | 50 | A map of module=>path/to/file.js with custom module paths. Used to override the search with a static path (like Browserify does with the "browser" config). 51 | 52 | Use this option if you want to skip the file resolution and directly resolve a module name to a given path. 53 | 54 | Example: 55 | 56 | ```js 57 | include: { 58 | // Import example: import angular from 'angular'; 59 | 'angular': 'bower_components/angular/angular.js' 60 | } 61 | ``` 62 | 63 | ### external 64 | 65 | An array of module names that should be excluded from the bundle (external modules). 66 | 67 | By default, all the node built-in modules will be marked as external. 68 | 69 | To include the built-ins, you can use the [builtins](https://github.com/calvinmetcalf/rollup-plugin-node-builtins) plugin and set this config to an empty array. 70 | 71 | Example: 72 | 73 | ```js 74 | // will not include the module 'angular' in the final bundle 75 | external: ['angular'] 76 | ``` 77 | 78 | 79 | ### extensions 80 | 81 | An array of file extensions to look for in the project. 82 | 83 | Default: `['.js', '.json']` 84 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rollup-plugin-includepaths", 3 | "version": "0.2.3", 4 | "description": "Rollup plugin to use relative paths in your project files", 5 | "main": "src/plugin.es5.js", 6 | "jsnext:main": "src/plugin.js", 7 | "files": [ 8 | "src" 9 | ], 10 | "release": { 11 | "branches": ["master"] 12 | }, 13 | "publishConfig": { 14 | "access": "public" 15 | }, 16 | "scripts": { 17 | "lint": "eslint src/plugin.js", 18 | "build": "babel src/plugin.js --out-file src/plugin.es5.js" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/dot-build/rollup-plugin-includepaths.git" 23 | }, 24 | "keywords": [ 25 | "rollup-plugin", 26 | "rollup", 27 | "es6" 28 | ], 29 | "author": "Darlan Alves ", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/dot-build/rollup-plugin-includepaths/issues" 33 | }, 34 | "homepage": "https://github.com/dot-build/rollup-plugin-includepaths#readme", 35 | "devDependencies": { 36 | "babel-cli": "^6.26.0", 37 | "babel-plugin-add-module-exports": "^1.0.2", 38 | "babel-preset-es2015": "^6.6.0", 39 | "eslint": "^7.6.0", 40 | "semantic-release": "^17.1.1" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/plugin.es5.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | exports.default = plugin; 10 | 11 | var _path = require('path'); 12 | 13 | var _path2 = _interopRequireDefault(_path); 14 | 15 | var _fs = require('fs'); 16 | 17 | var _fs2 = _interopRequireDefault(_fs); 18 | 19 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 20 | 21 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 22 | 23 | /** 24 | * Node.JS modules ignored in the resolveId method by default 25 | * TODO use https://www.npmjs.com/package/builtin-modules 26 | */ 27 | var externalModules = ['assert', 'buffer', 'console', 'constants', 'crypto', 'domain', 'events', 'http', 'https', 'os', 'path', 'punycode', 'querystring', 'stream', 'string_decoder', 'timers', 'tty', 'url', 'util', 'vm', 'zlib']; 28 | 29 | var defaultExtensions = ['.js', '.json']; 30 | 31 | var RollupIncludePaths = function () { 32 | /** 33 | * Options: 34 | * 35 | * - paths 36 | * An array of source paths in your project where the plugin should look for files 37 | * Example: ['src/lib', 'src/foo'] 38 | * 39 | * - include 40 | * A map of module=>path/to/file.js with custom module paths. Used to override 41 | * the search with a static path (like Browserify does with the "browser" config) 42 | * 43 | * - external 44 | * An array of module names that should be excluded from the bundle 45 | * 46 | * - extensions 47 | * An array of file extensions to look for in the project. 48 | * Default: ['.js', '.json'] 49 | * 50 | * @param {Object} options 51 | */ 52 | function RollupIncludePaths(options) { 53 | _classCallCheck(this, RollupIncludePaths); 54 | 55 | options = options || {}; 56 | 57 | // include paths 58 | this.projectPaths = options.paths || ['']; 59 | 60 | this.cache = {}; 61 | if (options.include) { 62 | this.copyStaticPathsToCache(options.include); 63 | } 64 | 65 | // external modules to ignore 66 | this.externalModules = options.external || externalModules; 67 | 68 | // file extensions 69 | this.extensions = options.extensions || defaultExtensions; 70 | var extensionMatchers = this.extensions.map(function (e) { 71 | return e.replace('.', '\\.'); 72 | }).join('|'); 73 | 74 | this.HAS_EXTENSION = RegExp('(' + extensionMatchers + ')$'); 75 | } 76 | 77 | /** 78 | * Rollup plugin method. Implements the Module resolution for project 79 | * files 80 | * 81 | * @param {string} file File path to search 82 | * @param {string} [origin] Origin of the module request 83 | */ 84 | 85 | 86 | _createClass(RollupIncludePaths, [{ 87 | key: 'resolveId', 88 | value: function resolveId(id, origin) { 89 | origin = origin || false; 90 | return this.resolveCachedPath(id, origin) || this.searchModule(id, origin); 91 | } 92 | 93 | /** 94 | * Rollup plugin method. Modifies the "external" option 95 | * 96 | * If options.external is a function, follows the Rollup API by keeping the 97 | * option as a function (https://github.com/rollup/rollup/wiki/JavaScript-API#external) 98 | * 99 | * If options.external is an array, append the Node.JS builtin modules to it. 100 | * 101 | * @param {Object} options 102 | */ 103 | 104 | }, { 105 | key: 'options', 106 | value: function options(_options) { 107 | var _this = this; 108 | 109 | if ('function' === typeof this.externalModules) { 110 | _options.external = this.externalModules; 111 | } else if (this.externalModules instanceof Array && this.externalModules.length) { 112 | var external = _options.external; 113 | if ('function' === typeof external) { 114 | _options.external = function (id) { 115 | return external(id) || _this.externalModules.indexOf(id) !== -1; 116 | }; 117 | } else { 118 | _options.external = (external && external instanceof Array ? external : []).concat(this.externalModules); 119 | } 120 | } 121 | 122 | return _options; 123 | } 124 | 125 | /** 126 | * Receives an object with { moduleName => fullPath } 127 | * 128 | * Used to override resolution process with static values, like the 129 | * `browser` config in Browserify 130 | * 131 | * If the path has no file extension, ".js" is implied 132 | * 133 | * @param {Object} paths 134 | */ 135 | 136 | }, { 137 | key: 'copyStaticPathsToCache', 138 | value: function copyStaticPathsToCache(staticPaths) { 139 | var cache = this.cache; 140 | 141 | Object.keys(staticPaths).forEach(function (id) { 142 | var modulePath = staticPaths[id]; 143 | cache[id] = resolveJsExtension(modulePath); 144 | }); 145 | 146 | /** 147 | * Add '.js' to the end of file path 148 | * @param {string} file 149 | * @return {string} 150 | */ 151 | function resolveJsExtension(file) { 152 | if (/\.js$/.test(file) === false) { 153 | file += '.js'; 154 | } 155 | 156 | return file; 157 | } 158 | } 159 | 160 | /** 161 | * Return a path from cache 162 | * @param {string} id 163 | * @return {string|nulld} 164 | */ 165 | 166 | }, { 167 | key: 'resolveCachedPath', 168 | value: function resolveCachedPath(id, origin) { 169 | var key = this.getCacheKey(id, origin); 170 | 171 | if (key in this.cache) { 172 | return this.cache[key]; 173 | } 174 | 175 | return false; 176 | } 177 | }, { 178 | key: 'getCacheKey', 179 | value: function getCacheKey(id, origin) { 180 | var isRelativePath = id.indexOf('.') === 0; 181 | 182 | return isRelativePath ? origin + ':' + id : id; 183 | } 184 | 185 | /** 186 | * @param {string} file File path to search 187 | * @param {string} [origin] Origin of the module request 188 | */ 189 | 190 | }, { 191 | key: 'searchModule', 192 | value: function searchModule(file, origin) { 193 | var newPath = this.searchRelativePath(file, origin) || this.searchProjectModule(file, origin); 194 | 195 | if (newPath) { 196 | // add result to cache 197 | var cacheKey = this.getCacheKey(file, origin); 198 | this.cache[cacheKey] = newPath; 199 | 200 | return newPath; 201 | } 202 | 203 | // if no path was found, null must be returned to keep the 204 | // plugin chain! 205 | return null; 206 | } 207 | 208 | /** 209 | * Sarch for a file in the defined include paths 210 | * 211 | * @param {string} file File path to search 212 | * @param {string} [origin] Origin of the module request 213 | * @return {string|null} 214 | */ 215 | 216 | }, { 217 | key: 'searchProjectModule', 218 | value: function searchProjectModule(file) { 219 | var newPath = void 0; 220 | var includePath = this.projectPaths; 221 | var workingDir = process.cwd(); 222 | 223 | for (var i = 0, ii = includePath.length; i < ii; i++) { 224 | newPath = this.resolvePath(_path2.default.resolve(workingDir, includePath[i], file)); 225 | if (newPath) return newPath; 226 | 227 | // #1 - also check for 'path/to/file/index.js' 228 | // #4 - also check for 'path/to/file/index.[extensions]' 229 | newPath = this.resolvePath(_path2.default.resolve(workingDir, includePath[i], file, 'index')); 230 | if (newPath) return newPath; 231 | } 232 | 233 | return null; 234 | } 235 | 236 | /** 237 | * Sarch for a file relative to who required it 238 | * 239 | * @param {string} file File path to search 240 | * @param {string} [origin] Origin of the module request 241 | */ 242 | 243 | }, { 244 | key: 'searchRelativePath', 245 | value: function searchRelativePath(file, origin) { 246 | if (!origin) { 247 | return null; 248 | } 249 | 250 | var basePath = _path2.default.dirname(origin); 251 | 252 | return ( 253 | // common case 254 | // require('./file.js') in 'path/origin.js' 255 | // > path/file.js 256 | this.resolvePath(_path2.default.join(basePath, file)) || 257 | 258 | // nodejs path case 259 | // require('./subfolder') in 'lib/origin.js' 260 | // > lib/subfolder/index.js 261 | this.resolvePath(_path2.default.join(basePath, file, 'index')) 262 | ); 263 | } 264 | 265 | /** 266 | * Resolve a given file path by checking if it exists. If it does not, 267 | * also checks if the file exists by appending the extensions to it, 268 | * i.e. checks for 'file', then 'file.js', 'file.json' 269 | * and so on, until one is found. 270 | * 271 | * Returns false if "file" was not found 272 | * 273 | * @param {string} file 274 | * @return {boolean} 275 | */ 276 | 277 | }, { 278 | key: 'resolvePath', 279 | value: function resolvePath(file) { 280 | if (this.fileExists(file)) { 281 | return file; 282 | } 283 | 284 | // check different file extensions 285 | for (var i = 0, ii = this.extensions.length; i < ii; i++) { 286 | var ext = this.extensions[i]; 287 | var newPath = file + ext; 288 | 289 | if (this.fileExists(newPath)) { 290 | return newPath; 291 | } 292 | } 293 | 294 | return false; 295 | } 296 | 297 | /** 298 | * Check if "file" has one of the extensions defined in the plugin options 299 | * @param {string} file 300 | * @return {boolean} 301 | */ 302 | 303 | }, { 304 | key: 'hasExtension', 305 | value: function hasExtension(file) { 306 | return this.HAS_EXTENSION.test(file); 307 | } 308 | 309 | /** 310 | * @param {string} file 311 | * @return {boolean} 312 | */ 313 | 314 | }, { 315 | key: 'fileExists', 316 | value: function fileExists(file) { 317 | try { 318 | var stat = _fs2.default.statSync(file); 319 | return stat.isFile(); 320 | } catch (e) { 321 | return false; 322 | } 323 | } 324 | }]); 325 | 326 | return RollupIncludePaths; 327 | }(); 328 | 329 | function plugin(options) { 330 | var resolver = new RollupIncludePaths(options); 331 | 332 | return { 333 | resolveId: function resolveId(file, origin) { 334 | return resolver.resolveId(file, origin); 335 | }, 336 | 337 | options: function options(_options2) { 338 | return resolver.options(_options2); 339 | } 340 | }; 341 | } 342 | module.exports = exports.default; 343 | -------------------------------------------------------------------------------- /src/plugin.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs'; 3 | 4 | /** 5 | * Node.JS modules ignored in the resolveId method by default 6 | * TODO use https://www.npmjs.com/package/builtin-modules 7 | */ 8 | const externalModules = ['assert', 'buffer', 'console', 'constants', 'crypto', 9 | 'domain', 'events', 'http', 'https', 'os', 'path', 'punycode', 'querystring', 10 | 'stream', 'string_decoder', 'timers', 'tty', 'url', 'util', 'vm', 'zlib']; 11 | 12 | const defaultExtensions = ['.js', '.json']; 13 | 14 | class RollupIncludePaths { 15 | /** 16 | * Options: 17 | * 18 | * - paths 19 | * An array of source paths in your project where the plugin should look for files 20 | * Example: ['src/lib', 'src/foo'] 21 | * 22 | * - include 23 | * A map of module=>path/to/file.js with custom module paths. Used to override 24 | * the search with a static path (like Browserify does with the "browser" config) 25 | * 26 | * - external 27 | * An array of module names that should be excluded from the bundle 28 | * 29 | * - extensions 30 | * An array of file extensions to look for in the project. 31 | * Default: ['.js', '.json'] 32 | * 33 | * @param {Object} options 34 | */ 35 | constructor(options) { 36 | options = options || {}; 37 | 38 | // include paths 39 | this.projectPaths = options.paths || ['']; 40 | 41 | this.cache = {}; 42 | if (options.include) { 43 | this.copyStaticPathsToCache(options.include); 44 | } 45 | 46 | // external modules to ignore 47 | this.externalModules = options.external || externalModules; 48 | 49 | // file extensions 50 | this.extensions = options.extensions || defaultExtensions; 51 | let extensionMatchers = this.extensions.map(e => e.replace('.', '\\.')).join('|'); 52 | 53 | this.HAS_EXTENSION = RegExp('(' + extensionMatchers + ')$'); 54 | } 55 | 56 | /** 57 | * Rollup plugin method. Implements the Module resolution for project 58 | * files 59 | * 60 | * @param {string} file File path to search 61 | * @param {string} [origin] Origin of the module request 62 | */ 63 | resolveId(id, origin) { 64 | origin = origin || false; 65 | return this.resolveCachedPath(id, origin) || this.searchModule(id, origin); 66 | } 67 | 68 | /** 69 | * Rollup plugin method. Modifies the "external" option 70 | * 71 | * If options.external is a function, follows the Rollup API by keeping the 72 | * option as a function (https://github.com/rollup/rollup/wiki/JavaScript-API#external) 73 | * 74 | * If options.external is an array, append the Node.JS builtin modules to it. 75 | * 76 | * @param {Object} options 77 | */ 78 | options(options) { 79 | if ('function' === typeof this.externalModules) { 80 | options.external = this.externalModules; 81 | } else if (this.externalModules instanceof Array && this.externalModules.length) { 82 | const external = options.external; 83 | if ('function' === typeof external) { 84 | options.external = (id) => external(id) || this.externalModules.indexOf(id) !== -1; 85 | } else { 86 | options.external = (external && external instanceof Array ? external : []).concat(this.externalModules); 87 | } 88 | } 89 | 90 | return options; 91 | } 92 | 93 | /** 94 | * Receives an object with { moduleName => fullPath } 95 | * 96 | * Used to override resolution process with static values, like the 97 | * `browser` config in Browserify 98 | * 99 | * If the path has no file extension, ".js" is implied 100 | * 101 | * @param {Object} paths 102 | */ 103 | copyStaticPathsToCache (staticPaths) { 104 | let cache = this.cache; 105 | 106 | Object.keys(staticPaths).forEach(function (id) { 107 | var modulePath = staticPaths[id]; 108 | cache[id] = resolveJsExtension(modulePath); 109 | }); 110 | 111 | /** 112 | * Add '.js' to the end of file path 113 | * @param {string} file 114 | * @return {string} 115 | */ 116 | function resolveJsExtension (file) { 117 | if ((/\.js$/).test(file) === false) { 118 | file += '.js'; 119 | } 120 | 121 | return file; 122 | } 123 | } 124 | 125 | /** 126 | * Return a path from cache 127 | * @param {string} id 128 | * @return {string|nulld} 129 | */ 130 | resolveCachedPath (id, origin) { 131 | const key = this.getCacheKey(id, origin); 132 | 133 | if (key in this.cache) { 134 | return this.cache[key]; 135 | } 136 | 137 | return false; 138 | } 139 | 140 | getCacheKey(id, origin) { 141 | const isRelativePath = id.indexOf('.') === 0; 142 | 143 | return isRelativePath ? `${origin}:${id}` : id; 144 | } 145 | 146 | /** 147 | * @param {string} file File path to search 148 | * @param {string} [origin] Origin of the module request 149 | */ 150 | searchModule (file, origin) { 151 | let newPath = 152 | this.searchRelativePath(file, origin) || 153 | this.searchProjectModule(file, origin); 154 | 155 | if (newPath) { 156 | // add result to cache 157 | let cacheKey = this.getCacheKey(file, origin); 158 | this.cache[cacheKey] = newPath; 159 | 160 | return newPath; 161 | } 162 | 163 | // if no path was found, null must be returned to keep the 164 | // plugin chain! 165 | return null; 166 | } 167 | 168 | /** 169 | * Sarch for a file in the defined include paths 170 | * 171 | * @param {string} file File path to search 172 | * @param {string} [origin] Origin of the module request 173 | * @return {string|null} 174 | */ 175 | searchProjectModule (file) { 176 | let newPath; 177 | let includePath = this.projectPaths; 178 | let workingDir = process.cwd(); 179 | 180 | for (let i = 0, ii = includePath.length; i < ii ; i++) { 181 | newPath = this.resolvePath(path.resolve(workingDir, includePath[i], file)); 182 | if (newPath) return newPath; 183 | 184 | // #1 - also check for 'path/to/file/index.js' 185 | // #4 - also check for 'path/to/file/index.[extensions]' 186 | newPath = this.resolvePath(path.resolve(workingDir, includePath[i], file, 'index')); 187 | if (newPath) return newPath; 188 | } 189 | 190 | return null; 191 | } 192 | 193 | /** 194 | * Sarch for a file relative to who required it 195 | * 196 | * @param {string} file File path to search 197 | * @param {string} [origin] Origin of the module request 198 | */ 199 | searchRelativePath (file, origin) { 200 | if (!origin) { 201 | return null; 202 | } 203 | 204 | let basePath = path.dirname(origin); 205 | 206 | return ( 207 | // common case 208 | // require('./file.js') in 'path/origin.js' 209 | // > path/file.js 210 | this.resolvePath(path.join(basePath, file)) || 211 | 212 | // nodejs path case 213 | // require('./subfolder') in 'lib/origin.js' 214 | // > lib/subfolder/index.js 215 | this.resolvePath(path.join(basePath, file, 'index')) 216 | ); 217 | } 218 | 219 | /** 220 | * Resolve a given file path by checking if it exists. If it does not, 221 | * also checks if the file exists by appending the extensions to it, 222 | * i.e. checks for 'file', then 'file.js', 'file.json' 223 | * and so on, until one is found. 224 | * 225 | * Returns false if "file" was not found 226 | * 227 | * @param {string} file 228 | * @return {boolean} 229 | */ 230 | resolvePath (file) { 231 | if (this.fileExists(file)) { 232 | return file; 233 | } 234 | 235 | // check different file extensions 236 | for (let i = 0, ii = this.extensions.length; i < ii; i++ ) { 237 | let ext = this.extensions[i]; 238 | let newPath = file + ext; 239 | 240 | if (this.fileExists(newPath)) { 241 | return newPath; 242 | } 243 | } 244 | 245 | return false; 246 | } 247 | 248 | /** 249 | * Check if "file" has one of the extensions defined in the plugin options 250 | * @param {string} file 251 | * @return {boolean} 252 | */ 253 | hasExtension (file) { 254 | return this.HAS_EXTENSION.test(file); 255 | } 256 | 257 | /** 258 | * @param {string} file 259 | * @return {boolean} 260 | */ 261 | fileExists (file) { 262 | try { 263 | let stat = fs.statSync(file); 264 | return stat.isFile(); 265 | } catch (e) { 266 | return false; 267 | } 268 | } 269 | } 270 | 271 | export default function plugin(options) { 272 | let resolver = new RollupIncludePaths(options); 273 | 274 | return { 275 | resolveId: function (file, origin) { 276 | return resolver.resolveId(file, origin); 277 | }, 278 | 279 | options: function (options) { 280 | return resolver.options(options); 281 | } 282 | }; 283 | } 284 | --------------------------------------------------------------------------------