From e3206246d612c8043c6fac7891cbdcf082b375d9 Mon Sep 17 00:00:00 2001 From: uhyo Date: Sun, 18 Feb 2024 16:37:06 +0900 Subject: [PATCH] feat(jest-transform): add additionalTransformer --- packages/jest-transform/README.md | 22 ++--- packages/jest-transform/src/index.ts | 135 +++++++++++++++++++-------- 2 files changed, 108 insertions(+), 49 deletions(-) diff --git a/packages/jest-transform/README.md b/packages/jest-transform/README.md index dbfbd7b..b98a5ca 100644 --- a/packages/jest-transform/README.md +++ b/packages/jest-transform/README.md @@ -18,23 +18,21 @@ Example Jest configuration: `@nitrogql/jest-transform` is only capable of emitting ES modules. If you are running CommonJS modules during your tests, you will need to use another transformer (for example, `babel-jest` or `ts-jest`) to further transform the output from ES modules to CommonJS. -Using multiple transformers should be possible by using the [jest-chain-transform](https://www.npmjs.com/package/jest-chain-transform) package: +`@nitrogql/jest-transform` lets you apply another transformer to the output. To do so, use the `additionalTransformer` and `additionalTransformerFilenameSuffix` option: ```js { "transform": { // For example, if you are using ts-jest... - "^.+\.tsx?": "ts-jest", - // Then, use jest-chain-transform to chain @nitrogql/jest-transform - // with ts-jest - "^.+\\.graphql$": ["jest-chain-transform", { - "transformers": [ - ["@nitrogql/jest-transform", { - "configFile": path.resolve(__dirname, "nitrogql.config.js") - }], - "ts-jest" - ] + "^.+\.tsx?": ["ts-jest", { isolatedModules: true }], + "^.+\\.graphql$": ["@nitrogql/jest-transform", { + "configFile": path.resolve(__dirname, "nitrogql.config.yml"), + // Then, use the additionalTransformer option to apply ts-jest to the output. + "additionalTransformer": ["ts-jest", { isolatedModules: true }], + // ts-jest expects .ts files, so we need to change the file extension + // by applying the additionalTransformerFilenameSuffix option. + "additionalTransformerFilenameSuffix": ".ts" }] - } + }, } ``` diff --git a/packages/jest-transform/src/index.ts b/packages/jest-transform/src/index.ts index 896826d..d6a2fc5 100644 --- a/packages/jest-transform/src/index.ts +++ b/packages/jest-transform/src/index.ts @@ -1,57 +1,118 @@ import { readFileSync } from "node:fs"; -import type { SyncTransformer, TransformedSource } from "@jest/transform"; +import type { + SyncTransformer, + TransformedSource, + TransformerCreator, +} from "@jest/transform"; import { executeConfigFileSync } from "@nitrogql/core"; import { init } from "@nitrogql/loader-core"; -const { initiateTask, getLog } = await init(); - -let lastLoadedConfigPath: string | undefined = undefined; - export type TransformerConfig = { /** * The path to the nitrogql config file. */ configFile?: string; + /** + * Additional transformer to apply to the generated source code. + */ + additionalTransformer?: + | string + | [transformer: string, transformerConfig: unknown]; + /** + * Suffix to add to filename when passing code to the additional transformer. + * @default ".js" + */ + additionalTransformerFilenameSuffix?: string; }; -const transformer: SyncTransformer = { - process(sourceText, sourcePath, options): TransformedSource { - const configFile = options.transformerConfig.configFile; - const task = initiateTask(sourcePath, sourceText); - - if (lastLoadedConfigPath !== configFile && configFile) { - const configFileSource = configFileIsJS(configFile) - ? executeConfigFileSync(configFile) - : readFileSync(configFile, "utf-8"); - task.loadConfig(configFileSource); - } - lastLoadedConfigPath = configFile; - - while (true) { - const status = task.status(); - switch (status.status) { - case "fileRequired": { - const requiredFiles = status.files; - for (const requiredFile of requiredFiles) { - const requiredFileSource = readFileSync(requiredFile, "utf-8"); - task.supplyFile(requiredFile, requiredFileSource); +const createTransformer: TransformerCreator< + SyncTransformer, + TransformerConfig +> = async (options) => { + const { initiateTask } = await init(); + + let lastLoadedConfigPath: string | undefined = undefined; + + const additionalTransformer = options?.additionalTransformer + ? await loadTransformer(options.additionalTransformer) + : undefined; + + const transformer: SyncTransformer = { + process(sourceText, sourcePath, options): TransformedSource { + const configFile = options.transformerConfig.configFile; + const task = initiateTask(sourcePath, sourceText); + + if (lastLoadedConfigPath !== configFile && configFile) { + const configFileSource = configFileIsJS(configFile) + ? executeConfigFileSync(configFile) + : readFileSync(configFile, "utf-8"); + task.loadConfig(configFileSource); + } + lastLoadedConfigPath = configFile; + + let result: string; + loop: while (true) { + const status = task.status(); + switch (status.status) { + case "fileRequired": { + const requiredFiles = status.files; + for (const requiredFile of requiredFiles) { + const requiredFileSource = readFileSync(requiredFile, "utf-8"); + task.supplyFile(requiredFile, requiredFileSource); + } + break; + } + case "ready": { + result = task.emit(); + task.free(); + break loop; } - break; - } - case "ready": { - const result = task.emit(); - task.free(); - return { - code: result, - }; } } - } - }, + if (additionalTransformer === undefined) { + return { code: result }; + } + const transformed = additionalTransformer[0].process( + result, + sourcePath + + (options.transformerConfig.additionalTransformerFilenameSuffix ?? + ".js"), + { + ...options, + transformerConfig: additionalTransformer[1], + } + ); + return transformed; + }, + }; + return transformer; }; -export default transformer; +export default { + createTransformer, +}; function configFileIsJS(configFile: string) { return /\.[cm]?[jt]s$/.test(configFile); } + +async function loadTransformer( + transformer: string | [transformer: string, transformerConfig: unknown] +): Promise<[transformer: SyncTransformer, config: unknown]> { + let transformerModule, transformerConfig: unknown; + if (typeof transformer === "string") { + transformerModule = await import(transformer); + } else { + transformerModule = await import(transformer[0]); + transformerConfig = transformer[1]; + } + while (transformerModule.default) { + transformerModule = transformerModule.default; + } + if (transformerModule.createTransformer) { + transformerModule = await transformerModule.createTransformer( + transformerConfig + ); + } + return [transformerModule, transformerConfig]; +}