├── .eslintignore ├── .eslintrc ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ ├── nodejs.yml │ └── npmpublish.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .prettierrc ├── LICENSE ├── README.md ├── jest.config.json ├── mocks ├── jsconfig.json └── tsconfig.paths.json ├── package.json ├── plugin ├── __snapshots__ │ ├── generate-module-name-mapper.test.js.snap │ └── normalize-plugin-options.test.js.snap ├── create-overrider.js ├── debug.js ├── exit-with-error.js ├── extract-aliases │ ├── __snapshots__ │ │ ├── index.test.js.snap │ │ └── normalize-aliases.test.js.snap │ ├── index.js │ ├── index.test.js │ ├── normalize-aliases.js │ └── normalize-aliases.test.js ├── filter-aliases.js ├── filter-aliases.test.js ├── generate-module-name-mapper.js ├── generate-module-name-mapper.test.js ├── helpers │ └── escape-string-for-regexp.js ├── index.js ├── normalize-plugin-options.js ├── normalize-plugin-options.test.js └── pre-check │ ├── check-config-contents.js │ ├── check-config-contents.test.js │ ├── check-config-existence.js │ ├── check-config-existence.test.js │ ├── check-config.js │ ├── check-options.js │ ├── check-options.test.js │ └── index.js └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/** -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | }, 5 | "extends": [ 6 | "@eslint-kit/base", 7 | "@eslint-kit/node", 8 | "@eslint-kit/prettier" 9 | ], 10 | "parser": "babel-eslint", 11 | "rules": { 12 | "no-console": "off" 13 | } 14 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Follow these steps: 11 | 12 | https://github.com/risenforces/craco-alias#ran-into-a-problem 13 | 14 | Then describe your problem and paste the plugin output. 15 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: 4 | pull_request: 5 | branches: master 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | strategy: 12 | matrix: 13 | node-version: [12.x] 14 | 15 | steps: 16 | - uses: actions/checkout@v1 17 | - name: Use Node.js ${{ matrix.node-version }} 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | - name: npm install, and test 22 | run: | 23 | yarn install --frozen-lockfile 24 | yarn test 25 | env: 26 | CI: true 27 | -------------------------------------------------------------------------------- /.github/workflows/npmpublish.yml: -------------------------------------------------------------------------------- 1 | name: Node.js Package 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v1 12 | - uses: actions/setup-node@v1 13 | with: 14 | node-version: 12 15 | - run: yarn install --frozen-lockfile 16 | - run: yarn test 17 | 18 | publish-npm: 19 | needs: test 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v1 23 | - uses: actions/setup-node@v1 24 | with: 25 | node-version: 12 26 | registry-url: https://registry.npmjs.org/ 27 | - run: yarn install --frozen-lockfile 28 | - run: npm publish 29 | env: 30 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint-staged 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "tabWidth": 2, 5 | "quoteProps": "consistent", 6 | "trailingComma": "es5", 7 | "endOfLine": "lf" 8 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Evgeny Zakharov 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This package is deprecated, use [react-app-alias](https://github.com/oklas/react-app-alias) instead 2 | 3 | [![npm](https://img.shields.io/npm/v/craco-alias.svg)](https://www.npmjs.com/package/craco-alias) 4 | 5 | A [craco](https://github.com/sharegate/craco) plugin for automatic aliases generation for Webpack and Jest. 6 | 7 | ## List of Contents 8 | 9 | - [Installation](#installation) 10 | - [Options](#options) 11 | - [Examples](#examples) 12 | - [Ran into a problem?](#ran-into-a-problem) 13 | - [If you want to help](#if-you-want-to-help) 14 | 15 | ### Installation 16 | 17 | 1. Install [craco](https://github.com/gsoft-inc/craco/blob/master/packages/craco/README.md#installation) 18 | 19 | 2. Install `craco-alias`: 20 | 21 | ```sh 22 | npm i -D craco-alias 23 | ``` 24 | 25 | ```sh 26 | yarn add -D craco-alias 27 | ``` 28 | 29 | 3. Edit `craco.config.js`: 30 | 31 | ```js 32 | const CracoAlias = require("craco-alias"); 33 | 34 | module.exports = { 35 | plugins: [ 36 | { 37 | plugin: CracoAlias, 38 | options: { 39 | // see in examples section 40 | } 41 | } 42 | ] 43 | }; 44 | ``` 45 | 46 | 4. Go to [Examples](#examples) section 47 | 48 | ### Options 49 | 50 | - `source`: 51 | One of `"options"`, `"jsconfig"`, `"tsconfig"` 52 | Optional, defaults to `"options"` 53 | 54 | - `baseUrl`: 55 | A base url for aliases. (`./src` for example) 56 | Optional, defaults to `./` (project root directory) 57 | 58 | - `aliases`: 59 | An object with aliases names and paths 60 | Only required when `source` is set to `"options"` 61 | 62 | - `tsConfigPath`: 63 | A path to tsconfig file 64 | Only required when `source` is set to `"tsconfig"` 65 | 66 | - `filter`: 67 | A function of type `([key, value]) => boolean` 68 | Optional, used to remove some aliases from the resulting config 69 | Example: `([key]) => !key.startsWith('node_modules')` 70 | 71 | - `unsafeAllowModulesOutsideOfSrc`: 72 | Allow importing modules outside of `./src` folder. 73 | Disables webpack `ModuleScopePlugin`. 74 | 75 | - `debug`: 76 | Enable it if you ran into a problem. It will log a useful info in console. 77 | Optional, defaults to `false` 78 | 79 | ### Examples 80 | 81 |
82 | Specify aliases manually (source: "options") 83 | 84 | > Note: you don't need to add `/*` part for directories in this case 85 | 86 | ```js 87 | /* craco.config.js */ 88 | 89 | const CracoAlias = require("craco-alias"); 90 | 91 | module.exports = { 92 | plugins: [ 93 | { 94 | plugin: CracoAlias, 95 | options: { 96 | source: "options", 97 | baseUrl: "./", 98 | aliases: { 99 | "@file": "./src/file.js", 100 | "@dir": "./src/some/dir", 101 | // you can alias packages too 102 | "@material-ui": "./node_modules/@material-ui-ie10" 103 | } 104 | } 105 | } 106 | ] 107 | }; 108 | ``` 109 | 110 |
111 | 112 |
113 | Use aliases from jsconfig.json (source: "jsconfig") 114 | 115 | ```js 116 | /* craco.config.js */ 117 | 118 | const CracoAlias = require("craco-alias"); 119 | 120 | module.exports = { 121 | plugins: [ 122 | { 123 | plugin: CracoAlias, 124 | options: { 125 | source: "jsconfig", 126 | // baseUrl SHOULD be specified 127 | // plugin does not take it from jsconfig 128 | baseUrl: "./src" 129 | } 130 | } 131 | ] 132 | }; 133 | ``` 134 | 135 | > **Note:** your jsconfig should always have `compilerOptions.paths` property. `baseUrl` is optional for plugin, but some IDEs and editors require it for intellisense. 136 | 137 | ```js 138 | /* jsconfig.json */ 139 | 140 | { 141 | "compilerOptions": { 142 | "baseUrl": "./src", 143 | "paths": { 144 | // file aliases 145 | "@baz": ["./baz.js"], 146 | "@boo": ["./boo.jsx"], 147 | 148 | // folder aliases 149 | "@root": ["./"], 150 | "@root/*": ["./*"], 151 | "@lib": ["./lib"], 152 | "@lib/*": ["./lib/*"], 153 | 154 | // package aliases (types is optional without ts) 155 | "@my-react-select": [ 156 | "../node_modules/react-select", 157 | "../node_modules/@types/react-select" 158 | ], 159 | "@my-react-select/*": [ 160 | "../node_modules/react-select/*", 161 | "../node_modules/@types/react-select" 162 | ] 163 | } 164 | } 165 | } 166 | ``` 167 | 168 |
169 | 170 |
171 | Use aliases from tsconfig.json (source: "tsconfig") 172 | 173 | 1. Go to project's root directory. 174 | 175 | 2. Create `tsconfig.extend.json`. 176 | 177 | 3. Edit it as follows: 178 | 179 | ```js 180 | { 181 | "compilerOptions": { 182 | "baseUrl": "./src", 183 | "paths": { 184 | // file aliases 185 | "@baz": ["./baz.ts"], 186 | "@boo": ["./boo.tsx"], 187 | 188 | // folder aliases 189 | "@root": ["./"], 190 | "@root/*": ["./*"], 191 | "@lib": ["./lib"], 192 | "@lib/*": ["./lib/*"], 193 | 194 | // package aliases 195 | "@my-react-select": [ 196 | "../node_modules/react-select", 197 | "../node_modules/@types/react-select" 198 | ], 199 | "@my-react-select/*": [ 200 | "../node_modules/react-select/*", 201 | "../node_modules/@types/react-select" 202 | ] 203 | } 204 | } 205 | } 206 | ``` 207 | 208 | 4. Go to `tsconfig.json`. 209 | 210 | 5. Extend `tsconfig.json` from `tsconfig.extend.json`: 211 | 212 | ```diff 213 | { 214 | + "extends": "./tsconfig.extend.json", 215 | "compilerOptions": { 216 | ... 217 | }, 218 | ... 219 | } 220 | ``` 221 | 222 | 6. Edit `craco.config.js`: 223 | 224 | ```js 225 | const CracoAlias = require("craco-alias"); 226 | 227 | module.exports = { 228 | plugins: [ 229 | { 230 | plugin: CracoAlias, 231 | options: { 232 | source: "tsconfig", 233 | // baseUrl SHOULD be specified 234 | // plugin does not take it from tsconfig 235 | baseUrl: "./src", 236 | // tsConfigPath should point to the file where "baseUrl" and "paths" are specified 237 | tsConfigPath: "./tsconfig.extend.json" 238 | } 239 | } 240 | ] 241 | }; 242 | ``` 243 | 244 |
245 | 246 | ### Ran into a problem? 247 | 248 | 1. Make sure your config is valid. 249 | 250 | 2. Set `debug` to `true` in [options](#options). 251 | 252 | 3. Run application again. 253 | 254 | 4. Copy a printed info. 255 | 256 | 5. [Here](https://github.com/risenforces/craco-alias/issues), create an issue describing your problem (do not forget to add the debug info). 257 | 258 | ### If you want to help 259 | 260 | Install: 261 | 262 | ```sh 263 | yarn 264 | ``` 265 | 266 | > Use yarn please. npm may fail the dependencies installation. 267 | 268 | Run tests: 269 | 270 | ``` 271 | yarn test 272 | ``` 273 | -------------------------------------------------------------------------------- /jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "snapshotSerializers": [ 3 | "jest-serializer-path" 4 | ] 5 | } -------------------------------------------------------------------------------- /mocks/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@file": ["./src/file.js"], 5 | "@file2": ["src/file2.js"], 6 | "@dir/*": ["./src/dir/*"], 7 | "@dir2/*": ["././src/dir2/*"], 8 | "$dir3/*": ["src/dir3/*", "src/dir3"], 9 | "my-package": [ 10 | "./node_modules/some-package", 11 | "./node_modules/some-package/*" 12 | ], 13 | "external-package": [ 14 | "/absolute_path/external-package", 15 | "/absolute_path/external-package/*" 16 | ], 17 | "@material-ui": ["node_modules/@material-ui/ie-10/ie-10.js"] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /mocks/tsconfig.paths.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@file": ["./src/file.js"], 5 | "@file2": ["src/file2.js"], 6 | "@dir/*": ["./src/dir/*"], 7 | "@dir2/*": ["././src/dir2/*"], 8 | "$dir3/*": ["src/dir3/*", "src/dir3"], 9 | "my-package": [ 10 | "./node_modules/some-package", 11 | "./node_modules/some-package/*" 12 | ], 13 | "external-package": [ 14 | "/absolute_path/external-package", 15 | "/absolute_path/external-package/*" 16 | ], 17 | "@material-ui": ["node_modules/@material-ui/ie-10/ie-10.js"] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "craco-alias", 3 | "version": "3.0.2", 4 | "description": "A craco plugin for automatic aliases generation", 5 | "main": "plugin/index.js", 6 | "scripts": { 7 | "test": "jest \".*\\.test\\.js\" --config jest.config.json", 8 | "prepare": "husky install" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/risenforces/craco-alias.git" 13 | }, 14 | "keywords": [ 15 | "craco", 16 | "create-react-app", 17 | "react", 18 | "cra", 19 | "alias", 20 | "aliases", 21 | "webpack", 22 | "jest" 23 | ], 24 | "author": "Evgeny Zakharov ", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/risenforces/craco-alias/issues" 28 | }, 29 | "homepage": "https://github.com/risenforces/craco-alias#readme", 30 | "devDependencies": { 31 | "@eslint-kit/eslint-config-base": "^3.2.0", 32 | "@eslint-kit/eslint-config-node": "^2.0.0", 33 | "@eslint-kit/eslint-config-prettier": "^2.0.0", 34 | "@types/jest": "^26.0.23", 35 | "babel-eslint": "10.1.0", 36 | "eslint": "7.10.0", 37 | "husky": "^6.0.0", 38 | "jest": "^24.9.0", 39 | "jest-serializer-path": "^0.1.15", 40 | "lint-staged": "^11.0.0", 41 | "prettier": "2.2.1" 42 | }, 43 | "lint-staged": { 44 | "**/*.js": [ 45 | "eslint --fix", 46 | "git add" 47 | ] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /plugin/__snapshots__/generate-module-name-mapper.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`generate-module-name-mapper should correctly generate moduleNameMapper 1`] = ` 4 | Object { 5 | "^@dir$": "/some/absolute/path/to/dir", 6 | "^@dir/(.*)$": "/some/absolute/path/to/dir/$1", 7 | "^@file$": "/some/absolute/path/to/file.js", 8 | } 9 | `; 10 | 11 | exports[`generate-module-name-mapper should correctly generate moduleNameMapper when alias name have special RegExp characters 1`] = ` 12 | Object { 13 | "^/$dir$": "/some/absolute/path/to/dir", 14 | "^/$dir/(.*)$": "/some/absolute/path/to/dir/$1", 15 | "^/$file$": "/some/absolute/path/to/file.js", 16 | } 17 | `; 18 | -------------------------------------------------------------------------------- /plugin/__snapshots__/normalize-plugin-options.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`normalize-plugin-options should return default config 1`] = ` 4 | Object { 5 | "aliases": null, 6 | "baseUrl": "./", 7 | "debug": false, 8 | "filter": [Function], 9 | "source": "options", 10 | "unsafeAllowModulesOutsideOfSrc": false, 11 | } 12 | `; 13 | 14 | exports[`normalize-plugin-options should return default config 2`] = ` 15 | Object { 16 | "aliases": null, 17 | "baseUrl": "./", 18 | "debug": false, 19 | "filter": [Function], 20 | "source": "options", 21 | "unsafeAllowModulesOutsideOfSrc": false, 22 | } 23 | `; 24 | 25 | exports[`normalize-plugin-options should return jsconfig-specific config 1`] = ` 26 | Object { 27 | "baseUrl": "./", 28 | "debug": false, 29 | "filter": [Function], 30 | "source": "jsconfig", 31 | "unsafeAllowModulesOutsideOfSrc": false, 32 | } 33 | `; 34 | 35 | exports[`normalize-plugin-options should return the same as an input 1`] = ` 36 | Object { 37 | "aliases": Object { 38 | "@file": "./file.js", 39 | }, 40 | "baseUrl": "./src", 41 | "debug": false, 42 | "filter": [Function], 43 | "source": "options", 44 | "unsafeAllowModulesOutsideOfSrc": true, 45 | } 46 | `; 47 | 48 | exports[`normalize-plugin-options should return tsconfig-specific config 1`] = ` 49 | Object { 50 | "baseUrl": "./", 51 | "debug": false, 52 | "filter": [Function], 53 | "source": "tsconfig", 54 | "tsConfigPath": "tsconfig.paths.json", 55 | "unsafeAllowModulesOutsideOfSrc": false, 56 | } 57 | `; 58 | -------------------------------------------------------------------------------- /plugin/create-overrider.js: -------------------------------------------------------------------------------- 1 | const preCheck = require('./pre-check') 2 | const normalizePluginOptions = require('./normalize-plugin-options') 3 | const extractAliases = require('./extract-aliases') 4 | const { searchObject, printBaseData, printObject } = require('./debug') 5 | const { filterAliases } = require('./filter-aliases') 6 | 7 | const createOverrider = (callback, debugInfo) => (cracoOptions) => { 8 | preCheck(cracoOptions) 9 | 10 | const options = normalizePluginOptions(cracoOptions.pluginOptions) 11 | const initialAliases = extractAliases(cracoOptions) 12 | const aliases = filterAliases(initialAliases, options.filter) 13 | 14 | if (options.debug) { 15 | printBaseData({ 16 | initialOptions: cracoOptions.pluginOptions, 17 | normalizedOptions: options, 18 | initialAliases, 19 | aliases, 20 | }) 21 | } 22 | 23 | const overridedConfig = callback({ aliases, options }, cracoOptions) 24 | 25 | if (options.debug) { 26 | const { name, aliasesPath } = debugInfo 27 | const aliases = searchObject(overridedConfig, aliasesPath) 28 | printObject(name, aliases) 29 | } 30 | 31 | return overridedConfig 32 | } 33 | 34 | module.exports = createOverrider 35 | -------------------------------------------------------------------------------- /plugin/debug.js: -------------------------------------------------------------------------------- 1 | const state = { 2 | wasOptionsPrinted: false, 3 | callCount: 0, 4 | } 5 | 6 | function debounce(callback, wait, immediate = false) { 7 | let timeout = null 8 | 9 | return function (...args) { 10 | const callNow = immediate && !timeout 11 | const next = () => callback.apply(this, args) 12 | 13 | clearTimeout(timeout) 14 | timeout = setTimeout(next, wait) 15 | 16 | if (callNow) { 17 | next() 18 | } 19 | } 20 | } 21 | 22 | function blue(string) { 23 | return '\u001B[34m' + string + '\u001B[0m' 24 | } 25 | 26 | function searchObject(object, path) { 27 | return path.split('.').reduce((acc, segment) => { 28 | if (typeof acc !== 'object') { 29 | return null 30 | } 31 | 32 | return acc[segment] 33 | }, object) 34 | } 35 | 36 | function exit() { 37 | process.exit(0) 38 | } 39 | 40 | const debouncedExit = debounce(exit, 70) 41 | 42 | function printBaseData({ 43 | initialOptions, 44 | normalizedOptions, 45 | initialAliases, 46 | aliases, 47 | }) { 48 | if (state.wasOptionsPrinted) return 49 | 50 | console.log(blue('Initial options:') + '\n') 51 | console.log(JSON.stringify(initialOptions, null, 2) + '\n') 52 | console.log(blue('Normalized options:') + '\n') 53 | console.log(JSON.stringify(normalizedOptions, null, 2) + '\n') 54 | console.log(blue('Initial aliases:') + '\n') 55 | console.log(JSON.stringify(initialAliases, null, 2) + '\n') 56 | console.log(blue('Aliases:') + '\n') 57 | console.log(JSON.stringify(aliases, null, 2) + '\n') 58 | 59 | state.wasOptionsPrinted = true 60 | 61 | debouncedExit() 62 | } 63 | 64 | function printObject(name, data) { 65 | console.log(blue(name + ':') + '\n') 66 | 67 | if (typeof data === 'object') { 68 | console.log(JSON.stringify(data, null, 2) + '\n') 69 | } else { 70 | console.log('Not an object \n') 71 | } 72 | 73 | debouncedExit() 74 | } 75 | 76 | exports.searchObject = searchObject 77 | exports.printBaseData = printBaseData 78 | exports.printObject = printObject 79 | -------------------------------------------------------------------------------- /plugin/exit-with-error.js: -------------------------------------------------------------------------------- 1 | const exitWithError = (message) => { 2 | console.log('\u001B[31m%s\u001B[0m', '[Craco-Alias Error]', message) 3 | 4 | console.log( 5 | '\nPlugin documentation:', 6 | '\u001B[34mhttps://github.com/risenforces/craco-alias\u001B[0m' 7 | ) 8 | 9 | process.exit(0) 10 | } 11 | 12 | module.exports = exitWithError 13 | -------------------------------------------------------------------------------- /plugin/extract-aliases/__snapshots__/index.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`extract-aliases should correctly extract aliases from jsconfig: extract-aliases/snap-1 1`] = ` 4 | Object { 5 | "$dir3": "/src/dir3", 6 | "@dir": "/src/dir", 7 | "@dir2": "/src/dir2", 8 | "@file": "/src/file.js", 9 | "@file2": "/src/file2.js", 10 | "@material-ui": "/node_modules/@material-ui/ie-10/ie-10.js", 11 | "external-package": "/absolute_path/external-package", 12 | "my-package": "/node_modules/some-package", 13 | } 14 | `; 15 | 16 | exports[`extract-aliases should correctly extract aliases from options: extract-aliases/snap-1 1`] = ` 17 | Object { 18 | "$dir3": "/src/dir3", 19 | "@dir": "/src/dir", 20 | "@dir2": "/src/dir2", 21 | "@file": "/src/file.js", 22 | "@file2": "/src/file2.js", 23 | "@material-ui": "/node_modules/@material-ui/ie-10/ie-10.js", 24 | "external-package": "/absolute_path/external-package", 25 | "my-package": "/node_modules/some-package", 26 | } 27 | `; 28 | 29 | exports[`extract-aliases should correctly extract aliases from tsconfig: extract-aliases/snap-1 1`] = ` 30 | Object { 31 | "$dir3": "/src/dir3", 32 | "@dir": "/src/dir", 33 | "@dir2": "/src/dir2", 34 | "@file": "/src/file.js", 35 | "@file2": "/src/file2.js", 36 | "@material-ui": "/node_modules/@material-ui/ie-10/ie-10.js", 37 | "external-package": "/absolute_path/external-package", 38 | "my-package": "/node_modules/some-package", 39 | } 40 | `; 41 | -------------------------------------------------------------------------------- /plugin/extract-aliases/__snapshots__/normalize-aliases.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`normalize-aliases should correctly normalize aliases 1`] = ` 4 | Object { 5 | "$dir3": "/src/dir3", 6 | "@dir": "/src/dir", 7 | "@dir2": "/src/dir2", 8 | "@file": "/src/file.js", 9 | "@file2": "/src/file2.js", 10 | "@material-ui": "/node_modules/@material-ui/ie-10/ie-10.js", 11 | "external-package": "/absolute_path/external-package", 12 | "my-package": "/node_modules/some-package", 13 | } 14 | `; 15 | 16 | exports[`normalize-aliases should correctly normalize aliases 2`] = ` 17 | Object { 18 | "$dir3": "/src/dir3", 19 | "@dir": "/src/dir", 20 | "@dir2": "/src/dir2", 21 | "@file": "/src/file.js", 22 | "@file2": "/src/file2.js", 23 | } 24 | `; 25 | 26 | exports[`normalize-aliases should correctly normalize aliases 3`] = ` 27 | Object { 28 | "$dir3": Array [ 29 | "/fallback_test/dir3", 30 | "/fallback_test/fallback/dir3", 31 | ], 32 | "@file2": Array [ 33 | "/fallback_test/file2.js", 34 | ], 35 | } 36 | `; 37 | -------------------------------------------------------------------------------- /plugin/extract-aliases/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const normalizePluginOptions = require('../normalize-plugin-options') 4 | const normalizeAliases = require('./normalize-aliases') 5 | 6 | const extractAliasesFromConfig = ({ configPath, absoluteBaseUrl }) => { 7 | const configFileContents = fs.readFileSync(configPath) 8 | const config = JSON.parse(configFileContents) 9 | 10 | const { compilerOptions } = config 11 | 12 | const standardAliases = {} 13 | 14 | for (const aliasName in compilerOptions.paths) { 15 | const alias = compilerOptions.paths[aliasName] 16 | if (typeof alias === 'string') { 17 | const [aliasPath] = compilerOptions.paths[aliasName] 18 | standardAliases[aliasName.replace('/*', '')] = aliasPath.replace('/*', '') 19 | } else { 20 | const aliasPath = [] 21 | alias.forEach((anAlias) => { 22 | const sanitized = anAlias.replace('/*', '') 23 | if (!aliasPath.includes(sanitized)) aliasPath.push(sanitized) 24 | }) 25 | standardAliases[aliasName.replace('/*', '')] = 26 | aliasPath.length > 1 ? aliasPath : aliasPath[0] 27 | } 28 | } 29 | 30 | return normalizeAliases({ 31 | absoluteBaseUrl, 32 | aliases: standardAliases, 33 | }) 34 | } 35 | 36 | const extractAliases = ({ pluginOptions, context: { paths } }) => { 37 | const options = normalizePluginOptions(pluginOptions) 38 | 39 | const { appPath } = paths 40 | const { baseUrl } = options 41 | 42 | const absoluteBaseUrl = path.resolve(appPath, baseUrl) 43 | 44 | if (options.source === 'jsconfig') 45 | return extractAliasesFromConfig({ 46 | configPath: paths.appJsConfig, 47 | absoluteBaseUrl, 48 | }) 49 | 50 | if (options.source === 'tsconfig') 51 | return extractAliasesFromConfig({ 52 | configPath: options.tsConfigPath, 53 | absoluteBaseUrl, 54 | }) 55 | 56 | if (options.source === 'options') 57 | return normalizeAliases({ 58 | absoluteBaseUrl, 59 | aliases: options.aliases, 60 | }) 61 | } 62 | 63 | module.exports = extractAliases 64 | -------------------------------------------------------------------------------- /plugin/extract-aliases/index.test.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const extractAliases = require('.') 3 | 4 | describe('extract-aliases', () => { 5 | const appPath = path.resolve(__dirname, '../..') 6 | const appJsConfig = path.resolve(appPath, 'mocks/jsconfig.json') 7 | const tsConfigPath = path.resolve(appPath, 'mocks/tsconfig.paths.json') 8 | 9 | const context = { 10 | paths: { 11 | appPath, 12 | appJsConfig, 13 | }, 14 | } 15 | 16 | const inputs = { 17 | fromOptions: { 18 | pluginOptions: { 19 | source: 'options', 20 | aliases: { 21 | '@file': './src/file.js', 22 | '@file2': 'src/file2.js', 23 | '@dir': './src/dir', 24 | '@dir2': '././src/dir2/', 25 | '$dir3': 'src/dir3', 26 | 'my-package': './node_modules/some-package', 27 | 'external-package': '/absolute_path/external-package', 28 | '@material-ui': 'node_modules/@material-ui/ie-10/ie-10.js', 29 | }, 30 | }, 31 | context, 32 | }, 33 | fromJsConfig: { 34 | pluginOptions: { 35 | source: 'jsconfig', 36 | }, 37 | context, 38 | }, 39 | fromTsConfig: { 40 | pluginOptions: { 41 | source: 'tsconfig', 42 | tsConfigPath, 43 | }, 44 | context, 45 | }, 46 | } 47 | 48 | const snapshotName = 'extract-aliases/snap-1' 49 | 50 | test('should correctly extract aliases from options', () => { 51 | expect(extractAliases(inputs.fromOptions)).toMatchSnapshot(snapshotName) 52 | }) 53 | 54 | test('should correctly extract aliases from jsconfig', () => { 55 | expect(extractAliases(inputs.fromJsConfig)).toMatchSnapshot(snapshotName) 56 | }) 57 | 58 | test('should correctly extract aliases from tsconfig', () => { 59 | expect(extractAliases(inputs.fromTsConfig)).toMatchSnapshot(snapshotName) 60 | }) 61 | }) 62 | -------------------------------------------------------------------------------- /plugin/extract-aliases/normalize-aliases.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const normalizeAliases = ({ absoluteBaseUrl, aliases }) => { 4 | const result = {} 5 | 6 | function resolve(alias) { 7 | // remove trailing slash 8 | const cleanAlias = alias.replace(/\/$/, '') 9 | 10 | // make alias path absolute 11 | return path.resolve(absoluteBaseUrl, cleanAlias) 12 | } 13 | 14 | for (const aliasName in aliases) { 15 | const alias = aliases[aliasName] 16 | if (typeof alias === 'string') { 17 | result[aliasName] = resolve(alias) 18 | } else { 19 | const results = [] 20 | alias.forEach((anAlias) => { 21 | results.push(resolve(anAlias)) 22 | }) 23 | result[aliasName] = results 24 | } 25 | } 26 | 27 | return result 28 | } 29 | 30 | module.exports = normalizeAliases 31 | -------------------------------------------------------------------------------- /plugin/extract-aliases/normalize-aliases.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable sonarjs/no-duplicate-string */ 2 | const path = require('path') 3 | 4 | const normalizeAliases = require('./normalize-aliases') 5 | 6 | describe('normalize-aliases', () => { 7 | const appPath = path.resolve(__dirname, '../..') 8 | 9 | test('should correctly normalize aliases', () => { 10 | expect( 11 | normalizeAliases({ 12 | absoluteBaseUrl: path.resolve(appPath, '.'), 13 | aliases: { 14 | '@file': './src/file.js', 15 | '@file2': 'src/file2.js', 16 | '@dir': './src/dir', 17 | '@dir2': '././src/dir2/', 18 | '$dir3': 'src/dir3', 19 | 'my-package': './node_modules/some-package', 20 | 'external-package': '/absolute_path/external-package', 21 | '@material-ui': 'node_modules/@material-ui/ie-10/ie-10.js', 22 | }, 23 | }) 24 | ).toMatchSnapshot() 25 | 26 | expect( 27 | normalizeAliases({ 28 | absoluteBaseUrl: path.resolve(appPath, './src'), 29 | aliases: { 30 | '@file': './file.js', 31 | '@file2': 'file2.js', 32 | '@dir': './dir', 33 | '@dir2': '././dir2/', 34 | '$dir3': 'dir3', 35 | }, 36 | }) 37 | ).toMatchSnapshot() 38 | 39 | expect( 40 | normalizeAliases({ 41 | absoluteBaseUrl: path.resolve(appPath, './fallback_test'), 42 | aliases: { 43 | '$dir3': ['dir3', 'fallback/dir3'], 44 | '@file2': ['file2.js'], 45 | }, 46 | }) 47 | ).toMatchSnapshot() 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /plugin/filter-aliases.js: -------------------------------------------------------------------------------- 1 | function fromEntries(entries) { 2 | const result = {} 3 | for (const [key, value] of entries) { 4 | result[key] = value 5 | } 6 | return result 7 | } 8 | 9 | function filterAliases(aliases, filter) { 10 | const entries = Object.entries(aliases) 11 | const filteredEntries = entries.filter(filter) 12 | return fromEntries(filteredEntries) 13 | } 14 | 15 | exports.filterAliases = filterAliases 16 | -------------------------------------------------------------------------------- /plugin/filter-aliases.test.js: -------------------------------------------------------------------------------- 1 | const { filterAliases } = require('./filter-aliases') 2 | 3 | test('filter-aliases', () => { 4 | expect( 5 | filterAliases( 6 | { 7 | '@my-alias-one': 'sdfsdfsdf', 8 | '@my-alias-two': '123123', 9 | '@my-alias-three:': '321', 10 | }, 11 | ([key, value]) => { 12 | if (key === '@my-alias-one') return false 13 | if (value === '321') return false 14 | return true 15 | } 16 | ) 17 | ).toEqual({ 18 | '@my-alias-two': '123123', 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /plugin/generate-module-name-mapper.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const escapeStringForRegExp = require('./helpers/escape-string-for-regexp') 3 | 4 | const getModuleNameMapper = ({ aliases }) => { 5 | const moduleNameMapper = {} 6 | 7 | for (const unescapedAliasName in aliases) { 8 | const aliasName = escapeStringForRegExp(unescapedAliasName) 9 | const aliasPath = aliases[unescapedAliasName] 10 | 11 | const isFile = path.extname(aliasPath).length > 0 12 | 13 | if (isFile) { 14 | moduleNameMapper[`^${aliasName}$`] = aliasPath 15 | } else { 16 | moduleNameMapper[`^${aliasName}$`] = aliasPath 17 | moduleNameMapper[`^${aliasName}/(.*)$`] = `${aliasPath}/$1` 18 | } 19 | } 20 | 21 | return moduleNameMapper 22 | } 23 | 24 | module.exports = getModuleNameMapper 25 | -------------------------------------------------------------------------------- /plugin/generate-module-name-mapper.test.js: -------------------------------------------------------------------------------- 1 | const generateModuleNameMapper = require('./generate-module-name-mapper') 2 | 3 | describe('generate-module-name-mapper', () => { 4 | const paths = { 5 | file: '/some/absolute/path/to/file.js', 6 | dir: '/some/absolute/path/to/dir', 7 | } 8 | 9 | test('should correctly generate moduleNameMapper', () => { 10 | const moduleNameMapper = generateModuleNameMapper({ 11 | aliases: { 12 | '@file': paths.file, 13 | '@dir': paths.dir, 14 | }, 15 | }) 16 | 17 | expect(moduleNameMapper).toMatchSnapshot() 18 | }) 19 | 20 | test('should correctly generate moduleNameMapper when alias name have special RegExp characters', () => { 21 | const moduleNameMapper = generateModuleNameMapper({ 22 | aliases: { 23 | $file: paths.file, 24 | $dir: paths.dir, 25 | }, 26 | }) 27 | 28 | expect(moduleNameMapper).toMatchSnapshot() 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /plugin/helpers/escape-string-for-regexp.js: -------------------------------------------------------------------------------- 1 | module.exports = function escapeStringForRegExp(string) { 2 | return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') 3 | } 4 | -------------------------------------------------------------------------------- /plugin/index.js: -------------------------------------------------------------------------------- 1 | const createOverrider = require('./create-overrider') 2 | const generateModuleNameMapper = require('./generate-module-name-mapper') 3 | 4 | module.exports = { 5 | overrideWebpackConfig: createOverrider( 6 | ({ aliases, options }, { webpackConfig }) => ({ 7 | ...webpackConfig, 8 | resolve: { 9 | ...webpackConfig.resolve, 10 | alias: { 11 | ...webpackConfig.resolve.alias, 12 | ...aliases, 13 | }, 14 | plugins: options.unsafeAllowModulesOutsideOfSrc 15 | ? webpackConfig.resolve.plugins.filter(({ constructor }) => { 16 | if (!constructor) return true 17 | return constructor.name !== 'ModuleScopePlugin' 18 | }) 19 | : webpackConfig.resolve.plugins, 20 | }, 21 | }), 22 | { 23 | name: 'Webpack Config', 24 | aliasesPath: 'resolve.alias', 25 | } 26 | ), 27 | overrideJestConfig: createOverrider( 28 | ({ aliases }, { jestConfig }) => ({ 29 | ...jestConfig, 30 | moduleNameMapper: { 31 | ...jestConfig.moduleNameMapper, 32 | ...generateModuleNameMapper({ aliases }), 33 | }, 34 | }), 35 | { 36 | name: 'Jest Config', 37 | aliasesPath: 'moduleNameMapper', 38 | } 39 | ), 40 | } 41 | -------------------------------------------------------------------------------- /plugin/normalize-plugin-options.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef NormalizedConfig 3 | * @type {object} 4 | * @property {'jsconfig' | 'tsconfig' | 'options'} source 5 | * @property {string} baseUrl 6 | * @property {Object.} aliases 7 | * @property {string} [tsConfigPath] 8 | * @property {() => true} [filter] 9 | * @property {boolean} [unsafeAllowModulesOutsideOfSrc] 10 | */ 11 | 12 | /** 13 | * @param {any} originalOptions 14 | * @returns {NormalizedConfig} 15 | */ 16 | const normalizePluginOptions = (originalOptions) => { 17 | if (!originalOptions) 18 | return { 19 | source: 'options', 20 | baseUrl: './', 21 | aliases: null, 22 | debug: false, 23 | filter: () => true, 24 | unsafeAllowModulesOutsideOfSrc: false, 25 | } 26 | 27 | const { 28 | source = 'options', 29 | baseUrl = './', 30 | tsConfigPath, 31 | aliases = null, 32 | debug = false, 33 | filter = () => true, 34 | unsafeAllowModulesOutsideOfSrc = false, 35 | } = originalOptions 36 | 37 | if (source === 'jsconfig') 38 | return { 39 | source, 40 | baseUrl, 41 | debug, 42 | filter, 43 | unsafeAllowModulesOutsideOfSrc, 44 | } 45 | 46 | if (source === 'tsconfig') 47 | return { 48 | source, 49 | baseUrl, 50 | tsConfigPath, 51 | debug, 52 | filter, 53 | unsafeAllowModulesOutsideOfSrc, 54 | } 55 | 56 | return { 57 | source, 58 | baseUrl, 59 | aliases, 60 | debug, 61 | filter, 62 | unsafeAllowModulesOutsideOfSrc, 63 | } 64 | } 65 | 66 | module.exports = normalizePluginOptions 67 | -------------------------------------------------------------------------------- /plugin/normalize-plugin-options.test.js: -------------------------------------------------------------------------------- 1 | const normalize = require('./normalize-plugin-options') 2 | 3 | describe('normalize-plugin-options', () => { 4 | test('should return default config', () => { 5 | expect(normalize(undefined)).toMatchSnapshot() 6 | expect(normalize({})).toMatchSnapshot() 7 | }) 8 | 9 | test('should return jsconfig-specific config', () => { 10 | expect( 11 | normalize({ 12 | source: 'jsconfig', 13 | baseUrl: './', 14 | aliases: {}, 15 | }) 16 | ).toMatchSnapshot() 17 | }) 18 | 19 | test('should return tsconfig-specific config', () => { 20 | expect( 21 | normalize({ 22 | source: 'tsconfig', 23 | tsConfigPath: 'tsconfig.paths.json', 24 | aliases: {}, 25 | }) 26 | ).toMatchSnapshot() 27 | }) 28 | 29 | test('should return the same as an input', () => { 30 | expect( 31 | normalize({ 32 | source: 'options', 33 | baseUrl: './src', 34 | aliases: { 35 | '@file': './file.js', 36 | }, 37 | filter: () => true, 38 | unsafeAllowModulesOutsideOfSrc: true, 39 | }) 40 | ).toMatchSnapshot() 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /plugin/pre-check/check-config-contents.js: -------------------------------------------------------------------------------- 1 | const checkConfigContents = ({ 2 | unparsedConfig, 3 | configFileName, 4 | handleError, 5 | }) => { 6 | let config 7 | 8 | try { 9 | config = JSON.parse(unparsedConfig) 10 | } catch (error) { 11 | return handleError( 12 | `Cannot parse ${configFileName}. Please validate it on https://jsonformatter.curiousconcept.com.` 13 | ) 14 | } 15 | 16 | if (!config.compilerOptions) 17 | return handleError( 18 | `Property "compilerOptions" is missing in ${configFileName}` 19 | ) 20 | 21 | if (!config.compilerOptions.paths) 22 | return handleError( 23 | `Property "compilerOptions.paths" is missing in ${configFileName}` 24 | ) 25 | } 26 | 27 | module.exports = checkConfigContents 28 | -------------------------------------------------------------------------------- /plugin/pre-check/check-config-contents.test.js: -------------------------------------------------------------------------------- 1 | const check = require('./check-config-contents') 2 | 3 | describe('check-config-contents', () => { 4 | const handleErrorMock = jest.fn(() => {}) 5 | 6 | const configFileName = 'some-config.json' 7 | 8 | const mockedCheck = ({ unparsedConfig }) => 9 | check({ 10 | unparsedConfig, 11 | configFileName, 12 | handleError: handleErrorMock, 13 | }) 14 | 15 | test('should try to parse config contents', () => { 16 | mockedCheck({ 17 | unparsedConfig: 'invalid-json', 18 | }) 19 | 20 | expect(handleErrorMock).toHaveBeenLastCalledWith( 21 | `Cannot parse ${configFileName}. Please validate it on https://jsonformatter.curiousconcept.com.` 22 | ) 23 | }) 24 | 25 | const handyMockedCheck = (parsedConfig) => 26 | mockedCheck({ 27 | unparsedConfig: JSON.stringify(parsedConfig, null, 2), 28 | }) 29 | 30 | test('should check config contents', () => { 31 | handyMockedCheck({}) 32 | 33 | expect(handleErrorMock).toHaveBeenLastCalledWith( 34 | `Property "compilerOptions" is missing in ${configFileName}` 35 | ) 36 | 37 | handyMockedCheck({ 38 | compilerOptions: { 39 | baseUrl: 'src', 40 | }, 41 | }) 42 | 43 | expect(handleErrorMock).toHaveBeenLastCalledWith( 44 | `Property "compilerOptions.paths" is missing in ${configFileName}` 45 | ) 46 | }) 47 | }) 48 | -------------------------------------------------------------------------------- /plugin/pre-check/check-config-existence.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | 3 | const checkConfigExistence = ({ 4 | configName, 5 | configFileName, 6 | configPath, 7 | handleError, 8 | }) => { 9 | const isExist = fs.existsSync(configPath) 10 | 11 | if (!isExist) 12 | return handleError( 13 | `The "source" option is set to "${configName}",` + 14 | ` but no ${configFileName} was found in the project` 15 | ) 16 | } 17 | 18 | module.exports = checkConfigExistence 19 | -------------------------------------------------------------------------------- /plugin/pre-check/check-config-existence.test.js: -------------------------------------------------------------------------------- 1 | const check = require('./check-config-existence') 2 | 3 | describe('check-config-existence', () => { 4 | const handleErrorMock = jest.fn(() => {}) 5 | 6 | test('should check config existence', () => { 7 | const configName = 'some-config' 8 | const configFileName = 'path.json' 9 | const configPath = 'some/config/path.json' 10 | 11 | check({ 12 | configName, 13 | configFileName, 14 | configPath, 15 | handleError: handleErrorMock, 16 | }) 17 | 18 | expect(handleErrorMock).toHaveBeenLastCalledWith( 19 | `The "source" option is set to "${configName}",` + 20 | ` but no ${configFileName} was found in the project` 21 | ) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /plugin/pre-check/check-config.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | 4 | const exitWithError = require('../exit-with-error') 5 | const checkConfigExistence = require('./check-config-existence') 6 | const checkConfigContents = require('./check-config-contents') 7 | 8 | const checkConfig = ({ configName, configPath }) => { 9 | const configFileName = path.basename(configPath) 10 | 11 | checkConfigExistence({ 12 | configName, 13 | configFileName, 14 | configPath, 15 | handleError: exitWithError, 16 | }) 17 | 18 | const unparsedConfig = fs.readFileSync(configPath) 19 | 20 | checkConfigContents({ 21 | unparsedConfig, 22 | configFileName, 23 | handleError: exitWithError, 24 | }) 25 | } 26 | 27 | module.exports = checkConfig 28 | -------------------------------------------------------------------------------- /plugin/pre-check/check-options.js: -------------------------------------------------------------------------------- 1 | const normalizePluginOptions = require('../normalize-plugin-options') 2 | 3 | const checkOptions = ({ pluginOptions, handleError }) => { 4 | if (typeof pluginOptions === 'undefined') { 5 | return handleError('Plugin options should be specified') 6 | } 7 | 8 | if (typeof pluginOptions !== 'object') { 9 | return handleError('Plugin options should be an object') 10 | } 11 | 12 | const options = normalizePluginOptions(pluginOptions) 13 | 14 | const availableSources = ['jsconfig', 'tsconfig', 'options'] 15 | 16 | if (!availableSources.includes(options.source)) { 17 | const availableSourcesString = availableSources 18 | .map((s) => `"${s}"`) 19 | .join(', ') 20 | 21 | return handleError( 22 | 'You have provided an invalid aliases source.' + 23 | ` Available sources are: ${availableSourcesString}` 24 | ) 25 | } 26 | 27 | if ( 28 | options.source === 'tsconfig' && 29 | typeof options.tsConfigPath !== 'string' 30 | ) { 31 | return handleError( 32 | 'The "source" option is set to "tsconfig",' + 33 | ' but option "tsConfigPath" is missing or has incorrect value' 34 | ) 35 | } 36 | 37 | if (options.source === 'options') { 38 | if (typeof options.baseUrl !== 'string') { 39 | return handleError('The "baseUrl" option should be a string') 40 | } 41 | 42 | if (typeof options.aliases !== 'object' || options.aliases === null) 43 | return handleError( 44 | 'The "source" option is set to "options",' + 45 | ' but option "aliases" is missing or has incorrect value' 46 | ) 47 | } 48 | 49 | if (typeof options.debug !== 'boolean') { 50 | return handleError('The "debug" option should be a boolean value') 51 | } 52 | 53 | if (typeof options.filter !== 'function') { 54 | return handleError('The "filter" option should be a function') 55 | } 56 | 57 | if (typeof options.unsafeAllowModulesOutsideOfSrc !== 'boolean') { 58 | return handleError( 59 | 'The "unsafeAllowModulesOutsideOfSrc" option should be a boolean value' 60 | ) 61 | } 62 | } 63 | 64 | module.exports = checkOptions 65 | -------------------------------------------------------------------------------- /plugin/pre-check/check-options.test.js: -------------------------------------------------------------------------------- 1 | const check = require('./check-options') 2 | 3 | let handleErrorMock = jest.fn(() => {}) 4 | 5 | beforeEach(() => { 6 | handleErrorMock = jest.fn(() => {}) 7 | }) 8 | 9 | describe('check-options', () => { 10 | const mockedCheck = (pluginOptions) => 11 | check({ 12 | pluginOptions, 13 | handleError: handleErrorMock, 14 | }) 15 | 16 | test('should check pluginOptions type', () => { 17 | mockedCheck(undefined) 18 | 19 | expect(handleErrorMock).toHaveBeenLastCalledWith( 20 | 'Plugin options should be specified' 21 | ) 22 | 23 | mockedCheck(123) 24 | 25 | expect(handleErrorMock).toHaveBeenLastCalledWith( 26 | 'Plugin options should be an object' 27 | ) 28 | }) 29 | 30 | test('should not fail on a valid config', () => { 31 | mockedCheck({ 32 | aliases: {}, 33 | }) 34 | 35 | mockedCheck({ 36 | source: 'options', 37 | aliases: {}, 38 | }) 39 | 40 | mockedCheck({ 41 | source: 'options', 42 | aliases: {}, 43 | }) 44 | 45 | mockedCheck({ 46 | source: 'tsconfig', 47 | tsConfigPath: 'foo', 48 | }) 49 | 50 | mockedCheck({ 51 | source: 'jsconfig', 52 | }) 53 | 54 | mockedCheck({ 55 | aliases: {}, 56 | filter: () => true, 57 | }) 58 | 59 | mockedCheck({ 60 | aliases: {}, 61 | unsafeAllowModulesOutsideOfSrc: true, 62 | }) 63 | 64 | expect(handleErrorMock).toHaveBeenCalledTimes(0) 65 | }) 66 | 67 | test('should check pluginOptions.source', () => { 68 | mockedCheck({ 69 | source: 'unknown-source', 70 | }) 71 | 72 | const availableSources = ['jsconfig', 'tsconfig', 'options'] 73 | 74 | const availableSourcesString = availableSources 75 | .map((s) => `"${s}"`) 76 | .join(', ') 77 | 78 | expect(handleErrorMock).toHaveBeenLastCalledWith( 79 | 'You have provided an invalid aliases source.' + 80 | ` Available sources are: ${availableSourcesString}` 81 | ) 82 | }) 83 | 84 | test('should check "tsConfigPath" when source is "tsconfig"', () => { 85 | mockedCheck({ 86 | source: 'tsconfig', 87 | }) 88 | 89 | expect(handleErrorMock).toHaveBeenLastCalledWith( 90 | 'The "source" option is set to "tsconfig",' + 91 | ' but option "tsConfigPath" is missing or has incorrect value' 92 | ) 93 | }) 94 | 95 | test('should check "baseUrl" when source is "options"', () => { 96 | mockedCheck({ 97 | source: 'options', 98 | baseUrl: 345345, 99 | }) 100 | 101 | expect(handleErrorMock).toHaveBeenLastCalledWith( 102 | 'The "baseUrl" option should be a string' 103 | ) 104 | }) 105 | 106 | test('should check "aliases" when source is "options"', () => { 107 | mockedCheck({ 108 | source: 'options', 109 | }) 110 | 111 | expect(handleErrorMock).toHaveBeenLastCalledWith( 112 | 'The "source" option is set to "options",' + 113 | ' but option "aliases" is missing or has incorrect value' 114 | ) 115 | 116 | mockedCheck({ 117 | source: 'options', 118 | aliases: 123, 119 | }) 120 | 121 | expect(handleErrorMock).toHaveBeenLastCalledWith( 122 | 'The "source" option is set to "options",' + 123 | ' but option "aliases" is missing or has incorrect value' 124 | ) 125 | }) 126 | 127 | test('should check "filter"', () => { 128 | mockedCheck({ 129 | source: 'options', 130 | aliases: {}, 131 | filter: 35345, 132 | }) 133 | 134 | expect(handleErrorMock).toHaveBeenLastCalledWith( 135 | 'The "filter" option should be a function' 136 | ) 137 | }) 138 | 139 | test('should check "unsafeAllowModulesOutsideOfSrc"', () => { 140 | mockedCheck({ 141 | source: 'options', 142 | aliases: {}, 143 | unsafeAllowModulesOutsideOfSrc: 35345, 144 | }) 145 | 146 | expect(handleErrorMock).toHaveBeenLastCalledWith( 147 | 'The "unsafeAllowModulesOutsideOfSrc" option should be a boolean value' 148 | ) 149 | }) 150 | 151 | test('should check "debug"', () => { 152 | mockedCheck({ 153 | source: 'options', 154 | aliases: {}, 155 | debug: 35345, 156 | }) 157 | 158 | expect(handleErrorMock).toHaveBeenLastCalledWith( 159 | 'The "debug" option should be a boolean value' 160 | ) 161 | }) 162 | }) 163 | -------------------------------------------------------------------------------- /plugin/pre-check/index.js: -------------------------------------------------------------------------------- 1 | const normalizePluginOptions = require('../normalize-plugin-options') 2 | const exitWithError = require('../exit-with-error') 3 | const checkOptions = require('./check-options') 4 | const checkConfig = require('./check-config') 5 | 6 | const preCheck = ({ pluginOptions, context: { paths } }) => { 7 | checkOptions({ 8 | pluginOptions, 9 | handleError: exitWithError, 10 | }) 11 | 12 | const options = normalizePluginOptions(pluginOptions) 13 | 14 | if (options.source === 'jsconfig') 15 | checkConfig({ 16 | configName: 'jsconfig', 17 | configPath: paths.appJsConfig, 18 | }) 19 | 20 | if (options.source === 'tsconfig') 21 | checkConfig({ 22 | configName: 'tsconfig', 23 | configPath: options.tsConfigPath, 24 | }) 25 | } 26 | 27 | module.exports = preCheck 28 | --------------------------------------------------------------------------------