mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-03-15 14:05:47 -05:00
Adds support for gulp.watch
This commit is contained in:
@@ -16,16 +16,16 @@ const { reportDiagnostics } = require("./diagnostics");
|
||||
|
||||
class CompilationGulp extends gulp.Gulp {
|
||||
/**
|
||||
* @param {boolean} [verbose]
|
||||
* @param {boolean} [verbose]
|
||||
*/
|
||||
fork(verbose) {
|
||||
const child = new ForkedGulp(this.tasks);
|
||||
if (verbose) {
|
||||
this.on("task_start", e => gulp.emit("task_start", e));
|
||||
this.on("task_stop", e => gulp.emit("task_stop", e));
|
||||
this.on("task_err", e => gulp.emit("task_err", e));
|
||||
this.on("task_not_found", e => gulp.emit("task_not_found", e));
|
||||
this.on("task_recursion", e => gulp.emit("task_recursion", e));
|
||||
child.on("task_start", e => gulp.emit("task_start", e));
|
||||
child.on("task_stop", e => gulp.emit("task_stop", e));
|
||||
child.on("task_err", e => gulp.emit("task_err", e));
|
||||
child.on("task_not_found", e => gulp.emit("task_not_found", e));
|
||||
child.on("task_recursion", e => gulp.emit("task_recursion", e));
|
||||
}
|
||||
return child;
|
||||
}
|
||||
@@ -58,13 +58,15 @@ const typescriptAliasMap = new Map();
|
||||
/**
|
||||
* Defines a gulp orchestration for a TypeScript project, returning a callback that can be used to trigger compilation.
|
||||
* @param {string} projectSpec The path to a tsconfig.json file or its containing directory.
|
||||
* @param {ProjectOptions} [options] Project compilation options.
|
||||
* @param {CompileOptions} [options] Project compilation options.
|
||||
* @returns {() => Promise<void>}
|
||||
*/
|
||||
function createCompiler(projectSpec, options) {
|
||||
const resolvedOptions = resolveProjectOptions(options);
|
||||
const resolvedOptions = resolveCompileOptions(options);
|
||||
const resolvedProjectSpec = resolveProjectSpec(projectSpec, resolvedOptions.paths, /*referrer*/ undefined);
|
||||
const taskName = compileTaskName(ensureCompileTask(getOrCreateProjectGraph(resolvedProjectSpec, resolvedOptions.paths), resolvedOptions), resolvedOptions.typescript);
|
||||
const projectGraph = getOrCreateProjectGraph(resolvedProjectSpec, resolvedOptions.paths);
|
||||
projectGraph.isRoot = true;
|
||||
const taskName = compileTaskName(ensureCompileTask(projectGraph, resolvedOptions), resolvedOptions.typescript);
|
||||
return () => new Promise((resolve, reject) => compilationGulp
|
||||
.fork(resolvedOptions.verbose)
|
||||
.start(taskName, err => err ? reject(err) : resolve()));
|
||||
@@ -74,10 +76,10 @@ exports.createCompiler = createCompiler;
|
||||
/**
|
||||
* Defines and executes a gulp orchestration for a TypeScript project.
|
||||
* @param {string} projectSpec The path to a tsconfig.json file or its containing directory.
|
||||
* @param {ProjectOptions} [options] Project compilation options.
|
||||
* @param {CompileOptions} [options] Project compilation options.
|
||||
* @returns {Promise<void>}
|
||||
*
|
||||
* @typedef ProjectOptions
|
||||
*
|
||||
* @typedef CompileOptions
|
||||
* @property {string} [cwd] The path to use for the current working directory. Defaults to `process.cwd()`.
|
||||
* @property {string} [base] The path to use as the base for relative paths. Defaults to `cwd`.
|
||||
* @property {string} [typescript] A module specifier or path (relative to gulpfile.js) to the version of TypeScript to use.
|
||||
@@ -86,7 +88,7 @@ exports.createCompiler = createCompiler;
|
||||
* @property {boolean} [verbose] Indicates whether verbose logging is enabled.
|
||||
* @property {boolean} [force] Force recompilation (no up-to-date check).
|
||||
* @property {boolean} [inProcess] Indicates whether to run gulp-typescript in-process or out-of-process (default).
|
||||
*
|
||||
*
|
||||
* @typedef {(stream: NodeJS.ReadableStream) => NodeJS.ReadWriteStream} Hook
|
||||
*/
|
||||
function compile(projectSpec, options) {
|
||||
@@ -103,7 +105,9 @@ exports.compile = compile;
|
||||
function createCleaner(projectSpec, options) {
|
||||
const paths = resolvePathOptions(options);
|
||||
const resolvedProjectSpec = resolveProjectSpec(projectSpec, paths, /*referrer*/ undefined);
|
||||
const taskName = cleanTaskName(ensureCleanTask(getOrCreateProjectGraph(resolvedProjectSpec, paths)));
|
||||
const projectGraph = getOrCreateProjectGraph(resolvedProjectSpec, paths);
|
||||
projectGraph.isRoot = true;
|
||||
const taskName = cleanTaskName(ensureCleanTask(projectGraph));
|
||||
return () => new Promise((resolve, reject) => compilationGulp
|
||||
.fork()
|
||||
.start(taskName, err => err ? reject(err) : resolve()));
|
||||
@@ -121,6 +125,25 @@ function clean(projectSpec, options) {
|
||||
}
|
||||
exports.clean = clean;
|
||||
|
||||
/**
|
||||
* Defines a watcher to execute a gulp orchestration to recompile a TypeScript project.
|
||||
* @param {string} projectSpec
|
||||
* @param {WatchCallback | string[] | CompileOptions} [options]
|
||||
* @param {WatchCallback | string[]} [tasks]
|
||||
* @param {WatchCallback} [callback]
|
||||
*/
|
||||
function watch(projectSpec, options, tasks, callback) {
|
||||
if (typeof tasks === "function") callback = tasks, tasks = /**@type {string[] | undefined}*/(undefined);
|
||||
if (typeof options === "function") callback = options, tasks = /**@type {string[] | undefined}*/(undefined), options = /**@type {CompileOptions | undefined}*/(undefined);
|
||||
if (Array.isArray(options)) tasks = options, options = /**@type {CompileOptions | undefined}*/(undefined);
|
||||
const resolvedOptions = resolveCompileOptions(options);
|
||||
const resolvedProjectSpec = resolveProjectSpec(projectSpec, resolvedOptions.paths, /*referrer*/ undefined);
|
||||
const projectGraph = getOrCreateProjectGraph(resolvedProjectSpec, resolvedOptions.paths);
|
||||
projectGraph.isRoot = true;
|
||||
ensureWatcher(projectGraph, resolvedOptions, tasks, callback);
|
||||
}
|
||||
exports.watch = watch;
|
||||
|
||||
/**
|
||||
* Adds a named alias for a TypeScript language service path
|
||||
* @param {string} alias An alias for a TypeScript version.
|
||||
@@ -138,7 +161,7 @@ exports.addTypeScript = addTypeScript;
|
||||
* @param {string} projectSpec The path to a tsconfig.json file or its containing directory.
|
||||
* @param {string} flattenedProjectSpec The output path for the flattened tsconfig.json file.
|
||||
* @param {FlattenOptions} [options] Options used to flatten a project hierarchy.
|
||||
*
|
||||
*
|
||||
* @typedef FlattenOptions
|
||||
* @property {string} [cwd] The path to use for the current working directory. Defaults to `process.cwd()`.
|
||||
* @property {CompilerOptions} [compilerOptions] Compiler option overrides.
|
||||
@@ -167,7 +190,7 @@ function flatten(projectSpec, flattenedProjectSpec, options = {}) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ProjectGraph} projectGraph
|
||||
* @param {ProjectGraph} projectGraph
|
||||
*/
|
||||
function recur(projectGraph) {
|
||||
if (skipProjects.has(projectGraph)) return;
|
||||
@@ -190,14 +213,14 @@ exports.flatten = flatten;
|
||||
* @param {string} typescript An unresolved module specifier to a TypeScript version.
|
||||
* @param {ResolvedPathOptions} paths Paths used to resolve `typescript`.
|
||||
* @returns {ResolvedTypeScript}
|
||||
*
|
||||
*
|
||||
* @typedef {string & {_isResolvedTypeScript: never}} ResolvedTypeScriptSpec
|
||||
*
|
||||
*
|
||||
* @typedef ResolvedTypeScript
|
||||
* @property {ResolvedTypeScriptSpec} typescript
|
||||
* @property {string} [alias]
|
||||
*/
|
||||
function resolveTypeScript(typescript, paths) {
|
||||
function resolveTypeScript(typescript = "default", paths) {
|
||||
let alias;
|
||||
while (typescriptAliasMap.has(typescript)) {
|
||||
({ typescript, alias, paths } = typescriptAliasMap.get(typescript));
|
||||
@@ -226,31 +249,34 @@ function getTaskNameSuffix(typescript, paths) {
|
||||
}
|
||||
|
||||
/** @type {ResolvedPathOptions} */
|
||||
const defaultPaths = { cwd: process.cwd(), base: process.cwd() };
|
||||
const defaultPaths = (() => {
|
||||
const cwd = /**@type {AbsolutePath}*/(normalizeSlashes(process.cwd()));
|
||||
return { cwd, base: cwd };
|
||||
})();
|
||||
|
||||
/**
|
||||
* @param {PathOptions | undefined} options Path options to resolve and normalize.
|
||||
* @returns {ResolvedPathOptions}
|
||||
*
|
||||
*
|
||||
* @typedef PathOptions
|
||||
* @property {string} [cwd] The path to use for the current working directory. Defaults to `process.cwd()`.
|
||||
* @property {string} [base] The path to use as the base for relative paths. Defaults to `cwd`.
|
||||
*
|
||||
*
|
||||
* @typedef ResolvedPathOptions
|
||||
* @property {string} cwd The path to use for the current working directory. Defaults to `process.cwd()`.
|
||||
* @property {string} base The path to use as the base for relative paths. Defaults to `cwd`.
|
||||
* @property {AbsolutePath} cwd The path to use for the current working directory. Defaults to `process.cwd()`.
|
||||
* @property {AbsolutePath} base The path to use as the base for relative paths. Defaults to `cwd`.
|
||||
*/
|
||||
function resolvePathOptions(options) {
|
||||
const cwd = options && options.cwd ? path.resolve(process.cwd(), options.cwd) : process.cwd();
|
||||
const base = options && options.base ? path.resolve(cwd, options.base) : cwd;
|
||||
const cwd = options && options.cwd ? resolvePath(defaultPaths.cwd, options.cwd) : defaultPaths.cwd;
|
||||
const base = options && options.base ? resolvePath(cwd, options.base) : cwd;
|
||||
return cwd === defaultPaths.cwd && base === defaultPaths.base ? defaultPaths : { cwd, base };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ProjectOptions} [options]
|
||||
* @returns {ResolvedProjectOptions}
|
||||
*
|
||||
* @typedef ResolvedProjectOptions
|
||||
* @param {CompileOptions} [options]
|
||||
* @returns {ResolvedCompileOptions}
|
||||
*
|
||||
* @typedef ResolvedCompileOptions
|
||||
* @property {ResolvedPathOptions} paths
|
||||
* @property {ResolvedTypeScript} typescript A resolved reference to a TypeScript implementation.
|
||||
* @property {Hook} [js] Pipeline hook for .js file outputs.
|
||||
@@ -259,9 +285,9 @@ function resolvePathOptions(options) {
|
||||
* @property {boolean} [force] Force recompilation (no up-to-date check).
|
||||
* @property {boolean} [inProcess] Indicates whether to run gulp-typescript in-process or out-of-process (default).
|
||||
*/
|
||||
function resolveProjectOptions(options = {}) {
|
||||
function resolveCompileOptions(options = {}) {
|
||||
const paths = resolvePathOptions(options);
|
||||
const typescript = resolveTypeScript(options.typescript || "default", paths);
|
||||
const typescript = resolveTypeScript(options.typescript, paths);
|
||||
return {
|
||||
paths,
|
||||
typescript,
|
||||
@@ -274,13 +300,13 @@ function resolveProjectOptions(options = {}) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ResolvedProjectOptions} left
|
||||
* @param {ResolvedProjectOptions} right
|
||||
* @returns {ResolvedProjectOptions}
|
||||
* @param {ResolvedCompileOptions} left
|
||||
* @param {ResolvedCompileOptions} right
|
||||
* @returns {ResolvedCompileOptions}
|
||||
*/
|
||||
function mergeProjectOptions(left, right) {
|
||||
function mergeCompileOptions(left, right) {
|
||||
if (left.typescript !== right.typescript) throw new Error("Cannot merge project options targeting different TypeScript packages");
|
||||
if (tryReuseProjectOptions(left, right)) return left;
|
||||
if (tryReuseCompileOptions(left, right)) return left;
|
||||
return {
|
||||
paths: left.paths,
|
||||
typescript: left.typescript,
|
||||
@@ -293,10 +319,10 @@ function mergeProjectOptions(left, right) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ResolvedProjectOptions} left
|
||||
* @param {ResolvedProjectOptions} right
|
||||
* @param {ResolvedCompileOptions} left
|
||||
* @param {ResolvedCompileOptions} right
|
||||
*/
|
||||
function tryReuseProjectOptions(left, right) {
|
||||
function tryReuseCompileOptions(left, right) {
|
||||
return left === right
|
||||
|| left.js === (right.js || left.js)
|
||||
&& left.dts === (right.dts || left.dts)
|
||||
@@ -309,11 +335,13 @@ function tryReuseProjectOptions(left, right) {
|
||||
* @param {ResolvedProjectSpec} projectSpec
|
||||
* @param {ResolvedPathOptions} paths
|
||||
* @returns {UnqualifiedProjectName}
|
||||
*
|
||||
*
|
||||
* @typedef {string & {_isUnqualifiedProjectName:never}} UnqualifiedProjectName
|
||||
*/
|
||||
function getUnqualifiedProjectName(projectSpec, paths) {
|
||||
return /**@type {UnqualifiedProjectName}*/(normalizeSlashes(path.relative(paths.base, projectSpec)));
|
||||
let projectName = path.relative(paths.base, projectSpec);
|
||||
if (path.basename(projectName) === "tsconfig.json") projectName = path.dirname(projectName);
|
||||
return /**@type {UnqualifiedProjectName}*/(normalizeSlashes(projectName));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -321,16 +349,16 @@ function getUnqualifiedProjectName(projectSpec, paths) {
|
||||
* @param {ResolvedPathOptions} paths
|
||||
* @param {ResolvedTypeScript} typescript
|
||||
* @returns {QualifiedProjectName}
|
||||
*
|
||||
*
|
||||
* @typedef {string & {_isQualifiedProjectName:never}} QualifiedProjectName
|
||||
*/
|
||||
function getQualifiedProjectName(projectName, paths, typescript) {
|
||||
return /**@type {QualifiedProjectName}*/(projectName + getTaskNameSuffix(typescript, paths));
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @typedef {import("../../lib/typescript").ParseConfigFileHost} ParseConfigFileHost
|
||||
* @type {ParseConfigFileHost}
|
||||
* @type {ParseConfigFileHost}
|
||||
*/
|
||||
const parseConfigFileHost = {
|
||||
useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames,
|
||||
@@ -342,11 +370,11 @@ const parseConfigFileHost = {
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} [cwd]
|
||||
* @param {AbsolutePath} [cwd]
|
||||
* @returns {ParseConfigFileHost}
|
||||
*/
|
||||
function getParseConfigFileHost(cwd) {
|
||||
if (!cwd || cwd === process.cwd()) return parseConfigFileHost;
|
||||
if (!cwd || cwd === defaultPaths.cwd) return parseConfigFileHost;
|
||||
return {
|
||||
useCaseSensitiveFileNames: parseConfigFileHost.useCaseSensitiveFileNames,
|
||||
fileExists: parseConfigFileHost.fileExists,
|
||||
@@ -363,14 +391,21 @@ function getParseConfigFileHost(cwd) {
|
||||
* @returns {ProjectGraph}
|
||||
*
|
||||
* @typedef ProjectGraph
|
||||
* @property {ResolvedPathOptions} paths
|
||||
* @property {ResolvedProjectSpec} projectSpec The fully qualified path to the tsconfig.json of the project
|
||||
* @property {UnqualifiedProjectName} projectName The relative project name, excluding any TypeScript suffix.
|
||||
* @property {string} projectDirectory The fully qualified path to the project directory.
|
||||
* @property {AbsolutePath} projectDirectory The fully qualified path to the project directory.
|
||||
* @property {ParsedCommandLine} project The parsed tsconfig.json file.
|
||||
* @property {ProjectGraphReference[]} references An array of project references.
|
||||
* @property {Set<ProjectGraph>} referrers An array of referring projects.
|
||||
* @property {Set<AbsolutePath>} inputs A set of compilation inputs.
|
||||
* @property {Set<AbsolutePath>} outputs A set of compilation outputs.
|
||||
* @property {Map<ResolvedTypeScriptSpec, ProjectGraphConfiguration>} configurations TypeScript-specific configurations for the project.
|
||||
* @property {boolean} cleanTaskCreated A value indicating whether a `clean:` task has been created for this project (not dependent on TypeScript version).
|
||||
*
|
||||
* @property {boolean} watcherCreated A value indicating whether a watcher has been created for this project.
|
||||
* @property {boolean} isRoot The project graph is a root project reference.
|
||||
* @property {Set<Watcher>} [allWatchers] Tasks to execute when the compilation has completed after being triggered by a watcher.
|
||||
*
|
||||
* @typedef ProjectGraphReference
|
||||
* @property {ProjectGraph} source The referring project.
|
||||
* @property {ProjectGraph} target The referenced project.
|
||||
@@ -378,15 +413,22 @@ function getParseConfigFileHost(cwd) {
|
||||
function getOrCreateProjectGraph(projectSpec, paths) {
|
||||
let projectGraph = projectGraphCache.get(projectSpec);
|
||||
if (!projectGraph) {
|
||||
const project = ts.getParsedCommandLineOfConfigFile(projectSpec, {}, getParseConfigFileHost(paths.cwd));
|
||||
const project = parseProject(projectSpec, paths);
|
||||
const projectDirectory = parentDirectory(projectSpec);
|
||||
projectGraph = {
|
||||
paths,
|
||||
projectSpec,
|
||||
projectName: getUnqualifiedProjectName(projectSpec, paths),
|
||||
projectDirectory: path.dirname(projectSpec),
|
||||
projectDirectory,
|
||||
project,
|
||||
references: [],
|
||||
referrers: new Set(),
|
||||
inputs: new Set(project.fileNames.map(file => resolvePath(projectDirectory, file))),
|
||||
outputs: new Set(ts.getAllProjectOutputs(project).map(file => resolvePath(projectDirectory, file))),
|
||||
configurations: new Map(),
|
||||
cleanTaskCreated: false
|
||||
cleanTaskCreated: false,
|
||||
watcherCreated: false,
|
||||
isRoot: false
|
||||
};
|
||||
projectGraphCache.set(projectSpec, projectGraph);
|
||||
if (project.projectReferences) {
|
||||
@@ -395,6 +437,7 @@ function getOrCreateProjectGraph(projectSpec, paths) {
|
||||
const referencedProject = getOrCreateProjectGraph(resolvedProjectSpec, paths);
|
||||
const reference = { source: projectGraph, target: referencedProject };
|
||||
projectGraph.references.push(reference);
|
||||
referencedProject.referrers.add(projectGraph);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -403,13 +446,56 @@ function getOrCreateProjectGraph(projectSpec, paths) {
|
||||
|
||||
/**
|
||||
* @param {ProjectGraph} projectGraph
|
||||
* @param {ResolvedProjectOptions} resolvedOptions
|
||||
* @param {ParsedCommandLine} parsedProject
|
||||
*/
|
||||
function updateProjectGraph(projectGraph, parsedProject) {
|
||||
projectGraph.project = parsedProject;
|
||||
projectGraph.inputs = new Set(projectGraph.project.fileNames.map(file => resolvePath(projectGraph.projectDirectory, file)));
|
||||
projectGraph.outputs = new Set(ts.getAllProjectOutputs(projectGraph.project).map(file => resolvePath(projectGraph.projectDirectory, file)));
|
||||
|
||||
// Update project references.
|
||||
const oldReferences = new Set(projectGraph.references.map(ref => ref.target));
|
||||
projectGraph.references = [];
|
||||
if (projectGraph.project.projectReferences) {
|
||||
for (const projectReference of projectGraph.project.projectReferences) {
|
||||
const resolvedProjectSpec = resolveProjectSpec(projectReference.path, projectGraph.paths, projectGraph);
|
||||
const referencedProject = getOrCreateProjectGraph(resolvedProjectSpec, projectGraph.paths);
|
||||
const reference = { source: projectGraph, target: referencedProject };
|
||||
projectGraph.references.push(reference);
|
||||
referencedProject.referrers.add(projectGraph);
|
||||
oldReferences.delete(referencedProject);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove project references that have been removed from the project
|
||||
for (const referencedProject of oldReferences) {
|
||||
referencedProject.referrers.delete(projectGraph);
|
||||
// If there are no more references to this project and the project was not directly requested,
|
||||
// remove it from the cache.
|
||||
if (referencedProject.referrers.size === 0 && !referencedProject.isRoot) {
|
||||
projectGraphCache.delete(referencedProject.projectSpec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ResolvedProjectSpec} projectSpec
|
||||
* @param {ResolvedPathOptions} paths
|
||||
*/
|
||||
function parseProject(projectSpec, paths) {
|
||||
return ts.getParsedCommandLineOfConfigFile(projectSpec, {}, getParseConfigFileHost(paths.cwd));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ProjectGraph} projectGraph
|
||||
* @param {ResolvedCompileOptions} resolvedOptions
|
||||
* @returns {ProjectGraphConfiguration}
|
||||
*
|
||||
* @typedef ProjectGraphConfiguration
|
||||
* @property {QualifiedProjectName} projectName
|
||||
* @property {ResolvedProjectOptions} resolvedOptions
|
||||
* @property {boolean} compileTaskCreated
|
||||
* @property {ResolvedCompileOptions} resolvedOptions
|
||||
* @property {boolean} compileTaskCreated A value indicating whether a `compile:` task has been created for this project.
|
||||
* @property {Set<Watcher>} [watchers] Tasks to execute when the compilation has completed after being triggered by a watcher.
|
||||
*/
|
||||
function getOrCreateProjectGraphConfiguration(projectGraph, resolvedOptions) {
|
||||
let projectGraphConfig = projectGraph.configurations.get(resolvedOptions.typescript.typescript);
|
||||
@@ -424,18 +510,56 @@ function getOrCreateProjectGraphConfiguration(projectGraph, resolvedOptions) {
|
||||
return projectGraphConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a series of path steps as a normalized, canonical, and absolute path.
|
||||
* @param {AbsolutePath} basePath
|
||||
* @param {...string} paths
|
||||
* @returns {AbsolutePath}
|
||||
*
|
||||
* @typedef {string & {_isResolvedPath:never}} AbsolutePath
|
||||
*/
|
||||
function resolvePath(basePath, ...paths) {
|
||||
return /**@type {AbsolutePath}*/(normalizeSlashes(path.resolve(basePath, ...paths)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AbsolutePath} from
|
||||
* @param {AbsolutePath} to
|
||||
* @returns {Path}
|
||||
*
|
||||
* @typedef {string & {_isRelativePath:never}} RelativePath
|
||||
* @typedef {RelativePath | AbsolutePath} Path
|
||||
*/
|
||||
function relativePath(from, to) {
|
||||
let relativePath = normalizeSlashes(path.relative(from, to));
|
||||
if (!relativePath) relativePath = ".";
|
||||
if (path.isAbsolute(relativePath)) return /**@type {AbsolutePath}*/(relativePath);
|
||||
if (relativePath.charAt(0) !== ".") relativePath = "./" + relativePath;
|
||||
return /**@type {RelativePath}*/(relativePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AbsolutePath} file
|
||||
* @returns {AbsolutePath}
|
||||
*/
|
||||
function parentDirectory(file) {
|
||||
const dirname = path.dirname(file);
|
||||
if (!dirname || dirname === file) return file;
|
||||
return /**@type {AbsolutePath}*/(normalizeSlashes(dirname));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} projectSpec
|
||||
* @param {ResolvedPathOptions} paths
|
||||
* @param {ProjectGraph | undefined} referrer
|
||||
* @returns {ResolvedProjectSpec}
|
||||
*
|
||||
* @typedef {string & {_isResolvedProjectSpec: never}} ResolvedProjectSpec
|
||||
*
|
||||
* @typedef {AbsolutePath & {_isResolvedProjectSpec: never}} ResolvedProjectSpec
|
||||
*/
|
||||
function resolveProjectSpec(projectSpec, paths, referrer) {
|
||||
projectSpec = path.resolve(paths.cwd, referrer && referrer.projectDirectory || "", projectSpec);
|
||||
if (!ts.sys.fileExists(projectSpec)) projectSpec = path.join(projectSpec, "tsconfig.json");
|
||||
return /**@type {ResolvedProjectSpec}*/(normalizeSlashes(projectSpec));
|
||||
let projectPath = resolvePath(paths.cwd, referrer && referrer.projectDirectory || "", projectSpec);
|
||||
if (!ts.sys.fileExists(projectPath)) projectPath = resolvePath(paths.cwd, projectPath, "tsconfig.json");
|
||||
return /**@type {ResolvedProjectSpec}*/(normalizeSlashes(projectPath));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -443,24 +567,24 @@ function resolveProjectSpec(projectSpec, paths, referrer) {
|
||||
* @param {ResolvedPathOptions} paths
|
||||
*/
|
||||
function resolveDestPath(projectGraph, paths) {
|
||||
/** @type {string} */
|
||||
/** @type {AbsolutePath} */
|
||||
let destPath = projectGraph.projectDirectory;
|
||||
if (projectGraph.project.options.outDir) {
|
||||
destPath = path.resolve(paths.cwd, destPath, projectGraph.project.options.outDir);
|
||||
destPath = resolvePath(paths.cwd, destPath, projectGraph.project.options.outDir);
|
||||
}
|
||||
else if (projectGraph.project.options.outFile || projectGraph.project.options.out) {
|
||||
destPath = path.dirname(path.resolve(paths.cwd, destPath, projectGraph.project.options.outFile || projectGraph.project.options.out));
|
||||
destPath = parentDirectory(resolvePath(paths.cwd, destPath, projectGraph.project.options.outFile || projectGraph.project.options.out));
|
||||
}
|
||||
return normalizeSlashes(path.relative(paths.base, destPath));
|
||||
return relativePath(paths.base, destPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ProjectGraph} projectGraph
|
||||
* @param {ResolvedProjectOptions} options
|
||||
* @param {ResolvedCompileOptions} options
|
||||
*/
|
||||
function ensureCompileTask(projectGraph, options) {
|
||||
const projectGraphConfig = getOrCreateProjectGraphConfiguration(projectGraph, options);
|
||||
projectGraphConfig.resolvedOptions = options = mergeProjectOptions(options, options);
|
||||
projectGraphConfig.resolvedOptions = options = mergeCompileOptions(options, options);
|
||||
if (!projectGraphConfig.compileTaskCreated) {
|
||||
const deps = makeProjectReferenceCompileTasks(projectGraph, options.typescript, options.paths);
|
||||
compilationGulp.task(compileTaskName(projectGraph, options.typescript), deps, () => {
|
||||
@@ -528,7 +652,300 @@ function makeProjectReferenceCleanTasks(projectGraph) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ProjectGraph} projectGraph
|
||||
* @param {ProjectGraph} projectGraph
|
||||
* @param {ResolvedCompileOptions} options
|
||||
* @param {string[]} [tasks]
|
||||
* @param {(err?: any) => void} [callback]
|
||||
*
|
||||
* @typedef Watcher
|
||||
* @property {string[]} [tasks]
|
||||
* @property {(err?: any) => void} [callback]
|
||||
*
|
||||
* @typedef WatcherRegistration
|
||||
* @property {() => void} end
|
||||
*/
|
||||
function ensureWatcher(projectGraph, options, tasks, callback) {
|
||||
ensureCompileTask(projectGraph, options);
|
||||
if (!projectGraph.watcherCreated) {
|
||||
projectGraph.watcherCreated = true;
|
||||
makeProjectReferenceWatchers(projectGraph, options.typescript, options.paths);
|
||||
createWatcher(projectGraph, options, () => {
|
||||
for (const config of projectGraph.configurations.values()) {
|
||||
const taskName = compileTaskName(projectGraph, config.resolvedOptions.typescript);
|
||||
const task = compilationGulp.tasks[taskName];
|
||||
if (!task) continue;
|
||||
possiblyTriggerRecompilation(config, task);
|
||||
}
|
||||
});
|
||||
}
|
||||
if ((tasks && tasks.length) || callback) {
|
||||
const projectGraphConfig = getOrCreateProjectGraphConfiguration(projectGraph, options);
|
||||
if (!projectGraphConfig.watchers) projectGraphConfig.watchers = new Set();
|
||||
if (!projectGraph.allWatchers) projectGraph.allWatchers = new Set();
|
||||
|
||||
/** @type {Watcher} */
|
||||
const watcher = { tasks, callback };
|
||||
projectGraphConfig.watchers.add(watcher);
|
||||
projectGraph.allWatchers.add(watcher);
|
||||
|
||||
/** @type {WatcherRegistration} */
|
||||
const registration = {
|
||||
end() {
|
||||
projectGraphConfig.watchers.delete(watcher);
|
||||
projectGraph.allWatchers.delete(watcher);
|
||||
}
|
||||
};
|
||||
return registration;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ProjectGraphConfiguration} config
|
||||
* @param {import("orchestrator").Task} task
|
||||
*/
|
||||
function possiblyTriggerRecompilation(config, task) {
|
||||
// if any of the task's dependencies are still running, wait until they are complete.
|
||||
for (const dep of task.dep) {
|
||||
if (compilationGulp.tasks[dep].running) {
|
||||
setTimeout(possiblyTriggerRecompilation, 50, config, task);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
triggerRecompilation(task, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("orchestrator").Task} task
|
||||
* @param {ProjectGraphConfiguration} config
|
||||
*/
|
||||
function triggerRecompilation(task, config) {
|
||||
compilationGulp._resetTask(task);
|
||||
if (config.watchers && config.watchers.size) {
|
||||
compilationGulp.fork().start(task.name, () => {
|
||||
/** @type {Set<string>} */
|
||||
const taskNames = new Set();
|
||||
/** @type {((err?: any) => void)[]} */
|
||||
const callbacks = [];
|
||||
for (const { tasks, callback } of config.watchers) {
|
||||
if (tasks) for (const task of tasks) taskNames.add(task);
|
||||
if (callback) callbacks.push(callback);
|
||||
}
|
||||
if (taskNames.size) {
|
||||
gulp.start([...taskNames], error => {
|
||||
for (const callback of callbacks) callback(error);
|
||||
});
|
||||
}
|
||||
else {
|
||||
for (const callback of callbacks) callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
compilationGulp.fork(/*verbose*/ true).start(task.name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ProjectGraph} projectGraph
|
||||
* @param {ResolvedTypeScript} typescript
|
||||
* @param {ResolvedPathOptions} paths
|
||||
*/
|
||||
function makeProjectReferenceWatchers(projectGraph, typescript, paths) {
|
||||
for (const { target } of projectGraph.references) {
|
||||
ensureWatcher(target, { paths, typescript });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ProjectGraph} projectGraph
|
||||
* @param {ResolvedCompileOptions} options
|
||||
* @param {() => void} callback
|
||||
*/
|
||||
function createWatcher(projectGraph, options, callback) {
|
||||
let projectRemoved = false;
|
||||
let patterns = collectWatcherPatterns(projectGraph.projectSpec, projectGraph.project, projectGraph);
|
||||
let watcher = /**@type {GulpWatcher}*/ (gulp.watch(patterns, { cwd: projectGraph.projectDirectory }, onWatchEvent));
|
||||
|
||||
/**
|
||||
* @param {WatchEvent} event
|
||||
*/
|
||||
function onWatchEvent(event) {
|
||||
const file = resolvePath(options.paths.cwd, event.path);
|
||||
if (file === projectGraph.projectSpec) {
|
||||
onProjectWatchEvent(event);
|
||||
}
|
||||
else {
|
||||
onInputOrOutputChanged(file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {WatchEvent} event
|
||||
*/
|
||||
function onProjectWatchEvent(event) {
|
||||
if (event.type === "renamed" || event.type === "deleted") {
|
||||
onProjectRenamedOrDeleted();
|
||||
}
|
||||
else {
|
||||
onProjectCreatedOrModified();
|
||||
}
|
||||
}
|
||||
|
||||
function onProjectRenamedOrDeleted() {
|
||||
// stop listening for file changes and wait for the project to be created again
|
||||
projectRemoved = true;
|
||||
watcher.end();
|
||||
watcher = /**@type {GulpWatcher}*/ (gulp.watch([projectGraph.projectSpec], onWatchEvent));
|
||||
}
|
||||
|
||||
function onProjectCreatedOrModified() {
|
||||
const newParsedProject = parseProject(projectGraph.projectSpec, options.paths);
|
||||
const newPatterns = collectWatcherPatterns(projectGraph.projectSpec, newParsedProject, projectGraph);
|
||||
if (projectRemoved || !sameValues(patterns, newPatterns)) {
|
||||
projectRemoved = false;
|
||||
watcher.end();
|
||||
updateProjectGraph(projectGraph, newParsedProject);
|
||||
// Ensure we catch up with any added projects
|
||||
for (const config of projectGraph.configurations.values()) {
|
||||
if (config.watchers) {
|
||||
makeProjectReferenceWatchers(projectGraph, config.resolvedOptions.typescript, config.resolvedOptions.paths);
|
||||
}
|
||||
}
|
||||
patterns = newPatterns;
|
||||
watcher = /**@type {GulpWatcher}*/ (gulp.watch(patterns, onWatchEvent));
|
||||
}
|
||||
onProjectInvalidated();
|
||||
}
|
||||
|
||||
function onProjectInvalidated() {
|
||||
callback();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AbsolutePath} file
|
||||
*/
|
||||
function onInputOrOutputChanged(file) {
|
||||
if (projectGraph.inputs.has(file) ||
|
||||
projectGraph.references.some(ref => ref.target.outputs.has(file))) {
|
||||
onProjectInvalidated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ResolvedProjectSpec} projectSpec
|
||||
* @param {ParsedCommandLine} parsedProject
|
||||
* @param {ProjectGraph} projectGraph
|
||||
*/
|
||||
function collectWatcherPatterns(projectSpec, parsedProject, projectGraph) {
|
||||
const configFileSpecs = parsedProject.configFileSpecs;
|
||||
|
||||
// NOTE: we do not currently handle files from `/// <reference />` tags
|
||||
const patterns = /**@type {string[]} */([]);
|
||||
|
||||
// Add the project contents.
|
||||
if (configFileSpecs) {
|
||||
addIncludeSpecs(patterns, configFileSpecs.validatedIncludeSpecs);
|
||||
addExcludeSpecs(patterns, configFileSpecs.validatedExcludeSpecs);
|
||||
addIncludeSpecs(patterns, configFileSpecs.filesSpecs);
|
||||
}
|
||||
else {
|
||||
addWildcardDirectories(patterns, parsedProject.wildcardDirectories);
|
||||
addIncludeSpecs(patterns, parsedProject.fileNames);
|
||||
}
|
||||
|
||||
// Add the project itself.
|
||||
addIncludeSpec(patterns, projectSpec);
|
||||
|
||||
// TODO: Add the project base.
|
||||
// addExtendsSpec(patterns, project.raw && project.raw.extends);
|
||||
|
||||
// Add project reference outputs.
|
||||
addProjectReferences(patterns, parsedProject.projectReferences);
|
||||
|
||||
return patterns;
|
||||
|
||||
/**
|
||||
* @param {string[]} patterns
|
||||
* @param {string | undefined} includeSpec
|
||||
*/
|
||||
function addIncludeSpec(patterns, includeSpec) {
|
||||
if (!includeSpec) return;
|
||||
patterns.push(includeSpec);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} patterns
|
||||
* @param {ReadonlyArray<string> | undefined} includeSpecs
|
||||
*/
|
||||
function addIncludeSpecs(patterns, includeSpecs) {
|
||||
if (!includeSpecs) return;
|
||||
for (const includeSpec of includeSpecs) {
|
||||
addIncludeSpec(patterns, includeSpec);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} patterns
|
||||
* @param {string | undefined} excludeSpec
|
||||
*/
|
||||
function addExcludeSpec(patterns, excludeSpec) {
|
||||
if (!excludeSpec) return;
|
||||
patterns.push("!" + excludeSpec);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} patterns
|
||||
* @param {ReadonlyArray<string> | undefined} excludeSpecs
|
||||
*/
|
||||
function addExcludeSpecs(patterns, excludeSpecs) {
|
||||
if (!excludeSpecs) return;
|
||||
for (const excludeSpec of excludeSpecs) {
|
||||
addExcludeSpec(patterns, excludeSpec);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} patterns
|
||||
* @param {ts.MapLike<ts.WatchDirectoryFlags> | undefined} wildcardDirectories
|
||||
*/
|
||||
function addWildcardDirectories(patterns, wildcardDirectories) {
|
||||
if (!wildcardDirectories) return;
|
||||
for (const dirname of Object.keys(wildcardDirectories)) {
|
||||
const flags = wildcardDirectories[dirname];
|
||||
patterns.push(path.join(dirname, flags & ts.WatchDirectoryFlags.Recursive ? "**" : "", "*"));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add the project base
|
||||
// /**
|
||||
// * @param {string[]} patterns
|
||||
// * @param {string | undefined} base
|
||||
// */
|
||||
// function addExtendsSpec(patterns, base) {
|
||||
// if (!base) return;
|
||||
// addIncludeSpec(patterns, base);
|
||||
// }
|
||||
|
||||
/**
|
||||
* @param {string[]} patterns
|
||||
* @param {ReadonlyArray<ProjectReference>} projectReferences
|
||||
*/
|
||||
function addProjectReferences(patterns, projectReferences) {
|
||||
if (!projectReferences) return;
|
||||
for (const projectReference of projectReferences) {
|
||||
const resolvedProjectSpec = resolveProjectSpec(projectReference.path, projectGraph.paths, projectGraph);
|
||||
const referencedProject = getOrCreateProjectGraph(resolvedProjectSpec, projectGraph.paths);
|
||||
for (const output of referencedProject.outputs) {
|
||||
patterns.push(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ProjectGraph} projectGraph
|
||||
* @param {ResolvedTypeScript} typescript
|
||||
*/
|
||||
function compileTaskName(projectGraph, typescript) {
|
||||
@@ -536,7 +953,7 @@ function compileTaskName(projectGraph, typescript) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ProjectGraph} projectGraph
|
||||
* @param {ProjectGraph} projectGraph
|
||||
*/
|
||||
function cleanTaskName(projectGraph) {
|
||||
return `clean:${projectGraph.projectName}`;
|
||||
@@ -558,7 +975,32 @@ function isPath(moduleSpec) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import("../../lib/typescript").ParsedCommandLine & { options: CompilerOptions }} ParsedCommandLine
|
||||
* @template T
|
||||
* @param {ReadonlyArray<T>} left
|
||||
* @param {ReadonlyArray<T>} right
|
||||
*/
|
||||
function sameValues(left, right) {
|
||||
if (left === right) return true;
|
||||
if (left.length !== right.length) return false;
|
||||
for (let i = 0; i < left.length; i++) {
|
||||
if (left[i] !== right[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import("../../lib/typescript").ParsedCommandLine & { options: CompilerOptions, configFileSpecs?: ConfigFileSpecs }} ParsedCommandLine
|
||||
* @typedef {import("../../lib/typescript").CompilerOptions & { configFilePath?: string }} CompilerOptions
|
||||
* @typedef {import("../../lib/typescript").ProjectReference} ProjectReference
|
||||
* @typedef {import("gulp").WatchEvent} WatchEvent
|
||||
* @typedef {import("gulp").WatchCallback} WatchCallback
|
||||
* @typedef {NodeJS.EventEmitter & { end(): void, add(files: string | string[], done?: () => void): void, remove(file: string): void }} GulpWatcher
|
||||
*
|
||||
* @typedef ConfigFileSpecs
|
||||
* @property {ReadonlyArray<string> | undefined} filesSpecs
|
||||
* @property {ReadonlyArray<ProjectReference> | undefined} referenceSpecs
|
||||
* @property {ReadonlyArray<string> | undefined} validatedIncludeSpecs
|
||||
* @property {ReadonlyArray<string> | undefined} validatedExcludeSpecs
|
||||
* @property {ts.MapLike<ts.WatchDirectoryFlags>} wildcardDirectories
|
||||
*/
|
||||
void 0;
|
||||
Reference in New Issue
Block a user