Skip to content

Commit 32aaba7

Browse files
committed
fix(compiler): toggle projectReferences support by a ts-jest option
1 parent 5cdbabb commit 32aaba7

13 files changed

Lines changed: 181 additions & 61 deletions

File tree

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
title: experimentalProjectReferences option
3+
---
4+
5+
By default `ts-jest` doesn't support TypeScript `projectReferences`. This option allows one to ask `ts-jest` to find files
6+
which are generated by `tsc -b ` with TypeScript `projectReferences` configured in `tsconfig`.
7+
8+
This mode is still in experimental period. It requires the project has to be built first with `tsc -b`.
9+
10+
Here is how to enable `projectReferences` support:
11+
12+
### Example
13+
14+
<div class="row"><div class="col-md-6" markdown="block">
15+
16+
```js
17+
// jest.config.js
18+
module.exports = {
19+
// [...]
20+
globals: {
21+
'ts-jest': {
22+
experimentalProjectReferences: true
23+
}
24+
}
25+
};
26+
```
27+
28+
</div><div class="col-md-6" markdown="block">
29+
30+
```js
31+
// OR package.json
32+
{
33+
// [...]
34+
"jest": {
35+
"globals": {
36+
"ts-jest": {
37+
"experimentalProjectReferences": true
38+
}
39+
}
40+
}
41+
}
42+
```
43+
44+
</div></div>

docs/user/config/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ All options have default values which should fit most of the projects. Click on
211211
| [**`babelConfig`**][babelConfig] | [Babel(Jest) related configuration.][babelConfig] | `boolean`\|`string`\|`object` | _disabled_ |
212212
| [**`stringifyContentPathRegex`**][stringifyContentPathRegex] | [Files which will become modules returning self content.][stringifyContentPathRegex] | `string`\|`RegExp` | _disabled_ |
213213
| [**`packageJson`**][packageJson] | [Package metadata.][packageJson] | `string`\|`object`\|`boolean` | _auto_ |
214+
| [**`experimentalProjectReferences`**][experimentalProjectReferences] | [TypeScript projectReferences support][experimentalProjectReferences] | `boolean` | _disabled_ |
214215

215216
### Upgrading
216217

e2e/__external-repos__/simple-project-references/jest.config.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
/** @type {import('@jest/types').Config.InitialOptions} */
2+
/** @typedef {import('ts-jest')} */
3+
14
module.exports = {
25
preset: 'ts-jest',
36
testEnvironment: 'node',
@@ -7,6 +10,7 @@ module.exports = {
710
globals: {
811
'ts-jest': {
912
isolatedModules: true,
13+
experimentalProjectReferences: true,
1014
},
1115
},
1216
}

e2e/__external-repos__/yarn-workspace-composite/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"packages/*"
77
],
88
"scripts": {
9-
"test": "yarn tsc -b packages/my-app/tsconfig.json && jest --no-cache"
9+
"test": "jest --no-cache"
1010
},
1111
"devDependencies": {
1212
"@types/jest": "^25.2.1",

src/__helpers__/fakers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export function tsJestConfig(options?: Partial<TsJestConfig>): TsJestConfig {
2020
babelConfig: undefined,
2121
tsConfig: undefined,
2222
packageJson: undefined,
23+
experimentalProjectReferences: false,
2324
stringifyContentPathRegex: undefined,
2425
diagnostics: { ignoreCodes: [], pretty: false, throws: true },
2526
...options,

src/compiler/language-service.spec.ts

Lines changed: 61 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { makeCompiler } from '../__helpers__/fakers'
66
import { logTargetMock } from '../__helpers__/mocks'
77
import { tempDir } from '../__helpers__/path'
88
import ProcessedSource from '../__helpers__/processed-source'
9+
import { TsCompiler } from '../types'
910
import { normalizeSlashes } from '../util/normalize-slashes'
1011

1112
import * as compilerUtils from './compiler-utils'
@@ -17,42 +18,40 @@ describe('Language service', () => {
1718
logTarget.clear()
1819
})
1920

20-
it('should get compile result from referenced project when there is a built reference project', () => {
21-
const tmp = tempDir('compiler')
22-
const compiler = makeCompiler({
23-
jestConfig: { cache: true, cacheDirectory: tmp },
24-
tsJestConfig: { tsConfig: false },
25-
})
26-
const source = 'console.log("hello")'
27-
const fileName = 'test-reference-project.ts'
28-
const getAndCacheProjectReferenceSpy = jest
29-
.spyOn(compilerUtils, 'getAndCacheProjectReference')
30-
.mockReturnValueOnce({} as any)
31-
jest
32-
.spyOn(compilerUtils, 'getCompileResultFromReferencedProject')
33-
.mockImplementationOnce(() => [
34-
source,
35-
'{"version":3,"file":"test-reference-project.js","sourceRoot":"","sources":["test-reference-project.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA","sourcesContent":["console.log(\\"hello\\")"]}',
36-
])
37-
writeFileSync(fileName, source, 'utf8')
38-
39-
compiler.compile(source, fileName)
40-
41-
expect(getAndCacheProjectReferenceSpy).toHaveBeenCalled()
42-
expect(compilerUtils.getCompileResultFromReferencedProject).toHaveBeenCalled()
43-
44-
jest.restoreAllMocks()
45-
removeSync(fileName)
46-
})
47-
48-
it('should get compile result from language service when there is no referenced project', () => {
49-
const tmp = tempDir('compiler')
50-
const compiler = makeCompiler({
51-
jestConfig: { cache: true, cacheDirectory: tmp },
52-
tsJestConfig: { tsConfig: false },
53-
})
21+
it(
22+
'should get compile result from referenced project when there is a built reference project and' +
23+
' experimentalProjectReferences is enabled',
24+
() => {
25+
const tmp = tempDir('compiler')
26+
const compiler = makeCompiler({
27+
jestConfig: { cache: true, cacheDirectory: tmp },
28+
tsJestConfig: { tsConfig: false, experimentalProjectReferences: true },
29+
})
30+
const source = 'console.log("hello")'
31+
const fileName = 'test-reference-project.ts'
32+
const getAndCacheProjectReferenceSpy = jest
33+
.spyOn(compilerUtils, 'getAndCacheProjectReference')
34+
.mockReturnValueOnce({} as any)
35+
jest
36+
.spyOn(compilerUtils, 'getCompileResultFromReferencedProject')
37+
.mockImplementationOnce(() => [
38+
source,
39+
'{"version":3,"file":"test-reference-project.js","sourceRoot":"","sources":["test-reference-project.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA","sourcesContent":["console.log(\\"hello\\")"]}',
40+
])
41+
writeFileSync(fileName, source, 'utf8')
42+
43+
compiler.compile(source, fileName)
44+
45+
expect(getAndCacheProjectReferenceSpy).toHaveBeenCalled()
46+
expect(compilerUtils.getCompileResultFromReferencedProject).toHaveBeenCalled()
47+
48+
jest.restoreAllMocks()
49+
removeSync(fileName)
50+
},
51+
)
52+
53+
function runTestsForNonProjectReferences(compiler: TsCompiler, fileName: string) {
5454
const source = 'console.log("hello")'
55-
const fileName = 'test-no-reference-project.ts'
5655
const getAndCacheProjectReferenceSpy = jest
5756
.spyOn(compilerUtils, 'getAndCacheProjectReference')
5857
.mockReturnValueOnce(undefined)
@@ -66,7 +65,33 @@ describe('Language service', () => {
6665

6766
jest.restoreAllMocks()
6867
removeSync(fileName)
69-
})
68+
}
69+
70+
it(
71+
'should get compile result from language service when there is no referenced project' +
72+
' and experimentalProjectReferences is enabled',
73+
() => {
74+
const tmp = tempDir('compiler')
75+
const compiler = makeCompiler({
76+
jestConfig: { cache: true, cacheDirectory: tmp },
77+
tsJestConfig: { tsConfig: false, experimentalProjectReferences: true },
78+
})
79+
runTestsForNonProjectReferences(compiler, 'test-no-reference-project-1.ts')
80+
},
81+
)
82+
83+
it(
84+
'should get compile result from language service when there is no referenced project' +
85+
' and experimentalProjectReferences is disabled',
86+
() => {
87+
const tmp = tempDir('compiler')
88+
const compiler = makeCompiler({
89+
jestConfig: { cache: true, cacheDirectory: tmp },
90+
tsJestConfig: { tsConfig: false },
91+
})
92+
runTestsForNonProjectReferences(compiler, 'test-no-reference-project-2.ts')
93+
},
94+
)
7095

7196
it('should cache resolved modules for test file with testMatchPatterns from jest config when match', () => {
7297
const spy = jest.spyOn(compilerUtils, 'cacheResolvedModules').mockImplementationOnce(() => {})

src/compiler/language-service.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export const initializeLanguageServiceInstance = (
3838
const cwd = configs.cwd
3939
const cacheDir = configs.tsCacheDir
4040
const { options, projectReferences, fileNames } = configs.parsedTsConfig
41+
const { experimentalProjectReferences } = configs.tsJest
4142
const serviceHostTraceCtx = {
4243
namespace: 'ts:serviceHost',
4344
call: null,
@@ -82,9 +83,8 @@ export const initializeLanguageServiceInstance = (
8283

8384
if (shouldIncrementProjectVersion) projectVersion++
8485
}
85-
const serviceHost: _ts.LanguageServiceHost = {
86+
let serviceHost: _ts.LanguageServiceHost = {
8687
getProjectVersion: () => String(projectVersion),
87-
getProjectReferences: () => projectReferences,
8888
getScriptFileNames: () => [...memoryCache.files.keys()],
8989
getScriptVersion: (fileName: string) => {
9090
const normalizedFileName = normalize(fileName)
@@ -128,6 +128,12 @@ export const initializeLanguageServiceInstance = (
128128
getDefaultLibFileName: () => ts.getDefaultLibFilePath(options),
129129
getCustomTransformers: () => configs.tsCustomTransformers,
130130
}
131+
if (experimentalProjectReferences) {
132+
serviceHost = {
133+
...serviceHost,
134+
getProjectReferences: () => projectReferences,
135+
}
136+
}
131137

132138
logger.debug('initializeLanguageServiceInstance(): creating language service')
133139

@@ -145,7 +151,7 @@ export const initializeLanguageServiceInstance = (
145151
memoryCache.files,
146152
projectReferences,
147153
)
148-
if (referencedProject !== undefined) {
154+
if (experimentalProjectReferences && referencedProject !== undefined) {
149155
logger.debug({ fileName }, 'compileFn(): get compile result from referenced project')
150156

151157
return getCompileResultFromReferencedProject(fileName, configs, memoryCache.files, referencedProject)

src/compiler/transpiler.spec.ts

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as _ts from 'typescript'
44
import { makeCompiler } from '../__helpers__/fakers'
55
import ProcessedSource from '../__helpers__/processed-source'
66
import { TS_JEST_OUT_DIR } from '../config/config-set'
7+
import { TsCompiler } from '../types'
78

89
import * as compilerUtils from './compiler-utils'
910

@@ -14,7 +15,8 @@ describe('Transpiler', () => {
1415

1516
it(
1617
'should call createProgram() with projectReferences, call getAndCacheProjectReference()' +
17-
' and getCompileResultFromReferenceProject() when there are projectReferences from tsconfig',
18+
' and getCompileResultFromReferenceProject() when there are projectReferences from tsconfig and' +
19+
' experimentalProjectReferences is enabled',
1820
() => {
1921
const programSpy = jest.spyOn(_ts, 'createProgram')
2022
const source = 'console.log("hello")'
@@ -32,6 +34,7 @@ describe('Transpiler', () => {
3234
const compiler = makeCompiler({
3335
tsJestConfig: {
3436
...baseTsJestConfig,
37+
experimentalProjectReferences: true,
3538
tsConfig: 'src/__mocks__/tsconfig-project-references.json',
3639
},
3740
})
@@ -47,21 +50,14 @@ describe('Transpiler', () => {
4750
},
4851
)
4952

50-
it('should call createProgram() without projectReferences when there are no projectReferences from tsconfig', () => {
53+
function runTestsForNonProjectReferences(compiler: TsCompiler, fileName: string) {
5154
const programSpy = jest.spyOn(_ts, 'createProgram')
5255
const source = 'console.log("hello")'
53-
const fileName = 'isolated-test-reference-project-1.ts'
5456
const getAndCacheProjectReferenceSpy = jest
5557
.spyOn(compilerUtils, 'getAndCacheProjectReference')
5658
.mockReturnValueOnce(undefined)
5759
jest.spyOn(compilerUtils, 'getCompileResultFromReferencedProject')
5860
writeFileSync(fileName, source, 'utf8')
59-
const compiler = makeCompiler({
60-
tsJestConfig: {
61-
...baseTsJestConfig,
62-
tsConfig: false,
63-
},
64-
})
6561
compiler.compile(source, fileName)
6662

6763
expect(programSpy).toHaveBeenCalled()
@@ -71,7 +67,36 @@ describe('Transpiler', () => {
7167

7268
jest.restoreAllMocks()
7369
removeSync(fileName)
74-
})
70+
}
71+
72+
it(
73+
'should call createProgram() without projectReferences when there are no projectReferences from tsconfig' +
74+
' and experimentalProjectReferences is enabled',
75+
() => {
76+
const compiler = makeCompiler({
77+
tsJestConfig: {
78+
...baseTsJestConfig,
79+
experimentalProjectReferences: true,
80+
tsConfig: false,
81+
},
82+
})
83+
runTestsForNonProjectReferences(compiler, 'isolated-test-reference-project-1.ts')
84+
},
85+
)
86+
87+
it(
88+
'should call createProgram() without projectReferences when there are no projectReferences from tsconfig' +
89+
' and experimentalProjectReferences is disabled',
90+
() => {
91+
const compiler = makeCompiler({
92+
tsJestConfig: {
93+
...baseTsJestConfig,
94+
tsConfig: false,
95+
},
96+
})
97+
runTestsForNonProjectReferences(compiler, 'isolated-test-reference-project-2.ts')
98+
},
99+
)
75100

76101
it('should compile js file for allowJs true', () => {
77102
const fileName = `${__filename}.test.js`

src/compiler/transpiler.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,16 @@ export const initializeTranspilerInstance = (
1717
logger.debug('initializeTranspilerInstance(): create typescript compiler')
1818

1919
const { options, projectReferences, fileNames } = configs.parsedTsConfig
20+
const { experimentalProjectReferences } = configs.tsJest
2021
const ts = configs.compilerModule
21-
const program = projectReferences
22-
? ts.createProgram({
23-
rootNames: fileNames,
24-
options,
25-
projectReferences,
26-
})
27-
: ts.createProgram([], options)
22+
const program =
23+
experimentalProjectReferences && projectReferences
24+
? ts.createProgram({
25+
rootNames: fileNames,
26+
options,
27+
projectReferences,
28+
})
29+
: ts.createProgram([], options)
2830
/* istanbul ignore next (we leave this for e2e) */
2931
const updateFileInCache = (contents: string, filePath: string) => {
3032
const file = memoryCache.files.get(filePath)
@@ -39,7 +41,7 @@ export const initializeTranspilerInstance = (
3941
updateFileInCache(code, fileName)
4042
const referencedProject = getAndCacheProjectReference(fileName, program, memoryCache.files, projectReferences)
4143
/* istanbul ignore next (referencedProject object is too complex to mock so we leave this for e2e) */
42-
if (referencedProject !== undefined) {
44+
if (experimentalProjectReferences && referencedProject !== undefined) {
4345
logger.debug({ fileName }, 'compileFn(): get compile result from referenced project')
4446

4547
return getCompileResultFromReferencedProject(fileName, configs, memoryCache.files, referencedProject)

src/config/__snapshots__/config-set.spec.ts.snap

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`cacheKey should be a string 1`] = `"{\\"digest\\":\\"a0d51ca854194df8191d0e65c0ca4730f510f332\\",\\"jest\\":{\\"__backported\\":true,\\"globals\\":{}},\\"projectDepVersions\\":{\\"dev\\":\\"1.2.5\\",\\"opt\\":\\"1.2.3\\",\\"peer\\":\\"1.2.4\\",\\"std\\":\\"1.2.6\\"},\\"transformers\\":[\\"hoisting-jest-mock@1\\"],\\"tsJest\\":{\\"compiler\\":\\"typescript\\",\\"diagnostics\\":{\\"ignoreCodes\\":[6059,18002,18003],\\"pretty\\":true,\\"throws\\":true},\\"experimentalProjectReferences\\":false,\\"isolatedModules\\":false,\\"packageJson\\":{\\"kind\\":\\"file\\"},\\"transformers\\":[]},\\"tsconfig\\":{\\"declaration\\":false,\\"inlineSourceMap\\":false,\\"inlineSources\\":true,\\"module\\":1,\\"noEmit\\":false,\\"removeComments\\":false,\\"sourceMap\\":true,\\"target\\":1}}"`;
4+
35
exports[`jest should merge parent config if any with globals is an empty object 1`] = `
46
Object {
57
"__backported": true,
@@ -51,6 +53,7 @@ Object {
5153
"pretty": true,
5254
"throws": true,
5355
},
56+
"experimentalProjectReferences": false,
5457
"isolatedModules": false,
5558
"packageJson": Object {
5659
"kind": "file",
@@ -165,6 +168,7 @@ Object {
165168
"pretty": true,
166169
"throws": true,
167170
},
171+
"experimentalProjectReferences": false,
168172
"isolatedModules": false,
169173
"packageJson": Object {
170174
"kind": "file",

0 commit comments

Comments
 (0)