Skip to content

Commit bb516da

Browse files
thebanjomaticAdam Hines
and
Adam Hines
authored
fix: parse extended tsconfigs when transpiling script blocks (#502)
* fix(tsconfig): parse extended tsconfigs when transpiling script blocks A change introduced in v28.1.0 in PR #471 unintentionally changed the behavior of the tsconfig parsing such that configs using "extends" were no longer being considered. Fixes: #495 * chore(cache): cache tsconfig parsing to avoid the cost per vue file / interpolated string Co-authored-by: Adam Hines <[email protected]>
1 parent 62d6ebc commit bb516da

16 files changed

+220
-61
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<template>
2+
<div>
3+
{{ exclamationMarks }}
4+
<type-script-child />
5+
</div>
6+
</template>
7+
8+
<script lang="ts">
9+
import TypeScriptChild from './TypeScriptChild.vue'
10+
11+
import moduleRequiringEsModuleInterop from './ModuleRequiringEsModuleInterop'
12+
13+
// The default import above relies on esModuleInterop being set to true in order to use it from
14+
// an import statement instead of require. This option is configured in the tsconfig.base.json,
15+
// so if we are no longer fully processing the tsconfig options (extended from a base config)
16+
// this test should fail. This was one of the only reliable ways I could get a test to fail if
17+
// these conditions are not being met and happen to be the use-case which was triggering errors
18+
// in my config setup.
19+
20+
if (moduleRequiringEsModuleInterop()) {
21+
throw new Error('Should never hit this')
22+
}
23+
24+
export default {
25+
computed: {
26+
exclamationMarks(): string {
27+
return 'string'
28+
}
29+
},
30+
components: {
31+
TypeScriptChild
32+
}
33+
}
34+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = () => false

e2e/2.x/basic/test.js

+7
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import Jsx from './components/Jsx.vue'
2121
import Constructor from './components/Constructor.vue'
2222
import { compileStyle } from '@vue/component-compiler-utils'
2323
import ScriptSetup from './components/ScriptSetup'
24+
import ExtendedTsConfig from './components/ExtendedTsConfig.vue'
25+
2426
jest.mock('@vue/component-compiler-utils', () => ({
2527
...jest.requireActual('@vue/component-compiler-utils'),
2628
compileStyle: jest.fn(() => ({ errors: [], code: '' }))
@@ -163,6 +165,11 @@ test('processes SFC with <script setup>', () => {
163165
expect(wrapper.html()).toContain('Welcome to Your Vue.js App')
164166
})
165167

168+
test('handles extended tsconfig.json files', () => {
169+
const wrapper = mount(ExtendedTsConfig)
170+
expect(wrapper.element.tagName).toBe('DIV')
171+
})
172+
166173
test('should pass properly "styleOptions" into "preprocessOptions"', () => {
167174
const filePath = resolve(__dirname, './components/Basic.vue')
168175
const fileString = readFileSync(filePath, { encoding: 'utf8' })

e2e/2.x/basic/tsconfig.base.json

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es5",
4+
"lib": ["dom", "es6"],
5+
"module": "es2015",
6+
"moduleResolution": "node",
7+
"types": ["vue-typescript-import-dts", "node"],
8+
"isolatedModules": false,
9+
"experimentalDecorators": true,
10+
"noImplicitAny": true,
11+
"noImplicitThis": true,
12+
"strictNullChecks": true,
13+
"removeComments": true,
14+
"emitDecoratorMetadata": true,
15+
"suppressImplicitAnyIndexErrors": true,
16+
"allowSyntheticDefaultImports": true,
17+
"sourceMap": true,
18+
"esModuleInterop": true,
19+
"allowJs": true
20+
}
21+
}

e2e/2.x/basic/tsconfig.json

+1-18
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,3 @@
11
{
2-
"compilerOptions": {
3-
"target": "es5",
4-
"lib": ["dom", "es6"],
5-
"module": "es2015",
6-
"moduleResolution": "node",
7-
"types": ["vue-typescript-import-dts", "node"],
8-
"isolatedModules": false,
9-
"experimentalDecorators": true,
10-
"noImplicitAny": true,
11-
"noImplicitThis": true,
12-
"strictNullChecks": true,
13-
"removeComments": true,
14-
"emitDecoratorMetadata": true,
15-
"suppressImplicitAnyIndexErrors": true,
16-
"allowSyntheticDefaultImports": true,
17-
"sourceMap": true,
18-
"allowJs": true
19-
}
2+
"extends": "./tsconfig.base.json"
203
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<template>
2+
<div>
3+
{{ exclamationMarks }}
4+
<type-script-child />
5+
</div>
6+
</template>
7+
8+
<script lang="ts">
9+
import TypeScriptChild from './TypeScriptChild.vue'
10+
11+
import moduleRequiringEsModuleInterop from './ModuleRequiringEsModuleInterop'
12+
13+
// The default import above relies on esModuleInterop being set to true in order to use it from
14+
// an import statement instead of require. This option is configured in the tsconfig.base.json,
15+
// so if we are no longer fully processing the tsconfig options (extended from a base config)
16+
// this test should fail. This was one of the only reliable ways I could get a test to fail if
17+
// these conditions are not being met and happen to be the use-case which was triggering errors
18+
// in my config setup.
19+
20+
if (moduleRequiringEsModuleInterop()) {
21+
throw new Error('Should never hit this')
22+
}
23+
24+
export default {
25+
computed: {
26+
exclamationMarks(): string {
27+
return 'string'
28+
}
29+
},
30+
components: {
31+
TypeScriptChild
32+
}
33+
}
34+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = () => false

e2e/3.x/basic/test.js

+7
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import ScriptSetup from './components/ScriptSetup.vue'
2323
import ScriptSetupSugarRef from './components/ScriptSetupSugarRef.vue'
2424
import FunctionalRenderFn from './components/FunctionalRenderFn.vue'
2525
import CompilerDirective from './components/CompilerDirective.vue'
26+
import ExtendedTsConfig from './components/ExtendedTsConfig.vue'
2627

2728
// TODO: JSX for Vue 3? TSX?
2829
import Jsx from './components/Jsx.vue'
@@ -207,3 +208,9 @@ test('ensure compilerOptions is passed down', () => {
207208
const elm = document.querySelector('h1')
208209
expect(elm.hasAttribute('data-test')).toBe(false)
209210
})
211+
212+
test('handles extended tsconfig.json files', () => {
213+
mount(ExtendedTsConfig)
214+
const elm = document.querySelector('div')
215+
expect(elm).toBeDefined()
216+
})

e2e/3.x/basic/tsconfig.base.json

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es5",
4+
"lib": ["dom", "es6"],
5+
"module": "es2015",
6+
"moduleResolution": "node",
7+
"types": ["vue-typescript-import-dts", "node"],
8+
"isolatedModules": false,
9+
"experimentalDecorators": true,
10+
"noImplicitAny": true,
11+
"noImplicitThis": true,
12+
"strictNullChecks": true,
13+
"removeComments": true,
14+
"emitDecoratorMetadata": true,
15+
"suppressImplicitAnyIndexErrors": true,
16+
"allowSyntheticDefaultImports": true,
17+
"sourceMap": true,
18+
"esModuleInterop": true,
19+
"allowJs": true
20+
}
21+
}

e2e/3.x/basic/tsconfig.json

+1-18
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,3 @@
11
{
2-
"compilerOptions": {
3-
"target": "es5",
4-
"lib": ["dom", "es6"],
5-
"module": "es2015",
6-
"moduleResolution": "node",
7-
"types": ["vue-typescript-import-dts", "node"],
8-
"isolatedModules": false,
9-
"experimentalDecorators": true,
10-
"noImplicitAny": true,
11-
"noImplicitThis": true,
12-
"strictNullChecks": true,
13-
"removeComments": true,
14-
"emitDecoratorMetadata": true,
15-
"suppressImplicitAnyIndexErrors": true,
16-
"allowSyntheticDefaultImports": true,
17-
"sourceMap": true,
18-
"allowJs": true
19-
}
2+
"extends": "./tsconfig.base.json"
203
}

packages/vue2-jest/lib/ensure-require.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const throwError = require('./utils').throwError
1+
const throwError = require('./throw-error')
22

33
module.exports = function(name, deps) {
44
let i, len

packages/vue2-jest/lib/throw-error.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = function throwError(msg) {
2+
throw new Error('\n[vue-jest] Error: ' + msg + '\n')
3+
}

packages/vue2-jest/lib/utils.js

+41-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
const ensureRequire = require('./ensure-require')
2+
const throwError = require('./throw-error')
13
const constants = require('./constants')
24
const loadPartialConfig = require('@babel/core').loadPartialConfig
3-
const { loadSync: loadTsConfigSync } = require('tsconfig')
5+
const { resolveSync: resolveTsConfigSync } = require('tsconfig')
46
const chalk = require('chalk')
57
const path = require('path')
68
const fs = require('fs')
@@ -68,23 +70,55 @@ const getBabelOptions = function loadBabelOptions(filename, options = {}) {
6870
return loadPartialConfig(opts).options
6971
}
7072

73+
const tsConfigCache = new Map()
74+
7175
/**
7276
* Load TypeScript config from tsconfig.json.
7377
* @param {string | undefined} path tsconfig.json file path (default: root)
7478
* @returns {import('typescript').TranspileOptions | null} TypeScript compilerOptions or null
7579
*/
7680
const getTypeScriptConfig = function getTypeScriptConfig(path) {
77-
const tsconfig = loadTsConfigSync(process.cwd(), path || '')
78-
if (!tsconfig.path) {
81+
if (tsConfigCache.has(path)) {
82+
return tsConfigCache.get(path)
83+
}
84+
85+
ensureRequire('typescript', ['typescript'])
86+
const typescript = require('typescript')
87+
88+
const tsconfigPath = resolveTsConfigSync(process.cwd(), path || '')
89+
if (!tsconfigPath) {
7990
warn(`Not found tsconfig.json.`)
8091
return null
8192
}
82-
const compilerOptions =
83-
(tsconfig.config && tsconfig.config.compilerOptions) || {}
8493

85-
return {
86-
compilerOptions: { ...compilerOptions, module: 'commonjs' }
94+
const parsedConfig = typescript.getParsedCommandLineOfConfigFile(
95+
tsconfigPath,
96+
{},
97+
{
98+
...typescript.sys,
99+
onUnRecoverableConfigFileDiagnostic: e => {
100+
const errorMessage = typescript.formatDiagnostic(e, {
101+
getCurrentDirectory: () => process.cwd(),
102+
getNewLine: () => `\n`,
103+
getCanonicalFileName: file => file.replace(/\\/g, '/')
104+
})
105+
warn(errorMessage)
106+
}
107+
}
108+
)
109+
110+
const compilerOptions = parsedConfig ? parsedConfig.options : {}
111+
112+
const transpileConfig = {
113+
compilerOptions: {
114+
...compilerOptions,
115+
module: typescript.ModuleKind.CommonJS
116+
}
87117
}
118+
119+
tsConfigCache.set(path, transpileConfig)
120+
121+
return transpileConfig
88122
}
89123

90124
function isValidTransformer(transformer) {
@@ -131,10 +165,6 @@ const getCustomTransformer = function getCustomTransformer(
131165
: transformer
132166
}
133167

134-
const throwError = function error(msg) {
135-
throw new Error('\n[vue-jest] Error: ' + msg + '\n')
136-
}
137-
138168
const stripInlineSourceMap = function(str) {
139169
return str.slice(0, str.indexOf('//# sourceMappingURL'))
140170
}

packages/vue3-jest/lib/ensure-require.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const throwError = require('./utils').throwError
1+
const throwError = require('./throw-error')
22

33
module.exports = function(name, deps) {
44
let i, len

packages/vue3-jest/lib/throw-error.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = function throwError(msg) {
2+
throw new Error('\n[vue-jest] Error: ' + msg + '\n')
3+
}

packages/vue3-jest/lib/utils.js

+43-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
const ensureRequire = require('./ensure-require')
2+
const throwError = require('./throw-error')
13
const constants = require('./constants')
24
const loadPartialConfig = require('@babel/core').loadPartialConfig
3-
const { loadSync: loadTsConfigSync } = require('tsconfig')
5+
const { resolveSync: resolveTsConfigSync } = require('tsconfig')
46
const chalk = require('chalk')
57
const path = require('path')
68
const fs = require('fs')
@@ -68,24 +70,57 @@ const getBabelOptions = function loadBabelOptions(filename, options = {}) {
6870
return loadPartialConfig(opts).options
6971
}
7072

73+
const tsConfigCache = new Map()
74+
7175
/**
7276
* Load TypeScript config from tsconfig.json.
7377
* @param {string | undefined} path tsconfig.json file path (default: root)
7478
* @returns {import('typescript').TranspileOptions | null} TypeScript compilerOptions or null
7579
*/
7680
const getTypeScriptConfig = function getTypeScriptConfig(path) {
77-
const tsconfig = loadTsConfigSync(process.cwd(), path || '')
78-
if (!tsconfig.path) {
81+
if (tsConfigCache.has(path)) {
82+
return tsConfigCache.get(path)
83+
}
84+
85+
ensureRequire('typescript', ['typescript'])
86+
const typescript = require('typescript')
87+
88+
const tsconfigPath = resolveTsConfigSync(process.cwd(), path || '')
89+
if (!tsconfigPath) {
7990
warn(`Not found tsconfig.json.`)
8091
return null
8192
}
82-
const compilerOptions =
83-
(tsconfig.config && tsconfig.config.compilerOptions) || {}
8493

85-
// Force es5 to prevent const vue_1 = require('vue') from conflicting
86-
return {
87-
compilerOptions: { ...compilerOptions, target: 'es5', module: 'commonjs' }
94+
const parsedConfig = typescript.getParsedCommandLineOfConfigFile(
95+
tsconfigPath,
96+
{},
97+
{
98+
...typescript.sys,
99+
onUnRecoverableConfigFileDiagnostic: e => {
100+
const errorMessage = typescript.formatDiagnostic(e, {
101+
getCurrentDirectory: () => process.cwd(),
102+
getNewLine: () => `\n`,
103+
getCanonicalFileName: file => file.replace(/\\/g, '/')
104+
})
105+
warn(errorMessage)
106+
}
107+
}
108+
)
109+
110+
const compilerOptions = parsedConfig ? parsedConfig.options : {}
111+
112+
const transpileConfig = {
113+
compilerOptions: {
114+
...compilerOptions,
115+
// Force es5 to prevent const vue_1 = require('vue') from conflicting
116+
target: typescript.ScriptTarget.ES5,
117+
module: typescript.ModuleKind.CommonJS
118+
}
88119
}
120+
121+
tsConfigCache.set(path, transpileConfig)
122+
123+
return transpileConfig
89124
}
90125

91126
function isValidTransformer(transformer) {
@@ -133,10 +168,6 @@ const getCustomTransformer = function getCustomTransformer(
133168
: transformer
134169
}
135170

136-
const throwError = function error(msg) {
137-
throw new Error('\n[vue-jest] Error: ' + msg + '\n')
138-
}
139-
140171
const stripInlineSourceMap = function(str) {
141172
return str.slice(0, str.indexOf('//# sourceMappingURL'))
142173
}

0 commit comments

Comments
 (0)