From 4ff53565104a4673a65b014431195eb08240911f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Sat, 2 Dec 2017 11:31:56 +0100 Subject: [PATCH 1/8] Initial ES modules support --- index.js | 66 ++++++++++++++++++++++++++++++++++++++++------------ package.json | 1 + 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/index.js b/index.js index 45cbeac..a46dc7a 100644 --- a/index.js +++ b/index.js @@ -5,6 +5,7 @@ var relativePath = require('cached-path-relative') var browserResolve = require('browser-resolve'); var nodeResolve = require('resolve'); var detective = require('detective'); +var detectiveEsm = require('detective-esm'); var through = require('through2'); var concat = require('concat-stream'); var parents = require('parents'); @@ -58,6 +59,7 @@ function Deps (opts) { this.globalTransforms = [].concat(opts.globalTransform).filter(Boolean); this.resolver = opts.resolve || browserResolve; this.options = xtend(opts); + if (typeof this.options.esm === 'undefined') this.options.esm = false; if (!this.options.modules) this.options.modules = {}; // If the caller passes options.expose, store resolved pathnames for exposed @@ -376,14 +378,22 @@ Deps.prototype.walk = function (id, parent, cb) { } var c = self.cache && self.cache[file]; - if (c) return fromDeps(file, c.source, c.package, fakePath, Object.keys(c.deps)); + if (c) return fromDeps(file, c.source, c.package, fakePath, { + deps: Object.keys(c.deps), + imports: c.imports, + exports: c.exports + }); self.persistentCache(file, id, pkg, persistentCacheFallback, function (err, c) { if (err) { self.emit('error', err); return; } - fromDeps(file, c.source, c.package, fakePath, Object.keys(c.deps)); + fromDeps(file, c.source, c.package, fakePath, { + deps: Object.keys(c.deps), + imports: c.imports, + exports: c.exports + }); }); function persistentCacheFallback (dataAsString, cb) { @@ -395,15 +405,17 @@ Deps.prototype.walk = function (id, parent, cb) { })) .pipe(concat(function (body) { var src = body.toString('utf8'); - var deps = getDeps(file, src); - if (deps) { + var result = getDeps(file, src); + if (result) { cb(null, { source: src, package: pkg, - deps: deps.reduce(function (deps, dep) { + deps: result.deps.reduce(function (deps, dep) { deps[dep] = true; return deps; - }, {}) + }, {}), + imports: result.imports, + exports: result.exports }); } })); @@ -411,15 +423,18 @@ Deps.prototype.walk = function (id, parent, cb) { }); function getDeps (file, src) { - return rec.noparse ? [] : self.parseDeps(file, src); + return rec.noparse ? { deps: [], imports: null, exports: null } : self.parseDeps(file, src); } function fromSource (file, src, pkg, fakePath) { - var deps = getDeps(file, src); - if (deps) fromDeps(file, src, pkg, fakePath, deps); + var result = getDeps(file, src); + if (result) fromDeps(file, src, pkg, fakePath, result); } - function fromDeps (file, src, pkg, fakePath, deps) { + function fromDeps (file, src, pkg, fakePath, modules) { + var deps = modules.deps; + var imports = modules.imports; + var exports = modules.exports; var p = deps.length; var resolved = {}; @@ -454,6 +469,9 @@ Deps.prototype.walk = function (id, parent, cb) { if (!rec.source) rec.source = src; if (!rec.deps) rec.deps = resolved; if (!rec.file) rec.file = file; + if (opts.esm && isEsm(file)) { + rec.esm = { imports: imports, exports: exports }; + } if (self.entries.indexOf(file) >= 0) { rec.entry = true; @@ -467,15 +485,29 @@ Deps.prototype.walk = function (id, parent, cb) { }; Deps.prototype.parseDeps = function (file, src, cb) { - if (this.options.noParse === true) return []; - if (/\.json$/.test(file)) return []; + if (this.options.noParse === true) return { deps: [] }; + if (/\.json$/.test(file)) return { deps: [] }; if (Array.isArray(this.options.noParse) && this.options.noParse.indexOf(file) >= 0) { - return []; + return { deps: [] }; } + + var imports = null; + var exports = null; + var deps = null; - try { var deps = detective(src) } + try { + if (this.options.esm && isEsm(file)) { + var result = detectiveEsm(src); + deps = result.strings; + imports = result.imports; + exports = result.exports; + } + else { + deps = detective(src) + } + } catch (ex) { var message = ex && ex.message ? ex.message : ex; this.emit('error', new Error( @@ -483,7 +515,7 @@ Deps.prototype.parseDeps = function (file, src, cb) { )); return; } - return deps; + return { deps: deps, imports: imports, exports: exports }; }; Deps.prototype.lookupPackage = function (file, cb) { @@ -596,3 +628,7 @@ function wrapTransform (tr) { tr.on('error', function (err) { wrapper.emit('error', err) }); return wrapper; } + +function isEsm (file) { + return /\.mjs$/.test(file) +} diff --git a/package.json b/package.json index ad72a96..6083fd8 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "concat-stream": "~1.5.0", "defined": "^1.0.0", "detective": "^4.0.0", + "detective-esm": "*", "duplexer2": "^0.1.2", "inherits": "^2.0.1", "parents": "^1.0.0", From b59410273fff8548e8aeeb1c7c2924c8ed027cf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Sat, 2 Dec 2017 12:42:28 +0100 Subject: [PATCH 2/8] set `.esm` property on imported ES modules --- index.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index a46dc7a..d8b8a9b 100644 --- a/index.js +++ b/index.js @@ -470,7 +470,14 @@ Deps.prototype.walk = function (id, parent, cb) { if (!rec.deps) rec.deps = resolved; if (!rec.file) rec.file = file; if (opts.esm && isEsm(file)) { - rec.esm = { imports: imports, exports: exports }; + rec.esm = { + imports: imports.map(function (imp) { + var name = imp.from; + if (isEsm(rec.deps[name])) { imp.esm = true; } + return imp; + }), + exports: exports + }; } if (self.entries.indexOf(file) >= 0) { From 67f4b902f8c2c41e4d565e9aba25b12c65287926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Sun, 24 Dec 2017 14:05:13 +0100 Subject: [PATCH 3/8] Set `opts._flags.esm` when transforming ES modules --- index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/index.js b/index.js index d8b8a9b..38ff89d 100644 --- a/index.js +++ b/index.js @@ -261,6 +261,9 @@ Deps.prototype.getTransforms = function (file, pkg, opts) { tr = tr[0]; } trOpts._flags = trOpts.hasOwnProperty('_flags') ? trOpts._flags : self.options; + if (isEsm(file)) { + trOpts._flags = Object.assign({}, trOpts._flags, { esm: true }); + } if (typeof tr === 'function') { var t = tr(file, trOpts); self.emit('transform', t, file); From e73b46005e5c8c68f94d4dac91532663d4cb018e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Tue, 26 Dec 2017 12:53:33 +0100 Subject: [PATCH 4/8] use detective-esm from github --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6083fd8..3489ee3 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "concat-stream": "~1.5.0", "defined": "^1.0.0", "detective": "^4.0.0", - "detective-esm": "*", + "detective-esm": "browserify/detective-esm", "duplexer2": "^0.1.2", "inherits": "^2.0.1", "parents": "^1.0.0", From e26bdf8b64180adcd6d6e99b8deade36f670166c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Wed, 13 Nov 2019 12:48:45 +0100 Subject: [PATCH 5/8] Look at package.json#type field to determine whether a file is ESM --- index.js | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/index.js b/index.js index f202655..ed4cc4f 100644 --- a/index.js +++ b/index.js @@ -263,7 +263,7 @@ Deps.prototype.getTransforms = function (file, pkg, opts) { tr = tr[0]; } trOpts._flags = trOpts.hasOwnProperty('_flags') ? trOpts._flags : self.options; - if (isEsm(file)) { + if (isEsm(file, pkg)) { trOpts._flags = Object.assign({}, trOpts._flags, { esm: true }); } if (typeof tr === 'function') { @@ -431,7 +431,7 @@ Deps.prototype.walk = function (id, parent, cb) { .on('error', cb) .pipe(concat(function (body) { var src = body.toString('utf8'); - try { var result = getDeps(file, src); } + try { var result = getDeps(file, src, pkg); } catch (err) { cb(err); } if (result) { cb(null, { @@ -449,8 +449,8 @@ Deps.prototype.walk = function (id, parent, cb) { } }); - function getDeps (file, src) { - var deps = rec.noparse ? { deps: [], imports: null, exports: null } : self.parseDeps(file, src); + function getDeps (file, src, pkg) { + var deps = rec.noparse ? { deps: [], imports: null, exports: null } : self.parseDeps(file, src, pkg); // dependencies emitted by transforms if (self._transformDeps[file]) deps.deps = deps.deps.concat(self._transformDeps[file]); return deps; @@ -500,11 +500,13 @@ Deps.prototype.walk = function (id, parent, cb) { if (!rec.source) rec.source = src; if (!rec.deps) rec.deps = resolved; if (!rec.file) rec.file = file; - if (opts.esm && isEsm(file)) { + if (opts.esm && isEsm(file, pkg)) { rec.esm = { imports: imports.map(function (imp) { var name = imp.from; - if (isEsm(rec.deps[name])) { imp.esm = true; } + var importee = rec.deps[name]; + var pkg = self.pkgCache[importee]; + if (isEsm(importee, pkg)) { imp.esm = true; } return imp; }), exports: exports @@ -522,7 +524,7 @@ Deps.prototype.walk = function (id, parent, cb) { } }; -Deps.prototype.parseDeps = function (file, src, cb) { +Deps.prototype.parseDeps = function (file, src, pkg) { var self = this; if (this.options.noParse === true) return { deps: [] }; if (/\.json$/.test(file)) return { deps: [] }; @@ -537,7 +539,7 @@ Deps.prototype.parseDeps = function (file, src, cb) { var deps = null; try { - if (this.options.esm && isEsm(file)) { + if (this.options.esm && isEsm(file, pkg)) { var result = detectiveEsm(src); deps = result.strings; imports = result.imports; @@ -667,6 +669,10 @@ function wrapTransform (tr) { return wrapper; } -function isEsm (file) { - return /\.mjs$/.test(file) +function isEsm (file, pkg) { + if (pkg && pkg.type === 'module') { + return !/\.cjs$/.test(file); + } else { + return /\.mjs$/.test(file); + } } From 9180eaa31a39513603d8be4b955685e6dd0daa62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Wed, 13 Nov 2019 12:57:24 +0100 Subject: [PATCH 6/8] Pass `sourceType` to detective() calls --- index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index ed4cc4f..7bf3a5d 100644 --- a/index.js +++ b/index.js @@ -540,13 +540,13 @@ Deps.prototype.parseDeps = function (file, src, pkg) { try { if (this.options.esm && isEsm(file, pkg)) { - var result = detectiveEsm(src); + var result = detectiveEsm(src, { sourceType: 'module' }); deps = result.strings; imports = result.imports; exports = result.exports; } else { - deps = self.detective(src) + deps = self.detective(src, { sourceType: 'script' }) } } catch (ex) { From 91d90e12f0d98812b4fd3c3fbff29567dbc08a00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Wed, 13 Nov 2019 13:21:28 +0100 Subject: [PATCH 7/8] Resolve .mjs by default from es modules --- index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/index.js b/index.js index 7bf3a5d..2e0c791 100644 --- a/index.js +++ b/index.js @@ -178,6 +178,9 @@ Deps.prototype.resolve = function (id, parent, cb) { if (opts.extensions) parent.extensions = opts.extensions; if (opts.modules) parent.modules = opts.modules; + if (isEsm(parent.filename, parent.package)) { + parent.extensions = ['.mjs'].concat(parent.extensions || ['.js']) + } self.resolver(id, parent, function onresolve (err, file, pkg, fakePath) { if (err) return cb(err); From 8e4a30a6ca264bae8c70d0f6328d1c0b74df4070 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Wed, 13 Nov 2019 13:21:43 +0100 Subject: [PATCH 8/8] Add package.json "type" test --- test/esm_type.js | 65 ++++++++++++++++++++++++++++++++ test/files/esm_type/cjs.cjs | 1 + test/files/esm_type/esm.js | 1 + test/files/esm_type/main.js | 4 ++ test/files/esm_type/package.json | 3 ++ 5 files changed, 74 insertions(+) create mode 100644 test/esm_type.js create mode 100644 test/files/esm_type/cjs.cjs create mode 100644 test/files/esm_type/esm.js create mode 100644 test/files/esm_type/main.js create mode 100644 test/files/esm_type/package.json diff --git a/test/esm_type.js b/test/esm_type.js new file mode 100644 index 0000000..ced1e90 --- /dev/null +++ b/test/esm_type.js @@ -0,0 +1,65 @@ +var parser = require('../'); +var test = require('tap').test; +var fs = require('fs'); +var path = require('path'); + +var files = { + main: path.join(__dirname, '/files/esm_type/main.js'), + esm: path.join(__dirname, '/files/esm_type/esm.js'), + cjs: path.join(__dirname, '/files/esm_type/cjs.cjs') +}; + +var sources = Object.keys(files).reduce(function (acc, file) { + acc[file] = fs.readFileSync(files[file], 'utf8'); + return acc; +}, {}); + +test('package.json type: "module"', function (t) { + t.plan(1); + var p = parser({ esm: true }); + p.end({ file: files.main, entry: true }); + + var rows = []; + p.on('data', function (row) { rows.push(row) }); + p.on('end', function () { + t.same(rows.sort(cmp), [ + { + id: files.main, + file: files.main, + source: sources.main, + entry: true, + deps: { './esm.js': files.esm, './cjs.cjs': files.cjs }, + esm: { + imports: [ + { from: './esm.js', import: 'default', as: 'esm' }, + { from: './cjs.cjs', import: 'default', as: 'cjs' } + ], + exports: [ + { export: 'esm', as: 'esm' }, + { export: 'cjs', as: 'cjs' } + ] + } + }, + { + id: files.esm, + file: files.esm, + source: sources.esm, + deps: {}, + esm: { + imports: [], + exports: [ + { export: null, as: 'default' } + ] + } + }, + { + id: files.cjs, + file: files.cjs, + source: sources.cjs, + deps: {} + } + ].sort(cmp)); + }); +}); + +function cmp (a, b) { return a.id < b.id ? -1 : 1 } diff --git a/test/files/esm_type/cjs.cjs b/test/files/esm_type/cjs.cjs new file mode 100644 index 0000000..2ef28d1 --- /dev/null +++ b/test/files/esm_type/cjs.cjs @@ -0,0 +1 @@ +module.exports = 'commonjs' diff --git a/test/files/esm_type/esm.js b/test/files/esm_type/esm.js new file mode 100644 index 0000000..c6bfd4e --- /dev/null +++ b/test/files/esm_type/esm.js @@ -0,0 +1 @@ +export default 'esmodules' diff --git a/test/files/esm_type/main.js b/test/files/esm_type/main.js new file mode 100644 index 0000000..d87283f --- /dev/null +++ b/test/files/esm_type/main.js @@ -0,0 +1,4 @@ +import esm from './esm.js' +import cjs from './cjs.cjs' + +export { esm, cjs } diff --git a/test/files/esm_type/package.json b/test/files/esm_type/package.json new file mode 100644 index 0000000..4720025 --- /dev/null +++ b/test/files/esm_type/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +}