TypeScript 3.9.7 makes the optional tracing module path explicit
The 3.9.7 maintenance update changes how the compiler loads its optional ETW integration. A configured path or explicit relative fallback replaces the previous bare module request.
A compiler's optional diagnostics code still loads executable dependencies. That makes its module-loading rules relevant even when an application never imports the integration itself. TypeScript 3.9.7 was published to npm on July 16, and its maintenance changes include a more explicit path for the optional Event Tracing for Windows module.
The patch between 3.9.6 and 3.9.7 replaces a bare request for @microsoft/typescript-etw with a configured path or an explicit relative fallback. The rest of the integration remains optional. If the module cannot be loaded or does not provide the expected entry point, the compiler uses a logger whose operations do nothing.
I care about this part of TypeScript because the compiler and language service are dependencies of the development workflow as well as the application build. A small maintenance change in their host integration can matter without adding any language syntax.
The integration exists outside application imports
The released performance logger source identifies this as an optional module for Event Tracing for Windows. It defines a logger interface by reference to that package and prepares a fallback object implementing the expected operations.
Those operations include events around program updates, module resolution and parsing source files. They also cover reading and binding files. Separate start and stop methods describe the boundaries of these activities, while other methods accept informational, error and performance events. The integration therefore observes compiler and language-service activity rather than serving as an application dependency selected by a user's import statement.
This distinction explains why examining only emitted application JavaScript would miss the change. The compiler host loads the integration while doing its own work. The relevant dependency request is in the tool's implementation, not in the program being checked.
The update does not require every TypeScript user to install the tracing module. Its optional status is visible in the fallback path. The tool can continue using no-op logging when the native integration is unavailable.
A path is selected before require runs
The new source first selects a path with nullish coalescing. It then uses that selected string as the argument to require.
const etwModulePath = process.env.TS_ETW_MODULE_PATH
?? "./node_modules/@microsoft/typescript-etw";
etwModule = require(etwModulePath);
This excerpt belongs to the compiler's internal initialization, not to a suggested application setup. The environment variable permits an explicit module path. In its absence, the fallback is an explicit relative module request instead of the earlier package-name-only request.
The placement of the code in a try block also matters. An exception from loading the module sets the integration variable to undefined. The logger-selection expression later decides whether a usable logger was obtained.
The patch therefore changes dependency selection without changing the basic failure policy. Selecting another path does not make a failed optional load fatal to ordinary compiler operation. The code still has a defined way to proceed without that integration.
A loaded object must expose the expected entry point
Successful require is not the only condition used to select the tracing logger. The source checks that the returned object has a logEvent member. A comment explains that the object may be empty in a browserified environment.
If that condition is not met, perfLogger becomes nullLogger. The fallback provides methods with the same expected names, all mapped to the compiler's no-op function. Call sites can therefore issue logging operations without each one separately deciding whether the optional integration exists.
This is a useful separation of responsibilities. Module initialization decides whether tracing is available. Compiler operations use the resulting logger interface. A load failure changes observability while the fallback keeps those logging calls from becoming an additional condition at every compilation step.
I prefer that explicit failure behaviour for optional tooling. An unavailable performance integration should have an identifiable effect. Here the source shows that effect as no-op logging, so it does not need to be guessed from a successful build alone.
The distributed tools carry the change
The same patch updates the built JavaScript files shipped with the compiler and language service. The changed path selection appears in outputs including tsc.js, tsserver.js and typescript.js. It is therefore part of the distributed maintenance release, not only a source edit waiting for another build.
The npm publication record supplies the date and version independently of that implementation detail. Those pieces of evidence answer different questions. The registry establishes which release became available, while the tagged source and patch establish how its optional loader behaves.
This maintenance update leaves the application's module imports and type declarations outside the change. The concrete new behaviour is in the tool's own tracing initialization. It selects TS_ETW_MODULE_PATH when supplied, otherwise requests the explicit relative fallback, and chooses no-op logging if a usable tracing module is not obtained.