Skip to content

Commit 28d6e51

Browse files
lkmillljharb
authored andcommitted
[New] Use import() on esm files in supported node versions
1 parent 9bbf270 commit 28d6e51

File tree

19 files changed

+257
-11
lines changed

19 files changed

+257
-11
lines changed

.eslintrc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@
2929
"strict": "error",
3030
},
3131
"overrides": [
32+
{
33+
"files": ["*.mjs", "test/import/package_type/*.js"],
34+
"extends": "@ljharb/eslint-config/esm",
35+
},
36+
{
37+
"files": ["bin/import-or-require.js"],
38+
"parserOptions": {
39+
"ecmaVersion": 2020,
40+
},
41+
},
3242
{
3343
"files": ["test/async-await/*"],
3444
"parserOptions": {

.nycrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"check-coverage": false,
44
"reporter": ["text-summary", "text", "html", "json"],
55
"exclude": [
6+
"bin/import-or-require.js",
67
"coverage",
78
"example",
89
"test"

bin/import-or-require.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
'use strict';
2+
3+
const { extname: extnamePath } = require('path');
4+
const getPackageType = require('get-package-type');
5+
6+
module.exports = function (file) {
7+
const ext = extnamePath(file);
8+
9+
if (ext === '.mjs' || (ext === '.js' && getPackageType.sync(file) === 'module')) {
10+
return import(file);
11+
}
12+
require(file);
13+
};

bin/tape

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ var readFileSync = require('fs').readFileSync;
88
var parseOpts = require('minimist');
99
var glob = require('glob');
1010
var ignore = require('dotignore');
11+
var hasImport = require('has-dynamic-import');
12+
13+
var tape = require('../');
1114

1215
var opts = parseOpts(process.argv.slice(2), {
1316
alias: { r: 'require', i: 'ignore' },
@@ -47,7 +50,7 @@ var files = opts._.reduce(function (result, arg) {
4750
var files = glob.sync(arg);
4851

4952
if (!Array.isArray(files)) {
50-
throw new TypeError('unknown error: glob.sync did not return an array or throw. Please report this.');
53+
throw new TypeError('unknown error: glob.sync("' + arg + '") did not return an array or throw. Please report this.');
5154
}
5255

5356
return result.concat(files);
@@ -57,6 +60,28 @@ var files = opts._.reduce(function (result, arg) {
5760
return resolvePath(cwd, file);
5861
});
5962

60-
files.forEach(function (x) { require(x); });
63+
hasImport().then(function (hasSupport) {
64+
// the nextTick callback gets called outside the promise chain, avoiding
65+
// promises and unhandled rejections when only loading commonjs files
66+
process.nextTick(importFiles, hasSupport);
67+
});
68+
69+
function importFiles(hasSupport) {
70+
if (!hasSupport) {
71+
return files.forEach(function (x) { require(x); });
72+
}
73+
74+
var importOrRequire = require('./import-or-require');
75+
76+
tape.wait();
77+
78+
var promise = files.reduce(function (promise, file) {
79+
return promise ? promise.then(function () {
80+
return importOrRequire(file);
81+
}) : importOrRequire(file);
82+
}, null);
83+
84+
return promise ? promise.then(function () { tape.run(); }) : tape.run();
85+
}
6186

6287
// vim: ft=javascript

index.js

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,22 @@ var canExit = typeof process !== 'undefined' && process
1414
;
1515

1616
exports = module.exports = (function () {
17+
var wait = false;
1718
var harness;
1819
var lazyLoad = function () {
1920
return getHarness().apply(this, arguments);
2021
};
2122

23+
lazyLoad.wait = function () {
24+
wait = true;
25+
};
26+
27+
lazyLoad.run = function () {
28+
var run = getHarness().run;
29+
30+
if (run) run();
31+
};
32+
2233
lazyLoad.only = function () {
2334
return getHarness().only.apply(this, arguments);
2435
};
@@ -48,26 +59,25 @@ exports = module.exports = (function () {
4859
function getHarness(opts) {
4960
if (!opts) opts = {};
5061
opts.autoclose = !canEmitExit;
51-
if (!harness) harness = createExitHarness(opts);
62+
if (!harness) harness = createExitHarness(opts, wait);
5263
return harness;
5364
}
5465
})();
5566

56-
function createExitHarness(conf) {
67+
function createExitHarness(conf, wait) {
5768
if (!conf) conf = {};
5869
var harness = createHarness({
5970
autoclose: defined(conf.autoclose, false)
6071
});
72+
var running = false;
73+
var ended = false;
6174

62-
var stream = harness.createStream({ objectMode: conf.objectMode });
63-
var es = stream.pipe(conf.stream || createDefaultStream());
64-
if (canEmitExit) {
65-
es.on('error', function (err) { harness._exitCode = 1; });
75+
if (wait) {
76+
harness.run = run;
77+
} else {
78+
run();
6679
}
6780

68-
var ended = false;
69-
stream.on('end', function () { ended = true; });
70-
7181
if (conf.exit === false) return harness;
7282
if (!canEmitExit || !canExit) return harness;
7383

@@ -92,6 +102,17 @@ function createExitHarness(conf) {
92102
});
93103

94104
return harness;
105+
106+
function run() {
107+
if (running) return;
108+
running = true;
109+
var stream = harness.createStream({ objectMode: conf.objectMode });
110+
var es = stream.pipe(conf.stream || createDefaultStream());
111+
if (canEmitExit) {
112+
es.on('error', function (err) { harness._exitCode = 1; });
113+
}
114+
stream.on('end', function () { ended = true; });
115+
};
95116
}
96117

97118
exports.createHarness = createHarness;

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@
2727
"defined": "^1.0.0",
2828
"dotignore": "^0.1.2",
2929
"for-each": "^0.3.3",
30+
"get-package-type": "^0.1.0",
3031
"glob": "^7.1.7",
3132
"has": "^1.0.3",
33+
"has-dynamic-import": "^2.0.0",
3234
"inherits": "^2.0.4",
3335
"is-regex": "^1.1.3",
3436
"minimist": "^1.2.5",
@@ -41,6 +43,7 @@
4143
"through": "^2.3.8"
4244
},
4345
"devDependencies": {
46+
"@ljharb/eslint-config": "^17.6.0",
4447
"array.prototype.flatmap": "^1.2.4",
4548
"aud": "^1.1.5",
4649
"concat-stream": "^1.6.2",

test/import.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
'use strict';
2+
3+
var tap = require('tap');
4+
var spawn = require('child_process').spawn;
5+
var concat = require('concat-stream');
6+
var hasDynamicImport = require('has-dynamic-import');
7+
8+
tap.test('importing mjs files', function (t) {
9+
hasDynamicImport().then(function (hasSupport) {
10+
if (hasSupport) {
11+
var tc = function (rows) {
12+
t.same(rows.toString('utf8'), [
13+
'TAP version 13',
14+
'# mjs-a',
15+
'ok 1 test ran',
16+
'# mjs-b',
17+
'ok 2 test ran after mjs-a',
18+
'# mjs-c',
19+
'ok 3 test ran after mjs-b',
20+
'# mjs-d',
21+
'ok 4 test ran after mjs-c',
22+
'# mjs-e',
23+
'ok 5 test ran after mjs-d',
24+
'# mjs-f',
25+
'ok 6 test ran after mjs-e',
26+
'# mjs-g',
27+
'ok 7 test ran after mjs-f',
28+
'# mjs-h',
29+
'ok 8 test ran after mjs-g',
30+
'',
31+
'1..8',
32+
'# tests 8',
33+
'# pass 8',
34+
'',
35+
'# ok'
36+
].join('\n') + '\n\n');
37+
};
38+
39+
var ps = tape('import/*.mjs');
40+
ps.stdout.pipe(concat(tc));
41+
ps.stderr.pipe(process.stderr);
42+
ps.on('exit', function (code) {
43+
t.equal(code, 0);
44+
t.end();
45+
});
46+
} else {
47+
t.pass('does not support dynamic import');
48+
t.end();
49+
}
50+
});
51+
});
52+
53+
tap.test('importing type: "module" files', function (t) {
54+
hasDynamicImport().then(function (hasSupport) {
55+
if (hasSupport) {
56+
var tc = function (rows) {
57+
t.same(rows.toString('utf8'), [
58+
'TAP version 13',
59+
'# package-type-a',
60+
'ok 1 test ran',
61+
'# package-type-b',
62+
'ok 2 test ran after package-type-a',
63+
'# package-type-c',
64+
'ok 3 test ran after package-type-b',
65+
'',
66+
'1..3',
67+
'# tests 3',
68+
'# pass 3',
69+
'',
70+
'# ok'
71+
].join('\n') + '\n\n');
72+
};
73+
74+
var ps = tape('import/package_type/*.js');
75+
ps.stdout.pipe(concat(tc));
76+
ps.stderr.pipe(process.stderr);
77+
ps.on('exit', function (code) {
78+
t.equal(code, 0);
79+
t.end();
80+
});
81+
} else {
82+
t.pass('does not support dynamic import');
83+
t.end();
84+
}
85+
});
86+
});
87+
88+
function tape(args) {
89+
var proc = require('child_process');
90+
var bin = __dirname + '/../bin/tape';
91+
92+
return proc.spawn('node', [bin].concat(args.split(' ')), { cwd: __dirname });
93+
}

test/import/mjs-a.mjs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import tape from '../../index.js';
2+
3+
tape.test('mjs-a', function (t) {
4+
t.pass('test ran');
5+
t.end();
6+
global.mjs_a = true;
7+
});
8+

test/import/mjs-b.mjs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import tape from '../../index.js';
2+
3+
tape.test('mjs-b', function (t) {
4+
t.ok(global.mjs_a, 'test ran after mjs-a');
5+
t.end();
6+
global.mjs_b = true;
7+
});

test/import/mjs-c.mjs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import tape from '../../index.js';
2+
3+
tape.test('mjs-c', function (t) {
4+
t.ok(global.mjs_b, 'test ran after mjs-b');
5+
t.end();
6+
global.mjs_c = true;
7+
});

0 commit comments

Comments
 (0)