Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] ES modules #137

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
91 changes: 73 additions & 18 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -60,6 +61,7 @@ function Deps (opts) {
this.resolver = opts.resolve || browserResolve;
this.detective = opts.detect || detective;
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
Expand Down Expand Up @@ -176,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);
Expand Down Expand Up @@ -261,6 +266,9 @@ Deps.prototype.getTransforms = function (file, pkg, opts) {
tr = tr[0];
}
trOpts._flags = trOpts.hasOwnProperty('_flags') ? trOpts._flags : self.options;
if (isEsm(file, pkg)) {
trOpts._flags = Object.assign({}, trOpts._flags, { esm: true });
}
if (typeof tr === 'function') {
var t = tr(file, trOpts);
// allow transforms to `stream.emit('dep', path)` to add dependencies for this file
Expand Down Expand Up @@ -397,15 +405,23 @@ 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) {
self.emit('file', file, id);
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) {
Expand All @@ -418,35 +434,40 @@ Deps.prototype.walk = function (id, parent, cb) {
.on('error', cb)
.pipe(concat(function (body) {
var src = body.toString('utf8');
try { var deps = getDeps(file, src); }
try { var result = getDeps(file, src, pkg); }
catch (err) { cb(err); }
if (deps) {
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
});
}
}));
}
});

function getDeps (file, src) {
var deps = rec.noparse ? [] : 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.concat(self._transformDeps[file]);
if (self._transformDeps[file]) deps.deps = deps.deps.concat(self._transformDeps[file]);
return deps;
}

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 = {};

Expand Down Expand Up @@ -482,6 +503,18 @@ 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, pkg)) {
rec.esm = {
imports: imports.map(function (imp) {
var name = imp.from;
var importee = rec.deps[name];
var pkg = self.pkgCache[importee];
if (isEsm(importee, pkg)) { imp.esm = true; }
return imp;
}),
exports: exports
};
}

if (self.entries.indexOf(file) >= 0) {
rec.entry = true;
Expand All @@ -494,24 +527,38 @@ 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 [];
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 = self.detective(src) }
try {
if (this.options.esm && isEsm(file, pkg)) {
var result = detectiveEsm(src, { sourceType: 'module' });
deps = result.strings;
imports = result.imports;
exports = result.exports;
}
else {
deps = self.detective(src, { sourceType: 'script' })
}
}
catch (ex) {
var message = ex && ex.message ? ex.message : ex;
throw new Error(
'Parsing file ' + file + ': ' + message
);
}
return deps;
return { deps: deps, imports: imports, exports: exports };
};

Deps.prototype.lookupPackage = function (file, cb) {
Expand Down Expand Up @@ -624,3 +671,11 @@ function wrapTransform (tr) {
tr.on('error', function (err) { wrapper.emit('error', err) });
return wrapper;
}

function isEsm (file, pkg) {
if (pkg && pkg.type === 'module') {
return !/\.cjs$/.test(file);
} else {
return /\.mjs$/.test(file);
}
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"concat-stream": "~1.6.0",
"defined": "^1.0.0",
"detective": "^5.0.2",
"detective-esm": "browserify/detective-esm",
"duplexer2": "^0.1.2",
"inherits": "^2.0.1",
"parents": "^1.0.0",
Expand Down
65 changes: 65 additions & 0 deletions test/esm_type.js
Original file line number Diff line number Diff line change
@@ -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 }
1 change: 1 addition & 0 deletions test/files/esm_type/cjs.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = 'commonjs'
1 change: 1 addition & 0 deletions test/files/esm_type/esm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 'esmodules'
4 changes: 4 additions & 0 deletions test/files/esm_type/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import esm from './esm.js'
import cjs from './cjs.cjs'

export { esm, cjs }
3 changes: 3 additions & 0 deletions test/files/esm_type/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"type": "module"
}