diff --git a/.gitignore b/.gitignore index dbb591d..90ab9d3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ /.idea /node_modules -/npm-debug.log +npm-debug.log diff --git a/README.md b/README.md index eb579c9..6458083 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ cjs.transform(options).then(function (result) { * **map**: `String|Function(input, output)|Boolean` — source map file; optional, doesn't generate source map by default; if `true` is provided, path default to `function (input, output) { return output + '.map' }`. * **exports**: `String|Function(input, output)` — Exports top module with [UMD](https://github.com/umdjs/umd) with given global object name; optional, doesn't wrap into UMD by default. * **transform**: `Array|Function(input)` — Array of or single function that returns transformation [through](https://github.com/dominictarr/through)-stream(s) to be used against input files before their usage; optional. -* **dryRun**: `Boolean` — if set to `true`, doesn't write output to disk. +* **dryRun**: `Boolean` — if set to `true`, doesn't write output to disk and doesn't append `//# sourceMappingURL=...` to code so you can handle it differently on your own. ### Result object diff --git a/lib/astConsts.js b/lib/astConsts.js index 8b3ce9c..f77e0e9 100644 --- a/lib/astConsts.js +++ b/lib/astConsts.js @@ -1,11 +1,11 @@ -var recast = require('recast'), - fs = require('fs'), - b = recast.types.builders; +var astUtils = require('./astUtils'), + b = astUtils.builders, + fs = require('fs'); exports.moduleArgs = [b.identifier('module'), b.identifier('exports')]; exports.require = b.identifier('_require'); exports.factoryArgs = [b.identifier('define')]; exports.localReqModules = b.memberExpression(exports.require, b.identifier('modules'), false); -exports.preamble = recast.parse(fs.readFileSync(__dirname + '/templates/preamble.js'), {sourceFileName: 'preamble.js'}).program.body; -exports.umdWrapper = recast.parse(fs.readFileSync(__dirname + '/templates/umdWrapper.js'), {sourceFileName: 'umdWrapper.js'}).program.body[0].expression; \ No newline at end of file +exports.preamble = astUtils.parse(fs.readFileSync(__dirname + '/templates/preamble.js'), {loc: true, source: 'preamble.js'}).body; +exports.umdWrapper = astUtils.parse(fs.readFileSync(__dirname + '/templates/umdWrapper.js'), {loc: true, source: 'umdWrapper.js'}).body[0].expression; \ No newline at end of file diff --git a/lib/astUtils.js b/lib/astUtils.js new file mode 100644 index 0000000..973a1a0 --- /dev/null +++ b/lib/astUtils.js @@ -0,0 +1,35 @@ +var parse = require('esprima').parse, + generate = require('escodegen').generate, + types = require('ast-types'), + traverse = types.traverse.fast; + +// Polyfill until https://github.com/ariya/esprima/pull/148/files is merged into npm version + +exports.parse = function (code, options) { + var ast = parse.apply(this, arguments); + + if (options.source) { + traverse(ast, function (node) { + node.loc.source = options; + }); + } + + return ast; +}; + +// Workaround until https://github.com/Constellation/escodegen/issues/174 is fixed + +exports.generate = function (ast, options) { + var output = generate(ast, options); + + if (options.sourceMapWithCode && typeof output === 'string') { + output = {code: output}; + } + + return output; +}; + +exports.traverse = traverse; + +exports.builders = types.builders; +exports.namedTypes = types.namedTypes; \ No newline at end of file diff --git a/lib/optionsParser.js b/lib/parseOptions.js similarity index 100% rename from lib/optionsParser.js rename to lib/parseOptions.js diff --git a/lib/promise.js b/lib/promise.js new file mode 100644 index 0000000..0bc5949 --- /dev/null +++ b/lib/promise.js @@ -0,0 +1 @@ +module.exports = require('davy'); \ No newline at end of file diff --git a/lib/pureCjs.js b/lib/pureCjs.js index 833396e..9d98ce9 100644 --- a/lib/pureCjs.js +++ b/lib/pureCjs.js @@ -1,15 +1,15 @@ var fs = require('fs'), - recast = require('recast'), - Promise = require('davy'), + astUtils = require('./astUtils'), + b = astUtils.builders, + Promise = require('./promise'), astConsts = require('./astConsts'), - optionsParser = require('./optionsParser'), + parseOptions = require('./parseOptions'), pathUtils = require('./pathUtils'), ReplacerMap = require('./replacerMap'), - b = recast.types.builders, whenWriteFile = Promise.wrap(fs.writeFile); exports.transform = function (inOptions) { - var options = optionsParser(inOptions), + var options = parseOptions(inOptions), map = new ReplacerMap(options), replacer = map.get(options.input); @@ -34,14 +34,14 @@ exports.transform = function (inOptions) { ? b.callExpression(astConsts.umdWrapper, [b.literal(options.exports), factoryExpr]) : b.callExpression(factoryExpr, []) ), - _result = recast.print(b.program([b.expressionStatement(expr)]), {sourceMapName: options.map}), - result = { - code: _result.code, - map: _result.map, - options: options - }, + result = astUtils.generate(b.program([b.expressionStatement(expr)]), { + sourceMap: options.map, + sourceMapWithCode: true + }), whenOut; + result.options = options; + if (!options.dryRun) { if (options.map) { result.code += '\n//# sourceMappingURL=' + pathUtils.relative(pathUtils.std.dirname(options.output), options.map); diff --git a/lib/replacer.js b/lib/replacer.js index 73ee89e..27d4fca 100644 --- a/lib/replacer.js +++ b/lib/replacer.js @@ -1,54 +1,51 @@ -var recast = require('recast'), - fs = require('fs'), - Promise = require('davy'), +var fs = require('fs'), + Promise = require('./promise'), es = require('event-stream'), - astConsts = require('./astConsts'), pathUtils = require('./pathUtils'), isCoreModule = require('resolve').isCore, - - types = recast.types, - b = types.builders, - n = types.namedTypes; - -module.exports = recast.Visitor.extend({ - init: function (options) { - this.id = options.id; - this.map = options.map; - this.path = options.path; - - var pipeline = [fs.createReadStream(this.path, {encoding: 'utf-8'})]; - - this.map.transform.forEach(function (transform) { - pipeline.push(transform(this.path)); - }, this); - - var promise = new Promise(); - pipeline.push(es.wait(function (err, js) { - err ? promise.reject(err) : promise.fulfill(js); - })); - - this.promise = promise.then(function (js) { - var ast = recast.parse(js, {sourceFileName: this.path}).program; - this.visit(ast); - return b.functionExpression(null, astConsts.moduleArgs, b.blockStatement(ast.body)); - }.bind(this)); - - es.pipeline.apply(es, pipeline); - }, - - getDependency: function (path) { - return this.map.get(pathUtils.getNodePath(this.path, path)); - }, - - visitCallExpression: function (node) { - var func = node.callee, - arg = node.arguments[0]; - - if (n.Identifier.check(func) && func.name === 'require' && n.Literal.check(arg) && !isCoreModule(arg.value)) { - func.name = astConsts.require.name; - arg.value = this.getDependency(arg.value).id; + astUtils = require('./astUtils'), + astConsts = require('./astConsts'), + b = astUtils.builders, + n = astUtils.namedTypes; + +function Replacer(options) { + this.id = options.id; + this.map = options.map; + this.path = options.path; + + var pipeline = this.map.transform.reduce(function (stream, transform) { + return stream.pipe(transform(this.path)); + }, fs.createReadStream(this.path, {encoding: 'utf-8'})); + + var promise = new Promise(); + + pipeline.pipe(es.wait(function (err, js) { + err ? promise.reject(err) : promise.fulfill(js); + })); + + this.promise = promise.then(function (js) { + var ast = astUtils.parse(js, {loc: true, source: this.path}); + this.visit(ast); + return b.functionExpression(null, astConsts.moduleArgs, b.blockStatement(ast.body)); + }.bind(this)); +} + +Replacer.prototype.getDependency = function (path) { + return this.map.get(pathUtils.getNodePath(this.path, path)); +}; + +Replacer.prototype.visit = function (ast) { + return astUtils.traverse(ast, function (node) { + if (n.CallExpression.check(node)) { + var func = node.callee, + arg = node.arguments[0]; + + if (n.Identifier.check(func) && func.name === 'require' && n.Literal.check(arg) && !isCoreModule(arg.value)) { + func.name = astConsts.require.name; + arg.value = this.getDependency(arg.value).id; + } } + }.bind(this)); +}; - this.genericVisit(node); - } -}); \ No newline at end of file +module.exports = Replacer; \ No newline at end of file diff --git a/lib/replacerMap.js b/lib/replacerMap.js index eaa2618..4999cd6 100644 --- a/lib/replacerMap.js +++ b/lib/replacerMap.js @@ -1,4 +1,4 @@ -var Promise = require('davy'), +var Promise = require('./promise'), Replacer = require('./replacer'); function ReplacerMap(options) { diff --git a/package.json b/package.json index 8a0f273..8c783b1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pure-cjs", - "version": "1.8.6", + "version": "1.9.0", "description": "Pure minimalistic CommonJS builder", "bin": "./bin/pure-cjs", "main": "./lib/pureCjs", @@ -20,8 +20,10 @@ "commander": "~2.1.0", "resolve": "~0.6.1", "davy": "0.0.8", - "recast": "~0.5.7", - "event-stream": "~3.1.0" + "event-stream": "~3.1.0", + "escodegen": "~1.2.0", + "ast-types": "~0.3.18", + "esprima": "~1.0.4" }, "devDependencies": { "nodeunit": "~0.8.6" diff --git a/test/suites/a (exports A with map)/expected.js b/test/suites/a (exports A with map)/expected.js new file mode 100644 index 0000000..737a1b4 --- /dev/null +++ b/test/suites/a (exports A with map)/expected.js @@ -0,0 +1,38 @@ +(function (name, factory) { + if (typeof define === 'function' && define.amd) { + define([], factory); + } else if (typeof exports === 'object') { + module.exports = factory(); + } else { + this[name] = factory(); + } +}('A', function (define) { + function _require(index) { + var module = _require.cache[index]; + if (!module) { + var exports = {}; + module = _require.cache[index] = { + id: index, + exports: exports + }; + _require.modules[index].call(exports, module, exports); + } + return module.exports; + } + _require.cache = []; + _require.modules = [ + function (module, exports) { + var c = _require(1), url = require('url'); + this.topValue = _require(2) * 2; + }, + function (module, exports) { + var a = _require(0); + exports.value = 3; + }, + function (module, exports) { + module.exports = _require(1).value * 7; + } + ]; + return _require(0); +})); +//# sourceMappingURL=expected.js.map \ No newline at end of file diff --git a/test/suites/a (exports A with map)/expected.js.map b/test/suites/a (exports A with map)/expected.js.map new file mode 100644 index 0000000..663dabf --- /dev/null +++ b/test/suites/a (exports A with map)/expected.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["suites/a (exports A with map)/expected.js.map"],"names":["name","factory","define","amd","exports","module","_require","index","cache","id","modules","call","c","url","require","topValue","a","value"],"mappings":"CAAC,UAAUA,IAAV,EAAgBC,OAAhB,EAAyB;AAAA,IACtB,IAAI,OAAOC,MAAP,KAAkB,UAAlB,IAAgCA,MAAA,CAAOC,GAA3C,EAAgD;AAAA,QAE5CD,MAAA,CAAO,EAAP,EAAWD,OAAX,EAF4C;AAAA,KAAhD,MAGO,IAAI,OAAOG,OAAP,KAAmB,QAAvB,EAAiC;AAAA,QAIpCC,MAAA,CAAOD,OAAP,GAAiBH,OAAA,EAAjB,CAJoC;AAAA,KAAjC,MAKA;AAAA,QAEH,KAAKD,IAAL,IAAaC,OAAA,EAAb,CAFG;AAAA,KATe;AAAA,C;IAA1B,SAASK,QAAT,CAAkBC,KAAlB,EAAyB;AAAA,QACxB,IAAIF,MAAA,GAASC,QAAA,CAASE,KAAT,CAAeD,KAAf,CAAb,CADwB;AAAA,QAGxB,IAAI,CAACF,MAAL,EAAa;AAAA,YACZ,IAAID,OAAA,GAAU,EAAd,CADY;AAAA,YAEZC,MAAA,GAASC,QAAA,CAASE,KAAT,CAAeD,KAAf,IAAwB;AAAA,gBAACE,EAAA,EAAIF,KAAL;AAAA,gBAAYH,OAAA,EAASA,OAArB;AAAA,aAAjC,CAFY;AAAA,YAGZE,QAAA,CAASI,OAAT,CAAiBH,KAAjB,EAAwBI,IAAxB,CAA6BP,OAA7B,EAAsCC,MAAtC,EAA8CD,OAA9C,EAHY;AAAA,SAHW;AAAA,QASxB,OAAOC,MAAA,CAAOD,OAAd,CATwB;AAAA,K;IAYzBE,QAAA,CAASE,KAAT,GAAiB,EAAjB,C;;;YAZA,IAAII,CAAA,GAAIN,QAAA,CAAQ,CAAR,CAAR,EACCO,GAAA,GAAMC,OAAA,CAAQ,KAAR,CADP,C;YAGA,KAAKC,QAAL,GAAgBT,QAAA,CAAQ,CAAR,IAAwB,CAAxC,C;;;YAHA,IAAIU,CAAA,GAAIV,QAAA,CAAQ,CAAR,CAAR,C;YACAF,OAAA,CAAQa,KAAR,GAAgB,CAAhB,C;;;YADAZ,MAAA,CAAOD,OAAP,GAAiBE,QAAA,CAAQ,CAAR,EAAgBW,KAAhB,GAAwB,CAAzC,C"} \ No newline at end of file diff --git a/test/suites/a (exports A with map)/options.js b/test/suites/a (exports A with map)/options.js new file mode 100644 index 0000000..b8a4751 --- /dev/null +++ b/test/suites/a (exports A with map)/options.js @@ -0,0 +1,5 @@ +module.exports = { + input: 'fixtures/a.js', + exports: 'A', + map: true +}; \ No newline at end of file diff --git a/test/suites/a (exports A)/expected.js b/test/suites/a (exports A)/expected.js index 0e6824d..e4c992f 100644 --- a/test/suites/a (exports A)/expected.js +++ b/test/suites/a (exports A)/expected.js @@ -1,42 +1,37 @@ (function (name, factory) { if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module. define([], factory); } else if (typeof exports === 'object') { - // Node. Does not work with strict CommonJS, but - // only CommonJS-like enviroments that support module.exports, - // like Node. module.exports = factory(); } else { - // Browser globals (root is window) this[name] = factory(); - } -})("A", function(define) { + } +}('A', function (define) { function _require(index) { var module = _require.cache[index]; - if (!module) { var exports = {}; - module = _require.cache[index] = {id: index, exports: exports}; + module = _require.cache[index] = { + id: index, + exports: exports + }; _require.modules[index].call(exports, module, exports); } - return module.exports; } - _require.cache = []; - - _require.modules = [function(module, exports) { - var c = _require(1), - url = require('url'); - - this.topValue = _require(2) * 2; - }, function(module, exports) { - var a = _require(0); - exports.value = 3; - }, function(module, exports) { - module.exports = _require(1).value * 7; - }]; - + _require.modules = [ + function (module, exports) { + var c = _require(1), url = require('url'); + this.topValue = _require(2) * 2; + }, + function (module, exports) { + var a = _require(0); + exports.value = 3; + }, + function (module, exports) { + module.exports = _require(1).value * 7; + } + ]; return _require(0); -}); \ No newline at end of file +})); \ No newline at end of file diff --git a/test/suites/c (no exports)/expected.js b/test/suites/c (no exports)/expected.js index 0ac96e9..e4cfa8e 100644 --- a/test/suites/c (no exports)/expected.js +++ b/test/suites/c (no exports)/expected.js @@ -1,29 +1,29 @@ -(function(define) { +(function (define) { function _require(index) { var module = _require.cache[index]; - if (!module) { var exports = {}; - module = _require.cache[index] = {id: index, exports: exports}; + module = _require.cache[index] = { + id: index, + exports: exports + }; _require.modules[index].call(exports, module, exports); } - return module.exports; } - _require.cache = []; - - _require.modules = [function(module, exports) { - var a = _require(1); - exports.value = 3; - }, function(module, exports) { - var c = _require(0), - url = require('url'); - - this.topValue = _require(2) * 2; - }, function(module, exports) { - module.exports = _require(0).value * 7; - }]; - + _require.modules = [ + function (module, exports) { + var a = _require(1); + exports.value = 3; + }, + function (module, exports) { + var c = _require(0), url = require('url'); + this.topValue = _require(2) * 2; + }, + function (module, exports) { + module.exports = _require(0).value * 7; + } + ]; _require(0); -})(); \ No newline at end of file +}()); \ No newline at end of file diff --git a/test/test.js b/test/test.js index 1e032c1..440f4f7 100644 --- a/test/test.js +++ b/test/test.js @@ -1,6 +1,6 @@ var fs = require('fs'), cjs = require('..'), - Promise = require('davy'), + Promise = require('../lib/promise'), whenReadFile = Promise.wrap(fs.readFile), suitesPath = 'suites/'; @@ -13,18 +13,22 @@ fs.readdirSync(__dirname + '/' + suitesPath).forEach(function (suiteName) { var options = require('./' + suitePath + 'options'); options.output = suitePath + 'expected.js'; options.dryRun = true; + + console.time('Execution time'); cjs.transform(options).then(function (output) { + console.timeEnd('Execution time'); + var promises = [ whenReadFile(output.options.output, 'utf-8').then(function (contents) { - test.equal(output.code, contents); + test.equal(output.code, contents.replace(/\s*\/\/#\s+sourceMappingURL=.*$/, '')); }) ]; if (output.options.map) { promises.push( whenReadFile(output.options.map, 'utf-8').then(function (contents) { - test.deepEqual(output.map, JSON.parse(contents)); + test.equal(output.map.toString(), contents); }) ); } else {