Skip to content

Commit f3b9ee6

Browse files
committed
add support for esm named exports
Through an ESM wrapper and package.exports this change allows the semver module to be both imported and required while still supporting legacy versions of Node.js. An important advantage to this change is that semver will now support named imports, which are unsupported in commonjs modules. One thing to note. This change introduces the package.exports field which locks down what modules can be deeply imported. Currently support is maintained to allow deep imports of all files currently included in package.files to keep the change Semver Minor. In the future this interface could be further locked down.
1 parent 226e6dc commit f3b9ee6

File tree

6 files changed

+212
-1
lines changed

6 files changed

+212
-1
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ language: node_js
22

33
node_js:
44
- node
5+
- 14
56
- 12
67
- 10
78

esm-wrapper.mjs

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// just pre-load all the stuff that index.js lazily exports
2+
import {createRequire} from 'module';
3+
4+
const require = createRequire(import.meta.url);
5+
6+
const internalRe = require('./internal/re');
7+
8+
const re = internalRe.re;
9+
const src = internalRe.src;
10+
const tokens = internalRe.t;
11+
const SEMVER_SPEC_VERSION = require('./internal/constants').SEMVER_SPEC_VERSION;
12+
const SemVer = require('./classes/semver');
13+
const compareIdentifiers = require('./internal/identifiers').compareIdentifiers;
14+
const rcompareIdentifiers = require('./internal/identifiers').rcompareIdentifiers;
15+
const parse = require('./functions/parse');
16+
const valid = require('./functions/valid');
17+
const clean = require('./functions/clean');
18+
const inc = require('./functions/inc');
19+
const diff = require('./functions/diff');
20+
const major = require('./functions/major');
21+
const minor = require('./functions/minor');
22+
const patch = require('./functions/patch');
23+
const prerelease = require('./functions/prerelease');
24+
const compare = require('./functions/compare');
25+
const rcompare = require('./functions/rcompare');
26+
const compareLoose = require('./functions/compare-loose');
27+
const compareBuild = require('./functions/compare-build');
28+
const sort = require('./functions/sort');
29+
const rsort = require('./functions/rsort');
30+
const gt = require('./functions/gt');
31+
const lt = require('./functions/lt');
32+
const eq = require('./functions/eq');
33+
const neq = require('./functions/neq');
34+
const gte = require('./functions/gte');
35+
const lte = require('./functions/lte');
36+
const cmp = require('./functions/cmp');
37+
const coerce = require('./functions/coerce');
38+
const Comparator = require('./classes/comparator');
39+
const Range = require('./classes/range');
40+
const satisfies = require('./functions/satisfies');
41+
const toComparators = require('./ranges/to-comparators');
42+
const maxSatisfying = require('./ranges/max-satisfying');
43+
const minSatisfying = require('./ranges/min-satisfying');
44+
const minVersion = require('./ranges/min-version');
45+
const validRange = require('./ranges/valid');
46+
const outside = require('./ranges/outside');
47+
const gtr = require('./ranges/gtr');
48+
const ltr = require('./ranges/ltr');
49+
const intersects = require('./ranges/intersects');
50+
const simplifyRange = require('./ranges/simplify');
51+
const subset = require('./ranges/subset');
52+
53+
export default {
54+
re,
55+
src,
56+
tokens,
57+
SEMVER_SPEC_VERSION,
58+
SemVer,
59+
compareIdentifiers,
60+
rcompareIdentifiers,
61+
parse,
62+
valid,
63+
clean,
64+
inc,
65+
diff,
66+
major,
67+
minor,
68+
patch,
69+
prerelease,
70+
compare,
71+
rcompare,
72+
compareLoose,
73+
compareBuild,
74+
sort,
75+
rsort,
76+
gt,
77+
lt,
78+
eq,
79+
neq,
80+
gte,
81+
lte,
82+
cmp,
83+
coerce,
84+
Comparator,
85+
Range,
86+
satisfies,
87+
toComparators,
88+
maxSatisfying,
89+
minSatisfying,
90+
minVersion,
91+
validRange,
92+
outside,
93+
gtr,
94+
ltr,
95+
intersects,
96+
simplifyRange,
97+
subset
98+
};
99+
100+
export {
101+
re,
102+
src,
103+
tokens,
104+
SEMVER_SPEC_VERSION,
105+
SemVer,
106+
compareIdentifiers,
107+
rcompareIdentifiers,
108+
parse,
109+
valid,
110+
clean,
111+
inc,
112+
diff,
113+
major,
114+
minor,
115+
patch,
116+
prerelease,
117+
compare,
118+
rcompare,
119+
compareLoose,
120+
compareBuild,
121+
sort,
122+
rsort,
123+
gt,
124+
lt,
125+
eq,
126+
neq,
127+
gte,
128+
lte,
129+
cmp,
130+
coerce,
131+
Comparator,
132+
Range,
133+
satisfies,
134+
toComparators,
135+
maxSatisfying,
136+
minSatisfying,
137+
minVersion,
138+
validRange,
139+
outside,
140+
gtr,
141+
ltr,
142+
intersects,
143+
simplifyRange,
144+
subset
145+
};

package.json

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,24 @@
33
"version": "7.3.2",
44
"description": "The semantic version parser used by npm.",
55
"main": "index.js",
6+
"exports": {
7+
".": {
8+
"require": "./index.js",
9+
"import": "./esm-wrapper.mjs"
10+
},
11+
"./bin/": "./bin/",
12+
"./range.bnf": "./range.bnf",
13+
"./classes/": "./classes/",
14+
"./functions/": "./functions/",
15+
"./internal/": "./internal/",
16+
"./ranges/": "./ranges/",
17+
"./index.js": "./index.js",
18+
"./index": "./index.js",
19+
"./preload.js": "./preload.js",
20+
"./preload": "./preload.js",
21+
"./package.json": "./package.json",
22+
"./package": "./package.json"
23+
},
624
"scripts": {
725
"test": "tap",
826
"snap": "tap",
@@ -30,7 +48,8 @@
3048
],
3149
"tap": {
3250
"check-coverage": true,
33-
"coverage-map": "map.js"
51+
"coverage-map": "map.js",
52+
"test-ignore": "/test/esm/"
3453
},
3554
"engines": {
3655
"node": ">=10"

test/esm/test-default.mjs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import {createRequire} from 'module'
2+
import semver from '../../esm-wrapper.mjs'
3+
import t from 'tap'
4+
5+
const require = createRequire(import.meta.url)
6+
7+
const {SEMVER_SPEC_VERSION} = require('../../internal/constants')
8+
9+
t.match(Object.getOwnPropertyDescriptor(semver, 'SEMVER_SPEC_VERSION'), {
10+
get: undefined,
11+
set: undefined,
12+
value: SEMVER_SPEC_VERSION,
13+
configurable: true,
14+
enumerable: true
15+
}, 'just a normal value property')

test/esm/test-named.mjs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import {clean, coerce, gt, lt, satisfies, valid} from '../../esm-wrapper.mjs'
2+
import t from 'tap'
3+
4+
t.test('esm: test named exports', t => {
5+
t.ok(valid('1.2.3'));
6+
t.notOk(valid('a.b.c'))
7+
t.equals(clean(' =v1.2.3 '), '1.2.3')
8+
t.ok(satisfies('1.2.3', '1.x || >=2.5.0 || 5.0.0 - 7.2.3'))
9+
t.notOk(gt('1.2.3', '9.8.7'))
10+
t.ok(lt('1.2.3', '9.8.7'))
11+
t.equals(valid(coerce('v2')), '2.0.0')
12+
t.equals(valid(coerce('42.6.7.9.3-alpha')), '42.6.7')
13+
t.end()
14+
});

test/test-esm.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
const {resolve} = require('path');
2+
const {gt} = require('../');
3+
const tap = require('tap');
4+
5+
if (gt(process.version, '14.0.0')) {
6+
tap.test('test esm', async t => {
7+
const defaultTest = resolve(__dirname, './esm/test-default.mjs');
8+
const namedExportsTest = resolve(__dirname, './esm/test-named.mjs');
9+
await t.spawn(process.execPath, defaultTest);
10+
await t.spawn(process.execPath, namedExportsTest)
11+
t.ok(true)
12+
t.end();
13+
});
14+
} else {
15+
tap.ok(true, 'Skip ESM Test on unsupported Node.js');
16+
tap.end();
17+
}

0 commit comments

Comments
 (0)