From c6bbafb5163f05220b557324edddbd93f62293aa Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 12 Feb 2022 13:52:24 +0100 Subject: [PATCH 1/4] cli: updated jest transform to include source maps Signed-off-by: Patrik Oldsberg --- .changeset/sweet-keys-dress.md | 5 +++++ packages/cli/config/jestSucraseTransform.js | 12 ++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 .changeset/sweet-keys-dress.md diff --git a/.changeset/sweet-keys-dress.md b/.changeset/sweet-keys-dress.md new file mode 100644 index 0000000000..e931856a84 --- /dev/null +++ b/.changeset/sweet-keys-dress.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Updated the default [sucrase](https://github.com/alangpierce/sucrase)-based Jest transform to include inline source maps. diff --git a/packages/cli/config/jestSucraseTransform.js b/packages/cli/config/jestSucraseTransform.js index c88909d21f..65c71d7daf 100644 --- a/packages/cli/config/jestSucraseTransform.js +++ b/packages/cli/config/jestSucraseTransform.js @@ -46,11 +46,17 @@ function process(source, filePath) { } if (transforms) { - return transform(source, { + const { code, sourceMap: map } = transform(source, { transforms, filePath, disableESTransforms: true, - }).code; + sourceMapOptions: { + compiledFilename: filePath, + }, + }); + const b64 = Buffer.from(JSON.stringify(map), 'utf8').toString('base64'); + const suffix = `//# sourceMappingURL=data:application/json;charset=utf-8;base64,${b64}`; + return `${code}\n${suffix}`; } return source; @@ -64,6 +70,8 @@ function getCacheKey(sourceText) { .update(sucrasePkg.version) .update(Buffer.alloc(1)) .update(sucrasePluginPkg.version) + .update(Buffer.alloc(1)) + .update('1') // increment whenever the transform logic in this file changes .digest('hex'); } From f1a93494463907848fe3254eeb12d90c79a7a8dd Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 12 Feb 2022 14:37:46 +0100 Subject: [PATCH 2/4] docs: update test and jest docs Signed-off-by: Patrik Oldsberg --- docs/local-dev/cli-build-system.md | 13 +++++++++++-- docs/local-dev/cli-commands.md | 17 +---------------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/docs/local-dev/cli-build-system.md b/docs/local-dev/cli-build-system.md index fdfede6e05..db8d03a7fd 100644 --- a/docs/local-dev/cli-build-system.md +++ b/docs/local-dev/cli-build-system.md @@ -444,8 +444,9 @@ working directory to the package that the test is in. Where small customizations are needed, such as setting coverage thresholds or support for specific transforms, it is possible to override the Jest -configuration through the `"jest"` field in `package.json`. These overrides will -be loaded in from all `package.json` files in the directory ancestry, meaning +configuration through the `"jest"` field in `package.json`. For a full list of +options, see the [Jest documentation](https://jestjs.io/docs/en/configuration). +These overrides will be loaded in from all `package.json` files in the directory ancestry, meaning that you can place common configuration in the `package.json` at the root of a monorepo. If multiple overrides are found, they will be merged together with configuration further down in the directory tree taking precedence. @@ -464,6 +465,14 @@ The overrides in a single `package.json` may for example look like this: }, ``` +If you want to configure editor integration for tests we recommend executing the bundled configuration directly with Jest, rather than running through the Yarn test script. For example, with the Jest extension for VS Code, the configuration would look like this: + +```json +{ + "jest.jestCommandLine": "node_modules/.bin/jest --config node_modules/@backstage/cli/config/jest.js" +} +``` + ## Publishing Package publishing is an optional part of the Backstage build system and not diff --git a/docs/local-dev/cli-commands.md b/docs/local-dev/cli-commands.md index daf53ac0b2..eff6404e3b 100644 --- a/docs/local-dev/cli-commands.md +++ b/docs/local-dev/cli-commands.md @@ -440,22 +440,7 @@ a yarn workspaces monorepo by automatically creating one grouped configuration that includes all packages that have `backstage-cli test` in their package `test` script. -If needed, the configuration can be extended using a `"jest"` field in -`package.json`, both within the target package and the monorepo root, with -configuration in the target package taking precedence. Refer to the -[Jest configuration documentation](https://jestjs.io/docs/en/configuration) for -a full list of configuration options. - -In addition to the Jest configuration there's an optional `transformModules` -option, which is an array of module names to include in transformations. -Normally modules inside `node_modules` are not transformed, but there are cases -were published packages are not transpiled far enough to be usable by Jest, in -which case you need to enable transform of them. - -Another way to override the Jest configuration is to place a `jest.config.js` or -`jest.config.ts` file in the package root. As opposed to the `package.json` way -of overriding config, this completely removes the base config, and so you need -to set it up from scratch. +For more information about configuration overrides and editor support, see the [Jest Configuration section](./cli-build-system.md#jest-configuration) in the build system documentation. ```text Usage: backstage-cli test [options] From 86ab640b20fa8078d33aedbaf7a50e28db8b1569 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 12 Feb 2022 16:47:41 +0100 Subject: [PATCH 3/4] cli,docs: update jest transform to only include source maps when requested Signed-off-by: Patrik Oldsberg --- .changeset/sweet-keys-dress.md | 2 +- docs/local-dev/cli-build-system.md | 26 ++++- packages/cli/config/jest.js | 7 +- packages/cli/config/jestSucraseTransform.js | 109 +++++++++++--------- 4 files changed, 91 insertions(+), 53 deletions(-) diff --git a/.changeset/sweet-keys-dress.md b/.changeset/sweet-keys-dress.md index e931856a84..4217f2fcf4 100644 --- a/.changeset/sweet-keys-dress.md +++ b/.changeset/sweet-keys-dress.md @@ -2,4 +2,4 @@ '@backstage/cli': patch --- -Updated the default [sucrase](https://github.com/alangpierce/sucrase)-based Jest transform to include inline source maps. +Updated the default [sucrase](https://github.com/alangpierce/sucrase)-based Jest transform to include source maps if the environment variable `ENABLE_SOURCE_MAPS` is non-empty. This can be used to better support editor test debugging integrations. diff --git a/docs/local-dev/cli-build-system.md b/docs/local-dev/cli-build-system.md index db8d03a7fd..5df9b751c5 100644 --- a/docs/local-dev/cli-build-system.md +++ b/docs/local-dev/cli-build-system.md @@ -465,7 +465,7 @@ The overrides in a single `package.json` may for example look like this: }, ``` -If you want to configure editor integration for tests we recommend executing the bundled configuration directly with Jest, rather than running through the Yarn test script. For example, with the Jest extension for VS Code, the configuration would look like this: +If you want to configure editor integration for tests we recommend executing the bundled configuration directly with Jest, rather than running through the Yarn test script. For example, with the Jest extension for VS Code the configuration would look like this: ```json { @@ -473,6 +473,30 @@ If you want to configure editor integration for tests we recommend executing the } ``` +If you also want to enable source maps when debugging tests, you can do so by setting the `ENABLE_SOURCE_MAPS` environment variable. For example, a complete launch configuration for VS Code debugging may look like this: + +```json +{ + "type": "node", + "name": "vscode-jest-tests", + "request": "launch", + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "disableOptimisticBPs": true, + "program": "${workspaceFolder}/node_modules/.bin/jest", + "cwd": "${workspaceFolder}", + "env": { + "ENABLE_SOURCE_MAPS": "true" + }, + "args": [ + "--config", + "node_modules/@backstage/cli/config/jest.js", + "--runInBand", + "--watchAll=false" + ] +} +``` + ## Publishing Package publishing is an optional part of the Backstage build system and not diff --git a/packages/cli/config/jest.js b/packages/cli/config/jest.js index 978c598584..91cdd3c41b 100644 --- a/packages/cli/config/jest.js +++ b/packages/cli/config/jest.js @@ -102,9 +102,10 @@ async function getProjectConfig(targetPath, displayName) { }, transform: { - '\\.(js|jsx|ts|tsx|mjs|cjs)$': require.resolve( - './jestSucraseTransform.js', - ), + '\\.(js|jsx|ts|tsx|mjs|cjs)$': [ + require.resolve('./jestSucraseTransform.js'), + { enableSourceMaps: Boolean(process.env.ENABLE_SOURCE_MAPS) }, + ], '\\.(bmp|gif|jpg|jpeg|png|frag|xml|svg|eot|woff|woff2|ttf)$': require.resolve('./jestFileTransform.js'), '\\.(yaml)$': require.resolve('jest-transform-yaml'), diff --git a/packages/cli/config/jestSucraseTransform.js b/packages/cli/config/jestSucraseTransform.js index 65c71d7daf..d0400f4f3b 100644 --- a/packages/cli/config/jestSucraseTransform.js +++ b/packages/cli/config/jestSucraseTransform.js @@ -21,58 +21,71 @@ const sucrasePluginPkg = require('@sucrase/jest-plugin/package.json'); const ESM_REGEX = /\b(?:import|export)\b/; -function process(source, filePath) { - let transforms; +function createTransformer(config) { + const process = (source, filePath) => { + let transforms; - if (filePath.endsWith('.esm.js')) { - transforms = ['imports']; - } else if (filePath.endsWith('.js')) { - // This is a very rough filter to avoid transforming things that we quickly - // can be sure are definitely not ESM modules. - if (ESM_REGEX.test(source)) { - transforms = ['imports', 'jsx']; // JSX within .js is currently allowed + if (filePath.endsWith('.esm.js')) { + transforms = ['imports']; + } else if (filePath.endsWith('.js')) { + // This is a very rough filter to avoid transforming things that we quickly + // can be sure are definitely not ESM modules. + if (ESM_REGEX.test(source)) { + transforms = ['imports', 'jsx']; // JSX within .js is currently allowed + } + } else if (filePath.endsWith('.jsx')) { + transforms = ['jsx', 'imports']; + } else if (filePath.endsWith('.ts')) { + transforms = ['typescript', 'imports']; + } else if (filePath.endsWith('.tsx')) { + transforms = ['typescript', 'jsx', 'imports']; } - } else if (filePath.endsWith('.jsx')) { - transforms = ['jsx', 'imports']; - } else if (filePath.endsWith('.ts')) { - transforms = ['typescript', 'imports']; - } else if (filePath.endsWith('.tsx')) { - transforms = ['typescript', 'jsx', 'imports']; - } - // Only apply the jest transform to the test files themselves - if (transforms && filePath.includes('.test.')) { - transforms.push('jest'); - } + // Only apply the jest transform to the test files themselves + if (transforms && filePath.includes('.test.')) { + transforms.push('jest'); + } - if (transforms) { - const { code, sourceMap: map } = transform(source, { - transforms, - filePath, - disableESTransforms: true, - sourceMapOptions: { - compiledFilename: filePath, - }, - }); - const b64 = Buffer.from(JSON.stringify(map), 'utf8').toString('base64'); - const suffix = `//# sourceMappingURL=data:application/json;charset=utf-8;base64,${b64}`; - return `${code}\n${suffix}`; - } + if (transforms) { + const { code, sourceMap: map } = transform(source, { + transforms, + filePath, + disableESTransforms: true, + sourceMapOptions: { + compiledFilename: filePath, + }, + }); + if (config.enableSourceMaps) { + const b64 = Buffer.from(JSON.stringify(map), 'utf8').toString('base64'); + const suffix = `//# sourceMappingURL=data:application/json;charset=utf-8;base64,${b64}`; + // Include both inline and object source maps, as inline source maps are + // needed for support of some editor integrations. + return { code: `${code}\n${suffix}`, map }; + } + // We only return the `map` result if source maps are enabled, as they + // have a negative impact on the coverage accuracy. + return code; + } - return source; + return source; + }; + + // TODO: contribute something like this to @sucrase/jest-plugin + const getCacheKey = sourceText => { + return createHash('md5') + .update(sourceText) + .update(Buffer.alloc(1)) + .update(sucrasePkg.version) + .update(Buffer.alloc(1)) + .update(sucrasePluginPkg.version) + .update(Buffer.alloc(1)) + .update(JSON.stringify(config)) + .update(Buffer.alloc(1)) + .update('1') // increment whenever the transform logic in this file changes + .digest('hex'); + }; + + return { process, getCacheKey }; } -// TODO: contribute something like this to @sucrase/jest-plugin -function getCacheKey(sourceText) { - return createHash('md5') - .update(sourceText) - .update(Buffer.alloc(1)) - .update(sucrasePkg.version) - .update(Buffer.alloc(1)) - .update(sucrasePluginPkg.version) - .update(Buffer.alloc(1)) - .update('1') // increment whenever the transform logic in this file changes - .digest('hex'); -} - -module.exports = { process, getCacheKey }; +module.exports = { createTransformer }; From 70f9655034bab4a8fb3fdba6e8678fb4eab7dfdb Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 14 Feb 2022 20:53:52 +0100 Subject: [PATCH 4/4] docs: add suggestion to disable full test runs in Jest editor integration Signed-off-by: Patrik Oldsberg --- docs/local-dev/cli-build-system.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/local-dev/cli-build-system.md b/docs/local-dev/cli-build-system.md index 5df9b751c5..c4a08afc56 100644 --- a/docs/local-dev/cli-build-system.md +++ b/docs/local-dev/cli-build-system.md @@ -465,11 +465,18 @@ The overrides in a single `package.json` may for example look like this: }, ``` -If you want to configure editor integration for tests we recommend executing the bundled configuration directly with Jest, rather than running through the Yarn test script. For example, with the Jest extension for VS Code the configuration would look like this: +If you want to configure editor integration for tests we recommend executing the bundled configuration directly with Jest rather than running through the Yarn test script. For example, with the Jest extension for VS Code the configuration would look something like this: -```json +```jsonc { - "jest.jestCommandLine": "node_modules/.bin/jest --config node_modules/@backstage/cli/config/jest.js" + "jest.jestCommandLine": "node_modules/.bin/jest --config node_modules/@backstage/cli/config/jest.js", + // In a large repo like the Backstage main repo you likely want to disable + // watch mode and the initial test run too, leaving just manual and perhaps + // on-save test runs in place. + "jest.autoRun": { + "watch": false, + "onSave": "test-src-file" + } } ```