Skip to content
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

Add support for named imports in ESM #1440

Merged
merged 12 commits into from
Jan 20, 2021
10 changes: 8 additions & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const javascriptSettings = {
files: ['*.js'],
files: ['*.js', '*.mjs'],
extends: [
'standard',
'plugin:jest/recommended'
Expand Down Expand Up @@ -59,6 +59,12 @@ module.exports = {
},
overrides: [
javascriptSettings,
typescriptSettings
typescriptSettings,
{
files: ['*.mjs'],
parserOptions: {
sourceType: 'module'
}
}
]
};
31 changes: 17 additions & 14 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ Read this in other languages: English | [简体中文](./Readme_zh-CN.md)
- [Legacy options as properties](#legacy-options-as-properties)
- [TypeScript](#typescript)
- [createCommand()](#createcommand)
- [Import into ECMAScript Module](#import-into-ecmascript-module)
- [Node options such as `--harmony`](#node-options-such-as---harmony)
- [Debugging stand-alone executable subcommands](#debugging-stand-alone-executable-subcommands)
- [Override exit and output handling](#override-exit-and-output-handling)
Expand Down Expand Up @@ -74,6 +73,23 @@ const program = new Command();
program.version('0.0.1');
```

For named imports in ECMAScript modules, import from `commander/esm.mjs`.

```js
// index.mjs
import { Command } from 'commander/esm.mjs';
const program = new Command();
```

And in TypeScript:

```ts
// index.ts
import { Command } from 'commander';
const program = new Command();
```


## Options

Options are defined with the `.option()` method, also serving as documentation for the options. Each option can have a short flag (single character) and a long name, separated by a comma or space or vertical bar ('|').
Expand Down Expand Up @@ -740,8 +756,6 @@ program

### TypeScript

The Commander package includes its TypeScript Definition file.

If you use `ts-node` and stand-alone executable subcommands written as `.ts` files, you need to call your program through node to get the subcommands called correctly. e.g.

```bash
Expand All @@ -761,17 +775,6 @@ const program = createCommand();
when creating subcommands using `.command()`, and you may override it to
customise the new subcommand (example file [custom-command-class.js](./examples/custom-command-class.js)).

### Import into ECMAScript Module

Commander is currently a CommonJS package, and the default export can be imported into an ES Module:

```js
// index.mjs
import commander from 'commander';
const program = commander.program;
const newCommand = new commander.Command();
```

### Node options such as `--harmony`

You can enable `--harmony` option in two ways:
Expand Down
4 changes: 4 additions & 0 deletions esm.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import commander from './index.js';

// wrapper to provide named exports for ESM.
export const { program, Option, Command, CommanderError, InvalidOptionArgumentError, Help, createCommand } = commander;
9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,21 @@
"url": "https://github.com/tj/commander.js.git"
},
"scripts": {
"lint": "eslint index.js \"tests/**/*.js\"",
"lint": "eslint index.js esm.mjs \"tests/**/*.js\"",
"typescript-lint": "eslint typings/*.ts",
"test": "jest && npm run test-typings",
"test-esm": "node --experimental-modules ./tests/esm-test.mjs",
"test-typings": "tsc -p tsconfig.json",
"typescript-checkJS": "tsc --allowJS --checkJS index.js --noEmit",
"test-all": "npm run test && npm run lint && npm run typescript-lint && npm run typescript-checkJS"
"test-all": "npm run test && npm run lint && npm run typescript-lint && npm run typescript-checkJS && npm run test-esm"
},
"main": "index",
"main": "./index.js",
"files": [
"index.js",
"wrapper.mjs",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like a typo, must be esm.mjs

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, good catch, thanks! I changed the name half way through.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(For interest, I thought wrapper was a suitable name for a wrapper used with conditional exports and not seen by normal client use. But when I changed to deep import and the name became visible, I wanted something more meaningful.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in #1443

"typings/index.d.ts"
],
"type": "commonjs",
"dependencies": {},
"devDependencies": {
"@types/jest": "^26.0.20",
Expand Down
32 changes: 32 additions & 0 deletions tests/esm-test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { program, Command, Option, CommanderError, InvalidOptionArgumentError, Help, createCommand } from '../esm.mjs';

// Do some simple checks that expected imports are available.
// Run using `npm run test-esm`.

function check(condition, explanation) {
if (!condition) {
console.log(`Failed assertion: ${explanation}`);
process.exit(2);
}
}

function checkClass(obj, name) {
console.log(`Checking ${name}`);
check(typeof obj === 'object', `new ${name}() produces object`);
check(obj.constructor.name === name, `object constructor is ${name}`);
}

console.log('Checking program');
check(typeof program === 'object', 'program is object');
check(program.constructor.name === 'Command', 'program is class Command');

checkClass(new Command(), 'Command');
checkClass(new Option('-e, --example'), 'Option');
checkClass(new CommanderError(1, 'code', 'failed'), 'CommanderError');
checkClass(new InvalidOptionArgumentError('failed'), 'InvalidOptionArgumentError');
checkClass(new Help(), 'Help');

console.log('Checking createCommand');
check(typeof createCommand === 'function', 'createCommand is function');

console.log('No problems');