Description
This is a follow-up to #3469. Most of this work is already done at https://github.com/RyanCavanaugh/tsbuild and just needs to be ported into the TypeScript code base.
"Build Mode" for TypeScript
TypeScript currently (soon...) supports project references. However, if an upstream project is unbuilt, the developer will see an error telling them to build that project first.
There is a clear need for a build-scheduling mode for TypeScript that intelligently builds upstream projects.
In build mode (tsc -b
), tsc
will determine which other builds need to happen, order them correctly, and perform that build.
One way to think of it:
"tsc -p" : "tsc -b" :: "csc" : "msbuild"
or
"tsc -p" : "tsc -b" :: "gcc" : "make"
Terminology
- Project: A project is a single compilation unit, i.e. result of TypeScript's internal
createProgram
call, originating from atsconfig
file, with a defined set of inputs and outputs - Upstream/downstream: A project depends on upstream projects; projects which depend on it are downstream
Commandline Options
The following options are supported under -b
:
--clean
: Remove all output files from output folders and exit--force
: Force all projects to be rebuilt, even if they appear to be up-to-date--dry
: Do nothing, but display which projects would be built- Special case:
--dry --clean
displays which files would be deleted
- Special case:
--verbose
: Include verbose logging- (What else do we need?)
The following regular tsc
options are available when -b
is specified.
Using any other non--b
-specific flag is an error.
--help
--watch
Input Specification
The -b
switch takes any number of arguments:
tsc -b [projectOrGlob1] [projectOrGlob2] [projectOrGlobN...]
Each argument can be:
- A filename pointing to a
tsconfig.json
file (though it may have a different name, e.g.mybuildcfg.json
) - A directory name, which is assumed to contain a
tsconfig.json
file - A glob that matches filenames
What happens next?
- If
tsc -b
is invoked with zero arguments, then- If a
tsconfig.json
file exists in the current directory, it is the only input - Otherwise, the current directory is recursively enumerated (excluding
node_modules
) and anytsconfig.json
files found are added to the input
- If a
- Otherwise, the input is the specified list of configs or globs
After determining the set of inputs from the commandline, each input is opened and its references are added to the input list (if it wasn't already present). This repeats until no new inputs are found.
If any input cannot be found, an error is issued and nothing happens.
Inputs are then topologically sorted according to their dependencies and built in that order (upstream projects first).
Circular dependencies
Circular dependencies are allowed as long as at least one dependency in the circularity-causing cycle is marked "circular": true
.
For the purposes of the topological sorting of inputs, edges which would cause circularity are ignored. Note that this makes the final build order dependent on the initial ordering of inputs, which may be in turn dependent on file system enumeration order. Developers using circular dependencies should observe their build behavior carefully to ensure it's sufficiently deterministic.
Errors
When a project has a configuration, syntactic, or semantic error, no files are emitted.
Sidebar: Why? If an upstream project has errors but emits anyway, a downstream build will see the project as "up to date" and the errors will be hidden until a clean build or edit to the upstream project occurs. This is potentially fixable; we could write out the errors to disk and re-issue them on subsequent builds. It's not clear if this is desirable or not.
Why have two modes (-p and -b) ?
Users running tsc -p
may sometimes see errors that basically amount to "You should run tsc -b
instead". So why not just make -p
and -b
the same?
The reason is to prevent an O(n^2)
increase in build I/O for developers writing large solutions using msbuild
or similar tools. If msbuild
invokes tsc -p
on each project in a deep graph, then the files of the every project will get checked once for each downstream project.
For example, in a linear graph of 3 projects with 10 files each, tsc -p
on each project (in the correct order) will do 10 + 10 + 10 file checks, whereas tsc -b
would check 10 + 20 + 30 files.
Faster Rebuilds
During a build, tsc -b
will detect if it's writing an output that is (bytewise) identical to an existing disk artifact. This is an unchanged output.
When a downstream project would need to be rebuilt, but only depends on older or unchanged outputs, then it can be fast rebuilt.
A fast rebuild of a module compilation simply increments the timestamps of all the output files to the current time.
A fast rebuild of an outFile
compilation with upstream-changed prepend
s needs to break apart the .js
file on disk, use the bundleinfo
file to identify the correct offset, and re-output the JS file.
TODO: Consider cutting this feature? If all endpoints have small additions, this isn't a big perf hit
More to come...