UNPKG

32.4 kBJavaScriptView Raw
1import path, { resolve, dirname, relative, win32, posix, normalize } from 'path';
2import { createFilter } from '@rollup/pluginutils';
3import * as defaultTs from 'typescript';
4import { DiagnosticCategory } from 'typescript';
5import resolveId from 'resolve';
6import fs, { readFileSync } from 'fs';
7
8/**
9 * Create a format diagnostics host to use with the Typescript type checking APIs.
10 * Typescript hosts are used to represent the user's system,
11 * with an API for checking case sensitivity etc.
12 * @param compilerOptions Typescript compiler options. Affects functions such as `getNewLine`.
13 * @see https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API
14 */
15function createFormattingHost(ts, compilerOptions) {
16 return {
17 /** Returns the compiler options for the project. */
18 getCompilationSettings: () => compilerOptions,
19 /** Returns the current working directory. */
20 getCurrentDirectory: () => process.cwd(),
21 /** Returns the string that corresponds with the selected `NewLineKind`. */
22 getNewLine() {
23 switch (compilerOptions.newLine) {
24 case ts.NewLineKind.CarriageReturnLineFeed:
25 return '\r\n';
26 case ts.NewLineKind.LineFeed:
27 return '\n';
28 default:
29 return ts.sys.newLine;
30 }
31 },
32 /** Returns a lower case name on case insensitive systems, otherwise the original name. */
33 getCanonicalFileName: (fileName) => ts.sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase()
34 };
35}
36
37/**
38 * Create a helper for resolving modules using Typescript.
39 * @param host Typescript host that extends `ModuleResolutionHost`
40 * with methods for sanitizing filenames and getting compiler options.
41 */
42function createModuleResolver(ts, host) {
43 const compilerOptions = host.getCompilationSettings();
44 const cache = ts.createModuleResolutionCache(process.cwd(), host.getCanonicalFileName, compilerOptions);
45 const moduleHost = Object.assign(Object.assign({}, ts.sys), host);
46 return (moduleName, containingFile) => {
47 const resolved = ts.nodeModuleNameResolver(moduleName, containingFile, compilerOptions, moduleHost, cache);
48 return resolved.resolvedModule;
49 };
50}
51
52/*! *****************************************************************************
53Copyright (c) Microsoft Corporation.
54
55Permission to use, copy, modify, and/or distribute this software for any
56purpose with or without fee is hereby granted.
57
58THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
59REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
60AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
61INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
62LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
63OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
64PERFORMANCE OF THIS SOFTWARE.
65***************************************************************************** */
66
67function __rest(s, e) {
68 var t = {};
69 for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
70 t[p] = s[p];
71 if (s != null && typeof Object.getOwnPropertySymbols === "function")
72 for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
73 if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
74 t[p[i]] = s[p[i]];
75 }
76 return t;
77}
78
79const resolveIdAsync = (file, opts) => new Promise((fulfil, reject) => resolveId(file, opts, (err, contents) => (err || typeof contents === 'undefined' ? reject(err) : fulfil(contents))));
80/**
81 * Returns code asynchronously for the tslib helper library.
82 */
83function getTsLibPath() {
84 return resolveIdAsync('tslib/tslib.es6.js', { basedir: __dirname });
85}
86
87/**
88 * Separate the Rollup plugin options from the Typescript compiler options,
89 * and normalize the Rollup options.
90 * @returns Object with normalized options:
91 * - `filter`: Checks if a file should be included.
92 * - `tsconfig`: Path to a tsconfig, or directive to ignore tsconfig.
93 * - `compilerOptions`: Custom Typescript compiler options that override tsconfig.
94 * - `typescript`: Instance of Typescript library (possibly custom).
95 * - `tslib`: ESM code from the tslib helper library (possibly custom).
96 */
97function getPluginOptions(options) {
98 const { cacheDir, exclude, include, transformers, tsconfig, tslib, typescript } = options, compilerOptions = __rest(options, ["cacheDir", "exclude", "include", "transformers", "tsconfig", "tslib", "typescript"]);
99 const filter = createFilter(include || ['*.ts+(|x)', '**/*.ts+(|x)'], exclude);
100 return {
101 cacheDir,
102 filter,
103 tsconfig,
104 compilerOptions: compilerOptions,
105 typescript: typescript || defaultTs,
106 tslib: tslib || getTsLibPath(),
107 transformers
108 };
109}
110
111/**
112 * Converts a Typescript type error into an equivalent Rollup warning object.
113 */
114function diagnosticToWarning(ts, host, diagnostic) {
115 const pluginCode = `TS${diagnostic.code}`;
116 const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
117 // Build a Rollup warning object from the diagnostics object.
118 const warning = {
119 pluginCode,
120 message: `@rollup/plugin-typescript ${pluginCode}: ${message}`
121 };
122 if (diagnostic.file) {
123 // Add information about the file location
124 const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
125 warning.loc = {
126 column: character + 1,
127 line: line + 1,
128 file: diagnostic.file.fileName
129 };
130 if (host) {
131 // Extract a code frame from Typescript
132 const formatted = ts.formatDiagnosticsWithColorAndContext([diagnostic], host);
133 // Typescript only exposes this formatter as a string prefixed with the flattened message.
134 // We need to remove it here since Rollup treats the properties as separate parts.
135 let frame = formatted.slice(formatted.indexOf(message) + message.length);
136 const newLine = host.getNewLine();
137 if (frame.startsWith(newLine)) {
138 frame = frame.slice(frame.indexOf(newLine) + newLine.length);
139 }
140 warning.frame = frame;
141 }
142 }
143 return warning;
144}
145
146const DEFAULT_COMPILER_OPTIONS = {
147 module: 'esnext',
148 skipLibCheck: true
149};
150const FORCED_COMPILER_OPTIONS = {
151 // Always use tslib
152 noEmitHelpers: true,
153 importHelpers: true,
154 // Typescript needs to emit the code for us to work with
155 noEmit: false,
156 emitDeclarationOnly: false,
157 // Preventing Typescript from resolving code may break compilation
158 noResolve: false
159};
160
161/* eslint-disable no-param-reassign */
162const DIRECTORY_PROPS = ['outDir', 'declarationDir'];
163/**
164 * Mutates the compiler options to convert paths from relative to absolute.
165 * This should be used with compiler options passed through the Rollup plugin options,
166 * not those found from loading a tsconfig.json file.
167 * @param compilerOptions Compiler options to _mutate_.
168 * @param relativeTo Paths are resolved relative to this path.
169 */
170function makePathsAbsolute(compilerOptions, relativeTo) {
171 for (const pathProp of DIRECTORY_PROPS) {
172 if (compilerOptions[pathProp]) {
173 compilerOptions[pathProp] = resolve(relativeTo, compilerOptions[pathProp]);
174 }
175 }
176}
177/**
178 * Mutates the compiler options to normalize some values for Rollup.
179 * @param compilerOptions Compiler options to _mutate_.
180 * @returns True if the source map compiler option was not initially set.
181 */
182function normalizeCompilerOptions(ts, compilerOptions) {
183 let autoSetSourceMap = false;
184 if (compilerOptions.inlineSourceMap) {
185 // Force separate source map files for Rollup to work with.
186 compilerOptions.sourceMap = true;
187 compilerOptions.inlineSourceMap = false;
188 }
189 else if (typeof compilerOptions.sourceMap !== 'boolean') {
190 // Default to using source maps.
191 // If the plugin user sets sourceMap to false we keep that option.
192 compilerOptions.sourceMap = true;
193 // Using inlineSources to make sure typescript generate source content
194 // instead of source path.
195 compilerOptions.inlineSources = true;
196 autoSetSourceMap = true;
197 }
198 switch (compilerOptions.module) {
199 case ts.ModuleKind.ES2015:
200 case ts.ModuleKind.ESNext:
201 case ts.ModuleKind.CommonJS:
202 // OK module type
203 return autoSetSourceMap;
204 case ts.ModuleKind.None:
205 case ts.ModuleKind.AMD:
206 case ts.ModuleKind.UMD:
207 case ts.ModuleKind.System: {
208 // Invalid module type
209 const moduleType = ts.ModuleKind[compilerOptions.module];
210 throw new Error(`@rollup/plugin-typescript: The module kind should be 'ES2015' or 'ESNext, found: '${moduleType}'`);
211 }
212 default:
213 // Unknown or unspecified module type, force ESNext
214 compilerOptions.module = ts.ModuleKind.ESNext;
215 }
216 return autoSetSourceMap;
217}
218
219/**
220 * Finds the path to the tsconfig file relative to the current working directory.
221 * @param relativePath Relative tsconfig path given by the user.
222 * If `false` is passed, then a null path is returned.
223 * @returns The absolute path, or null if the file does not exist.
224 */
225function getTsConfigPath(ts, relativePath) {
226 if (relativePath === false)
227 return null;
228 // Resolve path to file. `tsConfigOption` defaults to 'tsconfig.json'.
229 const tsConfigPath = resolve(process.cwd(), relativePath || 'tsconfig.json');
230 if (!ts.sys.fileExists(tsConfigPath)) {
231 if (relativePath) {
232 // If an explicit path was provided but no file was found, throw
233 throw new Error(`Could not find specified tsconfig.json at ${tsConfigPath}`);
234 }
235 else {
236 return null;
237 }
238 }
239 return tsConfigPath;
240}
241/**
242 * Tries to read the tsconfig file at `tsConfigPath`.
243 * @param tsConfigPath Absolute path to tsconfig JSON file.
244 * @param explicitPath If true, the path was set by the plugin user.
245 * If false, the path was computed automatically.
246 */
247function readTsConfigFile(ts, tsConfigPath) {
248 const { config, error } = ts.readConfigFile(tsConfigPath, (path) => readFileSync(path, 'utf8'));
249 if (error) {
250 throw Object.assign(Error(), diagnosticToWarning(ts, null, error));
251 }
252 return config || {};
253}
254/**
255 * Returns true if any of the `compilerOptions` contain an enum value (i.e.: ts.ScriptKind) rather than a string.
256 * This indicates that the internal CompilerOptions type is used rather than the JsonCompilerOptions.
257 */
258function containsEnumOptions(compilerOptions) {
259 const enums = [
260 'module',
261 'target',
262 'jsx',
263 'moduleResolution',
264 'newLine'
265 ];
266 return enums.some((prop) => prop in compilerOptions && typeof compilerOptions[prop] === 'number');
267}
268const configCache = new Map();
269/**
270 * Parse the Typescript config to use with the plugin.
271 * @param ts Typescript library instance.
272 * @param tsconfig Path to the tsconfig file, or `false` to ignore the file.
273 * @param compilerOptions Options passed to the plugin directly for Typescript.
274 *
275 * @returns Parsed tsconfig.json file with some important properties:
276 * - `options`: Parsed compiler options.
277 * - `fileNames` Type definition files that should be included in the build.
278 * - `errors`: Any errors from parsing the config file.
279 */
280function parseTypescriptConfig(ts, tsconfig, compilerOptions) {
281 /* eslint-disable no-undefined */
282 const cwd = process.cwd();
283 makePathsAbsolute(compilerOptions, cwd);
284 let parsedConfig;
285 // Resolve path to file. If file is not found, pass undefined path to `parseJsonConfigFileContent`.
286 // eslint-disable-next-line no-undefined
287 const tsConfigPath = getTsConfigPath(ts, tsconfig) || undefined;
288 const tsConfigFile = tsConfigPath ? readTsConfigFile(ts, tsConfigPath) : {};
289 const basePath = tsConfigPath ? dirname(tsConfigPath) : cwd;
290 // If compilerOptions has enums, it represents an CompilerOptions object instead of parsed JSON.
291 // This determines where the data is passed to the parser.
292 if (containsEnumOptions(compilerOptions)) {
293 parsedConfig = ts.parseJsonConfigFileContent(Object.assign(Object.assign({}, tsConfigFile), { compilerOptions: Object.assign(Object.assign({}, DEFAULT_COMPILER_OPTIONS), tsConfigFile.compilerOptions) }), ts.sys, basePath, Object.assign(Object.assign({}, compilerOptions), FORCED_COMPILER_OPTIONS), tsConfigPath, undefined, undefined, configCache);
294 }
295 else {
296 parsedConfig = ts.parseJsonConfigFileContent(Object.assign(Object.assign({}, tsConfigFile), { compilerOptions: Object.assign(Object.assign(Object.assign({}, DEFAULT_COMPILER_OPTIONS), tsConfigFile.compilerOptions), compilerOptions) }), ts.sys, basePath, FORCED_COMPILER_OPTIONS, tsConfigPath, undefined, undefined, configCache);
297 }
298 const autoSetSourceMap = normalizeCompilerOptions(ts, parsedConfig.options);
299 return Object.assign(Object.assign({}, parsedConfig), { autoSetSourceMap });
300}
301/**
302 * If errors are detected in the parsed options,
303 * display all of them as warnings then emit an error.
304 */
305function emitParsedOptionsErrors(ts, context, parsedOptions) {
306 if (parsedOptions.errors.length > 0) {
307 parsedOptions.errors.forEach((error) => context.warn(diagnosticToWarning(ts, null, error)));
308 context.error(`@rollup/plugin-typescript: Couldn't process compiler options`);
309 }
310}
311
312/**
313 * Validate that the `compilerOptions.sourceMap` option matches `outputOptions.sourcemap`.
314 * @param context Rollup plugin context used to emit warnings.
315 * @param compilerOptions Typescript compiler options.
316 * @param outputOptions Rollup output options.
317 * @param autoSetSourceMap True if the `compilerOptions.sourceMap` property was set to `true`
318 * by the plugin, not the user.
319 */
320function validateSourceMap(context, compilerOptions, outputOptions, autoSetSourceMap) {
321 if (compilerOptions.sourceMap && !outputOptions.sourcemap && !autoSetSourceMap) {
322 context.warn(`@rollup/plugin-typescript: Rollup 'sourcemap' option must be set to generate source maps.`);
323 }
324 else if (!compilerOptions.sourceMap && outputOptions.sourcemap) {
325 context.warn(`@rollup/plugin-typescript: Typescript 'sourceMap' compiler option must be set to generate source maps.`);
326 }
327}
328/**
329 * Validate that the out directory used by Typescript can be controlled by Rollup.
330 * @param context Rollup plugin context used to emit errors.
331 * @param compilerOptions Typescript compiler options.
332 * @param outputOptions Rollup output options.
333 */
334function validatePaths(ts, context, compilerOptions, outputOptions) {
335 if (compilerOptions.out) {
336 context.error(`@rollup/plugin-typescript: Deprecated Typescript compiler option 'out' is not supported. Use 'outDir' instead.`);
337 }
338 else if (compilerOptions.outFile) {
339 context.error(`@rollup/plugin-typescript: Typescript compiler option 'outFile' is not supported. Use 'outDir' instead.`);
340 }
341 for (const dirProperty of DIRECTORY_PROPS) {
342 if (compilerOptions[dirProperty] && outputOptions.dir) {
343 // Checks if the given path lies within Rollup output dir
344 const fromRollupDirToTs = relative(outputOptions.dir, compilerOptions[dirProperty]);
345 if (fromRollupDirToTs.startsWith('..')) {
346 context.error(`@rollup/plugin-typescript: Path of Typescript compiler option '${dirProperty}' must be located inside Rollup 'dir' option.`);
347 }
348 }
349 }
350 const tsBuildInfoPath = ts.getTsBuildInfoEmitOutputFilePath(compilerOptions);
351 if (tsBuildInfoPath && compilerOptions.incremental) {
352 if (!outputOptions.dir) {
353 context.error(`@rollup/plugin-typescript: Rollup 'dir' option must be used when Typescript compiler options 'tsBuildInfoFile' or 'incremental' are specified.`);
354 }
355 // Checks if the given path lies within Rollup output dir
356 const fromRollupDirToTs = relative(outputOptions.dir, tsBuildInfoPath);
357 if (fromRollupDirToTs.startsWith('..')) {
358 context.error(`@rollup/plugin-typescript: Path of Typescript compiler option 'tsBuildInfoFile' must be located inside Rollup 'dir' option.`);
359 }
360 }
361 if (compilerOptions.declaration || compilerOptions.declarationMap || compilerOptions.composite) {
362 if (DIRECTORY_PROPS.every((dirProperty) => !compilerOptions[dirProperty])) {
363 context.error(`@rollup/plugin-typescript: You are using one of Typescript's compiler options 'declaration', 'declarationMap' or 'composite'. ` +
364 `In this case 'outDir' or 'declarationDir' must be specified to generate declaration files.`);
365 }
366 }
367}
368
369/**
370 * Checks if the given OutputFile represents some code
371 */
372function isCodeOutputFile(name) {
373 return !isMapOutputFile(name) && !name.endsWith('.d.ts');
374}
375/**
376 * Checks if the given OutputFile represents some source map
377 */
378function isMapOutputFile(name) {
379 return name.endsWith('.map');
380}
381/**
382 * Returns the content of a filename either from the current
383 * typescript compiler instance or from the cached content.
384 * @param fileName The filename for the contents to retrieve
385 * @param emittedFiles The files emitted in the current typescript instance
386 * @param tsCache A cache to files cached by Typescript
387 */
388function getEmittedFile(fileName, emittedFiles, tsCache) {
389 let code;
390 if (fileName) {
391 if (emittedFiles.has(fileName)) {
392 code = emittedFiles.get(fileName);
393 }
394 else {
395 code = tsCache.getCached(fileName);
396 }
397 }
398 return code;
399}
400/**
401 * Finds the corresponding emitted Javascript files for a given Typescript file.
402 * @param id Path to the Typescript file.
403 * @param emittedFiles Map of file names to source code,
404 * containing files emitted by the Typescript compiler.
405 */
406function findTypescriptOutput(ts, parsedOptions, id, emittedFiles, tsCache) {
407 const emittedFileNames = ts.getOutputFileNames(parsedOptions, id, !ts.sys.useCaseSensitiveFileNames);
408 const codeFile = emittedFileNames.find(isCodeOutputFile);
409 const mapFile = emittedFileNames.find(isMapOutputFile);
410 return {
411 code: getEmittedFile(codeFile, emittedFiles, tsCache),
412 map: getEmittedFile(mapFile, emittedFiles, tsCache),
413 declarations: emittedFileNames.filter((name) => name !== codeFile && name !== mapFile)
414 };
415}
416
417// `Cannot compile modules into 'es6' when targeting 'ES5' or lower.`
418const CANNOT_COMPILE_ESM = 1204;
419/**
420 * Emit a Rollup warning or error for a Typescript type error.
421 */
422function emitDiagnostic(ts, context, host, diagnostic) {
423 if (diagnostic.code === CANNOT_COMPILE_ESM)
424 return;
425 const { noEmitOnError } = host.getCompilationSettings();
426 // Build a Rollup warning object from the diagnostics object.
427 const warning = diagnosticToWarning(ts, host, diagnostic);
428 // Errors are fatal. Otherwise emit warnings.
429 if (noEmitOnError && diagnostic.category === ts.DiagnosticCategory.Error) {
430 context.error(warning);
431 }
432 else {
433 context.warn(warning);
434 }
435}
436function buildDiagnosticReporter(ts, context, host) {
437 return function reportDiagnostics(diagnostic) {
438 emitDiagnostic(ts, context, host, diagnostic);
439 };
440}
441
442/**
443 * Merges all received custom transformer definitions into a single CustomTransformers object
444 */
445function mergeTransformers(builder, ...input) {
446 // List of all transformer stages
447 const transformerTypes = ['after', 'afterDeclarations', 'before'];
448 const accumulator = {
449 after: [],
450 afterDeclarations: [],
451 before: []
452 };
453 let program;
454 let typeChecker;
455 input.forEach((transformers) => {
456 if (!transformers) {
457 // Skip empty arguments lists
458 return;
459 }
460 transformerTypes.forEach((stage) => {
461 getTransformers(transformers[stage]).forEach((transformer) => {
462 if (!transformer) {
463 // Skip empty
464 return;
465 }
466 if ('type' in transformer) {
467 if (typeof transformer.factory === 'function') {
468 // Allow custom factories to grab the extra information required
469 program = program || builder.getProgram();
470 typeChecker = typeChecker || program.getTypeChecker();
471 let factory;
472 if (transformer.type === 'program') {
473 program = program || builder.getProgram();
474 factory = transformer.factory(program);
475 }
476 else {
477 program = program || builder.getProgram();
478 typeChecker = typeChecker || program.getTypeChecker();
479 factory = transformer.factory(typeChecker);
480 }
481 // Forward the requested reference to the custom transformer factory
482 if (factory) {
483 accumulator[stage].push(factory);
484 }
485 }
486 }
487 else {
488 // Add normal transformer factories as is
489 accumulator[stage].push(transformer);
490 }
491 });
492 });
493 });
494 return accumulator;
495}
496function getTransformers(transformers) {
497 return transformers || [];
498}
499
500// @see https://github.com/microsoft/TypeScript/blob/master/src/compiler/diagnosticMessages.json
501var DiagnosticCode;
502(function (DiagnosticCode) {
503 DiagnosticCode[DiagnosticCode["FILE_CHANGE_DETECTED"] = 6032] = "FILE_CHANGE_DETECTED";
504 DiagnosticCode[DiagnosticCode["FOUND_1_ERROR_WATCHING_FOR_FILE_CHANGES"] = 6193] = "FOUND_1_ERROR_WATCHING_FOR_FILE_CHANGES";
505 DiagnosticCode[DiagnosticCode["FOUND_N_ERRORS_WATCHING_FOR_FILE_CHANGES"] = 6194] = "FOUND_N_ERRORS_WATCHING_FOR_FILE_CHANGES";
506})(DiagnosticCode || (DiagnosticCode = {}));
507function createDeferred(timeout) {
508 let promise;
509 let resolve = () => { };
510 if (timeout) {
511 promise = Promise.race([
512 new Promise((r) => setTimeout(r, timeout, true)),
513 new Promise((r) => (resolve = r))
514 ]);
515 }
516 else {
517 promise = new Promise((r) => (resolve = r));
518 }
519 return { promise, resolve };
520}
521/**
522 * Typescript watch program helper to sync Typescript watch status with Rollup hooks.
523 */
524class WatchProgramHelper {
525 constructor() {
526 this._startDeferred = null;
527 this._finishDeferred = null;
528 }
529 watch(timeout = 1000) {
530 // Race watcher start promise against a timeout in case Typescript and Rollup change detection is not in sync.
531 this._startDeferred = createDeferred(timeout);
532 this._finishDeferred = createDeferred();
533 }
534 handleStatus(diagnostic) {
535 // Fullfil deferred promises by Typescript diagnostic message codes.
536 if (diagnostic.category === DiagnosticCategory.Message) {
537 switch (diagnostic.code) {
538 case DiagnosticCode.FILE_CHANGE_DETECTED:
539 this.resolveStart();
540 break;
541 case DiagnosticCode.FOUND_1_ERROR_WATCHING_FOR_FILE_CHANGES:
542 case DiagnosticCode.FOUND_N_ERRORS_WATCHING_FOR_FILE_CHANGES:
543 this.resolveFinish();
544 break;
545 }
546 }
547 }
548 resolveStart() {
549 if (this._startDeferred) {
550 this._startDeferred.resolve(false);
551 this._startDeferred = null;
552 }
553 }
554 resolveFinish() {
555 if (this._finishDeferred) {
556 this._finishDeferred.resolve(false);
557 this._finishDeferred = null;
558 }
559 }
560 async wait() {
561 var _a;
562 if (this._startDeferred) {
563 const timeout = await this._startDeferred.promise;
564 // If there is no file change detected by Typescript skip deferred promises.
565 if (timeout) {
566 this._startDeferred = null;
567 this._finishDeferred = null;
568 }
569 await ((_a = this._finishDeferred) === null || _a === void 0 ? void 0 : _a.promise);
570 }
571 }
572}
573/**
574 * Create a language service host to use with the Typescript compiler & type checking APIs.
575 * Typescript hosts are used to represent the user's system,
576 * with an API for reading files, checking directories and case sensitivity etc.
577 * @see https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API
578 */
579function createWatchHost(ts, context, { formatHost, parsedOptions, writeFile, status, resolveModule, transformers }) {
580 const createProgram = ts.createEmitAndSemanticDiagnosticsBuilderProgram;
581 const baseHost = ts.createWatchCompilerHost(parsedOptions.fileNames, parsedOptions.options, ts.sys, createProgram, buildDiagnosticReporter(ts, context, formatHost), status, parsedOptions.projectReferences);
582 return Object.assign(Object.assign({}, baseHost), {
583 /** Override the created program so an in-memory emit is used */
584 afterProgramCreate(program) {
585 const origEmit = program.emit;
586 // eslint-disable-next-line no-param-reassign
587 program.emit = (targetSourceFile, _, ...args) => origEmit(targetSourceFile, writeFile,
588 // cancellationToken
589 args[0],
590 // emitOnlyDtsFiles
591 args[1], mergeTransformers(program, transformers, args[2]));
592 return baseHost.afterProgramCreate(program);
593 },
594 /** Add helper to deal with module resolution */
595 resolveModuleNames(moduleNames, containingFile) {
596 return moduleNames.map((moduleName) => resolveModule(moduleName, containingFile));
597 } });
598}
599function createWatchProgram(ts, context, options) {
600 return ts.createWatchProgram(createWatchHost(ts, context, options));
601}
602
603/** Creates the folders needed given a path to a file to be saved*/
604const createFileFolder = (filePath) => {
605 const folderPath = path.dirname(filePath);
606 fs.mkdirSync(folderPath, { recursive: true });
607};
608class TSCache {
609 constructor(cacheFolder = '.rollup.cache') {
610 this._cacheFolder = cacheFolder;
611 }
612 /** Returns the path to the cached file */
613 cachedFilename(fileName) {
614 return path.join(this._cacheFolder, fileName.replace(/^([A-Z]+):/, '$1'));
615 }
616 /** Emits a file in the cache folder */
617 cacheCode(fileName, code) {
618 const cachedPath = this.cachedFilename(fileName);
619 createFileFolder(cachedPath);
620 fs.writeFileSync(cachedPath, code);
621 }
622 /** Checks if a file is in the cache */
623 isCached(fileName) {
624 return fs.existsSync(this.cachedFilename(fileName));
625 }
626 /** Read a file from the cache given the output name*/
627 getCached(fileName) {
628 let code;
629 if (this.isCached(fileName)) {
630 code = fs.readFileSync(this.cachedFilename(fileName), { encoding: 'utf-8' });
631 }
632 return code;
633 }
634}
635
636function typescript(options = {}) {
637 const { cacheDir, compilerOptions, filter, transformers, tsconfig, tslib, typescript: ts } = getPluginOptions(options);
638 const tsCache = new TSCache(cacheDir);
639 const emittedFiles = new Map();
640 const watchProgramHelper = new WatchProgramHelper();
641 const parsedOptions = parseTypescriptConfig(ts, tsconfig, compilerOptions);
642 parsedOptions.fileNames = parsedOptions.fileNames.filter(filter);
643 const formatHost = createFormattingHost(ts, parsedOptions.options);
644 const resolveModule = createModuleResolver(ts, formatHost);
645 let program = null;
646 function normalizePath(fileName) {
647 return fileName.split(win32.sep).join(posix.sep);
648 }
649 return {
650 name: 'typescript',
651 buildStart() {
652 emitParsedOptionsErrors(ts, this, parsedOptions);
653 // Fixes a memory leak https://github.com/rollup/plugins/issues/322
654 if (!program) {
655 program = createWatchProgram(ts, this, {
656 formatHost,
657 resolveModule,
658 parsedOptions,
659 writeFile(fileName, data) {
660 if (parsedOptions.options.composite || parsedOptions.options.incremental) {
661 tsCache.cacheCode(fileName, data);
662 }
663 emittedFiles.set(fileName, data);
664 },
665 status(diagnostic) {
666 watchProgramHelper.handleStatus(diagnostic);
667 },
668 transformers
669 });
670 }
671 },
672 watchChange(id) {
673 if (!filter(id))
674 return;
675 watchProgramHelper.watch();
676 },
677 buildEnd() {
678 if (this.meta.watchMode !== true) {
679 // ESLint doesn't understand optional chaining
680 // eslint-disable-next-line
681 program === null || program === void 0 ? void 0 : program.close();
682 }
683 },
684 renderStart(outputOptions) {
685 validateSourceMap(this, parsedOptions.options, outputOptions, parsedOptions.autoSetSourceMap);
686 validatePaths(ts, this, parsedOptions.options, outputOptions);
687 },
688 resolveId(importee, importer) {
689 if (importee === 'tslib') {
690 return tslib;
691 }
692 if (!importer)
693 return null;
694 // Convert path from windows separators to posix separators
695 const containingFile = normalizePath(importer);
696 const resolved = resolveModule(importee, containingFile);
697 if (resolved) {
698 if (resolved.extension === '.d.ts')
699 return null;
700 return normalize(resolved.resolvedFileName);
701 }
702 return null;
703 },
704 async load(id) {
705 if (!filter(id))
706 return null;
707 await watchProgramHelper.wait();
708 const fileName = normalizePath(id);
709 if (!parsedOptions.fileNames.includes(fileName)) {
710 // Discovered new file that was not known when originally parsing the TypeScript config
711 parsedOptions.fileNames.push(fileName);
712 }
713 const output = findTypescriptOutput(ts, parsedOptions, id, emittedFiles, tsCache);
714 return output.code != null ? output : null;
715 },
716 generateBundle(outputOptions) {
717 parsedOptions.fileNames.forEach((fileName) => {
718 const output = findTypescriptOutput(ts, parsedOptions, fileName, emittedFiles, tsCache);
719 output.declarations.forEach((id) => {
720 const code = getEmittedFile(id, emittedFiles, tsCache);
721 let baseDir = outputOptions.dir;
722 if (!baseDir && tsconfig) {
723 baseDir = tsconfig.substring(0, tsconfig.lastIndexOf('/'));
724 }
725 if (!code || !baseDir)
726 return;
727 this.emitFile({
728 type: 'asset',
729 fileName: normalizePath(relative(baseDir, id)),
730 source: code
731 });
732 });
733 });
734 const tsBuildInfoPath = ts.getTsBuildInfoEmitOutputFilePath(parsedOptions.options);
735 if (tsBuildInfoPath) {
736 const tsBuildInfoSource = emittedFiles.get(tsBuildInfoPath);
737 // https://github.com/rollup/plugins/issues/681
738 if (tsBuildInfoSource) {
739 this.emitFile({
740 type: 'asset',
741 fileName: normalizePath(relative(outputOptions.dir, tsBuildInfoPath)),
742 source: tsBuildInfoSource
743 });
744 }
745 }
746 }
747 };
748}
749
750export default typescript;