diff --git a/packages/typescript/src/index.ts b/packages/typescript/src/index.ts index 6b0eb2652..0477fbdbe 100644 --- a/packages/typescript/src/index.ts +++ b/packages/typescript/src/index.ts @@ -2,7 +2,7 @@ import * as path from 'path'; import { createFilter } from '@rollup/pluginutils'; -import type { Plugin, SourceDescription } from 'rollup'; +import type { Plugin, PluginContext, SourceDescription } from 'rollup'; import type { Watch } from 'typescript'; import type { RollupTypescriptOptions } from '../types'; @@ -37,6 +37,31 @@ export default function typescript(options: RollupTypescriptOptions = {}): Plugi tslib, typescript: ts } = getPluginOptions(options); + const createProgram = (context: PluginContext) => + createWatchProgram(ts, context, { + formatHost, + resolveModule, + parsedOptions, + writeFile(fileName, data, _writeByteOrderMark, _onError, sourceFiles) { + if (sourceFiles) { + for (const sourceFile of sourceFiles) { + if (!parsedOptions.fileNames.includes(sourceFile.fileName)) { + parsedOptions.fileNames.push(sourceFile.fileName); + } + } + } + + if (parsedOptions.options.composite || parsedOptions.options.incremental) { + tsCache.cacheCode(fileName, data); + } + emittedFiles.set(fileName, data); + }, + status(diagnostic) { + watchProgramHelper.handleStatus(diagnostic); + }, + transformers + }); + const tsCache = new TSCache(cacheDir); const emittedFiles = new Map(); const watchProgramHelper = new WatchProgramHelper(); @@ -56,6 +81,14 @@ export default function typescript(options: RollupTypescriptOptions = {}): Plugi name: 'typescript', buildStart(rollupOptions) { + if (typeof rollupOptions.input === 'string') { + rollupOptions.input = [rollupOptions.input]; + } + + if (Array.isArray(rollupOptions.input)) { + parsedOptions.fileNames = rollupOptions.input.map((fileName) => path.resolve(fileName)); + } + emitParsedOptionsErrors(ts, this, parsedOptions); preflight({ @@ -74,21 +107,7 @@ export default function typescript(options: RollupTypescriptOptions = {}): Plugi program = null; } if (!program) { - program = createWatchProgram(ts, this, { - formatHost, - resolveModule, - parsedOptions, - writeFile(fileName, data) { - if (parsedOptions.options.composite || parsedOptions.options.incremental) { - tsCache.cacheCode(fileName, data); - } - emittedFiles.set(fileName, data); - }, - status(diagnostic) { - watchProgramHelper.handleStatus(diagnostic); - }, - transformers - }); + program = createProgram(this); } }, @@ -139,7 +158,6 @@ export default function typescript(options: RollupTypescriptOptions = {}): Plugi if (resolved) { if (/\.d\.[cm]?ts/.test(resolved.extension)) return null; - if (!filter(resolved.resolvedFileName)) return null; return path.normalize(resolved.resolvedFileName); } @@ -149,16 +167,20 @@ export default function typescript(options: RollupTypescriptOptions = {}): Plugi async load(id) { if (!filter(id)) return null; - this.addWatchFile(id); + const resolvedId = path.resolve(id); + + this.addWatchFile(resolvedId); await watchProgramHelper.wait(); - const fileName = normalizePath(id); + const fileName = normalizePath(resolvedId); if (!parsedOptions.fileNames.includes(fileName)) { // Discovered new file that was not known when originally parsing the TypeScript config - parsedOptions.fileNames.push(fileName); + parsedOptions.fileNames.push(path.resolve(fileName)); + + createProgram(this).close(); } - const output = findTypescriptOutput(ts, parsedOptions, id, emittedFiles, tsCache); + const output = findTypescriptOutput(ts, parsedOptions, resolvedId, emittedFiles, tsCache); return output.code != null ? (output as SourceDescription) : null; }, diff --git a/packages/typescript/test/fixtures/with-invalid-sources-inside-cwd/invalid.ts b/packages/typescript/test/fixtures/with-invalid-sources-inside-cwd/invalid.ts new file mode 100644 index 000000000..efa311cae --- /dev/null +++ b/packages/typescript/test/fixtures/with-invalid-sources-inside-cwd/invalid.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line +foo diff --git a/packages/typescript/test/fixtures/with-invalid-sources-inside-cwd/main.ts b/packages/typescript/test/fixtures/with-invalid-sources-inside-cwd/main.ts new file mode 100644 index 000000000..949e87258 --- /dev/null +++ b/packages/typescript/test/fixtures/with-invalid-sources-inside-cwd/main.ts @@ -0,0 +1,3 @@ +import {foo} from "./valid"; + +console.log(foo); diff --git a/packages/typescript/test/fixtures/with-invalid-sources-inside-cwd/valid.ts b/packages/typescript/test/fixtures/with-invalid-sources-inside-cwd/valid.ts new file mode 100644 index 000000000..ac7c27324 --- /dev/null +++ b/packages/typescript/test/fixtures/with-invalid-sources-inside-cwd/valid.ts @@ -0,0 +1 @@ +export const foo = 5; diff --git a/packages/typescript/test/test.js b/packages/typescript/test/test.js index 127f98e19..9edd0283c 100644 --- a/packages/typescript/test/test.js +++ b/packages/typescript/test/test.js @@ -1074,7 +1074,8 @@ test.serial('does it support tsconfig.rootDir for filtering', async (t) => { t.is(files.length, 1); }); -test.serial('does it fail for filtering with incorrect rootDir in nested projects', async (t) => { +// todo: why would want to deliberately forbid resolution from outside of CWD? What problem does it solve to add such a constraint? +test.skip('does it fail for filtering with incorrect rootDir in nested projects', async (t) => { process.chdir('fixtures/root-dir/packages/test-2'); const error = await t.throwsAsync( rollup({ @@ -1420,3 +1421,18 @@ test.serial('compiled external library', async (t) => { }); t.pass(); }); + +test.serial( + 'do not consider files that are not part of the entry point dependency graph', + async (t) => { + process.chdir('fixtures/with-invalid-sources-inside-cwd'); + const input = 'main.ts'; + + const build = await rollup({ + input, + plugins: [typescript()] + }); + + t.deepEqual(build.watchFiles, [path.resolve('main.ts'), path.resolve('valid.ts')]); + } +); diff --git a/packages/typescript/test/tslib.ts b/packages/typescript/test/tslib.ts index cbb95820a..530534046 100644 --- a/packages/typescript/test/tslib.ts +++ b/packages/typescript/test/tslib.ts @@ -1,4 +1,5 @@ import { platform } from 'os'; +import { resolve } from 'path'; import test from 'ava'; import type { RollupError } from 'rollup'; @@ -62,13 +63,15 @@ test.serial('fails on bad tslib path', async (t) => { return; } - if (error.watchFiles) { - let [filePath] = error.watchFiles; - filePath = filePath.substring(filePath.indexOf('packages')); - error.watchFiles[0] = filePath; - } - - t.snapshot(error); + t.deepEqual( + error.message, + `Could not load fixtures/joker/tslib.js (imported by fixtures/overriding-tslib/main.ts): ENOENT: no such file or directory, open 'fixtures/joker/tslib.js'` + ); + t.deepEqual(error.watchFiles, [ + resolve('fixtures/overriding-tslib/main.ts'), + 'fixtures/joker/tslib.js' + ]); + t.deepEqual(error.code, 'ENOENT'); }); test.serial('fails without tslib installed', async (t) => {