├── .editorconfig ├── .eslintrc.cjs ├── .gitattributes ├── .github ├── issue_template.md ├── pull_request_template.md └── workflows │ └── nodejs.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── __tests__ ├── context.spec.ts ├── diagnostics-format-host.spec.ts ├── diagnostics.spec.ts ├── fixtures │ ├── context.ts │ └── options.ts ├── get-options-overrides.spec.ts ├── host.spec.ts ├── integration │ ├── errors.spec.ts │ ├── fixtures │ │ ├── errors │ │ │ ├── import-type-error.ts │ │ │ ├── semantic.ts │ │ │ ├── syntax.ts │ │ │ ├── tsconfig.json │ │ │ └── type-only-import-with-error.ts │ │ ├── no-errors.ts │ │ ├── no-errors │ │ │ ├── index.ts │ │ │ ├── some-import.ts │ │ │ ├── some-js-import.d.ts │ │ │ ├── some-js-import.js │ │ │ ├── tsconfig.json │ │ │ ├── type-only-import-import.ts │ │ │ └── type-only-import.ts │ │ └── tsconfig.json │ ├── helpers.ts │ ├── no-errors.spec.ts │ └── watch.spec.ts ├── parse-tsconfig.spec.ts ├── rollingcache.spec.ts └── tslib.spec.ts ├── dist ├── context.d.ts ├── context.d.ts.map ├── diagnostics-format-host.d.ts ├── diagnostics-format-host.d.ts.map ├── diagnostics.d.ts ├── diagnostics.d.ts.map ├── get-options-overrides.d.ts ├── get-options-overrides.d.ts.map ├── host.d.ts ├── host.d.ts.map ├── icache.d.ts ├── icache.d.ts.map ├── index.d.ts ├── index.d.ts.map ├── ioptions.d.ts ├── ioptions.d.ts.map ├── parse-tsconfig.d.ts ├── parse-tsconfig.d.ts.map ├── rollingcache.d.ts ├── rollingcache.d.ts.map ├── rollup-plugin-typescript2.cjs.js ├── rollup-plugin-typescript2.cjs.js.map ├── rollup-plugin-typescript2.es.js ├── rollup-plugin-typescript2.es.js.map ├── tscache.d.ts ├── tscache.d.ts.map ├── tslib.d.ts ├── tslib.d.ts.map ├── tsproxy.d.ts └── tsproxy.d.ts.map ├── jest.config.js ├── package-lock.json ├── package.json ├── rollup.config.base.js ├── rollup.config.js ├── rollup.config.self.js ├── src ├── context.ts ├── diagnostics-format-host.ts ├── diagnostics.ts ├── get-options-overrides.ts ├── host.ts ├── icache.ts ├── index.ts ├── ioptions.ts ├── parse-tsconfig.ts ├── rollingcache.ts ├── tscache.ts ├── tslib.ts └── tsproxy.ts ├── tsconfig.base.json ├── tsconfig.json └── tsconfig.test.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | charset = utf-8 6 | indent_style = tab 7 | indent_size = 4 8 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | module.exports = { 3 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], 4 | parser: '@typescript-eslint/parser', 5 | plugins: ['@typescript-eslint'], 6 | root: true, 7 | ignorePatterns: ['dist/**', 'node_modules/**', 'build-self/**', '*.js', "__tests__/integration/fixtures/errors/**"], 8 | rules: { 9 | "@typescript-eslint/no-explicit-any": "off", // these are explicit, so they are intentional 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * -text 2 | dist/* linguist-generated 3 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | ## Troubleshooting 2 | 6 | 7 | 1. Does `tsc` have the same output? If so, please explain why this is incorrect behavior 8 | 13 | 14 | 1. Does your Rollup plugin order match [this plugin's compatibility](https://github.com/ezolenko/rollup-plugin-typescript2#compatibility)? If not, please elaborate 15 | 18 | 19 | 1. Can you create a [minimal example](https://stackoverflow.com/help/minimal-reproducible-example) that reproduces this behavior? Preferably, use [this environment](https://stackblitz.com/edit/rpt2-repro) for your reproduction 20 | 25 | 26 | ## What happens and why it is incorrect 27 | 31 | 32 | ## Environment 33 | 34 | 35 | ### Versions 36 | 43 | 44 | ```text 45 | 46 | ``` 47 | 48 | 49 |
50 |

rollup.config.js

:
51 | 52 | 53 | 54 | ```js 55 | 56 | ``` 57 | 58 |
59 | 60 | 61 |
62 |

tsconfig.json

:
63 | 64 | 65 | 66 | ```json5 67 | 68 | ``` 69 | 70 |
71 | 72 | 73 |
74 |

package.json

:
75 | 76 | 77 | 78 | ```json 79 | 80 | ``` 81 | 82 |
83 | 84 | 85 |
86 |

plugin output with verbosity 3

:
87 | 88 | 89 | 90 | ```text 91 | 92 | ``` 93 | 94 |
95 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Summary 2 | 3 | 7 | 8 | ## Details 9 | 10 | 13 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, 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: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | lint-build-test: 14 | name: Lint, Build, Test - Node ${{ matrix.node-version }}, ${{ matrix.os }} 15 | 16 | runs-on: ${{ matrix.os }} 17 | strategy: 18 | matrix: 19 | node-version: [18.x, 20.x] 20 | os: [ubuntu-latest, windows-latest, macOS-latest] 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v4 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | cache: 'npm' 29 | - run: npm ci 30 | - run: npm run lint 31 | - run: npm run build 32 | - run: npm run build-self 33 | - run: npm run build-self 34 | env: 35 | CI: true 36 | - run: npm run test:coverage 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /npm-debug.log 3 | /typings 4 | /.vscode 5 | /.idea 6 | /build-self 7 | /build 8 | .rpt2_cache 9 | *.swp 10 | *.swo 11 | coverage 12 | __temp 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | See release notes on [the GitHub Releases page](https://github.com/ezolenko/rollup-plugin-typescript2/releases). 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Reporting bugs 4 | 5 | Report any bugs [in the GitHub Issue Tracker](https://github.com/ezolenko/rollup-plugin-typescript2/issues). 6 | 7 | Please follow the issue template as closely as possible: 8 | 9 | - Attach your `tsconfig.json`, `package.json` (for versions of dependencies), `rollup.config.js`, and any other pieces of your environment that could influence module resolution, ambient types, and TS compilation. 10 | 11 | Some additional debugging steps you can take to help diagnose the issue: 12 | 13 | - Attach plugin output with `verbosity` option set to `3` (this will list all files being transpiled and their imports). 14 | - If it makes sense, check if running `tsc` directly produces similar results. 15 | - Check if you get the same problem with `clean` option set to `true` (might indicate a bug in the cache). 16 | - Check if the problem is reproducible after running `npm prune` to clear any rogue types from `node_modules` (by default TS grabs _all_ ambient types). 17 | 18 | ## Developing 19 | 20 | Use the [standard GitHub process](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/getting-started/about-collaborative-development-models#fork-and-pull-model) of forking, making a branch, creating a PR when ready, and fixing any failing checks on the PR. 21 | 22 | ### Linting and Style 23 | 24 | 1. Use an editor that supports [`editorconfig`](https://editorconfig.org/), or match the settings from [`.editorconfig`](./.editorconfig) manually. 25 | 1. Fix all linting problems with `npm run lint`. 26 | 27 | ### Testing 28 | 29 | 1. `npm test` to verify that all tests pass 30 | 1. `npm run test:watch` to run tests in watch mode while developing 31 | 1. `npm run test:coverage` to run tests and output a test coverage report 32 | 33 | While this repo now has an assortment of unit tests and integration tests, it still needs more integration tests with various scenarios and expected outcomes. 34 | 35 | ### Building and Self-Build 36 | 37 | One can test changes by doing a self-build; the plugin is part of its own build system. 38 | 39 | 1. make changes 40 | 1. run `npm run build` (uses last released version on npm) 41 | 1. check that you get expected changes in `dist` 42 | 1. run `npm run build-self` (uses fresh local build) 43 | 1. check `dist` for the expected changes 44 | 1. run `npm run build-self` _again_ to make sure plugin built by new version can still build itself 45 | 46 | If `build-self` breaks at some point, fix the problem and restart from the `build` step (a known good copy). 47 | 48 | ## Learning the codebase 49 | 50 | If you're looking to learn more about the codebase, either to contribute or just to educate yourself, this section contains an outline as well as tips and useful resources.
51 | These docs have been written by contributors who have gone through the process of learning the codebase themselves! 52 | 53 | ### General Overview 54 | 55 | Before starting, make sure you're familiar with the [`README`](README.md) in its entirety, as it describes this plugin's options that make up its API surface.
56 | It can also be useful to review some issues and have a "goal" in mind (especially if you're looking to contribute), so that you can focus on understanding how a certain part of the codebase works. 57 | 58 | 1. Can read [`get-options-overrides`](src/get-options-overrides.ts) as a quick intro to the codebase that dives a bit deeper into the `compilerOptions` that this plugin forces. 59 | - The [TSConfig Reference](https://www.typescriptlang.org/tsconfig) can be a helpful resource to understand these options. 60 | 1. Get a _quick_ read-through of [`index`](src/index.ts) (which is actually relatively small), to get a general understanding of this plugin's workflow. 61 | - Rollup's [Plugin docs](https://rollupjs.org/guide/en/#plugins-overview) are _very_ helpful to reference as you're going through, especially if you're not familiar with the Rollup Plugin API. 62 | 63 | ### Deeper Dive 64 | 65 | Once you have some understanding of the codebase's main workflow, you can start to dive deeper into pieces that require more domain knowledge.
66 | A useful resource as you dive deeper are the [unit tests](__tests__/). They're good to look through as you dig into a module to understand how it's used. 67 | 68 | 1. From here, you can start to read more of the modules that integrate with the TypeScript API, such as [`host`](src/host.ts) and [`parse-tsconfig`](src/parse-tsconfig.ts), and maybe how TS is imported in [`tsproxy`](src/tsproxy.ts) and [`tslib`](src/tslib.ts) 69 | - A _very_ useful reference here is the [TypeScript Wiki](https://github.com/microsoft/TypeScript/wiki), which has two main articles that are the basis for most Compiler integrations: 70 | - [Using the Compiler API](https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API) 71 | - [Using the Language Service API](https://github.com/microsoft/TypeScript/wiki/Using-the-Language-Service-API) 72 | - _NOTE_: These are fairly short and unfortunately leave a lot to be desired... especially when you consider that this plugin is actually one of the simpler integrations out there. 73 | 1. At this point, you may be ready to read the more complicated bits of [`index`](src/index.ts) in detail and see how it interacts with the other modules. 74 | - The [integration tests](__tests__/integration/) could be useful to review at this point as well. 75 | 1. Once you're pretty familiar with `index`, you can dive into some of the cache code in [`tscache`](src/tscache.ts) and [`rollingcache`](src/rollingcache.ts). 76 | 1. And finally, you can see some of the Rollup logging nuances in [`context`](src/context.ts) and then the TS logging nuances in [`diagnostics`](src/diagnostics.ts), and [`diagnostics-format-host`](src/diagnostics-format-host.ts) 77 | - While these are necessary to the implementation, they are fairly ancillary to understanding and working with the codebase. 78 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Eugene Zolenko 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 | # rollup-plugin-typescript2 2 | 3 | [![npm-version](https://img.shields.io/npm/v/rollup-plugin-typescript2.svg?maxAge=259200)](https://npmjs.org/package/rollup-plugin-typescript2) 4 | [![npm-monthly-downloads](https://img.shields.io/npm/dm/rollup-plugin-typescript2.svg?maxAge=259200)](https://npmjs.org/package/rollup-plugin-typescript2) 5 | [![Node.js CI](https://github.com/ezolenko/rollup-plugin-typescript2/workflows/Node.js%20CI/badge.svg)](https://github.com/ezolenko/rollup-plugin-typescript2/actions?query=workflow%3A"Node.js+CI") 6 | 7 | Rollup plugin for typescript with compiler errors. 8 | 9 | This is a rewrite of the [original](https://github.com/rollup/rollup-plugin-typescript/tree/v0.8.1) `rollup-plugin-typescript`, starting and borrowing from [this fork](https://github.com/alexlur/rollup-plugin-typescript). 10 | 11 | This version is somewhat slower than the original, but it will print out TypeScript syntactic and semantic diagnostic messages (the main reason for using TypeScript after all). 12 | 13 | ## Installation 14 | 15 | ```bash 16 | # with npm 17 | npm install rollup-plugin-typescript2 typescript tslib --save-dev 18 | # with yarn 19 | yarn add rollup-plugin-typescript2 typescript tslib --dev 20 | ``` 21 | 22 | ## Usage 23 | 24 | ```js 25 | // rollup.config.js 26 | import typescript from 'rollup-plugin-typescript2'; 27 | 28 | export default { 29 | input: './main.ts', 30 | 31 | plugins: [ 32 | typescript(/*{ plugin options }*/) 33 | ] 34 | } 35 | ``` 36 | 37 | This plugin inherits all compiler options and file lists from your `tsconfig.json` file. 38 | If your `tsconfig` has another name or another relative path from the root directory, see `tsconfigDefaults`, `tsconfig`, and `tsconfigOverride` options below. 39 | This also allows for passing in different `tsconfig` files depending on your build target. 40 | 41 | ### Some compiler options are forced 42 | 43 | * `noEmitHelpers`: false 44 | * `importHelpers`: true 45 | * `noResolve`: false 46 | * `noEmit`: false (Rollup controls emit) 47 | * `noEmitOnError`: false (Rollup controls emit. See [#254](https://github.com/ezolenko/rollup-plugin-typescript2/issues/254) and the `abortOnError` plugin option below) 48 | * `inlineSourceMap`: false (see [#71](https://github.com/ezolenko/rollup-plugin-typescript2/issues/71)) 49 | * `outDir`: `./placeholder` in cache root (see [#83](https://github.com/ezolenko/rollup-plugin-typescript2/issues/83) and [Microsoft/TypeScript#24715](https://github.com/Microsoft/TypeScript/issues/24715)) 50 | * `declarationDir`: Rollup's `output.file` or `output.dir` (*unless `useTsconfigDeclarationDir` is true in the plugin options*) 51 | * `allowNonTsExtensions`: true to let other plugins on the chain generate typescript; update plugin's `include` filter to pick them up (see [#111](https://github.com/ezolenko/rollup-plugin-typescript2/issues/111)) 52 | 53 | ### Some compiler options have more than one compatible value 54 | 55 | * `module`: defaults to `ES2015`. Other valid values are `ES2020`, `ES2022` and `ESNext` (required for dynamic imports, see [#54](https://github.com/ezolenko/rollup-plugin-typescript2/issues/54)). 56 | 57 | * `moduleResolution`: defaults to `node10` (same as `node`), but value from tsconfig is used if specified. Other valid (but mostly untested) values are `node16`, `nodenext` and `bundler`. If in doubt, use `node10`. 58 | * `classic` is [deprecated](https://www.typescriptlang.org/docs/handbook/module-resolution.html) and changed to `node10`. It also breaks this plugin, see [#12](https://github.com/ezolenko/rollup-plugin-typescript2/issues/12) and [#14](https://github.com/ezolenko/rollup-plugin-typescript2/issues/14). 59 | 60 | ### Some options need additional configuration on plugin side 61 | 62 | * `allowJs`: lets TypeScript process JS files as well. If you use it, modify this plugin's `include` option to add `"*.js+(|x)", "**/*.js+(|x)"` (might also want to `exclude` `"**/node_modules/**/*"`, as it can slow down the build significantly). 63 | 64 | ### Compatibility 65 | 66 | #### @rollup/plugin-node-resolve 67 | 68 | Must be before `rollup-plugin-typescript2` in the plugin list, especially when the `browser: true` option is used (see [#66](https://github.com/ezolenko/rollup-plugin-typescript2/issues/66)). 69 | 70 | #### @rollup/plugin-commonjs 71 | 72 | See the explanation for `rollupCommonJSResolveHack` option below. 73 | 74 | #### @rollup/plugin-babel 75 | 76 | This plugin transpiles code, but doesn't change file extensions. `@rollup/plugin-babel` only looks at code with these extensions [by default](https://github.com/rollup/plugins/tree/master/packages/babel#extensions): `.js,.jsx,.es6,.es,.mjs`. To workaround this, add `.ts` and `.tsx` to its list of extensions. 77 | 78 | ```js 79 | // ... 80 | import { DEFAULT_EXTENSIONS } from '@babel/core'; 81 | // ... 82 | babel({ 83 | extensions: [ 84 | ...DEFAULT_EXTENSIONS, 85 | '.ts', 86 | '.tsx' 87 | ] 88 | }), 89 | // ... 90 | ``` 91 | 92 | See [#108](https://github.com/ezolenko/rollup-plugin-typescript2/issues/108) 93 | 94 | ### Plugin options 95 | 96 | * `cwd`: `string` 97 | 98 | The current working directory. Defaults to `process.cwd()`. 99 | 100 | * `tsconfigDefaults`: `{}` 101 | 102 | The object passed as `tsconfigDefaults` will be merged with the loaded `tsconfig.json`. 103 | The final config passed to TypeScript will be the result of values in `tsconfigDefaults` replaced by values in the loaded `tsconfig.json`, replaced by values in `tsconfigOverride`, and then replaced by forced `compilerOptions` overrides on top of that (see above). 104 | 105 | For simplicity and other tools' sake, try to minimize the usage of defaults and overrides and keep everything in a `tsconfig.json` file (`tsconfig`s can themselves be chained with [`extends`](https://www.typescriptlang.org/tsconfig#extends), so save some turtles). 106 | 107 | ```js 108 | let defaults = { compilerOptions: { declaration: true } }; 109 | let override = { compilerOptions: { declaration: false } }; 110 | 111 | // ... 112 | plugins: [ 113 | typescript({ 114 | tsconfigDefaults: defaults, 115 | tsconfig: "tsconfig.json", 116 | tsconfigOverride: override 117 | }) 118 | ] 119 | ``` 120 | 121 | This is a [deep merge](https://lodash.com/docs/4.17.4#merge): objects are merged, arrays are merged by index, primitives are replaced, etc. 122 | Increase `verbosity` to `3` and look for `parsed tsconfig` if you get something unexpected. 123 | 124 | * `tsconfig`: `undefined` 125 | 126 | Path to `tsconfig.json`. 127 | Set this if your `tsconfig` has another name or relative location from the project directory. 128 | 129 | By default, will try to load `./tsconfig.json`, but will not fail if the file is missing, unless the value is explicitly set. 130 | 131 | * `tsconfigOverride`: `{}` 132 | 133 | See `tsconfigDefaults`. 134 | 135 | * `check`: true 136 | 137 | Set to false to avoid doing any diagnostic checks on the code. 138 | Setting to false is sometimes referred to as `transpileOnly` by other TypeScript integrations. 139 | 140 | * `verbosity`: 1 141 | 142 | - 0 -- Error 143 | - 1 -- Warning 144 | - 2 -- Info 145 | - 3 -- Debug 146 | 147 | * `clean`: false 148 | 149 | Set to true to disable the cache and do a clean build. 150 | This also wipes any existing cache. 151 | 152 | * `cacheRoot`: `node_modules/.cache/rollup-plugin-typescript2` 153 | 154 | Path to cache. 155 | Defaults to a folder in `node_modules`. 156 | 157 | * `include`: `[ "*.ts+(|x)", "**/*.ts+(|x)", "**/*.cts", "**/*.mts" ]` 158 | 159 | By default compiles all `.ts` and `.tsx` files with TypeScript. 160 | 161 | * `exclude`: `[ "*.d.ts", "**/*.d.ts", "**/*.d.cts", "**/*.d.mts" ]` 162 | 163 | But excludes type definitions. 164 | 165 | * `abortOnError`: true 166 | 167 | Bail out on first syntactic or semantic error. 168 | In some cases, setting this to false will result in an exception in Rollup itself (for example, unresolvable imports). 169 | 170 | * `rollupCommonJSResolveHack`: false 171 | 172 | _Deprecated_. OS native paths are now _always_ used since [`0.30.0`](https://github.com/ezolenko/rollup-plugin-typescript2/releases/0.30.0) (see [#251](https://github.com/ezolenko/rollup-plugin-typescript2/pull/251)), so this no longer has any effect -- as if it is always `true`. 173 | 174 | * `objectHashIgnoreUnknownHack`: false 175 | 176 | The plugin uses your Rollup config as part of its cache key. 177 | `object-hash` is used to generate a hash, but it can have trouble with some uncommon types of elements. 178 | Setting this option to true will make `object-hash` ignore unknowns, at the cost of not invalidating the cache if ignored elements are changed. 179 | 180 | Only enable this option if you need it (e.g. if you get `Error: Unknown object type "xxx"`) and make sure to run with `clean: true` once in a while and definitely before a release. 181 | (See [#105](https://github.com/ezolenko/rollup-plugin-typescript2/issues/105) and [#203](https://github.com/ezolenko/rollup-plugin-typescript2/pull/203)) 182 | 183 | * `useTsconfigDeclarationDir`: false 184 | 185 | If true, declaration files will be emitted in the [`declarationDir`](https://www.typescriptlang.org/tsconfig#declarationDir) given in the `tsconfig`. 186 | If false, declaration files will be placed inside the destination directory given in the Rollup configuration. 187 | 188 | Set to false if any other Rollup plugins need access to declaration files. 189 | 190 | * `typescript`: peerDependency 191 | 192 | If you'd like to use a different version of TS than the peerDependency, you can import a different TypeScript module and pass it in as `typescript: require("path/to/other/typescript")`. 193 | 194 | You can also use an alternative TypeScript implementation, such as [`ttypescript`](https://github.com/cevek/ttypescript), with this option. 195 | 196 | Must be TS 2.0+; things might break if the compiler interfaces changed enough from what the plugin was built against. 197 | 198 | * `transformers`: `undefined` 199 | 200 | **experimental**, TypeScript 2.4.1+ 201 | 202 | Transformers will likely be available in `tsconfig` eventually, so this is not a stable interface (see [Microsoft/TypeScript#14419](https://github.com/Microsoft/TypeScript/issues/14419)). 203 | 204 | For example, integrating [kimamula/ts-transformer-keys](https://github.com/kimamula/ts-transformer-keys): 205 | 206 | ```js 207 | const keysTransformer = require('ts-transformer-keys/transformer').default; 208 | const transformer = (service) => ({ 209 | before: [ keysTransformer(service.getProgram()) ], 210 | after: [] 211 | }); 212 | 213 | // ... 214 | plugins: [ 215 | typescript({ transformers: [transformer] }) 216 | ] 217 | ``` 218 | 219 | ### Declarations 220 | 221 | This plugin respects [`declaration: true`](https://www.typescriptlang.org/tsconfig#declaration) in your `tsconfig.json` file. 222 | When set, it will emit `*.d.ts` files for your bundle. 223 | The resulting file(s) can then be used with the `types` property in your `package.json` file as described [here](https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html).
224 | By default, the declaration files will be located in the same directory as the generated Rollup bundle. 225 | If you want to override this behavior and instead use [`declarationDir`](https://www.typescriptlang.org/tsconfig#declarationDir), set `useTsconfigDeclarationDir: true` in the plugin options. 226 | 227 | The above also applies to [`declarationMap: true`](https://www.typescriptlang.org/tsconfig#declarationMap) and `*.d.ts.map` files for your bundle. 228 | 229 | This plugin also respects [`emitDeclarationOnly: true`](https://www.typescriptlang.org/tsconfig#emitDeclarationOnly) and will only emit declarations (and declaration maps, if enabled) if set in your `tsconfig.json`. 230 | If you use `emitDeclarationOnly`, you will need another plugin to compile any TypeScript sources, such as `@rollup/plugin-babel`, `rollup-plugin-esbuild`, `rollup-plugin-swc`, etc. 231 | When composing Rollup plugins this way, `rollup-plugin-typescript2` will perform type-checking and declaration generation, while another plugin performs the TypeScript to JavaScript compilation.
232 | Some scenarios where this can be particularly useful: you want to use Babel plugins on TypeScript source, or you want declarations and type-checking for your Vite builds (**NOTE**: this space has not been fully explored yet). 233 | 234 | ### Watch mode 235 | 236 | The way TypeScript handles type-only imports and ambient types effectively hides them from Rollup's watch mode, because import statements are not generated and changing them doesn't trigger a rebuild. 237 | 238 | Otherwise the plugin should work in watch mode. Make sure to run a normal build after watch session to catch any type errors. 239 | 240 | ### Requirements 241 | 242 | * TypeScript `2.4+` 243 | * Rollup `1.26.3+` 244 | * Node `6.4.0+` (basic ES6 support) 245 | 246 | ### Reporting bugs and Contributing 247 | 248 | See [CONTRIBUTING.md](./CONTRIBUTING.md) 249 | -------------------------------------------------------------------------------- /__tests__/context.spec.ts: -------------------------------------------------------------------------------- 1 | import { jest, test, expect } from "@jest/globals"; 2 | 3 | import { makeContext } from "./fixtures/context"; 4 | import { RollupContext, VerbosityLevel } from "../src/context"; 5 | 6 | (global as any).console = { 7 | warn: jest.fn(), 8 | log: jest.fn(), 9 | info: jest.fn(), 10 | }; 11 | 12 | test("RollupContext", () => { 13 | const innerContext = makeContext(); 14 | const context = new RollupContext(5 as VerbosityLevel, false, innerContext); 15 | 16 | context.warn("test"); 17 | expect(innerContext.warn).toHaveBeenLastCalledWith("test"); 18 | 19 | context.warn(() => "test2"); 20 | expect(innerContext.warn).toHaveBeenLastCalledWith("test2"); 21 | 22 | context.error("test!"); 23 | expect(innerContext.warn).toHaveBeenLastCalledWith("test!"); 24 | 25 | context.error(() => "test2!"); 26 | expect(innerContext.warn).toHaveBeenLastCalledWith("test2!"); 27 | 28 | context.info("test3"); 29 | expect(console.log).toHaveBeenLastCalledWith("test3"); 30 | 31 | context.info(() => "test4"); 32 | expect(console.log).toHaveBeenLastCalledWith("test4"); 33 | 34 | context.debug("test5"); 35 | expect(console.log).toHaveBeenLastCalledWith("test5"); 36 | 37 | context.debug(() => "test6"); 38 | expect(console.log).toHaveBeenLastCalledWith("test6"); 39 | }); 40 | 41 | test("RollupContext with 0 verbosity", () => { 42 | const innerContext = makeContext(); 43 | const context = new RollupContext(VerbosityLevel.Error, false, innerContext); 44 | 45 | context.debug("verbosity is too low here"); 46 | expect(innerContext.debug).not.toBeCalled(); 47 | context.info("verbosity is too low here"); 48 | expect(innerContext.debug).not.toBeCalled(); 49 | context.warn("verbosity is too low here") 50 | expect(innerContext.warn).not.toBeCalled(); 51 | }); 52 | 53 | test("RollupContext.error + debug negative verbosity", () => { 54 | const innerContext = makeContext(); 55 | const context = new RollupContext(-100 as VerbosityLevel, true, innerContext); 56 | 57 | context.error("verbosity is too low here"); 58 | expect(innerContext.error).not.toBeCalled(); 59 | context.debug("verbosity is too low here"); 60 | expect(innerContext.debug).not.toBeCalled(); 61 | }); 62 | 63 | test("RollupContext.error with bail", () => { 64 | const innerContext = makeContext(); 65 | const context = new RollupContext(5 as VerbosityLevel, true, innerContext); 66 | 67 | context.error("bail"); 68 | expect(innerContext.error).toHaveBeenLastCalledWith("bail"); 69 | }); 70 | -------------------------------------------------------------------------------- /__tests__/diagnostics-format-host.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "@jest/globals"; 2 | import * as ts from "typescript"; 3 | 4 | import { setTypescriptModule } from "../src/tsproxy"; 5 | import { formatHost } from "../src/diagnostics-format-host"; 6 | 7 | setTypescriptModule(ts); 8 | 9 | test("formatHost", () => { 10 | expect(formatHost.getCurrentDirectory()).toEqual(process.cwd()); 11 | expect(formatHost.getCanonicalFileName("package.json")).toEqual("package.json"); 12 | expect(formatHost.getNewLine()).toEqual(ts.sys.newLine); 13 | }); 14 | -------------------------------------------------------------------------------- /__tests__/diagnostics.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "@jest/globals"; 2 | import * as ts from "typescript"; 3 | import { red } from "colors/safe"; 4 | 5 | import { makeContext } from "./fixtures/context"; 6 | import { setTypescriptModule } from "../src/tsproxy"; 7 | import { formatHost } from "../src/diagnostics-format-host"; 8 | import { convertDiagnostic, printDiagnostics } from "../src/diagnostics"; 9 | 10 | setTypescriptModule(ts); 11 | 12 | const tsDiagnostic = { 13 | file: undefined, 14 | start: undefined, 15 | length: undefined, 16 | messageText: "Compiler option 'include' requires a value of type Array.", 17 | category: ts.DiagnosticCategory.Error, 18 | code: 5024, 19 | reportsUnnecessary: undefined, 20 | reportsDeprecated: undefined, 21 | }; 22 | 23 | const diagnostic = { 24 | flatMessage: "Compiler option 'include' requires a value of type Array.", 25 | formatted: `\x1B[91merror\x1B[0m\x1B[90m TS5024: \x1B[0mCompiler option 'include' requires a value of type Array.${formatHost.getNewLine()}`, 26 | category: ts.DiagnosticCategory.Error, 27 | code: 5024, 28 | type: "config", 29 | }; 30 | 31 | test("convertDiagnostic", () => { 32 | expect(convertDiagnostic("config", [tsDiagnostic])).toStrictEqual([diagnostic]); 33 | }); 34 | 35 | test("printDiagnostics - categories", () => { 36 | const context = makeContext(); 37 | 38 | printDiagnostics(context, [diagnostic]); 39 | expect(context.error).toHaveBeenLastCalledWith(diagnostic.formatted); 40 | 41 | printDiagnostics(context, [{ ...diagnostic, category: ts.DiagnosticCategory.Warning } ]); 42 | expect(context.warn).toHaveBeenLastCalledWith(diagnostic.formatted); 43 | 44 | printDiagnostics(context, [{ ...diagnostic, category: ts.DiagnosticCategory.Suggestion } ]); // default case 45 | expect(context.warn).toHaveBeenLastCalledWith(diagnostic.formatted); 46 | 47 | printDiagnostics(context, [{ ...diagnostic, category: ts.DiagnosticCategory.Message } ]); 48 | expect(context.info).toHaveBeenLastCalledWith(diagnostic.formatted); 49 | 50 | // should match exactly, no more 51 | expect(context.error).toBeCalledTimes(1); 52 | expect(context.warn).toBeCalledTimes(2) 53 | expect(context.info).toBeCalledTimes(1); 54 | expect(context.debug).toBeCalledTimes(0); 55 | }); 56 | 57 | test("printDiagnostics - formatting / style", () => { 58 | const context = makeContext(); 59 | const category = "error"; // string version 60 | 61 | printDiagnostics(context, [diagnostic], false); 62 | expect(context.error).toHaveBeenLastCalledWith(`${diagnostic.type} ${category} TS${diagnostic.code}: ${red(diagnostic.flatMessage)}`); 63 | 64 | const fileLine = "0" 65 | printDiagnostics(context, [{ ...diagnostic, fileLine }], false); 66 | expect(context.error).toHaveBeenLastCalledWith(`${fileLine}: ${diagnostic.type} ${category} TS${diagnostic.code}: ${red(diagnostic.flatMessage)}`); 67 | 68 | // should match exactly, no more 69 | expect(context.error).toBeCalledTimes(2); 70 | expect(context.warn).toBeCalledTimes(0) 71 | expect(context.info).toBeCalledTimes(0); 72 | expect(context.debug).toBeCalledTimes(0); 73 | }); 74 | -------------------------------------------------------------------------------- /__tests__/fixtures/context.ts: -------------------------------------------------------------------------------- 1 | import { jest } from "@jest/globals"; 2 | import { PluginContext } from "rollup"; 3 | 4 | import { RollupContext } from "../../src/context"; 5 | 6 | // if given a function, make sure to call it (for code coverage etc) 7 | function returnText (message: string | (() => string)) { 8 | if (typeof message === "string") 9 | return message; 10 | 11 | return message(); 12 | } 13 | 14 | export function makeContext(): PluginContext & RollupContext { 15 | return { 16 | error: jest.fn(returnText), 17 | warn: jest.fn(returnText), 18 | info: jest.fn(returnText), 19 | debug: jest.fn(returnText), 20 | } as unknown as PluginContext & RollupContext; 21 | } 22 | -------------------------------------------------------------------------------- /__tests__/fixtures/options.ts: -------------------------------------------------------------------------------- 1 | import * as ts from "typescript"; 2 | 3 | import { setTypescriptModule } from "../../src/tsproxy"; 4 | import { IOptions } from "../../src/ioptions"; 5 | 6 | setTypescriptModule(ts); 7 | 8 | export function makeOptions(cacheDir: string, cwd: string): IOptions { 9 | return { 10 | include: ["*.ts+(|x)", "**/*.ts+(|x)"], 11 | exclude: ["*.d.ts", "**/*.d.ts"], 12 | check: false, 13 | verbosity: 5, 14 | clean: false, 15 | cacheRoot: cacheDir, 16 | cwd, 17 | abortOnError: false, 18 | rollupCommonJSResolveHack: false, 19 | typescript: ts, 20 | objectHashIgnoreUnknownHack: false, 21 | tsconfigOverride: null, 22 | useTsconfigDeclarationDir: false, 23 | tsconfigDefaults: null, 24 | sourceMapCallback: (id: string, map: string): void => { 25 | console.log(id + map); 26 | }, 27 | transformers: [(ls: ts.LanguageService) => { 28 | console.log(ls); 29 | return {}; 30 | }], 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /__tests__/get-options-overrides.spec.ts: -------------------------------------------------------------------------------- 1 | import { afterAll, test, expect } from "@jest/globals"; 2 | import * as path from "path"; 3 | import * as ts from "typescript"; 4 | import { normalizePath as normalize } from "@rollup/pluginutils"; 5 | import { remove } from "fs-extra"; 6 | 7 | import { makeOptions } from "./fixtures/options"; 8 | import { makeContext } from "./fixtures/context"; 9 | import { getOptionsOverrides, createFilter } from "../src/get-options-overrides"; 10 | 11 | const local = (x: string) => normalize(path.resolve(__dirname, x)); 12 | const cacheDir = local("__temp/get-options-overrides"); 13 | 14 | // filter expects an absolute path and resolves include/exclude to process.cwd() by default: https://github.com/ezolenko/rollup-plugin-typescript2/pull/321#discussion_r873077874 15 | const filtPath = (relPath: string) => normalize(`${process.cwd()}/${relPath}`); 16 | 17 | afterAll(() => remove(cacheDir)); 18 | 19 | const defaultConfig = makeOptions(cacheDir, local("")); 20 | 21 | const forcedOptions: ts.CompilerOptions = { 22 | allowNonTsExtensions: true, 23 | importHelpers: true, 24 | inlineSourceMap: false, 25 | noEmit: false, 26 | noEmitOnError: false, 27 | noEmitHelpers: false, 28 | noResolve: false, 29 | outDir: `${cacheDir}/placeholder`, 30 | }; 31 | 32 | const defaultPreParsedTsConfig: ts.ParsedCommandLine = { 33 | options: {}, 34 | fileNames: [], 35 | errors: [], 36 | }; 37 | 38 | test("getOptionsOverrides", () => { 39 | const config = { ...defaultConfig }; 40 | 41 | expect(getOptionsOverrides(config)).toStrictEqual(forcedOptions); 42 | }); 43 | 44 | test("getOptionsOverrides - preParsedTsConfig", () => { 45 | const config = { ...defaultConfig }; 46 | const preParsedTsConfig = { ...defaultPreParsedTsConfig }; 47 | 48 | expect(getOptionsOverrides(config, preParsedTsConfig)).toStrictEqual({ 49 | ...forcedOptions, 50 | declarationDir: undefined, 51 | module: ts.ModuleKind.ES2015, 52 | sourceRoot: undefined, 53 | }); 54 | }); 55 | 56 | test("getOptionsOverrides - preParsedTsConfig with options.module", () => { 57 | const config = { ...defaultConfig }; 58 | const preParsedTsConfig = { 59 | ...defaultPreParsedTsConfig, 60 | options: { 61 | module: ts.ModuleKind.AMD, 62 | }, 63 | }; 64 | 65 | expect(getOptionsOverrides(config, preParsedTsConfig)).toStrictEqual({ 66 | ...forcedOptions, 67 | declarationDir: undefined, 68 | sourceRoot: undefined, 69 | }); 70 | }); 71 | 72 | test("getOptionsOverrides - with declaration", () => { 73 | const config = { ...defaultConfig, useTsconfigDeclarationDir: true }; 74 | const preParsedTsConfig = { ...defaultPreParsedTsConfig }; 75 | 76 | expect(getOptionsOverrides(config, preParsedTsConfig)).toStrictEqual({ 77 | ...forcedOptions, 78 | module: ts.ModuleKind.ES2015, 79 | sourceRoot: undefined, 80 | }); 81 | }); 82 | 83 | test("getOptionsOverrides - with sourceMap", () => { 84 | const config = { ...defaultConfig }; 85 | const preParsedTsConfig = { 86 | ...defaultPreParsedTsConfig, 87 | options: { 88 | sourceMap: true, 89 | }, 90 | }; 91 | 92 | expect(getOptionsOverrides(config, preParsedTsConfig)).toStrictEqual({ 93 | ...forcedOptions, 94 | declarationDir: undefined, 95 | module: ts.ModuleKind.ES2015, 96 | }); 97 | }); 98 | 99 | test("createFilter", () => { 100 | const config = { ...defaultConfig }; 101 | const preParsedTsConfig = { ...defaultPreParsedTsConfig }; 102 | const filter = createFilter(makeContext(), config, preParsedTsConfig); 103 | 104 | expect(filter(filtPath("src/test.ts"))).toBe(true); 105 | expect(filter(filtPath("src/test.js"))).toBe(false); 106 | expect(filter(filtPath("src/test.d.ts"))).toBe(false); 107 | }); 108 | 109 | test("createFilter - context.debug", () => { 110 | const config = { ...defaultConfig }; 111 | const preParsedTsConfig = { ...defaultPreParsedTsConfig }; 112 | const context = makeContext(); 113 | createFilter(context, config, preParsedTsConfig); 114 | 115 | expect(context.debug).toHaveBeenCalledTimes(2); 116 | }); 117 | 118 | test("createFilter - rootDirs", () => { 119 | const config = { ...defaultConfig }; 120 | const preParsedTsConfig = { 121 | ...defaultPreParsedTsConfig, 122 | options: { 123 | rootDirs: ["src", "lib"] 124 | }, 125 | }; 126 | const filter = createFilter(makeContext(), config, preParsedTsConfig); 127 | 128 | expect(filter(filtPath("src/test.ts"))).toBe(true); 129 | expect(filter(filtPath("src/test.js"))).toBe(false); 130 | expect(filter(filtPath("src/test.d.ts"))).toBe(false); 131 | 132 | expect(filter(filtPath("lib/test.ts"))).toBe(true); 133 | expect(filter(filtPath("lib/test.js"))).toBe(false); 134 | expect(filter(filtPath("lib/test.d.ts"))).toBe(false); 135 | 136 | expect(filter(filtPath("not-src/test.ts"))).toBe(false); 137 | }); 138 | 139 | test("createFilter - projectReferences", () => { 140 | // test string include and also don't match with "**" 141 | const config = { ...defaultConfig, include: "*.ts+(|x)" }; 142 | const preParsedTsConfig = { 143 | ...defaultPreParsedTsConfig, 144 | projectReferences: [ 145 | { path: "src" }, 146 | { path: "lib" }, 147 | ], 148 | }; 149 | const filter = createFilter(makeContext(), config, preParsedTsConfig); 150 | 151 | expect(filter(filtPath("src/test.ts"))).toBe(true); 152 | expect(filter(filtPath("src/test.js"))).toBe(false); 153 | expect(filter(filtPath("src/test.d.ts"))).toBe(false); 154 | 155 | expect(filter(filtPath("lib/test.ts"))).toBe(true); 156 | expect(filter(filtPath("lib/test.js"))).toBe(false); 157 | expect(filter(filtPath("lib/test.d.ts"))).toBe(false); 158 | 159 | expect(filter(filtPath("not-src/test.ts"))).toBe(false); 160 | }); 161 | -------------------------------------------------------------------------------- /__tests__/host.spec.ts: -------------------------------------------------------------------------------- 1 | import { afterAll, beforeAll, test, expect, jest } from "@jest/globals"; 2 | import * as ts from "typescript"; 3 | import * as path from "path"; 4 | import { normalizePath as normalize } from "@rollup/pluginutils"; 5 | import { remove, ensureDir, writeFile, ensureSymlink } from "fs-extra"; 6 | 7 | import { setTypescriptModule } from "../src/tsproxy"; 8 | import { LanguageServiceHost } from "../src/host"; 9 | 10 | setTypescriptModule(ts); 11 | 12 | // mock for host.trace 13 | (global as any).console = { 14 | log: jest.fn(), 15 | }; 16 | 17 | const defaultConfig = { fileNames: [], errors: [], options: {} }; 18 | 19 | const unaryFunc = "const unary = (x: string): string => x.reverse()"; 20 | const unaryFuncSnap = { text: unaryFunc }; 21 | 22 | // host.ts uses `/` normalized path, as does TS itself (https://github.com/microsoft/TypeScript/blob/7f022c58fb8b7253f23c49f0d9eee6fde82b477b/src/compiler/path.ts#L4) 23 | const local = (x: string) => normalize(path.resolve(__dirname, x)); 24 | const testDir = local("__temp/host"); 25 | const testFile = `${testDir}/file.ts`; 26 | const linkedTestFile = `${testDir}/link.ts`; 27 | const nonExistent = `${testDir}/this-does-not-exist.ts`; 28 | 29 | afterAll(() => remove(testDir)); 30 | beforeAll(async () => { 31 | await ensureDir(testDir); 32 | await writeFile(testFile, unaryFunc, "utf8"); 33 | await ensureSymlink(testFile, linkedTestFile); 34 | }); 35 | 36 | test("LanguageServiceHost", async () => { 37 | const testOpts = { test: "this is a test" }; 38 | const config = { ...defaultConfig, options: testOpts }; 39 | const transformers = [() => ({})]; 40 | const host = new LanguageServiceHost(config, transformers, testDir); 41 | 42 | // test core snapshot functionality 43 | expect(host.getScriptSnapshot(testFile)).toEqual(unaryFuncSnap); 44 | expect(host.getScriptVersion(testFile)).toEqual("1"); 45 | 46 | expect(host.setSnapshot(testFile, unaryFunc)).toEqual(unaryFuncSnap); // version 2 47 | expect(host.getScriptSnapshot(testFile)).toEqual(unaryFuncSnap); // get from dict 48 | expect(host.getScriptVersion(testFile)).toEqual("2"); 49 | 50 | expect(host.getScriptSnapshot(nonExistent)).toBeFalsy(); 51 | expect(host.getScriptVersion(nonExistent)).toEqual("0"); 52 | 53 | expect(host.getScriptFileNames()).toEqual([testFile]); 54 | 55 | host.reset(); // back to version 1 56 | expect(host.setSnapshot(testFile, unaryFunc)).toEqual(unaryFuncSnap); 57 | expect(host.getScriptVersion(testFile)).toEqual("1"); 58 | 59 | // test fs functionality, which just uses tsModule.sys equivalents 60 | expect(host.getCurrentDirectory()).toEqual(testDir); 61 | expect(host.getDirectories(testDir)).toEqual([]); 62 | expect(host.directoryExists(nonExistent)).toBeFalsy(); 63 | expect(host.fileExists(nonExistent)).toBeFalsy(); 64 | expect(host.fileExists(testFile)).toBeTruthy(); 65 | expect(host.readDirectory(testDir)).toEqual([testFile, linkedTestFile]); 66 | expect(host.readFile(nonExistent)).toBeFalsy(); 67 | expect(host.readFile(testFile)).toEqual(unaryFunc); 68 | expect(host.useCaseSensitiveFileNames()).toBe(process.platform === "linux"); 69 | // test realpath w/ symlinks. this returns a host path, so expect path.normalize() 70 | expect(host.realpath(testFile)).toEqual(path.normalize(testFile)); 71 | expect(host.realpath(linkedTestFile)).toEqual(path.normalize(testFile)); 72 | 73 | // test misc functionality 74 | expect(host.getCompilationSettings()).toEqual(testOpts); 75 | // TODO: check against `normalize(require.resolve("typescript/lib/lib.dts"))` once https://github.com/microsoft/TypeScript/issues/49050 is fixed -- endsWith is just a workaround for now 76 | expect(host.getDefaultLibFileName({}).endsWith("lib.d.ts")).toBeTruthy(); 77 | expect(host.getTypeRootsVersion()).toEqual(0); 78 | 79 | // mock out trace 80 | host.trace('test log'); 81 | expect(console.log).toHaveBeenCalledWith('test log'); 82 | }); 83 | 84 | 85 | test("LanguageServiceHost - getCustomTransformers", () => { 86 | const config = { ...defaultConfig }; 87 | const transformers = [() => ({ 88 | before: () => "testBefore", 89 | after: () => "testAfter", 90 | afterDeclarations: () => "testAfterDeclarations", 91 | })]; 92 | const host = new LanguageServiceHost(config, transformers as any, testDir); 93 | 94 | host.setLanguageService(true as any); 95 | const customTransformers = host.getCustomTransformers(); 96 | // tiny helper for all the type coercion etc 97 | const callTransform = (type: 'before' | 'after' | 'afterDeclarations') => { 98 | return customTransformers?.[type]?.[0](true as any); 99 | } 100 | 101 | expect(callTransform('before')).toEqual(transformers[0]().before()); 102 | expect(callTransform('after')).toEqual(transformers[0]().after()); 103 | expect(callTransform('afterDeclarations')).toEqual(transformers[0]().afterDeclarations()); 104 | }); 105 | 106 | test("LanguageServiceHost - getCustomTransformers -- undefined cases", () => { 107 | const config = { ...defaultConfig }; 108 | 109 | // no LS and no transformers cases 110 | let host = new LanguageServiceHost(config, undefined as any, testDir); 111 | expect(host.getCustomTransformers()).toBeFalsy(); // no LS 112 | host.setLanguageService(true as any); 113 | expect(host.getCustomTransformers()).toBeFalsy(); // no transformers 114 | 115 | // empty transformers case 116 | host = new LanguageServiceHost(config, [], testDir); 117 | host.setLanguageService(true as any); 118 | expect(host.getCustomTransformers()).toBeFalsy(); // empty transformers 119 | }); 120 | -------------------------------------------------------------------------------- /__tests__/integration/errors.spec.ts: -------------------------------------------------------------------------------- 1 | import { jest, afterAll, test, expect } from "@jest/globals"; 2 | import { Mock } from "jest-mock" 3 | import * as path from "path"; 4 | import { normalizePath as normalize } from "@rollup/pluginutils"; 5 | import * as fs from "fs-extra"; 6 | 7 | import { RPT2Options } from "../../src/index"; 8 | import { findName, genBundle as genBundleH } from "./helpers"; 9 | 10 | // increase timeout to 15s for whole file since CI occassionally timed out -- these are integration and cache tests, so longer timeout is warranted 11 | jest.setTimeout(15000); 12 | 13 | const local = (x: string) => normalize(path.resolve(__dirname, x)); 14 | const testDir = local("__temp/errors"); 15 | 16 | afterAll(async () => { 17 | // workaround: there seems to be some race condition causing fs.remove to fail, so give it a sec first (c.f. https://github.com/jprichardson/node-fs-extra/issues/532) 18 | await new Promise(resolve => setTimeout(resolve, 1000)); 19 | await fs.remove(testDir); 20 | }); 21 | 22 | async function genBundle(relInput: string, extraOpts?: RPT2Options, onwarn?: Mock) { 23 | const input = local(`fixtures/errors/${relInput}`); 24 | return genBundleH({ 25 | input, 26 | tsconfig: local("fixtures/errors/tsconfig.json"), 27 | testDir, 28 | extraOpts: { include: [input], ...extraOpts }, // only include the input itself, not other error files (to only generate types and type-check the one file) 29 | onwarn, 30 | }); 31 | } 32 | 33 | test("integration - semantic error", async () => { 34 | await expect(genBundle("semantic.ts")).rejects.toThrow("Type 'string' is not assignable to type 'number'."); 35 | }); 36 | 37 | test("integration - semantic error - abortOnError: false / check: false", async () => { 38 | const onwarn = jest.fn(); 39 | // either warning or not type-checking should result in the same bundle 40 | const { output } = await genBundle("semantic.ts", { abortOnError: false }, onwarn); 41 | const { output: output2 } = await genBundle("semantic.ts", { check: false }, onwarn); 42 | expect(output).toEqual(output2); 43 | 44 | const files = ["index.js", "semantic.d.ts", "semantic.d.ts.map"]; 45 | files.forEach(file => { 46 | expect(findName(output, file)).toBeTruthy(); 47 | }); 48 | expect(output.length).toEqual(files.length); // no other files 49 | expect(onwarn).toBeCalledTimes(1); 50 | }); 51 | 52 | test("integration - syntax error", async () => { 53 | await expect(genBundle("syntax.ts")).rejects.toThrow("';' expected."); 54 | }); 55 | 56 | test("integration - syntax error - abortOnError: false / check: false", async () => { 57 | const onwarn = jest.fn(); 58 | const err = "Unexpected token (Note that you need plugins to import files that are not JavaScript)"; 59 | await expect(genBundle("syntax.ts", { abortOnError: false }, onwarn)).rejects.toThrow(err); 60 | await expect(genBundle("syntax.ts", { check: false }, onwarn)).rejects.toThrow(err); 61 | }); 62 | 63 | const typeOnlyIncludes = ["**/import-type-error.ts", "**/type-only-import-with-error.ts"]; 64 | 65 | test("integration - type-only import error", async () => { 66 | await expect(genBundle("import-type-error.ts", { 67 | include: typeOnlyIncludes, 68 | })).rejects.toThrow("Property 'nonexistent' does not exist on type 'someObj'."); 69 | }); 70 | 71 | test("integration - type-only import error - abortOnError: false / check: false", async () => { 72 | const onwarn = jest.fn(); 73 | // either warning or not type-checking should result in the same bundle 74 | const { output } = await genBundle("import-type-error.ts", { 75 | include: typeOnlyIncludes, 76 | abortOnError: false, 77 | }, onwarn); 78 | const { output: output2 } = await genBundle("import-type-error.ts", { 79 | include: typeOnlyIncludes, 80 | check: false, 81 | }, onwarn); 82 | expect(output).toEqual(output2); 83 | 84 | const files = ["index.js", "import-type-error.d.ts", "import-type-error.d.ts.map", "type-only-import-with-error.d.ts.map", "type-only-import-with-error.d.ts.map"]; 85 | files.forEach(file => { 86 | expect(findName(output, file)).toBeTruthy(); 87 | }); 88 | expect(output.length).toEqual(files.length); // no other files 89 | expect(onwarn).toBeCalledTimes(1); 90 | }); 91 | 92 | // integration test variant of parse-tsconfig unit test, to test how other hooks interact with an error thrown in buildStart 93 | test("integration - tsconfig error", async () => { 94 | await expect(genBundle("semantic.ts", { 95 | tsconfigOverride: { compilerOptions: { module: "none" } }, 96 | })).rejects.toThrow("Incompatible tsconfig option. Module resolves to 'None'. This is incompatible with Rollup, please use"); 97 | }); 98 | -------------------------------------------------------------------------------- /__tests__/integration/fixtures/errors/import-type-error.ts: -------------------------------------------------------------------------------- 1 | // this file has no errors itself; it is used an entry file to test an error in a type-only import 2 | 3 | export type { typeError } from "./type-only-import-with-error"; 4 | 5 | // some code so this file isn't empty 6 | export function sum(a: number, b: number) { 7 | return a + b; 8 | } 9 | -------------------------------------------------------------------------------- /__tests__/integration/fixtures/errors/semantic.ts: -------------------------------------------------------------------------------- 1 | export const sum = (a: number, b: number): number => { 2 | return "a + b"; 3 | } 4 | -------------------------------------------------------------------------------- /__tests__/integration/fixtures/errors/syntax.ts: -------------------------------------------------------------------------------- 1 | export const incorrectSyntax => { 2 | return "a + b"; 3 | } 4 | -------------------------------------------------------------------------------- /__tests__/integration/fixtures/errors/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | } 4 | -------------------------------------------------------------------------------- /__tests__/integration/fixtures/errors/type-only-import-with-error.ts: -------------------------------------------------------------------------------- 1 | type someObj = {}; 2 | export type typeError = someObj['nonexistent']; 3 | -------------------------------------------------------------------------------- /__tests__/integration/fixtures/no-errors.ts: -------------------------------------------------------------------------------- 1 | export const filesArr = [ 2 | "index.js", 3 | "index.d.ts", 4 | "index.d.ts.map", 5 | "some-import.d.ts", 6 | "some-import.d.ts.map", 7 | "type-only-import.d.ts", 8 | "type-only-import.d.ts.map", 9 | "type-only-import-import.d.ts", 10 | "type-only-import-import.d.ts.map", 11 | ]; 12 | -------------------------------------------------------------------------------- /__tests__/integration/fixtures/no-errors/index.ts: -------------------------------------------------------------------------------- 1 | export function sum(a: number, b: number) { 2 | return a + b; 3 | } 4 | 5 | import { difference } from "./some-import"; 6 | export const diff2 = difference; // add an alias so that this file has to change when the import does (to help test cache invalidation etc) 7 | 8 | export { difference } from "./some-import" 9 | export type { num, num2 } from "./type-only-import" 10 | 11 | export { identity } from "./some-js-import" 12 | -------------------------------------------------------------------------------- /__tests__/integration/fixtures/no-errors/some-import.ts: -------------------------------------------------------------------------------- 1 | export function difference(a: number, b: number) { 2 | return a - b; 3 | } 4 | -------------------------------------------------------------------------------- /__tests__/integration/fixtures/no-errors/some-js-import.d.ts: -------------------------------------------------------------------------------- 1 | // TS needs a declaration in order to understand the import 2 | // but this is ambient, and so should not be directly imported into rpt2, just found by TS 3 | 4 | export function identity(a: any): any; 5 | -------------------------------------------------------------------------------- /__tests__/integration/fixtures/no-errors/some-js-import.js: -------------------------------------------------------------------------------- 1 | // should be filtered out by rpt2, but still bundled by Rollup itself (as this is ESM, no need for a plugin) 2 | 3 | export function identity(a) { 4 | return a; 5 | } 6 | -------------------------------------------------------------------------------- /__tests__/integration/fixtures/no-errors/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | } 4 | -------------------------------------------------------------------------------- /__tests__/integration/fixtures/no-errors/type-only-import-import.ts: -------------------------------------------------------------------------------- 1 | export type numb = number; 2 | -------------------------------------------------------------------------------- /__tests__/integration/fixtures/no-errors/type-only-import.ts: -------------------------------------------------------------------------------- 1 | import type { numb } from "./type-only-import-import"; 2 | 3 | export type num = number; 4 | export type num2 = numb; 5 | -------------------------------------------------------------------------------- /__tests__/integration/fixtures/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "forceConsistentCasingInFileNames": true, 4 | "moduleResolution": "node", 5 | "sourceMap": true, 6 | "declaration": true, 7 | "declarationMap": true, 8 | "target": "ES6", 9 | "strict": true, 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /__tests__/integration/helpers.ts: -------------------------------------------------------------------------------- 1 | import { rollup, watch, RollupOptions, OutputOptions, RollupOutput, OutputAsset, RollupWatcher } from "rollup"; 2 | import * as path from "path"; 3 | 4 | import rpt2, { RPT2Options } from "../../src/index"; 5 | 6 | type Params = { 7 | input: string, 8 | tsconfig: string, 9 | testDir: string, 10 | extraOpts?: RPT2Options, 11 | onwarn?: RollupOptions['onwarn'], 12 | }; 13 | 14 | function createInput ({ input, tsconfig, testDir, extraOpts, onwarn }: Params) { 15 | return { 16 | input, 17 | plugins: [rpt2({ 18 | tsconfig, 19 | cacheRoot: `${testDir}/rpt2-cache`, // don't use the one in node_modules 20 | ...extraOpts, 21 | })], 22 | onwarn, 23 | } 24 | } 25 | 26 | function createOutput (testDir: string): OutputOptions { 27 | return { 28 | file: path.resolve(`${testDir}/dist/index.js`), // put outputs in temp test dir 29 | format: "esm", 30 | exports: "named", 31 | } 32 | } 33 | 34 | export async function genBundle (inputArgs: Params) { 35 | const bundle = await rollup(createInput(inputArgs)); 36 | const esm = await bundle.generate(createOutput(inputArgs.testDir)); 37 | 38 | // Rollup has some deprecated properties like `get isAsset`, so enumerating them with, e.g. `.toEqual`, causes a bunch of warnings to be output 39 | // delete the `isAsset` property for (much) cleaner logs 40 | const { output: files } = esm; 41 | for (const file of files) { 42 | if ("isAsset" in file) { 43 | const optIsAsset = file as Partial> & Omit; 44 | delete optIsAsset["isAsset"]; 45 | } 46 | } 47 | 48 | return esm; 49 | } 50 | 51 | /** wrap Listener interface in a Promise */ 52 | export function watchEnd (watcher: RollupWatcher) { 53 | return new Promise((resolve, reject) => { 54 | watcher.on("event", event => { 55 | if ("result" in event) 56 | event.result?.close(); // close all bundles 57 | 58 | if (event.code === "END") 59 | resolve(); 60 | else if (event.code === "ERROR") 61 | reject(event.error); 62 | }); 63 | }); 64 | } 65 | 66 | export async function watchBundle (inputArgs: Params) { 67 | const watcher = watch({ 68 | ...createInput(inputArgs), 69 | output: createOutput(inputArgs.testDir), 70 | }); 71 | 72 | await watchEnd(watcher); // wait for build to end before returning, similar to genBundle 73 | return watcher; 74 | } 75 | 76 | export function findName (output: RollupOutput['output'], name: string) { 77 | // type-cast to simplify type-checking -- [0] is always chunk, rest are always asset in our case 78 | return output.find(file => file.fileName === name) as OutputAsset; 79 | } 80 | -------------------------------------------------------------------------------- /__tests__/integration/no-errors.spec.ts: -------------------------------------------------------------------------------- 1 | import { jest, afterAll, test, expect } from "@jest/globals"; 2 | import * as path from "path"; 3 | import * as fs from "fs-extra"; 4 | import { normalizePath as normalize } from "@rollup/pluginutils"; 5 | 6 | import { RPT2Options } from "../../src/index"; 7 | import { filesArr } from "./fixtures/no-errors"; 8 | import { findName, genBundle as genBundleH } from "./helpers"; 9 | 10 | // increase timeout to 20s for whole file since CI occassionally timed out -- these are integration and cache tests, so longer timeout is warranted 11 | jest.setTimeout(20000); 12 | 13 | const local = (x: string) => path.resolve(__dirname, x); 14 | const testDir = local("__temp/no-errors"); 15 | const fixtureDir = local("fixtures/no-errors"); 16 | 17 | afterAll(() => fs.remove(testDir)); 18 | 19 | async function genBundle(relInput: string, extraOpts?: RPT2Options) { 20 | return genBundleH({ 21 | input: `${fixtureDir}/${relInput}`, 22 | tsconfig: `${fixtureDir}/tsconfig.json`, 23 | testDir, 24 | extraOpts, 25 | }); 26 | } 27 | 28 | test("integration - no errors", async () => { 29 | const { output } = await genBundle("index.ts", { clean: true }); 30 | 31 | // populate the cache 32 | await genBundle("index.ts"); 33 | const { output: outputWithCache } = await genBundle("index.ts"); 34 | expect(output).toEqual(outputWithCache); 35 | 36 | const files = filesArr; 37 | files.forEach(file => { 38 | expect(findName(output, file)).toBeTruthy(); 39 | }); 40 | expect(output.length).toEqual(files.length); // no other files 41 | 42 | // JS file should be bundled by Rollup, even though rpt2 does not resolve it (as Rollup natively understands ESM) 43 | expect(output[0].code).toEqual(expect.stringContaining("identity")); 44 | 45 | // declaration map sources should be correctly remapped (and not point to placeholder dir, c.f. https://github.com/ezolenko/rollup-plugin-typescript2/pull/221) 46 | const decMap = findName(output, "index.d.ts.map"); 47 | const decMapSources = JSON.parse(decMap.source as string).sources; 48 | const decRelPath = normalize(path.relative(`${testDir}/dist`, `${fixtureDir}/index.ts`)); 49 | expect(decMapSources).toEqual([decRelPath]); 50 | }); 51 | 52 | test("integration - no errors - using files list", async () => { 53 | const { output } = await genBundle("index.ts", { tsconfigOverride: { files: ["index.ts"] } }); 54 | 55 | // should still have the type-only import and type-only import import! 56 | const files = filesArr; 57 | files.forEach(file => { 58 | expect(findName(output, file)).toBeTruthy(); 59 | }); 60 | expect(output.length).toEqual(files.length); // no other files 61 | }); 62 | 63 | test("integration - no errors - no declaration maps", async () => { 64 | const noDeclarationMaps = { compilerOptions: { declarationMap: false } }; 65 | const { output } = await genBundle("index.ts", { 66 | tsconfigOverride: noDeclarationMaps, 67 | clean: true, 68 | }); 69 | 70 | const files = filesArr.filter(file => !file.endsWith(".d.ts.map")); 71 | files.forEach(file => { 72 | expect(findName(output, file)).toBeTruthy(); 73 | }); 74 | expect(output.length).toEqual(files.length); // no other files 75 | }); 76 | 77 | 78 | test("integration - no errors - no declarations", async () => { 79 | const noDeclarations = { compilerOptions: { declaration: false, declarationMap: false } }; 80 | const { output } = await genBundle("index.ts", { 81 | tsconfigOverride: noDeclarations, 82 | clean: true, 83 | }); 84 | 85 | expect(output[0].fileName).toEqual("index.js"); 86 | expect(output.length).toEqual(1); // no other files 87 | }); 88 | 89 | test("integration - no errors - allowJs + emitDeclarationOnly", async () => { 90 | const { output } = await genBundle("some-js-import.js", { 91 | include: ["**/*.js"], 92 | tsconfigOverride: { 93 | compilerOptions: { 94 | allowJs: true, 95 | emitDeclarationOnly: true, 96 | }, 97 | }, 98 | }); 99 | 100 | const files = ["index.js", "some-js-import.d.ts", "some-js-import.d.ts.map"]; 101 | files.forEach(file => { 102 | expect(findName(output, file)).toBeTruthy(); 103 | }); 104 | expect(output.length).toEqual(files.length); // no other files 105 | 106 | expect(output[0].code).toEqual(expect.stringContaining("identity")); 107 | expect(output[0].code).not.toEqual(expect.stringContaining("sum")); // no TS files included 108 | 109 | const dec = findName(output, "some-js-import.d.ts"); 110 | expect(dec.source).toEqual(expect.stringContaining("identity")); 111 | }); 112 | -------------------------------------------------------------------------------- /__tests__/integration/watch.spec.ts: -------------------------------------------------------------------------------- 1 | import { jest, beforeAll, afterAll, test, expect } from "@jest/globals"; 2 | import * as path from "path"; 3 | import * as fs from "fs-extra"; 4 | 5 | import { RPT2Options } from "../../src/index"; 6 | import { filesArr } from "./fixtures/no-errors"; 7 | import * as helpers from "./helpers"; 8 | 9 | // increase timeout to 15s for whole file since CI occassionally timed out -- these are integration and cache tests, so longer timeout is warranted 10 | jest.setTimeout(15000); 11 | 12 | const local = (x: string) => path.resolve(__dirname, x); 13 | const testDir = local("__temp/watch"); 14 | const fixtureDir = `${testDir}/fixtures`; 15 | 16 | beforeAll(async () => { 17 | await fs.ensureDir(fixtureDir); 18 | // copy the dir to not interfere with other parallel tests since we need to change files for watch mode 19 | // note we're copying the root fixture dir bc we need the _base_ tsconfig too. maybe optimize in the future or use the other fixtures? 20 | await fs.copy(local("fixtures"), fixtureDir); 21 | }); 22 | afterAll(() => fs.remove(testDir)); 23 | 24 | async function watchBundle(input: string, extraOpts?: RPT2Options) { 25 | return helpers.watchBundle({ 26 | input, 27 | tsconfig: `${path.dirname(input)}/tsconfig.json`, // use the tsconfig of whatever fixture we're in 28 | testDir, 29 | extraOpts, 30 | }); 31 | } 32 | 33 | test("integration - watch", async () => { 34 | const srcPath = `${fixtureDir}/no-errors/index.ts`; 35 | const importPath = `${fixtureDir}/no-errors/some-import.ts`; 36 | const distDir = `${testDir}/dist`; 37 | const distPath = `${testDir}/dist/index.js`; 38 | const decPath = `${distDir}/index.d.ts`; 39 | const decMapPath = `${decPath}.map`; 40 | 41 | const watcher = await watchBundle(srcPath); 42 | 43 | const files = await fs.readdir(distDir); 44 | expect(files).toEqual(expect.arrayContaining(filesArr)); 45 | expect(files.length).toBe(filesArr.length); // no other files 46 | 47 | // save content to test against later 48 | const dist = await fs.readFile(distPath, "utf8"); 49 | const dec = await fs.readFile(decPath, "utf8"); 50 | const decMap = await fs.readFile(decMapPath, "utf8"); 51 | 52 | // modify an imported file -- this should cause it and index to change 53 | await fs.writeFile(importPath, "export const difference = 2", "utf8"); 54 | await helpers.watchEnd(watcher); 55 | 56 | // should have same structure, since names haven't changed and dist hasn't been cleaned 57 | const files2 = await fs.readdir(distDir); 58 | expect(files2).toEqual(expect.arrayContaining(filesArr)); 59 | expect(files2.length).toBe(filesArr.length); // no other files 60 | 61 | // should have different content now though 62 | expect(dist).not.toEqual(await fs.readFile(distPath, "utf8")); 63 | expect(dec).not.toEqual(await fs.readFile(decPath, "utf8")); 64 | expect(decMap).not.toEqual(await fs.readFile(decMapPath, "utf8")); 65 | 66 | // modify an imported file to cause a semantic error 67 | await fs.writeFile(importPath, "export const difference = nonexistent", "utf8") 68 | await expect(helpers.watchEnd(watcher)).rejects.toThrow("Cannot find name 'nonexistent'."); 69 | 70 | await watcher.close(); 71 | }); 72 | -------------------------------------------------------------------------------- /__tests__/parse-tsconfig.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "@jest/globals"; 2 | import * as path from "path"; 3 | import { normalizePath as normalize } from "@rollup/pluginutils"; 4 | 5 | import { makeOptions } from "./fixtures/options"; 6 | import { makeContext } from "./fixtures/context"; 7 | import { parseTsConfig } from "../src/parse-tsconfig"; 8 | 9 | const local = (x: string) => normalize(path.resolve(__dirname, x)); 10 | 11 | const defaultOpts = makeOptions("", ""); 12 | 13 | test("parseTsConfig", () => { 14 | const context = makeContext(); 15 | 16 | parseTsConfig(context, defaultOpts); 17 | 18 | expect(context.error).not.toBeCalled(); 19 | }); 20 | 21 | test("parseTsConfig - incompatible module", () => { 22 | const context = makeContext(); 23 | 24 | parseTsConfig(context, { 25 | ...defaultOpts, 26 | tsconfigOverride: { compilerOptions: { module: "none" } }, 27 | }); 28 | 29 | expect(context.error).toHaveBeenLastCalledWith(expect.stringContaining("Incompatible tsconfig option. Module resolves to 'None'. This is incompatible with Rollup, please use")); 30 | }); 31 | 32 | test("parseTsConfig - tsconfig errors", () => { 33 | const context = makeContext(); 34 | 35 | parseTsConfig(context, { 36 | ...defaultOpts, 37 | tsconfigOverride: { 38 | include: "should-be-an-array", 39 | }, 40 | }); 41 | 42 | expect(context.error).toHaveBeenLastCalledWith(expect.stringContaining("Compiler option 'include' requires a value of type Array")); 43 | }); 44 | 45 | test("parseTsConfig - failed to open", () => { 46 | const context = makeContext(); 47 | const nonExistentTsConfig = "non-existent-tsconfig"; 48 | 49 | parseTsConfig(context, { 50 | ...defaultOpts, 51 | tsconfig: nonExistentTsConfig, 52 | }) 53 | 54 | expect(context.error).toHaveBeenLastCalledWith(expect.stringContaining(`failed to open '${nonExistentTsConfig}`)); 55 | }); 56 | 57 | test("parseTsConfig - failed to parse", () => { 58 | const context = makeContext(); 59 | const notTsConfigPath = local("fixtures/options.ts"); // a TS file should fail to parse 60 | 61 | parseTsConfig(context, { 62 | ...defaultOpts, 63 | tsconfig: notTsConfigPath, 64 | }) 65 | 66 | expect(context.error).toHaveBeenLastCalledWith(expect.stringContaining(`failed to parse '${notTsConfigPath}'`)); 67 | }); 68 | -------------------------------------------------------------------------------- /__tests__/rollingcache.spec.ts: -------------------------------------------------------------------------------- 1 | import { beforeEach, afterAll, test, expect } from "@jest/globals"; 2 | import * as path from "path"; 3 | import { remove, ensureDir, writeFile, pathExists } from "fs-extra"; 4 | 5 | import { RollingCache } from "../src/rollingcache"; 6 | 7 | 8 | const local = (x: string) => path.resolve(__dirname, x); 9 | const testDir = local("__temp/rollingcache"); 10 | const oldCacheDir = `${testDir}/cache`; 11 | const newCacheDir = `${testDir}/cache_`; 12 | const testFile = "file.json"; 13 | const oldTestFile = `${oldCacheDir}/${testFile}`; 14 | 15 | const nonExistentFile = "this-does-not-exist.json"; 16 | const emptyCacheRoot = `${testDir}/empty-cache-root`; 17 | 18 | const testFileShape = { a: 1, b: 2, c: 3 }; 19 | 20 | 21 | beforeEach(async () => { 22 | await ensureDir(oldCacheDir); 23 | await ensureDir(newCacheDir); 24 | await writeFile(oldTestFile, JSON.stringify(testFileShape), "utf8"); 25 | }); 26 | afterAll(() => remove(testDir)); 27 | 28 | 29 | test("RollingCache", async () => { 30 | const cache = new RollingCache(testDir); 31 | 32 | expect(cache.exists(nonExistentFile)).toBeFalsy(); 33 | expect(cache.exists(testFile)).toBeTruthy(); 34 | expect(cache.path("x")).toEqual(`${oldCacheDir}/x`); 35 | expect(cache.match([nonExistentFile])).toBeFalsy(); 36 | expect(cache.match([testFile])).toBeTruthy(); 37 | expect(cache.read(testFile)).toEqual(testFileShape); 38 | 39 | cache.write("write-test.json", {a: 2, b: 2, c: 2}); 40 | expect(cache.read("write-test.json")).toEqual({a: 2, b: 2, c: 2}); 41 | 42 | cache.write("write-fail.json", (undefined as any)); 43 | expect(cache.read("write-fail.json")).toBeFalsy(); 44 | 45 | cache.touch("touched.json"); 46 | expect(await pathExists(`${newCacheDir}/touched.json`)).toBeTruthy(); 47 | 48 | cache.roll(); 49 | expect(await pathExists(newCacheDir)).toBeFalsy(); 50 | }); 51 | 52 | test("RollingCache, rolled", async () => { 53 | const cache = new RollingCache(testDir); 54 | // roll the cache 55 | cache.roll(); 56 | // rolling again hits coverage for this.rolled being true already 57 | cache.roll(); 58 | expect(cache.exists("anything")).toBeFalsy(); 59 | expect(cache.match([])).toBeFalsy(); 60 | 61 | cache.write("whatever.json", {whatever: true}); 62 | expect(await pathExists(`${oldCacheDir}/whatever.json`)).toBeFalsy(); 63 | 64 | cache.touch("touched.json"); 65 | expect(await pathExists(`${oldCacheDir}/touched.json`)).toBeFalsy(); 66 | }); 67 | 68 | test("RollingCache, test newCache", async () => { 69 | const cache = new RollingCache(testDir); 70 | 71 | const preExistingFile = `${newCacheDir}/pre-existing.json`; 72 | await writeFile(preExistingFile, JSON.stringify({})); 73 | expect(cache.exists("pre-existing.json")).toBeTruthy(); 74 | }); 75 | 76 | test("RollingCache, test match when oldCacheDir is empty", () => { 77 | const cache = new RollingCache(emptyCacheRoot); 78 | 79 | expect(cache.match([])).toBeTruthy(); 80 | expect(cache.match([testFile])).toBeFalsy(); 81 | }); 82 | -------------------------------------------------------------------------------- /__tests__/tslib.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect, jest } from "@jest/globals"; 2 | import * as fs from "fs-extra"; 3 | 4 | (global as any).console = { warn: jest.fn() }; 5 | 6 | // the error case _must_ come first because order matters for module mocks 7 | test("tslib - errors", async () => { 8 | jest.mock("tslib/package.json", () => undefined); // mock the module subpath bc we actually never import "tslib" directly. module mocks only work on exact match 9 | await expect(import("../src/tslib")).rejects.toThrow(); 10 | expect(console.warn).toBeCalledTimes(1); 11 | }); 12 | 13 | test("tslib", async () => { 14 | jest.unmock("tslib/package.json"); 15 | 16 | const { tslibVersion, tslibSource } = await import("../src/tslib"); 17 | // eslint-disable-next-line @typescript-eslint/no-var-requires 18 | expect(tslibVersion).toEqual(require("tslib/package.json").version); 19 | 20 | const tslibES6 = await fs.readFile(require.resolve("tslib/tslib.es6.js"), "utf8"); 21 | expect(tslibSource).toEqual(tslibES6); 22 | }); 23 | -------------------------------------------------------------------------------- /dist/context.d.ts: -------------------------------------------------------------------------------- 1 | import { PluginContext } from "rollup"; 2 | export declare enum VerbosityLevel { 3 | Error = 0, 4 | Warning = 1, 5 | Info = 2, 6 | Debug = 3 7 | } 8 | /** cannot be used in options hook (which does not have this.warn and this.error), but can be in other hooks */ 9 | export declare class RollupContext { 10 | private verbosity; 11 | private bail; 12 | private context; 13 | private prefix; 14 | constructor(verbosity: VerbosityLevel, bail: boolean, context: PluginContext, prefix?: string); 15 | warn(message: string | (() => string)): void; 16 | error(message: string | (() => string)): void | never; 17 | info(message: string | (() => string)): void; 18 | debug(message: string | (() => string)): void; 19 | } 20 | //# sourceMappingURL=context.d.ts.map -------------------------------------------------------------------------------- /dist/context.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAEvC,oBAAY,cAAc;IAEzB,KAAK,IAAI;IACT,OAAO,IAAA;IACP,IAAI,IAAA;IACJ,KAAK,IAAA;CACL;AAMD,+GAA+G;AAC/G,qBAAa,aAAa;IAEb,OAAO,CAAC,SAAS;IAAkB,OAAO,CAAC,IAAI;IAAW,OAAO,CAAC,OAAO;IAAiB,OAAO,CAAC,MAAM;gBAAhG,SAAS,EAAE,cAAc,EAAU,IAAI,EAAE,OAAO,EAAU,OAAO,EAAE,aAAa,EAAU,MAAM,GAAE,MAAW;IAI1H,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,GAAG,IAAI;IAO5C,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,GAAG,IAAI,GAAG,KAAK;IAWrD,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,GAAG,IAAI;IAO5C,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,GAAG,IAAI;CAMpD"} -------------------------------------------------------------------------------- /dist/diagnostics-format-host.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import * as path from "path"; 3 | import * as tsTypes from "typescript"; 4 | export declare class FormatHost implements tsTypes.FormatDiagnosticsHost { 5 | getCurrentDirectory(): string; 6 | getCanonicalFileName: typeof path.normalize; 7 | getNewLine: () => string; 8 | } 9 | export declare const formatHost: FormatHost; 10 | //# sourceMappingURL=diagnostics-format-host.d.ts.map -------------------------------------------------------------------------------- /dist/diagnostics-format-host.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"diagnostics-format-host.d.ts","sourceRoot":"","sources":["../src/diagnostics-format-host.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,OAAO,MAAM,YAAY,CAAC;AAItC,qBAAa,UAAW,YAAW,OAAO,CAAC,qBAAqB;IAExD,mBAAmB,IAAI,MAAM;IAK7B,oBAAoB,wBAAkB;IACtC,UAAU,eAA8B;CAC/C;AAED,eAAO,MAAM,UAAU,YAAmB,CAAC"} -------------------------------------------------------------------------------- /dist/diagnostics.d.ts: -------------------------------------------------------------------------------- 1 | import * as tsTypes from "typescript"; 2 | import { RollupContext } from "./context"; 3 | export interface IDiagnostics { 4 | flatMessage: string; 5 | formatted: string; 6 | fileLine?: string; 7 | category: tsTypes.DiagnosticCategory; 8 | code: number; 9 | type: string; 10 | } 11 | export declare function convertDiagnostic(type: string, data: tsTypes.Diagnostic[]): IDiagnostics[]; 12 | export declare function printDiagnostics(context: RollupContext, diagnostics: IDiagnostics[], pretty?: boolean): void; 13 | //# sourceMappingURL=diagnostics.d.ts.map -------------------------------------------------------------------------------- /dist/diagnostics.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,YAAY,CAAC;AAItC,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAG1C,MAAM,WAAW,YAAY;IAE5B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC,kBAAkB,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACb;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,UAAU,EAAE,GAAG,YAAY,EAAE,CAoB1F;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,EAAE,MAAM,UAAO,GAAG,IAAI,CAqCzG"} -------------------------------------------------------------------------------- /dist/get-options-overrides.d.ts: -------------------------------------------------------------------------------- 1 | import * as tsTypes from "typescript"; 2 | import { IOptions } from "./ioptions"; 3 | import { RollupContext } from "./context"; 4 | export declare function getOptionsOverrides({ useTsconfigDeclarationDir, cacheRoot }: IOptions, preParsedTsconfig?: tsTypes.ParsedCommandLine): tsTypes.CompilerOptions; 5 | export declare function createFilter(context: RollupContext, pluginOptions: IOptions, parsedConfig: tsTypes.ParsedCommandLine): (id: unknown) => boolean; 6 | //# sourceMappingURL=get-options-overrides.d.ts.map -------------------------------------------------------------------------------- /dist/get-options-overrides.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"get-options-overrides.d.ts","sourceRoot":"","sources":["../src/get-options-overrides.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,OAAO,MAAM,YAAY,CAAC;AAItC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE1C,wBAAgB,mBAAmB,CAAC,EAAE,yBAAyB,EAAE,SAAS,EAAE,EAAE,QAAQ,EAAE,iBAAiB,CAAC,EAAE,OAAO,CAAC,iBAAiB,GAAG,OAAO,CAAC,eAAe,CA+B9J;AAeD,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,CAAC,iBAAiB,4BAoBpH"} -------------------------------------------------------------------------------- /dist/host.d.ts: -------------------------------------------------------------------------------- 1 | import * as tsTypes from "typescript"; 2 | import { TransformerFactoryCreator } from "./ioptions"; 3 | export declare class LanguageServiceHost implements tsTypes.LanguageServiceHost { 4 | private parsedConfig; 5 | private transformers; 6 | private cwd; 7 | private snapshots; 8 | private versions; 9 | private service?; 10 | private fileNames; 11 | constructor(parsedConfig: tsTypes.ParsedCommandLine, transformers: TransformerFactoryCreator[], cwd: string); 12 | reset(): void; 13 | setLanguageService(service: tsTypes.LanguageService): void; 14 | setSnapshot(fileName: string, source: string): tsTypes.IScriptSnapshot; 15 | getScriptSnapshot(fileName: string): tsTypes.IScriptSnapshot | undefined; 16 | getScriptFileNames: () => string[]; 17 | getScriptVersion(fileName: string): string; 18 | getCustomTransformers(): tsTypes.CustomTransformers | undefined; 19 | getCompilationSettings: () => tsTypes.CompilerOptions; 20 | getTypeRootsVersion: () => number; 21 | getCurrentDirectory: () => string; 22 | useCaseSensitiveFileNames: () => boolean; 23 | getDefaultLibFileName: typeof tsTypes.getDefaultLibFilePath; 24 | readDirectory: (path: string, extensions?: readonly string[] | undefined, exclude?: readonly string[] | undefined, include?: readonly string[] | undefined, depth?: number | undefined) => string[]; 25 | readFile: (path: string, encoding?: string | undefined) => string | undefined; 26 | fileExists: (path: string) => boolean; 27 | directoryExists: (path: string) => boolean; 28 | getDirectories: (path: string) => string[]; 29 | realpath: (path: string) => string; 30 | trace: { 31 | (...data: any[]): void; 32 | (message?: any, ...optionalParams: any[]): void; 33 | }; 34 | } 35 | //# sourceMappingURL=host.d.ts.map -------------------------------------------------------------------------------- /dist/host.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,YAAY,CAAC;AAItC,OAAO,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAEvD,qBAAa,mBAAoB,YAAW,OAAO,CAAC,mBAAmB;IAO1D,OAAO,CAAC,YAAY;IAA6B,OAAO,CAAC,YAAY;IAA+B,OAAO,CAAC,GAAG;IAL3H,OAAO,CAAC,SAAS,CAAuD;IACxE,OAAO,CAAC,QAAQ,CAAsC;IACtD,OAAO,CAAC,OAAO,CAAC,CAA0B;IAC1C,OAAO,CAAC,SAAS,CAAc;gBAEX,YAAY,EAAE,OAAO,CAAC,iBAAiB,EAAU,YAAY,EAAE,yBAAyB,EAAE,EAAU,GAAG,EAAE,MAAM;IAK5H,KAAK;IAML,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,eAAe;IAKnD,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe;IAWtE,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS;IAcxE,kBAAkB,iBAA6C;IAE/D,gBAAgB,CAAC,QAAQ,EAAE,MAAM;IAOjC,qBAAqB,IAAI,OAAO,CAAC,kBAAkB,GAAG,SAAS;IA0B/D,sBAAsB,gCAAmC;IACzD,mBAAmB,eAAW;IAC9B,mBAAmB,eAAkB;IAErC,yBAAyB,gBAAgD;IACzE,qBAAqB,uCAAkC;IAEvD,aAAa,uLAA8B;IAC3C,QAAQ,sEAAyB;IACjC,UAAU,4BAA2B;IACrC,eAAe,4BAAgC;IAC/C,cAAc,6BAA+B;IAC7C,QAAQ,2BAA0B;IAElC,KAAK;;;MAAe;CAC3B"} -------------------------------------------------------------------------------- /dist/icache.d.ts: -------------------------------------------------------------------------------- 1 | export interface ICache { 2 | exists(name: string): boolean; 3 | path(name: string): string; 4 | match(names: string[]): boolean; 5 | read(name: string): DataType | null | undefined; 6 | write(name: string, data: DataType): void; 7 | touch(name: string): void; 8 | roll(): void; 9 | } 10 | //# sourceMappingURL=icache.d.ts.map -------------------------------------------------------------------------------- /dist/icache.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"icache.d.ts","sourceRoot":"","sources":["../src/icache.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,MAAM,CAAE,QAAQ;IAEhC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAE9B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAE3B,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAEhC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,GAAG,SAAS,CAAC;IAEhD,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;IAE1C,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAE1B,IAAI,IAAI,IAAI,CAAC;CACb"} -------------------------------------------------------------------------------- /dist/index.d.ts: -------------------------------------------------------------------------------- 1 | import { PluginImpl } from "rollup"; 2 | import { IOptions } from "./ioptions"; 3 | type RPT2Options = Partial; 4 | export { RPT2Options }; 5 | declare const typescript: PluginImpl; 6 | export default typescript; 7 | //# sourceMappingURL=index.d.ts.map -------------------------------------------------------------------------------- /dist/index.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAoD,MAAM,QAAQ,CAAC;AAUtF,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAWtC,KAAK,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAErC,OAAO,EAAE,WAAW,EAAE,CAAA;AAEtB,QAAA,MAAM,UAAU,EAAE,UAAU,CAAC,WAAW,CA6ZvC,CAAC;AAEF,eAAe,UAAU,CAAC"} -------------------------------------------------------------------------------- /dist/ioptions.d.ts: -------------------------------------------------------------------------------- 1 | import * as tsTypes from "typescript"; 2 | import { tsModule } from "./tsproxy"; 3 | export interface ICustomTransformer { 4 | before?: tsTypes.TransformerFactory; 5 | after?: tsTypes.TransformerFactory; 6 | afterDeclarations?: tsTypes.TransformerFactory; 7 | } 8 | export type TransformerFactoryCreator = (ls: tsTypes.LanguageService) => tsTypes.CustomTransformers | ICustomTransformer; 9 | export interface IOptions { 10 | cwd: string; 11 | include: string | string[]; 12 | exclude: string | string[]; 13 | check: boolean; 14 | verbosity: number; 15 | clean: boolean; 16 | cacheRoot: string; 17 | abortOnError: boolean; 18 | rollupCommonJSResolveHack: boolean; 19 | tsconfig?: string; 20 | useTsconfigDeclarationDir: boolean; 21 | typescript: typeof tsModule; 22 | tsconfigOverride: any; 23 | transformers: TransformerFactoryCreator[]; 24 | tsconfigDefaults: any; 25 | sourceMapCallback: (id: string, map: string) => void; 26 | objectHashIgnoreUnknownHack: boolean; 27 | } 28 | //# sourceMappingURL=ioptions.d.ts.map -------------------------------------------------------------------------------- /dist/ioptions.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"ioptions.d.ts","sourceRoot":"","sources":["../src/ioptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,YAAY,CAAC;AAEtC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC,MAAM,WAAW,kBAAkB;IAElC,MAAM,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACxD,KAAK,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACvD,iBAAiB,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,OAAO,CAAC,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CACpF;AAED,MAAM,MAAM,yBAAyB,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,eAAe,KAAK,OAAO,CAAC,kBAAkB,GAAG,kBAAkB,CAAC;AAEzH,MAAM,WAAW,QAAQ;IAExB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,GAAC,MAAM,EAAE,CAAC;IACzB,OAAO,EAAE,MAAM,GAAC,MAAM,EAAE,CAAC;IACzB,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;IACtB,yBAAyB,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yBAAyB,EAAE,OAAO,CAAC;IACnC,UAAU,EAAE,OAAO,QAAQ,CAAC;IAC5B,gBAAgB,EAAE,GAAG,CAAC;IACtB,YAAY,EAAE,yBAAyB,EAAE,CAAC;IAC1C,gBAAgB,EAAE,GAAG,CAAC;IACtB,iBAAiB,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACrD,2BAA2B,EAAE,OAAO,CAAC;CACrC"} -------------------------------------------------------------------------------- /dist/parse-tsconfig.d.ts: -------------------------------------------------------------------------------- 1 | import { RollupContext } from "./context"; 2 | import { IOptions } from "./ioptions"; 3 | export declare function parseTsConfig(context: RollupContext, pluginOptions: IOptions): { 4 | parsedTsConfig: import("typescript").ParsedCommandLine; 5 | fileName: string | undefined; 6 | }; 7 | //# sourceMappingURL=parse-tsconfig.d.ts.map -------------------------------------------------------------------------------- /dist/parse-tsconfig.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"parse-tsconfig.d.ts","sourceRoot":"","sources":["../src/parse-tsconfig.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAG1C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,wBAAgB,aAAa,CAAC,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,QAAQ;;;EA8C5E"} -------------------------------------------------------------------------------- /dist/rollingcache.d.ts: -------------------------------------------------------------------------------- 1 | import { ICache } from "./icache"; 2 | /** 3 | * Saves data in new cache folder or reads it from old one. 4 | * Avoids perpetually growing cache and situations when things need to consider changed and then reverted data to be changed. 5 | */ 6 | export declare class RollingCache implements ICache { 7 | private cacheRoot; 8 | private oldCacheRoot; 9 | private newCacheRoot; 10 | private rolled; 11 | /** @param cacheRoot: root folder for the cache */ 12 | constructor(cacheRoot: string); 13 | /** @returns true if name exists in either old cache or new cache */ 14 | exists(name: string): boolean; 15 | path(name: string): string; 16 | /** @returns true if old cache contains all names and nothing more */ 17 | match(names: string[]): boolean; 18 | /** @returns data for name, must exist in either old cache or new cache */ 19 | read(name: string): DataType | null | undefined; 20 | write(name: string, data: DataType): void; 21 | touch(name: string): void; 22 | /** clears old cache and moves new in its place */ 23 | roll(): void; 24 | } 25 | //# sourceMappingURL=rollingcache.d.ts.map -------------------------------------------------------------------------------- /dist/rollingcache.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"rollingcache.d.ts","sourceRoot":"","sources":["../src/rollingcache.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC;;;GAGG;AACH,qBAAa,YAAY,CAAC,QAAQ,CAAE,YAAW,MAAM,CAAC,QAAQ,CAAC;IAOlD,OAAO,CAAC,SAAS;IAL7B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,MAAM,CAAkB;IAEhC,kDAAkD;gBAC9B,SAAS,EAAE,MAAM;IAOrC,oEAAoE;IAC7D,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAW7B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAKjC,qEAAqE;IAC9D,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO;IAWtC,0EAA0E;IACnE,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,GAAG,SAAS;IAQ/C,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI;IAWzC,KAAK,CAAC,IAAI,EAAE,MAAM;IAQzB,kDAAkD;IAC3C,IAAI;CAWX"} -------------------------------------------------------------------------------- /dist/tscache.d.ts: -------------------------------------------------------------------------------- 1 | import * as tsTypes from "typescript"; 2 | import { RollupContext } from "./context"; 3 | import { IDiagnostics } from "./diagnostics"; 4 | export interface ICode { 5 | code: string; 6 | map?: string; 7 | dts?: tsTypes.OutputFile; 8 | dtsmap?: tsTypes.OutputFile; 9 | references?: string[]; 10 | } 11 | export declare function convertEmitOutput(output: tsTypes.EmitOutput, references?: string[]): ICode; 12 | export declare function getAllReferences(importer: string, snapshot: tsTypes.IScriptSnapshot | undefined, options: tsTypes.CompilerOptions): string[]; 13 | export declare class TsCache { 14 | private noCache; 15 | private host; 16 | private cacheRoot; 17 | private options; 18 | private rollupConfig; 19 | private context; 20 | private cacheVersion; 21 | private cachePrefix; 22 | private dependencyTree; 23 | private ambientTypes; 24 | private ambientTypesDirty; 25 | private cacheDir; 26 | private codeCache; 27 | private typesCache; 28 | private semanticDiagnosticsCache; 29 | private syntacticDiagnosticsCache; 30 | private hashOptions; 31 | constructor(noCache: boolean, runClean: boolean, hashIgnoreUnknown: boolean, host: tsTypes.LanguageServiceHost, cacheRoot: string, options: tsTypes.CompilerOptions, rollupConfig: any, rootFilenames: string[], context: RollupContext); 32 | private clean; 33 | setDependency(importee: string, importer: string): void; 34 | walkTree(cb: (id: string) => void | false): void; 35 | done(): void; 36 | getCompiled(id: string, snapshot: tsTypes.IScriptSnapshot, transform: () => ICode | undefined): ICode | undefined; 37 | getSyntacticDiagnostics(id: string, snapshot: tsTypes.IScriptSnapshot, check: () => tsTypes.Diagnostic[]): IDiagnostics[]; 38 | getSemanticDiagnostics(id: string, snapshot: tsTypes.IScriptSnapshot, check: () => tsTypes.Diagnostic[]): IDiagnostics[]; 39 | private checkAmbientTypes; 40 | private getDiagnostics; 41 | private getCached; 42 | private init; 43 | private markAsDirty; 44 | /** @returns true if node, any of its imports, or any ambient types changed */ 45 | private isDirty; 46 | /** @returns an FS-safe hash string for use as a path to the cached content */ 47 | private createHash; 48 | } 49 | //# sourceMappingURL=tscache.d.ts.map -------------------------------------------------------------------------------- /dist/tscache.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"tscache.d.ts","sourceRoot":"","sources":["../src/tscache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,YAAY,CAAC;AAOtC,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAI1C,OAAO,EAAE,YAAY,EAAqB,MAAM,eAAe,CAAC;AAEhE,MAAM,WAAW,KAAK;IAErB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC;IACzB,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAaD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,KAAK,CAiB1F;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,eAAe,GAAG,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,eAAe,YAYjI;AAED,qBAAa,OAAO;IAcP,OAAO,CAAC,OAAO;IAA0D,OAAO,CAAC,IAAI;IAA+B,OAAO,CAAC,SAAS;IAAU,OAAO,CAAC,OAAO;IAA2B,OAAO,CAAC,YAAY;IAAgC,OAAO,CAAC,OAAO;IAZxQ,OAAO,CAAC,YAAY,CAAO;IAC3B,OAAO,CAAC,WAAW,CAAW;IAC9B,OAAO,CAAC,cAAc,CAAQ;IAC9B,OAAO,CAAC,YAAY,CAAmB;IACvC,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAU;IAC1B,OAAO,CAAC,SAAS,CAA6B;IAC9C,OAAO,CAAC,UAAU,CAAkB;IACpC,OAAO,CAAC,wBAAwB,CAA0B;IAC1D,OAAO,CAAC,yBAAyB,CAA0B;IAC3D,OAAO,CAAC,WAAW,CAA+C;gBAE9C,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAU,IAAI,EAAE,OAAO,CAAC,mBAAmB,EAAU,SAAS,EAAE,MAAM,EAAU,OAAO,EAAE,OAAO,CAAC,eAAe,EAAU,YAAY,EAAE,GAAG,EAAE,aAAa,EAAE,MAAM,EAAE,EAAU,OAAO,EAAE,aAAa;IAqCvR,OAAO,CAAC,KAAK;IA6BN,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAQvD,QAAQ,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,GAAG,KAAK,GAAG,IAAI;IAShD,IAAI;IAYJ,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,eAAe,EAAE,SAAS,EAAE,MAAM,KAAK,GAAG,SAAS,GAAG,KAAK,GAAG,SAAS;IAOjH,uBAAuB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,eAAe,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC,UAAU,EAAE,GAAG,YAAY,EAAE;IAKzH,sBAAsB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,eAAe,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC,UAAU,EAAE,GAAG,YAAY,EAAE;IAK/H,OAAO,CAAC,iBAAiB;IAkBzB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,SAAS;IA8BjB,OAAO,CAAC,IAAI;IAQZ,OAAO,CAAC,WAAW;IAKnB,8EAA8E;IAC9E,OAAO,CAAC,OAAO;IA+Bf,8EAA8E;IAC9E,OAAO,CAAC,UAAU;CAKlB"} -------------------------------------------------------------------------------- /dist/tslib.d.ts: -------------------------------------------------------------------------------- 1 | export declare const TSLIB = "tslib"; 2 | export declare const TSLIB_VIRTUAL = "\0tslib.js"; 3 | export declare let tslibSource: string; 4 | export declare let tslibVersion: string; 5 | //# sourceMappingURL=tslib.d.ts.map -------------------------------------------------------------------------------- /dist/tslib.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"tslib.d.ts","sourceRoot":"","sources":["../src/tslib.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,KAAK,UAAU,CAAC;AAC7B,eAAO,MAAM,aAAa,eAAe,CAAC;AAC1C,eAAO,IAAI,WAAW,EAAE,MAAM,CAAC;AAC/B,eAAO,IAAI,YAAY,EAAE,MAAM,CAAC"} -------------------------------------------------------------------------------- /dist/tsproxy.d.ts: -------------------------------------------------------------------------------- 1 | import * as tsTypes from "typescript"; 2 | export declare let tsModule: typeof tsTypes; 3 | export declare function setTypescriptModule(override: typeof tsTypes): void; 4 | //# sourceMappingURL=tsproxy.d.ts.map -------------------------------------------------------------------------------- /dist/tsproxy.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"tsproxy.d.ts","sourceRoot":"","sources":["../src/tsproxy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,YAAY,CAAC;AAEtC,eAAO,IAAI,QAAQ,EAAE,OAAO,OAAO,CAAC;AAEpC,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,OAAO,OAAO,QAG3D"} -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const pkg = require("./package.json"); 2 | 3 | /** @type {import("ts-jest").InitialOptionsTsJest} */ 4 | const config = { 5 | // ts-jest settings 6 | preset: "ts-jest", 7 | globals: { 8 | "ts-jest": { 9 | tsconfig: "./tsconfig.test.json", 10 | }, 11 | // other globals (unrelated to ts-jest) -- these are namespaced so they don't conflict with anything else 12 | "rpt2__TS_VERSION_RANGE": pkg.peerDependencies.typescript, 13 | "rpt2__ROLLUP_VERSION_RANGE": pkg.peerDependencies.rollup, 14 | "rpt2__RPT2_VERSION": pkg.version, 15 | }, 16 | 17 | // jest settings 18 | injectGlobals: false, // use @jest/globals instead 19 | restoreMocks: true, 20 | // only use *.spec.ts files in __tests__, no auto-generated files 21 | testMatch: ["**/__tests__/**/*.spec.ts?(x)"], 22 | coveragePathIgnorePatterns: [ 23 | "node_modules", // default 24 | "/__tests__/" // ignore any test helper files 25 | ], 26 | }; 27 | 28 | module.exports = config; 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rollup-plugin-typescript2", 3 | "version": "0.36.1", 4 | "description": "Seamless integration between Rollup and TypeScript. Now with errors.", 5 | "main": "dist/rollup-plugin-typescript2.cjs.js", 6 | "module": "dist/rollup-plugin-typescript2.es.js", 7 | "jsnext:main": "dist/rollup-plugin-typescript2.es.js", 8 | "types": "dist/index.d.ts", 9 | "files": [ 10 | "dist" 11 | ], 12 | "keywords": [ 13 | "rollup-plugin-typescript2", 14 | "rollup-plugin-typescript", 15 | "rollup-plugin", 16 | "typescript", 17 | "es2015", 18 | "rollup", 19 | "npm" 20 | ], 21 | "license": "MIT", 22 | "homepage": "https://github.com/ezolenko/rollup-plugin-typescript2", 23 | "author": "@ezolenko", 24 | "scripts": { 25 | "prebuild": "rimraf dist/*", 26 | "build": "rimraf dist/* && rollup -c", 27 | "watch": "rollup -c rollup.config.self.js -w ", 28 | "build-self": "rimraf dist/* && rollup -c rollup.config.self.js", 29 | "lint": "eslint .", 30 | "test": "jest", 31 | "test:watch": "jest --watch", 32 | "test:coverage": "jest --coverage" 33 | }, 34 | "dependencies": { 35 | "@rollup/pluginutils": "^4.1.2", 36 | "find-cache-dir": "^3.3.2", 37 | "fs-extra": "^10.0.0", 38 | "semver": "^7.5.4", 39 | "tslib": "^2.6.2" 40 | }, 41 | "peerDependencies": { 42 | "rollup": ">=1.26.3", 43 | "typescript": ">=2.4.0" 44 | }, 45 | "devDependencies": { 46 | "@jest/globals": "^28.0.3", 47 | "@rollup/plugin-commonjs": "^22.0.0", 48 | "@rollup/plugin-node-resolve": "13.2.1", 49 | "@types/find-cache-dir": "^2.0.0", 50 | "@types/fs-extra": "8.0.1", 51 | "@types/graphlib": "2.1.7", 52 | "@types/jest": "^27.5.0", 53 | "@types/lodash": "4.14.161", 54 | "@types/node": "8.0.47", 55 | "@types/object-hash": "1.3.3", 56 | "@types/semver": "7.3.12", 57 | "@typescript-eslint/eslint-plugin": "^6.7.3", 58 | "@typescript-eslint/parser": "^6.7.3", 59 | "colors": "1.4.0", 60 | "eslint": "^8.50.0", 61 | "graphlib": "2.1.8", 62 | "jest": "^29.7.0", 63 | "lodash": "4.17.21", 64 | "object-hash": "3.0.0", 65 | "rimraf": "3.0.2", 66 | "rollup": "^2.70.2", 67 | "rollup-plugin-re": "1.0.7", 68 | "rollup-plugin-typescript2": "0.35.0", 69 | "ts-jest": "^29.1.1", 70 | "typescript": "^5.1.3" 71 | }, 72 | "repository": { 73 | "type": "git", 74 | "url": "git+https://github.com/ezolenko/rollup-plugin-typescript2.git" 75 | }, 76 | "bugs": { 77 | "url": "https://github.com/ezolenko/rollup-plugin-typescript2/issues" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /rollup.config.base.js: -------------------------------------------------------------------------------- 1 | import resolve from "@rollup/plugin-node-resolve"; 2 | import commonjs from "@rollup/plugin-commonjs"; 3 | import replace from "rollup-plugin-re"; 4 | 5 | const pkg = require("./package.json"); 6 | 7 | export default { 8 | input: "src/index.ts", 9 | 10 | external: [ 11 | // Node built-ins 12 | "fs", 13 | "crypto", 14 | "path", 15 | "constants", 16 | "stream", 17 | "util", 18 | "assert", 19 | "os", 20 | // deps 21 | "fs-extra", 22 | "semver", 23 | "@rollup/pluginutils", 24 | ], 25 | 26 | plugins: [ 27 | replace 28 | ({ 29 | replaces: 30 | { 31 | "$TS_VERSION_RANGE": pkg.peerDependencies.typescript, 32 | "$ROLLUP_VERSION_RANGE": pkg.peerDependencies.rollup, 33 | "$RPT2_VERSION": pkg.version, 34 | }, 35 | }), 36 | resolve({ jsnext: true, preferBuiltins: true, }), 37 | commonjs 38 | ({ 39 | include: "node_modules/**", 40 | }), 41 | ], 42 | 43 | output: [ 44 | { 45 | format: "cjs", 46 | file: pkg.main, 47 | sourcemap: true, 48 | banner: "/* eslint-disable */", 49 | exports: "auto", 50 | }, 51 | { 52 | format: "es", 53 | file: pkg.module, 54 | sourcemap: true, 55 | banner: "/* eslint-disable */", 56 | exports: "auto", 57 | }, 58 | { 59 | format: "es", 60 | file: "build-self/" + pkg.module, 61 | sourcemap: true, 62 | banner: "/* eslint-disable */", 63 | exports: "auto", 64 | }, 65 | ], 66 | }; 67 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import ts from "rollup-plugin-typescript2"; 2 | 3 | import config from "./rollup.config.base"; 4 | 5 | config.plugins.push(ts({ verbosity: 2, abortOnError: false })); 6 | 7 | export default config; 8 | -------------------------------------------------------------------------------- /rollup.config.self.js: -------------------------------------------------------------------------------- 1 | import ts from "./build-self/dist/rollup-plugin-typescript2.es"; 2 | 3 | import config from "./rollup.config.base"; 4 | 5 | config.plugins.push(ts({ verbosity: 2, abortOnError: false, clean: false })); 6 | 7 | export default config; 8 | -------------------------------------------------------------------------------- /src/context.ts: -------------------------------------------------------------------------------- 1 | import { PluginContext } from "rollup"; 2 | 3 | export enum VerbosityLevel 4 | { 5 | Error = 0, 6 | Warning, 7 | Info, 8 | Debug, 9 | } 10 | 11 | function getText (message: string | (() => string)): string { 12 | return typeof message === "string" ? message : message(); 13 | } 14 | 15 | /** cannot be used in options hook (which does not have this.warn and this.error), but can be in other hooks */ 16 | export class RollupContext 17 | { 18 | constructor(private verbosity: VerbosityLevel, private bail: boolean, private context: PluginContext, private prefix: string = "") 19 | { 20 | } 21 | 22 | public warn(message: string | (() => string)): void 23 | { 24 | if (this.verbosity < VerbosityLevel.Warning) 25 | return; 26 | this.context.warn(`${getText(message)}`); 27 | } 28 | 29 | public error(message: string | (() => string)): void | never 30 | { 31 | if (this.verbosity < VerbosityLevel.Error) 32 | return; 33 | 34 | if (this.bail) 35 | this.context.error(`${getText(message)}`); 36 | else 37 | this.context.warn(`${getText(message)}`); 38 | } 39 | 40 | public info(message: string | (() => string)): void 41 | { 42 | if (this.verbosity < VerbosityLevel.Info) 43 | return; 44 | console.log(`${this.prefix}${getText(message)}`); 45 | } 46 | 47 | public debug(message: string | (() => string)): void 48 | { 49 | if (this.verbosity < VerbosityLevel.Debug) 50 | return; 51 | console.log(`${this.prefix}${getText(message)}`); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/diagnostics-format-host.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import * as tsTypes from "typescript"; 3 | 4 | import { tsModule } from "./tsproxy"; 5 | 6 | export class FormatHost implements tsTypes.FormatDiagnosticsHost 7 | { 8 | public getCurrentDirectory(): string 9 | { 10 | return tsModule.sys.getCurrentDirectory(); 11 | } 12 | 13 | public getCanonicalFileName = path.normalize; 14 | public getNewLine = () => tsModule.sys.newLine; 15 | } 16 | 17 | export const formatHost = new FormatHost(); 18 | -------------------------------------------------------------------------------- /src/diagnostics.ts: -------------------------------------------------------------------------------- 1 | import * as tsTypes from "typescript"; 2 | import { red, white, yellow } from "colors/safe"; 3 | 4 | import { tsModule } from "./tsproxy"; 5 | import { RollupContext } from "./context"; 6 | import { formatHost } from "./diagnostics-format-host"; 7 | 8 | export interface IDiagnostics 9 | { 10 | flatMessage: string; 11 | formatted: string; 12 | fileLine?: string; 13 | category: tsTypes.DiagnosticCategory; 14 | code: number; 15 | type: string; 16 | } 17 | 18 | export function convertDiagnostic(type: string, data: tsTypes.Diagnostic[]): IDiagnostics[] 19 | { 20 | return data.map((diagnostic) => 21 | { 22 | const entry: IDiagnostics = { 23 | flatMessage: tsModule.flattenDiagnosticMessageText(diagnostic.messageText, formatHost.getNewLine()), 24 | formatted: tsModule.formatDiagnosticsWithColorAndContext(data, formatHost), 25 | category: diagnostic.category, 26 | code: diagnostic.code, 27 | type, 28 | }; 29 | 30 | if (diagnostic.file && diagnostic.start !== undefined) 31 | { 32 | const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); 33 | entry.fileLine = `${diagnostic.file.fileName}(${line + 1},${character + 1})`; 34 | } 35 | 36 | return entry; 37 | }); 38 | } 39 | 40 | export function printDiagnostics(context: RollupContext, diagnostics: IDiagnostics[], pretty = true): void 41 | { 42 | diagnostics.forEach((diagnostic) => 43 | { 44 | let print; 45 | let color; 46 | let category; 47 | switch (diagnostic.category) 48 | { 49 | case tsModule.DiagnosticCategory.Message: 50 | print = context.info; 51 | color = white; 52 | category = ""; 53 | break; 54 | case tsModule.DiagnosticCategory.Error: 55 | print = context.error; 56 | color = red; 57 | category = "error"; 58 | break; 59 | case tsModule.DiagnosticCategory.Warning: 60 | default: 61 | print = context.warn; 62 | color = yellow; 63 | category = "warning"; 64 | break; 65 | } 66 | 67 | const type = diagnostic.type + " "; 68 | 69 | if (pretty) 70 | return print.call(context, `${diagnostic.formatted}`); 71 | 72 | if (diagnostic.fileLine !== undefined) 73 | return print.call(context, `${diagnostic.fileLine}: ${type}${category} TS${diagnostic.code}: ${color(diagnostic.flatMessage)}`); 74 | 75 | return print.call(context, `${type}${category} TS${diagnostic.code}: ${color(diagnostic.flatMessage)}`); 76 | }); 77 | } 78 | -------------------------------------------------------------------------------- /src/get-options-overrides.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import * as tsTypes from "typescript"; 3 | import { createFilter as createRollupFilter, normalizePath as normalize } from "@rollup/pluginutils"; 4 | 5 | import { tsModule } from "./tsproxy"; 6 | import { IOptions } from "./ioptions"; 7 | import { RollupContext } from "./context"; 8 | 9 | export function getOptionsOverrides({ useTsconfigDeclarationDir, cacheRoot }: IOptions, preParsedTsconfig?: tsTypes.ParsedCommandLine): tsTypes.CompilerOptions 10 | { 11 | const overrides: tsTypes.CompilerOptions = { 12 | noEmitHelpers: false, 13 | importHelpers: true, 14 | noResolve: false, 15 | noEmit: false, 16 | noEmitOnError: false, 17 | inlineSourceMap: false, 18 | outDir: normalize(`${cacheRoot}/placeholder`), // need an outdir that is different from source or tsconfig parsing trips up. https://github.com/Microsoft/TypeScript/issues/24715 19 | allowNonTsExtensions: true, 20 | }; 21 | 22 | if (!preParsedTsconfig) 23 | return overrides; 24 | 25 | if (preParsedTsconfig.options.moduleResolution === tsModule.ModuleResolutionKind.Classic) 26 | overrides.moduleResolution = tsModule.ModuleResolutionKind.Node10; 27 | if (preParsedTsconfig.options.module === undefined) 28 | overrides.module = tsModule.ModuleKind.ES2015; 29 | 30 | // only set declarationDir if useTsconfigDeclarationDir is enabled 31 | if (!useTsconfigDeclarationDir) 32 | overrides.declarationDir = undefined; 33 | 34 | // unsetting sourceRoot if sourceMap is not enabled (in case original tsconfig had inlineSourceMap set that is being unset and would cause TS5051) 35 | const sourceMap = preParsedTsconfig.options.sourceMap; 36 | if (!sourceMap) 37 | overrides.sourceRoot = undefined; 38 | 39 | return overrides; 40 | } 41 | 42 | function expandIncludeWithDirs(include: string | string[], dirs: string[]) 43 | { 44 | const newDirs: string[] = []; 45 | 46 | dirs.forEach(root => { 47 | if (include instanceof Array) 48 | include.forEach(x => newDirs.push(normalize(path.join(root, x)))); 49 | else 50 | newDirs.push(normalize(path.join(root, include))); 51 | }); 52 | return newDirs; 53 | } 54 | 55 | export function createFilter(context: RollupContext, pluginOptions: IOptions, parsedConfig: tsTypes.ParsedCommandLine) 56 | { 57 | let included = pluginOptions.include; 58 | let excluded = pluginOptions.exclude; 59 | 60 | if (parsedConfig.options.rootDirs) 61 | { 62 | included = expandIncludeWithDirs(included, parsedConfig.options.rootDirs); 63 | excluded = expandIncludeWithDirs(excluded, parsedConfig.options.rootDirs); 64 | } 65 | 66 | if (parsedConfig.projectReferences) 67 | { 68 | included = expandIncludeWithDirs(included, parsedConfig.projectReferences.map((x) => x.path)).concat(included); 69 | excluded = expandIncludeWithDirs(excluded, parsedConfig.projectReferences.map((x) => x.path)).concat(excluded); 70 | } 71 | 72 | context.debug(() => `included:\n${JSON.stringify(included, undefined, 4)}`); 73 | context.debug(() => `excluded:\n${JSON.stringify(excluded, undefined, 4)}`); 74 | return createRollupFilter(included, excluded, { resolve: parsedConfig.options.rootDir }); 75 | } 76 | -------------------------------------------------------------------------------- /src/host.ts: -------------------------------------------------------------------------------- 1 | import * as tsTypes from "typescript"; 2 | import { normalizePath as normalize } from "@rollup/pluginutils"; 3 | 4 | import { tsModule } from "./tsproxy"; 5 | import { TransformerFactoryCreator } from "./ioptions"; 6 | 7 | export class LanguageServiceHost implements tsTypes.LanguageServiceHost 8 | { 9 | private snapshots: { [fileName: string]: tsTypes.IScriptSnapshot } = {}; 10 | private versions: { [fileName: string]: number } = {}; 11 | private service?: tsTypes.LanguageService; 12 | private fileNames: Set; 13 | 14 | constructor(private parsedConfig: tsTypes.ParsedCommandLine, private transformers: TransformerFactoryCreator[], private cwd: string) 15 | { 16 | this.fileNames = new Set(parsedConfig.fileNames); 17 | } 18 | 19 | public reset() 20 | { 21 | this.snapshots = {}; 22 | this.versions = {}; 23 | } 24 | 25 | public setLanguageService(service: tsTypes.LanguageService) 26 | { 27 | this.service = service; 28 | } 29 | 30 | public setSnapshot(fileName: string, source: string): tsTypes.IScriptSnapshot 31 | { 32 | fileName = normalize(fileName); 33 | 34 | const snapshot = tsModule.ScriptSnapshot.fromString(source); 35 | this.snapshots[fileName] = snapshot; 36 | this.versions[fileName] = (this.versions[fileName] || 0) + 1; 37 | this.fileNames.add(fileName); 38 | return snapshot; 39 | } 40 | 41 | public getScriptSnapshot(fileName: string): tsTypes.IScriptSnapshot | undefined 42 | { 43 | fileName = normalize(fileName); 44 | 45 | if (fileName in this.snapshots) 46 | return this.snapshots[fileName]; 47 | 48 | const source = tsModule.sys.readFile(fileName); 49 | if (source) 50 | return this.setSnapshot(fileName, source); 51 | 52 | return undefined; 53 | } 54 | 55 | public getScriptFileNames = () => Array.from(this.fileNames.values()); 56 | 57 | public getScriptVersion(fileName: string) 58 | { 59 | fileName = normalize(fileName); 60 | 61 | return (this.versions[fileName] || 0).toString(); 62 | } 63 | 64 | public getCustomTransformers(): tsTypes.CustomTransformers | undefined 65 | { 66 | if (this.service === undefined || this.transformers === undefined || this.transformers.length === 0) 67 | return undefined; 68 | 69 | const transformer: tsTypes.CustomTransformers = 70 | { 71 | before: [], 72 | after: [], 73 | afterDeclarations: [], 74 | }; 75 | 76 | for (const creator of this.transformers) 77 | { 78 | const factory = creator(this.service); 79 | if (factory.before) 80 | transformer.before = transformer.before!.concat(factory.before); 81 | if (factory.after) 82 | transformer.after = transformer.after!.concat(factory.after); 83 | if (factory.afterDeclarations) 84 | transformer.afterDeclarations = transformer.afterDeclarations!.concat(factory.afterDeclarations); 85 | } 86 | 87 | return transformer; 88 | } 89 | 90 | public getCompilationSettings = () => this.parsedConfig.options; 91 | public getTypeRootsVersion = () => 0; 92 | public getCurrentDirectory = () => this.cwd; 93 | 94 | public useCaseSensitiveFileNames = () => tsModule.sys.useCaseSensitiveFileNames; 95 | public getDefaultLibFileName = tsModule.getDefaultLibFilePath; // confusing naming: https://github.com/microsoft/TypeScript/issues/35318 96 | 97 | public readDirectory = tsModule.sys.readDirectory; 98 | public readFile = tsModule.sys.readFile; 99 | public fileExists = tsModule.sys.fileExists; 100 | public directoryExists = tsModule.sys.directoryExists; 101 | public getDirectories = tsModule.sys.getDirectories; 102 | public realpath = tsModule.sys.realpath!; // this exists in the default implementation: https://github.com/microsoft/TypeScript/blob/ab2523bbe0352d4486f67b73473d2143ad64d03d/src/compiler/sys.ts#L1288 103 | 104 | public trace = console.log; 105 | } 106 | -------------------------------------------------------------------------------- /src/icache.ts: -------------------------------------------------------------------------------- 1 | export interface ICache 2 | { 3 | exists(name: string): boolean; 4 | 5 | path(name: string): string; 6 | 7 | match(names: string[]): boolean; 8 | 9 | read(name: string): DataType | null | undefined; 10 | 11 | write(name: string, data: DataType): void; 12 | 13 | touch(name: string): void; 14 | 15 | roll(): void; 16 | } 17 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { relative, dirname, normalize as pathNormalize, resolve } from "path"; 2 | import * as tsTypes from "typescript"; 3 | import { PluginImpl, InputOptions, TransformResult, SourceMap, Plugin } from "rollup"; 4 | import { normalizePath as normalize } from "@rollup/pluginutils"; 5 | import { blue, red, yellow, green } from "colors/safe"; 6 | import { satisfies } from "semver"; 7 | import findCacheDir from "find-cache-dir"; 8 | 9 | import { RollupContext, VerbosityLevel } from "./context"; 10 | import { LanguageServiceHost } from "./host"; 11 | import { TsCache, convertEmitOutput, getAllReferences, ICode } from "./tscache"; 12 | import { tsModule, setTypescriptModule } from "./tsproxy"; 13 | import { IOptions } from "./ioptions"; 14 | import { parseTsConfig } from "./parse-tsconfig"; 15 | import { convertDiagnostic, printDiagnostics } from "./diagnostics"; 16 | import { TSLIB, TSLIB_VIRTUAL, tslibSource, tslibVersion } from "./tslib"; 17 | import { createFilter } from "./get-options-overrides"; 18 | 19 | // these use globals during testing and are substituted by rollup-plugin-re during builds 20 | const TS_VERSION_RANGE = (global as any)?.rpt2__TS_VERSION_RANGE || "$TS_VERSION_RANGE"; 21 | const ROLLUP_VERSION_RANGE = (global as any)?.rpt2__ROLLUP_VERSION_RANGE || "$ROLLUP_VERSION_RANGE"; 22 | const RPT2_VERSION = (global as any)?.rpt2__ROLLUP_VERSION_RANGE || "$RPT2_VERSION"; 23 | 24 | type RPT2Options = Partial; 25 | 26 | export { RPT2Options } 27 | 28 | const typescript: PluginImpl = (options) => 29 | { 30 | let watchMode = false; 31 | let supportsThisLoad = false; 32 | let generateRound = 0; 33 | let rollupOptions: InputOptions; 34 | let context: RollupContext; 35 | let filter: ReturnType; 36 | let parsedConfig: tsTypes.ParsedCommandLine; 37 | let tsConfigPath: string | undefined; 38 | let servicesHost: LanguageServiceHost; 39 | let service: tsTypes.LanguageService; 40 | let documentRegistry: tsTypes.DocumentRegistry; // keep the same DocumentRegistry between watch cycles 41 | let cache: TsCache; 42 | let noErrors = true; 43 | let transformedFiles: Set; 44 | const declarations: { [name: string]: { type: tsTypes.OutputFile; map?: tsTypes.OutputFile } } = {}; 45 | const checkedFiles = new Set(); 46 | 47 | const getDiagnostics = (id: string, snapshot: tsTypes.IScriptSnapshot) => 48 | { 49 | return cache.getSyntacticDiagnostics(id, snapshot, () => 50 | { 51 | return service.getSyntacticDiagnostics(id); 52 | }).concat(cache.getSemanticDiagnostics(id, snapshot, () => 53 | { 54 | return service.getSemanticDiagnostics(id); 55 | })); 56 | } 57 | 58 | const typecheckFile = (id: string, snapshot: tsTypes.IScriptSnapshot | undefined, tcContext: RollupContext) => 59 | { 60 | if (!snapshot) 61 | return; 62 | 63 | id = normalize(id); 64 | checkedFiles.add(id); // must come before print, as that could bail 65 | 66 | const diagnostics = getDiagnostics(id, snapshot); 67 | printDiagnostics(tcContext, diagnostics, parsedConfig.options.pretty !== false); 68 | 69 | if (diagnostics.length > 0) 70 | noErrors = false; 71 | } 72 | 73 | const addDeclaration = (id: string, result: ICode) => 74 | { 75 | if (!result.dts) 76 | return; 77 | 78 | const key = normalize(id); 79 | declarations[key] = { type: result.dts, map: result.dtsmap }; 80 | context.debug(() => `${blue("generated declarations")} for '${key}'`); 81 | } 82 | 83 | /** common resolution check -- only resolve files that aren't declarations and pass `filter` */ 84 | const shouldResolve = (id: string): boolean => { 85 | if (id.endsWith(".d.ts") || id.endsWith(".d.cts") || id.endsWith(".d.mts")) 86 | return false; 87 | 88 | if (!filter(id)) 89 | return false; 90 | 91 | return true; 92 | } 93 | 94 | /** to be called at the end of Rollup's build phase, before output generation */ 95 | const buildDone = (): void => 96 | { 97 | if (!watchMode && !noErrors) 98 | context.info(yellow("there were errors or warnings.")); 99 | 100 | cache?.done(); // if there's an initialization error in `buildStart`, such as a `tsconfig` error, the cache may not exist yet 101 | } 102 | 103 | const pluginOptions: IOptions = Object.assign({}, 104 | { 105 | check: true, 106 | verbosity: VerbosityLevel.Warning, 107 | clean: false, 108 | cacheRoot: findCacheDir({ name: "rollup-plugin-typescript2" }), 109 | include: ["*.ts+(|x)", "**/*.ts+(|x)", "**/*.cts", "**/*.mts"], 110 | exclude: ["*.d.ts", "**/*.d.ts", "**/*.d.cts", "**/*.d.mts"], 111 | abortOnError: true, 112 | rollupCommonJSResolveHack: false, 113 | tsconfig: undefined, 114 | useTsconfigDeclarationDir: false, 115 | tsconfigOverride: {}, 116 | transformers: [], 117 | tsconfigDefaults: {}, 118 | objectHashIgnoreUnknownHack: false, 119 | cwd: process.cwd(), 120 | }, options as IOptions); 121 | 122 | if (!pluginOptions.typescript) { 123 | pluginOptions.typescript = require("typescript"); 124 | } 125 | setTypescriptModule(pluginOptions.typescript); 126 | // eslint-disable-next-line prefer-const 127 | documentRegistry = tsModule.createDocumentRegistry(); 128 | 129 | const self: Plugin = { 130 | 131 | name: "rpt2", 132 | 133 | options(config) 134 | { 135 | rollupOptions = { ...config }; 136 | return config; 137 | }, 138 | 139 | buildStart() 140 | { 141 | context = new RollupContext(pluginOptions.verbosity, pluginOptions.abortOnError, this, "rpt2: "); 142 | 143 | watchMode = process.env.ROLLUP_WATCH === "true" || !!this.meta.watchMode; // meta.watchMode was added in 2.14.0 to capture watch via Rollup API (i.e. no env var) (c.f. https://github.com/rollup/rollup/blob/master/CHANGELOG.md#2140) 144 | ({ parsedTsConfig: parsedConfig, fileName: tsConfigPath } = parseTsConfig(context, pluginOptions)); 145 | 146 | // print out all versions and configurations 147 | context.info(`typescript version: ${tsModule.version}`); 148 | context.info(`tslib version: ${tslibVersion}`); 149 | context.info(`rollup version: ${this.meta.rollupVersion}`); 150 | 151 | if (!satisfies(tsModule.version, TS_VERSION_RANGE, { includePrerelease: true })) 152 | context.error(`Installed TypeScript version '${tsModule.version}' is outside of supported range '${TS_VERSION_RANGE}'`); 153 | 154 | if (!satisfies(this.meta.rollupVersion, ROLLUP_VERSION_RANGE, { includePrerelease: true })) 155 | context.error(`Installed Rollup version '${this.meta.rollupVersion}' is outside of supported range '${ROLLUP_VERSION_RANGE}'`); 156 | 157 | supportsThisLoad = satisfies(this.meta.rollupVersion, ">=2.60.0", { includePrerelease : true }); // this.load is 2.60.0+ only (c.f. https://github.com/rollup/rollup/blob/master/CHANGELOG.md#2600) 158 | if (!supportsThisLoad) 159 | context.warn(() => `${yellow("You are using a Rollup version '<2.60.0'")}. This may result in type-only files being ignored.`); 160 | 161 | context.info(`rollup-plugin-typescript2 version: ${RPT2_VERSION}`); 162 | context.debug(() => `plugin options:\n${JSON.stringify(pluginOptions, (key, value) => key === "typescript" ? `version ${(value as typeof tsModule).version}` : value, 4)}`); 163 | context.debug(() => `rollup config:\n${JSON.stringify(rollupOptions, undefined, 4)}`); 164 | context.debug(() => `tsconfig path: ${tsConfigPath}`); 165 | 166 | if (pluginOptions.objectHashIgnoreUnknownHack) 167 | context.warn(() => `${yellow("You are using 'objectHashIgnoreUnknownHack' option")}. If you enabled it because of async functions, try disabling it now.`); 168 | 169 | if (pluginOptions.rollupCommonJSResolveHack) 170 | context.warn(() => `${yellow("You are using 'rollupCommonJSResolveHack' option")}. This is no longer needed, try disabling it now.`); 171 | 172 | if (watchMode) 173 | context.info(`running in watch mode`); 174 | 175 | filter = createFilter(context, pluginOptions, parsedConfig); 176 | 177 | servicesHost = new LanguageServiceHost(parsedConfig, pluginOptions.transformers, pluginOptions.cwd); 178 | service = tsModule.createLanguageService(servicesHost, documentRegistry); 179 | servicesHost.setLanguageService(service); 180 | 181 | const runClean = pluginOptions.clean; 182 | const noCache = pluginOptions.clean || watchMode; 183 | 184 | cache = new TsCache(noCache, runClean, pluginOptions.objectHashIgnoreUnknownHack, servicesHost, pluginOptions.cacheRoot, parsedConfig.options, rollupOptions, parsedConfig.fileNames, context); 185 | 186 | // reset transformedFiles Set on each watch cycle 187 | transformedFiles = new Set(); 188 | 189 | // printing compiler option errors 190 | if (pluginOptions.check) { 191 | const diagnostics = convertDiagnostic("options", service.getCompilerOptionsDiagnostics()); 192 | printDiagnostics(context, diagnostics, parsedConfig.options.pretty !== false); 193 | if (diagnostics.length > 0) 194 | noErrors = false; 195 | } 196 | }, 197 | 198 | watchChange(id) 199 | { 200 | const key = normalize(id); 201 | delete declarations[key]; 202 | checkedFiles.delete(key); 203 | }, 204 | 205 | resolveId(importee, importer) 206 | { 207 | if (importee === TSLIB) 208 | return TSLIB_VIRTUAL; 209 | 210 | if (!importer) 211 | return; 212 | 213 | importer = normalize(importer); 214 | 215 | // TODO: use module resolution cache 216 | const result = tsModule.nodeModuleNameResolver(importee, importer, parsedConfig.options, tsModule.sys); 217 | const resolved = result.resolvedModule?.resolvedFileName; 218 | 219 | if (!resolved) 220 | return; 221 | 222 | if (!shouldResolve(resolved)) 223 | return; 224 | 225 | cache.setDependency(resolved, importer); 226 | 227 | context.debug(() => `${blue("resolving")} '${importee}' imported by '${importer}'`); 228 | context.debug(() => ` to '${resolved}'`); 229 | 230 | return pathNormalize(resolved); // use host OS separators to fix Windows issue: https://github.com/ezolenko/rollup-plugin-typescript2/pull/251 231 | }, 232 | 233 | load(id) 234 | { 235 | if (id === TSLIB_VIRTUAL) 236 | return tslibSource; 237 | 238 | return null; 239 | }, 240 | 241 | async transform(code, id) 242 | { 243 | transformedFiles.add(id); // note: this does not need normalization as we only compare Rollup <-> Rollup, and not Rollup <-> TS 244 | 245 | if (!filter(id)) 246 | return undefined; 247 | 248 | const snapshot = servicesHost.setSnapshot(id, code); 249 | 250 | // getting compiled file from cache or from ts 251 | const result = cache.getCompiled(id, snapshot, () => 252 | { 253 | const output = service.getEmitOutput(id); 254 | 255 | if (output.emitSkipped) 256 | { 257 | noErrors = false; 258 | // always checking on fatal errors, even if options.check is set to false 259 | typecheckFile(id, snapshot, context); 260 | // since no output was generated, aborting compilation 261 | this.error(red(`Emit skipped for '${id}'. See https://github.com/microsoft/TypeScript/issues/49790 for potential reasons why this may occur`)); 262 | } 263 | 264 | const references = getAllReferences(id, snapshot, parsedConfig.options); 265 | return convertEmitOutput(output, references); 266 | }); 267 | 268 | if (pluginOptions.check) 269 | typecheckFile(id, snapshot, context); 270 | 271 | if (!result) 272 | return undefined; 273 | 274 | if (watchMode && result.references) 275 | { 276 | if (tsConfigPath) 277 | this.addWatchFile(tsConfigPath); 278 | 279 | result.references.map(this.addWatchFile, this); 280 | context.debug(() => `${green(" watching")}: ${result.references!.join("\nrpt2: ")}`); 281 | } 282 | 283 | addDeclaration(id, result); 284 | 285 | // handle all type-only imports by resolving + loading all of TS's references 286 | // Rollup can't see these otherwise, because they are "emit-less" and produce no JS 287 | if (result.references && supportsThisLoad) { 288 | for (const ref of result.references) { 289 | // pre-emptively filter out files that we don't resolve ourselves (e.g. declarations). don't add new files to Rollup's pipeline if we can't resolve them 290 | if (!shouldResolve(ref)) 291 | continue; 292 | 293 | const module = await this.resolve(ref, id); 294 | if (!module || transformedFiles.has(module.id)) // check for circular references (per https://rollupjs.org/guide/en/#thisload) 295 | continue; 296 | 297 | // wait for all to be loaded (otherwise, as this is async, some may end up only loading after `generateBundle`) 298 | await this.load({id: module.id}); 299 | } 300 | } 301 | 302 | // if a user sets this compilerOption, they probably want another plugin (e.g. Babel, ESBuild) to transform their TS instead, while rpt2 just type-checks and/or outputs declarations 303 | // note that result.code is non-existent if emitDeclarationOnly per https://github.com/ezolenko/rollup-plugin-typescript2/issues/268 304 | if (parsedConfig.options.emitDeclarationOnly) 305 | { 306 | context.debug(() => `${blue("emitDeclarationOnly")} enabled, not transforming TS`); 307 | return undefined; 308 | } 309 | 310 | const transformResult: TransformResult = { code: result.code, map: { mappings: "" } }; 311 | 312 | if (result.map) 313 | { 314 | pluginOptions.sourceMapCallback?.(id, result.map); 315 | transformResult.map = JSON.parse(result.map); 316 | } 317 | 318 | return transformResult; 319 | }, 320 | 321 | buildEnd(err) 322 | { 323 | generateRound = 0; // in watch mode, buildEnd resets generate count just before generateBundle for each output 324 | 325 | if (err) 326 | { 327 | buildDone(); 328 | // workaround: err.stack contains err.message and Rollup prints both, causing duplication, so split out the stack itself if it exists (c.f. https://github.com/ezolenko/rollup-plugin-typescript2/issues/103#issuecomment-1172820658) 329 | const stackOnly = err.stack?.split(err.message)[1]; 330 | if (stackOnly) 331 | this.error({ ...err, message: err.message, stack: stackOnly }); 332 | else 333 | this.error(err); 334 | } 335 | 336 | if (!pluginOptions.check) 337 | return buildDone(); 338 | 339 | // walkTree once on each cycle when in watch mode 340 | if (watchMode) 341 | { 342 | cache.walkTree((id) => 343 | { 344 | if (!filter(id)) 345 | return; 346 | 347 | const snapshot = servicesHost.getScriptSnapshot(id); 348 | typecheckFile(id, snapshot, context); 349 | }); 350 | } 351 | 352 | // type-check missed files as well 353 | parsedConfig.fileNames.forEach((name) => 354 | { 355 | const key = normalize(name); 356 | if (checkedFiles.has(key) || !filter(key)) // don't duplicate if it's already been checked 357 | return; 358 | 359 | context.debug(() => `type-checking missed '${key}'`); 360 | const snapshot = servicesHost.getScriptSnapshot(key); 361 | typecheckFile(key, snapshot, context); 362 | }); 363 | 364 | buildDone(); 365 | }, 366 | 367 | generateBundle(this, _output) 368 | { 369 | context.debug(() => `generating target ${generateRound + 1}`); 370 | generateRound++; 371 | 372 | if (!parsedConfig.options.declaration) 373 | return; 374 | 375 | parsedConfig.fileNames.forEach((name) => 376 | { 377 | const key = normalize(name); 378 | if (key in declarations || !filter(key)) 379 | return; 380 | 381 | context.debug(() => `generating missed declarations for '${key}'`); 382 | const out = convertEmitOutput(service.getEmitOutput(key, true)); 383 | addDeclaration(key, out); 384 | }); 385 | 386 | const emitDeclaration = (key: string, extension: string, entry?: tsTypes.OutputFile) => 387 | { 388 | if (!entry) 389 | return; 390 | 391 | let fileName = entry.name; 392 | if (fileName.includes("?")) // HACK for rollup-plugin-vue, it creates virtual modules in form 'file.vue?rollup-plugin-vue=script.ts' 393 | fileName = fileName.split("?", 1) + extension; 394 | 395 | // If 'useTsconfigDeclarationDir' is in plugin options, directly write to 'declarationDir'. 396 | // This may not be under Rollup's output directory, and thus can't be emitted as an asset. 397 | if (pluginOptions.useTsconfigDeclarationDir) 398 | { 399 | context.debug(() => `${blue("emitting declarations")} for '${key}' to '${fileName}'`); 400 | tsModule.sys.writeFile(fileName, entry.text, entry.writeByteOrderMark); 401 | return; 402 | } 403 | 404 | // don't mutate the entry because generateBundle gets called multiple times 405 | let entryText = entry.text 406 | const cachePlaceholder = `${pluginOptions.cacheRoot}/placeholder` 407 | 408 | // modify declaration map sources to correct relative path (only if outputting) 409 | if (extension === ".d.ts.map" && (_output?.file || _output?.dir)) 410 | { 411 | const declarationDir = (_output.file ? dirname(_output.file) : _output.dir) as string; 412 | const parsedText = JSON.parse(entryText) as SourceMap; 413 | // invert back to absolute, then make relative to declarationDir 414 | parsedText.sources = parsedText.sources.map(source => 415 | { 416 | const absolutePath = resolve(cachePlaceholder, source); 417 | return normalize(relative(declarationDir, absolutePath)); 418 | }); 419 | entryText = JSON.stringify(parsedText); 420 | } 421 | 422 | const relativePath = normalize(relative(cachePlaceholder, fileName)); 423 | context.debug(() => `${blue("emitting declarations")} for '${key}' to '${relativePath}'`); 424 | this.emitFile({ 425 | type: "asset", 426 | source: entryText, 427 | fileName: relativePath, 428 | }); 429 | }; 430 | 431 | Object.keys(declarations).forEach((key) => 432 | { 433 | const { type, map } = declarations[key]; 434 | emitDeclaration(key, ".d.ts", type); 435 | emitDeclaration(key, ".d.ts.map", map); 436 | }); 437 | }, 438 | }; 439 | 440 | return self; 441 | }; 442 | 443 | export default typescript; 444 | -------------------------------------------------------------------------------- /src/ioptions.ts: -------------------------------------------------------------------------------- 1 | import * as tsTypes from "typescript"; 2 | 3 | import { tsModule } from "./tsproxy"; 4 | 5 | export interface ICustomTransformer 6 | { 7 | before?: tsTypes.TransformerFactory; 8 | after?: tsTypes.TransformerFactory; 9 | afterDeclarations?: tsTypes.TransformerFactory; 10 | } 11 | 12 | export type TransformerFactoryCreator = (ls: tsTypes.LanguageService) => tsTypes.CustomTransformers | ICustomTransformer; 13 | 14 | export interface IOptions 15 | { 16 | cwd: string; 17 | include: string|string[]; 18 | exclude: string|string[]; 19 | check: boolean; 20 | verbosity: number; 21 | clean: boolean; 22 | cacheRoot: string; 23 | abortOnError: boolean; 24 | rollupCommonJSResolveHack: boolean; 25 | tsconfig?: string; 26 | useTsconfigDeclarationDir: boolean; 27 | typescript: typeof tsModule; 28 | tsconfigOverride: any; 29 | transformers: TransformerFactoryCreator[]; 30 | tsconfigDefaults: any; 31 | sourceMapCallback: (id: string, map: string) => void; 32 | objectHashIgnoreUnknownHack: boolean; 33 | } 34 | -------------------------------------------------------------------------------- /src/parse-tsconfig.ts: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import * as _ from "lodash"; 3 | 4 | import { tsModule } from "./tsproxy"; 5 | import { RollupContext } from "./context"; 6 | import { convertDiagnostic, printDiagnostics } from "./diagnostics"; 7 | import { getOptionsOverrides } from "./get-options-overrides"; 8 | import { IOptions } from "./ioptions"; 9 | 10 | export function parseTsConfig(context: RollupContext, pluginOptions: IOptions) 11 | { 12 | const fileName = tsModule.findConfigFile(pluginOptions.cwd, tsModule.sys.fileExists, pluginOptions.tsconfig); 13 | 14 | // if the value was provided, but no file, fail hard 15 | if (pluginOptions.tsconfig !== undefined && !fileName) 16 | context.error(`failed to open '${pluginOptions.tsconfig}'`); 17 | 18 | let loadedConfig: any = {}; 19 | let baseDir = pluginOptions.cwd; 20 | let configFileName; 21 | let pretty = true; 22 | if (fileName) 23 | { 24 | const text = tsModule.sys.readFile(fileName)!; // readFile only returns undefined when the file doesn't exist, which we already checked above 25 | const result = tsModule.parseConfigFileTextToJson(fileName, text); 26 | pretty = result.config?.pretty ?? pretty; 27 | 28 | if (result.error !== undefined) 29 | { 30 | printDiagnostics(context, convertDiagnostic("config", [result.error]), pretty); 31 | context.error(`failed to parse '${fileName}'`); 32 | } 33 | 34 | loadedConfig = result.config; 35 | baseDir = dirname(fileName); 36 | configFileName = fileName; 37 | } 38 | 39 | const mergedConfig = {}; 40 | _.merge(mergedConfig, pluginOptions.tsconfigDefaults, loadedConfig, pluginOptions.tsconfigOverride); 41 | 42 | const preParsedTsConfig = tsModule.parseJsonConfigFileContent(mergedConfig, tsModule.sys, baseDir, getOptionsOverrides(pluginOptions), configFileName); 43 | const compilerOptionsOverride = getOptionsOverrides(pluginOptions, preParsedTsConfig); 44 | const parsedTsConfig = tsModule.parseJsonConfigFileContent(mergedConfig, tsModule.sys, baseDir, compilerOptionsOverride, configFileName); 45 | 46 | const module = parsedTsConfig.options.module!; 47 | if (module !== tsModule.ModuleKind.ES2015 && module !== tsModule.ModuleKind.ES2020 && module !== tsModule.ModuleKind.ES2022 && module !== tsModule.ModuleKind.ESNext) 48 | context.error(`Incompatible tsconfig option. Module resolves to '${tsModule.ModuleKind[module]}'. This is incompatible with Rollup, please use 'module: "ES2015"', 'module: "ES2020"', 'module: "ES2022"', or 'module: "ESNext"'.`); 49 | 50 | printDiagnostics(context, convertDiagnostic("config", parsedTsConfig.errors), pretty); 51 | 52 | context.debug(`built-in options overrides: ${JSON.stringify(compilerOptionsOverride, undefined, 4)}`); 53 | context.debug(`parsed tsconfig: ${JSON.stringify(parsedTsConfig, undefined, 4)}`); 54 | 55 | return { parsedTsConfig, fileName }; 56 | } 57 | -------------------------------------------------------------------------------- /src/rollingcache.ts: -------------------------------------------------------------------------------- 1 | import { existsSync, readdirSync, renameSync } from "fs"; 2 | import { emptyDirSync, ensureFileSync, readJsonSync, removeSync, writeJsonSync } from "fs-extra"; 3 | import * as _ from "lodash"; 4 | 5 | import { ICache } from "./icache"; 6 | 7 | /** 8 | * Saves data in new cache folder or reads it from old one. 9 | * Avoids perpetually growing cache and situations when things need to consider changed and then reverted data to be changed. 10 | */ 11 | export class RollingCache implements ICache 12 | { 13 | private oldCacheRoot: string; 14 | private newCacheRoot: string; 15 | private rolled: boolean = false; 16 | 17 | /** @param cacheRoot: root folder for the cache */ 18 | constructor(private cacheRoot: string) 19 | { 20 | this.oldCacheRoot = `${this.cacheRoot}/cache`; 21 | this.newCacheRoot = `${this.cacheRoot}/cache_`; 22 | emptyDirSync(this.newCacheRoot); 23 | } 24 | 25 | /** @returns true if name exists in either old cache or new cache */ 26 | public exists(name: string): boolean 27 | { 28 | if (this.rolled) 29 | return false; 30 | 31 | if (existsSync(`${this.newCacheRoot}/${name}`)) 32 | return true; 33 | 34 | return existsSync(`${this.oldCacheRoot}/${name}`); 35 | } 36 | 37 | public path(name: string): string 38 | { 39 | return `${this.oldCacheRoot}/${name}`; 40 | } 41 | 42 | /** @returns true if old cache contains all names and nothing more */ 43 | public match(names: string[]): boolean 44 | { 45 | if (this.rolled) 46 | return false; 47 | 48 | if (!existsSync(this.oldCacheRoot)) 49 | return names.length === 0; // empty folder matches 50 | 51 | return _.isEqual(readdirSync(this.oldCacheRoot).sort(), names.sort()); 52 | } 53 | 54 | /** @returns data for name, must exist in either old cache or new cache */ 55 | public read(name: string): DataType | null | undefined 56 | { 57 | if (existsSync(`${this.newCacheRoot}/${name}`)) 58 | return readJsonSync(`${this.newCacheRoot}/${name}`, { encoding: "utf8", throws: false }); 59 | 60 | return readJsonSync(`${this.oldCacheRoot}/${name}`, { encoding: "utf8", throws: false }); 61 | } 62 | 63 | public write(name: string, data: DataType): void 64 | { 65 | if (this.rolled) 66 | return; 67 | 68 | if (data === undefined) 69 | return; 70 | 71 | writeJsonSync(`${this.newCacheRoot}/${name}`, data); 72 | } 73 | 74 | public touch(name: string) 75 | { 76 | if (this.rolled) 77 | return; 78 | 79 | ensureFileSync(`${this.newCacheRoot}/${name}`); 80 | } 81 | 82 | /** clears old cache and moves new in its place */ 83 | public roll() 84 | { 85 | if (this.rolled) 86 | return; 87 | 88 | this.rolled = true; 89 | removeSync(this.oldCacheRoot); 90 | if (existsSync(this.newCacheRoot)) { 91 | renameSync(this.newCacheRoot, this.oldCacheRoot); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/tscache.ts: -------------------------------------------------------------------------------- 1 | import * as tsTypes from "typescript"; 2 | import * as fs from "fs-extra"; 3 | import * as _ from "lodash"; 4 | import { Graph, alg } from "graphlib"; 5 | import objHash from "object-hash"; 6 | import { blue, yellow, green } from "colors/safe"; 7 | 8 | import { RollupContext } from "./context"; 9 | import { RollingCache } from "./rollingcache"; 10 | import { ICache } from "./icache"; 11 | import { tsModule } from "./tsproxy"; 12 | import { IDiagnostics, convertDiagnostic } from "./diagnostics"; 13 | 14 | export interface ICode 15 | { 16 | code: string; 17 | map?: string; 18 | dts?: tsTypes.OutputFile; 19 | dtsmap?: tsTypes.OutputFile; 20 | references?: string[]; 21 | } 22 | 23 | interface INodeLabel 24 | { 25 | dirty: boolean; 26 | } 27 | 28 | interface ITypeSnapshot 29 | { 30 | id: string; 31 | snapshot: tsTypes.IScriptSnapshot | undefined; 32 | } 33 | 34 | export function convertEmitOutput(output: tsTypes.EmitOutput, references?: string[]): ICode 35 | { 36 | const out: ICode = { code: "", references }; 37 | 38 | output.outputFiles.forEach((e) => 39 | { 40 | if (e.name.endsWith(".d.ts")) 41 | out.dts = e; 42 | else if (e.name.endsWith(".d.ts.map")) 43 | out.dtsmap = e; 44 | else if (e.name.endsWith(".map")) 45 | out.map = e.text; 46 | else 47 | out.code = e.text; 48 | }); 49 | 50 | return out; 51 | } 52 | 53 | export function getAllReferences(importer: string, snapshot: tsTypes.IScriptSnapshot | undefined, options: tsTypes.CompilerOptions) 54 | { 55 | if (!snapshot) 56 | return []; 57 | 58 | const info = tsModule.preProcessFile(snapshot.getText(0, snapshot.getLength()), true, true); 59 | 60 | return _.compact(info.referencedFiles.concat(info.importedFiles).map((reference) => 61 | { 62 | const resolved = tsModule.nodeModuleNameResolver(reference.fileName, importer, options, tsModule.sys); 63 | return resolved.resolvedModule?.resolvedFileName; 64 | })); 65 | } 66 | 67 | export class TsCache 68 | { 69 | private cacheVersion = "9"; 70 | private cachePrefix = "rpt2_"; 71 | private dependencyTree: Graph; 72 | private ambientTypes!: ITypeSnapshot[]; 73 | private ambientTypesDirty = false; 74 | private cacheDir!: string; 75 | private codeCache!: ICache; 76 | private typesCache!: ICache; 77 | private semanticDiagnosticsCache!: ICache; 78 | private syntacticDiagnosticsCache!: ICache; 79 | private hashOptions = { algorithm: "sha1", ignoreUnknown: false }; 80 | 81 | constructor(private noCache: boolean, runClean: boolean, hashIgnoreUnknown: boolean, private host: tsTypes.LanguageServiceHost, private cacheRoot: string, private options: tsTypes.CompilerOptions, private rollupConfig: any, rootFilenames: string[], private context: RollupContext) 82 | { 83 | this.dependencyTree = new Graph({ directed: true }); 84 | this.dependencyTree.setDefaultNodeLabel(() => ({ dirty: false })); 85 | 86 | if (runClean) 87 | this.clean(); 88 | 89 | if (noCache) 90 | return; 91 | 92 | this.hashOptions.ignoreUnknown = hashIgnoreUnknown; 93 | this.cacheDir = `${this.cacheRoot}/${this.cachePrefix}${objHash( 94 | { 95 | version: this.cacheVersion, 96 | rootFilenames, 97 | options: this.options, 98 | rollupConfig: this.rollupConfig, 99 | tsVersion: tsModule.version, 100 | }, 101 | this.hashOptions, 102 | )}`; 103 | 104 | this.init(); 105 | 106 | const automaticTypes = tsModule.getAutomaticTypeDirectiveNames(options, tsModule.sys) 107 | .map((entry) => tsModule.resolveTypeReferenceDirective(entry, undefined, options, tsModule.sys)) 108 | .filter((entry) => entry.resolvedTypeReferenceDirective?.resolvedFileName) 109 | .map((entry) => entry.resolvedTypeReferenceDirective!.resolvedFileName!); 110 | 111 | this.ambientTypes = rootFilenames.filter(file => file.endsWith(".d.ts")) 112 | .concat(automaticTypes) 113 | .map((id) => ({ id, snapshot: this.host.getScriptSnapshot(id) })); 114 | 115 | this.checkAmbientTypes(); 116 | } 117 | 118 | private clean() 119 | { 120 | if (!fs.pathExistsSync(this.cacheRoot)) 121 | return; 122 | 123 | const entries = fs.readdirSync(this.cacheRoot); 124 | entries.forEach((e) => 125 | { 126 | const dir = `${this.cacheRoot}/${e}`; 127 | 128 | /* istanbul ignore if -- this is a safety check, but shouldn't happen when using a dedicated cache dir */ 129 | if (!e.startsWith(this.cachePrefix)) 130 | { 131 | this.context.debug(`skipping cleaning '${dir}' as it does not have prefix '${this.cachePrefix}'`); 132 | return; 133 | } 134 | 135 | /* istanbul ignore if -- this is a safety check, but should never happen in normal usage */ 136 | if (!fs.statSync(dir).isDirectory) 137 | { 138 | this.context.debug(`skipping cleaning '${dir}' as it is not a directory`); 139 | return; 140 | } 141 | 142 | this.context.info(blue(`cleaning cache: ${dir}`)); 143 | fs.removeSync(`${dir}`); 144 | }); 145 | } 146 | 147 | public setDependency(importee: string, importer: string): void 148 | { 149 | // importee -> importer 150 | this.context.debug(`${blue("dependency")} '${importee}'`); 151 | this.context.debug(` imported by '${importer}'`); 152 | this.dependencyTree.setEdge(importer, importee); 153 | } 154 | 155 | public walkTree(cb: (id: string) => void | false): void 156 | { 157 | if (alg.isAcyclic(this.dependencyTree)) 158 | return alg.topsort(this.dependencyTree).forEach(id => cb(id)); 159 | 160 | this.context.info(yellow("import tree has cycles")); 161 | this.dependencyTree.nodes().forEach(id => cb(id)); 162 | } 163 | 164 | public done() 165 | { 166 | if (this.noCache) 167 | return; 168 | 169 | this.context.info(blue("rolling caches")); 170 | this.codeCache.roll(); 171 | this.semanticDiagnosticsCache.roll(); 172 | this.syntacticDiagnosticsCache.roll(); 173 | this.typesCache.roll(); 174 | } 175 | 176 | public getCompiled(id: string, snapshot: tsTypes.IScriptSnapshot, transform: () => ICode | undefined): ICode | undefined 177 | { 178 | this.context.info(`${blue("transpiling")} '${id}'`); 179 | // if !isolatedModules, compiled JS code can change if its imports do (e.g. enums). also, declarations can change based on imports as well 180 | return this.getCached(this.codeCache, id, snapshot, Boolean(!this.options.isolatedModules || this.options.declaration), transform); 181 | } 182 | 183 | public getSyntacticDiagnostics(id: string, snapshot: tsTypes.IScriptSnapshot, check: () => tsTypes.Diagnostic[]): IDiagnostics[] 184 | { 185 | return this.getDiagnostics("syntax", this.syntacticDiagnosticsCache, id, snapshot, check); 186 | } 187 | 188 | public getSemanticDiagnostics(id: string, snapshot: tsTypes.IScriptSnapshot, check: () => tsTypes.Diagnostic[]): IDiagnostics[] 189 | { 190 | return this.getDiagnostics("semantic", this.semanticDiagnosticsCache, id, snapshot, check); 191 | } 192 | 193 | private checkAmbientTypes(): void 194 | { 195 | this.context.debug(blue("Ambient types:")); 196 | const typeHashes = this.ambientTypes.filter((snapshot) => snapshot.snapshot !== undefined) 197 | .map((snapshot) => 198 | { 199 | this.context.debug(` ${snapshot.id}`); 200 | return this.createHash(snapshot.id, snapshot.snapshot!); 201 | }); 202 | // types dirty if any d.ts changed, added or removed 203 | this.ambientTypesDirty = !this.typesCache.match(typeHashes); 204 | 205 | if (this.ambientTypesDirty) 206 | this.context.info(yellow("ambient types changed, redoing all semantic diagnostics")); 207 | 208 | typeHashes.forEach(this.typesCache.touch, this.typesCache); 209 | } 210 | 211 | private getDiagnostics(type: string, cache: ICache, id: string, snapshot: tsTypes.IScriptSnapshot, check: () => tsTypes.Diagnostic[]): IDiagnostics[] 212 | { 213 | // don't need to check imports for syntactic diagnostics (per https://github.com/microsoft/TypeScript/wiki/Using-the-Language-Service-API#design-goals) 214 | return this.getCached(cache, id, snapshot, type === "semantic", () => convertDiagnostic(type, check())); 215 | } 216 | 217 | private getCached(cache: ICache, id: string, snapshot: tsTypes.IScriptSnapshot, checkImports: boolean, convert: () => CacheType): CacheType 218 | { 219 | if (this.noCache) 220 | return convert(); 221 | 222 | const hash = this.createHash(id, snapshot); 223 | this.context.debug(` cache: '${cache.path(hash)}'`); 224 | 225 | if (cache.exists(hash) && !this.isDirty(id, checkImports)) 226 | { 227 | this.context.debug(green(" cache hit")); 228 | 229 | const data = cache.read(hash); 230 | if (data) 231 | { 232 | cache.write(hash, data); 233 | return data; 234 | } 235 | else /* istanbul ignore next -- should only happen when corrupted cache */ 236 | this.context.warn(yellow(" cache broken, discarding")); 237 | } 238 | 239 | this.context.debug(yellow(" cache miss")); 240 | 241 | const convertedData = convert(); 242 | cache.write(hash, convertedData); 243 | this.markAsDirty(id); 244 | return convertedData; 245 | } 246 | 247 | private init() 248 | { 249 | this.codeCache = new RollingCache(`${this.cacheDir}/code`); 250 | this.typesCache = new RollingCache(`${this.cacheDir}/types`); 251 | this.syntacticDiagnosticsCache = new RollingCache(`${this.cacheDir}/syntacticDiagnostics`); 252 | this.semanticDiagnosticsCache = new RollingCache(`${this.cacheDir}/semanticDiagnostics`); 253 | } 254 | 255 | private markAsDirty(id: string): void 256 | { 257 | this.dependencyTree.setNode(id, { dirty: true }); 258 | } 259 | 260 | /** @returns true if node, any of its imports, or any ambient types changed */ 261 | private isDirty(id: string, checkImports: boolean): boolean 262 | { 263 | const label = this.dependencyTree.node(id) as INodeLabel; 264 | 265 | if (!label) 266 | return false; 267 | 268 | if (!checkImports || label.dirty) 269 | return label.dirty; 270 | 271 | if (this.ambientTypesDirty) 272 | return true; 273 | 274 | const dependencies = alg.dijkstra(this.dependencyTree, id); 275 | 276 | return Object.keys(dependencies).some(node => 277 | { 278 | const dependency = dependencies[node]; 279 | if (!node || dependency.distance === Infinity) 280 | return false; 281 | 282 | const l = this.dependencyTree.node(node) as INodeLabel | undefined; 283 | const dirty = l === undefined ? true : l.dirty; 284 | 285 | if (dirty) 286 | this.context.debug(` import changed: ${node}`); 287 | 288 | return dirty; 289 | }); 290 | } 291 | 292 | /** @returns an FS-safe hash string for use as a path to the cached content */ 293 | private createHash(id: string, snapshot: tsTypes.IScriptSnapshot) 294 | { 295 | const data = snapshot.getText(0, snapshot.getLength()); 296 | return objHash({ data, id }, this.hashOptions); 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /src/tslib.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from "fs"; 2 | 3 | // The injected id for helpers. 4 | export const TSLIB = "tslib"; 5 | export const TSLIB_VIRTUAL = "\0tslib.js"; 6 | export let tslibSource: string; 7 | export let tslibVersion: string; 8 | 9 | try 10 | { 11 | // eslint-disable-next-line @typescript-eslint/no-var-requires 12 | const tslibPackage = require("tslib/package.json"); 13 | const tslibPath = require.resolve("tslib/" + tslibPackage.module); 14 | tslibSource = readFileSync(tslibPath, "utf8"); 15 | tslibVersion = tslibPackage.version; 16 | } catch (e) 17 | { 18 | console.warn("rpt2: Error loading `tslib` helper library."); 19 | throw e; 20 | } 21 | -------------------------------------------------------------------------------- /src/tsproxy.ts: -------------------------------------------------------------------------------- 1 | import * as tsTypes from "typescript"; 2 | 3 | export let tsModule: typeof tsTypes; 4 | 5 | export function setTypescriptModule(override: typeof tsTypes) 6 | { 7 | tsModule = override; 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "declarationMap": true, 5 | }, 6 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base", 3 | "compilerOptions": { 4 | "target": "es6", 5 | "module": "ES2020", 6 | "sourceMap": true, 7 | "noUnusedParameters": true, 8 | "noUnusedLocals": true, 9 | "diagnostics": true, 10 | "listFiles": true, 11 | "moduleResolution": "node", 12 | "forceConsistentCasingInFileNames": true, 13 | "noImplicitReturns": true, 14 | "strict": true, 15 | "outDir": "./build", 16 | "allowSyntheticDefaultImports": true 17 | }, 18 | "include": [ 19 | "src/**/*.ts" 20 | ], 21 | "exclude": [] 22 | } 23 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "compilerOptions": { 4 | "esModuleInterop": true, // needed to parse some imports with ts-jest (since Rollup isn't used when importing during testing) 5 | }, 6 | } 7 | --------------------------------------------------------------------------------