Skip to content

Commit

Permalink
Merge branch 'master' into reportFiles
Browse files Browse the repository at this point in the history
  • Loading branch information
johnnyreilly committed Jan 20, 2018
2 parents a0da49a + 6d55769 commit 64914e0
Show file tree
Hide file tree
Showing 205 changed files with 10,482 additions and 68 deletions.
6 changes: 3 additions & 3 deletions .travis.yml
Expand Up @@ -3,17 +3,17 @@ addons:
chrome: stable
language: node_js
node_js:
- "4"
- "6"
- "8"
sudo: false
sudo: required
install:
- yarn install
- yarn build
- yarn add $TYPESCRIPT
env:
- TYPESCRIPT=typescript@2.6.1
- TYPESCRIPT=typescript@2.7.0-rc
- TYPESCRIPT=typescript@next
- TYPESCRIPT=typescript@2.6.1
- TYPESCRIPT=typescript@2.5.2
- TYPESCRIPT=typescript@2.4.1
- TYPESCRIPT=typescript@2.3.1
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,9 @@
# Changelog

## v3.3.0

- [Added support for the new watch api of TypeScript compiler.](https://github.com/TypeStrong/ts-loader/pull/685) nb This feature has been placed behind a new `experimentalWatchApi` option until it has been thoroughly tested. All being well it is likely to become the default behaviour for ts-loader in future - thanks @sheetalkamat!

## v3.2.0

- [Add new loader option `contextAsConfigBasePath`](https://github.com/TypeStrong/ts-loader/pull/681) - thanks @christiantinauer
Expand Down
3 changes: 2 additions & 1 deletion appveyor.yml
Expand Up @@ -3,8 +3,9 @@ environment:
FORCE_COLOR: 1
nodejs_version: "8"
matrix:
- TYPESCRIPT: typescript@2.6.1
- TYPESCRIPT: typescript@2.7.0-rc
- TYPESCRIPT: typescript@next
- TYPESCRIPT: typescript@2.6.1
- TYPESCRIPT: typescript@2.5.2
- TYPESCRIPT: typescript@2.4.1
- TYPESCRIPT: typescript@2.3.1
Expand Down
6 changes: 3 additions & 3 deletions package.json
@@ -1,6 +1,6 @@
{
"name": "ts-loader",
"version": "3.2.0",
"version": "3.3.0",
"description": "TypeScript loader for webpack",
"main": "index.js",
"scripts": {
Expand Down Expand Up @@ -62,11 +62,11 @@
"karma-jasmine": "^1.0.0",
"karma-mocha-reporter": "^2.0.0",
"karma-sourcemap-loader": "^0.3.6",
"karma-webpack": "^2.0.1",
"karma-webpack": "2.0.6",
"mkdirp": "^0.5.1",
"mocha": "^4.0.0",
"rimraf": "^2.6.2",
"typescript": "^2.6.1",
"typescript": "^2.7.0-dev.20180120",
"webpack": "^3.6.0"
}
}
36 changes: 23 additions & 13 deletions src/after-compile.ts
@@ -1,5 +1,4 @@
import * as path from 'path';
import * as typescript from 'typescript';

import { collectAllDependants, formatErrors, hasOwnProperty, registerWebpackErrors } from './utils';
import * as constants from './constants';
Expand All @@ -10,6 +9,7 @@ import {
WebpackError,
WebpackModule
} from './interfaces';
import { getEmitOutput } from './instances';

export function makeAfterCompile(
instance: TSInstance,
Expand Down Expand Up @@ -38,7 +38,7 @@ export function makeAfterCompile(
const filesWithErrors: TSFiles = {};
provideErrorsToWebpack(filesToCheckForErrors, filesWithErrors, compilation, modules, instance);

provideDeclarationFilesToWebpack(filesToCheckForErrors, instance.languageService!, compilation);
provideDeclarationFilesToWebpack(filesToCheckForErrors, instance, compilation);

instance.filesWithErrors = filesWithErrors;
instance.modifiedFiles = null;
Expand All @@ -60,11 +60,13 @@ function provideCompilerOptionDiagnosticErrorsToWebpack(
configFilePath: string | undefined
) {
if (getCompilerOptionDiagnostics) {
const { languageService, loaderOptions, compiler } = instance;
const { languageService, loaderOptions, compiler, program } = instance;
registerWebpackErrors(
compilation.errors,
formatErrors(
languageService!.getCompilerOptionsDiagnostics(),
program ?
program.getOptionsDiagnostics() :
languageService!.getCompilerOptionsDiagnostics(),
loaderOptions, instance.colors, compiler,
{ file: configFilePath || 'tsconfig.json' },
compilation.compiler.context));
Expand Down Expand Up @@ -100,18 +102,23 @@ function determineFilesToCheckForErrors(
checkAllFilesForErrors: boolean,
instance: TSInstance
) {
const { files, modifiedFiles, filesWithErrors } = instance
const { files, modifiedFiles, filesWithErrors, otherFiles } = instance
// calculate array of files to check
let filesToCheckForErrors: TSFiles = {};
if (checkAllFilesForErrors) {
// check all files on initial run
filesToCheckForErrors = files;
Object.keys(files).forEach(fileName => {
filesToCheckForErrors[fileName] = files[fileName];
});
Object.keys(otherFiles).forEach(fileName => {
filesToCheckForErrors[fileName] = otherFiles[fileName];
});
} else if (modifiedFiles !== null && modifiedFiles !== undefined) {
// check all modified files, and all dependants
Object.keys(modifiedFiles).forEach(modifiedFileName => {
collectAllDependants(instance.reverseDependencyGraph, modifiedFileName)
.forEach(fileName => {
filesToCheckForErrors[fileName] = files[fileName];
filesToCheckForErrors[fileName] = files[fileName] || otherFiles[fileName];
});
});
}
Expand All @@ -132,16 +139,19 @@ function provideErrorsToWebpack(
modules: Modules,
instance: TSInstance
) {
const { compiler, languageService, files, loaderOptions, compilerOptions } = instance;
const { compiler, program, languageService, files, loaderOptions, compilerOptions, otherFiles } = instance;

let filePathRegex = !!compilerOptions.checkJs ? constants.dtsTsTsxJsJsxRegex : constants.dtsTsTsxRegex;

Object.keys(filesToCheckForErrors)
.filter(filePath => filePath.match(filePathRegex))
.forEach(filePath => {
const errors = languageService!.getSyntacticDiagnostics(filePath).concat(languageService!.getSemanticDiagnostics(filePath));
const sourceFile = program && program.getSourceFile(filePath);
const errors = program ?
program.getSyntacticDiagnostics(sourceFile).concat(program.getSemanticDiagnostics(sourceFile)) :
languageService!.getSyntacticDiagnostics(filePath).concat(languageService!.getSemanticDiagnostics(filePath));
if (errors.length > 0) {
filesWithErrors[filePath] = files[filePath];
filesWithErrors[filePath] = files[filePath] || otherFiles[filePath];
}

// if we have access to a webpack module, use that
Expand Down Expand Up @@ -173,14 +183,14 @@ function provideErrorsToWebpack(
*/
function provideDeclarationFilesToWebpack(
filesToCheckForErrors: TSFiles,
languageService: typescript.LanguageService,
instance: TSInstance,
compilation: WebpackCompilation
) {
Object.keys(filesToCheckForErrors)
.filter(filePath => filePath.match(constants.tsTsxRegex))
.forEach(filePath => {
const output = languageService.getEmitOutput(filePath);
const declarationFile = output.outputFiles.filter(outputFile => outputFile.name.match(constants.dtsDtsxRegex)).pop();
const outputFiles = getEmitOutput(instance, filePath);
const declarationFile = outputFiles.filter(outputFile => outputFile.name.match(constants.dtsDtsxRegex)).pop();
if (declarationFile !== undefined) {
const assetPath = path.relative(compilation.compiler.context, declarationFile.name);
compilation.assets[assetPath] = {
Expand Down
39 changes: 31 additions & 8 deletions src/index.ts
@@ -1,7 +1,8 @@
import * as path from 'path';
import * as loaderUtils from 'loader-utils';
import * as typescript from 'typescript';

import { getTypeScriptInstance } from './instances';
import { getTypeScriptInstance, getEmitOutput } from './instances';
import { appendSuffixesIfMatch, arrify, formatErrors, hasOwnProperty, registerWebpackErrors } from './utils';
import * as constants from './constants';
import {
Expand Down Expand Up @@ -110,7 +111,7 @@ function getLoaderOptions(loader: Webpack) {
}

type ValidLoaderOptions = keyof LoaderOptions;
const validLoaderOptions: ValidLoaderOptions[] = ['silent', 'logLevel', 'logInfoToStdOut', 'instance', 'compiler', 'contextAsConfigBasePath', 'configFile', 'transpileOnly', 'ignoreDiagnostics', 'errorFormatter', 'colors', 'compilerOptions', 'appendTsSuffixTo', 'appendTsxSuffixTo', 'entryFileCannotBeJs' /* DEPRECATED */, 'onlyCompileBundledFiles', 'happyPackMode', 'getCustomTransformers', 'reportFiles'];
const validLoaderOptions: ValidLoaderOptions[] = ['silent', 'logLevel', 'logInfoToStdOut', 'instance', 'compiler', 'contextAsConfigBasePath', 'configFile', 'transpileOnly', 'ignoreDiagnostics', 'errorFormatter', 'colors', 'compilerOptions', 'appendTsSuffixTo', 'appendTsxSuffixTo', 'entryFileCannotBeJs' /* DEPRECATED */, 'onlyCompileBundledFiles', 'happyPackMode', 'getCustomTransformers', 'reportFiles', 'experimentalWatchApi'];

/**
* Validate the supplied loader options.
Expand Down Expand Up @@ -139,7 +140,6 @@ function makeLoaderOptions(instanceName: string, configFileOptions: Partial<Load
logInfoToStdOut: false,
compiler: 'typescript',
configFile: 'tsconfig.json',
contextAsConfigBasePath: false,
transpileOnly: false,
compilerOptions: {},
appendTsSuffixTo: [],
Expand All @@ -150,6 +150,8 @@ function makeLoaderOptions(instanceName: string, configFileOptions: Partial<Load
colors: true,
onlyCompileBundledFiles: false,
reportFiles: []
// When the watch API usage stabilises look to remove this option and make watch usage the default behaviour when available
experimentalWatchApi: false
} as Partial<LoaderOptions>, configFileOptions, loaderOptions);

options.ignoreDiagnostics = arrify(options.ignoreDiagnostics).map(Number);
Expand All @@ -167,16 +169,38 @@ function makeLoaderOptions(instanceName: string, configFileOptions: Partial<Load
* Also add the file to the modified files
*/
function updateFileInCache(filePath: string, contents: string, instance: TSInstance) {
let fileWatcherEventKind: typescript.FileWatcherEventKind | undefined;
// Update file contents
let file = instance.files[filePath];
if (file === undefined) {
file = instance.files[filePath] = <TSFile>{ version: 0 };
file = instance.otherFiles[filePath];
if (file !== undefined) {
delete instance.otherFiles[filePath];
instance.files[filePath] = file;
}
else {
fileWatcherEventKind = instance.compiler.FileWatcherEventKind.Created;
file = instance.files[filePath] = <TSFile>{ version: 0 };
}
instance.changedFilesList = true;
}

if (contents === undefined) {
fileWatcherEventKind === instance.compiler.FileWatcherEventKind.Deleted;
}

if (file.text !== contents) {
file.version++;
file.text = contents;
instance.version!++;
if (instance.watchHost && fileWatcherEventKind === undefined) {
instance.watchHost.invokeFileWatcher(filePath, instance.compiler.FileWatcherEventKind.Changed);
}
}

if (instance.watchHost && fileWatcherEventKind !== undefined) {
instance.watchHost.invokeFileWatcher(filePath, fileWatcherEventKind);
instance.watchHost.invokeDirectoryWatcher(path.dirname(filePath), filePath);
}

// push this file to modified files hash.
Expand All @@ -193,8 +217,7 @@ function getEmit(
instance: TSInstance,
loader: Webpack
) {
// Emit Javascript
const output = instance.languageService!.getEmitOutput(filePath);
const outputFiles = getEmitOutput(instance, filePath);

loader.clearDependencies();
loader.addDependency(rawFilePath);
Expand All @@ -218,10 +241,10 @@ function getEmit(
.concat(additionalDependencies)
.map(defFilePath => defFilePath + '@' + (instance.files[defFilePath] || { version: '?' }).version);

const outputFile = output.outputFiles.filter(outputFile => outputFile.name.match(constants.jsJsx)).pop();
const outputFile = outputFiles.filter(outputFile => outputFile.name.match(constants.jsJsx)).pop();
const outputText = (outputFile) ? outputFile.text : undefined;

const sourceMapFile = output.outputFiles.filter(outputFile => outputFile.name.match(constants.jsJsxMap)).pop();
const sourceMapFile = outputFiles.filter(outputFile => outputFile.name.match(constants.jsJsxMap)).pop();
const sourceMapText = (sourceMapFile) ? sourceMapFile.text : undefined;

return { outputText, sourceMapText };
Expand Down
49 changes: 43 additions & 6 deletions src/instances.ts
Expand Up @@ -9,7 +9,7 @@ import { EOL, dtsDtsxRegex } from './constants';
import { getCompilerOptions, getCompiler } from './compilerSetup';
import { hasOwnProperty, makeError, formatErrors, registerWebpackErrors } from './utils';
import * as logger from './logger';
import { makeServicesHost } from './servicesHost';
import { makeServicesHost, makeWatchHost } from './servicesHost';
import { makeWatchRun } from './watch-run';
import {
LoaderOptions,
Expand All @@ -34,6 +34,15 @@ export function getTypeScriptInstance(
loader: Webpack
): { instance?: TSInstance, error?: WebpackError } {
if (hasOwnProperty(instances, loaderOptions.instance)) {
const instance = instances[loaderOptions.instance];
if (instance && instance.watchHost) {
if (instance.changedFilesList) {
instance.watchHost.updateRootFileNames();
}
if (instance.watchOfFilesAndCompilerOptions) {
instance.program = instance.watchOfFilesAndCompilerOptions.getProgram().getProgram();
}
}
return { instance: instances[loaderOptions.instance] };
}

Expand Down Expand Up @@ -88,28 +97,30 @@ function successfulTypeScriptInstance(

const compilerOptions = getCompilerOptions(configParseResult);
const files: TSFiles = {};
const otherFiles: TSFiles = {};

const getCustomTransformers = loaderOptions.getCustomTransformers || Function.prototype;

if (loaderOptions.transpileOnly) {
// quick return for transpiling
// we do need to check for any issues with TS options though
const program = compiler!.createProgram([], compilerOptions);
const diagnostics = program.getOptionsDiagnostics();

// happypack does not have _module.errors - see https://github.com/TypeStrong/ts-loader/issues/336
if (!loaderOptions.happyPackMode) {
const diagnostics = program.getOptionsDiagnostics();
registerWebpackErrors(
loader._module.errors,
formatErrors(diagnostics, loaderOptions, colors, compiler!,
{file: configFilePath || 'tsconfig.json'}, loader.context));
}

const instance = {
const instance: TSInstance = {
compiler,
compilerOptions,
loaderOptions,
files,
files,
otherFiles,
dependencyGraph: {},
reverseDependencyGraph: {},
transformers: getCustomTransformers(),
Expand Down Expand Up @@ -148,6 +159,7 @@ function successfulTypeScriptInstance(
compilerOptions,
loaderOptions,
files,
otherFiles,
languageService: null,
version: 0,
transformers: getCustomTransformers(),
Expand All @@ -157,11 +169,36 @@ function successfulTypeScriptInstance(
colors
};

const servicesHost = makeServicesHost(scriptRegex, log, loader, instance, loaderOptions.appendTsSuffixTo, loaderOptions.appendTsxSuffixTo);
instance.languageService = compiler.createLanguageService(servicesHost, compiler.createDocumentRegistry());
if (loaderOptions.experimentalWatchApi && compiler.createWatchProgram) {
log.logInfo("Using watch api");

// If there is api available for watch, use it instead of language service
const watchHost = makeWatchHost(scriptRegex, log, loader, instance, loaderOptions.appendTsSuffixTo, loaderOptions.appendTsxSuffixTo);
instance.watchOfFilesAndCompilerOptions = compiler.createWatchProgram(watchHost);
instance.program = instance.watchOfFilesAndCompilerOptions.getProgram().getProgram();
}
else {
const servicesHost = makeServicesHost(scriptRegex, log, loader, instance, loaderOptions.appendTsSuffixTo, loaderOptions.appendTsxSuffixTo);
instance.languageService = compiler.createLanguageService(servicesHost, compiler.createDocumentRegistry());
}

loader._compiler.plugin("after-compile", makeAfterCompile(instance, configFilePath));
loader._compiler.plugin("watch-run", makeWatchRun(instance));

return { instance };
}

export function getEmitOutput(instance: TSInstance, filePath: string) {
if (instance.program) {
const outputFiles: typescript.OutputFile[] = [];
const writeFile = (fileName: string, text: string, writeByteOrderMark: boolean) =>
outputFiles.push({ name: fileName, writeByteOrderMark, text });
const sourceFile = instance.program.getSourceFile(filePath);
instance.program.emit(sourceFile, writeFile, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ false, instance.transformers);
return outputFiles;
}
else {
// Emit Javascript
return instance.languageService!.getEmitOutput(filePath).outputFiles;
}
}

0 comments on commit 64914e0

Please sign in to comment.