From b83e9c2263859ffcff4a2618dfc82c6b05a48175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20O=E2=80=99Shannessy?= Date: Fri, 27 Mar 2015 16:15:53 -0700 Subject: [PATCH] Add the new API and CLI This mostly behaves the same way react-tools did. One big difference is that the transform function exported does not have a simple string return value. This is more like transformWithDetails. This is inline with how babel does it, and I like it. We already have the information, we might as well expose it and leave it to the consumer to get what it wants off. --- bin/jstransform | 3 + package.json | 2 + simple.js | 2 + src/cli.js | 56 ++++++++++++ src/inline-source-map.js | 27 ++++++ src/simple.js | 185 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 275 insertions(+) create mode 100755 bin/jstransform create mode 100644 simple.js create mode 100644 src/cli.js create mode 100644 src/inline-source-map.js create mode 100644 src/simple.js diff --git a/bin/jstransform b/bin/jstransform new file mode 100755 index 0000000..e24f7b1 --- /dev/null +++ b/bin/jstransform @@ -0,0 +1,3 @@ +#!/usr/bin/env node + +require('../src/cli'); diff --git a/package.json b/package.json index 6bc1b7d..427a090 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "email": "jeffmo@fb.com" } ], + "bin": "bin/jstransform", "main": "src/jstransform", "repository": { "type": "git", @@ -21,6 +22,7 @@ ], "dependencies": { "base62": "0.1.1", + "commoner": "^0.10.1", "esprima-fb": "^14001.1.0-dev-harmony-fb", "source-map": "0.1.31" }, diff --git a/simple.js b/simple.js new file mode 100644 index 0000000..a5372ef --- /dev/null +++ b/simple.js @@ -0,0 +1,2 @@ +// Just re-export the simple API. This will eventually go away. require('jstransform') will give the simple API and the current API will be moved to something else (perhaps 'jstranform/advanced') +module.exports = require('./src/simple'); diff --git a/src/cli.js b/src/cli.js new file mode 100644 index 0000000..5ab6d85 --- /dev/null +++ b/src/cli.js @@ -0,0 +1,56 @@ +var transform = require('./simple').transform; + +require('commoner').version( + require('../package.json').version +).resolve(function(id) { + return this.readModuleP(id); +}).option( + '--react', + 'Turns on the React JSX and React displayName transforms' +).option( + '--harmony', + 'Turns on JS transformations such as ES6 Classes etc.' +).option( + '--target [version]', + 'Specify your target version of ECMAScript. Valid values are "es3" and ' + + '"es5". The default is "es5". "es3" will avoid uses of defineProperty and ' + + 'will quote reserved words. WARNING: "es5" is not properly supported, even ' + + 'with the use of es5shim, es5sham. If you need to support IE8, use "es3".', + 'es5' +).option( + '--strip-types', + 'Strips out type annotations.' +).option( + '--es6module', + 'Parses the file as a valid ES6 module. ' + + '(Note that this means implicit strict mode)' +).option( + '--non-strict-es6module', + 'Parses the file as an ES6 module, except disables implicit strict-mode. ' + + '(This is useful if you\'re porting non-ES6 modules to ES6, but haven\'t ' + + 'yet verified that they are strict-mode safe yet)' +).option( + '--source-map-inline', + 'Embed inline sourcemap in transformed source' +).process(function(id, source) { + // This is where JSX, ES6, etc. desugaring happens. + // We don't do any pre-processing of options so that the command line and the + // JS API both expose the same set of options. We do extract the options that + // we care about from commoner though so we aren't passing too many things + // along. + // var options = { + // harmony: this.options.harmony, + // sourceMap: this.options.sourceMapInline, + // stripTypes: this.options.stripTypes, + // es6module: this.options.es6module, + // nonStrictEs6module: this.options.nonStrictEs6module, + // target: this.options.target + // }; + // return transform(source, options).code; + // console.log(source, this.options); + // return ''; + var result = transform(source, this.options); + + return this.options.sourceMapInline ? result.sourceMapInline : result.code; +}); + diff --git a/src/inline-source-map.js b/src/inline-source-map.js new file mode 100644 index 0000000..7d0219e --- /dev/null +++ b/src/inline-source-map.js @@ -0,0 +1,27 @@ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +'use strict'; +/*eslint-disable no-undef*/ +var Buffer = require('buffer').Buffer; + +function inlineSourceMap(sourceMap, sourceCode, sourceFilename) { + // This can be used with a sourcemap that has already has toJSON called on it. + // Check first. + var json = sourceMap; + if (typeof sourceMap.toJSON === 'function') { + json = sourceMap.toJSON(); + } + json.sources = [sourceFilename]; + json.sourcesContent = [sourceCode]; + var base64 = Buffer(JSON.stringify(json)).toString('base64'); + return '//# sourceMappingURL=data:application/json;base64,' + base64; +} + +module.exports = inlineSourceMap; diff --git a/src/simple.js b/src/simple.js new file mode 100644 index 0000000..b765c75 --- /dev/null +++ b/src/simple.js @@ -0,0 +1,185 @@ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +'use strict'; +/*eslint-disable no-undef*/ +var visitors = require('../visitors'); +var jstransform = require('./jstransform'); +var typesSyntax = require('../visitors/type-syntax'); +var inlineSourceMap = require('./inline-source-map'); + +var fs = require('fs'); + +/** + * Transforms the given code with the given options. + * + * @param {string} code + * @param {object} options + * @return {object} + */ +function transform(code, options) { + // Process options + var transformOptions = {}; + + // transformOptions.harmony = options.harmony; + // transformOptions.stripTypes = options.stripTypes; + // transformOptions.sourceMap = options.sourceMap; + transformOptions.filename = options.sourceFilename; + + if (options.es6module) { + transformOptions.sourceType = 'module'; + } + if (options.nonStrictEs6module) { + transformOptions.sourceType = 'nonStrictModule'; + } + + // Instead of doing any fancy validation, only look for 'es3'. If we have + // that, then use it. Otherwise use 'es5'. + transformOptions.es3 = options.target === 'es3'; + transformOptions.es5 = !transformOptions.es3; + + // Determine visitors to use + var visitorSets = []; + + if (options.react) { + visitorSets.push('react'); + } + + if (options.harmony) { + visitorSets.push('harmony'); + } + + if (options.es6) { + visitorSets.push('es6'); + } + + if (options.es7) { + visitorSets.push('es7'); + } + + if (transformOptions.es3) { + visitorSets.push('target:es3'); + } + + + if (options.stripTypes) { + // Stripping types needs to happen before the other transforms + // unfortunately, due to bad interactions. For example, + // es6-rest-param-visitors conflict with stripping rest param type + // annotation + code = jstransform.transform( + typesSyntax.visitorList, + code, + transformOptions + ).code; + } + + var visitorList = visitors.getVisitorsBySet(visitorSets); + // var result = 'code'; + // console.log(code, visitorList); + var result = jstransform.transform(visitorList, code, transformOptions); + // + // TODO: Handle sourcemap processing + if (options.sourceMapInline) { + result.inlineSourceMap = inlineSourceMap( + result.sourceMap, + code, + options.fileName + ); + } + + return result; +} + +function transformFile(file, options, callback) { + if (typeof options === 'function') { + callback = options; + options = {}; + } + + fs.readFile(file, 'utf-8', function(err, contents) { + if (err) { + return callback(err, null); + } + + var result = transform(contents, options); + callback(result); + }); +} + +function transformFileSync(file, options) { + var contents = fs.readFileSync(file, 'utf-8'); + return transform(contents, options); +} + +module.exports = { + transform: transform, + transformFile: transformFile, + transformFileSync: transformFileSync +}; + +// module.exports = { +// transform: function(input, options) { +// options = processOptions(options); +// var output = innerTransform(input, options); +// var result = output.code; +// if (options.sourceMap) { +// var map = inlineSourceMap( +// output.sourceMap, +// input, +// options.filename +// ); +// result += '\n' + map; +// } +// return result; +// }, +// transformWithDetails: function(input, options) { +// options = processOptions(options); +// var output = innerTransform(input, options); +// var result = {}; +// result.code = output.code; +// if (options.sourceMap) { +// result.sourceMap = output.sourceMap.toJSON(); +// } +// if (options.filename) { +// result.sourceMap.sources = [options.filename]; +// } +// return result; +// } +// }; +// +// /** +// * Only copy the values that we need. We'll do some preprocessing to account for +// * converting command line flags to options that jstransform can actually use. +// */ +// function processOptions(opts) { +// +// } +// +// function innerTransform(input, options) { +// var visitorSets = ['react']; +// if (options.harmony) { +// visitorSets.push('harmony'); +// } +// +// if (options.es3) { +// visitorSets.push('es3'); +// } +// +// if (options.stripTypes) { +// // Stripping types needs to happen before the other transforms +// // unfortunately, due to bad interactions. For example, +// // es6-rest-param-visitors conflict with stripping rest param type +// // annotation +// input = transform(typesSyntax.visitorList, input, options).code; +// } +// +// var visitorList = visitors.getVisitorsBySet(visitorSets); +// return transform(visitorList, input, options); +// }