Skip to content

Commit

Permalink
Merge pull request backstage#9490 from backstage/rugvip/maps
Browse files Browse the repository at this point in the history
cli: add test source maps and update docs
  • Loading branch information
Rugvip authored Feb 14, 2022
2 parents e2cf066 + 70f9655 commit 80c627d
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 60 deletions.
5 changes: 5 additions & 0 deletions .changeset/sweet-keys-dress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---

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.
44 changes: 42 additions & 2 deletions docs/local-dev/cli-build-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -464,6 +465,45 @@ 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 something like this:

```jsonc
{
"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"
}
}
```

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
Expand Down
17 changes: 1 addition & 16 deletions docs/local-dev/cli-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
7 changes: 4 additions & 3 deletions packages/cli/config/jest.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
99 changes: 60 additions & 39 deletions packages/cli/config/jestSucraseTransform.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,50 +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,
},
});
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;
}

if (transforms) {
return transform(source, {
transforms,
filePath,
disableESTransforms: true,
}).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');
};

// 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)
.digest('hex');
return { process, getCacheKey };
}

module.exports = { process, getCacheKey };
module.exports = { createTransformer };

0 comments on commit 80c627d

Please sign in to comment.