Skip to content

Commit 814405e

Browse files
authoredFeb 12, 2020
fix(compiler): allow transformation of typescript files in node_modules (#1385)
1 parent 879454d commit 814405e

13 files changed

+6454
-56
lines changed
 

‎.travis.yml

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ script:
4040
else
4141
npm run test -- --runInBand;
4242
fi
43+
- npm run test:monorepo
4344

4445
after_success:
4546
# report coverages to coveralls
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const A = 1.0
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { A } from './imported-module'
2+
3+
export default A
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "dependency",
3+
"version": "0.0.1",
4+
"peerDependencies": {
5+
"typescript": "^3.7.5"
6+
},
7+
"main": "./index.ts"
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es5",
4+
"module": "commonjs",
5+
"lib": [
6+
"esnext"
7+
],
8+
"allowJs": true,
9+
"declaration": true,
10+
"sourceMap": true,
11+
"declarationDir": "./lib",
12+
"outDir": "./lib",
13+
"downlevelIteration": true,
14+
"strict": true,
15+
"moduleResolution": "node",
16+
"esModuleInterop": true
17+
},
18+
"include": [
19+
"./src"
20+
]
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import A from 'dependency'
2+
3+
export const fn = () => A

‎e2e/__monorepos__/simple/with-dependency/package-lock.json

+6,248
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"name": "with-dependency",
3+
"version": "0.0.1",
4+
"scripts": {
5+
"test": "jest '/test/index.spec.ts' --no-cache"
6+
},
7+
"jest": {
8+
"preset": "ts-jest",
9+
"transformIgnorePatterns": [
10+
"<rootDir>/node_modules/(?!(dependency))"
11+
],
12+
"transform": {
13+
"^.+\\.tsx?$": "ts-jest"
14+
},
15+
"testRegex": "/test/index.spec.ts$",
16+
"moduleDirectories": [
17+
"<rootDir>/node_modules",
18+
"node_modules"
19+
],
20+
"moduleFileExtensions": [
21+
"ts",
22+
"tsx",
23+
"js",
24+
"json",
25+
"node"
26+
],
27+
"moduleNameMapper": {
28+
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/mocks/fileMock.js",
29+
"\\.(s?css|sass)$": "<rootDir>/mocks/styleMock.js"
30+
},
31+
"globals": {
32+
"ts-jest": {
33+
"diagnostics": true,
34+
"tsConfig": "<rootDir>/tsconfig.json"
35+
}
36+
}
37+
},
38+
"devDependencies": {
39+
"typescript": "^3.7.5"
40+
},
41+
"dependencies": {
42+
"@types/jest": "^25.1.2",
43+
"dependency": "file:../dependency",
44+
"jest": "^25.1.0",
45+
"ts-jest": "^25.0.0"
46+
},
47+
"main": "./index.ts"
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { fn } from '../index'
2+
3+
describe('simple test', () => {
4+
it('should compile typescript function from node_modules', async () => {
5+
expect(fn()).toEqual(1.0)
6+
})
7+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es5",
4+
"module": "commonjs",
5+
"lib": [
6+
"esnext"
7+
],
8+
"allowJs": true,
9+
"declaration": true,
10+
"sourceMap": true,
11+
"declarationDir": "./lib",
12+
"outDir": "./lib",
13+
"downlevelIteration": true,
14+
"strict": true,
15+
"moduleResolution": "node",
16+
"esModuleInterop": true
17+
},
18+
"include": [
19+
"./src"
20+
]
21+
}

‎package.json

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"test:e2e:update-snaphots": "node scripts/e2e.js --updateSnapshot",
1919
"test:unit": "node_modules/.bin/jest",
2020
"test:external": "node scripts/test-external-project.js",
21+
"test:monorepo": "npm run test:external monorepo",
2122
"lint": "run-s lint:ts lint:js",
2223
"lint:js": "node_modules/.bin/eslint . -f stylish",
2324
"lint:ts": "node_modules/.bin/tslint -t stylish --project .",

‎scripts/test-external-project.js

+65-55
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,9 @@ const jestArgs = process.argv.slice(3)
1515
let gitUrl = false
1616

1717
const randomStr = () => parseInt(Math.random() * 1e17, 10).toString(36)
18-
19-
if (/^((https|ssh|git):\/\/|[a-z0-9]+@[a-z0-9.]+:).+$/.test(projectPath)) {
20-
gitUrl = projectPath
21-
projectPath = resolve(tmpdir(), 'ts-jest-git-ext', randomStr(), randomStr())
22-
} else {
18+
const executeTest = (path) => {
2319
try {
24-
projectPath = realpathSync(resolve(process.cwd(), projectPath))
20+
projectPath = realpathSync(resolve(process.cwd(), path))
2521
} catch (err) {
2622
projectPath = undefined
2723
}
@@ -33,61 +29,75 @@ if (/^((https|ssh|git):\/\/|[a-z0-9]+@[a-z0-9.]+:).+$/.test(projectPath)) {
3329
logger.error('First argument must be the path to a project or a git URL')
3430
process.exit(1)
3531
}
36-
}
32+
// first we need to create a bundle
33+
const bundle = createBundle()
3734

38-
// first we need to create a bundle
39-
const bundle = createBundle()
35+
// if it's a git URL we first need to clone
36+
if (gitUrl) {
37+
logger.log('found what could be a git URL, trying to clone')
38+
spawnSync('git', ['clone', gitUrl, projectPath])
39+
}
4040

41-
// if it's a git URL we first need to clone
42-
if (gitUrl) {
43-
logger.log('found what could be a git URL, trying to clone')
44-
spawnSync('git', ['clone', gitUrl, projectPath])
45-
}
41+
// we change current directory
42+
process.chdir(projectPath)
4643

47-
// we change current directory
48-
process.chdir(projectPath)
44+
// reading package.json
45+
const projectPkg = require(join(projectPath, 'package.json'))
46+
if (!projectPkg.name) projectPkg.name = 'unknown'
47+
if (!projectPkg.version) projectPkg.version = 'unknown'
4948

50-
// reading package.json
51-
const projectPkg = require(join(projectPath, 'package.json'))
52-
if (!projectPkg.name) projectPkg.name = 'unknown'
53-
if (!projectPkg.version) projectPkg.version = 'unknown'
49+
logger.log()
50+
logger.log(
51+
'='.repeat(20),
52+
`${projectPkg.name}@${projectPkg.version}`,
53+
'in',
54+
projectPath,
55+
'='.repeat(20)
56+
)
57+
logger.log()
5458

55-
logger.log()
56-
logger.log(
57-
'='.repeat(20),
58-
`${projectPkg.name}@${projectPkg.version}`,
59-
'in',
60-
projectPath,
61-
'='.repeat(20)
62-
)
63-
logger.log()
59+
// then we install it in the repo
60+
logger.log('ensuring all depedencies of target project are installed')
61+
npm.spawnSync(
62+
['install', '--no-package-lock', '--no-shrinkwrap', '--no-save'],
63+
{ cwd: projectPath }
64+
)
65+
logger.log('installing bundled version of ts-jest')
66+
npm.spawnSync(
67+
['install', '--no-package-lock', '--no-shrinkwrap', '--no-save', bundle],
68+
{ cwd: projectPath }
69+
)
6470

65-
// then we install it in the repo
66-
logger.log('ensuring all depedencies of target project are installed')
67-
npm.spawnSync(
68-
['install', '--no-package-lock', '--no-shrinkwrap', '--no-save'],
69-
{ cwd: projectPath }
70-
)
71-
logger.log('installing bundled version of ts-jest')
72-
npm.spawnSync(
73-
['install', '--no-package-lock', '--no-shrinkwrap', '--no-save', bundle],
74-
{ cwd: projectPath }
75-
)
71+
// then we can run the tests
72+
const cmdLine =
73+
projectPkg.scripts && projectPkg.scripts.test
74+
? ['npm', 'test', '--']
75+
: ['jest']
76+
cmdLine.push(...jestArgs)
7677

77-
// then we can run the tests
78-
const cmdLine =
79-
projectPkg.scripts && projectPkg.scripts.test
80-
? ['npm', 'test', '--']
81-
: ['jest']
82-
cmdLine.push(...jestArgs)
78+
logger.log('starting the tests using:', ...cmdLine)
79+
logger.log()
8380

84-
logger.log('starting the tests using:', ...cmdLine)
85-
logger.log()
81+
spawnSync(cmdLine.shift(), cmdLine, {
82+
cwd: projectPath,
83+
stdio: 'inherit',
84+
env: Object.assign({}, process.env, {
85+
TS_JEST_IGNORE_DIAGNOSTICS: '5023,5024',
86+
}),
87+
})
88+
}
8689

87-
spawnSync(cmdLine.shift(), cmdLine, {
88-
cwd: projectPath,
89-
stdio: 'inherit',
90-
env: Object.assign({}, process.env, {
91-
TS_JEST_IGNORE_DIAGNOSTICS: '5023,5024',
92-
}),
93-
})
90+
if (/^((https|ssh|git):\/\/|[a-z0-9]+@[a-z0-9.]+:).+$/.test(projectPath)) {
91+
gitUrl = projectPath
92+
projectPath = resolve(tmpdir(), 'ts-jest-git-ext', randomStr(), randomStr())
93+
} else {
94+
if (projectPath === 'monorepo') {
95+
[
96+
'e2e/__monorepos__/simple/with-dependency',
97+
].forEach(monorepoPath => {
98+
executeTest(monorepoPath)
99+
})
100+
} else {
101+
executeTest(projectPath)
102+
}
103+
}

‎src/compiler.ts

+27-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { LogContexts, LogLevels, Logger } from 'bs-logger'
3333
import bufferFrom = require('buffer-from')
3434
import stableStringify = require('fast-json-stable-stringify')
3535
import { readFileSync, writeFileSync } from 'fs'
36+
import { defaults as JestDefaults, replaceRootDirInPath } from 'jest-config/build'
3637
import memoize = require('lodash.memoize')
3738
import mkdirp = require('mkdirp')
3839
import { basename, extname, join, normalize, relative } from 'path'
@@ -134,6 +135,11 @@ export function createCompiler(configs: ConfigSet): TsCompiler {
134135
[LogContexts.logLevel]: LogLevels.trace,
135136
}
136137

138+
const transformIgnorePattern = (configs.jest.transformIgnorePatterns || JestDefaults.transformIgnorePatterns)
139+
.map(pattern => replaceRootDirInPath(configs.rootDir, pattern))
140+
.join('|')
141+
const transformIgnoreRegExp = new RegExp(transformIgnorePattern)
142+
137143
const serviceHost: LanguageServiceHost = {
138144
getScriptFileNames: () => Object.keys(memoryCache.versions),
139145
getScriptVersion: (fileName: string) => {
@@ -183,7 +189,7 @@ export function createCompiler(configs: ConfigSet): TsCompiler {
183189
// Must set memory cache before attempting to read file.
184190
updateMemoryCache(code, fileName)
185191

186-
const output = service.getEmitOutput(fileName)
192+
let output = service.getEmitOutput(fileName)
187193

188194
if (configs.shouldReportDiagnostic(fileName)) {
189195
logger.debug({ fileName }, 'getOutput(): computing diagnostics')
@@ -202,6 +208,26 @@ export function createCompiler(configs: ConfigSet): TsCompiler {
202208
throw new TypeError(`${relative(cwd, fileName)}: Emit skipped`)
203209
}
204210

211+
// if this block executes we have attempted to compile a resolved dependency
212+
// that typescript ignored, likely because the file was located in node_modules.
213+
// we can force the compiler to retry by invalidating its internal module
214+
// resolution cache.
215+
//
216+
// @see https://github.com/Microsoft/TypeScript/issues/12358
217+
// @see https://github.com/microsoft/TypeScript/issues/11946
218+
if (output.outputFiles.length === 0 && !transformIgnoreRegExp.test(fileName)) {
219+
const normalizedFileName = normalize(fileName)
220+
221+
// our implementation of getScriptFileNames() depends on the keys
222+
// of memoryCache.versions so we must make sure that the file is
223+
// in there.
224+
if (!hasOwn.call(memoryCache.versions, normalizedFileName)) {
225+
memoryCache.versions[normalizedFileName] = 0
226+
}
227+
228+
output = service.getEmitOutput(fileName)
229+
}
230+
205231
// Throw an error when requiring `.d.ts` files.
206232
/* istanbul ignore next (this should never happen but is kept for security) */
207233
if (output.outputFiles.length === 0) {

0 commit comments

Comments
 (0)
Please sign in to comment.