Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(typescript): Move to BuilderProgram API #217

Merged
merged 23 commits into from
Feb 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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