Skip to content

"Build mode" for TypeScript #22997

Closed
Closed
@RyanCavanaugh

Description

@RyanCavanaugh

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 a tsconfig 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
  • --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 any tsconfig.json files found are added to the input
  • 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 prepends 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...

Metadata

Metadata

Assignees

Labels

CommittedThe team has roadmapped this issueSuggestionAn idea for TypeScript

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions