From bb516dad94c88ec08ffbfc0680973953358cd89b Mon Sep 17 00:00:00 2001 From: Adam Hines Date: Wed, 2 Nov 2022 22:49:12 -0600 Subject: [PATCH] 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 --- e2e/2.x/basic/components/ExtendedTsConfig.vue | 34 ++++++++++++ .../ModuleRequiringEsModuleInterop.js | 1 + e2e/2.x/basic/test.js | 7 +++ e2e/2.x/basic/tsconfig.base.json | 21 +++++++ e2e/2.x/basic/tsconfig.json | 19 +------ e2e/3.x/basic/components/ExtendedTsConfig.vue | 34 ++++++++++++ .../ModuleRequiringEsModuleInterop.js | 1 + e2e/3.x/basic/test.js | 7 +++ e2e/3.x/basic/tsconfig.base.json | 21 +++++++ e2e/3.x/basic/tsconfig.json | 19 +------ packages/vue2-jest/lib/ensure-require.js | 2 +- packages/vue2-jest/lib/throw-error.js | 3 + packages/vue2-jest/lib/utils.js | 52 ++++++++++++++---- packages/vue3-jest/lib/ensure-require.js | 2 +- packages/vue3-jest/lib/throw-error.js | 3 + packages/vue3-jest/lib/utils.js | 55 +++++++++++++++---- 16 files changed, 220 insertions(+), 61 deletions(-) create mode 100644 e2e/2.x/basic/components/ExtendedTsConfig.vue create mode 100644 e2e/2.x/basic/components/ModuleRequiringEsModuleInterop.js create mode 100644 e2e/2.x/basic/tsconfig.base.json create mode 100644 e2e/3.x/basic/components/ExtendedTsConfig.vue create mode 100644 e2e/3.x/basic/components/ModuleRequiringEsModuleInterop.js create mode 100644 e2e/3.x/basic/tsconfig.base.json create mode 100644 packages/vue2-jest/lib/throw-error.js create mode 100644 packages/vue3-jest/lib/throw-error.js diff --git a/e2e/2.x/basic/components/ExtendedTsConfig.vue b/e2e/2.x/basic/components/ExtendedTsConfig.vue new file mode 100644 index 00000000..8dc7001f --- /dev/null +++ b/e2e/2.x/basic/components/ExtendedTsConfig.vue @@ -0,0 +1,34 @@ + + + diff --git a/e2e/2.x/basic/components/ModuleRequiringEsModuleInterop.js b/e2e/2.x/basic/components/ModuleRequiringEsModuleInterop.js new file mode 100644 index 00000000..fed7caa7 --- /dev/null +++ b/e2e/2.x/basic/components/ModuleRequiringEsModuleInterop.js @@ -0,0 +1 @@ +module.exports = () => false diff --git a/e2e/2.x/basic/test.js b/e2e/2.x/basic/test.js index 7ec85043..a340be9c 100644 --- a/e2e/2.x/basic/test.js +++ b/e2e/2.x/basic/test.js @@ -21,6 +21,8 @@ import Jsx from './components/Jsx.vue' import Constructor from './components/Constructor.vue' import { compileStyle } from '@vue/component-compiler-utils' import ScriptSetup from './components/ScriptSetup' +import ExtendedTsConfig from './components/ExtendedTsConfig.vue' + jest.mock('@vue/component-compiler-utils', () => ({ ...jest.requireActual('@vue/component-compiler-utils'), compileStyle: jest.fn(() => ({ errors: [], code: '' })) @@ -163,6 +165,11 @@ test('processes SFC with diff --git a/e2e/3.x/basic/components/ModuleRequiringEsModuleInterop.js b/e2e/3.x/basic/components/ModuleRequiringEsModuleInterop.js new file mode 100644 index 00000000..fed7caa7 --- /dev/null +++ b/e2e/3.x/basic/components/ModuleRequiringEsModuleInterop.js @@ -0,0 +1 @@ +module.exports = () => false diff --git a/e2e/3.x/basic/test.js b/e2e/3.x/basic/test.js index 553ec119..3a5dddaa 100644 --- a/e2e/3.x/basic/test.js +++ b/e2e/3.x/basic/test.js @@ -23,6 +23,7 @@ import ScriptSetup from './components/ScriptSetup.vue' import ScriptSetupSugarRef from './components/ScriptSetupSugarRef.vue' import FunctionalRenderFn from './components/FunctionalRenderFn.vue' import CompilerDirective from './components/CompilerDirective.vue' +import ExtendedTsConfig from './components/ExtendedTsConfig.vue' // TODO: JSX for Vue 3? TSX? import Jsx from './components/Jsx.vue' @@ -207,3 +208,9 @@ test('ensure compilerOptions is passed down', () => { const elm = document.querySelector('h1') expect(elm.hasAttribute('data-test')).toBe(false) }) + +test('handles extended tsconfig.json files', () => { + mount(ExtendedTsConfig) + const elm = document.querySelector('div') + expect(elm).toBeDefined() +}) diff --git a/e2e/3.x/basic/tsconfig.base.json b/e2e/3.x/basic/tsconfig.base.json new file mode 100644 index 00000000..0b98e194 --- /dev/null +++ b/e2e/3.x/basic/tsconfig.base.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "es6"], + "module": "es2015", + "moduleResolution": "node", + "types": ["vue-typescript-import-dts", "node"], + "isolatedModules": false, + "experimentalDecorators": true, + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "suppressImplicitAnyIndexErrors": true, + "allowSyntheticDefaultImports": true, + "sourceMap": true, + "esModuleInterop": true, + "allowJs": true + } +} diff --git a/e2e/3.x/basic/tsconfig.json b/e2e/3.x/basic/tsconfig.json index 8073706e..ffcbb947 100644 --- a/e2e/3.x/basic/tsconfig.json +++ b/e2e/3.x/basic/tsconfig.json @@ -1,20 +1,3 @@ { - "compilerOptions": { - "target": "es5", - "lib": ["dom", "es6"], - "module": "es2015", - "moduleResolution": "node", - "types": ["vue-typescript-import-dts", "node"], - "isolatedModules": false, - "experimentalDecorators": true, - "noImplicitAny": true, - "noImplicitThis": true, - "strictNullChecks": true, - "removeComments": true, - "emitDecoratorMetadata": true, - "suppressImplicitAnyIndexErrors": true, - "allowSyntheticDefaultImports": true, - "sourceMap": true, - "allowJs": true - } + "extends": "./tsconfig.base.json" } diff --git a/packages/vue2-jest/lib/ensure-require.js b/packages/vue2-jest/lib/ensure-require.js index 17f40920..dd945ee2 100644 --- a/packages/vue2-jest/lib/ensure-require.js +++ b/packages/vue2-jest/lib/ensure-require.js @@ -1,4 +1,4 @@ -const throwError = require('./utils').throwError +const throwError = require('./throw-error') module.exports = function(name, deps) { let i, len diff --git a/packages/vue2-jest/lib/throw-error.js b/packages/vue2-jest/lib/throw-error.js new file mode 100644 index 00000000..5d1272e3 --- /dev/null +++ b/packages/vue2-jest/lib/throw-error.js @@ -0,0 +1,3 @@ +module.exports = function throwError(msg) { + throw new Error('\n[vue-jest] Error: ' + msg + '\n') +} diff --git a/packages/vue2-jest/lib/utils.js b/packages/vue2-jest/lib/utils.js index 25b8a5fa..f68d79ec 100644 --- a/packages/vue2-jest/lib/utils.js +++ b/packages/vue2-jest/lib/utils.js @@ -1,6 +1,8 @@ +const ensureRequire = require('./ensure-require') +const throwError = require('./throw-error') const constants = require('./constants') const loadPartialConfig = require('@babel/core').loadPartialConfig -const { loadSync: loadTsConfigSync } = require('tsconfig') +const { resolveSync: resolveTsConfigSync } = require('tsconfig') const chalk = require('chalk') const path = require('path') const fs = require('fs') @@ -68,23 +70,55 @@ const getBabelOptions = function loadBabelOptions(filename, options = {}) { return loadPartialConfig(opts).options } +const tsConfigCache = new Map() + /** * Load TypeScript config from tsconfig.json. * @param {string | undefined} path tsconfig.json file path (default: root) * @returns {import('typescript').TranspileOptions | null} TypeScript compilerOptions or null */ const getTypeScriptConfig = function getTypeScriptConfig(path) { - const tsconfig = loadTsConfigSync(process.cwd(), path || '') - if (!tsconfig.path) { + if (tsConfigCache.has(path)) { + return tsConfigCache.get(path) + } + + ensureRequire('typescript', ['typescript']) + const typescript = require('typescript') + + const tsconfigPath = resolveTsConfigSync(process.cwd(), path || '') + if (!tsconfigPath) { warn(`Not found tsconfig.json.`) return null } - const compilerOptions = - (tsconfig.config && tsconfig.config.compilerOptions) || {} - return { - compilerOptions: { ...compilerOptions, module: 'commonjs' } + const parsedConfig = typescript.getParsedCommandLineOfConfigFile( + tsconfigPath, + {}, + { + ...typescript.sys, + onUnRecoverableConfigFileDiagnostic: e => { + const errorMessage = typescript.formatDiagnostic(e, { + getCurrentDirectory: () => process.cwd(), + getNewLine: () => `\n`, + getCanonicalFileName: file => file.replace(/\\/g, '/') + }) + warn(errorMessage) + } + } + ) + + const compilerOptions = parsedConfig ? parsedConfig.options : {} + + const transpileConfig = { + compilerOptions: { + ...compilerOptions, + module: typescript.ModuleKind.CommonJS + } } + + tsConfigCache.set(path, transpileConfig) + + return transpileConfig } function isValidTransformer(transformer) { @@ -131,10 +165,6 @@ const getCustomTransformer = function getCustomTransformer( : transformer } -const throwError = function error(msg) { - throw new Error('\n[vue-jest] Error: ' + msg + '\n') -} - const stripInlineSourceMap = function(str) { return str.slice(0, str.indexOf('//# sourceMappingURL')) } diff --git a/packages/vue3-jest/lib/ensure-require.js b/packages/vue3-jest/lib/ensure-require.js index 17f40920..dd945ee2 100644 --- a/packages/vue3-jest/lib/ensure-require.js +++ b/packages/vue3-jest/lib/ensure-require.js @@ -1,4 +1,4 @@ -const throwError = require('./utils').throwError +const throwError = require('./throw-error') module.exports = function(name, deps) { let i, len diff --git a/packages/vue3-jest/lib/throw-error.js b/packages/vue3-jest/lib/throw-error.js new file mode 100644 index 00000000..5d1272e3 --- /dev/null +++ b/packages/vue3-jest/lib/throw-error.js @@ -0,0 +1,3 @@ +module.exports = function throwError(msg) { + throw new Error('\n[vue-jest] Error: ' + msg + '\n') +} diff --git a/packages/vue3-jest/lib/utils.js b/packages/vue3-jest/lib/utils.js index cbd7fd7a..5b1e70ee 100644 --- a/packages/vue3-jest/lib/utils.js +++ b/packages/vue3-jest/lib/utils.js @@ -1,6 +1,8 @@ +const ensureRequire = require('./ensure-require') +const throwError = require('./throw-error') const constants = require('./constants') const loadPartialConfig = require('@babel/core').loadPartialConfig -const { loadSync: loadTsConfigSync } = require('tsconfig') +const { resolveSync: resolveTsConfigSync } = require('tsconfig') const chalk = require('chalk') const path = require('path') const fs = require('fs') @@ -68,24 +70,57 @@ const getBabelOptions = function loadBabelOptions(filename, options = {}) { return loadPartialConfig(opts).options } +const tsConfigCache = new Map() + /** * Load TypeScript config from tsconfig.json. * @param {string | undefined} path tsconfig.json file path (default: root) * @returns {import('typescript').TranspileOptions | null} TypeScript compilerOptions or null */ const getTypeScriptConfig = function getTypeScriptConfig(path) { - const tsconfig = loadTsConfigSync(process.cwd(), path || '') - if (!tsconfig.path) { + if (tsConfigCache.has(path)) { + return tsConfigCache.get(path) + } + + ensureRequire('typescript', ['typescript']) + const typescript = require('typescript') + + const tsconfigPath = resolveTsConfigSync(process.cwd(), path || '') + if (!tsconfigPath) { warn(`Not found tsconfig.json.`) return null } - const compilerOptions = - (tsconfig.config && tsconfig.config.compilerOptions) || {} - // Force es5 to prevent const vue_1 = require('vue') from conflicting - return { - compilerOptions: { ...compilerOptions, target: 'es5', module: 'commonjs' } + const parsedConfig = typescript.getParsedCommandLineOfConfigFile( + tsconfigPath, + {}, + { + ...typescript.sys, + onUnRecoverableConfigFileDiagnostic: e => { + const errorMessage = typescript.formatDiagnostic(e, { + getCurrentDirectory: () => process.cwd(), + getNewLine: () => `\n`, + getCanonicalFileName: file => file.replace(/\\/g, '/') + }) + warn(errorMessage) + } + } + ) + + const compilerOptions = parsedConfig ? parsedConfig.options : {} + + const transpileConfig = { + compilerOptions: { + ...compilerOptions, + // Force es5 to prevent const vue_1 = require('vue') from conflicting + target: typescript.ScriptTarget.ES5, + module: typescript.ModuleKind.CommonJS + } } + + tsConfigCache.set(path, transpileConfig) + + return transpileConfig } function isValidTransformer(transformer) { @@ -133,10 +168,6 @@ const getCustomTransformer = function getCustomTransformer( : transformer } -const throwError = function error(msg) { - throw new Error('\n[vue-jest] Error: ' + msg + '\n') -} - const stripInlineSourceMap = function(str) { return str.slice(0, str.indexOf('//# sourceMappingURL')) }