From 15d064867185b27a7de9bf62e3aecf8e7e2e22e0 Mon Sep 17 00:00:00 2001 From: Sebastian Noack Date: Tue, 2 Jun 2020 22:51:03 -0400 Subject: [PATCH] New: Support ESM w/ mjs extension where available (#214) --- lib/shared/require-or-import.js | 28 ++++++++++++++++++ lib/versioned/^3.7.0/index.js | 23 +++++++++------ lib/versioned/^4.0.0-alpha.1/index.js | 15 ++++++---- lib/versioned/^4.0.0-alpha.2/index.js | 15 ++++++---- lib/versioned/^4.0.0/index.js | 15 ++++++---- package.json | 2 +- test/esm.js | 41 +++++++++++++++++++++++++++ test/expected/esm.txt | 3 ++ test/fixtures/gulpfiles/gulpfile.mjs | 10 +++++++ 9 files changed, 124 insertions(+), 28 deletions(-) create mode 100644 lib/shared/require-or-import.js create mode 100644 test/esm.js create mode 100644 test/expected/esm.txt create mode 100644 test/fixtures/gulpfiles/gulpfile.mjs diff --git a/lib/shared/require-or-import.js b/lib/shared/require-or-import.js new file mode 100644 index 00000000..6a5bdf55 --- /dev/null +++ b/lib/shared/require-or-import.js @@ -0,0 +1,28 @@ +'use strict'; + +var pathToFileURL = require('url').pathToFileURL; + +var importESM; +try { + importESM = new Function('id', 'return import(id);'); +} catch (e) { + importESM = null; +} + +function requireOrImport(path, callback) { + var err = null; + var cjs; + try { + cjs = require(path); + } catch (e) { + if (pathToFileURL && importESM && e.code === 'ERR_REQUIRE_ESM') { + var url = pathToFileURL(path); + importESM(url).then(function(esm) { callback(null, esm); }, callback); + return; + } + err = e; + } + process.nextTick(function() { callback(err, cjs); }); +} + +module.exports = requireOrImport; diff --git a/lib/versioned/^3.7.0/index.js b/lib/versioned/^3.7.0/index.js index 542d0cc0..25d1bf6f 100644 --- a/lib/versioned/^3.7.0/index.js +++ b/lib/versioned/^3.7.0/index.js @@ -11,9 +11,11 @@ var copyTree = require('../../shared/log/copy-tree'); var tildify = require('../../shared/tildify'); var logTasks = require('../../shared/log/tasks'); var ansi = require('../../shared/ansi'); +var exit = require('../../shared/exit'); var logEvents = require('./log/events'); var logTasksSimple = require('./log/tasks-simple'); var registerExports = require('../../shared/register-exports'); +var requireOrImport = require('../../shared/require-or-import'); function execute(opts, env, config) { var tasks = opts._; @@ -25,20 +27,23 @@ function execute(opts, env, config) { } // This is what actually loads up the gulpfile - var exported = require(env.configPath); - log.info('Using gulpfile', ansi.magenta(tildify(env.configPath))); + requireOrImport(env.configPath, function(err, exported) { + if (err) { + console.error(err); + exit(1); + } - var gulpInst = require(env.modulePath); - logEvents(gulpInst); + log.info('Using gulpfile', ansi.magenta(tildify(env.configPath))); - registerExports(gulpInst, exported); + var gulpInst = require(env.modulePath); + logEvents(gulpInst); - // Always unmute stdout after gulpfile is required - stdout.unmute(); + registerExports(gulpInst, exported); - process.nextTick(function() { - var tree; + // Always unmute stdout after gulpfile is required + stdout.unmute(); + var tree; if (opts.tasksSimple) { return logTasksSimple(env, gulpInst); } diff --git a/lib/versioned/^4.0.0-alpha.1/index.js b/lib/versioned/^4.0.0-alpha.1/index.js index e0516e9a..ca1fd288 100644 --- a/lib/versioned/^4.0.0-alpha.1/index.js +++ b/lib/versioned/^4.0.0-alpha.1/index.js @@ -16,6 +16,7 @@ var logTasksSimple = require('../^4.0.0/log/tasks-simple'); var registerExports = require('../../shared/register-exports'); var copyTree = require('../../shared/log/copy-tree'); +var requireOrImport = require('../../shared/require-or-import'); function execute(opts, env, config) { @@ -32,16 +33,18 @@ function execute(opts, env, config) { logSyncTask(gulpInst, opts); // This is what actually loads up the gulpfile - var exported = require(env.configPath); + requireOrImport(env.configPath, function(err, exported) { + if (err) { + console.error(err); + exit(1); + } - registerExports(gulpInst, exported); + registerExports(gulpInst, exported); - // Always unmute stdout after gulpfile is required - stdout.unmute(); + // Always unmute stdout after gulpfile is required + stdout.unmute(); - process.nextTick(function() { var tree; - if (opts.tasksSimple) { return logTasksSimple(gulpInst.tree()); } diff --git a/lib/versioned/^4.0.0-alpha.2/index.js b/lib/versioned/^4.0.0-alpha.2/index.js index 3a4e6f83..8057e0dc 100644 --- a/lib/versioned/^4.0.0-alpha.2/index.js +++ b/lib/versioned/^4.0.0-alpha.2/index.js @@ -17,6 +17,7 @@ var registerExports = require('../../shared/register-exports'); var copyTree = require('../../shared/log/copy-tree'); var getTask = require('../^4.0.0/log/get-task'); +var requireOrImport = require('../../shared/require-or-import'); function execute(opts, env, config) { @@ -33,16 +34,18 @@ function execute(opts, env, config) { logSyncTask(gulpInst, opts); // This is what actually loads up the gulpfile - var exported = require(env.configPath); + requireOrImport(env.configPath, function(err, exported) { + if (err) { + console.error(err); + exit(1); + } - registerExports(gulpInst, exported); + registerExports(gulpInst, exported); - // Always unmute stdout after gulpfile is required - stdout.unmute(); + // Always unmute stdout after gulpfile is required + stdout.unmute(); - process.nextTick(function() { var tree; - if (opts.tasksSimple) { tree = gulpInst.tree(); return logTasksSimple(tree.nodes); diff --git a/lib/versioned/^4.0.0/index.js b/lib/versioned/^4.0.0/index.js index f053a5e1..a5b2623e 100644 --- a/lib/versioned/^4.0.0/index.js +++ b/lib/versioned/^4.0.0/index.js @@ -17,6 +17,7 @@ var registerExports = require('../../shared/register-exports'); var copyTree = require('../../shared/log/copy-tree'); var getTask = require('./log/get-task'); +var requireOrImport = require('../../shared/require-or-import'); function execute(opts, env, config) { @@ -33,16 +34,18 @@ function execute(opts, env, config) { logSyncTask(gulpInst, opts); // This is what actually loads up the gulpfile - var exported = require(env.configPath); + requireOrImport(env.configPath, function(err, exported) { + if (err) { + console.error(err); + exit(1); + } - registerExports(gulpInst, exported); + registerExports(gulpInst, exported); - // Always unmute stdout after gulpfile is required - stdout.unmute(); + // Always unmute stdout after gulpfile is required + stdout.unmute(); - process.nextTick(function() { var tree; - if (opts.tasksSimple) { tree = gulpInst.tree(); return logTasksSimple(tree.nodes); diff --git a/package.json b/package.json index 2c49d385..2ab95b5e 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "pretty-hrtime": "^1.0.0", "replace-homedir": "^1.0.0", "semver-greatest-satisfied-range": "^1.1.0", - "v8flags": "^3.0.1", + "v8flags": "^3.2.0", "yargs": "^7.1.0" }, "devDependencies": { diff --git a/test/esm.js b/test/esm.js new file mode 100644 index 00000000..afe7c78e --- /dev/null +++ b/test/esm.js @@ -0,0 +1,41 @@ +'use strict'; + +var expect = require('expect'); +var fs = require('fs'); +var path = require('path'); +var semver = require('semver'); +var skipLines = require('gulp-test-tools').skipLines; +var eraseTime = require('gulp-test-tools').eraseTime; +var runner = require('gulp-test-tools').gulpRunner; + +var expectedDir = path.join(__dirname, 'expected'); + +if (semver.gte(process.version, '10.15.3')) { + + describe('ESM', function() { + + it('prints the task list', function(done) { + var options = '--tasks --sort-tasks ' + + '--gulpfile ./test/fixtures/gulpfiles/gulpfile.mjs'; + var trailingLines = 1; + if (!semver.satisfies(process.version, '^12.17.0 || >=13.2.0')) { + options += ' --experimental-modules'; + trailingLines += 2; + } + + runner({ verbose: false }).gulp(options).run(cb); + + function cb(err, stdout, stderr) { + expect(err).toEqual(null); + expect(stderr).toMatch(/^(.*ExperimentalWarning: The ESM module loader is experimental\.\n)?$/); + var filepath = path.join(expectedDir, 'esm.txt'); + var expected = fs.readFileSync(filepath, 'utf-8'); + stdout = eraseTime(skipLines(stdout, trailingLines)); + expect(stdout).toEqual(expected); + done(err); + } + }); + + }); + +} diff --git a/test/expected/esm.txt b/test/expected/esm.txt new file mode 100644 index 00000000..e50b9893 --- /dev/null +++ b/test/expected/esm.txt @@ -0,0 +1,3 @@ +gulp-cli/test/fixtures/gulpfiles +├── exported +└── registered diff --git a/test/fixtures/gulpfiles/gulpfile.mjs b/test/fixtures/gulpfiles/gulpfile.mjs new file mode 100644 index 00000000..0b2b3885 --- /dev/null +++ b/test/fixtures/gulpfiles/gulpfile.mjs @@ -0,0 +1,10 @@ +import gulp from 'gulp'; + +function noop(cb) { + cb(); +} + +gulp.task('registered', noop); + +export function exported(){}; +export const string = 'no function';