Skip to content

Commit

Permalink
Emitting .tsbuildinfo when using watch api (#1017)
Browse files Browse the repository at this point in the history
* Watch api tests

* Updater the .tsbuildinfo

* Write tsbuild info when available using experimentalWatchApi

* update CHANGELOG.md

* Fix running command for webpack when karmaConfPath is absent in the test
  • Loading branch information
sheetalkamat authored and johnnyreilly committed Sep 27, 2019
1 parent ed8d596 commit ce39c25
Show file tree
Hide file tree
Showing 154 changed files with 9,322 additions and 162 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

## v6.2.0
* [Emitting .tsbuildinfo when using watch api](https://github.com/TypeStrong/ts-loader/pull/1017) - thanks @sheetalkamat!

## v6.1.2
* [don't emit declaration files for a declaration file](https://github.com/TypeStrong/ts-loader/pull/1015) (#1014) - thanks @gvinaccia!
* [Consume typescript apis from typescript nightly](https://github.com/TypeStrong/ts-loader/pull/1016) - thanks @sheetalkamat!
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ts-loader",
"version": "6.1.2",
"version": "6.2.0",
"description": "TypeScript loader for webpack",
"main": "index.js",
"types": "dist/types/index.d.ts",
Expand Down
25 changes: 24 additions & 1 deletion src/after-compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import * as ts from 'typescript';
import * as webpack from 'webpack';

import * as constants from './constants';
import { forEachResolvedProjectReference, getEmitOutput } from './instances';
import {
forEachResolvedProjectReference,
getEmitFromWatchHost,
getEmitOutput
} from './instances';
import {
TSFile,
TSFiles,
Expand Down Expand Up @@ -406,6 +410,25 @@ function provideTsBuildInfoFilesToWebpack(
);
}
}

if (instance.watchHost) {
// Ensure emit is complete
getEmitFromWatchHost(instance);
if (instance.watchHost.tsbuildinfo) {
const { tsbuildinfo } = instance.watchHost;
const assetPath = path.relative(
compilation.compiler.outputPath,
path.resolve(tsbuildinfo.name)
);
compilation.assets[assetPath] = {
source: () => tsbuildinfo.text,
size: () => tsbuildinfo.text.length
};
}

instance.watchHost.outputFiles.clear();
instance.watchHost.tsbuildinfo = undefined;
}
}

/**
Expand Down
63 changes: 60 additions & 3 deletions src/instances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,9 +307,8 @@ function successfulTypeScriptInstance(
instance.watchOfFilesAndCompilerOptions = compiler.createWatchProgram(
instance.watchHost
);
instance.program = instance.watchOfFilesAndCompilerOptions
.getProgram()
.getProgram();
instance.builderProgram = instance.watchOfFilesAndCompilerOptions.getProgram();
instance.program = instance.builderProgram.getProgram();

instance.transformers = getCustomTransformers(instance.program);
} else {
Expand Down Expand Up @@ -571,6 +570,60 @@ export function isReferencedFile(instance: TSInstance, filePath: string) {
);
}

export function getEmitFromWatchHost(instance: TSInstance, filePath?: string) {
const program = ensureProgram(instance);
const builderProgram = instance.builderProgram;
if (builderProgram && program) {
if (filePath) {
const existing = instance.watchHost!.outputFiles.get(filePath);
if (existing) {
return existing;
}
}

const outputFiles: typescript.OutputFile[] = [];
const writeFile: typescript.WriteFileCallback = (
fileName,
text,
writeByteOrderMark
) => {
if (fileName.endsWith('.tsbuildinfo')) {
instance.watchHost!.tsbuildinfo = {
name: fileName,
writeByteOrderMark,
text
};
} else {
outputFiles.push({ name: fileName, writeByteOrderMark, text });
}
};

const sourceFile = filePath ? program.getSourceFile(filePath) : undefined;
// Try emit Next file
while (true) {
const result = builderProgram.emitNextAffectedFile(
writeFile,
/*cancellationToken*/ undefined,
/*emitOnlyDtsFiles*/ false,
instance.transformers
);
if (!result) {
break;
}
if ((result.affected as typescript.SourceFile).fileName) {
instance.watchHost!.outputFiles.set(
path.resolve((result.affected as typescript.SourceFile).fileName),
outputFiles.slice()
);
}
if (result.affected === sourceFile) {
return outputFiles;
}
}
}
return undefined;
}

export function getEmitOutput(instance: TSInstance, filePath: string) {
if (fileExtensionIs(filePath, instance.compiler.Extension.Dts)) {
return [];
Expand All @@ -596,6 +649,10 @@ export function getEmitOutput(instance: TSInstance, filePath: string) {
) => outputFiles.push({ name: fileName, writeByteOrderMark, text });
// The source file will be undefined if it’s part of an unbuilt project reference
if (sourceFile !== undefined || !isUsingProjectReferences(instance)) {
const outputFilesFromWatch = getEmitFromWatchHost(instance, filePath);
if (outputFilesFromWatch) {
return outputFilesFromWatch;
}
program.emit(
sourceFile,
writeFile,
Expand Down
7 changes: 5 additions & 2 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,16 @@ export type ResolveSync = (

export interface WatchHost
extends typescript.WatchCompilerHostOfFilesAndCompilerOptions<
typescript.BuilderProgram
typescript.EmitAndSemanticDiagnosticsBuilderProgram
> {
invokeFileWatcher(
fileName: string,
eventKind: typescript.FileWatcherEventKind
): void;
invokeDirectoryWatcher(directory: string, fileAddedOrRemoved: string): void;
updateRootFileNames(): void;
outputFiles: Map<string, typescript.OutputFile[]>;
tsbuildinfo?: typescript.OutputFile;
}

export type WatchCallbacks<T> = Map<string, T[]>;
Expand Down Expand Up @@ -121,8 +123,9 @@ export interface TSInstance {
otherFiles: TSFiles;
watchHost?: WatchHost;
watchOfFilesAndCompilerOptions?: typescript.WatchOfFilesAndCompilerOptions<
typescript.BuilderProgram
typescript.EmitAndSemanticDiagnosticsBuilderProgram
>;
builderProgram?: typescript.EmitAndSemanticDiagnosticsBuilderProgram;
program?: typescript.Program;
hasUnaccountedModifiedFiles?: boolean;
changedFilesList?: boolean;
Expand Down
10 changes: 6 additions & 4 deletions src/servicesHost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -517,8 +517,10 @@ export function makeWatchHost(
},
createProgram:
projectReferences === undefined
? compiler.createAbstractBuilder
: createBuilderProgramWithReferences
? compiler.createEmitAndSemanticDiagnosticsBuilderProgram
: createBuilderProgramWithReferences,

outputFiles: new Map()
};
return watchHost;

Expand Down Expand Up @@ -549,7 +551,7 @@ export function makeWatchHost(
rootNames: ReadonlyArray<string> | undefined,
options: typescript.CompilerOptions | undefined,
host: typescript.CompilerHost | undefined,
oldProgram: typescript.BuilderProgram | undefined,
oldProgram: typescript.EmitAndSemanticDiagnosticsBuilderProgram | undefined,
configFileParsingDiagnostics:
| ReadonlyArray<typescript.Diagnostic>
| undefined
Expand All @@ -564,7 +566,7 @@ export function makeWatchHost(
});

const builderProgramHost: typescript.BuilderProgramHost = host!;
return compiler.createAbstractBuilder(
return compiler.createEmitAndSemanticDiagnosticsBuilderProgram(
program,
builderProgramHost,
oldProgram,
Expand Down
5 changes: 2 additions & 3 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,8 @@ export function ensureProgram(instance: TSInstance) {
instance.watchHost.updateRootFileNames();
}
if (instance.watchOfFilesAndCompilerOptions) {
instance.program = instance.watchOfFilesAndCompilerOptions
.getProgram()
.getProgram();
instance.builderProgram = instance.watchOfFilesAndCompilerOptions.getProgram();
instance.program = instance.builderProgram.getProgram();
}
instance.hasUnaccountedModifiedFiles = false;
}
Expand Down
25 changes: 20 additions & 5 deletions test/comparison-tests/create-and-execute-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,24 @@ function createTest(test, testPath, options) {

// copy all input to a staging area
mkdirp.sync(paths.testStagingPath);
const nonWatchNonCompositePath = testPath.replace(/(_Composite)?_WatchApi$/, "");
if (nonWatchNonCompositePath !== testPath) {
const nonWatchPath = testPath.replace(/_WatchApi$/, "");
// Copy things from non watch path
copySync(nonWatchNonCompositePath, paths.testStagingPath);
if (nonWatchPath !== nonWatchNonCompositePath) {
// Change the tsconfig to be composite
const configPath = path.resolve(paths.testStagingPath, "tsconfig.json");
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
config.files = [ "./app.ts"];
config.compilerOptions = { composite: true };
fs.writeFileSync(configPath, JSON.stringify(config, /*replacer*/ undefined, " "));
}
}
copySync(testPath, paths.testStagingPath);
if (test === "projectReferencesWatchRefWithTwoFilesAlreadyBuilt") {
if (test.startsWith("projectReferencesWatchRefWithTwoFilesAlreadyBuilt")) {
// Copy output
copySync(path.resolve(testPath, "libOutput"), path.resolve(paths.testStagingPath, "lib"));
copySync(path.resolve(paths.testStagingPath, "libOutput"), path.resolve(paths.testStagingPath, "lib"));
// Change the buildinfo to use typescript version we have
const buildInfoPath = path.resolve(paths.testStagingPath, "lib/tsconfig.tsbuildinfo");
fs.writeFileSync(buildInfoPath, fs.readFileSync(buildInfoPath, "utf8").replace("FakeTSVersion", typescript.version));
Expand All @@ -104,7 +118,7 @@ function createTest(test, testPath, options) {

// execute webpack
testState.watcher = webpack(
createWebpackConfig(paths, options)
createWebpackConfig(paths, options, nonWatchNonCompositePath !== testPath)
).watch({ aggregateTimeout: 1500 }, createWebpackWatchHandler(done, paths, testState, outputs, options, test));
};
}
Expand Down Expand Up @@ -153,7 +167,7 @@ function storeSavedOutputs(saveOutputMode, outputs, test, options, paths) {
}
}

function createWebpackConfig(paths, optionsOriginal) {
function createWebpackConfig(paths, optionsOriginal, useWatchApi) {
const config = require(path.join(paths.testStagingPath, 'webpack.config'));

const extraOptionMaybe = extraOption ? { [extraOption]: true } : {};
Expand All @@ -162,7 +176,8 @@ function createWebpackConfig(paths, optionsOriginal) {
silent: true,
compilerOptions: {
newLine: 'LF'
}
},
experimentalWatchApi: !!useWatchApi
}, optionsOriginal, extraOptionMaybe);

const tsLoaderPath = require('path').join(__dirname, "../../index.js");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = "./app.ts");
/******/ })
/************************************************************************/
/******/ ({

/***/ "./app.ts":
/*!****************!*\
!*** ./app.ts ***!
\****************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

"use strict";
eval("\nexports.__esModule = true;\nvar lib_1 = __webpack_require__(/*! ./lib */ \"./lib/index.ts\");\nconsole.log(lib_1.lib.one, lib_1.lib.two, lib_1.lib.three);\n\n\n//# sourceURL=webpack:///./app.ts?");

/***/ }),

/***/ "./lib/index.ts":
/*!**********************!*\
!*** ./lib/index.ts ***!
\**********************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

"use strict";
eval("\r\nexports.__esModule = true;\r\nexports.lib = {\r\n one: 1,\r\n two: 2,\r\n three: 3\r\n};\r\n\n\n//# sourceURL=webpack:///./lib/index.ts?");

/***/ })

/******/ });
Loading

0 comments on commit ce39c25

Please sign in to comment.