April 27, 2017·6 min read·engineeringtypescriptjavascriptstrictness

TypeScript 2.3 separates checking JavaScript from changing the build

TypeScript 2.3 adds opt-in JavaScript checking, a strict configuration preset, generic defaults, and downlevel iteration with explicit runtime requirements.

Adopting a type checker does not have to begin by replacing a JavaScript build pipeline. TypeScript 2.3 adds a checking mode for ordinary .js files that can run without emitting transformed code. Comments can supply type information where inference needs help. A project can therefore try the checker's diagnostics while continuing to execute JavaScript through the tools it already uses.

The April 27 release announcement calls this an experimental soft form of checking. It presents the capability alongside generic parameter defaults and the new strict option. These changes address different stages of adoption. An existing JavaScript project can ask for selected checks, while a new TypeScript project can start with a named group of stricter settings.

I find that flexibility useful in a web ecosystem with many established build arrangements. TypeScript's value includes its understanding of framework APIs, but obtaining that help should not automatically require changing every other tool. A checking-only configuration makes it possible to evaluate the actual diagnostics before adding another source transformation.

JavaScript files can opt into checking

The announcement's initial configuration combines allowJs with noEmit. The first admits JavaScript files to the project, while the second prevents output generation. An include pattern identifies the source directory. Adding // @ts-check to a JavaScript file then requests checking for that file.

{
  "compilerOptions": {
    "allowJs": true,
    "noEmit": true
  },
  "include": ["./src/"]
}

The configuration does not require renaming the source to .ts. A JSDoc parameter annotation can provide a string type, after which the checker can reject a misspelled string method. The release announcement uses the difference between tolowercase and toLowerCase to demonstrate that benefit. The comment adds information for analysis while leaving the file runnable as JavaScript.

This is a different migration unit from converting an entire repository. A file can participate in checking while its neighbors remain outside the opt-in comment mechanism. That can be useful when an existing application contains areas with different readiness for stricter analysis. The checker is observing the selected JavaScript, not requiring the application to be rewritten into another source format first.

For broader checking, checkJs enables the mode across the project instead of requiring a comment in every file. The announcement also supports // @ts-nocheck to exclude a file under that arrangement, and // @ts-ignore to suppress errors on the next line. Those controls offer different scopes, so a local suppression need not be confused with disabling analysis for an entire module.

The same project can choose to emit downlevel JavaScript as well. The release gives a configuration targeting ES5 with CommonJS modules and a separate output directory. That is an additional choice, not a requirement of JavaScript checking. Separating these choices helps explain why allowJs by itself does not mean that a team has adopted a particular deployment pipeline.

The announcement presents this analysis as complementary to existing linters such as ESLint. A linter can continue to enforce its own rules while TypeScript supplies information derived from types. The project does not need to treat the new checker as a replacement for every existing source-quality tool merely because both can report errors in JavaScript.

Declaration files also remain useful in this mode. The release describes adding .d.ts information for libraries so JavaScript users receive completion and checking against those interfaces. That connects gradual adoption to the same library descriptions used by TypeScript source. The consumer can remain a JavaScript file while the dependency's declaration provides the information needed to understand a call.

Strict is a preset with explicit overrides

The new strict option enables noImplicitAny, strictNullChecks, noImplicitThis, and alwaysStrict. JavaScript strict mode is the responsibility of the last option, so the group contains both type-checking policy and a language-mode choice. Newly generated projects from tsc --init enable strict by default, according to the release announcement.

The April 10 release-candidate explanation describes the usability problem behind the preset. Developers wanting the strict settings had to remember several independent flags. The number of available compiler options made it easy to overlook one. A single named preset expresses the team's recommended combination without making each project reconstruct that list.

Individual settings can still override the preset. The candidate announcement shows strict: true together with noImplicitThis: false. That gives a project a compact statement of its general policy and a visible exception. A reader can distinguish a deliberate opt-out from a setting omitted because the author did not know it existed.

The candidate announcement also says future strict checks may join the preset. That makes the option a maintained policy, not merely an immutable abbreviation for today's four flags. A compiler upgrade can therefore change what opting into the policy entails. Explicit individual overrides remain the mechanism described for departing from a particular check.

I prefer that arrangement when the exceptions stay visible. A project using one preset with a documented local difference is easier to understand than a long copied configuration whose purpose has been forgotten. The preset also makes checking behavior something the project can name, instead of inferring it from the absence of diagnostics.

Generic defaults distinguish optional information

Default type arguments address another recurring source of declaration noise. The announcement uses a simplified component with type parameters for props and state. A component that does not use state still previously needed to supply that second type argument explicitly. The new syntax lets the declaration provide the default.

class Component<Props, State = object> {
}

interface LabelProps {
    text: string;
}

class Label extends Component<LabelProps> {
}

The omitted state argument takes the declared default. That does not mean every possible state value is valid according to every component API. The declaration chooses the type used when the caller does not provide one. A library can reduce repetitive arguments for the common case while retaining an explicit parameter for callers with different requirements.

A default is also distinct from a constraint. The release states that a default must be assignable to its constraint, but the two need not be identical. A constraint defines which arguments are permitted. A default supplies the argument when it is omitted. Treating them as the same would confuse a fallback choice with the whole set of allowed choices.

Downlevel iteration changes emitted behavior

The release adds generator and iterator support when targeting older JavaScript versions through downlevelIteration. The candidate announcement explains why this is opt-in. Earlier ES3 and ES5 output assumed array-oriented behavior for constructs such as for...of. General iterator support needs more elaborate output and can add overhead for cases that do not need it.

The option therefore expresses a runtime-semantics requirement, not just a preference about formatting the emitted JavaScript. A program consuming a general iterator needs behavior that an array-index loop does not describe. A program using only arrays can retain simpler output. The compiler gives the project a way to select which model its code requires.

Async generators and async iterators extend that work. An async generator can await while producing results, and an async function can consume the results through for await. The candidate announcement describes an iterable wrapper that delays before yielding each item. The consumer waits for each asynchronous iteration result without turning the source into nested promise callbacks.

The required runtime facilities remain separate. The announcement says async iteration needs Symbol.asyncIterator, which may require a polyfill, and declaration support through the esnext library option. ES5 and earlier targets also need downlevelIteration. Declaring that an API exists does not install it in the runtime, and transforming generator syntax does not decide which polyfills a deployed application has loaded.

Editor extensions can describe embedded content

TypeScript 2.3 also makes a language-server plugin API available. The release connects it to work with Angular on template completion, navigation, and errors. Templates were outside the content TypeScript had chosen to special-case. Plugins provide a way to extend the editing experience without making the core compiler understand every framework's embedded language.

The announcement names GraphQL and TSLint plugins as other uses, and describes Vue's Vetur integration through the plugin model. The general point is that framework knowledge can participate in editing while remaining an extension. That is useful where an application combines JavaScript or TypeScript with a separate template language whose rules still need editor support.

Discovery improves through configuration output as well. tsc --help groups options by topic and reserves a full listing for --help --all. tsc --init lists potential options in comments so developers can see capabilities they might otherwise miss. These changes connect the release's adoption work to concrete tool behavior. A project can discover JavaScript checking, select a strictness policy, and choose iteration semantics through options the generated configuration now makes easier to inspect.