Skip to content

Commit

Permalink
feat(typescript): Move to BuilderProgram API (#217)
Browse files Browse the repository at this point in the history
* Move to BuilderProgram API

* Update tests for new API

* Use include/exclude for files

* Clean up refactored code

* Change tslib to be a path instead of source

* Update overriding test

* Work on supporting project references

* Working declaration files

* Stop watching after build is done

* Add warnings for sourceMap flag

* Add incremental build support

* Add rebuild test

* Separate module resolution

* Work on project reference support

* Fix incremental test

* Fix path normalization

* Cleanup

* Update override options

* Skip project reference test

* Try skipping incremental build

* Try no cache

* Try serial

* Posix paths
  • Loading branch information
NotWoods authored Feb 28, 2020
1 parent 5ccd8af commit 262cf3d
Show file tree
Hide file tree
Showing 51 changed files with 2,885 additions and 467 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ pnpm-debug.log
!packages/node-resolve/test/fixtures/**/node_modules
!packages/commonjs/test/**/node_modules
!packages/typescript/test/fixtures/**/node_modules
!packages/typescript/test/fixtures/**/dist
4 changes: 1 addition & 3 deletions packages/typescript/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ Overrides the injected TypeScript helpers with a custom version.

```js
typescript({
tslib: fs.readFileSync(require.resolve('some-fork-of-tslib'))
tslib: require.resolve('some-fork-of-tslib')
});
```

Expand All @@ -134,8 +134,6 @@ Declaration files are automatically included if they are listed in the `files` f

These compiler options are ignored by Rollup:

- `declaration`, `declarationMap`: This plugin currently cannot emit declaration files.
- `incremental`, `tsBuildInfoFile`: This plugin currently does not support incremental compilation using Typescript.
- `noEmitHelpers`, `importHelpers`: The `tslib` helper module always must be used.
- `noEmit`, `emitDeclarationOnly`: Typescript needs to emit code for the plugin to work with.
- `noResolve`: Preventing Typescript from resolving code may break compilation
Expand Down
2 changes: 1 addition & 1 deletion packages/typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"ci:coverage": "nyc pnpm run test && nyc report --reporter=text-lcov > coverage.lcov",
"ci:lint": "pnpm run build && pnpm run lint",
"ci:lint:commits": "commitlint --from=${CIRCLE_BRANCH} --to=${CIRCLE_SHA1}",
"ci:test": "pnpm run test -- --verbose",
"ci:test": "pnpm run test -- --verbose --serial",
"lint": "pnpm run lint:js && pnpm run lint:docs && pnpm run lint:package",
"lint:docs": "prettier --single-quote --write README.md",
"lint:js": "eslint --fix --cache src test --ext .js,.ts",
Expand Down
52 changes: 36 additions & 16 deletions packages/typescript/src/diagnostics/emit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,49 @@ import diagnosticToWarning from './toWarning';
// `Cannot compile modules into 'es6' when targeting 'ES5' or lower.`
const CANNOT_COMPILE_ESM = 1204;

/**
* Emit a Rollup warning or error for a Typescript type error.
*/
export function emitDiagnostic(
ts: typeof import('typescript'),
context: PluginContext,
host: DiagnosticsHost,
diagnostic: import('typescript').Diagnostic
) {
if (diagnostic.code === CANNOT_COMPILE_ESM) return;

const { noEmitOnError } = host.getCompilationSettings();

// Build a Rollup warning object from the diagnostics object.
const warning = diagnosticToWarning(ts, host, diagnostic);

// Errors are fatal. Otherwise emit warnings.
if (noEmitOnError && diagnostic.category === ts.DiagnosticCategory.Error) {
context.error(warning);
} else {
context.warn(warning);
}
}

export function buildDiagnosticReporter(
ts: typeof import('typescript'),
context: PluginContext,
host: DiagnosticsHost
): import('typescript').DiagnosticReporter {
return function reportDiagnostics(diagnostic) {
emitDiagnostic(ts, context, host, diagnostic);
};
}

/**
* For each type error reported by Typescript, emit a Rollup warning or error.
*/
export default function emitDiagnostics(
export function emitDiagnostics(
ts: typeof import('typescript'),
context: PluginContext,
host: DiagnosticsHost,
diagnostics: readonly import('typescript').Diagnostic[] | undefined
) {
if (!diagnostics) return;
const { noEmitOnError } = host.getCompilationSettings();

diagnostics
.filter((diagnostic) => diagnostic.code !== CANNOT_COMPILE_ESM)
.forEach((diagnostic) => {
// Build a Rollup warning object from the diagnostics object.
const warning = diagnosticToWarning(ts, host, diagnostic);

// Errors are fatal. Otherwise emit warnings.
if (noEmitOnError && diagnostic.category === ts.DiagnosticCategory.Error) {
context.error(warning);
} else {
context.warn(warning);
}
});
diagnostics.forEach(buildDiagnosticReporter(ts, context, host));
}
26 changes: 0 additions & 26 deletions packages/typescript/src/documentRegistry.ts

This file was deleted.

109 changes: 0 additions & 109 deletions packages/typescript/src/host.ts

This file was deleted.

107 changes: 68 additions & 39 deletions packages/typescript/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,83 +1,112 @@
import * as path from 'path';

import { Plugin } from 'rollup';
import { Plugin, SourceDescription } from 'rollup';

import { RollupTypescriptOptions } from '../types';

import emitDiagnostics from './diagnostics/emit';
import createFormattingHost from './diagnostics/host';
import getDocumentRegistry from './documentRegistry';
import createHost from './host';
import { emitParsedOptionsErrors, getPluginOptions, parseTypescriptConfig } from './options';
import typescriptOutputToRollupTransformation from './outputToRollupTransformation';
import { TSLIB_ID } from './tslib';
import createModuleResolver from './moduleResolution';
import getPluginOptions from './options/plugin';
import { emitParsedOptionsErrors, parseTypescriptConfig } from './options/tsconfig';
import { validatePaths, validateSourceMap } from './options/validate';
import findTypescriptOutput from './outputFile';
import createWatchProgram from './watchProgram';

export default function typescript(options: RollupTypescriptOptions = {}): Plugin {
const { filter, tsconfig, compilerOptions, tslib, typescript: ts } = getPluginOptions(options);
const emittedFiles = new Map<string, string>();
const declarationFiles = new Set<string>();

const parsedOptions = parseTypescriptConfig(ts, tsconfig, compilerOptions);
parsedOptions.fileNames = parsedOptions.fileNames.filter(filter);

const formatHost = createFormattingHost(ts, parsedOptions.options);
const host = createHost(ts, parsedOptions);
const services = ts.createLanguageService(host, getDocumentRegistry(ts, process.cwd()));
const resolveModule = createModuleResolver(ts, formatHost);

let program: import('typescript').Watch<unknown> | null = null;

function normalizePath(fileName: string) {
return fileName.split(path.win32.sep).join(path.posix.sep);
}

return {
name: 'typescript',

buildStart() {
emitParsedOptionsErrors(ts, this, parsedOptions);

program = createWatchProgram(ts, this, {
formatHost,
resolveModule,
parsedOptions,
writeFile(fileName, data) {
emittedFiles.set(fileName, data);
}
});
},

buildEnd() {
if (process.env.ROLLUP_WATCH !== 'true') {
// ESLint doesn't understand optional chaining
// eslint-disable-next-line
program?.close();
}
},

renderStart(outputOptions) {
validateSourceMap(this, parsedOptions.options, outputOptions, parsedOptions.autoSetSourceMap);
validatePaths(ts, this, parsedOptions.options, outputOptions);
},

resolveId(importee, importer) {
if (importee === 'tslib') {
return TSLIB_ID;
return tslib;
}

if (!importer) return null;

// Convert path from windows separators to posix separators
const containingFile = importer.split(path.win32.sep).join(path.posix.sep);
const containingFile = normalizePath(importer);

const resolved = host.resolveModuleNames([importee], containingFile);
const resolvedFile = resolved[0]?.resolvedFileName;
const resolved = resolveModule(importee, containingFile);

if (resolvedFile) {
if (resolvedFile.endsWith('.d.ts')) return null;
return resolvedFile;
if (resolved) {
if (resolved.extension === '.d.ts') return null;
return resolved.resolvedFileName;
}

return null;
},

load(id) {
if (id === TSLIB_ID) {
return tslib;
}
return null;
},

transform(code, id) {
if (!filter(id)) return null;

host.addFile(id, code);
const output = services.getEmitOutput(id);
const output = findTypescriptOutput(ts, parsedOptions, id, emittedFiles);
output.declarations.forEach((declaration) => declarationFiles.add(declaration));

if (output.emitSkipped) {
// Emit failed, print all diagnostics for this file
const allDiagnostics = ([] as import('typescript').Diagnostic[])
.concat(services.getSyntacticDiagnostics(id))
.concat(services.getSemanticDiagnostics(id));
emitDiagnostics(ts, this, formatHost, allDiagnostics);
return output.code ? (output as SourceDescription) : null;
},

throw new Error(`Couldn't compile ${id}`);
generateBundle(outputOptions) {
for (const id of declarationFiles) {
const code = emittedFiles.get(id);
if (code) {
this.emitFile({
type: 'asset',
fileName: normalizePath(path.relative(outputOptions.dir!, id)),
source: code
});
}
}

return typescriptOutputToRollupTransformation(output.outputFiles);
},

generateBundle() {
const program = services.getProgram();
if (program == null) return;
emitDiagnostics(ts, this, formatHost, ts.getPreEmitDiagnostics(program));
const tsBuildInfoPath = ts.getTsBuildInfoEmitOutputFilePath(parsedOptions.options);
if (tsBuildInfoPath) {
this.emitFile({
type: 'asset',
fileName: normalizePath(path.relative(outputOptions.dir!, tsBuildInfoPath)),
source: emittedFiles.get(tsBuildInfoPath)
});
}
}
};
}
Loading

0 comments on commit 262cf3d

Please sign in to comment.