mirror of
https://github.com/ezolenko/rollup-plugin-typescript2.git
synced 2025-12-08 19:06:16 +00:00
348 lines
11 KiB
TypeScript
348 lines
11 KiB
TypeScript
import { RollupContext } from "./rollupcontext";
|
|
import { ConsoleContext, IRollupContext, VerbosityLevel } from "./context";
|
|
import { LanguageServiceHost } from "./host";
|
|
import { TsCache, convertDiagnostic, IRollupCode, convertEmitOutput } from "./tscache";
|
|
import { tsModule, setTypescriptModule } from "./tsproxy";
|
|
import * as tsTypes from "typescript";
|
|
import * as resolve from "resolve";
|
|
import * as _ from "lodash";
|
|
import { IRollupOptions } from "./irollup-options";
|
|
import { IOptions } from "./ioptions";
|
|
import { Partial } from "./partial";
|
|
import { parseTsConfig } from "./parse-tsconfig";
|
|
import { printDiagnostics } from "./print-diagnostics";
|
|
import { TSLIB, tslibSource, tslibVersion } from "./tslib";
|
|
import { blue, red, yellow } from "colors/safe";
|
|
import { dirname, isAbsolute, join, relative } from "path";
|
|
import { normalize } from "./normalize";
|
|
|
|
export default function typescript(options?: Partial<IOptions>)
|
|
{
|
|
// tslint:disable-next-line:no-var-requires
|
|
const createFilter = require("rollup-pluginutils").createFilter;
|
|
// tslint:enable-next-line:no-var-requires
|
|
let watchMode = false;
|
|
let generateRound = 0;
|
|
let rollupOptions: IRollupOptions;
|
|
let context: ConsoleContext;
|
|
let filter: any;
|
|
let parsedConfig: tsTypes.ParsedCommandLine;
|
|
let servicesHost: LanguageServiceHost;
|
|
let service: tsTypes.LanguageService;
|
|
let noErrors = true;
|
|
const declarations: { [name: string]: { type: tsTypes.OutputFile; map?: tsTypes.OutputFile } } = {};
|
|
|
|
let _cache: TsCache;
|
|
const cache = (): TsCache =>
|
|
{
|
|
if (!_cache)
|
|
_cache = new TsCache(pluginOptions.clean, servicesHost, pluginOptions.cacheRoot, parsedConfig.options, rollupOptions, parsedConfig.fileNames, context);
|
|
return _cache;
|
|
};
|
|
|
|
const pluginOptions = { ...options } as IOptions;
|
|
|
|
_.defaults(pluginOptions,
|
|
{
|
|
check: true,
|
|
verbosity: VerbosityLevel.Warning,
|
|
clean: false,
|
|
cacheRoot: `${process.cwd()}/.rpt2_cache`,
|
|
include: ["*.ts+(|x)", "**/*.ts+(|x)"],
|
|
exclude: ["*.d.ts", "**/*.d.ts"],
|
|
abortOnError: true,
|
|
rollupCommonJSResolveHack: false,
|
|
typescript: require("typescript"),
|
|
tsconfig: undefined,
|
|
useTsconfigDeclarationDir: false,
|
|
tsconfigOverride: {},
|
|
transformers: [],
|
|
tsconfigDefaults: {},
|
|
});
|
|
|
|
setTypescriptModule(pluginOptions.typescript);
|
|
|
|
return {
|
|
|
|
name: "rpt2",
|
|
|
|
options(config: IRollupOptions)
|
|
{
|
|
rollupOptions = {... config};
|
|
context = new ConsoleContext(pluginOptions.verbosity, "rpt2: ");
|
|
|
|
context.info(`typescript version: ${tsModule.version}`);
|
|
context.info(`tslib version: ${tslibVersion}`);
|
|
|
|
context.info(`rollup-plugin-typescript2 version: $RPT2_VERSION`);
|
|
context.debug(() => `plugin options:\n${JSON.stringify(pluginOptions, (key, value) => key === "typescript" ? `version ${(value as typeof tsModule).version}` : value, 4)}`);
|
|
context.debug(() => `rollup config:\n${JSON.stringify(rollupOptions, undefined, 4)}`);
|
|
|
|
watchMode = process.env.ROLLUP_WATCH === "true";
|
|
|
|
if (watchMode)
|
|
context.info(`running in watch mode`);
|
|
|
|
parsedConfig = parseTsConfig(context, pluginOptions);
|
|
|
|
if (parsedConfig.options.rootDirs)
|
|
{
|
|
const included = _
|
|
.chain(parsedConfig.options.rootDirs)
|
|
.flatMap((root) =>
|
|
{
|
|
if (pluginOptions.include instanceof Array)
|
|
return pluginOptions.include.map((include) => join(root, include));
|
|
else
|
|
return join(root, pluginOptions.include);
|
|
})
|
|
.uniq()
|
|
.value();
|
|
|
|
const excluded = _
|
|
.chain(parsedConfig.options.rootDirs)
|
|
.flatMap((root) =>
|
|
{
|
|
if (pluginOptions.exclude instanceof Array)
|
|
return pluginOptions.exclude.map((exclude) => join(root, exclude));
|
|
else
|
|
return join(root, pluginOptions.exclude);
|
|
})
|
|
.uniq()
|
|
.value();
|
|
|
|
filter = createFilter(included, excluded);
|
|
context.debug(() => `included:\n${JSON.stringify(included, undefined, 4)}`);
|
|
context.debug(() => `excluded:\n${JSON.stringify(excluded, undefined, 4)}`);
|
|
}
|
|
else
|
|
{
|
|
filter = createFilter(pluginOptions.include, pluginOptions.exclude);
|
|
context.debug(() => `included:\n'${JSON.stringify(pluginOptions.include, undefined, 4)}'`);
|
|
context.debug(() => `excluded:\n'${JSON.stringify(pluginOptions.exclude, undefined, 4)}'`);
|
|
}
|
|
|
|
servicesHost = new LanguageServiceHost(parsedConfig, pluginOptions.transformers);
|
|
|
|
service = tsModule.createLanguageService(servicesHost, tsModule.createDocumentRegistry());
|
|
servicesHost.setLanguageService(service);
|
|
|
|
// printing compiler option errors
|
|
if (pluginOptions.check)
|
|
printDiagnostics(context, convertDiagnostic("options", service.getCompilerOptionsDiagnostics()), parsedConfig.options.pretty === true);
|
|
|
|
if (pluginOptions.clean)
|
|
cache().clean();
|
|
},
|
|
|
|
resolveId(importee: string, importer: string)
|
|
{
|
|
if (importee === TSLIB)
|
|
return "\0" + TSLIB;
|
|
|
|
if (!importer)
|
|
return null;
|
|
|
|
importer = importer.split("\\").join("/");
|
|
|
|
// TODO: use module resolution cache
|
|
const result = tsModule.nodeModuleNameResolver(importee, importer, parsedConfig.options, tsModule.sys);
|
|
|
|
if (result.resolvedModule && result.resolvedModule.resolvedFileName)
|
|
{
|
|
if (filter(result.resolvedModule.resolvedFileName))
|
|
cache().setDependency(result.resolvedModule.resolvedFileName, importer);
|
|
|
|
if (_.endsWith(result.resolvedModule.resolvedFileName, ".d.ts"))
|
|
return null;
|
|
|
|
const resolved = pluginOptions.rollupCommonJSResolveHack
|
|
? resolve.sync(result.resolvedModule.resolvedFileName)
|
|
: result.resolvedModule.resolvedFileName;
|
|
|
|
context.debug(() => `${blue("resolving")} '${importee}'`);
|
|
context.debug(() => ` to '${resolved}'`);
|
|
|
|
return resolved;
|
|
}
|
|
|
|
return null;
|
|
},
|
|
|
|
load(id: string): string | undefined
|
|
{
|
|
if (id === "\0" + TSLIB)
|
|
return tslibSource;
|
|
|
|
return undefined;
|
|
},
|
|
|
|
transform(this: IRollupContext, code: string, id: string): IRollupCode | undefined
|
|
{
|
|
generateRound = 0; // in watch mode transform call resets generate count (used to avoid printing too many copies of the same error messages)
|
|
|
|
if (!filter(id))
|
|
return undefined;
|
|
|
|
const contextWrapper = new RollupContext(pluginOptions.verbosity, pluginOptions.abortOnError, this, "rpt2: ");
|
|
|
|
const snapshot = servicesHost.setSnapshot(id, code);
|
|
|
|
// getting compiled file from cache or from ts
|
|
const result = cache().getCompiled(id, snapshot, () =>
|
|
{
|
|
const output = service.getEmitOutput(id);
|
|
|
|
if (output.emitSkipped)
|
|
{
|
|
noErrors = false;
|
|
|
|
// always checking on fatal errors, even if options.check is set to false
|
|
const diagnostics = _.concat(
|
|
cache().getSyntacticDiagnostics(id, snapshot, () =>
|
|
{
|
|
return service.getSyntacticDiagnostics(id);
|
|
}),
|
|
cache().getSemanticDiagnostics(id, snapshot, () =>
|
|
{
|
|
return service.getSemanticDiagnostics(id);
|
|
}),
|
|
);
|
|
printDiagnostics(contextWrapper, diagnostics, parsedConfig.options.pretty === true);
|
|
|
|
// since no output was generated, aborting compilation
|
|
cache().done();
|
|
if (_.isFunction(this.error))
|
|
this.error(red(`failed to transpile '${id}'`));
|
|
}
|
|
|
|
return convertEmitOutput(output);
|
|
});
|
|
|
|
if (pluginOptions.check)
|
|
{
|
|
const diagnostics = _.concat(
|
|
cache().getSyntacticDiagnostics(id, snapshot, () =>
|
|
{
|
|
return service.getSyntacticDiagnostics(id);
|
|
}),
|
|
cache().getSemanticDiagnostics(id, snapshot, () =>
|
|
{
|
|
return service.getSemanticDiagnostics(id);
|
|
}),
|
|
);
|
|
|
|
if (diagnostics.length > 0)
|
|
noErrors = false;
|
|
|
|
printDiagnostics(contextWrapper, diagnostics, parsedConfig.options.pretty === true);
|
|
}
|
|
|
|
if (result)
|
|
{
|
|
if (result.dts)
|
|
{
|
|
const key = normalize(id);
|
|
declarations[key] = { type: result.dts, map: result.dtsmap };
|
|
context.debug(() => `${blue("generated declarations")} for '${key}'`);
|
|
}
|
|
|
|
const transformResult = { code: result.code, map: { mappings: "" } };
|
|
|
|
if (result.map)
|
|
{
|
|
if (pluginOptions.sourceMapCallback)
|
|
pluginOptions.sourceMapCallback(id, result.map);
|
|
transformResult.map = JSON.parse(result.map);
|
|
}
|
|
|
|
return transformResult;
|
|
}
|
|
|
|
return undefined;
|
|
},
|
|
|
|
ongenerate(): void
|
|
{
|
|
context.debug(() => `generating target ${generateRound + 1}`);
|
|
|
|
if (pluginOptions.check && watchMode && generateRound === 0)
|
|
{
|
|
cache().walkTree((id) =>
|
|
{
|
|
if (!filter(id))
|
|
return;
|
|
|
|
const diagnostics = _.concat(
|
|
convertDiagnostic("syntax", service.getSyntacticDiagnostics(id)),
|
|
convertDiagnostic("semantic", service.getSemanticDiagnostics(id)),
|
|
);
|
|
|
|
printDiagnostics(context, diagnostics, parsedConfig.options.pretty === true);
|
|
});
|
|
}
|
|
|
|
if (!watchMode && !noErrors)
|
|
context.info(yellow("there were errors or warnings."));
|
|
|
|
cache().done();
|
|
|
|
generateRound++;
|
|
},
|
|
|
|
onwrite({ dest, file }: IRollupOptions)
|
|
{
|
|
if (parsedConfig.options.declaration)
|
|
{
|
|
_.each(parsedConfig.fileNames, (name) =>
|
|
{
|
|
const key = normalize(name);
|
|
if (_.has(declarations, key) || !filter(key))
|
|
return;
|
|
context.debug(() => `generating missed declarations for '${key}'`);
|
|
const output = service.getEmitOutput(key, true);
|
|
const out = convertEmitOutput(output);
|
|
if (out.dts)
|
|
declarations[key] = { type: out.dts, map: out.dtsmap };
|
|
});
|
|
|
|
const bundleFile = file ? file : dest; // rollup 0.48+ has 'file' https://github.com/rollup/rollup/issues/1479
|
|
|
|
const writeDeclaration = (key: string, extension: string, entry?: tsTypes.OutputFile) =>
|
|
{
|
|
if (!entry)
|
|
return;
|
|
|
|
let fileName = entry.name;
|
|
if (fileName.includes("?")) // HACK for rollup-plugin-vue, it creates virtual modules in form 'file.vue?rollup-plugin-vue=script.ts'
|
|
fileName = fileName.split("?", 1) + extension;
|
|
|
|
let writeToPath: string;
|
|
// If for some reason no 'dest' property exists or if 'useTsconfigDeclarationDir' is given in the plugin options,
|
|
// use the path provided by Typescript's LanguageService.
|
|
if (!bundleFile || pluginOptions.useTsconfigDeclarationDir)
|
|
writeToPath = fileName;
|
|
else
|
|
{
|
|
// Otherwise, take the directory name from the path and make sure it is absolute.
|
|
const destDirname = dirname(bundleFile);
|
|
const destDirectory = isAbsolute(destDirname) ? destDirname : join(process.cwd(), destDirname);
|
|
writeToPath = join(destDirectory, relative(process.cwd(), fileName));
|
|
}
|
|
|
|
context.debug(() => `${blue("writing declarations")} for '${key}' to '${writeToPath}'`);
|
|
|
|
// Write the declaration file to disk.
|
|
tsModule.sys.writeFile(writeToPath, entry.text, entry.writeByteOrderMark);
|
|
};
|
|
|
|
_.each(declarations, ({ type, map }, key) =>
|
|
{
|
|
writeDeclaration(key, ".d.ts", type);
|
|
writeDeclaration(key, ".d.ts.map", map);
|
|
});
|
|
}
|
|
},
|
|
};
|
|
}
|