Skip to content

Commit

Permalink
Add support for named imports in ESM (#1440)
Browse files Browse the repository at this point in the history
* Add esm wrapper and conditional exports

* createCommand is an implict export, make explicit

* Use deep link instead of experimental conditional

* Add mjs to lint

* Add sanity checks for esm imports

* Create class instances in test

* Add option so test-esm works for node 10 through 15

* Remove stale comment

* Condense wrapper

* Move esm to declaring program section of README

* Add esm and TypeScript to program section

* fix typo
  • Loading branch information
shadowspawn committed Jan 20, 2021
1 parent 7ab9d3d commit 37825b3
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 19 deletions.
10 changes: 8 additions & 2 deletions .eslintrc.js
@@ -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
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
@@ -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
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",
"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
@@ -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');

0 comments on commit 37825b3

Please sign in to comment.