Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Emitting .tsbuildinfo when using watch api #1017

Merged
merged 5 commits into from Sep 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
@@ -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
@@ -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
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
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
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
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
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
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
@@ -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?");

/***/ })

/******/ });