Skip to content

Commit

Permalink
Merge pull request #817 from andrewbranch/project-references
Browse files Browse the repository at this point in the history
Support project references
  • Loading branch information
johnnyreilly committed Sep 23, 2018
2 parents ff2da73 + ca2c576 commit 635f745
Show file tree
Hide file tree
Showing 81 changed files with 1,614 additions and 174 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ sudo: required
install:
- yarn install
- yarn build
- yarn lint
- yarn add $TYPESCRIPT
env:
- TYPESCRIPT=typescript@3.0.1
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Changelog

## 5.2.0

* [feat: Initial support for project references - `projectReferences`](https://github.com/TypeStrong/ts-loader/pull/817) - thanks @andrewbranch!

## 5.1.1

* [fix(getTranspilationEmit): pass the raw path to transpileModule](https://github.com/TypeStrong/ts-loader/pull/835) - thanks @Brooooooklyn

## 5.1.0
Expand Down
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,7 @@ Extending `tsconfig.json`:

Note that changes in the extending file while not be respected by `ts-loader`. Its purpose is to satisfy the code editor.

### experimentalFileCaching _(boolean) (default=false)_
#### experimentalFileCaching _(boolean) (default=false)_

By default whenever the TypeScript compiler needs to check that a file/directory exists or resolve symlinks it makes syscalls.
It does not cache the result of these operations and this may result in many syscalls with the same arguments ([see comment](https://github.com/TypeStrong/ts-loader/issues/825#issue-354725524) with example).
Expand All @@ -552,6 +552,32 @@ In some cases it may produce performance degradation.
This flag enables caching for some FS-functions like `fileExists`, `realpath` and `directoryExists` for TypeScript compiler.
Note that caches are cleared between compilations.

#### projectReferences _(boolean) (default=false)_

**TL;DR:** Using project references currently requires building referenced projects outside of ts-loader. We don’t want to keep it that way, but we’re releasing what we’ve got now. To try it out, you’ll need to pass `projectReferences: true` to `loaderOptions`.

ts-loader has partial support for [project references](https://www.typescriptlang.org/docs/handbook/project-references.html) in that it will _load_ dependent composite projects that are already built, but will not currently _build/rebuild_ those upstream projects. The best way to explain exactly what this means is through an example. Say you have a project with a project reference pointing to the `lib/` directory:

```
tsconfig.json
app.ts
lib/
tsconfig.json
niftyUtil.ts
```

And we’ll assume that the root `tsconfig.json` has `{ "references": { "path": "lib" } }`, which means that any import of a file that’s part of the `lib` sub-project is treated as a reference to another project, not just a reference to a TypeScript file. Before discussing how ts-loader handles this, it’s helpful to review at a really basic level what `tsc` itself does here. If you were to run `tsc` on this tiny example project, the build would fail with the error:

```
error TS6305: Output file 'lib/niftyUtil.d.ts' has not been built from source file 'lib/niftyUtil.ts'.
```

Using project references actually instructs `tsc` _not_ to build anything that’s part of another project from source, but rather to look for any `.d.ts` and `.js` files that have already been generated from a previous build. Since we’ve never built the project in `lib` before, those files don’t exist, so building the root project fails. Still just thinking about how `tsc` works, there are two options to make the build succeed: either run `tsc -p lib/tsconfig.json` _first_, or simply run `tsc --build`, which will figure out that `lib` hasn’t been built and build it first for you.

Ok, so how is that relevant to ts-loader? Because the best way to think about what ts-loader does with project references is that it acts like `tsc`, but _not_ like `tsc --build`. If you run ts-loader on a project that’s using project references, and any upstream project hasn’t been built, you’ll get the exact same `error TS6305` that you would get with `tsc`. If you modify a source file in an upstream project and don’t rebuild that project, `ts-loader` won’t have any idea that you’ve changed anything—it will still be looking at the output from the last time you _built_ that file.

**“Hey, don’t you think that sounds kind of useless and terrible?”** Well, sort of. You can consider it a work-in-progress. It’s true that on its own, as of today, ts-loader doesn’t have everything you need to take advantage of project references in Webpack. In practice, though, _consuming_ upstream projects and _building_ upstream projects are somewhat separate concerns. Building them will likely come in a future release. For background, see the [original issue](https://github.com/TypeStrong/ts-loader/issues/815).

### Usage with Webpack watch

Because TS will generate .js and .d.ts files, you should ignore these files, otherwise watchers may go into an infinite watch loop. For example, when using Webpack, you may wish to add this to your webpack.conf.js file:
Expand Down
1 change: 1 addition & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ install:
- ps: Install-Product node $env:nodejs_version
- yarn install
- yarn build
- yarn lint
- yarn add %TYPESCRIPT%
test_script:
- node --version
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
{
"name": "ts-loader",
"version": "5.1.1",
"version": "5.2.0",
"description": "TypeScript loader for webpack",
"main": "index.js",
"types": "dist/types/index.d.ts",
"scripts": {
"build": "tsc --version && tsc --project \"./src\"",
"lint": "tslint --project \"./src\"",
"comparison-tests": "tsc --project \"./test/comparison-tests\" && npm link ./test/comparison-tests/testLib && node test/comparison-tests/run-tests.js",
"comparison-tests-generate": "node test/comparison-tests/stub-new-version.js",
"execution-tests": "npm i -g pnpm && node test/execution-tests/run-tests.js",
Expand Down Expand Up @@ -78,6 +79,8 @@
"mocha": "^5.0.0",
"prettier": "^1.11.1",
"rimraf": "^2.6.2",
"tslint": "^5.11.0",
"tslint-config-prettier": "^1.15.0",
"typescript": "^3.0.1",
"webpack": "^4.5.0",
"webpack-cli": "^2.1.2"
Expand Down
25 changes: 20 additions & 5 deletions src/after-compile.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import * as path from 'path';
import { collectAllDependants, formatErrors } from './utils';

import * as constants from './constants';
import { getEmitOutput } from './instances';
import {
TSFile,
TSFiles,
TSInstance,
WebpackCompilation,
WebpackError,
WebpackModule,
TSFile
WebpackModule
} from './interfaces';
import { getEmitOutput } from './instances';
import {
collectAllDependants,
formatErrors,
isUsingProjectReferences
} from './utils';

export function makeAfterCompile(
instance: TSInstance,
Expand Down Expand Up @@ -60,6 +65,7 @@ export function makeAfterCompile(

instance.filesWithErrors = filesWithErrors;
instance.modifiedFiles = null;
instance.projectsMissingSourceMaps = new Set();

callback();
};
Expand Down Expand Up @@ -171,7 +177,7 @@ function provideErrorsToWebpack(
otherFiles
} = instance;

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

Expand All @@ -181,6 +187,15 @@ function provideErrorsToWebpack(
}

const sourceFile = program && program.getSourceFile(filePath);

// If the source file is undefined, that probably means it’s actually part of an unbuilt project reference,
// which will have already produced a more useful error than the one we would get by proceeding here.
// If it’s undefined and we’re not using project references at all, I guess carry on so the user will
// get a useful error about which file was unexpectedly missing.
if (isUsingProjectReferences(instance) && !sourceFile) {
continue;
}

const errors = program
? [
...program.getSyntacticDiagnostics(sourceFile),
Expand Down
4 changes: 2 additions & 2 deletions src/compilerSetup.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as typescript from 'typescript';
import * as semver from 'semver';
import * as typescript from 'typescript';

import * as constants from './constants';
import * as logger from './logger';
import { LoaderOptions } from './interfaces';
import * as logger from './logger';

export function getCompiler(loaderOptions: LoaderOptions, log: logger.Logger) {
let compiler: typeof typescript | undefined;
Expand Down
6 changes: 3 additions & 3 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as typescript from 'typescript';
import * as path from 'path';
import { Chalk } from 'chalk';
import * as path from 'path';
import * as typescript from 'typescript';
import { LoaderOptions, Webpack, WebpackError } from './interfaces';
import * as logger from './logger';
import { formatErrors } from './utils';
import { LoaderOptions, Webpack, WebpackError } from './interfaces';

interface ConfigFile {
config?: any;
Expand Down
3 changes: 3 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@ export const ScriptTargetES2015 = 2;

export const ModuleKindCommonJs = 1;

export const extensionRegex = /\.[^.]+$/;
export const tsxRegex = /\.tsx$/i;
export const tsTsxRegex = /\.ts(x?)$/i;
export const dtsDtsxOrDtsDtsxMapRegex = /\.d\.ts(x?)(\.map)?$/i;
export const dtsTsTsxRegex = /(\.d)?\.ts(x?)$/i;
export const dtsTsTsxJsJsxRegex = /((\.d)?\.ts(x?)|js(x?))$/i;
export const tsTsxJsJsxRegex = /\.tsx?$|\.jsx?$/i;
export const jsJsx = /\.js(x?)$/i;
export const jsJsxMap = /\.js(x?)\.map$/i;
export const jsonRegex = /\.json$/i;
export const nodeModules = /node_modules/i;

0 comments on commit 635f745

Please sign in to comment.