Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

initial commit

  • Loading branch information...
commit 2be1a2e16da2eecdc50a1524c1a8fcf293d14219 0 parents
@millermedeiros authored
2  .gitignore
@@ -0,0 +1,2 @@
+node_modules/
+
63 README.md
@@ -0,0 +1,63 @@
+# nodefy
+
+convert AMD modules into a node.js compatible format.
+
+## How?
+
+this tool uses [Esprima](http://esprima.org/) to parse the code and replace
+`define()` calls, doing the less amount of changes as possible to the code.
+
+### Input
+
+```js
+define(['foo', '../bar/baz'], function(foo, baz){
+
+ var lorem = 'ipsum';
+
+ return {
+ log : function(){
+ console.log(lorem);
+ }
+ };
+
+});
+```
+
+### Output
+
+```js
+ var foo = require('foo');
+ var baz = require('../bar/baz');
+
+ var lorem = 'ipsum';
+
+ module.exports = {
+ log : function(){
+ console.log(lorem);
+ }
+ };
+```
+
+
+## Inspiration / Why?
+
+I couldn't find any tool that did what I wanted - convert AMD modules into
+plain node.js - so I decided to code my own. There are alternatives but they
+all add more complexity than I would like.
+
+This project was created mainly because of the
+[amd-utils](http://millermedeiros.github.com/amd-utils/) project, since many
+methods are useful on both environments.
+
+The name was inpired by
+[browserify](https://github.com/substack/node-browserify).
+
+
+## Alternatives
+
+ - [amdefine](https://github.com/jrburke/amdefine)
+ - [UMD](https://github.com/umdjs/umd)
+ - [r.js](https://github.com/jrburke/r.js)
+ - [uRequire](https://github.com/anodynos/uRequire)
+
+
17 bin/nodefy
@@ -0,0 +1,17 @@
+#!/usr/bin/env node
+
+var nodefy = require('../index');
+var cli = require('commander');
+
+cli
+ .description('convert AMD modules into node.js compatible modules.')
+ .usage('[options] [globs...]')
+ .option('-o,--output <folder>', 'Output folder. If omitted it will output to stdout.')
+
+cli.parse(process.argv);
+
+if (cli.args) {
+ var globs = cli.args.join();
+ // nodefy
+}
+
29 index.js
@@ -0,0 +1,29 @@
+
+
+var _parser = require('./src/parser');
+var _converter = require('./src/converter');
+
+
+/**
+ * Convert AMD-style JavaScript string into node.js compatible module
+ * @param String raw
+ * @return String
+ */
+exports.parse = _parser.parse;
+
+/**
+ * Read file content and output into destination path.
+ * @param String inputPath
+ * @param String outputPath
+ * @param Function callback
+ */
+exports.convert = _converter.convert;
+
+/**
+ * Read glob content and output files into output folder
+ * @param Glob inputGlob Glob that matches files that should be converted
+ * @param String outputFolder
+ * @param Function callback
+ */
+exports.batchConvert = _converter.batchConvert;
+
34 package.json
@@ -0,0 +1,34 @@
+{
+ "name": "nodefy",
+ "version": "0.1.0",
+ "description": "convert AMD modules into a node.js compatible format",
+ "main": "index.js",
+ "directories": {
+ "test": "test"
+ },
+ "scripts": {
+ "test": "./node_modules/.bin/jasmine-node test"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/millermedeiros/nodefy.git"
+ },
+ "keywords": [ "amd", "cjs", "modules", "convert" ],
+ "author": {
+ "name" : "Miller Medeiros",
+ "url" : "http://blog.millermedeiros.com/"
+ },
+ "dependencies" : {
+ "esprima" : ">= 1.0 <1.2",
+ "glob" : "~3.1.14",
+ "async" : "~0.1.22",
+ "commander" : "~1.1.0"
+ },
+ "devDependencies" : {
+ "jasmine-node" : "~1.0.26"
+ },
+ "engine" : {
+ "node" : ">= 0.8"
+ },
+ "license": "MIT"
+}
58 src/converter.js
@@ -0,0 +1,58 @@
+var _fs = require('fs');
+var _path = require('path');
+
+var _glob = require('glob');
+var _async = require('async');
+
+var _parser = require('./parser');
+
+
+
+/**
+ * Read file content and output into destination path.
+ */
+exports.convert = function(inputPath, outputPath, callback){
+ _fs.readFile(inputPath, function(err, content){
+ if (err) {
+ callback(err);
+ return;
+ }
+ content = _parser.parse( content.toString() );
+ safeCreateDir(outputPath, function(err){
+ if (err) {
+ callback(err);
+ return;
+ }
+ _fs.writeFile(outputPath, content, callback);
+ });
+ });
+};
+
+
+function safeCreateDir(filePath, callback){
+ var dir = _path.dirname(filePath);
+ if (! _fs.existsSync(dir) ) {
+ _fs.mkdir(dir, callback);
+ } else {
+ callback(null);
+ }
+}
+
+
+/**
+ * Read folder content and output files into output folder
+ */
+exports.batchConvert = function(inputGlob, outputFolder, callback){
+ _glob( inputGlob, function(err, files){
+ if (err) {
+ callback(err);
+ return;
+ }
+ // convert all files in parallel
+ _async.forEach(files, function(filePath, cb){
+ var outputPath = _path.join(outputFolder, _path.basename(filePath));
+ exports.convert(filePath, outputPath, cb);
+ }, callback);
+ });
+};
+
128 src/parser.js
@@ -0,0 +1,128 @@
+
+var esprima = require('esprima');
+
+
+var MAGIC_DEPS = {
+ 'exports' : true,
+ 'module' : true,
+ 'require' : true
+};
+
+
+
+
+/**
+ * Convert AMD-style JavaScript string into node.js compatible module
+ * @param String raw
+ * @return String
+ */
+exports.parse = function(raw){
+ var output = '';
+ var ast = esprima.parse(raw, {
+ range: true,
+ raw: true
+ });
+
+ var defines = ast.body.filter(isDefine);
+
+ if ( defines.length > 1 ){
+ throw new Error('Each file can have only a single define call. Found "'+ defines.length +'"');
+ }
+
+ var def = defines[0];
+ var args = def.expression['arguments'];
+ var factory = getFactory( args );
+
+ // do replacements in-place to avoid modifying the code more than needed
+ output += raw.substring( 0, def.range[0] ); // anything before define
+ output += getRequires(args, factory); // add requires
+ output += getBody(raw, factory.body); // module body
+ output += raw.substring( def.range[1], raw.length ); // anything after define
+
+ return output;
+};
+
+
+function getRequires(args, factory){
+ var deps = getDependenciesNames( args );
+
+ var params = getParams( factory ).map(function(param, i){
+ return {
+ name : param,
+ dep : deps[i]
+ };
+ });
+
+ // only do it for dependencies that have a matching param
+ // also skip "magic" dependencies used on simplified CJS
+ params = params.filter(function(param){
+ return param.dep && !MAGIC_DEPS[param.dep];
+ });
+
+ var requires = params.map(function(param){
+ return 'var '+ param.name +' = require(\''+ param.dep +'\');';
+ });
+
+ return requires.join('\n');
+}
+
+
+function getDependenciesNames(args){
+ var arr = args.filter(function(arg){
+ return arg.type === 'ArrayExpression';
+ });
+
+ var deps = [];
+
+ if (arr.length) {
+ deps = arr[0].elements.map(function(el){
+ return el.value;
+ });
+ }
+
+ return deps;
+}
+
+
+function getParams(factory){
+ return factory.params.map(function(param){
+ return param.name;
+ });
+}
+
+
+function isDefine(node){
+ return node.type === 'ExpressionStatement' &&
+ node.expression.type === 'CallExpression' &&
+ node.expression.callee.type === 'Identifier' &&
+ node.expression.callee.name === 'define';
+}
+
+
+function getFactory(args){
+ return args.filter(function(arg){
+ return arg.type === 'FunctionExpression';
+ })[0];
+}
+
+
+function getBody(raw, factoryBody){
+ var returnStatement = factoryBody.body.filter(function(node){
+ return node.type === 'ReturnStatement';
+ })[0];
+
+ var body = '';
+
+ if (returnStatement) {
+ body += raw.substring( factoryBody.range[0] + 1, returnStatement.range[0] );
+ // "return ".length === 7 so we add "6" to returnStatement start
+ body += 'module.exports ='+ raw.substring( returnStatement.range[0] + 6, factoryBody.range[1] - 1 );
+ } else {
+ // if using exports or module.exports or just a private module we
+ // simply return the factoryBody content
+ body = raw.substring( factoryBody.range[0] + 1, factoryBody.range[1] - 1 );
+ }
+
+ return body;
+}
+
73 test/convert.spec.js
@@ -0,0 +1,73 @@
+
+var nodefy = require('../index');
+
+var _fs = require('fs');
+var _path = require('path');
+
+var _helper = require('./helpers');
+var readFile = _helper.readFile;
+var readOut = _helper.readOut;
+var purgeFolder = _helper.purge;
+
+
+// ---
+
+var TEMP_DIR = _path.join(__dirname, '_tmp');
+var BATCH_DIR = _path.join(TEMP_DIR, 'batch');
+
+// ---
+
+
+describe('convert', function () {
+
+ beforeEach(function(){
+ _fs.mkdirSync(TEMP_DIR);
+ });
+
+ afterEach(function(){
+ purgeFolder(TEMP_DIR);
+ });
+
+ it('should read file from fs and output to a new file', function (done) {
+ var inPath = _path.join(__dirname, 'files/basic-in.js');
+ var outPath = _path.join(__dirname, '_tmp/basic-out.js');
+ expect( _fs.existsSync(outPath) ).toBe( false );
+ nodefy.convert(inPath, outPath, function(err){
+ expect(err).toBe(null);
+ expect( readFile(outPath) ).toEqual( readOut('basic') );
+ done();
+ });
+ });
+
+
+ describe('batchConvert', function () {
+
+ beforeEach(function(){
+ _fs.mkdirSync(BATCH_DIR);
+ });
+
+ afterEach(function(){
+ purgeFolder(BATCH_DIR);
+ });
+
+
+ it('should convert all files matched by glob', function (done) {
+
+ var glob = _path.join(__dirname, 'files/**-in.js');
+
+ nodefy.batchConvert( glob, BATCH_DIR, function(err){
+ expect( err ).toBe(null);
+
+ expect( readFile(BATCH_DIR + '/basic-in.js') ).toEqual( readOut('basic') );
+ expect( readFile(BATCH_DIR + '/simplified_cjs-in.js') ).toEqual( readOut('simplified_cjs') );
+ expect( readFile(BATCH_DIR + '/named_mixed-in.js') ).toEqual( readOut('named_mixed') );
+
+ done();
+ });
+ });
+ });
+
+});
+
+
+
13 test/files/basic-in.js
@@ -0,0 +1,13 @@
+// test comment
+define(['foo', '../bar/baz'], function (foo, baz) {
+
+ // another comment
+ var ipsum = 'dolor amet';
+
+ return {
+ doFoo: function(){
+ foo.bar( baz.dolor, ipsum );
+ }
+ };
+});
+
14 test/files/basic-out.js
@@ -0,0 +1,14 @@
+// test comment
+var foo = require('foo');
+var baz = require('../bar/baz');
+
+ // another comment
+ var ipsum = 'dolor amet';
+
+ module.exports = {
+ doFoo: function(){
+ foo.bar( baz.dolor, ipsum );
+ }
+ };
+
+
15 test/files/magic-in.js
@@ -0,0 +1,15 @@
+/**
+ * multi-line comment
+ */
+define(['exports', 'foo'], function (exports, foo) {
+
+ /*
+ * another comment
+ */
+ exports.bar = foo.ipsum;
+
+ // commented out code
+ // exports.foo = foo;
+
+});
+
15 test/files/magic-out.js
@@ -0,0 +1,15 @@
+/**
+ * multi-line comment
+ */
+var foo = require('foo');
+
+ /*
+ * another comment
+ */
+ exports.bar = foo.ipsum;
+
+ // commented out code
+ // exports.foo = foo;
+
+
+
15 test/files/named_mixed-in.js
@@ -0,0 +1,15 @@
+// test comment
+define('dolor', ['foo', 'exports', 'require', '../bar/baz'], function (foo, exports, require) {
+
+ var baz = require('../bar/baz');
+
+ // another comment
+ var ipsum = 'dolor amet';
+
+ exports.doFoo = function(){
+ foo.bar( baz.dolor, ipsum );
+ };
+
+});
+
+// something after define
15 test/files/named_mixed-out.js
@@ -0,0 +1,15 @@
+// test comment
+var foo = require('foo');
+
+ var baz = require('../bar/baz');
+
+ // another comment
+ var ipsum = 'dolor amet';
+
+ exports.doFoo = function(){
+ foo.bar( baz.dolor, ipsum );
+ };
+
+
+
+// something after define
8 test/files/simplified_cjs-in.js
@@ -0,0 +1,8 @@
+define(function (require, exports, module) {
+
+ var foo = require('foo');
+
+ exports.bar = 'dolor sit amet';
+
+});
+
8 test/files/simplified_cjs-out.js
@@ -0,0 +1,8 @@
+
+
+ var foo = require('foo');
+
+ exports.bar = 'dolor sit amet';
+
+
+
29 test/helpers.js
@@ -0,0 +1,29 @@
+
+var _fs = require('fs');
+var _path = require('path');
+
+// ---
+
+
+exports.readIn = function(id){
+ return exports.readFile( __dirname +'/files/'+ id +'-in.js' );
+};
+
+
+exports.readOut = function(id){
+ return exports.readFile( __dirname +'/files/'+ id +'-out.js' );
+};
+
+
+exports.readFile = function(path){
+ return _fs.readFileSync(path).toString();
+};
+
+
+exports.purge = function(dir){
+ _fs.readdirSync(dir).forEach(function(relPath){
+ _fs.unlinkSync( _path.join(dir, relPath) );
+ });
+ _fs.rmdirSync( dir );
+};
+
36 test/parse.spec.js
@@ -0,0 +1,36 @@
+
+var nodefy = require('../index');
+var _helpers = require('./helpers');
+
+var readIn = _helpers.readIn;
+var readOut = _helpers.readOut;
+
+
+describe('parse', function () {
+
+ it('should convert standard AMD', function () {
+ var output = nodefy.parse( readIn('basic') );
+ expect( output ).toMatch( /require\(['"]\w/ );
+ expect( output ).toEqual( readOut('basic') );
+ });
+
+ it('should work properly with magic AMD dependencies', function () {
+ var output = nodefy.parse( readIn('magic') );
+ expect( output ).toMatch( /require\(['"]\w/ );
+ expect( output ).toEqual( readOut('magic') );
+ });
+
+ it('should convert simplified CJS modules', function () {
+ var output = nodefy.parse( readIn('simplified_cjs') );
+ expect( output ).toMatch( /require\(['"]\w/ );
+ expect( output ).toEqual( readOut('simplified_cjs') );
+ });
+
+ it('should convert namedmodule and ignore magical dependencies', function () {
+ var output = nodefy.parse( readIn('named_mixed') );
+ expect( output ).toMatch( /require\(['"]\w/ );
+ expect( output ).toEqual( readOut('named_mixed') );
+ });
+
+});
+
Please sign in to comment.
Something went wrong with that request. Please try again.