-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(jest-config):Support loading TS config files via docblock loader #15190
Changes from 13 commits
7f17fe2
7dab201
7a9e2b1
dc23020
a729f80
849f6b0
1bd881a
b9c6ee5
9cd57e8
9747160
3c321f4
deea0d6
afb4509
d2c3a8d
8b70b95
b05a5f5
4d71390
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/** | ||
* Copyright (c) Meta Platforms, Inc. and affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
/**@jest-config-loader esbuild-register */ | ||
interface Config { | ||
jestConfig: string; | ||
} | ||
|
||
export default { | ||
jestConfig: 'jest.config.ts', | ||
} as Config; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,8 +10,8 @@ import {isNativeError} from 'util/types'; | |
import * as fs from 'graceful-fs'; | ||
import parseJson = require('parse-json'); | ||
import stripJsonComments = require('strip-json-comments'); | ||
import type {Service} from 'ts-node'; | ||
import type {Config} from '@jest/types'; | ||
import {extract, parse} from 'jest-docblock'; | ||
import {interopRequireDefault, requireOrImportModule} from 'jest-util'; | ||
import { | ||
JEST_CONFIG_EXT_CTS, | ||
|
@@ -20,6 +20,10 @@ import { | |
PACKAGE_JSON, | ||
} from './constants'; | ||
|
||
interface TsLoader { | ||
enabled: (bool: boolean) => void; | ||
} | ||
type TsLoaderModule = 'ts-node' | 'esbuild-register'; | ||
// Read the configuration and set its `rootDir` | ||
// 1. If it's a `package.json` file, we look into its "jest" property | ||
// 2. If it's a `jest.config.ts` file, we use `ts-node` to transpile & require it | ||
|
@@ -89,8 +93,20 @@ const loadTSConfigFile = async ( | |
configPath: string, | ||
): Promise<Config.InitialOptions> => { | ||
// Get registered TypeScript compiler instance | ||
const registeredCompiler = await getRegisteredCompiler(); | ||
const docblockPragmas = parse(extract(fs.readFileSync(configPath, 'utf8'))); | ||
const tsLoader = docblockPragmas['jest-config-loader'] || 'ts-node'; | ||
|
||
if (Array.isArray(tsLoader)) { | ||
throw new TypeError( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not working when passing multiple loaders like this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that's expected - for it to be an array you'd need to do /**
* @jest-config-loader ts-node
* @jest-config-loader esbuild-register
*/ |
||
`Jest: You can only define a single loader through docblocks, got "${tsLoader.join( | ||
', ', | ||
)}"`, | ||
); | ||
} | ||
|
||
const registeredCompiler = await getRegisteredCompiler( | ||
tsLoader as TsLoaderModule, | ||
); | ||
registeredCompiler.enabled(true); | ||
|
||
let configObject = interopRequireDefault(require(configPath)).default; | ||
|
@@ -105,36 +121,57 @@ const loadTSConfigFile = async ( | |
return configObject; | ||
}; | ||
|
||
let registeredCompilerPromise: Promise<Service>; | ||
let registeredCompilerPromise: Promise<TsLoader>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if this needs to be a Maybe a better thing would just be to save what module was loaded, and then throw an error if later another one is attempted to be used? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have added integration tests for different loaders in different projects and they seem to be working fine d2c3a8d |
||
|
||
function getRegisteredCompiler() { | ||
function getRegisteredCompiler(loader: TsLoaderModule) { | ||
// Cache the promise to avoid multiple registrations | ||
registeredCompilerPromise = registeredCompilerPromise ?? registerTsNode(); | ||
registeredCompilerPromise = | ||
registeredCompilerPromise ?? registerTsLoader(loader); | ||
return registeredCompilerPromise; | ||
} | ||
|
||
async function registerTsNode(): Promise<Service> { | ||
async function registerTsLoader(loader: TsLoaderModule): Promise<TsLoader> { | ||
try { | ||
// Register TypeScript compiler instance | ||
const tsNode = await import(/* webpackIgnore: true */ 'ts-node'); | ||
return tsNode.register({ | ||
compilerOptions: { | ||
module: 'CommonJS', | ||
moduleResolution: 'Node10', | ||
}, | ||
moduleTypes: { | ||
'**': 'cjs', | ||
}, | ||
transpileOnly: | ||
process.env.JEST_CONFIG_TRANSPILE_ONLY?.toLowerCase() === 'true', | ||
}); | ||
if (loader === 'ts-node') { | ||
const tsLoader = await import(/* webpackIgnore: true */ 'ts-node'); | ||
return tsLoader.register({ | ||
compilerOptions: { | ||
module: 'CommonJS', | ||
}, | ||
moduleTypes: { | ||
'**': 'cjs', | ||
}, | ||
transpileOnly: | ||
process.env.JEST_CONFIG_TRANSPILE_ONLY?.toLowerCase() === 'true', | ||
}); | ||
} else if (loader === 'esbuild-register') { | ||
const tsLoader = await import( | ||
/* webpackIgnore: true */ 'esbuild-register/dist/node' | ||
); | ||
let instance: {unregister: () => void} | undefined; | ||
return { | ||
enabled: (bool: boolean) => { | ||
if (bool) { | ||
instance = tsLoader.register({ | ||
target: `node${process.version.slice(1)}`, | ||
}); | ||
} else { | ||
instance?.unregister(); | ||
} | ||
}, | ||
}; | ||
} | ||
throw new Error( | ||
`Jest: '${loader}' is not a valid TypeScript configuration loader.`, | ||
); | ||
} catch (error) { | ||
if ( | ||
isNativeError(error) && | ||
(error as NodeJS.ErrnoException).code === 'ERR_MODULE_NOT_FOUND' | ||
) { | ||
throw new Error( | ||
`Jest: 'ts-node' is required for the TypeScript configuration files. Make sure it is installed\nError: ${error.message}`, | ||
`Jest: '${loader}' is required for the TypeScript configuration files. Make sure it is installed\nError: ${error.message}`, | ||
); | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not for this PR, but maybe in a follow-up - should we drop this new env variable and use docblock config instead (like we support for test environment: https://jestjs.io/blog/2022/04/25/jest-28#inline-testenvironmentoptions)? Then people could also e.g. opt into using
swc
(https://typestrong.org/ts-node/docs/swc/)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that would be better, will send PR for that
we might also need to add support for swc based loader at some point #12156