diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6eb25fa76..ccf49fea6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ Please see [CONTRIBUTING.md](./CONTRIBUTING.md) on how to contribute to Cucumber
### Changed
- BREAKING CHANGE: Use appropriate module loading mechanism for configuration files ([#2334](https://github.com/cucumber/cucumber-js/pull/2334))
- BREAKING CHANGE: Use `await import()` to load all custom formatters and snippet syntaxes ([#2334](https://github.com/cucumber/cucumber-js/pull/2334))
+- BREAKING CHANGE: Use `await import()` for default support code loading ([#2337](https://github.com/cucumber/cucumber-js/pull/2337))
### Removed
- BREAKING CHANGE: Drop support for Node.js 14, 16 and 19 ([#2331](https://github.com/cucumber/cucumber-js/pull/2331))
diff --git a/docs/configuration.md b/docs/configuration.md
index cf5c87f7d..8a3ceae84 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -8,40 +8,20 @@ _**You are reading the documentation in the `main` development branch, which mig
You can keep your configuration in a file. Cucumber will look for one of these files in the root of your project, and use the first one it finds:
-- `cucumber.js`
-- `cucumber.cjs`
-- `cucumber.mjs`
- `cucumber.json`
- `cucumber.yaml`
- `cucumber.yml`
+- `cucumber.js`
+- `cucumber.cjs`
+- `cucumber.mjs`
You can also put your file somewhere else and tell Cucumber via the `--config` CLI option:
```shell
-$ cucumber-js --config config/cucumber.js
-```
-
-Here's a concise example of a configuration file in CommonJS format:
-
-```js
-module.exports = {
- default: {
- parallel: 2,
- format: ['html:cucumber-report.html']
- }
-}
-```
-
-And the same in ESM format:
-
-```js
-export default {
- parallel: 2,
- format: ['html:cucumber-report.html']
-}
+$ cucumber-js --config config/cucumber.json
```
-And the same in JSON format:
+Here's a concise example of a configuration file in JSON format:
```json
{
@@ -61,6 +41,26 @@ default:
- "html:cucumber-report.html"
```
+And the same in JavaScript (ESM) format:
+
+```js
+export default {
+ parallel: 2,
+ format: ['html:cucumber-report.html']
+}
+```
+
+And the same in JavaScript (CommonJS) format:
+
+```js
+module.exports = {
+ default: {
+ parallel: 2,
+ format: ['html:cucumber-report.html']
+ }
+}
+```
+
Cucumber also supports the configuration being a string of options in the style of the CLI, though this isn't recommended:
```js
@@ -87,7 +87,7 @@ These options can be used in a configuration file (see [above](#files)) or on th
| `failFast` | `boolean` | No | `--fail-fast` | Stop running tests when a test fails - see [Fail Fast](./fail_fast.md) | false |
| `format` | `string[]` | Yes | `--format`, `-f` | Name/path and (optionally) output file path of each formatter to use - see [Formatters](./formatters.md) | [] |
| `formatOptions` | `object` | Yes | `--format-options` | Options to be provided to formatters - see [Formatters](./formatters.md) | {} |
-| `import` | `string[]` | Yes | `--import`, `-i` | Paths to where your support code is, for ESM - see [ESM](./esm.md) | [] |
+| `import` | `string[]` | Yes | `--import`, `-i` | Paths to where your support code is | [] |
| `language` | `string` | No | `--language` | Default language for your feature files | en |
| `name` | `string` | No | `--name` | Regular expressions of which scenario names should match one of to be run - see [Filtering](./filtering.md#names) | [] |
| `order` | `string` | No | `--order` | Run in the order defined, or in a random order | defined |
@@ -123,15 +123,15 @@ For more granular options to control _which scenarios_ from your features should
By default, Cucumber finds support code files with this logic:
* If the features live in a `features` directory (at any level)
- * `features/**/*.(js)`
+ * `features/**/*.@(js|cjs|mjs)`
* Otherwise
- * `
/**/*.(js)` for each directory containing the selected features
+ * `/**/*.@(js|cjs|mjs)` for each directory containing the selected features
-If your files are somewhere else, you can override this by proving your own [glob](https://github.com/isaacs/node-glob), directory or file path to the `require` configuration option:
+If your files are somewhere else, you can override this by proving your own [glob](https://github.com/isaacs/node-glob), directory or file path to the `import` configuration option:
-- In a configuration file `{ require: ['somewhere-else/support/*.js'] }`
-- On the CLI `$ cucumber-js --require somewhere-else/support/*.js`
+- In a configuration file `{ import: ['somewhere-else/support/*.js'] }`
+- On the CLI `$ cucumber-js --import somewhere-else/support/*.js`
-Once you specify any `require` options, the defaults described above are no longer applied. The option is repeatable, so you can provide several values and they'll be combined, meaning you can load files from multiple locations.
+Once you specify any `import` options, the defaults described above are no longer applied. The option is repeatable, so you can provide several values and they'll be combined, meaning you can load files from multiple locations.
-The default behaviour and the `require` option both use the [legacy CommonJS modules API](https://nodejs.org/api/modules.html) to load your files. If your files are native ES modules, you'll need to use the `import` option instead in the same way, and they'll be loaded with the [new ES modules API](https://nodejs.org/api/esm.html). See [ES Modules](./esm.md) for more on using Cucumber in an ESM project.
+The default behaviour and the `import` option both use the [new ES modules API](https://nodejs.org/api/esm.html) to load your files. This should work fine for the majority of cases, but sometimes (e.g. when transpiling with the `require-module` option), you'll need to use the `require` option instead in the same way, and they'll be loaded with the [legacy CommonJS modules API](https://nodejs.org/api/modules.html).
diff --git a/src/api/paths.ts b/src/api/paths.ts
index 2c103cc11..fc884cfc1 100644
--- a/src/api/paths.ts
+++ b/src/api/paths.ts
@@ -147,9 +147,8 @@ async function deriveSupportPaths(
unexpandedImportPaths.length === 0
) {
const defaultPaths = getFeatureDirectoryPaths(cwd, featurePaths)
- const requirePaths = await expandPaths(cwd, defaultPaths, '.js')
- const importPaths = await expandPaths(cwd, defaultPaths, '.mjs')
- return { requirePaths, importPaths }
+ const importPaths = await expandPaths(cwd, defaultPaths, '.@(js|cjs|mjs)')
+ return { requirePaths: [], importPaths }
}
const requirePaths =
unexpandedRequirePaths.length > 0
diff --git a/src/api/paths_spec.ts b/src/api/paths_spec.ts
index 3135cc672..cc8cf6586 100644
--- a/src/api/paths_spec.ts
+++ b/src/api/paths_spec.ts
@@ -51,8 +51,8 @@ describe('resolvePaths', () => {
// Assert
expect(featurePaths).to.eql([featurePath])
expect(unexpandedFeaturePaths).to.eql([relativeFeaturePath])
- expect(requirePaths).to.eql([jsSupportCodePath])
- expect(importPaths).to.eql([esmSupportCodePath])
+ expect(requirePaths).to.eql([])
+ expect(importPaths).to.eql([jsSupportCodePath, esmSupportCodePath])
})
it('deduplicates features based on overlapping expressions', async function () {
@@ -113,7 +113,7 @@ describe('resolvePaths', () => {
await fsExtra.outputFile(supportCodePath, '')
// Act
- const { featurePaths, unexpandedFeaturePaths, requirePaths } =
+ const { featurePaths, unexpandedFeaturePaths, importPaths } =
await resolvePaths(
new FakeLogger(),
cwd,
@@ -130,7 +130,7 @@ describe('resolvePaths', () => {
// Assert
expect(featurePaths).to.eql([featurePath])
expect(unexpandedFeaturePaths).to.eql([relativeFeaturePath])
- expect(requirePaths).to.eql([supportCodePath])
+ expect(importPaths).to.eql([supportCodePath])
})
})
@@ -145,7 +145,7 @@ describe('resolvePaths', () => {
await fsExtra.outputFile(supportCodePath, '')
// Act
- const { featurePaths, unexpandedFeaturePaths, requirePaths } =
+ const { featurePaths, unexpandedFeaturePaths, importPaths } =
await resolvePaths(
new FakeLogger(),
cwd,
@@ -162,7 +162,7 @@ describe('resolvePaths', () => {
// Assert
expect(featurePaths).to.eql([featurePath])
expect(unexpandedFeaturePaths).to.eql([relativeFeaturePath])
- expect(requirePaths).to.eql([supportCodePath])
+ expect(importPaths).to.eql([supportCodePath])
})
it('returns the appropriate .md and support code paths', async function () {
@@ -179,7 +179,7 @@ describe('resolvePaths', () => {
await fsExtra.outputFile(supportCodePath, '')
// Act
- const { featurePaths, unexpandedFeaturePaths, requirePaths } =
+ const { featurePaths, unexpandedFeaturePaths, importPaths } =
await resolvePaths(
new FakeLogger(),
cwd,
@@ -196,7 +196,7 @@ describe('resolvePaths', () => {
// Assert
expect(featurePaths).to.eql([featurePath])
expect(unexpandedFeaturePaths).to.eql([relativeFeaturePath])
- expect(requirePaths).to.eql([supportCodePath])
+ expect(importPaths).to.eql([supportCodePath])
})
})
@@ -297,8 +297,8 @@ describe('resolvePaths', () => {
const relativeFeaturePath = path.join('features', 'a.feature')
const featurePath = path.join(cwd, relativeFeaturePath)
await fsExtra.outputFile(featurePath, '')
- const jsSupportCodePath = path.join(cwd, 'features', 'a.js')
- await fsExtra.outputFile(jsSupportCodePath, '')
+ const cjsSupportCodePath = path.join(cwd, 'features', 'a.cjs')
+ await fsExtra.outputFile(cjsSupportCodePath, '')
const esmSupportCodePath = path.join(cwd, 'features', 'a.mjs')
await fsExtra.outputFile(esmSupportCodePath, '')
@@ -311,8 +311,8 @@ describe('resolvePaths', () => {
},
{
requireModules: [],
- requirePaths: [],
- importPaths: [],
+ requirePaths: [cjsSupportCodePath],
+ importPaths: [esmSupportCodePath],
}
)
@@ -323,7 +323,7 @@ describe('resolvePaths', () => {
)
expect(logger.debug).to.have.been.calledWith(
'Found support files to load via `require` based on configuration:',
- [jsSupportCodePath]
+ [cjsSupportCodePath]
)
expect(logger.debug).to.have.been.calledWith(
'Found support files to load via `import` based on configuration:',
diff --git a/src/configuration/validate_configuration.ts b/src/configuration/validate_configuration.ts
index 437332acb..1f4433172 100644
--- a/src/configuration/validate_configuration.ts
+++ b/src/configuration/validate_configuration.ts
@@ -10,6 +10,11 @@ export function validateConfiguration(
'`publishQuiet` option is no longer needed, you can remove it from your configuration; see https://github.com/cucumber/cucumber-js/blob/main/docs/deprecations.md'
)
}
+ if (configuration.requireModule.length && !configuration.require.length) {
+ logger.warn(
+ 'Use of `require-module` option normally means you should specify your support code paths with `require`; see https://github.com/cucumber/cucumber-js/blob/main/docs/configuration.md#finding-your-code'
+ )
+ }
if (configuration.retryTagFilter && !configuration.retry) {
throw new Error(
'a positive `retry` count must be specified when setting `retryTagFilter`'