diff --git a/_prelude.js b/_prelude.js new file mode 100644 index 0000000..b37708e --- /dev/null +++ b/_prelude.js @@ -0,0 +1 @@ +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l={},c=n[o]={exports:l};function h(e){var n=t[o][1][e];return s(n?n:e)}t[o][2]?t[o][0].call(undefined,h,function(e){var t=Object.keys(e);for(var n=0;n 0) { + // Define getters for exports to support live bindings, and to prevent writes to them from outside this module + setup += '_esmExport({' + row.esm.exports.forEach(function (record, i) { + if (i > 0) setup += ',' + if (record.name === 'default' && !record.as && !record.export) { + setup += 'default:function(){return ' + esmDefaultName + '}' + } else { + setup += JSON.stringify(record.as) + ':function(){return ' + record.export + '}' + } + }); + setup += '});' + } + + var needInterop = false; + var baseImports = {}; + row.esm.imports.forEach(function (record, i) { + var binding = scope.getBinding(record.as); + if (!record.esm) { + if (record.import !== 'default') { + throw new Error('The requested module does not provide an export named \'' + record.import + '\'') + } + setup += 'var ' + record.as + ' = ' + '_esmRequire(' + JSON.stringify(record.from) + ');'; + return; + } + + var base = baseImports[record.from]; + if (!base) { + base = baseImports[record.from] = '_esmImport' + i; + setup += 'var ' + base + ' = _esmRequire(' + JSON.stringify(record.from) + ');'; + } + binding.references.forEach(function (ref, i) { + if (ref === binding.definition) return + patches.push({ + start: ref.start, + end: ref.end, + string: base + '.' + record.import + }); + }); + }); + } + + row.source = patch(row.source, patches) + row.source = setup + '\n' + row.source + cb(null, row); + }); +}; + +function patch (str, patches) { + patches = patches.slice().sort(function (a, b) { return a.start - b.start }) + + var offset = 0 + patches.forEach(function (r) { + var start = r.start + offset + var end = r.end + offset + str = str.slice(0, start) + r.string + str.slice(end) + + offset += r.start - r.end + r.string.length + }) + + return str +} diff --git a/index.js b/index.js index 3b96de8..449d5f6 100644 --- a/index.js +++ b/index.js @@ -7,6 +7,7 @@ var fs = require('fs'); var path = require('path'); var combineSourceMap = require('combine-source-map'); +var esm = require('./esm'); var defaultPreludePath = path.join(__dirname, '_prelude.js'); var defaultPrelude = fs.readFileSync(defaultPreludePath, 'utf8'); @@ -25,7 +26,7 @@ module.exports = function (opts) { function (buf, enc, next) { parser.write(buf); next() }, function () { parser.end() } ); - parser.pipe(through.obj(write, end)); + parser.pipe(esm()).pipe(through.obj(write, end)); stream.standaloneModule = opts.standaloneModule; stream.hasExports = opts.hasExports; @@ -70,7 +71,7 @@ module.exports = function (opts) { (first ? '' : ','), JSON.stringify(row.id), ':[', - 'function(require,module,exports){\n', + row.esm ? 'function(_esmRequire,_esmExport){\n' : 'function(require,module,exports){\n', combineSourceMap.removeComments(row.source), '\n},', '{' + Object.keys(row.deps || {}).sort().map(function (key) { @@ -78,6 +79,7 @@ module.exports = function (opts) { + JSON.stringify(row.deps[key]) ; }).join(',') + '}', + row.esm ? ',1' : '', ']' ].join(''); diff --git a/package.json b/package.json index 5bf686f..15dad9d 100644 --- a/package.json +++ b/package.json @@ -8,17 +8,21 @@ }, "dependencies": { "JSONStream": "^1.0.3", + "acorn": "^5.2.1", "combine-source-map": "~0.8.0", "defined": "^1.0.0", + "estree-assign-parent": "^1.0.0", + "scope-analyzer": "^1.1.1", "through2": "^2.0.0", "umd": "^3.0.0" }, "devDependencies": { - "tap": "^10.7.2", - "uglify-js": "1.3.5", "concat-stream": "~1.5.1", "convert-source-map": "~1.1.0", - "parse-base64vlq-mappings": "~0.1.1" + "dedent": "^0.7.0", + "parse-base64vlq-mappings": "~0.1.1", + "tap": "^10.7.2", + "uglify-js": "1.3.5" }, "scripts": { "test": "tap test/*.js", diff --git a/prelude.js b/prelude.js index d291c69..bf83991 100644 --- a/prelude.js +++ b/prelude.js @@ -29,11 +29,29 @@ err.code = 'MODULE_NOT_FOUND'; throw err; } - var m = cache[name] = {exports:{}}; - modules[name][0].call(m.exports, function(x){ + var e = {}, m = cache[name] = {exports:e}; + function subReq(x){ var id = modules[name][1][x]; return newRequire(id ? id : x); - },m,m.exports,outer,modules,cache,entry); + } + // If [2] is truthy this is an ES module. + // ES modules expose exports by calling a function with + // [name, getter (for live bindings)] pairs. + if(modules[name][2]) { + modules[name][0].call(undefined, subReq, function(obj) { + var exp = Object.keys(obj); + for (var i = 0; i < exp.length; i++) { + Object.defineProperty(e, exp[i], { + get: obj[exp[i]], + set: function () { throw new Error('Assignment to constant variable.') }, + enumerable: true + }); + } + }); + } + else { + modules[name][0].call(m.exports, subReq,m,m.exports,outer,modules,cache,entry); + } } return cache[name].exports; } diff --git a/test/esm.js b/test/esm.js new file mode 100644 index 0000000..a835d29 --- /dev/null +++ b/test/esm.js @@ -0,0 +1,89 @@ +var test = require('tap').test; +var dedent = require('dedent'); +var pack = require('../'); +var vm = require('vm'); + +test('esm', function (t) { + t.plan(6); + + var p = pack({ raw: true }); + var src = ''; + p.on('data', function (buf) { src += buf; }); + p.on('end', function () { + var expected = [ + ['type', 'function'], + ['value', 0], + ['value', 5] + ] + vm.runInNewContext(src, { + T: t, + console: { log: log } + }); + function log (a, b) { + var cur = expected.shift() + t.ok(a === cur[0] && b === cur[1]) + } + }); + + p.write({ + id: 'test', + entry: true, + esm: { imports: [], exports: [] }, + source: dedent` + T.equal(typeof require, 'undefined') + T.equal(typeof module, 'undefined') + T.equal(typeof exports, 'undefined') + ` + }); + + p.write({ + id: 'abc', + entry: true, + esm: { + imports: [ + { from: 'x', import: 'default', as: 'x', esm: true }, + { from: 'y', import: 'default', as: 'y' }, + { from: 'z', import: 'c', as: 'renamed', esm: true }, + { from: 'z', import: 'x', as: 'i', esm: true } + ], + exports: [] + }, + source: dedent` + import x from "x"; + import y from "y"; + import { c as renamed, x as i } from "z"; + y.b(x); + x(renamed); + i() + x(renamed); + ` + }) + // Test importing a CommonJS module + p.write({ + id: 'y', + source: 'exports.a = "a"; exports.b = function b () {console.log("type",typeof arguments[0])}' + }) + // Test live bindings + p.write({ + id: 'z', + esm: { + imports: [], + exports: [ + { export: 'c', as: 'c' }, + { export: 'x', as: 'x' } + ] + }, + source: 'export var c = 0; export var x = function(){ c += 5 }' + }) + p.end({ + id: 'x', + esm: { + imports: [], + exports: [ + { export: 'c', as: 'default' } + ] + }, + source: 'export default c; function c(arg){ console.log("value", arg) }' + }); + +});