1 | import { resolve, dirname, relative, win32, posix } from 'path';
|
2 | import { createFilter } from '@rollup/pluginutils';
|
3 | import * as defaultTs from 'typescript';
|
4 | import resolveId from 'resolve';
|
5 | import { readFileSync } from 'fs';
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 | function createFormattingHost(ts, compilerOptions) {
|
15 | return {
|
16 |
|
17 | getCompilationSettings: () => compilerOptions,
|
18 |
|
19 | getCurrentDirectory: () => process.cwd(),
|
20 |
|
21 | getNewLine() {
|
22 | switch (compilerOptions.newLine) {
|
23 | case ts.NewLineKind.CarriageReturnLineFeed:
|
24 | return '\r\n';
|
25 | case ts.NewLineKind.LineFeed:
|
26 | return '\n';
|
27 | default:
|
28 | return ts.sys.newLine;
|
29 | }
|
30 | },
|
31 |
|
32 | getCanonicalFileName: (fileName) => ts.sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase()
|
33 | };
|
34 | }
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 | function createModuleResolver(ts, host) {
|
42 | const compilerOptions = host.getCompilationSettings();
|
43 | const cache = ts.createModuleResolutionCache(process.cwd(), host.getCanonicalFileName, compilerOptions);
|
44 | const moduleHost = Object.assign(Object.assign({}, ts.sys), host);
|
45 | return (moduleName, containingFile) => {
|
46 | const resolved = ts.nodeModuleNameResolver(moduleName, containingFile, compilerOptions, moduleHost, cache);
|
47 | return resolved.resolvedModule;
|
48 | };
|
49 | }
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 | function __rest(s, e) {
|
67 | var t = {};
|
68 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
69 | t[p] = s[p];
|
70 | if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
71 | for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
72 | if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
73 | t[p[i]] = s[p[i]];
|
74 | }
|
75 | return t;
|
76 | }
|
77 |
|
78 | const resolveIdAsync = (file, opts) => new Promise((fulfil, reject) => resolveId(file, opts, (err, contents) => (err ? reject(err) : fulfil(contents))));
|
79 |
|
80 |
|
81 |
|
82 | function getTsLibPath() {
|
83 | return resolveIdAsync('tslib/tslib.es6.js', { basedir: __dirname });
|
84 | }
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
96 | function getPluginOptions(options) {
|
97 | const { include, exclude, tsconfig, typescript, tslib } = options, compilerOptions = __rest(options, ["include", "exclude", "tsconfig", "typescript", "tslib"]);
|
98 | const filter = createFilter(include || ['*.ts+(|x)', '**/*.ts+(|x)'], exclude);
|
99 | return {
|
100 | filter,
|
101 | tsconfig,
|
102 | compilerOptions: compilerOptions,
|
103 | typescript: typescript || defaultTs,
|
104 | tslib: tslib || getTsLibPath()
|
105 | };
|
106 | }
|
107 |
|
108 |
|
109 |
|
110 |
|
111 | function diagnosticToWarning(ts, host, diagnostic) {
|
112 | const pluginCode = `TS${diagnostic.code}`;
|
113 | const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
|
114 |
|
115 | const warning = {
|
116 | pluginCode,
|
117 | message: `@rollup/plugin-typescript ${pluginCode}: ${message}`
|
118 | };
|
119 | if (diagnostic.file) {
|
120 |
|
121 | const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
|
122 | warning.loc = {
|
123 | column: character + 1,
|
124 | line: line + 1,
|
125 | file: diagnostic.file.fileName
|
126 | };
|
127 | if (host) {
|
128 |
|
129 | const formatted = ts.formatDiagnosticsWithColorAndContext([diagnostic], host);
|
130 |
|
131 |
|
132 | let frame = formatted.slice(formatted.indexOf(message) + message.length);
|
133 | const newLine = host.getNewLine();
|
134 | if (frame.startsWith(newLine)) {
|
135 | frame = frame.slice(frame.indexOf(newLine) + newLine.length);
|
136 | }
|
137 | warning.frame = frame;
|
138 | }
|
139 | }
|
140 | return warning;
|
141 | }
|
142 |
|
143 | const DEFAULT_COMPILER_OPTIONS = {
|
144 | module: 'esnext',
|
145 | noEmitOnError: true,
|
146 | skipLibCheck: true
|
147 | };
|
148 | const FORCED_COMPILER_OPTIONS = {
|
149 |
|
150 | noEmitHelpers: true,
|
151 | importHelpers: true,
|
152 |
|
153 | noEmit: false,
|
154 | emitDeclarationOnly: false,
|
155 |
|
156 | noResolve: false
|
157 | };
|
158 |
|
159 |
|
160 | const DIRECTORY_PROPS = ['outDir', 'declarationDir'];
|
161 |
|
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 | function makePathsAbsolute(compilerOptions, relativeTo) {
|
169 | for (const pathProp of DIRECTORY_PROPS) {
|
170 | if (compilerOptions[pathProp]) {
|
171 | compilerOptions[pathProp] = resolve(relativeTo, compilerOptions[pathProp]);
|
172 | }
|
173 | }
|
174 | }
|
175 |
|
176 |
|
177 |
|
178 |
|
179 |
|
180 | function normalizeCompilerOptions(ts, compilerOptions) {
|
181 | let autoSetSourceMap = false;
|
182 | if (compilerOptions.inlineSourceMap) {
|
183 |
|
184 | compilerOptions.sourceMap = true;
|
185 | compilerOptions.inlineSourceMap = false;
|
186 | }
|
187 | else if (typeof compilerOptions.sourceMap !== 'boolean') {
|
188 |
|
189 |
|
190 | compilerOptions.sourceMap = true;
|
191 |
|
192 |
|
193 | compilerOptions.inlineSources = true;
|
194 | autoSetSourceMap = true;
|
195 | }
|
196 | switch (compilerOptions.module) {
|
197 | case ts.ModuleKind.ES2015:
|
198 | case ts.ModuleKind.ESNext:
|
199 | case ts.ModuleKind.CommonJS:
|
200 |
|
201 | return autoSetSourceMap;
|
202 | case ts.ModuleKind.None:
|
203 | case ts.ModuleKind.AMD:
|
204 | case ts.ModuleKind.UMD:
|
205 | case ts.ModuleKind.System: {
|
206 |
|
207 | const moduleType = ts.ModuleKind[compilerOptions.module];
|
208 | throw new Error(`@rollup/plugin-typescript: The module kind should be 'ES2015' or 'ESNext, found: '${moduleType}'`);
|
209 | }
|
210 | default:
|
211 |
|
212 | compilerOptions.module = ts.ModuleKind.ESNext;
|
213 | }
|
214 | return autoSetSourceMap;
|
215 | }
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 | function getTsConfigPath(ts, relativePath) {
|
224 | if (relativePath === false)
|
225 | return null;
|
226 |
|
227 | const tsConfigPath = resolve(process.cwd(), relativePath || 'tsconfig.json');
|
228 | if (!ts.sys.fileExists(tsConfigPath)) {
|
229 | if (relativePath) {
|
230 |
|
231 | throw new Error(`Could not find specified tsconfig.json at ${tsConfigPath}`);
|
232 | }
|
233 | else {
|
234 | return null;
|
235 | }
|
236 | }
|
237 | return tsConfigPath;
|
238 | }
|
239 |
|
240 |
|
241 |
|
242 |
|
243 |
|
244 |
|
245 | function readTsConfigFile(ts, tsConfigPath) {
|
246 | const { config, error } = ts.readConfigFile(tsConfigPath, (path) => readFileSync(path, 'utf8'));
|
247 | if (error) {
|
248 | throw Object.assign(Error(), diagnosticToWarning(ts, null, error));
|
249 | }
|
250 | return config || {};
|
251 | }
|
252 |
|
253 |
|
254 |
|
255 |
|
256 | function containsEnumOptions(compilerOptions) {
|
257 | const enums = [
|
258 | 'module',
|
259 | 'target',
|
260 | 'jsx',
|
261 | 'moduleResolution',
|
262 | 'newLine'
|
263 | ];
|
264 | return enums.some((prop) => prop in compilerOptions && typeof compilerOptions[prop] === 'number');
|
265 | }
|
266 | const configCache = new Map();
|
267 |
|
268 |
|
269 |
|
270 |
|
271 |
|
272 |
|
273 |
|
274 |
|
275 |
|
276 |
|
277 |
|
278 | function parseTypescriptConfig(ts, tsconfig, compilerOptions) {
|
279 |
|
280 | const cwd = process.cwd();
|
281 | makePathsAbsolute(compilerOptions, cwd);
|
282 | let parsedConfig;
|
283 |
|
284 |
|
285 | const tsConfigPath = getTsConfigPath(ts, tsconfig) || undefined;
|
286 | const tsConfigFile = tsConfigPath ? readTsConfigFile(ts, tsConfigPath) : {};
|
287 | const basePath = tsConfigPath ? dirname(tsConfigPath) : cwd;
|
288 |
|
289 |
|
290 | if (containsEnumOptions(compilerOptions)) {
|
291 | 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);
|
292 | }
|
293 | else {
|
294 | 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);
|
295 | }
|
296 | const autoSetSourceMap = normalizeCompilerOptions(ts, parsedConfig.options);
|
297 | return Object.assign(Object.assign({}, parsedConfig), { autoSetSourceMap });
|
298 | }
|
299 |
|
300 |
|
301 |
|
302 |
|
303 | function emitParsedOptionsErrors(ts, context, parsedOptions) {
|
304 | if (parsedOptions.errors.length > 0) {
|
305 | parsedOptions.errors.forEach((error) => context.warn(diagnosticToWarning(ts, null, error)));
|
306 | context.error(`@rollup/plugin-typescript: Couldn't process compiler options`);
|
307 | }
|
308 | }
|
309 |
|
310 |
|
311 |
|
312 |
|
313 |
|
314 |
|
315 |
|
316 |
|
317 |
|
318 | function validateSourceMap(context, compilerOptions, outputOptions, autoSetSourceMap) {
|
319 | if (compilerOptions.sourceMap && !outputOptions.sourcemap && !autoSetSourceMap) {
|
320 | context.warn(`@rollup/plugin-typescript: Rollup 'sourcemap' option must be set to generate source maps.`);
|
321 | }
|
322 | else if (!compilerOptions.sourceMap && outputOptions.sourcemap) {
|
323 | context.warn(`@rollup/plugin-typescript: Typescript 'sourceMap' compiler option must be set to generate source maps.`);
|
324 | }
|
325 | }
|
326 |
|
327 |
|
328 |
|
329 |
|
330 |
|
331 |
|
332 | function validatePaths(ts, context, compilerOptions, outputOptions) {
|
333 | if (compilerOptions.out) {
|
334 | context.error(`@rollup/plugin-typescript: Deprecated 'out' option is not supported. Use 'outDir' instead.`);
|
335 | }
|
336 | else if (compilerOptions.outFile) {
|
337 | context.error(`@rollup/plugin-typescript: 'outFile' option is not supported. Use 'outDir' instead.`);
|
338 | }
|
339 | for (const dirProperty of DIRECTORY_PROPS) {
|
340 | if (compilerOptions[dirProperty]) {
|
341 | if (!outputOptions.dir) {
|
342 | context.error(`@rollup/plugin-typescript: 'dir' must be used when '${dirProperty}' is specified.`);
|
343 | }
|
344 |
|
345 | const fromRollupDirToTs = relative(outputOptions.dir, compilerOptions[dirProperty]);
|
346 | if (fromRollupDirToTs.startsWith('..')) {
|
347 | context.error(`@rollup/plugin-typescript: '${dirProperty}' must be located inside 'dir'.`);
|
348 | }
|
349 | }
|
350 | }
|
351 | const tsBuildInfoPath = ts.getTsBuildInfoEmitOutputFilePath(compilerOptions);
|
352 | if (tsBuildInfoPath && compilerOptions.incremental) {
|
353 | if (!outputOptions.dir) {
|
354 | context.error(`@rollup/plugin-typescript: 'dir' must be used when 'tsBuildInfoFile' or 'incremental' are specified.`);
|
355 | }
|
356 |
|
357 | const fromRollupDirToTs = relative(outputOptions.dir, tsBuildInfoPath);
|
358 | if (fromRollupDirToTs.startsWith('..')) {
|
359 | context.error(`@rollup/plugin-typescript: 'tsBuildInfoFile' must be located inside 'dir'.`);
|
360 | }
|
361 | }
|
362 | if (compilerOptions.declaration || compilerOptions.declarationMap) {
|
363 | if (DIRECTORY_PROPS.every((dirProperty) => !compilerOptions[dirProperty])) {
|
364 | context.error(`@rollup/plugin-typescript: 'outDir' or 'declarationDir' must be specified to generate declaration files.`);
|
365 | }
|
366 | }
|
367 | }
|
368 |
|
369 |
|
370 |
|
371 |
|
372 | function isCodeOutputFile(name) {
|
373 | return !isMapOutputFile(name) && !name.endsWith('.d.ts');
|
374 | }
|
375 |
|
376 |
|
377 |
|
378 | function isMapOutputFile(name) {
|
379 | return name.endsWith('.map');
|
380 | }
|
381 |
|
382 |
|
383 |
|
384 |
|
385 |
|
386 |
|
387 | function findTypescriptOutput(ts, parsedOptions, id, emittedFiles) {
|
388 | const emittedFileNames = ts.getOutputFileNames(parsedOptions, id, !ts.sys.useCaseSensitiveFileNames);
|
389 | const codeFile = emittedFileNames.find(isCodeOutputFile);
|
390 | const mapFile = emittedFileNames.find(isMapOutputFile);
|
391 | return {
|
392 | code: emittedFiles.get(codeFile),
|
393 | map: emittedFiles.get(mapFile),
|
394 | declarations: emittedFileNames.filter((name) => name !== codeFile && name !== mapFile)
|
395 | };
|
396 | }
|
397 |
|
398 |
|
399 | const CANNOT_COMPILE_ESM = 1204;
|
400 |
|
401 |
|
402 |
|
403 | function emitDiagnostic(ts, context, host, diagnostic) {
|
404 | if (diagnostic.code === CANNOT_COMPILE_ESM)
|
405 | return;
|
406 | const { noEmitOnError } = host.getCompilationSettings();
|
407 |
|
408 | const warning = diagnosticToWarning(ts, host, diagnostic);
|
409 |
|
410 | if (noEmitOnError && diagnostic.category === ts.DiagnosticCategory.Error) {
|
411 | context.error(warning);
|
412 | }
|
413 | else {
|
414 | context.warn(warning);
|
415 | }
|
416 | }
|
417 | function buildDiagnosticReporter(ts, context, host) {
|
418 | return function reportDiagnostics(diagnostic) {
|
419 | emitDiagnostic(ts, context, host, diagnostic);
|
420 | };
|
421 | }
|
422 |
|
423 |
|
424 |
|
425 |
|
426 |
|
427 |
|
428 |
|
429 | function createWatchHost(ts, context, { formatHost, parsedOptions, writeFile, resolveModule }) {
|
430 | const createProgram = ts.createEmitAndSemanticDiagnosticsBuilderProgram;
|
431 | const baseHost = ts.createWatchCompilerHost(parsedOptions.fileNames, parsedOptions.options, ts.sys, createProgram, buildDiagnosticReporter(ts, context, formatHost),
|
432 |
|
433 | () => { }, parsedOptions.projectReferences);
|
434 | return Object.assign(Object.assign({}, baseHost), {
|
435 |
|
436 | afterProgramCreate(program) {
|
437 | const origEmit = program.emit;
|
438 |
|
439 | program.emit = (targetSourceFile, _, ...args) => origEmit(targetSourceFile, writeFile, ...args);
|
440 | return baseHost.afterProgramCreate(program);
|
441 | },
|
442 |
|
443 | resolveModuleNames(moduleNames, containingFile) {
|
444 | return moduleNames.map((moduleName) => resolveModule(moduleName, containingFile));
|
445 | } });
|
446 | }
|
447 | function createWatchProgram(ts, context, options) {
|
448 | return ts.createWatchProgram(createWatchHost(ts, context, options));
|
449 | }
|
450 |
|
451 | function typescript(options = {}) {
|
452 | const { filter, tsconfig, compilerOptions, tslib, typescript: ts } = getPluginOptions(options);
|
453 | const emittedFiles = new Map();
|
454 | const parsedOptions = parseTypescriptConfig(ts, tsconfig, compilerOptions);
|
455 | parsedOptions.fileNames = parsedOptions.fileNames.filter(filter);
|
456 | const formatHost = createFormattingHost(ts, parsedOptions.options);
|
457 | const resolveModule = createModuleResolver(ts, formatHost);
|
458 | let program = null;
|
459 | function normalizePath(fileName) {
|
460 | return fileName.split(win32.sep).join(posix.sep);
|
461 | }
|
462 | return {
|
463 | name: 'typescript',
|
464 | buildStart() {
|
465 | emitParsedOptionsErrors(ts, this, parsedOptions);
|
466 | program = createWatchProgram(ts, this, {
|
467 | formatHost,
|
468 | resolveModule,
|
469 | parsedOptions,
|
470 | writeFile(fileName, data) {
|
471 | emittedFiles.set(fileName, data);
|
472 | }
|
473 | });
|
474 | },
|
475 | buildEnd() {
|
476 | var _a;
|
477 | if (process.env.ROLLUP_WATCH !== 'true') {
|
478 |
|
479 |
|
480 | (_a = program) === null || _a === void 0 ? void 0 : _a.close();
|
481 | }
|
482 | },
|
483 | renderStart(outputOptions) {
|
484 | validateSourceMap(this, parsedOptions.options, outputOptions, parsedOptions.autoSetSourceMap);
|
485 | validatePaths(ts, this, parsedOptions.options, outputOptions);
|
486 | },
|
487 | resolveId(importee, importer) {
|
488 | if (importee === 'tslib') {
|
489 | return tslib;
|
490 | }
|
491 | if (!importer)
|
492 | return null;
|
493 |
|
494 | const containingFile = normalizePath(importer);
|
495 | const resolved = resolveModule(importee, containingFile);
|
496 | if (resolved) {
|
497 | if (resolved.extension === '.d.ts')
|
498 | return null;
|
499 | return resolved.resolvedFileName;
|
500 | }
|
501 | return null;
|
502 | },
|
503 | load(id) {
|
504 | if (!filter(id))
|
505 | return null;
|
506 | const output = findTypescriptOutput(ts, parsedOptions, id, emittedFiles);
|
507 | return output.code ? output : null;
|
508 | },
|
509 | generateBundle(outputOptions) {
|
510 | parsedOptions.fileNames.forEach(fileName => {
|
511 | const output = findTypescriptOutput(ts, parsedOptions, fileName, emittedFiles);
|
512 | output.declarations.forEach((id) => {
|
513 | const code = emittedFiles.get(id);
|
514 | if (!code)
|
515 | return;
|
516 | this.emitFile({
|
517 | type: 'asset',
|
518 | fileName: normalizePath(relative(outputOptions.dir, id)),
|
519 | source: code
|
520 | });
|
521 | });
|
522 | });
|
523 | const tsBuildInfoPath = ts.getTsBuildInfoEmitOutputFilePath(parsedOptions.options);
|
524 | if (tsBuildInfoPath) {
|
525 | this.emitFile({
|
526 | type: 'asset',
|
527 | fileName: normalizePath(relative(outputOptions.dir, tsBuildInfoPath)),
|
528 | source: emittedFiles.get(tsBuildInfoPath)
|
529 | });
|
530 | }
|
531 | }
|
532 | };
|
533 | }
|
534 |
|
535 | export default typescript;
|