├── .github ├── dependabot.yml └── workflows │ ├── node.js.yml │ └── npm-publish.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src └── index.ts ├── test ├── index.test.ts └── test.tsconfig.json └── tsconfig.json /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "13:00" 8 | open-pull-requests-limit: 10 9 | ignore: 10 | - dependency-name: "@types/node" 11 | versions: 12 | - 14.14.22 13 | - 14.14.25 14 | - 14.14.27 15 | - 14.14.28 16 | - 14.14.30 17 | - 14.14.31 18 | - 14.14.32 19 | - 14.14.33 20 | - 14.14.34 21 | - 14.14.35 22 | - 14.14.36 23 | - 14.14.37 24 | - 14.14.39 25 | - 14.14.41 26 | - 15.0.0 27 | - dependency-name: "@types/jest" 28 | versions: 29 | - 26.0.20 30 | - 26.0.21 31 | - 26.0.22 32 | - dependency-name: ts-jest 33 | versions: 34 | - 26.5.0 35 | - 26.5.1 36 | - 26.5.2 37 | - 26.5.3 38 | - 26.5.4 39 | - dependency-name: typescript 40 | versions: 41 | - 3.9.8 42 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Build 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | test: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [12.x, 14.x, 16.x] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v2 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | cache: 'npm' 29 | - run: npm ci 30 | - run: npm run build --if-present 31 | - run: npm test 32 | - name: Coveralls 33 | uses: coverallsapp/github-action@master 34 | with: 35 | github-token: ${{ secrets.GITHUB_TOKEN }} 36 | flag-name: run-${{ matrix.node-version }}Upd 37 | parallel: true 38 | 39 | finish: 40 | needs: test 41 | runs-on: ubuntu-latest 42 | steps: 43 | - name: Coveralls Finished 44 | uses: coverallsapp/github-action@master 45 | with: 46 | github-token: ${{ secrets.github_token }} 47 | parallel-finished: true 48 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions/setup-node@v2 16 | with: 17 | node-version: 14 18 | - run: npm ci 19 | - run: npm test 20 | 21 | publish-npm: 22 | needs: build 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v2 26 | - uses: actions/setup-node@v2 27 | with: 28 | node-version: 14 29 | registry-url: https://registry.npmjs.org/ 30 | - run: npm ci 31 | - run: npm publish 32 | env: 33 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 34 | 35 | publish-gpr: 36 | needs: build 37 | runs-on: ubuntu-latest 38 | permissions: 39 | contents: read 40 | packages: write 41 | steps: 42 | - uses: actions/checkout@v2 43 | - uses: actions/setup-node@v2 44 | with: 45 | node-version: 14 46 | registry-url: https://npm.pkg.github.com/ 47 | - run: npm ci 48 | - run: npm publish 49 | env: 50 | NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/node,visualstudiocode 3 | # Edit at https://www.gitignore.io/?templates=node,visualstudiocode 4 | 5 | ### Node ### 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # Optional npm cache directory 48 | .npm 49 | 50 | # Optional eslint cache 51 | .eslintcache 52 | 53 | # Optional REPL history 54 | .node_repl_history 55 | 56 | # Output of 'npm pack' 57 | *.tgz 58 | 59 | # Yarn Integrity file 60 | .yarn-integrity 61 | 62 | # dotenv environment variables file 63 | .env 64 | .env.test 65 | 66 | # parcel-bundler cache (https://parceljs.org/) 67 | .cache 68 | 69 | # next.js build output 70 | .next 71 | 72 | # nuxt.js build output 73 | .nuxt 74 | 75 | # vuepress build output 76 | .vuepress/dist 77 | 78 | # Serverless directories 79 | .serverless/ 80 | 81 | # FuseBox cache 82 | .fusebox/ 83 | 84 | # DynamoDB Local files 85 | .dynamodb/ 86 | 87 | ### VisualStudioCode ### 88 | .vscode/* 89 | !.vscode/settings.json 90 | !.vscode/tasks.json 91 | !.vscode/launch.json 92 | !.vscode/extensions.json 93 | 94 | ### VisualStudioCode Patch ### 95 | # Ignore all local history of files 96 | .history 97 | 98 | # End of https://www.gitignore.io/api/node,visualstudiocode 99 | 100 | build 101 | lib 102 | output 103 | dist 104 | bundles 105 | tmp 106 | .localStorage 107 | Backupper.parameters.txt 108 | npm-debug.log 109 | jsconfig.json 110 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | This project adheres to [Semantic Versioning]. 4 | This file uses change log convention from [Keep a CHANGELOG]. 5 | 6 | ## [Unreleased] 7 | 8 | ## [1.3.0] - 2020-12-30 9 | ### Fixed 10 | - Fixed resolving paths with different working directory than `tsconfig.json` (Use the new `cwd` option) 11 | - Fixed support for multiple imports per line 12 | 13 | ## [1.2.0] - 2020-12-29 14 | ### Fixed 15 | - Fixed paths inside comments being resolved. 16 | - Fixed dependencies having security issues. 17 | - Fixed typings' path in `package.json` 18 | 19 | ## [1.1.0] - 2019-08-23 20 | ### Fixed 21 | - Fixed dependency issue and removed unused dependencies. 22 | 23 | ## [1.0.0] - 2019-08-23 24 | ### Added 25 | - Added support for `require()` and `import()` call imports. 26 | - Added a lot more tests to raise the coverage of the plugin. 27 | 28 | ## [0.2.1] - 2019-08-22 29 | ### Changed 30 | - Updated testing and CI 31 | ### Fixed 32 | - Fixed testing suite 33 | 34 | ## [0.2.0] - 2019-02-16 35 | ### Changed 36 | - Change some types to be more restrictive 37 | - Strip comments from compiled Javascript 38 | ### Fixed 39 | - Fix module exporting for ES5/ES6 users by specifying `module.exports` 40 | 41 | ## [0.1.5] - 2019-01-23 42 | ### Fixed 43 | - Fixed introduced bug 44 | 45 | ## [0.1.4] - 2019-01-23 46 | ### Changed 47 | - Begin adding some testing 48 | 49 | ## [0.1.3] - 2019-01-23 50 | ### Removed 51 | - Remove a `console.log` line that was used for debugging 52 | 53 | ## [0.1.2] - 2019-01-23 54 | ### Fixed 55 | - Fix `event-stream` dependency in both `package.json` and `package-lock.json` files 56 | 57 | ## [0.1.1] - 2019-01-23 58 | ### Changed 59 | - Lock `event-stream` to `3.3.4` under the recommendation of GitHub 60 | 61 | ## 0.1.0 - 2019-01-23 62 | ### Added 63 | - Initial commit. 64 | 65 | [Keep a CHANGELOG]: http://keepachangelog.com 66 | [Semantic Versioning]: http://semver.org/ 67 | 68 | [unreleased]: https://github.com/dhkatz/gulp-ts-alias/compare/1.3.0...HEAD 69 | [1.3.0]: https://github.com/dhkatz/gulp-ts-alias/compare/1.2.0...1.3.0 70 | [1.2.0]: https://github.com/dhkatz/gulp-ts-alias/compare/1.1.0...1.2.0 71 | [1.1.0]: https://github.com/dhkatz/gulp-ts-alias/compare/1.0.0...1.1.0 72 | [1.0.0]: https://github.com/dhkatz/gulp-ts-alias/compare/0.2.1...1.0.0 73 | [0.2.1]: https://github.com/dhkatz/gulp-ts-alias/compare/0.2.0...0.2.1 74 | [0.2.0]: https://github.com/dhkatz/gulp-ts-alias/compare/0.1.5...0.2.0 75 | [0.1.5]: https://github.com/dhkatz/gulp-ts-alias/compare/0.1.4...0.1.5 76 | [0.1.4]: https://github.com/dhkatz/gulp-ts-alias/compare/0.1.3...0.1.4 77 | [0.1.3]: https://github.com/dhkatz/gulp-ts-alias/compare/0.1.2...0.1.3 78 | [0.1.2]: https://github.com/dhkatz/gulp-ts-alias/compare/0.1.1...0.1.2 79 | [0.1.1]: https://github.com/dhkatz/gulp-ts-alias/compare/0.1.0...0.1.1 80 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 David Katz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @gulp-plugin/alias ![npm (custom registry)](https://img.shields.io/npm/v/@gulp-plugin/alias?logo=npm) [![GitHub Package Registry version](https://img.shields.io/github/release/gulp-plugin/alias.svg?label=gpr&logo=github)](https://github.com/gulp-plugin/alias/packages/896973) 2 | 3 | 4 | [![Build](https://github.com/dhkatz/gulp-ts-alias/actions/workflows/node.js.yml/badge.svg)](https://github.com/dhkatz/gulp-ts-alias/actions/workflows/node.js.yml) 5 | [![Coverage Status](https://coveralls.io/repos/github/dhkatz/gulp-ts-alias/badge.svg?branch=master)](https://coveralls.io/github/dhkatz/gulp-ts-alias?branch=master) [![dependencies Status](https://david-dm.org/gulp-plugin/alias/status.svg)](https://david-dm.org/gulp-plugin/alias) 6 | 7 | Resolve TypeScript import aliases and paths defined in `tsconfig`. 8 | 9 | ## Install 10 | 11 | `npm install --save-dev @gulp-plugin/alias` 12 | 13 | ## Information 14 | 15 | ### Features 16 | 17 | * Supports all import types: `import`, `require`, `await import()` 18 | * Supports wild card aliases 19 | 20 | ### Motivation 21 | 22 | There have been previous attempts at releasing Gulp plugins that accomplish something similar, but all have become unmaintained. 23 | 24 | For legacy’s sake, here is a list of previous packages/scripts that have been considered: 25 | 26 | [gulp-ts-paths](https://www.npmjs.com/package/gulp-ts-paths) 27 | 28 | [path-alias-resolver](https://gist.github.com/azarus/f369ee2ab0283ba0793b0ccf0e9ec590) 29 | 30 | **Note:** Imports within multiline comments may also be replaced. 31 | 32 | ## Usage 33 | 34 | ```javascript 35 | const typescript = require('gulp-typescript'); 36 | const sourcemaps = require('gulp-sourcemaps'); 37 | const alias = require('@gulp-plugin/alias'); 38 | 39 | const { config } = typescript.createProject('tsconfig.json'); 40 | 41 | function build() { 42 | const compiled = src('./src/**/*.ts') 43 | .pipe(alias(config)) 44 | // or .pipe(alias('tsconfig.json')) 45 | // or even .pipe(alias()) 46 | .pipe(sourcemaps.init()) 47 | .pipe(project()); 48 | 49 | return compiled.js 50 | .pipe(sourcemaps.write({ sourceRoot: file => path.relative(path.join(file.cwd, file.path), file.base) })) 51 | .pipe(dest('build/')) 52 | } 53 | ``` 54 | 55 | ## Example 56 | 57 | The following configuration is common in `tsconfig` configuration files 58 | 59 | ```json 60 | { 61 | "rootDir": "./src", 62 | "baseUrl": ".", 63 | "paths": { 64 | "@/*": ["src/*"] 65 | } 66 | } 67 | ``` 68 | 69 | In practice, these path aliases are often used in this fashion 70 | 71 | Input: 72 | 73 | ```typescript 74 | import express from 'express'; 75 | 76 | import A from './file'; // Normal relative import 77 | 78 | // Aliased import, resolves to some relative path to rootDir 79 | import B from '@/components'; 80 | ``` 81 | 82 | Output: 83 | 84 | ```typescript 85 | import express from 'express'; 86 | 87 | import A from './file'; 88 | 89 | // gulp-ts-alias finds the correct relative path 90 | // and replaces it before compilation 91 | import B from '../../components'; 92 | ``` 93 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gulp-plugin/alias", 3 | "version": "2.2.2", 4 | "description": "Use Gulp to resolve Typescript path aliases during compilation.", 5 | "main": "./lib/index.js", 6 | "types": "./typings/index.d.ts", 7 | "files": [ 8 | "lib/", 9 | "typings/", 10 | "src/", 11 | "test/" 12 | ], 13 | "scripts": { 14 | "build": "npm run build:types && npm run build:js", 15 | "build:types": "tsc --emitDeclarationOnly", 16 | "build:js": "tsc", 17 | "lint": "eslint src/**/* && tsc --noEmit", 18 | "test": "jest --ci --verbose --forceExit --detectOpenHandles --coverage", 19 | "test:coverage": "coveralls < coverage/lcov.info", 20 | "prepublishOnly": "npm run build" 21 | }, 22 | "author": { 23 | "name": "David Katz", 24 | "url": "https://github.com/dhkatz" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/gulp-plugin/alias" 29 | }, 30 | "license": "MIT", 31 | "keywords": [ 32 | "gulpplugin", 33 | "gulp", 34 | "typescript", 35 | "ts", 36 | "resolve", 37 | "relative", 38 | "paths" 39 | ], 40 | "publishConfig": { 41 | "registry": "https://registry.npmjs.org", 42 | "access": "public" 43 | }, 44 | "devDependencies": { 45 | "@types/jest": "^27.0.1", 46 | "@types/node": "^16.3.1", 47 | "@types/vinyl": "^2.0.2", 48 | "@typescript-eslint/eslint-plugin": "^4.28.2", 49 | "@typescript-eslint/parser": "^4.28.2", 50 | "coveralls": "^3.0.6", 51 | "eslint": "^7.30.0", 52 | "eslint-config-prettier": "^8.3.0", 53 | "eslint-plugin-prettier": "^4.0.0", 54 | "jest": "^26.6.3", 55 | "prettier": "^2.3.2", 56 | "ts-jest": "^26.4.4", 57 | "vinyl": "^2.2.0" 58 | }, 59 | "dependencies": { 60 | "typescript": "^4.3.5" 61 | }, 62 | "eslintConfig": { 63 | "extends": [ 64 | "plugin:@typescript-eslint/recommended", 65 | "plugin:prettier/recommended" 66 | ], 67 | "parser": "@typescript-eslint/parser", 68 | "rules": {} 69 | }, 70 | "prettier": { 71 | "semi": false, 72 | "singleQuote": true, 73 | "printWidth": 100 74 | }, 75 | "jest": { 76 | "preset": "ts-jest", 77 | "globals": { 78 | "ts-jest": { 79 | "tsconfig": "./tsconfig.json" 80 | } 81 | }, 82 | "testEnvironment": "node", 83 | "collectCoverage": true, 84 | "testRegex": "/test/.*\\.test\\.[jt]s$", 85 | "moduleFileExtensions": [ 86 | "ts", 87 | "js" 88 | ], 89 | "collectCoverageFrom": [ 90 | "src/**/*.{js,ts}", 91 | "!/node_modules/" 92 | ] 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { TransformCallback, Transform } from 'stream' 3 | import ts from 'typescript' 4 | 5 | import File = require('vinyl') 6 | 7 | export type CompilerOptions = ts.CompilerOptions 8 | 9 | export interface FileData { 10 | path: string 11 | index: number 12 | import: string 13 | } 14 | 15 | export interface PluginOptions { 16 | config?: ts.CompilerOptions | string 17 | cwd?: string 18 | } 19 | 20 | export type AliasPlugin = (pluginOptions: PluginOptions) => Transform 21 | 22 | const COMMENTED_PATTERN = /(\/\*(?:(?!\*\/).|[\n\r])*\*\/)|(\/\/[^\n\r]*(?:[\n\r]+|$))/ 23 | const IMPORT_PATTERNS = [ 24 | /from (["'])(.*?)\1/g, 25 | /import\((["'])(.*?)\1\)/g, 26 | /require\((["'])(.*?)\1\)/g, 27 | /import\s+(["'])(.*?)\1/g, 28 | ] 29 | 30 | function parseImports(file: ReadonlyArray, dir: string): FileData[] { 31 | return file.flatMap((line, index) => 32 | findImports(line).map((i) => ({ path: dir, index, import: i })) 33 | ) 34 | } 35 | 36 | function findImports(line: string): string[] | null { 37 | line = line.replace(COMMENTED_PATTERN, '') 38 | 39 | return IMPORT_PATTERNS.flatMap((pattern) => [...line.matchAll(pattern)].map((match) => match[2])) 40 | } 41 | 42 | function resolveImports( 43 | file: ReadonlyArray, 44 | imports: FileData[], 45 | options: ts.CompilerOptions 46 | ): string[] { 47 | const { baseUrl, paths, cwd } = options 48 | 49 | const aliases: { [key: string]: string[] | undefined } = {} 50 | for (const alias in paths) { 51 | /* istanbul ignore else */ 52 | if (paths.hasOwnProperty(alias)) { 53 | let resolved = alias 54 | if (alias.endsWith('/*')) { 55 | resolved = alias.replace('/*', '/') 56 | } 57 | 58 | aliases[resolved] = paths[alias] 59 | } 60 | } 61 | 62 | const lines: string[] = [...file] 63 | for (const imported of imports) { 64 | const line = file[imported.index] 65 | 66 | let resolved = '' 67 | for (const alias in aliases) { 68 | /* istanbul ignore else */ 69 | if (aliases.hasOwnProperty(alias) && imported.import.startsWith(alias)) { 70 | const choices: string[] | undefined = aliases[alias] 71 | 72 | if (choices !== undefined) { 73 | resolved = choices[0] 74 | if (resolved.endsWith('/*')) { 75 | resolved = resolved.replace('/*', '/') 76 | } 77 | 78 | resolved = imported.import.replace(alias, resolved) 79 | 80 | break 81 | } 82 | } 83 | } 84 | 85 | if (resolved.length < 1) { 86 | continue 87 | } 88 | 89 | const base = path.join(cwd as string, path.relative(cwd as string, baseUrl || './')) 90 | const current = path.relative(base, path.dirname(imported.path)) 91 | const target = path.relative(base, resolved) 92 | 93 | const relative = path.relative(current, target).replace(/\\/g, '/') 94 | 95 | lines[imported.index] = line.replace(imported.import, relative) 96 | } 97 | 98 | return lines 99 | } 100 | 101 | function resolveConfig(config?: string | ts.CompilerOptions, cwd?: string): ts.CompilerOptions { 102 | if (!config) { 103 | let configPath: string | undefined 104 | 105 | /* istanbul ignore if */ 106 | if (process.env.NODE_ENV !== 'test') { 107 | configPath = ts.findConfigFile(cwd, ts.sys.fileExists) 108 | } 109 | 110 | /* istanbul ignore else */ 111 | if (!configPath) { 112 | throw new Error("Could not find a valid 'tsconfig.json'") 113 | } else { 114 | const configFile = ts.readConfigFile(configPath, ts.sys.readFile) 115 | const { options } = ts.parseJsonConfigFileContent(configFile.config, ts.sys, cwd) 116 | 117 | return options 118 | } 119 | } 120 | 121 | if (typeof config === 'string') { 122 | const configFile = ts.readConfigFile(config, ts.sys.readFile) 123 | const { options } = ts.parseJsonConfigFileContent(configFile.config, ts.sys, cwd) 124 | 125 | return options 126 | } 127 | 128 | return config 129 | } 130 | 131 | const alias: AliasPlugin = ({ config, cwd }: PluginOptions) => { 132 | cwd = cwd === undefined ? process.cwd() : cwd === '.' ? './' : cwd 133 | 134 | const compilerOptions = resolveConfig(config, cwd) 135 | 136 | if (!compilerOptions.paths) { 137 | throw new Error("Unable to find the 'paths' property in the supplied configuration!") 138 | } 139 | 140 | if (compilerOptions.baseUrl === undefined || compilerOptions.baseUrl === '.') { 141 | compilerOptions.baseUrl = './' 142 | } 143 | 144 | compilerOptions.cwd = cwd 145 | 146 | return new Transform({ 147 | objectMode: true, 148 | transform(file: File, encoding: BufferEncoding, callback: TransformCallback) { 149 | /* istanbul ignore if */ 150 | if (file.isStream()) { 151 | return callback(new Error('Streaming is not supported.')) 152 | } 153 | 154 | if (file.isNull() || !file.contents) { 155 | return callback(undefined, file) 156 | } 157 | 158 | if (!file.path) { 159 | return callback( 160 | new Error('Received file with no path. Files must have path to be resolved.') 161 | ) 162 | } 163 | 164 | const lines = file.contents.toString().split('\n') 165 | const imports = parseImports(lines, file.path) 166 | 167 | if (imports.length === 0) { 168 | return callback(undefined, file) 169 | } 170 | 171 | const resolved = resolveImports(lines, imports, compilerOptions) 172 | 173 | file.contents = Buffer.from(resolved.join('\n')) 174 | 175 | callback(undefined, file) 176 | }, 177 | }) 178 | } 179 | 180 | export default alias 181 | 182 | // ES5/ES6 fallbacks 183 | module.exports = alias 184 | module.exports.default = alias 185 | -------------------------------------------------------------------------------- /test/index.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-non-null-assertion */ 2 | import File from 'vinyl' 3 | 4 | import alias, { PluginOptions, CompilerOptions } from '../src' 5 | import { Readable } from 'stream' 6 | 7 | interface Test { 8 | options: PluginOptions 9 | path: string 10 | input?: string 11 | output?: string 12 | error?: string 13 | env?: Record 14 | } 15 | 16 | const config: CompilerOptions = { 17 | paths: { components: ['./src/components/Component'] }, 18 | baseUrl: './', 19 | } 20 | 21 | const tests: Record = { 22 | ['should support ES6 imports']: { 23 | options: { config }, 24 | path: './src/pages/Page.ts', 25 | input: "import module from 'module'\nimport Component from 'components'", 26 | output: "import module from 'module'\nimport Component from '../components/Component'", 27 | }, 28 | ['should support dynamic imports']: { 29 | options: { config }, 30 | path: './src/pages/Page.ts', 31 | input: "const Component = await import('components')", 32 | output: "const Component = await import('../components/Component')", 33 | }, 34 | ["should support 'require()' imports"]: { 35 | options: { config }, 36 | path: './src/pages/Page.ts', 37 | input: "const module = require('module')\nconst Component = require('components')", 38 | output: 39 | "const module = require('module')\nconst Component = require('../components/Component')", 40 | }, 41 | ['should support side effect imports']: { 42 | options: { config }, 43 | path: './src/pages/Page.ts', 44 | input: "import module from 'module'\nimport 'components'", 45 | output: "import module from 'module'\nimport '../components/Component'", 46 | }, 47 | ['should support wild card aliases']: { 48 | options: { config: { paths: { '@/*': ['./src/*'] } } }, 49 | path: './src/pages/Page.ts', 50 | input: "import module from 'module'\nimport Component from '@/components'", 51 | output: "import module from 'module'\nimport Component from '../components'", 52 | }, 53 | ['should skip commented imports']: { 54 | options: { config }, 55 | path: './src/pages/Page.ts', 56 | input: "// import Component from 'components'\nimport module from 'module'", 57 | output: "// import Component from 'components'\nimport module from 'module'", 58 | }, 59 | ['should pass files with no aliases']: { 60 | options: { config }, 61 | path: './src/pages/Page.ts', 62 | input: "import module from 'module'", 63 | output: "import module from 'module'", 64 | }, 65 | ['should pass empty files']: { 66 | options: { config }, 67 | path: './src/pages/Page.ts', 68 | input: '', 69 | output: '', 70 | }, 71 | ['should pass invalid files']: { 72 | options: { config }, 73 | path: './src/pages/Page.ts', 74 | }, 75 | ["should work with no 'baseUrl'"]: { 76 | options: { config: { ...config, baseUrl: undefined } }, 77 | path: './src/pages/Page.ts', 78 | input: "import module from 'module'\nimport Component from 'components'", 79 | output: "import module from 'module'\nimport Component from '../components/Component'", 80 | }, 81 | ["should work with 'baseUrl' of '.'"]: { 82 | options: { config: { ...config, baseUrl: '.' } }, 83 | path: './src/pages/Page.ts', 84 | input: "import module from 'module'\nimport Component from 'components'", 85 | output: "import module from 'module'\nimport Component from '../components/Component'", 86 | }, 87 | ['should support different working directories']: { 88 | options: { config: { ...config, baseUrl: './src' }, cwd: '../' }, 89 | path: './src/pages/Page.ts', 90 | input: "import module from 'module'\nimport Component from 'components'", 91 | output: "import module from 'module'\nimport Component from '../components/Component'", 92 | }, 93 | ['should support multiple imports per line']: { 94 | options: { config }, 95 | path: './src/pages/Page.ts', 96 | input: "import module from 'module'; import Component from 'components'", 97 | output: "import module from 'module'; import Component from '../components/Component'", 98 | }, 99 | ['should support aliased node_modules']: { 100 | options: { 101 | config: { 102 | ...config, 103 | paths: { components: ['node_modules/@lib/Component'] }, 104 | }, 105 | }, 106 | path: './src/pages/Page.ts', 107 | input: "import module from 'module'\nimport Component from 'components'", 108 | output: 109 | "import module from 'module'\nimport Component from '../../node_modules/@lib/Component'", 110 | }, 111 | ["should support different working directory with 'node_modules'"]: { 112 | options: { 113 | config: { 114 | ...config, 115 | paths: { components: ['node_modules/@lib/Component'] }, 116 | baseUrl: './src', 117 | }, 118 | cwd: '../', 119 | }, 120 | path: './src/pages/Page.ts', 121 | input: "import module from 'module'\nimport Component from 'components'", 122 | output: 123 | "import module from 'module'\nimport Component from '../../node_modules/@lib/Component'", 124 | }, 125 | ['should support file name config']: { 126 | options: { config: './test/test.tsconfig.json' }, 127 | path: './src/pages/Page.ts', 128 | input: "import module from 'module'\nimport Component from 'components'", 129 | output: "import module from 'module'\nimport Component from '../components/Component'", 130 | }, 131 | ['should error with no config']: { 132 | options: {}, 133 | path: './src/pages/Page.ts', 134 | error: "Could not find a valid 'tsconfig.json'", 135 | }, 136 | ["should error with no 'paths' in config"]: { 137 | options: { config: { ...config, paths: undefined } }, 138 | path: './src/pages/Page.ts', 139 | error: "Unable to find the 'paths' property in the supplied configuration!", 140 | }, 141 | ["should error with no 'path' supplied"]: { 142 | options: { config }, 143 | path: undefined!, 144 | input: '', 145 | output: '', 146 | error: 'Received file with no path. Files must have path to be resolved.', 147 | }, 148 | } 149 | 150 | const run = async (test: Test): Promise => { 151 | return new Promise((resolve, reject) => { 152 | const input = new File({ 153 | contents: test.input == undefined ? undefined : Buffer.from(test.input), 154 | path: test.path, 155 | }) 156 | const stream = Readable.from([input], { objectMode: true }) 157 | 158 | stream 159 | .pipe(alias(test.options)) 160 | .on('data', (file: File) => { 161 | expect(file.contents?.toString()).toEqual(test.output) 162 | }) 163 | .on('end', resolve) 164 | .on('error', reject) 165 | }) 166 | } 167 | 168 | for (const [name, test] of Object.entries(tests)) { 169 | it(name, () => (test.error ? expect(run(test)).rejects.toThrow(test.error) : run(test))) 170 | } 171 | -------------------------------------------------------------------------------- /test/test.tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths": { 5 | "components": ["./src/components/Component"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Module/Target Options */ 4 | "target": "es6", 5 | "lib": ["es2019", "es2020.string"], 6 | "module": "commonjs", 7 | "sourceMap": true, 8 | "declaration": true, 9 | "outDir": "./lib", 10 | "declarationDir": "./typings", 11 | /* Strict Type-Checking Options */ 12 | "alwaysStrict": true, 13 | "noImplicitAny": true, 14 | /* Basic Options */ 15 | /* Extra Options */ 16 | "allowSyntheticDefaultImports": true, 17 | "esModuleInterop": true, 18 | "rootDir": "./src", 19 | "baseUrl": ".", 20 | "skipLibCheck": true 21 | }, 22 | "include": [ 23 | "src" 24 | ], 25 | "exclude": [ 26 | "lib", 27 | "node_modules" 28 | ] 29 | } 30 | --------------------------------------------------------------------------------