UNPKG

10.9 kBPlain TextView Raw
1import { RollupContext } from "./rollupcontext";
2import { ConsoleContext, IRollupContext, VerbosityLevel } from "./context";
3import { LanguageServiceHost } from "./host";
4import { TsCache, convertDiagnostic, IRollupCode, convertEmitOutput } from "./tscache";
5import { tsModule, setTypescriptModule } from "./tsproxy";
6import * as tsTypes from "typescript";
7import * as resolve from "resolve";
8import * as _ from "lodash";
9import { IRollupOptions } from "./irollup-options";
10import { IOptions } from "./ioptions";
11import { Partial } from "./partial";
12import { parseTsConfig } from "./parse-tsconfig";
13import { printDiagnostics } from "./print-diagnostics";
14import { TSLIB, tslibSource, tslibVersion } from "./tslib";
15import { blue, red, yellow } from "colors/safe";
16import { dirname, isAbsolute, join, relative } from "path";
17import { normalize } from "./normalize";
18
19export default function typescript(options?: Partial<IOptions>)
20{
21 // tslint:disable-next-line:no-var-requires
22 const createFilter = require("rollup-pluginutils").createFilter;
23 // tslint:enable-next-line:no-var-requires
24 let watchMode = false;
25 let generateRound = 0;
26 let rollupOptions: IRollupOptions;
27 let context: ConsoleContext;
28 let filter: any;
29 let parsedConfig: tsTypes.ParsedCommandLine;
30 let servicesHost: LanguageServiceHost;
31 let service: tsTypes.LanguageService;
32 let noErrors = true;
33 const declarations: { [name: string]: { type: tsTypes.OutputFile; map?: tsTypes.OutputFile } } = {};
34
35 let _cache: TsCache;
36 const cache = (): TsCache =>
37 {
38 if (!_cache)
39 _cache = new TsCache(pluginOptions.clean, servicesHost, pluginOptions.cacheRoot, parsedConfig.options, rollupOptions, parsedConfig.fileNames, context);
40 return _cache;
41 };
42
43 const pluginOptions = { ...options } as IOptions;
44
45 _.defaults(pluginOptions,
46 {
47 check: true,
48 verbosity: VerbosityLevel.Warning,
49 clean: false,
50 cacheRoot: `${process.cwd()}/.rpt2_cache`,
51 include: ["*.ts+(|x)", "**/*.ts+(|x)"],
52 exclude: ["*.d.ts", "**/*.d.ts"],
53 abortOnError: true,
54 rollupCommonJSResolveHack: false,
55 typescript: require("typescript"),
56 tsconfig: undefined,
57 useTsconfigDeclarationDir: false,
58 tsconfigOverride: {},
59 transformers: [],
60 tsconfigDefaults: {},
61 });
62
63 setTypescriptModule(pluginOptions.typescript);
64
65 return {
66
67 name: "rpt2",
68
69 options(config: IRollupOptions)
70 {
71 rollupOptions = {... config};
72 context = new ConsoleContext(pluginOptions.verbosity, "rpt2: ");
73
74 context.info(`typescript version: ${tsModule.version}`);
75 context.info(`tslib version: ${tslibVersion}`);
76
77 context.info(`rollup-plugin-typescript2 version: $RPT2_VERSION`);
78 context.debug(() => `plugin options:\n${JSON.stringify(pluginOptions, (key, value) => key === "typescript" ? `version ${(value as typeof tsModule).version}` : value, 4)}`);
79 context.debug(() => `rollup config:\n${JSON.stringify(rollupOptions, undefined, 4)}`);
80
81 watchMode = process.env.ROLLUP_WATCH === "true";
82
83 if (watchMode)
84 context.info(`running in watch mode`);
85
86 parsedConfig = parseTsConfig(context, pluginOptions);
87
88 if (parsedConfig.options.rootDirs)
89 {
90 const included = _
91 .chain(parsedConfig.options.rootDirs)
92 .flatMap((root) =>
93 {
94 if (pluginOptions.include instanceof Array)
95 return pluginOptions.include.map((include) => join(root, include));
96 else
97 return join(root, pluginOptions.include);
98 })
99 .uniq()
100 .value();
101
102 const excluded = _
103 .chain(parsedConfig.options.rootDirs)
104 .flatMap((root) =>
105 {
106 if (pluginOptions.exclude instanceof Array)
107 return pluginOptions.exclude.map((exclude) => join(root, exclude));
108 else
109 return join(root, pluginOptions.exclude);
110 })
111 .uniq()
112 .value();
113
114 filter = createFilter(included, excluded);
115 context.debug(() => `included:\n${JSON.stringify(included, undefined, 4)}`);
116 context.debug(() => `excluded:\n${JSON.stringify(excluded, undefined, 4)}`);
117 }
118 else
119 {
120 filter = createFilter(pluginOptions.include, pluginOptions.exclude);
121 context.debug(() => `included:\n'${JSON.stringify(pluginOptions.include, undefined, 4)}'`);
122 context.debug(() => `excluded:\n'${JSON.stringify(pluginOptions.exclude, undefined, 4)}'`);
123 }
124
125 servicesHost = new LanguageServiceHost(parsedConfig, pluginOptions.transformers);
126
127 service = tsModule.createLanguageService(servicesHost, tsModule.createDocumentRegistry());
128 servicesHost.setLanguageService(service);
129
130 // printing compiler option errors
131 if (pluginOptions.check)
132 printDiagnostics(context, convertDiagnostic("options", service.getCompilerOptionsDiagnostics()), parsedConfig.options.pretty === true);
133
134 if (pluginOptions.clean)
135 cache().clean();
136 },
137
138 resolveId(importee: string, importer: string)
139 {
140 if (importee === TSLIB)
141 return "\0" + TSLIB;
142
143 if (!importer)
144 return null;
145
146 importer = importer.split("\\").join("/");
147
148 // TODO: use module resolution cache
149 const result = tsModule.nodeModuleNameResolver(importee, importer, parsedConfig.options, tsModule.sys);
150
151 if (result.resolvedModule && result.resolvedModule.resolvedFileName)
152 {
153 if (filter(result.resolvedModule.resolvedFileName))
154 cache().setDependency(result.resolvedModule.resolvedFileName, importer);
155
156 if (_.endsWith(result.resolvedModule.resolvedFileName, ".d.ts"))
157 return null;
158
159 const resolved = pluginOptions.rollupCommonJSResolveHack
160 ? resolve.sync(result.resolvedModule.resolvedFileName)
161 : result.resolvedModule.resolvedFileName;
162
163 context.debug(() => `${blue("resolving")} '${importee}'`);
164 context.debug(() => ` to '${resolved}'`);
165
166 return resolved;
167 }
168
169 return null;
170 },
171
172 load(id: string): string | undefined
173 {
174 if (id === "\0" + TSLIB)
175 return tslibSource;
176
177 return undefined;
178 },
179
180 transform(this: IRollupContext, code: string, id: string): IRollupCode | undefined
181 {
182 generateRound = 0; // in watch mode transform call resets generate count (used to avoid printing too many copies of the same error messages)
183
184 if (!filter(id))
185 return undefined;
186
187 const contextWrapper = new RollupContext(pluginOptions.verbosity, pluginOptions.abortOnError, this, "rpt2: ");
188
189 const snapshot = servicesHost.setSnapshot(id, code);
190
191 // getting compiled file from cache or from ts
192 const result = cache().getCompiled(id, snapshot, () =>
193 {
194 const output = service.getEmitOutput(id);
195
196 if (output.emitSkipped)
197 {
198 noErrors = false;
199
200 // always checking on fatal errors, even if options.check is set to false
201 const diagnostics = _.concat(
202 cache().getSyntacticDiagnostics(id, snapshot, () =>
203 {
204 return service.getSyntacticDiagnostics(id);
205 }),
206 cache().getSemanticDiagnostics(id, snapshot, () =>
207 {
208 return service.getSemanticDiagnostics(id);
209 }),
210 );
211 printDiagnostics(contextWrapper, diagnostics, parsedConfig.options.pretty === true);
212
213 // since no output was generated, aborting compilation
214 cache().done();
215 if (_.isFunction(this.error))
216 this.error(red(`failed to transpile '${id}'`));
217 }
218
219 return convertEmitOutput(output);
220 });
221
222 if (pluginOptions.check)
223 {
224 const diagnostics = _.concat(
225 cache().getSyntacticDiagnostics(id, snapshot, () =>
226 {
227 return service.getSyntacticDiagnostics(id);
228 }),
229 cache().getSemanticDiagnostics(id, snapshot, () =>
230 {
231 return service.getSemanticDiagnostics(id);
232 }),
233 );
234
235 if (diagnostics.length > 0)
236 noErrors = false;
237
238 printDiagnostics(contextWrapper, diagnostics, parsedConfig.options.pretty === true);
239 }
240
241 if (result)
242 {
243 if (result.dts)
244 {
245 const key = normalize(id);
246 declarations[key] = { type: result.dts, map: result.dtsmap };
247 context.debug(() => `${blue("generated declarations")} for '${key}'`);
248 }
249
250 const transformResult = { code: result.code, map: { mappings: "" } };
251
252 if (result.map)
253 {
254 if (pluginOptions.sourceMapCallback)
255 pluginOptions.sourceMapCallback(id, result.map);
256 transformResult.map = JSON.parse(result.map);
257 }
258
259 return transformResult;
260 }
261
262 return undefined;
263 },
264
265 ongenerate(): void
266 {
267 context.debug(() => `generating target ${generateRound + 1}`);
268
269 if (pluginOptions.check && watchMode && generateRound === 0)
270 {
271 cache().walkTree((id) =>
272 {
273 if (!filter(id))
274 return;
275
276 const diagnostics = _.concat(
277 convertDiagnostic("syntax", service.getSyntacticDiagnostics(id)),
278 convertDiagnostic("semantic", service.getSemanticDiagnostics(id)),
279 );
280
281 printDiagnostics(context, diagnostics, parsedConfig.options.pretty === true);
282 });
283 }
284
285 if (!watchMode && !noErrors)
286 context.info(yellow("there were errors or warnings."));
287
288 cache().done();
289
290 generateRound++;
291 },
292
293 onwrite({ dest, file }: IRollupOptions)
294 {
295 if (parsedConfig.options.declaration)
296 {
297 _.each(parsedConfig.fileNames, (name) =>
298 {
299 const key = normalize(name);
300 if (_.has(declarations, key) || !filter(key))
301 return;
302 context.debug(() => `generating missed declarations for '${key}'`);
303 const output = service.getEmitOutput(key, true);
304 const out = convertEmitOutput(output);
305 if (out.dts)
306 declarations[key] = { type: out.dts, map: out.dtsmap };
307 });
308
309 const bundleFile = file ? file : dest; // rollup 0.48+ has 'file' https://github.com/rollup/rollup/issues/1479
310
311 const writeDeclaration = (key: string, extension: string, entry?: tsTypes.OutputFile) =>
312 {
313 if (!entry)
314 return;
315
316 let fileName = entry.name;
317 if (fileName.includes("?")) // HACK for rollup-plugin-vue, it creates virtual modules in form 'file.vue?rollup-plugin-vue=script.ts'
318 fileName = fileName.split("?", 1) + extension;
319
320 let writeToPath: string;
321 // If for some reason no 'dest' property exists or if 'useTsconfigDeclarationDir' is given in the plugin options,
322 // use the path provided by Typescript's LanguageService.
323 if (!bundleFile || pluginOptions.useTsconfigDeclarationDir)
324 writeToPath = fileName;
325 else
326 {
327 // Otherwise, take the directory name from the path and make sure it is absolute.
328 const destDirname = dirname(bundleFile);
329 const destDirectory = isAbsolute(destDirname) ? destDirname : join(process.cwd(), destDirname);
330 writeToPath = join(destDirectory, relative(process.cwd(), fileName));
331 }
332
333 context.debug(() => `${blue("writing declarations")} for '${key}' to '${writeToPath}'`);
334
335 // Write the declaration file to disk.
336 tsModule.sys.writeFile(writeToPath, entry.text, entry.writeByteOrderMark);
337 };
338
339 _.each(declarations, ({ type, map }, key) =>
340 {
341 writeDeclaration(key, ".d.ts", type);
342 writeDeclaration(key, ".d.ts.map", map);
343 });
344 }
345 },
346 };
347}
348
\No newline at end of file